Skip to content

colibri: add CONSIST=1 prefill/decode self-consistency check - #1555

Open
phreakocious wants to merge 1 commit into
JustVugg:devfrom
phreakocious:feat/prefill-decode-consistency
Open

phreakocious wants to merge 1 commit into
JustVugg:devfrom
phreakocious:feat/prefill-decode-consistency

Conversation

@phreakocious

Copy link
Copy Markdown

Summary

Prefill and decode must agree on the same positions. Arm A pushes the whole sequence through step_all in one batched prefill; arm B prefills a prefix and then walks the continuation one token at a time through the KV cache, exactly as generate() does. Both arms share weights, so any disagreement beyond float accumulation order is a KV-addressing or masking defect in one of them.

It needs no reference predictions β€” no tf_pred, no second implementation β€” because it compares the engine against itself. Tokens come from either source:

SNAP=<model> REF=<ref.json> CONSIST=1 ./colibri 64 16 16   # prompt_ids/full_ids
SNAP=<model> PROMPT="..."   CONSIST=1 ./colibri            # no ref file at all

The PROMPT form is what lets this run on a model no fixture exists for β€” including quantizations and backends no CI runner can build a reference for. It tokenizes with the same [gMASK]<sop> prefix run_text applies (skip that and the sequence is out-of-distribution, both arms agree on garbage, and the check cannot fail), then splits at CONSIST_NP, default halfway.

It also covers COLI_PREFILL_CHUNK, which only arm B honours, and whose single-shot equivalence was previously asserted only in a comment on step().

Why the gate is the relative gap

The two failure classes separate cleanly by magnitude: reordered f32 accumulation over the hidden dim lands near D*eps, a misaddressed KV row lands at O(1). Argmax flips are reported but deliberately not gated β€” a flip requires |a[ia]-a[ib]| < 2*gap by construction, so "the flip is explained by the gap" holds for every flip and would be an assertion that cannot fail.

Evidence

Measured on GLM-5.2, 744B int4-gs64, D=6144, 15 tokens split 8/7, CPU path:

tolerance worst relative gap result exit
1e-2 (default) 3.902e-04 CONSIST OK 0
1e-6 2.762e-04 CONSIST FAIL 1

D*eps at D=6144 is 7.3e-04, so the observed gap sits just under the float-reordering noise floor and ~3 orders of magnitude below where a KV defect would land.

The second row is a positive control: the gate fires and exits non-zero, so the passing result is not an assertion that cannot fail. The two gaps differ between runs because expert cache state changes accumulation order; both sit far below the default tolerance.

Validation

  • make -C c check β€” 921 tests, OK (85 skipped), exit 0. Builds with 0 warnings.
  • CUDA changes were tested with make -C c cuda-test β€” n/a, no CUDA code touched
  • Performance claims include hardware, commands, and repeatable measurements β€” n/a, no performance claims
  • Performance claims include a validated experiment manifest with raw evidence β€” n/a, no performance claims

Compatibility

  • The default CPU build remains dependency-free β€” one C file, no new headers or dependencies
  • No model files, generated binaries, or benchmark artifacts are included

The existing REF-driven path is unchanged; PROMPT mode previously returned before reaching the CONSIST branch, so nothing that worked before behaves differently.

The same positions evaluated two ways must produce the same logits. Arm A
pushes the whole sequence through step_all in one batched prefill; arm B
prefills a prefix and then walks the continuation one token at a time through
the KV cache, exactly as generate() does. The two arms share weights, so a
disagreement beyond float accumulation order is a KV-addressing or masking
defect in one of them.

It needs no reference PREDICTIONS -- no tf_pred, no second implementation --
because it compares the engine against itself. Tokens come from either source:

    SNAP=<model> REF=<ref.json> CONSIST=1 ./colibri 64 16 16   # prompt_ids/full_ids
    SNAP=<model> PROMPT="..."   CONSIST=1 ./colibri            # no ref file at all

The PROMPT form is what lets the check run on a model no fixture exists for. It
tokenizes with the same [gMASK]<sop> prefix run_text applies -- skip that and
the sequence is out-of-distribution, both arms agree on garbage, and the check
cannot fail -- then splits at CONSIST_NP (default halfway).

The gate is the largest RELATIVE logit gap, which is what separates the two
failure classes: reordered f32 accumulation over the hidden dim lands near
D*eps, a misaddressed KV row lands at O(1). Argmax flips are reported but NOT
gated: a flip requires |a[ia]-a[ib]| < 2*gap by construction, so "the flip is
explained by the gap" holds for every flip and would be an assertion that
cannot fail.

Measured on GLM-5.2, 744B int4-gs64, D=6144, 15 tokens split 8/7:

    CONSIST_TOL=1e-2 (default)  worst relative gap 3.902e-04   OK,   exit 0
    CONSIST_TOL=1e-6            worst relative gap 2.762e-04   FAIL, exit 1

D*eps at D=6144 is 7.3e-04, so the observed gap sits just under the float
reordering noise floor and ~3 orders of magnitude below where a KV defect would
land. The second run is the positive control: the gate fires and exits non-zero,
so the passing result is not an assertion that cannot fail. The two gaps differ
because expert cache state changes accumulation order between runs; both sit far
below the default tolerance.

make check passes (921 tests, 85 skipped).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant