Skip to content

quantized_kv_start default disagrees between generate_step and the CLI #1651

Description

@Vlor999

Summary

generate_step() and speculative_generate_step() default quantized_kv_start
to 0, while the CLI (mlx_lm.generate, mlx_lm.cache_prompt) ships
DEFAULT_QUANTIZED_KV_START = 5000. So the same arguments behave differently
depending on the entry point: a library caller passing kv_bits=8 without also
passing quantized_kv_start quantizes the KV cache from the very first token,
where the same request through the CLI would not quantize until step 5000.

Measurement

mlx-community/Qwen3-0.6B-4bit, kv_bits=8, 8-token prompt, median of 3,
Apple M4 Pro (14-core), macOS 26.6:

new tokens start=0 (library) start=5000 (CLI) speed delta peak mem start=0 peak mem start=5000
64 325.4 tok/s 375.4 tok/s +15.4% 367.2 MB 367.7 MB
128 314.2 tok/s 382.3 tok/s +21.7% 367.2 MB 367.7 MB
256 336.3 tok/s 381.9 tok/s +13.6% 367.4 MB 421.7 MB
512 335.9 tok/s 368.1 tok/s +9.6% 371.2 MB 471.7 MB
1024 322.8 tok/s 341.9 tok/s +5.9% 408.2 MB 576.8 MB

This is a genuine trade-off, not a free win, and I want to be explicit about
that.
Quantizing from step 0 costs 6-22% decode throughput, but below ~256
generated tokens it saves essentially no memory (367 MB either way), and above
that it does save real memory — 169 MB at 1024 tokens.

So 0 is a defensible memory-first default in isolation. What seems wrong is
that the library and the CLI disagree while sharing a constant that documents
the intended value.

Question for maintainers

Two ways to close the gap, and the choice is a policy call that is yours:

  1. Align the library defaults to DEFAULT_QUANTIZED_KV_START — consistent
    with the CLI, faster by default, at the cost of more memory for long
    generations. This changes behaviour for existing library callers who pass
    kv_bits= and rely on the current 0.
  2. Leave the defaults and document the difference — no behaviour change,
    but the inconsistency stays and remains easy to trip over.

I have (1) implemented with tests if that is the direction you want; happy to
switch to (2) instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions