Skip to content

Record the counted trip count as a measured-negative on the fuel cap [#75] - #342

Merged
iderex merged 1 commit into
mainfrom
issue/75
Aug 10, 2026
Merged

Record the counted trip count as a measured-negative on the fuel cap [#75]#342
iderex merged 1 commit into
mainfrom
issue/75

Conversation

@iderex

@iderex iderex commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Closes #75.

Measured-negative. No kernel code ships. This PR is the record.

The device gate that held this issue open is not in the way tonight. The
container route to the RTX 3080 answers, so the before-and-after this issue is
gated on was actually taken:

nvidia-smi --query-gpu=name,driver_version --format=csv,noheader
NVIDIA GeForce RTX 3080, 560.94
nvcc --version | tail -2
Cuda compilation tools, release 12.6, V12.6.77

What was measured

The cap costs a per-sequence decrement and the register its counter lives in.
The shape measured here removes the counter and spends the same budget as the
trip count of a counted loop, so the bound becomes the loop's own induction
variable:

const uint64_t budget = src_size >= SIZE_MAX - 1 ? SIZE_MAX : src_size + 2;
for (uint64_t left = budget; left != 0; --left) {
    ...
    if (done) { break; }
}

The budget is src_size + 2 rather than + 1 because the decrementing
spelling tested its counter after the call and so admitted one more call than
it was initialized with. Matching that exactly is what keeps the accept set
unmoved, and the oracle parity suite is what would have caught it if it had not.

This is the fourth formulation measured against this cap and it is not among
the three measured when the cap landed, which were a top-of-loop test, the
folded exit-branch test that shipped, and a 32-bit counter. It is also the only
shape left that the configure-time loop scanner admits: that scanner requires a
while or a condition-less for in the scanned sources to name an explicit
decrementing fuel counter, and a counted for with a visible condition
satisfies it by construction rather than by naming one.

Numbers

Three interleaved passes, after / before / after / before / after / before in
one session, both binaries built from the same tree, recorded 2026-08-10 inside
the digest-pinned nvidia/cuda:12.6.2-devel-ubuntu24.04 container
(sha256:738fba0fbdb225b7a2931c58a5c8f03a84d3cd2f6a84975826a157339ef750b8) on
the RTX 3080 (sm_86, driver 560.94, CUDA 12.6, nvcc V12.6.77), device-resident
and CUDA-event timed, 3 warmup + 30 measured runs. Baseline is 40a9f54.

bench_lz4 --gpu bench/corpora/silesia/* --warmup 3 --runs 30
bench_lz4 --gpu --worst4b --warmup 3 --runs 30
Corpus Metric Before (samples) After (samples) Change
Silesia GPU decode p50 11.834 / 11.544 / 11.588 ms 11.643 / 12.314 / 12.032 ms +2.9 %
Silesia Parse-only p50 6.724 / 6.724 / 6.717 ms 6.682 / 6.685 / 6.686 ms -0.6 %
worst-4Bmatch GPU decode p50 24.401 / 24.511 / 24.518 ms 24.445 / 24.619 / 24.630 ms +0.4 %
worst-4Bmatch Parse-only p50 14.500 / 14.582 / 14.592 ms 14.451 / 14.474 / 14.558 ms -0.4 %

Registers first, as the issue asks, and read off the built archive rather than a
compile log:

cuobjdump --dump-resource-usage build-base/libcudec.a
arch = sm_80  REG:54
arch = sm_86  REG:48
cuobjdump --dump-resource-usage build-cuda/libcudec.a
arch = sm_80  REG:47
arch = sm_86  REG:47

Inside the 48-register budget this issue set. On sm_86 that is inside the
recorded 41-48 bucket, so it moves no occupancy step and buys nothing there.

Verdict

The parse-only ceiling improves, by about half a percent, and it is the row
worth trusting here: the samples on each side sit within 0.005 ms of each other
and the two sides do not overlap. The counted form is genuinely a little cheaper
per sequence.

Nothing of that reaches the shipped decoder. Neither decode row improves;
Silesia reads +2.9 % off samples that overlap the before side, and
worst-4Bmatch +0.4 % well inside its own spread. The accept rule this issue
pre-registered needs a recorded improvement on at least one corpus with zero
regression on the worst case, and there is no improvement to weigh. Refused.

Why this closes the issue

The cap costs 14-17 % of the parse-only ceiling and 2.0 % of the decode. This
recovers roughly a thirtieth of the former and none of the latter. With the
three formulations measured when the cap landed, that is every counter shape
the scanner admits.

The per-block-precondition direction is separately closed off and does not need
a measurement: the budget is already derived once per chunk from the source
size, and what is left per iteration is enforcing a limit on iterations, which
means counting them. An entry check that does not count is the guard removed
rather than hoisted.

So the answer to what this issue asked is that the cap's decode cost is not in
the counter's shape and cannot be reformulated away. That is narrower than
calling it unrecoverable, and the record says so: nothing here measures a change
to the parser's liveness argument, which is what would remove the need for a
budget rather than re-spell it, and nothing here touches the second budget
inside AccumulateLength, which runs only on a length extension and so is
invisible on a corpus without them.

Gate

The counted-loop build passed the full suite before it was reverted, including
termination, termination_gpu and determinism_gpu:

100% tests passed, 0 tests failed out of 36

The tree as shipped here differs from main only in docs/BENCHMARKS.md.

The device-side sanitizer net did NOT run. Compute Sanitizer cannot attach to
this device through the WSL2 paravirtualized path, which is #258, and the remedy
the tool names is a machine-wide elevation that is not taken here. Nothing here
should be read as having passed that gate.

This change had no second reader. The evidence above stands in place of one.

…75]

The termination fuel cap costs a recorded +2.0 % on Silesia decode and
14-17 % on the parse-only ceiling. This measures the one remaining shape
that could remove its per-sequence counter: spending the same budget as
the trip count of a counted loop, so the bound is the loop's own induction
variable instead of a separate decrementing counter. It is the only such
shape the configure-time loop scanner admits, and it is not among the
three formulations measured when the cap landed, all of which were
counter shapes.

Over three interleaved passes against 40a9f54 it recovers nothing that
reaches the decoder. Silesia decode reads +2.9 % off samples overlapping
the before side, worst-4Bmatch +0.4 % inside its own spread. The
parse-only ceiling does improve, by 0.6 % on Silesia and 0.4 % on
worst-4Bmatch, tightly and without overlap, so the counted form is
genuinely cheaper per sequence; it is about a thirtieth of what the cap
costs there. Registers go 54 to 47 on sm_80 and 48 to 47 on sm_86, which
is inside the recorded 41-48 bucket and moves no occupancy step.

The accept rule needs a recorded decode improvement and there is none, so
no kernel code ships. The record states what the result does and does not
settle: the cap's decode cost is not in the counter's shape, which is what
the issue's shortlist reduced to, and that is narrower than calling it
unrecoverable.
@iderex
iderex merged commit 3fb2c55 into main Aug 10, 2026
7 checks passed
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.

Recover the ~3% Silesia decode cost of the termination fuel cap (measurement-gated)

1 participant