Few-Layer Numeric Validation#
This guide explains the few-layer numeric validation — an early-fail smoke test that checks whether the EdgeLLM engine still produces the right numbers, without waiting for a full model run.
It runs only the first N decoder layers (default 4) of a checkpoint through two producers and compares them:
a PyTorch golden (a high-confidence reference, not an oracle), and
EdgeLLM engine inference,
over a prefill plus a few greedy decode rounds. It compares per round, per sequence, per layer:
logits (last real token),
KV cache for attention layers, and
recurrent + conv state for Mamba / Gated-DeltaNet hybrid layers.
A numeric regression then shows up in seconds instead of after a full generation.
Quick Start#
Assuming you are in the repo working directory with EdgeLLM built (build/) and its
venv active:
./scripts/few-layer-validation.sh \
--model /path/to/checkpoint \
--num-layers 4 \
--cos 0.99
Flag |
Meaning |
Default |
|---|---|---|
|
HuggingFace / ModelOpt checkpoint directory (or HF repo id). |
(required) |
|
Number of leading decoder layers to validate. |
|
|
Minimum per-tensor cosine similarity; the run fails below it. |
|
|
|
|
|
|
|
|
Engine |
|
|
Engine |
|
|
Keep the temporary work directory (golden / engine / dump) for inspection. |
(off) |
The script’s exit code is the PASS/FAIL gate (0 = pass). Use it both locally during
development and as the body of the CI test.
It also accepts --build-dir and --python to point at a non-default build dir or
interpreter (handy when working across machines); they default to <repo>/build and
python3 and are omitted here to keep the common case simple.
Each round prints the minimum cosine (and the tensor that hit it), the maximum absolute
difference, and an allclose flag at the given --atol / --rtol; the final line reports
the worst cosine over the whole run alongside the thresholds. --verbose adds a per-tensor
breakdown.
What it does#
The script runs five stages, all into a temporary work directory:
PyTorch golden — runs the first
Nlayers of the checkpoint and dumps per-round logits + KV / recurrent / conv state, plus the token sequence it samples (tests/golden_layer_dump.py). For quantized checkpoints the recipe-quantized projections are swapped for fake-quant linears that reuse the same ops the export emits, so the golden matches the engine’s quantized numerics (tests/golden_quant_linears.py).Export —
tensorrt_edgellm.scripts.export ... --num-decoder-layer Nexports only the firstNlayers to ONNX.Build —
llm_buildbuilds the engine. The KV-cache capacity is kept small on purpose (see Dump size, below).Inference + dump —
llm_inferenceruns with the dump enabled and is teacher-forced with the golden’s token sequence (see Runtime hooks), so both sides decode the identical sequence; it writes a single safetensors file of all rounds.Compare —
tests/compare_layer_dumps.pyreconciles the two dumps and checks every tensor against the cosine threshold.
Runtime hooks#
The dump and teacher-forcing are driven by environment variables and are no-ops unless set, so they add no overhead to a normal run:
Variable |
Effect |
|---|---|
|
Number of leading decoder layers |
|
Output directory for the dump file. |
|
Optional teacher-forcing: a file of per-sequence token ids. When set, the decode loop replays these tokens instead of its own sampled ones, so the run follows the golden token-for-token. |
|
Force a fixed generation length (do not stop at EOS). |
The two EDGELLM_DUMP_LOGITS_KVCACHE_* variables are XOR-coupled: setting exactly one is
an error. Teacher-forcing is only active alongside a dump and logs a warning, since it
overrides the model’s own sampled tokens.
Why teacher-forcing?#
Without it, each side greedily samples its own next token. Greedy decoding is sensitive to near-tie argmax flips: a tiny numeric difference can pick a different token, after which the two sides decode unrelated sequences and every later round compares mismatched states — the comparison stops meaning anything.
Teacher-forcing feeds the golden’s tokens to the engine so both sides process the same
sequence. The handoff is automatic: the golden (stage 1) records the tokens it samples, the
script writes them to a force-tokens file, and the engine (stage 4) replays them via
EDGELLM_FORCE_TOKENS_FILE — you never write that file by hand. The dump still records the
token the engine would have sampled, so a genuine divergence is still visible.
Dump size#
The engine dumps each per-layer tensor full-length over the active batch (a plain
copy) and lets the comparison slice each sequence to its valid length in PyTorch — this
keeps the C++ side simple. The KV-cache sequence dimension therefore equals
maxKVCacheCapacity, so the dump size scales with it directly. The script keeps
maxInputLen / maxKVCacheCapacity small (the validation uses short, fixed prompts),
which keeps the dump at a few hundred MB rather than multiple GB. If you point the script
at long prompts, raise both — but note the dump grows with the cap.
Scope#
This is a base-model + vanilla-decoding smoke test. It does not cover the
speculative-decoding variants (EAGLE / MTP / DFlash); combining --num-decoder-layer with
those is rejected.