Skip to content

feat(engine): replace the ablation scoring mode with a checked, digest-bound one - #1356

Open
monotophic wants to merge 5 commits into
JustVugg:devfrom
monotophic:evidence/ablate-block
Open

monotophic wants to merge 5 commits into
JustVugg:devfrom
monotophic:evidence/ablate-block

Conversation

@monotophic

@monotophic monotophic commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Authored by Opus 5 in Claude Code, analysis in partnership with @monotophic

Revised 2026-09-17 in response to review: 15 files and +5,449 down to 12 files and +3,706, a 1,743-line
reduction.
The AblateWriter vtable is collapsed to a plain FILE * as asked, the cap now bounds what its name claims, and tools/check_ablate_evidence.py with its release-archive gate have moved here from #1355, where they had no writer.

ABLATE_SCORE=<manifest> replaces the decode with a teacher-forced sweep: for each item it configures the ablation cells, runs one prefill, and reads the final logits at every target position. That mode already exists on dev.
What it cannot do is produce evidence anyone can check. It accepts whatever a permissive whitespace scan can make of a line, skips lines it cannot parse and carries on, silently truncates an over-long cell list, and writes
records naming neither the config nor the manifest they came from. A partial run looks exactly like a complete one
β€” it exits 0 after writing nothing but a header.

This PR replaces that mode's implementation, and the change is visible to anyone parsing its output. The
artifact's schema goes from coli-ablate/1 to coli-ablate/2, the mode's exit status is now its own result
instead of a constant 0, and inputs the old mode would have partly executed are now refused as a whole.
docs/ENVIRONMENT.md describes the mode as it now behaves.

On replacing the mode rather than adding an alternative β€” the question is answered. The review asked whether
ABLATE_SCORE is used by anyone outside our tree, and pre-committed: if not, replacing it is right. We conducted
a search of the project's public GitHub and found nothing
β€” all upstream references, 53 issues and discussions,
and public code search. That is the strongest claim the evidence supports; it is not a claim of no use anywhere.
One fact offered without a recommendation: the mode was written by @jeswr, still an active contributor here.
We have not contacted them β€” that is the maintainer's call.

What the new mode gives you: the manifest is a defined format with an exact field count and single-space
separators, so the same content always produces the same digest; every field is bounded against the loaded config
before anything runs; anything outside that is refused as a whole, naming the record at fault, including a
duplicate item id. A CRLF terminator and a missing final newline are both accepted and normalised β€” a host editor
produces them without meaning to β€” and all three framings of the same content bind the same digest. The artifact
gains the digests of the config and the manifest, the item and target counts it expects, and a terminal record
carrying the counts actually completed, so a truncated file is detectable. Positions whose logits are not finite
are refused by name instead of being written out as unusable numbers, the top-k list holds real vocabulary entries
instead of padding a fixed width with sentinels, and the output path is created exclusively, never truncated.
Every refusal says why on stderr before the mode returns non-zero.

The first commit adds the two small foundations that make the above checkable: a classified reduction of a logit
row (logprob_row_checked), which reports why a row was unusable rather than only that it was; and a short
self-contained SHA-256 (evidence_digest.h) so a mode can name the exact bytes it consumed without the engine
taking on a dependency it otherwise never needs. The plain sampling path is untouched, and the new path is pinned
to agree with it on every well-behaved row.

The reported numbers are unchanged. The negative log-likelihood and the partition function are computed exactly
as before, in double, and are only printed at full precision now β€” a reader comparing old and new files sees more
digits of the same value.

The cap: we went past the prose fix you asked for

The review asked us to make the prose match what ABLATE_MAX_ITEM_TOKENS actually bounds. We started there, then
measured, and found the prose was understating it: three unbounded allocations are reachable from a manifest,
not one, and the largest is kv_alloc at 177.75 GiB at the cap β€” 7.4Γ— the prefill buffer the review named.

At that point "make the comment honest" had two forms: admit our own doc is false, or make it true. The doc in this
PR says the cap exists "so that a corrupt manifest cannot ask the loader for an arbitrary allocation", and
ABLATE_MAX_ITEM_TOKENS is new in this PR β€” so the variable whose comment lied is ours.

The cap now bounds what its name claims, derived from a limit the engine already has rather than a new
constant: ablate_item_token_limit() = min(CTX, ABLATE_MAX_ITEM_TOKENS), checked ahead of all three allocations.
A manifest over it is refused by name before anything is allocated; a manifest at the real maximum loads. No new
constant, no new environment variable.

Disclosed, not closed: this bounds item length, not item count. A long manifest still costs ~240 B/item,
which is ordinary loader behaviour proportional to input. The hazard was a short line demanding 178 GiB, and that
axis is shut.

The 21 GB figure in the review reproduces at hidden 5,120; at GLM-5.2's actual hidden of 6,144 the prefill
buffer is 24.00 GiB (2^20 * 6144 * 4 B). Both allocations are worked out in full in the comment beside the
cap rather than left as bare constants.

Context. This PR is one of five independent contributions derived from a single locally-verified working tree
(the fp8 container line in #1102, the OpenAI-compatible server hardening in #1353, the evaluation harness in #1355,
this engine block, and a transcript check tool in #1357). This one carries the engine block: the ablation scoring
mode, the row-status and digest headers, the offline checker that reads the mode's output, and their tests.
The round-trip case now runs entirely inside this PR β€” the producer and tools/check_ablate_evidence.py are
both here, so nothing about it waits on another PR. It no longer carries tools/engine_evidence.py at all. This revision moved the manifest helpers into
check_ablate_evidence.py, which left nothing here importing that module β€” so shipping it would have meant 524
lines of code nothing in this PR uses. It lives in #1355, which has a real consumer, and in #1357, which imports it. The others are proposed separately, each with its own
evidence. The set-level explanation and a traceability matrix covering every request across the three PRs are in the #1355 comment: #1355 (comment).

Behavioral contract

  • No default decode path changes what it computes: an unchanged binary and this one produce the same output, token for token, on the same container.
  • The mode runs only when ABLATE_SCORE is set, and no other path reaches it.
  • Every rejected input is rejected by name, on stderr, before a non-zero return; no input is partly executed.
  • A manifest declaring more tokens for one item than min(CTX, ABLATE_MAX_ITEM_TOKENS) is refused before any of the three allocations it would otherwise size.
  • The reported nll and partition function are bit-for-bit what dev computes.
  • Producer output passes an offline checker written independently of the engine, for each manifest framing the
    engine accepts, and all three bind one digest.
  • Everything that worked before still works: the branch builds and the existing suites pass with the rest of the
    set absent.

Structural changes on the default load path. The mode's digest is computed only when ABLATE_SCORE is set, so the load path does no new work on an ordinary run. Eight things about it are nonetheless different from dev, and
you should not have to find them yourself:

  1. two new unconditional includes, <stdarg.h> and <inttypes.h>;
  2. a new unconditional #include "evidence_digest.h" β€” a new file whose functions are plain static;
  3. Cfg gained char config_sha256[65], so every Cfg in the process grows;
  4. cfg_root gained a third parameter;
  5. the digest call inside cfg_root runs only when a caller passes a buffer;
  6. load_cfg reads one environment variable on every call to decide whether to pass that buffer β€” one getenv per model load;
  7. run_score's prefix probe passes NULL for the new parameter;
  8. sample.h gained three static inline functions and two types; that header is compiled into olmoe.c as well, where they are unused.

Capstone matrix β€” one decisive artifact per claim.

claim decisive evidence
the default decode path is unchanged by this PR an engine built from dev and one from the integrated tree, run against the same real GLM-5.2 container (78 layers, 256 routed experts, hidden 6,144, int3/g64), same prompt, temperature 0, KV cache deleted before each arm so neither could inherit the other's prefill: the same 24 generated tokens, id for id, on CPU and again on CUDA. The CUDA arms are asserted to have actually used the GPU β€” resident set: 625 tensors, 10.17 GB VRAM β€” because an earlier pass with CUDA_EXPERT_GB at its default of 0 placed nothing in VRAM and silently ran on the CPU
the mode is unreachable without its variable tests/test_ablate_mode_gate.py reads the engine source and asserts exactly one product entry into the mode plus one compile-gated adapter entry, and that run_ablate_score has exactly one caller inside the mode; it fails if a second call site appears
nll is still dev's double-precision reduction tests/test_ablate_mode.c drives a row through the real emitter whose logits are chosen so a single-precision intermediate differs, and compares the emitted text with == against an expectation computed in the test from dev's formula; substituting the float intermediate fails it
the header's config_sha256 really comes from the load path the same suite writes a temp config.json, calls load_cfg, and compares the field against an independently computed digest and a pinned literal; it also asserts the field is left unset when the variable is not set
the manifest digest means the same thing in C and in Python the same 64-character known answer is pinned in tests/test_ablate_mode.c and in this PR's Python suite, and reproduces outside both: printf 'coli-ablate-manifest/2\n0 3 2 0 0 1 2 3\n' | shasum -a 256 β†’ c63a48c375b14ca60f26c7e3c5dd36b5929ffaf669a45511c93deee6e8bbd5ed
producer output survives a real round trip an integration run drove this PR's own engine mode over a real container and fed the artifact to this PR's own checker, end to end on the tree being pushed: the checker validated the engine's own ABLATE_OUT against the manifest and the config, and passed
the refusals are real, not decorative tests/test_ablate_mode.c reports 79 named checks at runtime (ok: lines from a real run β€” a static count of CHECK( call sites gives 54, because several sit in loops) with no model and no weights β€” the loader, the writer, the three accepted framings, each named refusal, and the dispatch contract; tests/test_logprob_status.c reports 138 the same way, including published SHA-256 vectors at the block boundary and both padding branches. Both PASSED (0 failures) at the head being pushed
the cap refuses before it allocates, and is derived not invented tests/test_ablate_mode.c section "the per-item token limit is derived from CTX, not a bare constant": cap+1 (4097 tokens) is refused, naming the CTX=4096-derived limit, and a manifest at the real CTX-derived maximum (4096) loads and runs. The refusal is asserted to name the limit, not merely to occur
the packaged release archive imports what it ships RAN at this head: the archive is built, and test -f, ast.parse and the new python3 -c "import tools.check_ablate_evidence" all pass. Stated precisely, because the distinction matters: at this head check_ablate_evidence.py has no local imports at all β€” the manifest helpers moved into it during this revision β€” so the import check currently has no dependency to catch. It is kept because it is one line and asserts importability for any dependency the tool gains later. It is hardening a gate that could not detect importability, not a fix for a live break
What changed since the reviewed head, how the fleet comparison was set up, and origin accounting

What changed since cc80928e, the head reviewed on 2026-09-17

change origin
the AblateWriter vtable collapsed to a plain FILE *, tests driving temp files they create and remove (not literally tmpfile(3)). AblateOutputRun, also named in the review, is NOT collapsed β€” it keeps two production-path function pointers maintainer-requested, partially
the AblateModeRunFn typedef and its one-entry ablate_mode_dispatch table removed β€” a function pointer with exactly one implementation, wrapping what is now a direct call re-review-found, and not named in the review. ablate_model_mode_run remains as a plain static that null-checks the path and calls run_ablate_score
the cap derived and enforced ahead of all three allocations re-review-found β€” the review asked for prose; the measurement said prose was not enough
tools/check_ablate_evidence.py, its test, and the release.yml gate moved here from #1355 maintainer-requested, resolved differently β€” the review asked for them to be parked; their writer is this PR's coli-ablate/2 mode, so they moved instead
a real import check added to the release-archive gate alongside the existing test -f and ast.parse, not replacing them re-review-found β€” neither original executes anything, so neither can catch an import-time failure. That gate is ours, added in this PR
a 2.01Γ— logit_topk_select regression fixed, output byte-identical re-review-found
an ENVIRONMENT.md/comment contradiction corrected re-review-found

Where the added c/colibri.c lines go, measured at this head (+838 / βˆ’74). Categories are exact integer
counts and sum to 838: manifest parse / validation / digest 294 (35.1%), of which ablate_manifest_load is
171 (c/colibri.c:8178-8348); writer / output path 171 gross (20.4%); the cap / refusal path 56 (6.7%),
which is new in this revision; test-only #ifdef COLI_TEST_ABLATE_ADAPTERS lines 35 (4.2%), absent from a
normal build; remainder β€” scoring helpers, error paths, dispatch β€” 282 (33.7%). Comments are 151 of the 838
(18%), spread across categories. The old run_ablate_score on dev is 79 lines
(origin/dev c/colibri.c:7929-8007); "about 88" is the diff hunk's old-side span.

The writer collapse, stated precisely β€” including the part that is not done. AblateWriter and its
four-function vtable (vprintf_fn, puts_fn, flush_fn, close_fn) are gone: 21 references at the reviewed
head, 0 here.
The writer is a plain FILE *, driven in the tests by temporary files they create and clean up themselves (PID-suffixed, opened with fopen) rather than by tmpfile(3) itself. AblateOutputRun survives with
two function pointers
(open_fn, body_fn) which are not compile-gated and sit on the production path
(c/colibri.c:8443-8466) β€” they are the seam that lets the C tests run the parser with no model. The review named
both; only one is eliminated, and collapsing the second is offered as a follow-up rather than claimed as done.

c/Makefile and #1102 β€” the precise statement. #1356 and #1102 both add entries to c/Makefile and will
conflict there β€” only there; the engine sources merge cleanly
, which we verified by merging them in both orders.
Whichever lands second, we re-merge dev and push within a day. Separately, if #1102 lands first, quant.h gains
fp8_format.h and these two rules should list it too, exactly as their sibling rules do.

No shared file remains in this PR. tools/engine_evidence.py and tests/test_engine_evidence.py were dropped
from it entirely once this revision left it with no importer of that module; they stay in #1355 and #1357, which do
import it. Any merge order still works β€” all six orderings of the three were built in a scratch worktree and
produce one identical tree with zero conflicts, re-verified after the drop.

Origin accounting for the original content is unchanged from the reviewed head: the mode's implementation, the two
headers and their tests are new content re-expressed onto current dev; one behaviour of dev's is deliberately
superseded; the checker's CRLF leniency was corrected during review to match dev's contract of record; two
committed pins (the nll value and the load_cfg digest) close gaps nothing guarded before. One commit lists
evidence_digest.h among colibri's Makefile prerequisites, because upstream's Makefile-prerequisite test (#1284)
requires every header a source includes to be declared β€” no behaviour change. The shared logprob helper in
c/sample.h now promotes the logit before subtracting; no number this PR writes to disk changes, because the
evidence writer computes its own double subtraction. No surrounding code was reformatted.

Durable vs current state: the mode's format, its refusals, the digest binding, the derived cap and the dispatch
guard are durable. Everything measured in this body β€” diffstats, suite counts and timings, the cap behaviour, the
archive gates β€” is measured against base cc756a63 at the head being pushed. No evidence from an earlier
revision of this PR is carried into it
β€” the decode-identity and round-trip results above were regenerated
against the current integrated tree rather than quoted from the previous round.

@monotophic

Copy link
Copy Markdown
Contributor Author

Authored by Claude Fable 5.1 in Claude Code, analysis in partnership with @monotophic

Merged current dev (87c54a93) into this branch. Clean auto-merge, no conflict resolution. I read the merged colibri.c and sample.h by hand since dev landed several nearby changes (expert-FFN extraction, qwen38 CUDA tier, sparse routing, chat stats, OpenMP team sizing): this branch's added functions (logprob_row_checked, logprob_from_row_checked, the checked ablation-scoring rewrite, load_cfg's config-digest binding) are intact and called the same way. Content on this branch's side is unchanged on all 15 files. make check exit 0 on the merged head (1143 tests); this branch's C tests and Python modules pass.

@JustVugg similar steering request to #1102, #1353, and #1355, this PR is part of the set to make the full checkpoint-quality GLM 5.2/5.3 containers work and to establish the OpenAI API instrumentation needed for container quality studies. Let me know if this PR is off target or otherwise not likely to merge. Thanks for all you do, this project is awesome :)

@JustVugg

Copy link
Copy Markdown
Owner

Reviewed, and I want to answer your own question rather than pretend the review is about the code.

The code is careful and the defect it replaces is real. I read the old run_ablate_score on dev and it does exactly what you say: strtol failing means continue, an out-of-range cell count is silently clamped, a bad item prints to stderr and continues, and the whole thing returns 0 regardless. A manifest where every single line was malformed produced a header-only file and an exit status that said success. Anyone using that mode to make a claim about experts was building on sand.

You ask whether I want the old mode back. The answer is that this is the one place in the set that breaks the project's convention, and you say so plainly rather than hiding it: no new environment variable, but ABLATE_SCORE and ABLATE_OUT both change meaning, the schema goes from coli-ablate/1 to /2, the exit status changes, and ABLATE_OUT now refuses an existing path instead of truncating.

So the question back to you is narrow: is ABLATE_SCORE used by anyone outside your own tree? If it is, I want the new mode behind coli-ablate/2 as an opt-in and the old behaviour intact. If it is not, replacing it is right and I will take it as it stands.

Two smaller things either way.

801 lines of colibri.c to replace about 88, and a good part of that is the AblateWriter and AblateOutputRun indirection, which exists so the C tests can drive the parser with no model. In a project whose engines are deliberately flat single files that is a lot of abstraction bought for testability. Could the writer collapse to a plain FILE pointer with the tests driving a tmpfile?

And the new loader materialises the whole manifest in RAM where the old one streamed a line at a time. ABLATE_MAX_ITEM_TOKENS bounds the 4 MB token array, not the maxT * hidden prefill buffer, which at the cap is about 21 GB. Your comment next to it half concedes this; I would make the prose match what the cap actually bounds.

monotophic and others added 5 commits September 17, 2026 22:53
Add tools/check_ablate_evidence.py, a standalone validator for the
ABLATE_OUT evidence stream the engine's ablation-sweep mode writes: it
accepts only a complete, self-binding artifact and refuses everything
else -- a truncated or replayed record stream, a header that does not
bind the manifest or the external config.json it names, any field with
the wrong type, range or key set at any of the four record kinds
(header, item header, target row, terminal completion), and a target
row whose fields contradict one another in a way the producer can
never emit.

Framing is checked before content: NaN/Infinity JSON constants,
duplicate object keys, non-ASCII text and malformed JSON are all
rejected up front. The manifest proof and the header's config identity
are cross-checked against the actual config.json and manifest named in
the header, so evidence cannot be validated against the wrong run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hecked ablation scoring mode

Add evidence_digest.h (SHA-256 identity of the loaded config.json and
of a manifest, bound into the ABLATE_OUT header so evidence can never
be checked against the wrong run) and a logit-row status layer in
sample.h (LogprobStatus / LogprobRow / logprob_row_checked /
logprob_from_row_checked) that classifies a row's reduction --
FINITE, NAN, POS_INF, NEG_INF, ALL_NONFINITE, FINITE_OVERFLOW, or
INVALID -- before any log-probability arithmetic is trusted, so a
malformed row fails closed with a named status instead of silently
producing a NaN a reader cannot distinguish from a real value. The
per-target subtraction promotes the logit to double before subtracting
the row's own double logZ, so its rounding no longer depends on where
in the pipeline the widening happens.

Replace ABLATE_SCORE's ablation-sweep mode with a checked version built
on that status layer: every manifest field is validated against the
loaded config before anything runs, a manifest that does not fit the
documented format is refused whole with the offending line named on
stderr, and the per-item evidence stream records each target's status
alongside its negative log-likelihood, logits, margin, argmax and
top-k. The evidence writer takes a plain FILE* (opened O_EXCL, no
existing path overwritten) -- it only ever had one backend, so the
prior AblateWriter vtable and the AblateModeRunFn dispatch indirection
around it are both gone in favor of calling the writer functions
directly.

Coverage: test_ablate_mode.c drives the mode end-to-end against a small
fixture config (vocab=64), including the manifest parser's boundary
cases (token id exactly at the vocabulary bound, an ERANGE-triggering
field) found by a mutation sweep; test_ablate_mode_gate.py checks the
mode is reachable only through its documented environment gate;
test_logprob_status.c pins logprob_row_checked's and
logprob_from_row_checked's classification of every row shape in
isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
logit_topk_select measured 2.01x slower than dev's equivalent top-k
selection at V=151552 (real GLM-5.2 vocab), topk=32. Cause: after the
first k iterations fill all k slots, every one of the remaining V-k
iterations still ran an O(k) linear scan of tk_id[] asking whether an
empty slot exists, an answer that can only ever be 'no' from that
point on -- loop-invariant work paid on almost every iteration for a
result invariant after the first k.

Fixed with a fill counter: the first k iterations fill slots 0..k-1 in
that exact order (as the old empty-slot scan also did, since it always
returns the lowest-indexed empty slot), so 'is there an empty slot' is
'have fewer than k been filled', O(1) instead of O(k). The
minimum-slot scan for i>=k is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… imports

check_ablate_evidence.py is reached by nobody in coli's own import
graph -- it is a person-invoked verifier, the same position
k3_tokenizer.py has always been in -- so pack_python's import walk
cannot carry it into the release archive on its own. Bring in this
branch's release.yml hunks: copy the tool into dist/tools, assert it
shipped, assert it parses.

test -f and ast.parse both only look at the one file, and only at its
syntax -- neither executes it, so neither catches a real import-time
failure. Add a fourth gate that actually imports the packaged module
from the archive root, never executes it, so a real import failure
fails the job instead of shipping silently broken.

test_pack_python.py's HumanOnlyToolsShipExplicitly pins both halves of
this on the real tree: the import walk does not reach
check_ablate_evidence.py (if it ever does, the explicit copy became
redundant), and release.yml both copies and gates it, the same way it
already does for k3_tokenizer.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…allocations

Re-review found that documenting the prefill buffer this cap did not
bound undersold the problem twice. First, docs/ENVIRONMENT.md asserted
the hazard closed ('so that a corrupt manifest cannot ask the loader
for an arbitrary allocation') while this file's own comment said the
opposite; a doc our own comment refutes is worse than the narrower true
statement it replaced. Second, measurement found THREE unbounded
allocations behind this one constant, not one: kv_alloc's per-layer KV
buffers (177.75 GiB at the old 2^20 cap, GLM-5.2 shape), the prefill
buffer x (24.00 GiB, the one originally named), and this loop's own
resident manifest bookkeeping (240 B/item, unbounded in item count).
kv_alloc is 7.41x the prefill buffer, not the smaller of the two.

Given the doc already claims the hazard is closed, the honest fix is
to make the claim true rather than narrow it further. The bound: a
manifest item's T is now checked against min(CTX, ABLATE_MAX_ITEM_TOKENS)
via ablate_item_token_limit(), not the bare 2^20 constant. CTX
('Maximum context length (tokens) the KV cache is sized for',
docs/ENVIRONMENT.md) is an existing engine limit already read by
kv_pool_bytes and expert_avail for exactly this kind of sizing -- no
new constant, no new environment variable. An item longer than the
context this engine is configured to hold cannot be processed
regardless of what a manifest asks for. The check still runs before
the item can contribute to any of the three allocations; the backstop
constant remains in case CTX itself is set to something absurd.

Two tests, both required so the bound is proven exact, not merely
strict: a manifest at cap+1 (4097 under the CTX=4096 default) is
refused by name; a manifest at the real maximum (4096) loads and runs
to completion -- without this second case, a bound that refused
everything would also pass the first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
monotophic added a commit to monotophic/colibri that referenced this pull request Sep 18, 2026
Split reused verbatim from upstream/p0-engine-preamble (b6070718),
verified there by 18,093 old-vs-new behavioural comparisons with zero
divergences. Keeps PreambleError, parse_engine_banner,
parse_engine_loaded, parse_engine_preamble and their module constants.
Removes ManifestFormError and canonical_manifest_bytes, which travel to
JustVugg#1356 with their only consumer (check_ablate_evidence.py, removed from
this PR next). The module docstring no longer describes the absent
half.

This transiently breaks check_ablate_evidence.py's import of the
removed names; the next commit removes that module from this PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
monotophic added a commit to monotophic/colibri that referenced this pull request Sep 18, 2026
check_ablate_evidence.py, its test module, and the three release.yml
hunks that copy and gate it (the cp into dist/tools, the test -f
presence gate, the ast.parse packaged-file check) move to JustVugg#1356, which
owns their writer (canonical_manifest_bytes/ManifestFormError, removed
from engine_evidence.py the previous commit).

test_eval_glm.py's shared byte-limit test drops its ABLATE-side
assertions (module gone) and keeps the EVAL-side ones unchanged;
test_pack_python.py's HumanOnlyToolsShipExplicitly class is removed
verbatim rather than edited, since check_ablate_evidence.py was one of
its two pinned entries -- it is carried to
colibri_lab/dispatch/2026-09-17-program-d-r2/CARRIED_TO_P2_test_pack_python_class.txt
for JustVugg#1356 to restore alongside its own copy of the checker.
test_engine_evidence_is_needed_by_the_real_tree, this branch's own
addition to that file, is untouched.

This is a forward commit that deletes -- intended, not a mistake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@monotophic
monotophic force-pushed the evidence/ablate-block branch from cc80928 to 14f299c Compare September 18, 2026 04:38
@monotophic

Copy link
Copy Markdown
Contributor Author

Set-level explanation and the full traceability matrix: #1355 (comment)

The diff is smaller: 15 files and +5,449 β†’ 12 files and +3,706, a 1,743-line reduction.

ABLATE_SCORE β€” the answer to your narrow question

You asked whether ABLATE_SCORE is used by anyone outside our own tree, and pre-committed: if not, replacing it is
right and you will take it as it stands.

We conducted a search of the project's public GitHub and found nothing β€” all upstream references, 53 issues and
discussions, and public code search. That is the strongest claim we can make; you can price the residual risk from there better than we can.

Note the ablate mode was written by @jeswr, who is still an active contributor. We have not contacted them but maybe they can jump in if they have more to say about it.

Where the added colibri.c lines go

You asked why replacing about 88 lines costs 801. Two corrections, measured at the revised head: the
count is now +838 / βˆ’74, because this revision added the derived cap and its refusal path on top of what you
reviewed; and the old run_ablate_score on dev is 79 lines (origin/dev c/colibri.c:7929-8007) β€” "about 88"
is the diff hunk's old-side span. Neither changes your point.

category lines share of 838
manifest parse / validation / digest (ablate_manifest_load is 171 of these, c/colibri.c:8178-8348) 294 35.1%
writer / output path (gross added) 171 20.4%
cap / refusal path β€” new in this revision, did not exist when you reviewed 56 6.7%
test-only, #ifdef COLI_TEST_ABLATE_ADAPTERS, absent from a normal build 35 4.2%
remainder β€” scoring helpers, error paths, dispatch 282 33.7%

Categories are exact integer counts and sum to 838. Comments are 151 of the 838 (18%), spread across all
categories rather than concentrated in one.

The bulk is validation the old mode did not do β€” which we see by reading dev: strtol failure
meant continue, an out-of-range cell count was silently clamped, a bad item printed to stderr and continued, and
the whole thing returned 0 regardless.

The writer collapse β€” done, and one part of your ask that is NOT done

AblateWriter is gone. The four-function vtable you objected to β€” vprintf_fn, puts_fn, flush_fn,
close_fn β€” no longer exists: 21 references at the head you reviewed, 0 now. The writer is a plain FILE *
and the C tests drive it through temporary files they create and clean up themselves (PID-suffixed, opened with fopen) β€” the substance of what you suggested, though not literally tmpfile(3).

But you named two things, and the second one survives. AblateOutputRun still carries two function pointers,
open_fn and body_fn, and they are not compile-gated β€” they are on the production path
(c/colibri.c:8443-8466). They are the seam that lets the C tests run the parser with no model at all. We are
flagging it rather than letting you find it: the indirection is reduced, not eliminated. If you want that
collapsed too, say so and it is a small follow-up.

We also removed something you did not name: AblateModeRunFn and its one-entry ablate_mode_dispatch table β€”
a function-pointer typedef with exactly one implementation (2 references each, now 0). What remains is
ablate_model_mode_run, an 8-line wrapper that null-checks the path and calls run_ablate_score directly.

Your asks

you asked disposition where it lives now
"is ABLATE_SCORE used by anyone outside your own tree?" done β€” searched, nothing found; replacement stands per your own condition above
"Could the writer collapse to a plain FILE pointer with the tests driving a tmpfile?" done for AblateWriter, NOT for AblateOutputRun β€” the AblateWriter vtable is gone (21 references β†’ 0) and the writer is a plain FILE *, driven by temp files the tests create and remove (not literally tmpfile(3)). AblateOutputRun, which you also named, still carries two function pointers on the production path; see below. Bite re-checked: 4 distinct mutations against test_ablate_mode.c, each a real failure c/colibri.c
"I would make the prose match what the cap actually bounds" done, and then some β€” see the section below c/colibri.c, docs/ENVIRONMENT.md

The cap: we went past the prose fix, and here is why

You asked for prose only. We started there, then measured, and found the prose was understating it: there are
three unbounded allocations reachable from a manifest, not one, and the largest is kv_alloc at 177.75 GiB
at the cap β€” 7.4Γ— the prefill buffer you named.

At that point "make the comment honest" had two possible forms: admit our own doc is false, or make it true. Our doc
in this PR says the cap exists "so that a corrupt manifest cannot ask the loader for an arbitrary allocation", and
ABLATE_MAX_ITEM_TOKENS is new in this PR β€” so that one is ours.

The cap now bounds what its name claims, derived from a limit the engine already has rather than a new constant:
min(CTX, ABLATE_MAX_ITEM_TOKENS), checked ahead of all three allocations. A manifest over it is refused by name
before anything is allocated; a manifest at the real maximum loads. No new environment variable.

Disclosed, not closed: this bounds item length, not item count. A long manifest still costs ~240 B/item,
which is ordinary loader behaviour proportional to input. The hazard was a short line demanding 178 GiB, and that
axis is shut.

Your 21 GB figure reproduces at hidden 5,120; at GLM-5.2's actual hidden of 6,144 the prefill buffer is
24.00 GiB (2^20 * 6144 * 4 B), against 177.75 GiB for the KV allocation. Both are worked out in full in
the comment beside the cap rather than left as bare constants, and docs/ENVIRONMENT.md now states the derived
bound and names all three allocations it covers.

Details β€” origin accounting

maintainer-requested: ABLATE_SCORE search; writer collapse; cap prose.
re-review-found, disclosed: the three-allocation measurement and the derived bound; removal of the
AblateModeRunFn typedef and its one-entry ablate_mode_dispatch table β€” a function pointer with exactly one
implementation, wrapping what is now a direct call β€” which you did not name and we removed anyway
(ablate_model_mode_run remains, as a plain static that null-checks the path and calls run_ablate_score); a 2.01Γ— logit_topk_select regression fixed with
byte-identical output; and a real import check (python3 -c "import tools.check_ablate_evidence") added to the
release-archive gate alongside the existing test -f and ast.parse, not replacing them. That gate is ours,
added in this PR, and neither of the original two executes anything, so neither catches an import-time failure. The
archive is verified correct today; the gate now asserts that rather than implying it.

#1356 and #1102 both add entries to c/Makefile and will conflict there β€” only there; the engine sources merge
cleanly.
Whichever lands second, we re-merge dev and push within a day.

Authored by Opus 5 in Claude Code, analysis in partnership with @Monotophic

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.

2 participants