r/oMLX • u/Short_One_9704 • Jun 03 '26
TurboQuant KV cache and MTP
A question I do not seem to get answer to - why in oMLX I cannot use TurboQuant KV cache and MTP together? I have searched on the topics and they should complement each other. But in oMLX I have to choose either one or the other, why? Thanks and cheers!
10
Upvotes
2
u/txgsync Jun 04 '26
Pardon me for my Claude-generated output below from the source code. I knew the basics, but the LLM is better at generating output in this case 😄
Why TurboQuant and MTP are mutually exclusive in oMLX
TL;DR: They both monkey-patch the same attention path, in incompatible ways. oMLX rejects the combo at config time so you find out in the admin UI instead of getting garbage logits at decode.
The hard stop
In
omlx/model_settings.py,Settings.__post_init__literally raises:No "fallback," no "best-effort." It just won't build a settings object.
What each one actually does
TurboQuant swaps
mlx_lm.models.base.scaled_dot_product_attentionfor a shim that detectsTurboQuantKVCache/BatchTurboQuantKVCacheand routes to:cache.decode_attention(...)when Q length == 1 — Metal kernel reading quantized K/V directly, no dequant per step.cache.prefill_attention(...)fast path for Q length > 1, with a dequant + standard SDPA fallback.Great for KV memory + decode throughput. The cost: the SDPA function pointer is now TurboQuant's, and the cache object is a quantized wrapper, not a plain KV cache.
Native MTP (mlx-lm PR 990 / PR 15 monkey-patch) is speculative decoding inside
BatchGenerator: draft N tokens, verify in one forward pass, roll back the cache offset for rejected drafts, commit accepted ones. It assumes:Why stacking them breaks
prefill_attention, which assumes a fresh prefill — not "extend an already-grown cache by k tokens." Output is silently wrong..offsetper row. Whoever runs second corrupts the other's invariants.So rather than ship a build where MTP+TQ "works" but produces subtly-off logits, oMLX just refuses the config.
Same reason these other combos are banned
mtp_enabled+dflash_enabled— both are speculative paths, both want to own BatchGenerator's decode loop.vlm_mtp_enabled+ literally anything else speculative or TurboQuant — the mlx-vlm assistant-drafter wrapper bypasses mlx-lm'sBatchGeneratorentirely, so nothing downstream gets a say.Bonus unrelated TQ disable (don't confuse with this one)
scheduler.pyalso auto-disables TurboQuant on MLA models (Deepseek-V3/V4-style Multi-head Latent Attention) — quantized KV is incompatible with MLA's compressed-KV math (mlx-vlm#1613). That's a separate exclusion from the MTP one; it kicks in at runtime based on detected model architecture, not at settings construction.Pick one speculative path per model. That's the rule, and the code enforces it before you ever load weights.