veloxquant_mlx.cache.svdq_cache module¶
SVDq KV cache wrapper — sub-2-bit key compression via offline SVD.
Inspired by “SVDq: Singular Value Decomposition-based KV Cache Quantization”
(arXiv:2502.15304, Feb 2025, unreviewed preprint). Documented as
“SVDq-adapted (VeloxQuant-MLX implementation)” — not a faithful port. See
quantizers/svdq.py module docstring for the full list of remaining
deviations (per-sequence SVD instead of offline calibration, invented rank
heuristic, no model-level accuracy evidence).
Design:
- Prefill (first call where S > 1):
Compute truncated SVD of the incoming key batch K ∈ R^{S×D}.
Store the right singular vectors V [D, r] and mean key K̄ [D] as layer state. These are O(D²) and negligible for long sequences.
Project keys into latent space L = (K - K̄) @ V → [S, r].
Apply mixed-precision group quantization to L using the paper’s 8-group fixed bit schedule (Eq. 6): the r latent channels are split into
len(bit_schedule)equal-size contiguous groups (largest singular values first), each quantized at its own bit width — 0 bits truncates a group to exactly zero.Reconstruct fp16 keys for the downstream SDPA call.
Accumulate latents in a growing list (quantized-then-dequantized, so the downstream cache sees fp16, consistent with all other wrappers).
- Decode (S == 1 or S < prefill threshold):
Project new key into the already-stored V space.
Quantize and reconstruct fp16.
Pass through to the underlying mlx_lm KVCache.
Values are left at fp16 throughout (the paper notes values have weak low-rank structure; compressing values is left to stacked wrappers).
- Byte accounting:
compressed_key_bytes — latent storage at the schedule’s per-group bit rate fp16_key_bytes — what full fp16 would cost (for ratio computation) value_fp16_bytes — values are always fp16 (reported separately)
Effective key bit-width ≈ (r/D) * b̄, where b̄ is the schedule’s mean bit-width (paper Eq. 6: b̄ = mean(bit_schedule)). For the default schedule (8,4,2,1,1,0,0,0), b̄ = 2. At r = 0.5D: effective ≈ 0.5 * 2 = 1.0 bit/element.
- class veloxquant_mlx.cache.svdq_cache.SVDqKVCache(config)[source]¶
Bases:
KVCacheKV cache implementing SVDq sub-2-bit key compression.
- Parameters:
config (
Any) –KVCacheConfig. Fields consumed:head_dim(D),svdq_rank(int | None — explicit rank; None → energy threshold),svdq_energy_threshold(float, default 0.95),svdq_bit_schedule(Sequence[int], default (8,4,2,1,1,0,0,0) —paper Eq. 6’s 8-group per-group bit widths, most-significant group first; a 0 truncates that group to exactly zero),
svdq_group_size(int, default 32 — quantization group sizealong the token axis, independent of the latent-channel grouping above).
Notes
Does not expose
.bits— mlx_lm’s SDPA checkshasattr(cache, "bits")to route to a quantized kernel; we keep that path clean. Exposes.assigned_avg_bitswith the effective key bit-width.- update_and_fetch(keys, values)[source]¶
Fit per-head SVD on prefill (or project into the existing basis on decode), mixed-bit quantize the latents; values pass through fp16 unchanged.
- Parameters:
keys (mlx.core.array)
values (mlx.core.array)
- property compressed_key_bytes: int¶
Realized stored bytes for the compressed key cache (per-head mixed-bit latent codes + amortized projection basis, all heads/batches).
- property value_fp16_bytes: int¶
Actual value cost — values are stored fp16 throughout (key-only method).
- property assigned_avg_bits: float¶
schedule’s mean bit-width scaled by r/D, averaged across heads (each head can land at its own rank/schedule under energy-threshold auto-rank — see _run_prefill_svd).
- Type:
Effective key bit-width
- property rank: int¶
Actual SVD rank used after energy-threshold selection, averaged across heads (rounded down) — see assigned_avg_bits for why heads can differ. Use the per-head ranks directly (e.g. via a subclass instance’s internal state) if the per-head breakdown matters.
- property merge¶