Weight Quantization and Memory Residency
Overview
Since the engine-adoption arc (issues #338โ#346, engine 0.50/0.51 line), this
repository owns no quantization machinery of its own. Every family’s weight
loader is a thin wrapper over the SKaiNET engine’s
StreamingGgufParametersLoader, and what a weight looks like in memory is a
declared, resolved form โ not the emergent result of per-family converter
code. The historical multi-stage pipeline this page used to describe
(MemSegWeightConverter, QuantPolicy, pre-transposed FP32 copies, hand-managed
`Arena`s) is gone.
The form is the contract: WeightForm
A loader states what it wants per tensor; the engine resolves and delivers it โ
or visibly refuses. WeightForm has four axes:
| Axis | Values | What it decides |
|---|---|---|
|
|
What the bytes encode once loaded. Keep-as-stored costs nothing; a dequantization multiplies the tensor (~8ร for Q4_K โ FP32) and is priced in the memory plan before it is paid. |
|
|
Which order the packed blocks run in. Canonical GGUF is row-major blocks;
|
|
|
Which way round the dimensions are labelled. Every loader here asks |
|
|
Where the bytes live. |
The default every decoder-family loader requests:
WeightForm(shape = WeightShapeOrientation.OUT_IN, residency = WeightResidency.MAPPED)
A request the platform or encoding cannot honour is not an error โ the engine heap-stages that tensor and the memory plan says so. The precedence is always: caller override > resolver > file.
MAPPED residency: what it buys
A mapped weight is mmap-backed page cache: resident in RSS while hot, evictable
under pressure, and outside the JVM/ART heap cap. Measured consequences:
-
qwen2.5-1.5B (1.0 GB Q4_K_M) runs under
-Xmx512mon the JVM โ the plan charges ~176 MB of heap (KV cache + forward slab + headroom); the weights page against device RAM. -
On Android (engine 0.50 measurements, Pixel 8a): the same model loads in ~445 ms with ~566 KB of heap allocated for weights, and decodes with zero steady-state page faults.
The zero-copy part is the kernel story, not just the storage story: the
FfmRowMajorKernelPack (JVM) and JniMappedKernelPack (Android) register
matmul kernels under BLOCKED_ROW_MAJOR dispatch keys that read canonical
packed blocks where they lie โ mapped buffer or un-prepacked heap array โ
so nothing is copied or dequantized on the hot path. Installing them is two
lines at startup (the unified CLI does exactly this):
KernelPacks.install() // reference + best provider tiers
FfmRowMajorKernelPack.install() // zero-copy packed serving (JVM); JniMappedKernelPack on Android
Without the pack, dispatch falls back to the decoding reference kernel โ correct for every format, visibly slower, and (since engine 0.50) emitted as a trace event rather than silently absorbed.
The token embedding: row-dequant, not force-dequant
Embedding gathers rows; matmul kernels never see it. A packed embedding table
must not be read through per-element get() โ for packed tensor data that
returns the raw quantization code, not the value (an engine source-compat
quirk, and the root of the historical #993 class of bugs). Instead the decoder
loader rewraps a packed delivery as PackedRowDequantTensorData
(transformer-core): a RowDequantSource that dequantizes exactly the rows a
step looks up via PackedBlockStorage.dequantizeBlock.
This keeps a 152k ร 1536 vocabulary at its ~131 MB packed footprint instead of
a ~933 MB dense FP32 heap array โ and because the wrapper forwards
PackedBlockStorage and the underlying view, a tied output.weight that
aliases the embedding still routes matmulWeightTransposed through the packed
kernel chain. (Flipping SmolLM2’s tied lm_head from dense-transpose to the
packed kernel took the CLI smoke from ~3.6 to ~50 tok/s.)
The matmul seam: one expression
Every transformer DSL module projects through linearProject
(transformer-core), whose entire body is:
ops.matmulWeightTransposed(input, weight)
The engine primitive serves x ยท Wแต for a [out, in] weight without
materializing a transpose: mapped packed โ zero-copy row-major kernel; heap
packed โ relayout-once cache + packed kernels; dense โ the standard transpose
path. Dispatch selects by the weight’s storage format, never by model family.
The memory plan: priced before loaded
MemoryPlans.plan prices weights (split mapped vs heap-charged), KV cache,
forward slab, and headroom from the GGUF header โ before a byte of payload is
read. The unified CLI prints it on every run and warns when the plan exceeds
the heap cap; --explain-load additionally prints one line per weight saying
where it lands and why:
Smollm2 135M ยท llama ยท 30 layers ยท ctx 2048 weights mapped, as stored 98 MB mapped (page cache, evictable โ not heap) kv cache bf16 @ ctx 2048 45 MB resident (13 MB with TurboQuant 4-bit) forward prefill chunk 256 24 MB heap headroom 64 MB total heap 133 MB of 512 MB โ fits
Per-step activations: the forward scope
Weights are the big number but not the only one. OptimizedLLMRuntime runs
DIRECT decode inside an engine ForwardScope (#343): step activations
bump-allocate from one pre-sized slab and are recycled at the step boundary, so
steady-state decode allocates zero new heap bytes per token. The KV caches copy
their kept history out to ambient storage (detachFromStep); a caller holding a
logits tensor across steps fails loudly (StorageClosedException), never
silently. forwardScopeMetrics exposes peakFloats / overflowBytes for
plan-vs-actual checks โ ForwardScopeSteadyStateTest pins bit-identity with the
unscoped path and a flat, overflow-free slab.
Format support
All seven GGML block formats the engine loader delivers โ Q4_0, Q5_0, Q5_1,
Q8_0, Q4_K, Q5_K, Q6_K โ flow through the packed dispatch chain and are pinned
against the engine’s canonical dequant by
LinearProjectionPackedParityMatrixTest (W4A8 tolerance: packed kernels may
quantize activations to int8 per block, so parity is an abs-floor/relative
band, not bitwise equality). Ternary BitNet I2_S loads as packed
BITNET_B1_58 (0.25 B/weight) with a BITNET_PLANES lm_head โ see the BitNet
module. Formats outside the loader’s fail-fast set (Q2_K, Q3_K, IQ-series)
are rejected at load rather than silently mishandled.