What we were trying to find

CKE and llama.cpp produced different tokens while running Qwen3.6. That told us something was wrong, but not where it went wrong. We built X-Ray to compare the intermediate results and find the first part of the model where the two runtimes stopped agreeing.

For anyone new to the project, C-Kernel-Engine (CKE) is an open-source CPU runtime and kernel compiler for transformer-based language, vision, and audio models. We describe a model as smaller mathematical circuits, connect those circuits to C kernels, and generate the runtime. PyTorch and llama.cpp are important references for checking whether that generated runtime is still doing the same mathematics.

During the recent Qwen3.6 work, comparing only the final text was not helping much. A model can produce a reasonable sentence even when small numerical differences are accumulating inside it. It can also produce one wrong token after hundreds of operations worked correctly. We needed a way to open the run, compare useful points inside each layer, and connect a mismatch back to the generated call and kernel that produced it. That is the job of CKE X-Ray.

CKE X-Ray flow from wrong token through semantic checkpoints to a bounded kernel or topology diagnosis.
We start with the token mismatch, move backward through intermediate checkpoints, and then run the suspected kernel again with the same captured input.

We Had The Wrong Token. Where Did It Start?

A token mismatch can start in many places. The tokenizer may have produced different input IDs. RoPE may have used the wrong dimension. A projection may have loaded the wrong dtype. Attention or recurrent state may have drifted. The output projection may be fine and simply receive a bad residual from an earlier operation. Final logits combine all of those possibilities into one large result, so they are useful for detecting a failure but poor at locating it.

We therefore added checkpoints at points that mean the same thing in both runtimes: after Q/K normalization, after RoPE, after a recurrent-state update, after the block residual, and before sampling. I use the phrase semantic checkpoint for this. The buffers may have different names or layouts, but both checkpoints represent the same step in the model's mathematics.

wrong token
    |
    v
first logit-ranking disagreement
    |
    v
last passing semantic checkpoint
    |
    v
first failing semantic checkpoint
    |
    v
same-input replay of the suspected provider
    |
    +-- replay passes  -> upstream or propagated drift
    |
    +-- replay fails   -> bounded kernel, ABI, layout, or topology fault

There is one more complication. The first checkpoint that fails may not be the operation that created the difference. It may simply be the point where an earlier, smaller difference became easy to measure. To separate those cases, we can capture the input to the suspected kernel and give that exact input to both implementations. If they now agree, the difference arrived from upstream. If they still disagree, we have narrowed the problem to that kernel, its ABI, its layout, or the model topology it was told to use.

How X-Ray Looks Inside A Run

X-Ray does not automatically understand every runtime. We still need a small adapter for each reference, or oracle, that captures the matching checkpoints. Once those results exist, X-Ray connects them to information CKE already produces while compiling the model.

ArtifactQuestion it answersWhat must remain explicit
Call IRWhich operation executed in circuit order?Layer, phase, shapes, buffers, selected provider
Kernel mapWhat concrete C implementation was selected?ABI, dtype, layout, reduction and threading capabilities
Numerical contractWhat arithmetic behavior was required?Storage boundaries, accumulation, grouping and evaluation order
X-Ray reportWhere did subject and oracle first disagree?Metrics, checkpoint order, exactness, attribution classification
ProvenanceAre these artifacts from the same experiment?Model, runtime, generated artifact, oracle and run fingerprints

The visualizer shows the complete operation path even when we have not captured every intermediate value. Operations without evidence stay gray. This makes it easier to see the difference between “every checkpoint we captured passed” and “we compared every operation.” Those are not the same statement.

The implementation and operator workflow are documented in the CKE X-Ray visualizer runbook. PR #250 added the integrated visual surface and then hardened it against misleading joins.

When X-Ray Entered CKE

Qwen3-VL was the forcing function. CKE already had generic first-divergence checks, parity tests, and an IR visualizer, but they answered separate questions. The parity harness could tell us that two values differed. The visualizer could show the generated circuit. During Qwen3-VL vision prefill and decoder debugging, final-prefix cosine similarity could not tell us which graph edge had changed the token ranking. That limitation started X-Ray inside CKE v8, the current inference lane that lowers model circuits and kernel contracts into generated C. The implementation lives in the version/v8 source tree; “v8” is a CKE software-generation label, not a CPU or hardware generation.

DatePR and changeWhy it mattered
July 11 PR #136: canonical first-divergence attribution and bounded numerical diagnostics Propagated 382 circuit-owned checkpoints and turned a final mismatch into an ordered search. A real Qwen3-VL BF16 preflight identified vision.frontend.position.output as an FP32-versus-BF16 storage-contract mismatch.
July 12 PR #164: one X-Ray interface for llama.cpp and PyTorch vision captures Stopped the Qwen3-VL activation script from becoming a competing workflow. A 4,032-token capture changed the diagnosis from an alphabetically selected tensor to the semantic boundary vision.layer.0.q.pre_rope.
July 13 PR #171: execution-state diagnosis before tensor math Separated arithmetic failures from segmentation, position, cache, append, attention-input, and ranking failures.
July 14 PR #173: Qwen3-VL parity-harness and attribution hardening Made the harness part of the correctness architecture. It rejected stale generated libraries, post-EOS comparisons, ambiguous segmented occurrences, uninstrumented runtimes, and changed llama.cpp capture modes before blaming a kernel.
July 19–21 Qwen3.5 recurrent boundaries and Qwen3-VL BF16 provider, corpus, and sensitivity checks Forced X-Ray to understand recurrent schedules, storage dtypes, quantization boundaries, and provider-specific arithmetic.
July 26–27 Parity-drift tab, circuit overlays and runbook, merged through PR #250 Made coverage, drift growth, ranking changes, provenance, and missing evidence inspectable in one place.
July 27–28 Fail-closed provenance, propagated-drift classification, and the Qwen3.6 grouped-topology investigation Stopped the visualizer from turning incomplete evidence into a confident but unsupported diagnosis.

The earlier article What Numerical Parity Actually Requires documents the Qwen3-VL and Qwen3.5 parity work that made these diagnostics necessary. The present article continues that history through the integrated visualizer and the later Qwen3.6 investigation; Qwen3.6 did not originate X-Ray.

That sequence is still continuing. Every model family exposes another ambiguity: batched prefill versus sequential recurrence, persistent decode versus full replay, BF16 storage versus FP32 accumulation, or one logical tensor represented by different physical layouts. X-Ray is useful precisely because it is being hardened by real failures rather than designed only around a clean synthetic example.

Why Short Agreement Is Not Enough

Autoregressive generation feeds each selected token back into the next model step. If two deterministic greedy runs match for the prompt and the first few generated tokens, that is useful evidence, but it does not certify the rest of the trajectory. A small difference in a reduction, storage cast, cache update, recurrent state, mask, or positional calculation can remain below the top-1 decision boundary for many steps. If it eventually changes one selected token, the two runs now have different histories and later divergence is expected.

same prompt
  -> small hidden-state difference
  -> same top-1 token for many steps
  -> one ranking boundary is crossed
  -> different token becomes the next input
  -> the two trajectories are no longer the same experiment

Even before a token flips, growing hidden-state or recurrent-state drift can reveal a real implementation defect. This matters more as context windows grow. Qwen3-Next documents a native context of 262,144 tokens, while Google's Gemma 3 model card specifies 128K for its larger models. A runtime that agrees for three decode steps but applies a slightly wrong recurrent update, KV-cache cast, RoPE width, or reduction order has not yet shown that it preserves the model's intended long-context behavior.

CKE therefore pursues the strongest parity available at each boundary:

  • Bit-exact primitive parity when CKE deliberately implements a pinned llama.cpp production arithmetic contract.
  • Scoped numerical parity when BF16, FP16, compiler choice, or a different valid reduction schedule makes byte equality the wrong requirement.
  • State and ranking parity across prefill, persistent decode, long trajectories, thread counts, and execution phases.
  • Deterministic token parity only when the tokenizer, prompt, model bytes, cache state, sampling settings, and oracle are all fixed.

The target is not to pretend that every valid PyTorch kernel and every valid llama.cpp kernel must produce identical floating-point bytes. The target is to avoid dismissing unexplained drift as “normal.” If a selected CKE route claims compatibility with a particular production contract, we try to match it exactly. Where exactness is not the contract, we record the dtype, tolerance, schedule, and ranking behavior instead of quietly widening a global threshold.

Why PyTorch and llama.cpp are useful references

The canonical Transformers implementations are unusually close to the model publishers: the Qwen3-Next PyTorch implementation is copyrighted jointly by the Qwen and Hugging Face teams, and the Gemma 3 implementation by Google and Hugging Face. These are strong references for model semantics and storage behavior.

llama.cpp provides a separate, highly exercised CPU-oriented implementation. Its Qwen3-Next support was authored by llama.cpp contributors and explicitly began with correctness before performance. Its Gemma 3 support PR was likewise implemented in llama.cpp while crediting the Google and Hugging Face teams for direct support. I could not verify a claim that the Qwen team itself authored llama.cpp's Qwen3-Next path, so I do not make that claim here.

This distinction is useful. PyTorch/Transformers tells us how the publisher-facing model is expressed. llama.cpp tells us how a mature independent runtime executes that model on real hardware. CKE compares against both because agreement with only one layer of that chain can hide a conversion, dtype, quantization, scheduling, or implementation mistake.

Making Sure We Are Comparing The Same Run

The first version of X-Ray was useful, but it could join evidence too eagerly. For example, it could label mean absolute error as RMSE, infer an execution identity that the producer never recorded, or attach a vision report to decoder IR. The screen would still look convincing, but the comparison could be wrong.

This is where provenance matters. Here, provenance simply means the fingerprints that tell us which model, generated runtime, subject runtime, oracle, and experiment produced an artifact. Commit f2516797 changed the evidence contract:

  • Unknown metrics and execution identities remain null instead of being guessed.
  • Ranking correlation requires complete run, model, generated-runtime, subject-runtime and oracle fingerprints.
  • Circuit IR is selected by modality, preventing a vision report from silently joining decoder operations.
  • An explicit X-Ray directory is exclusive unless fallback loading is deliberately enabled.
  • Empty panels provide the producer command and prerequisites instead of merely saying evidence is absent.

We later found another edge case. An early checkpoint could be slightly different but still inside its accepted numerical gate. When a later checkpoint finally failed, X-Ray temporarily blamed that later kernel. Commit b3bba165 changed the classification back to DOWNSTREAM_OR_PROPAGATED_DIVERGENCE. In ordinary language: we can see the error here, but we cannot yet say this operation created it. We need the same-input replay before making that conclusion.

Sometimes the honest answer is “we do not know yet”

If the checkpoint identity, metric, circuit, or run fingerprints are incomplete, X-Ray leaves the result unattributed. A gray edge is less exciting than a confident diagnosis, but it tells us exactly what evidence we still need to collect.

What Went Wrong In Qwen3.6

The recent Qwen3.6-27B bring-up gave us a practical test of this workflow. The quantized checkpoint stored recurrent Q/K projections as Q6_K weights. CKE first needed explicit Q6_K-weight by Q8_K-input contracts for prefill GEMM and decode GEMV. That work reached bit-exact primitive parity against the pinned llama.cpp oracle across 1, 16, 20, and 24 threads.

But the recurrent block still diverged. The projection kernels were now correct, so we followed their outputs into DeltaNet. That exposed a different problem: the values were correct, but CKE was interpreting their shape incorrectly. Qwen3.6 stores 16 compact recurrent Q/K groups but maintains 48 value/state heads. The DeltaNet provider assumed that Q/K and value head extents were identical. Passing a 16-group Q/K buffer to a 48-head contract caused logical out-of-bounds reads during both prefill and decode.

Qwen3.6 grouped recurrent topology with 16 compact Q and K groups tiled across 48 value and state heads.
Q and K are compact groups, not 48 independent head rows. Each value/state head maps to group = head mod 16. The SVG shows how the 16 groups are reused across 48 heads.

We could have hidden the problem by expanding the buffer or adding a Qwen3.6 conditional inside one C kernel. Instead, we made group_count part of the DeltaNet ABI and sourced it from the model metadata. The kernel now selects the compact Q/K row with head % group_count, and prefill keeps separate strides for Q/K and value data.

for (int head = 0; head < num_heads; ++head) {
    const int group = head % group_count;
    const float *q_head = q + group * q_stride;
    const float *k_head = k + group * k_stride;
    float *state_head = state + head * state_stride;

    recurrent_update(q_head, k_head, value_head, state_head);
}

This is a simplified loop, not the optimized provider copied from the repository. It shows the relationship we needed to represent. Model metadata provides the group count, the circuit asks for grouped recurrent attention, the kernel map selects an ABI that supports it, and generated C receives both num_heads and group_count.

How We Reached The Fix

PRFailure exposedDurable repairEvidence boundary
#251 BF16 recurrent storage and grouped Q/K behavior were implicit. Added explicit BF16 RMSNorm, projections, Conv1D, SiLU, Q/K L2, DeltaNet, gate, residual and SwiGLU contracts. Recurrent attribution improved; complete model parity was not claimed.
#252 Full attention inherited a quantized route and stale metadata expanded rotary width from 64 to 256. Derived storage dtype from safetensors and preserved source-defined rotary metadata. Layer-3 max error fell from 0.5 to 0.00390625; later drift remained.
#253 Shared recurrent policy downgraded BF16 batched prefill to sequential replay. Required explicit metadata for BF16 batched prefill while keeping quantized recurrent models sequential. A 51.3 GiB model produced a coherent first decode; throughput was not certified.
#255 The real GGUF used Q6_K recurrent projections, but the circuit had no Q6 route. Added Q6_K × Q8_K GEMM/GEMV contracts and phase-specific kernel-map capabilities. Primitive rows were bit-exact across four thread counts; end-to-end Q4_K_M parity remained open.
#256 Sixteen compact Q/K groups were consumed as though they were 48 head rows. Added explicit group topology, independent strides and real H=48/G=16 oracle coverage. Short tokens matched; long-context chunked evaluation order remains unresolved.

This took several pull requests because each fix exposed the next problem. We first made BF16 recurrent behavior explicit. Then we corrected full-attention dtype and RoPE metadata. After that came batched prefill policy, the missing Q6 projection route, and finally the grouped Q/K topology. Saying “DeltaNet was broken” would hide all of those separate causes.

What Works Now, And What Still Does Not

After the grouped-topology fix, the pinned llama.cpp DeltaNet oracle became bit-exact for attention output and recurrent state at the real Qwen3.6 geometry: 48 heads, 16 Q/K groups and head dimension 128. The matrix covered decode and 18-row prefill at 1, 16, 20, and 24 threads. Layer-0 token-0 recurrent maximum error fell from 0.151899 to 4.47e-8, and the first three greedy tokens matched.

The short test is now much better, but the long-context work is not finished. At 353 tokens, logit cosine was 0.94280; at 1000 tokens it was 0.87854, even though top-1 remained stable in the measured cases. llama.cpp evaluates recurrent work in 64-token chunks, and CKE has not yet reproduced the complete chunked evaluation order. We fixed the grouped shape problem and moved the first important mismatch further into the run. We have not completed Qwen3.6 parity.

ClaimStatusWhy
Q6_K × Q8_K recurrent projection primitive paritySupportedBit-exact pinned-oracle rows across prefill, decode and four thread counts
Grouped H=48/G=16 DeltaNet primitive paritySupportedAttention output and recurrent state exact in the production oracle
First three measured greedy tokensSupportedMatched token sequence after topology repair
Complete Qwen3.6 Q4_K_M token parityNot certifiedLong-context evaluation-order drift remains
Qwen3.6 throughput claimNot certifiedLarge artifacts and constrained Xeon/NFS environment distort timing

Why This Work Matters For CKE

I do not think CKE is interesting simply because it runs AI on CPUs. llama.cpp already does CPU inference extremely well. Generated C by itself is also not enough. What I want CKE to provide is a connected view from the model architecture to the numerical contract, selected kernel, generated call, memory layout, and comparison evidence.

This investigation is a small example of that idea working. Reading the tensor headers showed that the recurrent projections were Q6_K. The kernel maps showed which GEMM and GEMV providers were selected for prefill and decode. X-Ray then showed that their correct outputs entered the wrong recurrent topology. We updated the circuit and ABI, tested the real 48-head/16-group shape, and could still see the remaining long-context drift afterward.

A lot of CKE is now developed with AI assistance, so this kind of investigative surface is increasingly important to me. The model can read the code, run tests, and propose a repair. I still need a way to inspect what it changed and connect a failure to the right part of the system. X-Ray is one step toward making that practical.

Code, Documentation, And Related Posts