Before an LLM can answer, it must process every token in the prompt and construct the per-layer key/value memory needed to continue. This first pass is prefill. After it selects the first output token, generation becomes an incremental loop: process one new token, consult the saved prompt state, append one new cache entry, and select the next token. This second phase is decode.
What Actually Happens From Prompt To First Answer
- Tokenization: the prompt text is divided into token IDs. One request may contain a long sequence; a server may also group several requests into a batch.
- Embedding: each token ID selects one dense vector from the model's embedding table. A batch of
Bprompts withTtoken positions and hidden widthKis now represented conceptually as rows shaped[B,T,K]. - Prefill: the transformer processes those prompt rows through every layer. Linear projections, attention, normalization, MLP kernels, and residual connections transform the embeddings into contextual hidden states.
- Cache construction: each attention layer writes the prompt's key and value vectors into its persistent KV cache. The cache does not store every temporary calculation. It preserves the K/V state that future queries would otherwise have to recompute.
- First output: the final prompt position is projected into vocabulary logits, and the sampling policy selects the first generated token.
- Decode: that selected token is embedded as one new input row. The transformer processes this row through the same model weights, but each attention layer reads the existing KV cache, appends the new token's K/V row, and produces the state needed to select another token.
- Repeat: only the newly selected token enters the next decode step. The original prompt is not run through the transformer again because its reusable attention state is already in the KV cache.
This is the bridge to the kernel shapes. Prefill exposes many activation rows at once: approximately B × T rows for a padded batch, or the equivalent packed-token rows for a packed batch. A projection can reuse weight tiles across those rows, which creates a GEMM-shaped workload. A private, single-request decode step exposes one new row, which creates a GEMV-shaped workload. A high-concurrency server can batch new rows from several active requests and recover some GEMM-like reuse during decode, but that is not the same contract as one user running one model locally.
The prerequisite notes explain each boundary independently: tokenization turns text into model IDs; matrix Wx+b connects scalar linear algebra to GEMV, GEMM, and transformer projections; attention explains how queries use keys and values; and the KV-cache article explains why incremental decode can reuse prompt state instead of recomputing it.
C-Kernel-Engine, abbreviated CKE, is a compiler and runtime project that represents an AI model as a circuit of mathematical kernels. Model weights, their sidecar metadata, and a circuit definition are lowered through intermediate representations into explicit memory plans, C calls, and a compiled runtime. This article continues the CKE kernel-registry article: the registry explains how CKE identifies an exact provider; here we examine why one model needs different providers and execution plans for prefill and decode.
Why should a user care? Prefill largely determines time to first token: how long the model appears to think before the answer begins. Decode determines inter-token latency: how quickly the answer continues once it starts. A runtime can improve one number while leaving the other almost unchanged. During recent CKE work, one production-shape Q4 prefill provider moved from roughly 1.64 times llama.cpp's time to 1.035 times on one measured i7-14700T. That did not make the complete request 1.6 times faster. It removed one constraint and exposed the next ones in attention, Q6 scheduling, runtime dispatch, and sustained package power.
The underlying reason is shape. During prefill, a projection can reuse one block of weights across many prompt rows and behave like matrix-matrix multiplication. During low-batch decode, the same weights are applied to one new row and behave like matrix-vector multiplication. Full attention changes shape too: prefill relates prompt tokens to one another, while decode relates one new query to the existing cache. CKE therefore cannot optimize “the model” once. It must preserve one mathematical circuit while compiling phase-specific execution plans.
1. The Request Changes Shape
Suppose the hidden width is K, a projection produces N outputs, and the prompt contains T tokens. Ignoring storage layout for a moment, prefill evaluates:
This is GEMM: general matrix-matrix multiplication. Each block of weights can be loaded, unpacked, or repacked once and reused across several activation rows. A useful prefill tile might process four or eight token rows and eight output columns together. That reuse is why replacing prefill with T independent GEMV calls can be mathematically correct and still unnecessarily slow.
After prefill, one decode step has one current hidden row:
This is GEMV: general matrix-vector multiplication. At low concurrency, there is no large token dimension over which to reuse every streamed weight. The runtime often becomes limited by how quickly quantized weights and KV-cache rows can move through memory and cache, not by the processor's peak multiply-add count. Parallelism usually divides independent output rows among workers. If a serving system batches many independent decode requests, a matrix-shaped opportunity can reappear, but that is a different workload contract from one private request producing one token.
| Property | Prefill | Low-batch decode |
|---|---|---|
| Activation shape | Many token rows, [T,K] | One new row, [1,K] |
| Linear operation | GEMM | GEMV |
| Primary reuse | Reuse packed weights across token rows and output tiles | Reuse persistent model and cache allocation across steps |
| Attention query count | Many prompt queries | One new query per request |
| State transition | Construct and append prompt K/V | Read old K/V, append one K/V row |
| Important metric | Time to first token and prompt tok/s | Inter-token latency and decode tok/s |
| Common CPU limit | Kernel throughput, packing, scheduling, attention, sustained package power | Weight/KV bandwidth, cache behavior, synchronization |
2. The Attention Complexity Needs A Precise Statement
It is common to write “prefill is O(T^2) and decode is O(T).” That is only a description of the full-attention component under fixed head dimensions; it is not the complexity of the entire transformer layer. The linear projections and MLP remain linear in token count for fixed model dimensions.
For full causal attention with prompt length T and head dimension d:
At decode step s, one new query reads the s cached key/value rows:
The KV cache removes the need to recompute the old keys and values. It does not make the old context disappear: the new query still has to attend across the retained cache unless the model uses a bounded-window, recurrent, compressed-memory, or other specialized attention contract. Over many generated tokens, those repeated cache reads remain real work.
3. What CKE Actually Exposes
The v8 runtime has separate public dispatch surfaces. ck_parallel_prefill_v8.h describes thread-pool-parallel GEMM wrappers with the shared call form gemm_nt_*(A, B, bias, C, M, N, K). It includes Q4_K/Q8_K and Q6_K/Q8_K paths used by quantized projections. ck_parallel_decode_v8.h describes GEMV dispatch where y[M] = W[M,K] @ x[K] and output rows are partitioned across the threadpool.
// Prefill: many activation rows.
gemm_nt_q4_k_q8_k_pairwise_split_min_parallel_dispatch(
activations, weights, bias, output, T, N, K);
// Decode: one activation row.
gemv_q4_k_q8_k_repacked_parallel_dispatch(
output, weights, activation_q8, N, K);These signatures expose only part of the contract. The corresponding Q4 prefill kernel map records packed-weight ownership, split-min reduction, persistent repacking scratch, AVX2 and AVX-VNNI providers, shape gates, threading capacity, exact llama.cpp comparison, and fallback behavior. The Q4 decode map separately records canonical Q8_K activation quantization, repacked matrix-vector arithmetic, runtime ISA dispatch, and bit-exact production evidence.
This separation matters because “Q4_K weights and Q8_K activations” still does not uniquely identify the arithmetic. Prefill can pack four activation rows using nearest-even behavior and reuse an interleaved eight-output weight layout. Decode can quantize one canonical row and traverse a repacked GEMV layout. The output shape may agree while byte packing, integer traversal, minimum-term accumulation, FP32 reduction order, and rounding differ.
4. One Circuit, Two Lowered Plans
A circuit describes the model semantics once: normalization, projections, rotary positions, attention, residuals, MLP operations, cache policy, and generation policy. The Qwen3-VL circuit, for example, declares an FP16 decoder KV cache, mixed visual/text prefill, a persistent cache, and incremental decode after prefill. The compiler then resolves phase-appropriate kernel maps rather than duplicating the architecture by hand.
- The tokenizer and, for a vision model, the vision bridge produce decoder input rows.
- Prefill lowers those rows through GEMM-oriented projections and prefill attention.
- Each layer appends its K/V rows into persistent cache storage.
- The final prompt row produces first-token logits.
- Decode consumes one selected token, executes GEMV-oriented projections and one-query attention, appends one cache row, and produces the next logits.
- The decode plan repeats without reconstructing the prompt circuit or reallocating the entire cache.
The kernel registry is the bridge between the shared circuit and these distinct plans. It lets IR1 request execution.phase=prefill or execution.phase=decode, resolve a provider whose numerical and storage contracts match, and preserve that provider through memory planning and generated C. A later code generator should not rediscover the phase from a model name or silently replace GEMM with repeated GEMV.
5. Why “Just Use GEMM” Was Not Enough
CKE's recent history is a useful record of what CPU optimization actually requires. The sequence below is not a claim that every model became faster by the same percentage. It is a bounded progression on specific Qwen3-VL and quantized prefill shapes, primarily measured on one i7-14700T. The important lesson is how many independent contracts had to become correct before a serious llama.cpp comparison was possible.
| Change | Problem exposed | Measured evidence and boundary |
|---|---|---|
426fb463: reuse packed Q4 weights | Workers repeatedly streamed and unpacked the same weights. | Q4 projection 54.32 s to 34.75 s; profiled mixed prefill 77.21 s to 56.87 s; bit-exact 9/9 reference cases on the measured host. |
be322926: exact 4M x 8N provider | Each weight block was still unpacked too often across activation rows. | Production-shape isolated kernel about 69.4-70.1 ms to 62.4-64.3 ms; mixed prefill about 65.84 s to 61.25-61.83 s. |
9060e0fe: exact 8M AVX2/VNNI provider | Four-row reuse and no 256-bit VNNI path retired excess instructions. | Isolated Q4 shape about 67.5 ms to 54.2 ms; 4,210,688 outputs bit-exact, but still 1.81-1.83x llama.cpp under the cited GCC comparison. |
19996c52: interleaved eight-output VNNI provider | The kernel reloaded canonical Q8 data and performed too much work per output. | CKE moved from roughly 1.64x to 1.15x llama.cpp on the same production shape while all 4,210,688 outputs remained bit-exact. |
67bf8729: bounded SMT capacity | Using every logical CPU was slower; the best provider needed a limited extension beyond physical cores. | 20-default/24-capacity measured 30.757 ms versus llama.cpp 29.729 ms, a 1.035x ratio, with bit-exact output on one i7-14700T. |
d0e27353: map-owned Q6 output tiling | Independent-row scheduling degraded during sustained production-shape work. | Q6 isolated 212.94 ms to 159.28 ms; mixed prefill 54.423 s to 51.786 s; exact bytes preserved. |
a7915a34: runtime VNNI capability | The optimized provider existed, but generated code compiled without native flags could not see it. | All 180 eligible visual-prefix calls moved to VNNI; matched mixed-prefill runs fell 48.41 s to 32.67 s on the cited host. |
ce5b54e6: parallel visual prefill heads | The exact Qwen3-VL attention provider processed 32 independent query heads serially. | One Q=1008 provider case fell about 223 ms to 34.5 ms; profiled attention 9.841 s to 2.651 s with the same output hash. |
a2b8774a: explicit FMA | A hosted Q=63 short-prefill boundary differed from llama.cpp by one FP32 ULP. | Explicit fmaf made the contraction part of the contract and removed the one-ULP difference without changing tolerance. |
The order matters. Packing had to remove repeated work before wider tiles could expose their real benefit. Exact wider tiles then made instruction cost and output layout visible. Once the arithmetic provider approached llama.cpp, thread topology and runtime ISA visibility became meaningful constraints. After the projections improved, serial visual attention became large enough to dominate the profile. Each fix removed one bottleneck and made the next bottleneck measurable; Section 8 reconnects this history to the Theory of Constraints.
6. What One FP32 ULP Means
ULP means unit in the last place: the distance between adjacent representable floating-point values near a particular number. A one-ULP difference can be extremely small and still matter when the acceptance contract is bit-exact. It can also become amplified by later normalization, softmax, residual accumulation, and repeated layers.
The Q=63 case showed why source-level similarity is insufficient. A compiler may contract a multiply and add into one fused operation in one build but round them separately in another. Writing an explicit fmaf stated the intended arithmetic rather than hoping compiler flags reproduced it. CKE did not increase the tolerance until the gate passed. It corrected the operation that defined the reference boundary.
This is also why an optimized GEMM cannot automatically replace a collection of exact GEMVs. Different tiling can change integer traversal, horizontal reductions, FP32 accumulator grouping, bias timing, and rounding. CKE promotes a provider only when its actual production shape and dispatch ABI satisfy the intended oracle, not merely when a tiny leaf function appears close.
7. What A Real llama.cpp Comparison Requires
CKE is not claiming a general victory over llama.cpp. The recent work shows why such a claim is difficult. llama.cpp has mature quantized layouts, architecture-specific kernels, repacking, scheduling, cache management, and years of measurement across many machines. A specialized engine may legitimately outperform a general engine on one model, one approved weight file, one platform, or one constrained workload. Hard-coding those assumptions can remove abstraction and branching costs. But “the model runs” does not establish a comparable result.
A serious performance comparison needs at least:
- the same checkpoint and quantization bytes, or a clearly explained quality-equivalent alternative;
- the same prompt, context, generated-token count, sampling policy, stop policy, and cache precision;
- separate prefill and decode timing rather than one blended number;
- the same thread, affinity, power, and thermal conditions;
- exact compiler, ISA, library, and executable provenance;
- quality or token-parity evidence, not only fluent output;
- repeated alternating runs so warm cache or package power does not favor one runtime;
- a distinction between isolated kernel speed and end-to-end model speed.
Prompts can generate a plausible loop. They do not generate trustworthy performance by magic. The missing work is usually in layouts, reductions, cache policy, dispatch, measurement, and the thousands of cases where “almost equivalent” is not equivalent.
8. The Theory Of Constraints View
A fast projection does not make a fast multimodal request if attention remains serial. More cores do not help if the provider reloads the same packed weights. AVX-VNNI does not help if generated code cannot discover the linked engine's capability. Every logical thread is not automatically useful if SMT causes contention or exceeds the package's sustained power window. The bottleneck moves as each constraint is removed.
| Observed constraint | Wrong reflex | CKE response |
|---|---|---|
| Repeated weight unpacking | Add threads | Pack once and reuse across rows/outputs |
| Low arithmetic efficiency | Relax parity for a wider tile | Design a tile that preserves the reduction contract |
| Provider hidden at runtime | Compile all generated code with host-native flags | Query capability from the linked engine and retain exact fallback |
| SMT regression | Use every logical CPU | Give the eligible provider bounded extra capacity |
| Serial attention heads | Keep optimizing GEMM | Parallelize independent heads under the attention map |
| Sustained thermal/power collapse | Quote the fastest isolated iteration | Measure long production-shape runs and alternate runtimes |
The same reasoning will matter on larger Xeon and EPYC systems. More memory channels, cache, cores, AVX-512, VNNI, AMX, and accelerator blocks expand the available machine, but they do not choose the right tile or reduction. A workload reaches useful hardware throughput only when data layout, reuse, worker placement, numerical contract, and sustained system behavior agree.
9. Measure The Two Phases Separately
A single “tokens per second” number can conceal the result. CKE should report at least:
| Metric | Question answered |
|---|---|
| Prompt tokens and prefill time | How long did the runtime spend building state? |
| Prompt tok/s | How efficiently did the machine process the input rows? |
| Time to first token | What latency did a user experience before generation began? |
| Decode tokens and decode time | How long did incremental generation take after prefill? |
| Decode tok/s and inter-token latency | How quickly did each new token arrive? |
| Provider trace | Which GEMM, GEMV, attention, ISA, and threading routes actually executed? |
| Parity/quality evidence | Was speed obtained under the same numerical and model contract? |
| CPU, memory, affinity, compiler, power state | Can another engineer interpret and reproduce the number? |
CKE's CKE Throughput Unit extends this idea toward topology-aware measurement. The purpose is not to invent a flattering universal score. It is to record useful work together with memory, communication, topology, and execution context so that a distributed or heterogeneous result remains interpretable.
10. Current Evidence Boundary
CKE now has strong evidence for several exact AVX2 and AVX-VNNI Q4/Q6 prefill providers and for the hardened Qwen3-VL AVX2 parity path. That does not mean every ISA, compiler, model family, prefill shape, and decode path is equally mature. AVX-512, BF16/AMX, additional quantized formats, larger systems, and end-to-end comparative runs remain separate promotion surfaces. The exact boundaries should continue to be recorded in kernel maps, tests, nightly reports, and commit evidence rather than compressed into “CPU support complete.”
The practical result of this work is not a viral benchmark yet. It is a reusable method: identify the phase, resolve the exact provider, measure the production route, compare every value when possible, preserve provenance, remove the current bottleneck, and run the gates again. That is how CKE can eventually make credible claims on laptops, Xeon workstations, servers, embedded ARM systems, and distributed CPU nodes without pretending those machines are interchangeable.
Related Notes And Source Artifacts
- CKE Kernel Registry: How Exact C Functions And Numerical Contracts Connect
- Gemma4-VL In CKE: How Images Become Decoder Tokens
- Nemotron Kernels From Inference To Backpropagation
- CKE Prefill Performance Roadmap
- CKE v8 kernel maps
- llama.cpp source repository
To inspect or contribute to CKE, use the contributor path or join the CKE Discord channel. Profiling adapters, reproducible baselines, numerical fixtures, kernel work, and compiler/runtime work are all valid entry points; the hardest multimodal parity problem is not the entrance exam.