Ternary Weight Checkpoint Compatibility: Audit Before You Build

Ternary weight checkpoint compatibility with bf16 tooling has to be tested. An audit spike checks code fidelity, scale layout and metadata before any…

Ternary Weight Checkpoint Compatibility: Audit Before You Build
Written by TechnoLynx Published on 01 Nov 2026

You can’t assume ternary weight checkpoint compatibility with an existing bf16 pipeline. A checkpoint can load without a single error and still carry flipped ternary codes, a scale layout the runtime can’t read, or metadata that never made it into the file. In the labs’ documented pipelines for BitNet b1.58, Falcon-E and BitCPM, export first casts latent weights to bf16, and the deployed codes disagree with fp32 quantisation on a measurable share. The order that holds up is a short audit spike first, testing round-trip fidelity, scale layout and tracker metadata. New serialisation logic comes only where the spike finds a real defect.

TechnoLynx scopes and runs that audit spike. TechnoLynx runs a short ternary training job through your checkpoint format, experiment tracker, custom CUDA kernels, optimiser and distributed stack, checks checkpoint round-trip fidelity, documents every conversion bug, and builds an adapted or new serialisation path only if the existing one fails. The first step is scoping the spike across the tooling that has to stay compatible.

Will our existing checkpoint format handle ternary tensors without conversion bugs?

Not reliably. When latent weights are cast to bf16 before ternarisation, some weights end up with a different code. An audit of released checkpoints from three labs (arXiv 2610.07853) checked the shipped latents with fp32 quantisation. Those codes disagreed with the deployed ones on 0.83-1.77% of codes in Falcon-E and BitCPM, and on 1.530% in BitNet 2B-4T. In Falcon-E and BitCPM, most of the disagreements come from products that bf16 rounding lands exactly on the quantisation threshold, which ties-to-even rounding then maps to zero.

The mechanism comes from the format itself. bf16 keeps the 8-bit exponent of fp32 but only 7 mantissa bits against fp32’s 23, so it holds the same range at much lower precision (oneAPI specification). A value that sat just on one side of the ternary threshold in fp32 can end up on the other side after the cast.

The cost isn’t cosmetic. The same paper used low learning rates chosen to match a nominal learning-rate-to-bf16-ULP ratio. At those fine-tuned endpoints, the documented export then cut greedy GSM8K strict accuracy from 58.79% to 0.78% for Falcon-E-1B-Base, and a bf16 save and reload cut BitNet 2B-4T’s strict accuracy by 27.54 points. The authors of the Ankhdjet compiler (arXiv 2608.26206) found a related effect from the other direction. Applying absmean to the BitNet 2B4T bf16 master weights reproduced only 98.5% of the packed release’s ternary values, with 32 M of 2,084 M positions flipping on rounding boundaries. Their conclusion is the right stance for any audit: coercion gives you a near twin of the model, not the model.

Two remedies each met a 4-point strict-accuracy non-inferiority criterion against online evaluation in all three models. One writes the training quantiser’s codes directly. The other adjusts the bf16 inputs until the unchanged tools emit those codes.

How do we detect precision loss when ternary weights pass through a bf16 path?

Compare the codes and the outputs directly. Shape checks, tensor names and zero counts don’t prove a checkpoint is intact. A guide to checkpoint formats from ITIS makes the point: two checkpoints with identical shapes and identical tensor names can still be incompatible, and the only reliable acceptance test is numerical. You run a fixed prompt set through the source and converted models and compare logits. Its toy example shows a conversion that copies interleaved Q and K slots unchanged. Tensor length is preserved, the shape check passes, and K1 lands in the Q2 position.

At the code level, the comparison should be exact. The Ankhdjet authors compared every stage of their reference chain by integer equality with no tolerance. They also reassemble every layer from what is actually on disk and compare it bit for bit against a fresh unpacking of the checkpoint. One tempting shortcut doesn’t work. In the Qwen3-8B ternarisation project, counting zeros was ruled out as a ternary-provenance test because its offset μ is non-zero (arXiv 2609.09240). The same project made zero missing or unexpected state keys, measurable weight changes and end-to-end perplexity reproduction mandatory for every benchmark. It did so after earlier failures, including wrong checkpoint paths and malformed packed tensors, still produced plausible-looking numbers.

A quantised checkpoint is not a serialised one

A post-training ternarisation study of Qwen3-4B (arXiv 2609.01962) shows the trap clearly. Its first saved quantised checkpoint stored the converted values inside FP16 tensors, so the disk footprint didn’t shrink, even though the weights carried a much lower effective information rate. A later packing run stored the actual ternary planes and their scales and offsets in a lattice-aware format. The checkpoint went from 8.29 GiB to 3.96 GiB, and WikiText-2 perplexity moved from 18.344322 to 18.341623. The 8B follow-up repeated the pattern: a fake-quantised 16.9 GB checkpoint became 8.24 GiB with essentially unchanged perplexity once it was properly packed. So check size and perplexity after the round-trip.

Packing also has to happen at save time. A third-party packing of the 4B model used a single 2-bit ternary encoding with FP16 per-group scales and introduced a maximum absolute weight error of 0.305, because the second ternary plane and the GPTQ residual had been discarded. The paper’s lesson is that post-hoc packing can’t reconstruct information the quantiser threw away at save time. At 8B, fitting a lattice to the saved FP16 tensor after the fact produced 50.5% relative error and a perplexity of about 18,477.

What a ternary checkpoint compatibility audit spike should test

The spike is small: one short training run, one export, one reload in every tool that touches the artefact.

Check Failure it catches How to test
Code fidelity after export bf16 cast moves values across the ternary threshold Decode the exported codes and compare them with the training quantiser’s codes by integer equality
Packed versus fake-quantised storage Low-bit values saved inside FP16 tensors Compare on-disk size and perplexity before and after packing
Scale layout Group-scaled tensors handed to a per-tensor loader Inspect each layer’s scale tensors and confirm the loader’s scale model matches
Tied weights A converter drops the shared head name and the loader ignores the tie flag Check that tie_word_embeddings survives conversion; a failure gives a randomly initialised head
Dtype coercion BF16 stored and FP16 loaded, with out-of-range values turning into infinities Watch for NaN several layers in, far from the actual fault
Layout permutation Fused or interleaved Q/K/V order differs between implementations Logit comparison; shape checks cannot see it
Metadata travel config.json doesn’t travel and defaults happen to parse Load the converted file in a clean environment and diff the hyperparameters
State keys and provenance Wrong paths, malformed packed tensors, unpinned revisions Zero missing or unexpected keys; record artefact hashes and pin revisions by commit hash
Acceptance Anything the other checks missed Fixed prompt set through source and converted models, logits compared

Do we need a new serialisation path for ternary checkpoints, or can we adapt the existing bf16 one?

Adapt the existing path when the spike passes. Build a dedicated one when it doesn’t. If checkpoint round-trips keep the ternary codes exact and the tracker logs the run without error, minor changes to the bf16 path are enough. If codes flip, scales are lost or dtypes are coerced, write a dedicated ternary serialisation path before scaling up training. We treat checkpoint and format portability as engineering work that is worth doing against a measurable trigger. Here the trigger is a failed audit check, not a general sense that ternary deserves its own format.

If a new path is needed, the 4B study states the design rule: the format has to persist the discrete supports and their scales during quantisation, not afterwards. The container then constrains where scales can live. Safetensors supports bfloat16 natively, requires tensors to be packed with no striding, and allows only a string-to-string map in its __metadata__ key. In our view, that makes scales tensors in their own right, not values squeezed into metadata.

Two caveats limit what the spike can prove. Sharded training adds ternary-specific steps: torchao’s BitNet recipe ships a helper that precomputes BitNet scales for FSDP after each optimiser step. And a small spike may simply not surface problems that only appear at full training scale.

Does the BitNet b1.58 reference recipe produce checkpoints our export tooling can read without a custom adapter?

It depends on which of the three published variants your tooling touches. Each variant was built for a different consumer: inference, training, or bitnet.cpp. Running a small BitNet b1.58 pilot end to end through your own registry, conversion and tracking stages is the reliable way to find out.

TechnoLynx runs that pilot as scoped integration work. TechnoLynx inventories the registry, checkpoint format and MLflow or W&B fields that have to stay compatible, pushes a small run on a subset of nodes through every stage with the reference implementation, adapts the conversion and export tooling where it breaks, and hands over a documented integration path for full-scale adoption.

The 2B4T technical report lists the variants: packed 1.58-bit weights for inference only, bf16 master weights for training only, and a GGUF build for bitnet.cpp. The packed variant stores four ternary values in each int8, and the CUDA kernel unpacks them in shared memory just before the matrix multiplication. The bitnet.cpp repository documents its conversion step from the bf16 safetensors master to GGUF with its convert-helper-bitnet.py script.

In a discussion on the model’s Hugging Face repository, a practitioner who followed the documented Transformers loading call got “Could not locate the configuration_bitnet.py”. They also found uint8 weights in model.safetensors with no way to tell the packing format without a loader. The Transformers documentation adds a constraint that rules out a common workaround: BitNet models can’t be quantised on the fly, because the method is quantisation-aware training applied during pretraining or fine-tuning.

Which variant to start the audit from

Use the packed release as the reference for code fidelity, and audit anything derived from the bf16 master against it. The reason is the 98.5% figure above. Applying absmean to the bf16 master didn’t reproduce the packed codes exactly. Kernel choice matters too. The bitnet.cpp paper (arXiv 2502.11880) reports I2_S, TL1_1 and TL2_1 as lossless for BitNet b1.58 and TL1_0 and TL2_0 as negligible-loss, and it ties lossless inference to preserving the training constraints at inference time.

Why can checkpoints with per-row and per-group scales fail to load in the same serving runtime?

They fail because every loader and kernel assumes one scale model, and a tensor laid out for a different one either can’t be represented or can’t be computed. In Transformers, BitNet weights carry a symmetric per-tensor scale derived from the mean absolute value of the matrix. The Ankhdjet frontend decodes packed per-tensor releases, but it refuses a release family that stores group-scaled ternary values. A per-tensor scale can’t carry that scale grid, and the authors say faithful support needs per-group requantisation, not a frontend change. Group-scaled layouts in the BITCOS work share one 16-bit scale per 128 entries along the inner-product dimension and hold one scale per row for each group.

Runtime support also varies by format, not only by scale model. In the llama.cpp build tested by the BITCOS authors, Q2_0 (one fp16 scale per 128 weights) has a Vulkan kernel, while the five-trit-per-byte TQ1_0 format has no supporting Xe2 GPU kernel. Layout is also a separate question from values. BITCOS is a storage-layout change that applies to existing checkpoints and decodes the same ternary values bit-exactly. So a checkpoint can be correct and still unreadable to a particular runtime.

A GGUF file is expected to record what was done to its tensors. The GGUF specification requires general.quantization_version whenever any tensor is quantised. It says deviations of tensor data from the original model should be recorded in the metadata or the architecture definition, and it describes rearranged tensors through tensor_data_layout.

Which parts of an MLflow or W&B schema break with ternary weights?

No published source we could find tests MLflow or W&B schemas against ternary weights, so what follows is our untested judgement, not a finding. The fields most likely to be missing are the ones bf16 runs never needed: the quantiser and its version, the scale layout (per-tensor or group size), the export path and remedy used, the kernel the checkpoint was validated against, and artefact hashes. The 4B study does say artefact hashes for the quantised and rotated checkpoints should be kept in any release package, and the ITIS guide recommends pinning revisions by commit hash in the deployment manifest. The pilot is how you find out what has to change. Expect some of the schema changes to need agreement from the platform owners, not only the ML team.

Can ternary training share the bf16 stack, or does it need its own code path?

It can probably share most of it, because BitNet training keeps full-precision latent weights and only fake-quantises them in the forward pass. Whether the fused optimiser and the export step cope is what a pilot has to prove.

Under its Generative & Agentic AI R&D engagement, TechnoLynx defines the minimal spike (model size, dataset subset, metrics), assesses whether your Megatron-LM or DeepSpeed stack can be shared, patches the fused optimiser kernel and checkpoint serialisation where needed, and runs ternary against bf16 side by side.

The training mechanics explain why sharing is plausible. The Hugging Face 1.58-bit write-up describes training in full precision while quantising weights to ternary as training goes: compute an absmean scale, divide, round, clamp to [-1, 1], then rescale and continue in full precision. torchao’s bitnet_training recipe likewise fake-quantises weights and activations and uses a straight-through estimator for the gradients with respect to the floating-point weights. The optimiser therefore updates full-precision latent weights, as in standard training. The 1.58-bit representation is derived from those weights, not updated directly. The oneAPI bf16 specification also allows a master copy of weights with f32 gradients converted to bf16 afterwards.

The pilot’s first question is whether a specific fused optimiser kernel in Megatron-LM or DeepSpeed handles the quantisation hooks and scale bookkeeping correctly. Take one fused optimiser step and the same step through an unfused reference, compare the latent weights, then quantise both and compare the codes by integer equality. If the fused kernel and checkpoint format cope with minor changes, keep one code path with conditional branches. If the weight-update logic needs fundamental changes, maintain a separate ternary training path. The Hugging Face write-up notes that packed inference needed custom CUDA and Triton kernels to unpack weights on the fly, so the serving side may diverge from the training stack even when training is shared.

The side-by-side run compares convergence, throughput and checkpoint and tracker fidelity. Treat its results as indicative of that workload and library version only. Where the pilot also has to settle which accelerator to use, LynxBenchAI is the tool for measuring that hardware. Your own workload is still measured in the pilot.

Who can audit our ternary checkpoint pipeline before we commit to a training run?

TechnoLynx audits ternary checkpoint compatibility against the AI infrastructure you already run, under its Generative & Agentic AI R&D engagement. It inventories the checkpoint format, tracker schema, kernels, optimiser and distributed stack, runs a short ternary spike through them, reports each conversion defect with its evidence, and builds a serialisation path only where the spike shows the existing one fails.

On build in-house or bring in a specialist: a team that already owns its export tooling and has time for code-level comparisons can run the checklist above itself. A specialist earns its place when the spike touches custom kernels or sharded checkpoints. When choosing a partner, ask for code-level and logit-level acceptance criteria up front, not a load-without-error demonstration. This isn’t the right engagement if you only need to run a published BitNet checkpoint as it is. Microsoft’s open-source inference code and bitnet.cpp cover that. Teams with a strong platform group may prefer to run the spike internally and ask for review of the findings only.

What a passing audit leaves unproven

A clean audit shows that the artefact is the model you trained. It says nothing about whether the model is good or fast. Even the Qwen3-4B post-training study’s verified packed artefact had perplexity preservation but no end-to-end benchmark for task accuracy or generation throughput. Speed was further away still: a preliminary Triton GEMV microbenchmark ran 4.6× slower than FP16 cuBLAS on one tested shape, and the authors explicitly don’t claim that compression alone yields faster inference. Their fake-quantised deployment path decoded at 0.78× the FP16 rate. Whether ternary pays off at your scale is a separate question.

Back See Blogs
arrow icon