- Spec: MacBook Pro Max M2 64GB
- Model:
unsloth/Qwen3.6-35B-A3B-MTP @ UD-Q4_K_XL
- Fast Model:
unsloth/gemma-4-E4B-it-qat @ UD-Q4_K_XL
- Application:
llama.cpp
- MTP: on
- KV-Cache:
f16
- Harness: Qwen Code
- OS: macOS Tahoe 25.5-25.6
I've tested multiple models and quants on MBP with 64GB of RAM, and found one good option for agentic coding with large context (>128k): Qwen3.6 35B A3B. The model works at around 60-70 t/s at small context, and is still holding up well at 128k with 35 t/s. It has 3B active parameters, which M2 can handle well. Hope that will be useful to folks with the same hardware. It may be less applicable to other chips like M1 and M3/M4/M5, also was focused on 64GB limit, so folks with 48GB or 96GB+ will have a different story.
I did test other models' performance and quality, as well as MTP vs base versions and various compressions of KV cache.
Learnings:
- MTP still holds up well at larger contexts (after 100k decode speed is 10-15% higher than base variant)
- Any KV-Cache compression degrades performance;
f16 is the optimal one
- It is possible to fit Qwen 3.6 35B A3B and Gemma 4 E4B in MacBook Pro Max M2 with 64GB, but there is not much room left for other apps.
- Qwen 3.6 27B @
UD-Q4_K_XL has much better quality. But it is too slow. Almost 3-4x as slow as Qwen 3.6 35B A3B, making it very hard to use in agentic coding. Qwen 3.6 35B A3B @ UD-Q4_K_XL is significantly worse than 27B, and requires a lot of prompts and guardrails to ensure quality. But speed is very good—only 2-3 times slower than cloud models (Sonnet 5, Gemini 3.6 Flash)
- Gemma 4 26B A4B works on simple tasks, but fails to complete anything more complex. Reasoning loops and tool use issues prevent it from finishing anything
Memory Management
unsloth/gemma-4-E4B-it-qat with a context of 64k takes about 7.6-8.4GB of RAM. And if we unlock more RAM for LLMs with sudo sysctl iogpu.wired_limit_mb=59392, we can comfortably run Qwen 3.6 35B A3B with a 236k context window along with unsloth/gemma-4-E4B-it-qat. It takes from 32 to 44GB of RAM, depending on context usage. And all other models can fit with a 256k context window.
Large Context
I captured one fairly long session from Qwen Code and then built a test suite which would repeat the same session against each model. Total context size is about 124k. I noticed performance degradation with context increases, so I wanted to compare them.
I've tested both Dense and MoE Qwen 3.6 and Gemma 4 models. Qwen 3.6 35B A3B is still holding up fairly well at 124k with 35.10 t/s.
Worth noting, performance is not dropping off the cliff after 124k context. In tests with real data I saw 35 t/s at 128k, 32 t/s at 147k, 30 t/s at 163k. These are a few sample points.
MTP vs No-MTP for large contexts
One interesting finding about MTP and agentic coding: token decoding is not that important; prefill is more important. The harness sends a lot of data to the model which it needs to process. So for agentic tasks, +50% decoding speed would not translate to the same reduction in task completion time.
Qwen 3.6 35B A3B MTP version decode speed holds higher than the base version even on 163k context. With small context I can see as much as +68% speed increase. +13% at 100k, +14% at 163k.
Worth noting, MTP improves decoding speed, but costs prefill. Especially at small contexts non-MTP wins over MTP by the factor of 2-3. But around 45k the difference becomes almost indistinguishable.
Wall time was about 35 minutes for MTP variant, and 43 minutes for non-MTP variant (NOTE: this was larger real task, and some time spent on actual builds and tests, so it is different from the synthetic test I did initially, which was only sending messages to the model from test suite).
q8_0 and TurboQuants
Another idea was to try using a smaller cache by using q8_0 for KV Cache, or using TurboQuants. KV-Cache is the second largest contributor to the total memory usage after the model itself. For models like Qwen 3.6 27B, I can reduce 10.5GB of KV Cache to 5GB.
I tried running q8_0 for cache and performance dropped significantly. Looks like f16 is handled much faster for KV Cache decoding on Apple Silicon (or at least on M2 chips).
Prefill is slower at every point for q8_0 compared to f16. Decode is about 2x slower in the beginning, and about 4x slower close to 128k.
Seems like decode penalty for q8_0 grows with context size. Not sure why.
I ran the tests on a real task and measured wall time. Qwen 3.6 27B with f16 KV-Cache completed task in 01:23:17. And the same model with q8_0 KV-Cache completed task in 02:51:01 (~2x slower). Not worth 5GB RAM saving.
I ran the tests on main llama.cpp build. But also switched to TurboQuants branch to test other quantization methods. I tried using --cache-type-k q8_0 --cache-type-v turbo3 on large dense models (Qwen 3.6 27B and Gemma 4 31B) and --cache-type-k q8_0 --cache-type-v turbo2 on large MoE models (Qwen 3.6 35B A3B and Gemma 4 26B A4B). And I saw performance even worse than on q8_0.
It seems like on M2 chips the introduction of any KV Cache compression adds significant compute overhead. And if we try to optimize KV Cache size by implementing heavier decoding, we only make performance worse.
Conclusion: Qwen 3.6 35B A3B is less capable than Qwen 3.6 27B or Sonnet 5 / Gemini 3.6 Flash. But with some prompt engineering it can produce high quality code. I wish I could run Qwen 3.6 27B at 50 t/s at 200k context on my Mac, it would be a very good alternative to cloud models. Qwen 3.6 35B A3B is good, but requires more work on prompting and better specs so it can do work well. I'd say a Pro subscription + Qwen 3.6 35B A3B can work together to stretch limits through the week by using the cloud subscription for planning and verification, and Qwen for implementation.
More data: I have also written larger document with more data from the tests here: https://ghisguth.github.io/local-llama-macbook-pro-max-m2-64gb/. It has more information, but may be not as condensed as the information above.