Pergunta original: "É possivel implementar o turboquant aqui para eu ter contexto de 256k sem perder performance (tok/s) com o mtplx?"
Hardware alvo: MacBook Pro M5 Max 128 GB Modelo alvo: Qwen3.6-27B-4bit MTPLX Constraint declarada: ≤15% perda de tok/s aceitável em troca de +4x contexto Escopo deste passo: Análise de viabilidade — não implementar.
Este documento responde se a feature é viável com o constraint dado, qual o caminho técnico real, e o que recomendamos antes de gastar tempo de implementação.
vllm_mlx/turboquant.py:298 (TurboQuantKVCache) é codec de armazenamento, não cache ativo:
"K stays FP16. V is compressed to 3-4 bits using rotation + Lloyd-Max. This class is used in the prefix cache (store/fetch), not during model forward passes."
Codec atual: encode/decode operam no tensor inteiro (turboquant_encode em vllm_mlx/turboquant.py:163). Não suporta append incremental por token (que é o que decode precisa). A decompressão envolve 3 passos sequenciais não fundidos:
- Unpack nibbles + lookup codebook Lloyd-Max
- Reshape em grupos + denormalize (× scale + mean)
- Inverse rotation:
rotated.astype(float32) @ Qemvllm_mlx/turboquant.py:288
Integração só na borda store/fetch do prefix cache em vllm_mlx/memory_cache.py:594 (_turboquant_compress_cache) e memory_cache.py:625 (_turboquant_decompress_cache). Após fetch, cache vai dequantizado para FP16 antes do forward.
- MTPLX (
vllm_mlx/speculative/mtp_generate.py:138) recebecachedemake_prompt_cache(model)→ default FP16KVCachedo mlx-lm. - Flag
--kv-cache-quantization(vllm_mlx/scheduler.py:83,cli.py:434) ativa quantização padrão do mlx-lm (4 ou 8 bit, fused) vialayer.to_quantized()— esse caminho é fundido commx.fast.scaled_dot_product_attentione tem custo de dequant ≈ zero porque o kernel Metal trata índices quantizados direto. - Flag
--kv-cache-turboquant(cli.py:401) é mutuamente exclusiva e hoje só liga a compressão no prefix cache.
vllm_mlx/attention.py é um shim — atenção verdadeira é resolvida pelo mlx-lm interno via mx.fast.scaled_dot_product_attention (linha 229 do shim, mas o caminho que importa é dentro do mlx-lm). Não há kernel customizado neste repo.
Estimativas para arquitetura típica Qwen3 27B (64 layers, 8 KV heads GQA, head_dim 128):
KV/token/layer = 2(K+V) × 8 × 128 × 2 bytes = 4 096 bytes/token/layer KV total a 256k × 64 layers = 67.1 GB em FP16
| Cenário | KV size | Total memória (peso 4-bit + KV + overhead) | Cabe em 128 GB? |
|---|---|---|---|
| FP16 KV, 256k | 67 GB | ~84 GB | Tight, mas cabe — pouco buffer |
mlx-lm QuantizedKVCache 4-bit, 256k |
~17 GB | ~33 GB | Sim, com folga |
| TurboQuant V-only 3-bit + K FP16, 256k | ~46 GB | ~62 GB | Sim |
| TurboQuant K+V 3-bit (hipotético, código não existe), 256k | ~25 GB | ~41 GB | Sim |
Ponto importante: memória não é o gargalo decisivo em 128 GB — mlx-lm 4-bit já resolve isso. O gargalo real em 256k é bandwidth de leitura do KV durante decode.
Decode em contextos longos é memory-bandwidth-bound: cada step lê o KV inteiro de todas as layers. M5 Max tem ~410-540 GB/s de bandwidth unificada (estimativa baseada em M4 Max).
Com MTPLX verificando 2 tokens por step, o custo de leitura é o mesmo (cache lido uma vez, atendendo Q de comprimento 2). Não dobra.
| Cenário | Bytes lidos/step | Step rate teórico (bandwidth-bound) | tok/s estimado (com MTP accept ~50%) |
|---|---|---|---|
| FP16 KV | 67 GB | ~6-8/s | ~9-12 tok/s |
| mlx-lm 4-bit fused | 17 GB | ~24-32/s | ~36-48 tok/s |
| TurboQuant V-only ativo (sem kernel fundido) | 46 GB lidos + dequant compute overhead | ~9-13/s | ~13-19 tok/s |
| TurboQuant V-only ativo (com kernel fundido — não existe) | 46 GB lidos | ~9-13/s | ~13-19 tok/s |
| TurboQuant K+V 3-bit ativo (com kernel fundido — não existe) | 25 GB | ~16-22/s | ~24-33 tok/s |
Baseline atual Qwen3.6-27B em contextos curtos: 31.8 tok/s sem MTPLX (README.md:14), até ~40 tok/s com MTPLX. Mas isso é com KV pequeno — em 256k a story muda totalmente.
Conclusão de performance:
- mlx-lm 4-bit fused é provavelmente o ganhador inclusive em tok/s a 256k, porque corta bandwidth em 4x e usa kernel já fundido pelo Metal.
- TurboQuant ativo sem kernel customizado cai abaixo da meta de "≤15% perda" — o overhead de dequant (rotation matmul em FP32 + lookup + denorm) executado por step custa caro.
- TurboQuant ativo fica competitivo apenas com kernel Metal fundido (semanas de trabalho) ou com fold de Q em W_v/W_o (mudança per-modelo, frágil).
Caminho recomendado (ordem):
- Ligar
--kv-cache-quantization 4+--kv-cache-quantization-group-size 64primeiro. Já existe. Já é fundido pelo mlx-lm. Memória cai 4x. Tok/s a 256k provavelmente melhora vs FP16 por causa de bandwidth. - Manter TurboQuant V-only no prefix cache (já está). Beneficia reuso de prompts longos, sem custo no hot path.
- Medir com bench real antes de qualquer trabalho novo —
make benchmarkouscripts/benchmark_realworld.py. Sem números, especulação não vale.
Apenas se:
- Bench em (1) mostrar que mlx-lm 4-bit degrada qualidade do 27B-MTPLX em tool-calling/reasoning de forma mensurável (p.ex. eval scores caem >2 pontos), E
- A diferença justificar 1-2 semanas de trabalho de kernel + integração.
A vantagem de qualidade do TurboQuant é real (~0.5-1 PPL melhor que quant uniforme em mesmos bits) mas pequena. Para tool-use agentic, mlx-lm 4-bit usualmente é suficiente.
Trabalho mínimo para chegar perto de "≤15% perda":
- Append incremental: estender
TurboQuantKVCachecom métodoupdate_and_fetch(keys, values)que quantiza só os tokens novos e concatena com indices/scales/zeros existentes. Hojefrom_kv_cachereencoda tudo. - Lazy dequant ativo: implementar interface compatível com
mlx_lm.models.cache.KVCache(state, offset, trim, is_trimmable, fetch) que retorna V dequantizado on-demand a cada attention call. - Pré-rotação de W_v + fold em W_o: modificar load do modelo para fundir Q (matriz de rotação) nos pesos. Elimina o matmul de rotação no decode. Isso é per-modelo e frágil — quebra com qualquer mudança de arquitetura.
- Kernel Metal fundido (opcional, dá os ganhos reais):
quantized_attention_lloyd_max(Q, K, V_codes, V_scales, V_zeros, codebook)em Metal puro. Esse é o item que faz a diferença entre 13 tok/s e 40 tok/s a 256k.
Sem (3) e (4), a perda real será muito maior que 15%.
vllm_mlx/turboquant.py— codec atual, encode/decode whole-tensorvllm_mlx/memory_cache.py:594— wrapping no prefix cache (cold)vllm_mlx/scheduler.py:83-85,1693-1695— flags KV quantvllm_mlx/cli.py:387-441— CLI de KV quantvllm_mlx/speculative/mtp_generate.py:97-138— loop MTPLX, recebe cache prontovllm_mlx/attention.py:229— shim de atenção (atenção real é mlx-lm)mlx_lm.models.cache.KVCache/QuantizedKVCache— interface do hot cache (externa ao repo)scripts/benchmark_realworld.py,evals/run_eval.py,make benchmark— infra de medição
-
Antes de qualquer código novo: rodar bench Qwen3.6-27B-MTPLX em prompt 32k-256k com:
- FP16 KV (baseline, se couber)
--kv-cache-quantization 4 --kv-cache-quantization-group-size 64
Métricas: tok/s decode, eval tool-use, eval reasoning, RAM peak.
-
Se mlx-lm 4-bit atender quality bar → encerrar. TurboQuant ativo não vale o custo.
-
Se não atender → planejar TurboQuant ativo com kernel Metal como projeto de 2-3 sprints, não feature de uma sessão.
- Ler benchmarks históricos:
harness/scorecard/latest.md,evals/results/para ver se algum run a 32k+ já existe com KV quant. - Confirmar specs Qwen3.6-27B exatas no manifest do modelo (
mtplx_runtime.json) — número de layers e head_dim exatos podem mover a matemática de memória ±10 GB. - Validar bandwidth real do M5 Max com microbench antes de confiar nas estimativas teóricas (M5 Max ainda é hardware recente, dados públicos podem estar imprecisos).