Skip to content

linalg/qr: regenerate timed inputs per repeat and mirror the stream rule - #170

Open
SamMausberg wants to merge 1 commit into
gpu-mode:mainfrom
SamMausberg:qr-validation-gaps-148
Open

linalg/qr: regenerate timed inputs per repeat and mirror the stream rule#170
SamMausberg wants to merge 1 commit into
gpu-mode:mainfrom
SamMausberg:qr-validation-gaps-148

Conversation

@SamMausberg

@SamMausberg SamMausberg commented Aug 16, 2026

Copy link
Copy Markdown

Fixes #148 (gaps 2, 3, and 4; gap 1 was fixed by #150).

qr_py/eval.py and qr_v2/eval.py get the same change and stay byte-identical, as on main.

Gaps 2+3: output replay in the timed region

The timed loop reused the same input objects on every repeat, and the shapes that dominate the leaderboard geomean have a data_list of length 1. A kernel could compute outputs during untimed calls (warmup, the pre-timing validation pass, earlier repeats), cache them by id() or content, and replay them inside the timed window at near-zero cost. The recheck from #150 cannot catch this, because a replayed output is still correct for that same input. This is the capped_sleepy_cache mechanism named in the issue.

Every timed repeat now gets fresh input content:

  • _timed_repeat_batch() derives per-item seeds through the existing _combine, from a shift unique per (invocation, repeat, item). % 2**63 keeps the nested result inside what manual_seed accepts. Disjointness is exact for local task seeds (verified exhaustively); with a server-combined base seed the mod can wrap, making collisions negligible-probability rather than impossible.
  • Each _run_single_benchmark invocation gets its own seed_salt (benchmark: warmup 0, timed 1+idx; leaderboard: warmups 1..N, timed N+1..2N). Untimed content never reappears in a timed window, even for a cache persisted to disk across the test, benchmark, and leaderboard runs of one submission.
  • Generation and cloning happen before clear_l2_cache(), outside the timed CUDA-event window, so per-repeat timing is unchanged. The warmup / pre-timing batch is bit-for-bit identical to today.

An id-keyed cache never hits (fresh objects), a content-keyed cache never hits (fresh content), and a stale replay fails the existing recheck.

Not added on purpose: an element-wise cross-check against torch.geqrf. (H, tau) is not unique (signs, blocking, inner precision), so exact matching would reject valid implementations the tolerances are meant to admit.

Stability-break floor

Found in A/B runs: the untimed per-repeat work arms the early-stop rule (err/mean < 0.001 once wall time passes 0.1s) almost immediately, and on a large shape it fired on a 3-sample estimate (runs: 44 -> 3). The break now needs at least 10 timed samples; the same shape then takes 37. The mean*runs and 120s budget caps are untouched, so worst-case wall time is bounded as before.

Behavior changes

  • Kernels that clobber their input in-place now pass benchmark/leaderboard (each repeat gets a fresh buffer). On main they failed at repeat 2. Test mode always handed kernels a clone, so this aligns the modes.
  • mixed benchmark lines draw a fresh profile assignment each repeat, so their mean ranks a distribution of instances instead of one fixed instance.

Gap 4: the stream rule, mirrored locally

KernelBot rejects any submission containing the substring stream (case-insensitive, comments included) at intake (api_utils.py and leaderboard_cog.py in discord-cluster-manager). Nothing checked this locally, so stream-using kernels passed every mode and only failed on their first remote submission. eval.py now runs the same substring test before dispatching any mode, emits a report entry plus the reason on stderr, and exits 112. Only the submission file is scanned, matching the remote rule.

Validation

RTX 5070 Ti, torch 2.8.0+cu128; this checks harness semantics, not B200 performance. Runs staged the real problem files exactly as KernelBot lays them out, on a true count=1 shape (batch 256, n 512), benchmark mode:

Submission main patched
torch.geqrf reference passes, mean 448.0 ms/input passes, mean 447.0 ms/input
shape-keyed output cache passes, mean 5.3 us/input fails first timed recheck
content-keyed output cache passes, mean 203 us/input passes, mean 448.8 ms/input (advantage gone)
id()-keyed output cache passes, mean 3.0 ms/input fails timed recheck (stale replay caught)
fp64 inner math (valid factors, differ element-wise from fp32 geqrf) passes passes
in-place input clobbering (correct output) fails at repeat 2 passes
side-stream kernel (correct, synchronized) passes locally, rejected at remote intake fails every local mode with the stream-rule message

Reference means agree to 0.2% between harnesses; leaderboard mode passes; test mode passes on the real task.yml shapes including mixed and the stress cases. Seed uniqueness was verified exhaustively, and timed batches are deterministic per (salt, repeat) with the warmup batch unchanged.

Not measured: B200 wall clock against the task timeouts. Here the three-shape reference benchmark was within 5% of main (237.6s vs 228.2s), but a B200 number would be worth taking before merge.

Follow-ups, out of scope: eigh_py and cholesky_py share the same timed loop and lack the stream gate, and per Bryce's comment on the issue the gate is worth propagating to the other problems.

Made in combination with Claude Fable 5.

Close the remaining gaps from gpu-mode#148 in qr_py and qr_v2 (gap 1 was fixed
in gpu-mode#150). The two eval.py files stay byte-identical.

Gaps 2+3, timed-region output replay: the timed loop reused the same
input objects on every repeat, and the geomean-dominant shapes have a
data_list of length 1. A kernel could compute outputs during untimed
calls (the warmup invocation, the pre-timing pass, earlier repeats),
cache them by id() or content, and replay them inside the timed window.
recheck=True does not catch this case, because a replayed output is
still correct for that same input. Each timed repeat now regenerates
fresh input content from a seed shift that is unique per (invocation,
repeat, item), and warmup and timed invocations draw from disjoint
seed_salt ranges. Untimed content therefore never reappears in a timed
window, including for a cache persisted to disk across the test /
benchmark / leaderboard runs of one submission. Generation and cloning
happen outside the timed CUDA-event window, before clear_l2_cache(),
and the warmup / pre-timing batch is bit-for-bit unchanged.

The stability break (err/mean < 0.001) now requires a minimum of 10
timed samples. The regeneration and recheck work between repeats pushes
wall time past the 0.1s arming threshold almost immediately on large
shapes, and without a floor the break can fire on a 3-sample error
estimate. The mean*runs and 120s budget caps are unchanged.

An element-wise cross-check against torch.geqrf was considered and
rejected: (H, tau) is not unique (sign conventions, blocking, inner
precision), so exact-match checking would reject valid implementations
that the tolerances are meant to admit.

Behavior changes worth knowing about: a kernel that clobbers its input
buffer in-place now passes benchmark/leaderboard (each repeat gets a
fresh buffer; test mode already handed kernels a clone), and "mixed"
benchmark lines draw a fresh profile assignment per repeat, so their
mean ranks a distribution of instances rather than one fixed instance.

Gap 4, the stream rule: KernelBot rejects any submission containing the
substring "stream" (case-insensitive, comments included) at intake, so
a stream-using kernel passes every local mode and only fails on its
first remote submission. eval.py now applies the same substring test to
the submission source before dispatching any mode and fails with a
report entry plus the reason on stderr. Only the submission file is
scanned, matching the remote rule.

Validated on an RTX 5070 Ti (torch 2.8.0+cu128); this checks harness
semantics, not B200 performance. old = main, new = this change, on a
count=1 shape (batch 256, n 512), benchmark mode:

  torch.geqrf reference      pass, 448.0 ms/input    pass, 447.0 ms/input
  shape-keyed output cache   pass, 5.3 us/input      fail, first timed recheck
  content-keyed output cache pass, 203 us/input      pass, 448.8 ms/input
  id()-keyed output cache    pass, 3.0 ms/input      fail, stale replay caught
  in-place input clobbering  fail at repeat 2        pass
  side-stream kernel         pass locally            fail in every local mode

Reference means agree to 0.2% between harnesses on that shape. On a
large mixed shape the timed sample count went 44 (main) -> 3 (without
the stability-break floor) -> 37 (with it).

Reference and legitimate-optimization kernels (fp64 inner math whose
factors differ element-wise from fp32 geqrf; in-place input clobbering)
pass test and benchmark modes on the new harness, and the reference
also passes leaderboard mode. Seed-shift uniqueness was checked
exhaustively for all benchmark-line base seeds (1.25M derived seeds per
line, distinct and disjoint from the warmup sequence); with a server
seed the shifted values wrap mod 2^63 and disjointness is
negligible-probability-of-collision rather than exact.

Developed and validated in combination with Claude Fable 5.

Closes gpu-mode#148.
@SamMausberg
SamMausberg force-pushed the qr-validation-gaps-148 branch from 5dbd0b4 to f715eec Compare August 16, 2026 17:56
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.

linalg/qr validation gaps

1 participant