Code Generation
How CKE v8 turns a GGUF or safetensors model into a compiled libmodel.so: conversion,
circuit selection, fail-closed provider resolution, staged IR, C emission, and native compilation —
with every hand-off between stages named by the artifact it produces.
CKKernelSpec/CKPlanStep registry and the generated forward/backward
examples) still describes the v7 training lane and is preserved as a
legacy appendix at the bottom of this page. For the v7 training
flow itself, see the v7 Inference + Training Runbook.
The v8 Pipeline, End to End
The runner version/v8/scripts/ck_run_v8.py drives the whole inference lane —
download GGUF → convert → build IR → codegen → compile → chat — but
every step is a separate script with a real, inspectable artifact on disk. The diagram names the
artifacts on the edges; the side rail lists the points where the pipeline is deliberately fail-closed.
| Stage | Script(s) | Artifacts in → out |
|---|---|---|
| 1. Convert | version/v8/scripts/convert_gguf_to_bump_v8.py, convert_safetensors_to_bump_v8.py |
GGUF / safetensors → weights.bump + config.json + weights_manifest.json (+ weights_manifest.map). Architecture → circuit is data-driven through version/v8/model_maps/gguf_ck_map.json and safetensors_ck_map.json; an unknown architecture is a hard failure — the converter does not alias or guess similar families. |
| 2. Circuit | version/v8/circuits/<arch>.json |
The circuit declares op instances, port edges, and required numerical contracts. Multimodal families compose circuits with extends, components, and stitch edges — see Kernel Architecture — Composite Circuits. |
| 3. Resolve + lower | version/v8/scripts/build_ir_v8.py (+ version/v8/kernel_maps/*.json) |
The fail-closed resolver binds each circuit-required operation to one provider from the 364 kernel maps, with operation-class and phase validation on circuit-bound providers (merged in #453 / #461). Any miss is a HARD KERNEL RESOLUTION FAULT. Emits ir1_prefill.json / ir1_decode.json → layout_prefill.json / layout_decode.json → lowered_prefill.json / lowered_decode.json → lowered_prefill_call.json / lowered_decode_call.json (plus init.json / init_call.json). See Kernel Maps and Provider Selection. |
| 4. Codegen | codegen_v8.py (orchestrator), codegen_core_v8.py (decode), codegen_prefill_v8.py (prefill) |
lowered_*_call.json + layout_*.json → model_v8.c. The generators emit exactly the call ABI the selected kernel map owns — no argument reconstruction, no model-name branches. |
| 5. Compile | host C compiler (default gcc, overridable via CK_V8_COMPILER) |
model_v8.c → libmodel.so (example compile line: gcc -O3 -fPIC -fopenmp -shared — flags vary by lane and compiler), linked against libckernel_engine.so and libckernel_tokenizer.so. Each bundle carries a provenance stamp (.ck_ir_bundle.json, .ck_codegen_bundle.json, .ck_runtime_bundle.json) so stale artifacts are rebuilt instead of silently reused. |
| 6. Run + prove | ck_run_v8.py, X-Ray, certify_*_v8.py harnesses |
libmodel.so serves prefill and KV-cached decode through the exported CK ABI. X-Ray records the selected providers and compares layer/state/logit boundaries against an independent implementation; the certification harnesses gate promotion. |
When Compilation Fails, Start Here
Every stage above fails closed with a distinct signature. Match the symptom to the stage first, then open the referenced doc — the fault messages are designed to name the op, phase, or architecture that failed.
| Symptom | Failing stage | Start here |
|---|---|---|
| Converter rejects the model: unknown / unsupported architecture | 1. Convert | The converter error names the architecture it could not map. Check version/v8/model_maps/gguf_ck_map.json (GGUF) or safetensors_ck_map.json (safetensors): the arch → circuit mapping is data-driven and intentionally refuses to guess. Supporting a new family means adding a map entry plus version/v8/circuits/<arch>.json — not patching the converter. |
HARD KERNEL RESOLUTION FAULT — missing or ambiguous provider binding |
3. Resolve | The fault message names the op (and phase) that has zero or ambiguous providers. Read Kernel Maps — the resolution algorithm and the real resolver trace, then check the circuit's required binding against version/v8/kernel_maps/*.json; the anti-patterns table covers the failure shapes the system is built to reject. |
| Operation-class / phase mismatch on a circuit-bound provider (#453/#461) | 3. Resolve | The circuit bound a provider whose declared operation class or phase set does not cover the required use. Compare the circuit's required_contracts against the provider's numerical contract and phases — v8 Numerical Contracts (the Gemma3 case study walks a full binding). |
| Memory planning / arena validation failure (scratch sizing, alignment, offsets) | 3. Lower | Kernel Maps — memory planning: sizing formulas, power-of-two alignment, arena offsets, and the fail-closed validation rules. version/v8/scripts/audit_layout_dataflow_v8.py cross-checks the layout chain. |
C compiler errors building model_v8.c / libmodel.so |
5. Native compile | The native build log printed by ck_run_v8.py step 5. .ck_runtime_bundle.json in the model directory records the exact compiler and flags used; vary the toolchain with CK_V8_COMPILER / CK_V8_EXTRA_CFLAGS / CK_V8_EXTRA_LDFLAGS. |
python3 version/v8/tools/open_ir_visualizer_v8.py --generate <model> --html-only)
renders the generated pipeline as HTML; the audit scripts
(audit_kernel_map_interfaces_v8.py, audit_dsl_policy_v8.py, audit_layout_dataflow_v8.py)
report registry, dispatch-policy, and layout-chain health; X-Ray records provider
selections and boundary comparisons, and each model directory carries an ir_report.html plus the
.ck_*_bundle.json provenance stamps.
Planned, not built: regenerating the registry/audit counters on this page during the docs
build — until that pipeline exists, the snapshot numbers here are hand-maintained.
One Command Runs the Lane
Generate and run a model (GGUF example)
.venv/bin/python version/v8/scripts/ck_run_v8.py run \ hf://bartowski/google_gemma-3-270m-it-GGUF/gemma-3-270m-it-Q5_K_M.gguf \ --context-len 2048 \ --force-convert --force-compile \ --prompt 'Give me a concise example of C code.' \ --max-tokens 256 \ --temperature 0.0
Add --generate-only to stop after compile (convert → IR → codegen → libmodel.so) without running inference. Everything lands under ~/.cache/ck-engine-v8/models/<model>/ unless --run overrides it. More per-family invocations: v8 Runbook.
What the Generated C Looks Like
The generated model_v8.c is a flat sequence of resolved provider calls against named bump-allocator
offsets — the resolver's decisions made literal. Verbatim excerpt from the decode step of a locally generated
Gemma3 270M (Q5_K_M) runtime (~/.cache/ck-engine-v8/models/gemma-3-270m-it-Q5_K_M/model_v8.c,
produced by the ck_run_v8.py lane above; … marks elided debug-export lines):
model_v8.c — decode step, ops 0–2 (verbatim, elisions marked)
static void ck_decode(CKModel *model, int32_t token) {
uint8_t *MEM = (uint8_t*)model->bump;
/* … debug environment setup elided … */
/* Store token at offset 265119296 (from layout) */
*(int32_t*)(MEM + 265119296) = token;
/* Op 0: embedding_forward_q8_0 (dense_embedding_lookup) layer=-1 section=header */
embedding_forward_q8_0(
(int32_t*)(model->bump + A_TOKEN_IDS),
1, 262144,
(const void*)(model->bump + W_TOKEN_EMB),
NULL,
(float*)(model->bump + A_EMBEDDED_INPUT),
640, 640, 1, 0
);
/* … Gemma embedding scale contract (inp * sqrt(EMBED_DIM)) elided … */
/* Op 1: ck_memcpy_parallel_dispatch (residual_save) layer=0 section=body */
/* … debug export/import lines elided … */
ck_memcpy_parallel_dispatch(
(void*)(model->bump + A_RESIDUAL),
(const void*)(model->bump + A_EMBEDDED_INPUT),
2560
);
/* Op 2: rmsnorm_forward_llama_production (attn_norm) layer=0 section=body */
rmsnorm_forward_llama_production(
(const float*)(model->bump + A_EMBEDDED_INPUT),
(const float*)(model->bump + W_LAYER_0_LN1_GAMMA),
(float*)(model->bump + A_EMBEDDED_INPUT),
NULL, 1, 640, 640, 9.999999974752427e-07
);
/* … 18 layers, then final norm + tied Q8_0 vocab projection … */
Every call site names its provider (embedding_forward_q8_0, rmsnorm_forward_llama_production),
its op comment (dense_embedding_lookup, attn_norm), its layer, and the layout-resolved
buffer offsets (A_TOKEN_IDS, W_LAYER_0_LN1_GAMMA). The provider choice was frozen by the
resolver before GraphIR construction; codegen only transcribes it. The surrounding
ck_debug_export_hidden calls are the X-Ray/debug export chain — present in generated code, compiled
out unless requested.
Anatomy of One Generated Call: rmsnorm_forward_llama_production
Take Op 2 from the excerpt and trace it back through every layer of the pipeline. The call site is:
/* Op 2: rmsnorm_forward_llama_production (attn_norm) layer=0 section=body */
rmsnorm_forward_llama_production(
(const float*)(model->bump + A_EMBEDDED_INPUT), /* input */
(const float*)(model->bump + W_LAYER_0_LN1_GAMMA), /* gamma */
(float*)(model->bump + A_EMBEDDED_INPUT), /* output */
NULL, /* rstd_cache */
1, 640, 640, 9.999999974752427e-07 /* tokens, d_model, aligned, eps */
);
The circuit operation. The op comment names it: attn_norm at layer 0 — Gemma3's
pre-attention RMSNorm. The Gemma3 circuit does not ask for "an RMSNorm"; its
required_contracts pin the llama.cpp production schedule for the norm boundaries, and the resolver
binds exactly this provider (the mechanism is the one walked in the
Gemma3 schedule case study).
The kernel map. version/v8/kernel_maps/rmsnorm_forward_llama_production.json —
operation interface rmsnorm.fp32.v1, variant llama_cpu_production_fp32_output, FP32
activation/weight/output, numerical contract rmsnorm_llama_cpu_production_fp32_output
(validated, decode phase, serial over independent rows, FP64 scalar accumulation, deterministic).
The map owns the eight-argument call_abi; codegen emits it verbatim:
| C argument | Emitted value | Logical port / dim (map call_abi source) | Notes |
|---|---|---|---|
input |
model->bump + A_EMBEDDED_INPUT |
activation:input — fp32 [T, E], token-major contiguous |
The embedded (and Gemma-scaled) token from Op 0; a layout-resolved bump offset. |
gamma |
model->bump + W_LAYER_0_LN1_GAMMA |
weight:gamma (alt names ln1_gamma/ln2_gamma/final_ln_weight) — fp32 [E] |
Layer 0's pre-attention norm weight, read from weights.bump. |
output |
model->bump + A_EMBEDDED_INPUT |
output:output — fp32 [T, E] |
Same offset as input: the op normalizes in place. The alias is a circuit/lowering decision, valid here because the kernel reads each row before writing it. |
rstd_cache |
NULL |
output:rstd (optional scratch) — call_abi source null |
The rstd scratch port is optional and unbound in decode: no arena scratch is allocated and the kernel skips the store (if (rstd_cache)). Scratch ownership stays with the caller — nothing is malloc'd inside the kernel. |
tokens |
1 |
dim:seq_len |
Single-token decode; prefill emits the actual sequence length. |
d_model / aligned_embed_dim |
640 / 640 |
dim:embed_dim (both) |
Gemma3 270M hidden size; equal here because 640 needs no stride padding. |
eps |
9.999999974752427e-07 |
dim:rms_eps |
Gemma3's rms_norm_eps = 1e-6, as the nearest float32. |
The C implementation. src/kernels/rmsnorm_kernels.c:196 — ordered FP32 square,
FP64 scalar accumulation, scalar sqrt then scalar divide (the explicit boundary that keeps ICX from
substituting vrsqrt14ss + Newton refinement), then the fused scale by gamma.
Evidence on the map: unit test unittest/test_rmsnorm_llama_production.cpp, plus a bit-exact
parity oracle against llama.cpp's fused RMSNorm+mul CPU graph at widths 128/4096/5120 across 1/16/20/24
threads. That chain — circuit requirement → map-owned ABI → generated call → tested kernel —
is the whole v8 codegen story in one function.
How the Pieces Stitch
Circuits select the logical
version/v8/circuits/*.json choose the op graph and the required numerical contracts — including composition via extends/components/stitch (composite circuits) and schedule pinning (the Gemma3 schedule case study). A circuit states requirements, never provider IDs.
Kernel maps own the physical
The 364 maps in version/v8/kernel_maps/ own provider requirements: port dtypes, layouts, phases, ISA variants, priority inside one equivalence group — and the exact C call ABI. See Kernel Maps and Provider Selection.
The DSL resolves — and must not guess
build_ir_v8.py matches requirements to maps mechanically. Zero matches: unsupported. Ambiguous tie: fault. Wrong operation class or phase on a circuit-bound provider: fault (#453/#461). There is no heuristic fallback and no model-name dispatch — the dsl_policy ratchet bans it.
Codegen emits
codegen_v8.py + codegen_core_v8.py + codegen_prefill_v8.py transcribe the lowered call IR into model_v8.c exactly as the map-owned ABI dictates, then the native compiler produces libmodel.so. X-Ray and the parity harnesses prove the composition afterwards.
What v8 Made Model-Agnostic
Adding a model family used to mean touching the compiler. In v8 the family-specific knowledge lives in data, and the shared compiler is ratcheted against re-growing model dispatch:
- Architecture → circuit is JSON-data-driven. The converters resolve the model architecture through
version/v8/model_maps/gguf_ck_map.json/safetensors_ck_map.jsonand fail hard on anything unknown — a new family adds a map entry and a circuit file, not a converter branch. - Circuit-required kernel bindings. Pattern-sensitive providers (the RoPE application pattern, MLA provider classes) are bound by the circuit and validated for operation class and phase by the resolver — merged in #453 and #461. The compiler enforces the binding; it does not infer it from the model name.
- The
dsl_policyratchet.version/v8/dsl_policy.jsonforbids model-family literals (qwen,gemma,llama, …) and dispatch keys (model,arch,family, …) in protected compiler functions, with per-file caps on the remaining allowlisted literal sites.version/v8/scripts/audit_dsl_policy_v8.pyenforces it and fails on regression. - The advisory novelty report.
version/v8/scripts/report_model_novelty_v8.pymeasures how much a family bring-up changes outside its own circuit, maps, kernels, and evidence — the core-compiler bucket's target trend is zero. Advisory only; not a CI gate.
dsl_policy audit — current actuals
Run: python3 version/v8/scripts/audit_dsl_policy_v8.py at commit f7aedf6e4 (snapshot 2026-09-12):
| Metric | Value |
|---|---|
| status | pass (0 findings, 11 checked functions) |
model-literal sites: build_ir_v8.py | 20 (cap 24) |
model-literal sites: codegen_core_v8.py | 11 (cap 11) |
model-literal sites: codegen_prefill_v8.py | 9 (cap 9) |
model-literal sites: codegen_v8.py | 0 (cap 0) |
ck_debug_export_hidden debug-export chains visible in the generated excerpt are
another: family-specific X-Ray probe names baked into emission. And the tokenizer contract is an explicit
exemption — _generate_tokenizer_c_code and _resolve_tokenizer_contract are excluded
from the dispatch ban in dsl_policy.json. None of this is hidden from the audit; all of it is
counted, capped, and only allowed to shrink.
Registry census from version/v8/kernel_maps/KERNEL_REGISTRY.json (364 maps across 126 ops) and
audit_dsl_policy_v8.py output at commit f7aedf6e4 (2026-09-12). These numbers are
hand-maintained until the docs build regenerates them; treat them as a dated snapshot, not a live counter.
Appendix: Legacy v7 Codegen (Training Lane)
It describes the
CKKernelSpec/CKBufferSpec/CKPlanStep registry and the
generated forward/backward passes used by the v7 training lane — a different system from the v8 pipeline
above (fail-closed kernel maps, staged IR, model_v8.c). It is kept because it still documents
v7 training; for the current v7 flow see the v7 Inference + Training Runbook,
and for the v8 successor to the registry see Kernel Maps and Provider Selection.
Legacy v7 codegen documentation (config → IR → forward/backward C)
C-Kernel-Engine can generate optimized C runtime code from HuggingFace model configs. This enables running models with zero Python overhead.
Pipeline Overview (v7)
1. Config Parsing
Load a HuggingFace-style config.json and extract model dimensions.
ck_model_config_from_hf_json(
"config.json",
&cfg
);
2. IR Building
Construct an intermediate representation graph of all operations.
ck_build_decoder_ir(
&cfg,
&forward_graph
);
ck_build_decoder_backward_ir(
&forward_graph,
&backward_graph
);
3. Code Emission
Generate complete C code with forward and backward passes.
ck_codegen_emit_runtime(
&forward_graph,
"model.c"
);
Quick Start (v7)
Generate from Config
# Build the codegen tool make build/ck_ir_demo # Generate C runtime from a HuggingFace config ./build/ck_ir_demo path/to/config.json --emit build/model.c # Or use the make target make ck-emit CONFIG=path/to/config.json OUT=build/model.c
Library Mode (Prefill + Decode)
Library mode emits a generated runtime with an exported ABI suitable for dlopen/ctypes.
# Emit a model runtime with exported symbols (prefill + KV-cache decode + training entry points) ./build/ck_ir_demo path/to/config.json --emit build/model.c --emit-lib # The generator also writes build/model.c.kernels (one kernel .c per line) cc -O3 -fPIC -fopenmp -shared -Iinclude -o build/libmodel.so build/model.c $(cat build/model.c.kernels) -lm
Inference sequence: enable KV cache → prefill once → decode token-by-token. Decode is disabled when training is enabled.
Input: Model Config (v7)
HuggingFace config.json
The codegen accepts standard HuggingFace model configurations:
{
"architectures": ["LlamaForCausalLM"],
"hidden_size": 768,
"num_attention_heads": 12,
"num_key_value_heads": 4,
"num_hidden_layers": 6,
"intermediate_size": 2048,
"vocab_size": 32000,
"max_position_embeddings": 2048,
"rms_norm_eps": 1e-5,
"rope_theta": 10000.0
}
Parsed CKModelConfig
typedef struct {
int hidden_size; // 768
int num_attention_heads; // 12
int num_key_value_heads; // 4 (for GQA)
int num_hidden_layers; // 6
int intermediate_size; // 2048
int vocab_size; // 32000
int max_position_embeddings; // 2048
float rms_norm_eps; // 1e-5
float rope_theta; // 10000.0
} CKModelConfig;
Kernel Map: Single Source of Truth (v7 registry)
Every kernel is tested individually with PyTorch parity. The codegen uses the exact same kernels that passed tests. No drift, no duplication.
For the v8 successor — hardened, schema-validated kernel maps with fail-closed provider selection — see Kernel Maps and Provider Selection. The map format below is the older v7-era registry.
CKKernelSpec: Kernel Registry
Each kernel maps to its tested C implementation:
const CKKernelSpec ck_kernel_specs[] = {
{"rmsnorm", "rmsnorm_forward", "rmsnorm_backward",
{"src/kernels/rmsnorm_kernels.c", NULL, ...}},
{"attention", "attention_forward_causal_head_major_gqa",
"attention_backward_causal_head_major_gqa",
{"src/kernels/attention_kernels.c",
"src/kernels/softmax_kernels.c", NULL, ...}},
{"swiglu", "swiglu_forward", "swiglu_backward",
{"src/kernels/swiglu_kernels.c",
"src/kernels/sigmoid_kernels.c", NULL, ...}},
};
CKBufferSpec: Buffer Definitions
All buffers with symbolic dimensions, roles, and conditions:
const CKBufferSpec ck_decoder_buffers[] = {
// Global buffers
{"token_emb", CK_SCOPE_GLOBAL, CK_ROLE_WEIGHT,
{{CK_DIM_VOCAB, 1, 1}, {CK_DIM_ALIGNED_EMBED, 1, 1}, ...}},
{"lm_head_weight", CK_SCOPE_GLOBAL, CK_ROLE_WEIGHT,
{{CK_DIM_VOCAB, 1, 1}, {CK_DIM_ALIGNED_EMBED, 1, 1}, ...},
.alias_of = "token_emb"}, // tied embeddings
{"rope_cos_cache", CK_SCOPE_GLOBAL, CK_ROLE_ACTIVATION,
{{CK_DIM_TOKENS, 1, 1}, {CK_DIM_HEAD_DIM, 1, 2}, ...},
.condition = "rope_theta"}, // only if RoPE enabled
// Per-layer buffers
{"q", CK_SCOPE_LAYER, CK_ROLE_OUTPUT,
{{CK_DIM_NUM_HEADS, 1, 1}, {CK_DIM_TOKENS, 1, 1},
{CK_DIM_ALIGNED_HEAD, 1, 1}, ...}},
};
CKPlanStep: Execution Plan
Forward pass as a sequence of kernel invocations:
const CKPlanStep ck_decoder_forward_plan[] = {
{"rmsnorm", NULL}, // LN1
{"qkv_project", NULL}, // Q, K, V projections
{"rope", "rope_theta>0"}, // RoPE (conditional)
{"attention", NULL}, // Self-attention
{"attn_proj", NULL}, // Output projection
{"residual_add", NULL}, // Residual connection
{"rmsnorm", NULL}, // LN2
{"mlp_up", NULL}, // FC1: hidden → 2×intermediate
{"swiglu", NULL}, // SwiGLU activation
{"mlp_down", NULL}, // FC2: intermediate → hidden
{"residual_add", NULL}, // Residual connection
};
How Codegen Uses This
- Read execution plan (
ck_decoder_forward_plan) - For each step, lookup kernel in
ck_kernel_specs - Emit source files from
sources[] - Emit function call using
forward/backward - Bind buffers from
ck_decoder_buffers
Why This Matters
- No duplication: codegen uses tested kernels
- No drift: one source of truth
- Composable: add new kernels to registry
- Conditional: RoPE, bias, etc. are optional
- Typed: symbolic dims, roles, conditions
Registry Flow: How It All Fits Together (v7)
The Complete Pipeline
| Step | Component | What It Does |
|---|---|---|
| 1 | config.json |
HuggingFace model config with dimensions (hidden_size, num_heads, etc.) |
| 2 | CKIRGraph |
Parse config into IR with operations and tensor shapes |
| 3 | CKKernelSpec[] |
Lookup kernel name → get forward/backward function + source files |
| 4 | CKBufferSpec[] |
Get buffer shapes, roles, dtypes → compute memory layout |
| 5 | CKPlanStep[] |
Execute kernels in order, respecting conditions |
| 6 | Bump Allocator |
Resolve symbolic dims → concrete byte offsets (dtype-aware) |
| 7 | Source Emitter |
Emit tested kernel sources + function call wiring |
| 8 | generated_model.c |
Standalone C file: gcc -O3 model.c -o model |
The generated
model.c contains the exact same kernel code that passed PyTorch parity tests. The registry ensures no code is written twice.
Adding a New Kernel
To add a new kernel (e.g., GeLU activation):
// 1. Write and test the kernel
// src/kernels/gelu_kernels.c
void gelu_forward(const float *in, float *out, int n);
void gelu_backward(const float *in, const float *d_out, float *d_in, int n);
// 2. Add to kernel specs
{"gelu", "gelu_forward", "gelu_backward",
{"src/kernels/gelu_kernels.c", NULL, ...}},
// 3. Add buffers if needed
{"gelu_out", CK_SCOPE_LAYER, CK_ROLE_OUTPUT,
{{CK_DIM_TOKENS, 1, 1}, {CK_DIM_ALIGNED_EMBED, 1, 1}, ...}},
// 4. Add to execution plan (for models that use GeLU)
{"gelu", "activation_type==gelu"},
Codegen automatically picks up the new kernel. No other changes needed.
Intermediate Representation (v7)
The IR layer decouples config parsing from code generation, enabling optimizations and different backends.
IR Graph Structure
typedef struct {
CKOpType op; // CK_OP_RMSNORM, CK_OP_ATTENTION, etc.
int layer_index;
int input_ids[4]; // References to input nodes
int output_id; // Output node ID
// ... dimension info
} CKIRNode;
typedef struct {
CKModelConfig config;
CKIRNode *nodes;
int num_nodes;
int num_layers;
} CKIRGraph;
Supported Operations
| Op Type | Forward | Backward |
|---|---|---|
CK_OP_RMSNORM |
rmsnorm_forward | rmsnorm_backward |
CK_OP_ATTENTION |
attention_forward_causal_head_major_gqa | attention_backward_causal_head_major_gqa |
CK_OP_ROPE |
rope_forward_qk | rope_backward_qk |
CK_OP_SWIGLU |
swiglu_forward | swiglu_backward |
CK_OP_LINEAR |
gemm_blocked_serial | fc_backward_kernel |
CK_OP_RESIDUAL |
ck_add_inplace | (gradient passthrough) |
Output: Generated Code (v7)
Generated Forward Pass
void forward_layer_0(
const float *input,
const ModelWeights *weights,
LayerActivations *acts,
const float *cos_cache,
const float *sin_cache,
int num_tokens
) {
// 1. Pre-attention RMSNorm
rmsnorm_forward(input, weights->ln1_gamma, acts->ln1_out,
acts->rstd1, num_tokens, 768, 768, 1e-5f);
// 2. QKV projection
ck_qkv_project_head_major(acts->ln1_out,
weights->wq, weights->bq,
weights->wk, weights->bk,
weights->wv, weights->bv,
acts->q, acts->k, acts->v,
num_tokens, 768, 12, 4, 64);
// 3. Apply RoPE
rope_forward_qk(acts->q, acts->k, cos_cache, sin_cache,
12, 4, num_tokens, 64, 0);
// 4. Attention
attention_forward_causal_head_major_gqa(
acts->q, acts->k, acts->v,
acts->scores, acts->attn_out,
12, 4, num_tokens, 64, 64, 2048);
// 5. Output projection + residual
ck_attention_project_head_major(acts->attn_out, weights->wo, weights->bo,
acts->proj_out, acts->scratch,
num_tokens, 768, 12, 64);
ck_add_inplace(acts->proj_out, input, num_tokens, 768);
// 6-8. MLP block...
}
Generated Backward Pass
void backward_layer_0(
const float *d_output,
const ModelWeights *weights,
const LayerActivations *acts,
WeightGradients *grads,
float *d_input
) {
// Reverse order of forward pass
// 1. Backward through MLP residual
// d_mlp_out = d_output (residual gradient passthrough)
// 2. Backward through FC2
fc2_backward_kernel(d_output, acts->swiglu_out, weights->w2,
d_swiglu, grads->d_w2, grads->d_b2, ...);
// 3. Backward through SwiGLU
swiglu_backward(acts->fc1_out, d_swiglu, d_fc1, num_tokens, 2048);
// ... continue backwards through all ops
// N. Backward through RMSNorm 1
rmsnorm_backward(d_rmsnorm, input, weights->ln1_gamma,
acts->rstd1, d_input, grads->d_ln1_gamma,
num_tokens, 768, 768);
}
IR Serialization (v7)
Export IR to JSON
ck_ir_serialize_json(
&graph,
"model_ir.json"
);
Enables inspection, debugging, and external tooling.
Load IR from JSON
ck_ir_parse_json(
"model_ir.json",
&graph
);
Two-stage pipeline: generate IR once, emit code multiple times.
Memory Layout (v7)
Buffer Allocation
The codegen calculates all buffer sizes based on config dimensions:
layout_transformer_from_ir(&model, &ir); // Computed sizes: // - Weight memory: embeddings + all layer weights // - Activation memory: per-layer intermediates // - KV cache: for autoregressive generation
| Buffer | Size Formula |
|---|---|
| Q | num_heads × max_tokens × head_dim |
| K, V | num_kv_heads × max_tokens × head_dim |
| Attention scores | num_heads × max_tokens × context_window |
| MLP hidden | max_tokens × 2 × intermediate_size |
| Layer weights | num_layers × (QKV + O + MLP weights) |
Bump Allocator: Dtype-Aware Layout (v7)
All memory is allocated in one contiguous block. Offsets are computed at codegen time based on dtype. No runtime malloc, clean strides, cache-friendly.
Mixed Precision Layout
Weights can be bf16/int8 while activations stay fp32:
// Generated at codegen time - dtype-aware size_t off = 0; // Weights: bf16 (2 bytes each) m->token_emb_offset = off; off += align64(V * aligned_embed * 2); // bf16 m->wq_offset = off; off += align64(num_heads * head_dim * embed * 2); // bf16 // Activations: fp32 (4 bytes each) m->embedded_input_offset = off; off += align64(T * aligned_embed * 4); // fp32 m->q_offset = off; off += align64(num_heads * T * head_dim * 4); // fp32
Dtype Enum
typedef enum {
CK_DTYPE_F32 = 0, // 4 bytes
CK_DTYPE_F16, // 2 bytes
CK_DTYPE_BF16, // 2 bytes
CK_DTYPE_I8, // 1 byte
CK_DTYPE_I4, // 0.5 bytes (packed)
} CKDType;
Per-Buffer Dtype
{"wq", CK_SCOPE_LAYER, CK_ROLE_WEIGHT,
CK_DTYPE_BF16, // weights in bf16
{{CK_DIM_NUM_HEADS, ...}}},
{"q", CK_SCOPE_LAYER, CK_ROLE_OUTPUT,
CK_DTYPE_F32, // activations in fp32
{{CK_DIM_NUM_HEADS, ...}}},
All Offsets Cacheline-Aligned
Every buffer starts at a 64-byte boundary for optimal SIMD access:
static size_t align64(size_t n) {
return (n + 63) & ~63;
}
static size_t bump(size_t *off, size_t bytes) {
size_t start = align64(*off);
*off = start + bytes;
return start;
}
Source Files (v7)
| File | Purpose |
|---|---|
ckernel_ir.c |
IR graph building, serialization |
ckernel_codegen.c |
C code emission from IR |
ckernel_kernel_specs.c |
Kernel map: specs, buffers, execution plan |
ckernel_model_layout.c |
Bump allocator, memory layout |
ckernel_model_load.c |
Weight loading from files |
ckernel_registry.c |
Op validation, kernel lookup |
For full source code, see the Doxygen source browser.
Related: Kernel Maps and Provider Selection, Composite Circuits, v8 Numerical Contracts — Gemma3 schedule case study, System Architecture, X-Ray, GGUF to Bump, v8 Runbook, and the v7 Inference + Training Runbook.