Allocating Quantization Precision Under a Memory Budget
How static allocations, Greedy-KL, and EvoPress distribute weight precision to fit an edge deployment budget, and how calibration and downstream evaluation check the resulting quality tradeoff.
What it is
- Precision allocation Store different weight tensors with different amounts of numerical detail. Save memory where rounding is less damaging, and preserve more detail where it matters.
- Calibration-guided search Try precision assignments on representative examples and measure how much the model’s predictions change. Use that signal to choose an assignment that fits the weight-memory budget.
Why it matters
Making a model smaller is not enough: it still needs to work well. Precision allocation makes the memory-versus-quality decision explicit, while downstream evaluations check whether a promising calibration score translates into useful behavior.
Here is how I think about this deployment problem: the model needs smaller weights, but not every weight tensor needs the same amount of numerical detail. Instead of turning one precision knob for the entire model, we can decide where the limited memory is worth spending.
-
Capacity: The language-model weight target is below 20 GB. Quantization stores weights in a more compact numerical representation; mixed precision gives different tensors different representations. The concrete example is Q4KM: it uses a lower-precision baseline while retaining higher precision for selected projections. The format names Q4_K, Q5_K, and Q6_K identify different K-quant precision choices, not complete descriptions of storage overhead.
-
Sensitivity: Some reductions change predictions more than others. Consider the output projection, which turns internal features into token scores: this Q4KM variant stores it as Q5_K rather than Q6_K. That is a specific allocation decision, not a rule that every output projection should use that format.
-
Validation: A compact model still has to perform useful work. Held-out text and code provide a screening signal, followed by reasoning and agentic evaluations. I would not treat a small validation-loss change as a substitute for testing the actual application.
So how do we decide which tensor gets the next byte?
Buying memory savings cheaply
Greedy-KL starts with Q6_K weights and considers lowering individual tensors’ precision. For each candidate, it compares the extra prediction disagreement with the bytes saved. KL divergence is the disagreement measure: for example, reducing a tensor’s precision can move probability away from tokens the BF16 reference considered likely. BF16 is the higher-precision reference representation.
Think of this as buying memory savings at different prices. Greedy-KL takes reductions with low disagreement per byte saved, then uses remaining capacity to upgrade the most sensitive Q4_K tensors to Q5_K. Unlike literal prices, those costs can depend on the other precision choices. What if a locally attractive sequence misses a better overall allocation?
Searching whole assignments
EvoPress addresses that question by evolving complete precision assignments under a memory constraint, using disagreement with BF16 as its objective. Its choices are Q4_K, Q5_K, and Q6_K.
The concrete unit matters: query, key, value, and output-gate slices of a fused attention projection can receive separate choices. A fused storage layout therefore does not force one precision for all its roles. Across the exposed projections and embeddings, the search has 418 weight units, with no role required to stay at high precision. But how do we decide whether the winning assignment is actually preferable?
Checking the deployment choice
Candidate screening uses the increase in negative log-likelihood relative to BF16: how much worse the model scores the observed next tokens. This is related to, but distinct from, matching the reference model’s full prediction distribution during search.
EvoPress has the lowest validation loss in the reported comparisons; Q4KM has the smallest footprint. They are selected for 32 GB and 24 GB VRAM deployments respectively. Weight size is only part of device memory, so I would keep those budgets separate. The remaining question is whether the chosen profile preserves the behavior your workload needs—not merely whether its weights fit.
Read the deep dive
Why People Wanted It
A local model has to fit before its capabilities are useful. The weight-size target here is below 20 GB, but reducing precision indiscriminately can change the predictions that reasoning and agentic tasks depend on.
So the real question was never just “how few bits can we use?” It was “which tensors can lose precision cheaply, and how do we check the resulting model?” This module concerns weight allocation; block drafting and verification are a separate decoding mechanism.
The Core Move
Treat each tensor’s storage format as a decision variable, then minimize prediction disagreement subject to a weight-memory budget.
Let be the format assigned to weight unit , its stored size, and the weight budget. The allocation problem can be written schematically as
Here measures disagreement between the quantized model and the BF16 reference on calibration examples. BF16 is the reference floating-point representation; Q4_K, Q5_K, and Q6_K are the candidate K-quant formats. Stored size includes representation overhead, not just a nominal bit width multiplied by the number of weights.
Why Greedy-KL Spends Bits Where They Matter
Here is the deeper machinery: Greedy-KL turns the constrained allocation into a sequence of local decisions, starting from Q6_K and ranking precision reductions by calibration KL increase per byte saved.
For a candidate reduction of unit , define
The candidate assignment differs from the current assignment at that unit. A reduction saves memory when . Its local cost is
A smaller ratio means less measured prediction damage for each byte recovered. After reducing precision, remaining capacity is used to upgrade the most sensitive Q4_K tensors to Q5_K.
KL divergence compares probability distributions, rather than just whether the highest-scoring token changes. For intuition, one conventional direction is
where is the reference distribution and the quantized distribution. This illustrates the disagreement measure; the ratio-based allocation rule does not require us to assume a particular implementation of the calibration aggregation.
The useful granularity is smaller than a fused tensor. Query, key, value, and output-gate slices are exposed independently, as are feed-forward gate and up projections. Along with attention output, feed-forward down, token embedding, and output projection, these form 418 weight units. No tensor role is fixed at high precision.
This is a local heuristic, not a guarantee of the best global assignment. Can a search over complete assignments make a better use of the same budget?
Why EvoPress Searches Whole Assignments
Now the version for readers who want the search-level distinction: EvoPress evolves precision assignments under a memory constraint, using KL divergence from BF16 as its objective rather than committing only to a sequence of locally ranked reductions.
Its search space uses the same Q4_K, Q5_K, and Q6_K choices. The selected profile assigns 51, 130, and 237 units to those formats respectively, all with group size 32. A group shares quantization metadata; changing group size therefore changes more than the precision label alone.
The selected assignment places the token embedding at Q4_K and the output projection at Q5_K, totaling 19.65 GB of model weights. That is an allocation result, not an instruction to always quantize embeddings more aggressively than outputs.
A static schedule offers a different engineering tradeoff. Q4KM follows llama.cpp’s subset-of-layers upcasting pattern for value, feed-forward down, and output projections. This variant uses Q5_K for the output projection and group size 128 for its Q6_K tensors and Q5_K output projection. Those choices accommodate the deployment representation and reduce scale overhead.
A searched assignment can spend memory according to calibration sensitivity; a static assignment avoids that search. But which prediction metric should decide between them?
Why Calibration Needs a Downstream Check
Here is the technical distinction behind evaluation: matching a reference distribution and assigning probability to observed tokens are related objectives, but they are not identical.
For held-out next-token examples , negative log-likelihood is
Unlike KL-based search, this score looks at the probability of the observed token, not disagreement across the entire reference distribution. Held-out text and code select candidates for downstream reasoning and agentic evaluations.
EvoPress has the lowest validation loss in the reported comparisons, while Q4KM has the smallest footprint. They are selected for 32 GB and 24 GB VRAM deployments respectively. The selected EvoPress weight size is not a claim about total runtime memory: weights and device capacity are different quantities.
The downstream comparison covers 15 tasks. Average accuracy is 75.7% for BF16, 75.6% for EvoPress, 75.3% for Greedy-KL, and 74.8% for Q4KM. These aggregate results check the allocation choices without making them interchangeable for every workload. What matters next is the behavior of the chosen assignment on the intended application.
Minimal Causal Code
This plain PyTorch-oriented loop makes the allocation state visible. It is an illustrative budget allocator, not a reproduction of search scheduling or quantization kernels. The supplied measurement functions must actually quantize and evaluate each candidate; format labels alone do not determine byte size or quality.
import torch
@torch.no_grad()
def allocate(n_units, budget_bytes, calibration_kl, stored_bytes):
# 0 = Q4_K, 1 = Q5_K, 2 = Q6_K.
# calibration_kl(profile) evaluates causal next-token
# distributions against BF16 and returns a scalar tensor.
# stored_bytes(profile) includes quantization metadata.
profile = torch.full((n_units,), 2, dtype=torch.long)
loss = calibration_kl(profile)
size = stored_bytes(profile)
while size > budget_bytes:
best = None
for i in range(n_units):
if profile[i].item() == 0:
continue
trial = profile.clone()
trial[i] -= 1
trial_size = stored_bytes(trial)
saved = size - trial_size
if saved <= 0:
continue
trial_loss = calibration_kl(trial)
ratio = (trial_loss - loss).item() / saved
if best is None or ratio < best[0]:
best = (ratio, trial, trial_loss, trial_size)
if best is None:
raise ValueError("No remaining reduction can meet budget")
_, profile, loss, size = best # Commit the reduction.
while True:
best = None
for i in range(n_units):
if profile[i].item() != 0:
continue
trial = profile.clone()
trial[i] = 1 # Spend spare capacity on Q4_K -> Q5_K.
trial_size = stored_bytes(trial)
if trial_size > budget_bytes:
continue
trial_loss = calibration_kl(trial)
benefit = (loss - trial_loss).item()
if benefit > 0 and (best is None or benefit > best[0]):
best = (benefit, trial, trial_loss, trial_size)
if best is None:
break
_, profile, loss, size = best # Commit the upgrade.
return profile
Candidate evaluations here recompute calibration scores for clarity. The visible state transition is the important part: change one unit’s format, measure the cost, and commit only the selected assignment.
What You Gain, and What You Lose
Mixed precision turns weight compression into an explicit allocation problem. It can preserve more detail where measured sensitivity is high, rather than requiring every tensor to use the same format.
The cost is calibration, search, format support, and deployment bookkeeping. A KL objective is a proxy for behavior, not a guarantee of reasoning quality. Static Q4KM and searched EvoPress also occupy different memory-quality points: the smallest profile is not the one with the lowest validation loss.
Finally, fitting the weights does not by itself establish generation latency or total runtime memory. Quantization allocation answers where to spend storage precision; speculative decoding answers a separate question about proposing and verifying future tokens.
Allocate precision by measured sensitivity under a weight budget, then validate the chosen profile on downstream behavior—not just calibration loss.