Source code for veloxquant_mlx.integration.mlx_lm_patch
"""Monkey-patches an mlx-lm model to build its KV cache via VeloxQuant-MLX.
Usage::
from mlx_lm import load
from veloxquant_mlx.integration.mlx_lm_patch import patch_model_kv_cache
from veloxquant_mlx.cache import KVCacheConfig
model, tokenizer = load("mlx-community/Meta-Llama-3.1-8B-Instruct-4bit")
config = KVCacheConfig(method="turboquant_rvq", bit_width_inlier=1, seed=42)
patch_model_kv_cache(model, config)
# mlx_lm.generate() now builds a quantized cache automatically
from mlx_lm import generate
response = generate(model, tokenizer, prompt="...", max_tokens=200)
"""
from __future__ import annotations
from typing import Any
from veloxquant_mlx.cache.base import KVCacheBuilder, KVCacheConfig
[docs]
def patch_model_kv_cache(model: Any, config: KVCacheConfig) -> list[Any]:
"""Wire a quantized KV cache into an mlx-lm model's generation path.
``mlx_lm.generate()`` builds its prompt cache via
``mlx_lm.models.cache.make_prompt_cache()``, which calls
``model.make_cache()`` if the model defines one, and falls back to a
default fp16 cache otherwise. There is no pre-existing ``.cache``
attribute on attention sub-modules to overwrite — caches are only ever
constructed lazily, on demand, through that hook.
This function builds the real per-layer cache list via
``KVCacheBuilder.for_model()`` (which already handles VLM wrappers,
MoE-gate fallback layers, per-layer bit-width lists, and cross-layer
methods like XQuant/MiniCache/PyramidKV) and overrides
``model.make_cache`` so every call builds a *fresh* cache list —
matching :func:`veloxquant_mlx.integration.mlx_vlm_patch.patch_vlm_kv_cache`.
A prior version closed over a single cache list built once at patch
time, so a second ``generate()`` call on the same patched model would
reuse the first call's leftover KV state instead of starting clean.
Args:
model: A loaded mlx_lm model instance.
config: KVCacheConfig describing the quantization scheme.
Returns:
The first freshly built cache list (also validates the config
eagerly). Subsequent ``generate()`` calls receive their own fresh
lists via the installed ``make_cache`` hook.
Raises:
QuantizerConfigError: If ``config.method`` is a standalone method
(does not implement the mlx_lm KVCache serving contract — see
``veloxquant_mlx.cache.base.STANDALONE_METHODS``). Raised
immediately by the underlying ``KVCacheBuilder.for_model()``
call, before ``model.make_cache`` is touched.
Example::
config = KVCacheConfig(
method="turboquant_rvq",
bit_width_inlier=1,
seed=42,
)
patch_model_kv_cache(model, config)
"""
def _make_cache(*_args: Any, **_kwargs: Any) -> list[Any]:
return KVCacheBuilder.for_model(model, config)
caches = _make_cache()
model.make_cache = _make_cache
print(
f"[veloxquant_mlx] Wired {len(caches)} layer cache(s) with "
f"{config.method!r} (fresh per call)."
)
return caches