Test strategy
Three-tier verification. Each tier has specific coverage, tolerance, and speed budget. A runtime claims correctness only when all three tiers pass for a given model + backend combination.
Tier 1: Op unit tests
Each op from ops.md has a dedicated test with golden values that any implementation must match.
Structure
tests/ops/<op_name>/
inputs.json # fixed seeded inputs
expected.json # golden outputs from reference (HF PyTorch f32)
test.rs # loads inputs, runs op on each backend, compares
Golden value provenance
Reference = HuggingFace PyTorch f32. One-time dump into expected.json
by a Python script (reference_dump.py in the repo). Committed to
source control.
Cross-check against llama.cpp f32 where the op exists there (matmul, rmsnorm, rope, sdpa). Discrepancies between HF and llama.cpp are resolved case-by-case and documented inline.
Tolerance
Per-op, per-dtype:
| Op | F32 | F16 | Q4_K |
|---|---|---|---|
| Matmul | 1e-6 | 1e-3 | 1e-2 |
| RmsNorm | 1e-6 | 1e-3 | N/A (not quantized) |
| Rope | 1e-6 | 1e-3 | N/A |
| Sdpa | 1e-5 | 1e-2 | N/A (activations) |
| Softmax | 1e-6 | 1e-3 | N/A |
| Silu / Gelu | 1e-6 | 1e-3 | N/A |
| Quantize round-trip | see quant.md |
Size
Op tests use small tensors (typically N=K=D=32 or 64). Execution is milliseconds. Entire Tier 1 suite runs in < 30 seconds.
Must-run
Every backend that claims to implement an op runs Tier 1 for that op.
Tier 2: Layer composition tests
Verify that ops composed into a layer produce outputs matching the reference. Catches bugs that don't surface in isolated ops — e.g. wrong KV cache read order, RoPE applied to wrong tensor half, skip connection before/after norm.
Structure
Per family (LlamaStyle, BertStyle, etc.), one test per layer primitive:
tests/layers/decoder/
attention_layer.rs # embed → norm → QKV → rope → sdpa → o_proj → skip
ffn_layer.rs # norm → gate/up → silu+mul → down → skip
full_block.rs # attention_layer + ffn_layer
with_qk_norm.rs # Qwen3 variant
with_gqa.rs # GQA with num_heads > kv_heads
Golden values
Dumped from HuggingFace transformers running in f32. For each test: given specific weight values (small ones, random seeded) and input activation, run the layer and save output.
Implementations on each backend must match to within tolerance.
Tolerance
Layer composition accumulates op errors. Tolerance scales with expected op count:
| Layer type | F32 | F16 |
|---|---|---|
| Single op | 1e-6 | 1e-3 |
| Attention layer (~7 ops) | 1e-5 | 5e-3 |
| Full decoder block (~15 ops) | 5e-5 | 1e-2 |
Size
Layer tests use realistic dimensions (e.g. hidden=1024, num_heads=16, head_dim=64). Single forward over 1-4 tokens. Runs in seconds.
Tier 3: Model golden tests
End-to-end. For each supported model, a small set of prompts with known-correct outputs.
Structure
tests/models/<model_name>/
config.toml # redundant with .model but explicit
goldens.json # [{prompt, expected_tokens, expected_text, tolerance}]
test.rs # loads, runs, compares
Prompts
Fixed canonical prompts designed to:
-
Trigger all sub-architectures: math (exercises reasoning), special tokens (tests EOS emission), code (tests long generation), language-switching (tests full vocab).
-
Have deterministic greedy outputs: temperature=0, golden is the exact token sequence.
Example:
Correctness source
Reference runs:
- HuggingFace transformers f32 (primary)
- llama.cpp with matching GGUF (cross-check)
Both must agree on the golden. Divergence documented and investigated.
Tolerance
Exact token match for greedy (temperature=0) is the goal. If reference tokenization differs across libraries, fallback to "contains_one_of" with enumerated acceptable outputs.
For temperature > 0, tests are not deterministic → skipped in this tier.
Coverage requirement
v1 acceptance (scope.md): at least one model per modality in Tier 3 tests, passing on every supported backend.
Tier 0: Import round-trip
Before Tiers 1-3 make sense, imports must be faithful.
Structure
For each source-format / model combination:
- Import source →
.model. - Read back
.model, dequantize tensors. - Dequantize source tensors (GGUF → f32 via llama.cpp, safetensors → f32 via Python).
- Compare tensor-by-tensor within quant.md.
Passes when
Every imported tensor matches source-dequantized within per-dtype tolerance. No tensor dropped, no name mismatch, no shape mismatch.
CI structure
Each commit runs:
- All Tier 0 (import round-trip) — must pass
- All Tier 1 (op unit tests) — must pass on CPU reference and default backend for the platform (wgpu+rs everywhere, honeycrisp on macOS)
- Tier 2 for curated families with implementation — must pass
- Tier 3 for models in a "fast" subset (small ones, <1B params) — must pass
Nightly / release runs:
- Tier 3 full: every model in manifest, every backend
- Stress test: same prompt 1000× with temperature=0 — must produce bit-identical output (catches nondeterminism)
Proving a bug
When a bug is suspected, reproduce by narrowing through tiers:
- Does Tier 3 pass? No → identify divergence point.
- Run Tier 2 for the layer containing the divergence. Which layer first shows off-by-ε values?
- Run Tier 1 for the ops in that layer. Which op produces wrong output?
- Fix that op. Re-run all tiers.
A bug that doesn't show in Tier 1 or Tier 2 but shows in Tier 3 is usually a composition bug (wrong buffer wiring, wrong op order, state pollution across forward calls) — Tier 2 should grow a new test that reproduces it.
Coverage tracking
cyb-llm test --report produces:
Tier 0: import/gguf/qwen3-0.6b ✓ (312 tensors, max_diff 1.4e-2)
Tier 1: op/rmsnorm ✓ (f32 ε<1e-6, f16 ε<9e-4)
Tier 1: op/sdpa ✓
Tier 2: layers/decoder/attention_layer ✗ at qk_norm output (first divergence 3.2e-2)
Tier 3: models/qwen3-0.6b ✗ (divergence at token 4)
Summary: 5/26 models passing Tier 3.
Regressions in report → block release.
Running locally
cyb-llm test # all tiers, current backend
cyb-llm test --tier 1 # just op tests
cyb-llm test --backend wgpu+rs # verify portable path
cyb-llm test --model qwen3-0.6b # specific model, all tiers
Responsibility
- Runtime author writes Tier 1 + Tier 2 when adding an op or family.
- Import author writes Tier 0 when adding a source format mapping.
- Model onboarding adds Tier 3 goldens per model.
A PR that adds a feature but not tests is incomplete.