Skip to content

tests: hyperloom_benchmark_test checks GTT only where amdgpu exposes it (onto #2115) - #2124

Closed
bong-water-water-bong wants to merge 1 commit into
1bit/2018-baseline-zerofrom
cl1077/2115-gpuless
Closed

bong-water-water-bong wants to merge 1 commit into
1bit/2018-baseline-zerofrom
cl1077/2115-gpuless

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Onto #2115's branch (1bit/2018-baseline-zero), not onto main. Merging it into that branch turns #2115's red build green. Refs #2018.

Why #2115 is red

CI's build job runs in an ubuntu:26.04 container on a GitHub-hosted runner, with no amdgpu. With #2115's registration, ctest fails the test there:

 36/211 Test  #36: hyperloom_benchmark_test .................***Failed    0.06 sec
  File "/__w/engine/engine/tests/hyperloom_benchmark_test.py", line 205, in main
    assert box["gtt_used_gib"] is not None and box["gtt_total_gib"] is not None, box
AssertionError: {'tctl_c': None, 'gtt_used_gib': None, 'gtt_total_gib': None, 'mem_available_gib': 14.62, ...}

That assert reads the real /sys/class/drm/card0/device/mem_info_gtt_*, which explains why the test never had a row.

The change, one file (tests/hyperloom_benchmark_test.py, +16/−2)

  • Where GPU_SYSFS/mem_info_gtt_used exists, the same two real-value asserts as before.
  • Where it does not, the script's own contract: gtt_used_gib/gtt_total_gib are None ("absent is a null, never a zero or a guess", the comment two lines up).
  • A new arm points bench.GPU_SYSFS at an empty temporary directory, so the GPU-less branch runs on every box and not only in CI. GPU_SYSFS is restored in a finally.

Proof

on strixhalo (amdgpu present):                      hyperloom_benchmark_test: OK   rc=0
simulated GPU-less (load() patched to a nonexistent GPU_SYSFS, i.e. CI's case):   OK   rc=0
mutant: box_conditions() reads a hard-coded /sys/class/drm/card0 path, ignoring GPU_SYSFS:
    AssertionError at the new nogpu arm                                        rc=1
tools/ci/tests_referenced_check.py --check  rc=0;  tools/copyright.py --check  rc=0

The mutant is the class the hwmon arm already guards (HWMON_ROOT): a reader that ignores the configured root.

🤖 Generated with Claude Code

…it (onto #2115, engine#2018)

#2115 registers this test, and CI's build job then fails it at line 205: the job
runs in a GPU-less ubuntu:26.04 container, box_conditions() reads no
/sys/class/drm/card0/device/mem_info_gtt_*, and the test asserted they were not
None. That is also why the test never had a ctest row.

The real-value asserts now run where the counters exist. Where they do not,
the test asserts the script's own contract ("absent is a null, never a zero").
A faked GPU_SYSFS root exercises the GPU-less branch on every box.

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

2018 - Partially compliant

Compliant requirements:

  • The PR ensures that tests/hyperloom_benchmark_test.py is now properly registered in the test suite, addressing the drift issue
  • The test file is a proper test (matches *_test.py naming convention) and is now registered

Non-compliant requirements:

  • The existing six unregistered tests are not addressed in this PR; they are not added to the allow-list or registered

Requires further human verification:

  • Confirmation that the test is correctly registered in CMakeLists.txt or equivalent
  • Verification that the test is not a helper file that should be excluded from the drift check
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Conditional GTT assertion logic

The test now conditionally asserts GTT memory usage only when the GTT sysfs files exist. This is a good approach for handling GPU-less environments, but the change introduces a new code path that must be carefully validated to ensure it doesn't mask underlying issues or introduce false positives in environments where GTT is expected to be present.

# The GTT counters exist only where amdgpu does. On such a box every one must come back a number; CI's build job
# runs in a GPU-less container (engine#2018: this is why the test had no ctest row), where they must be null.
if (bench.GPU_SYSFS / "mem_info_gtt_used").exists():
    assert box["gtt_used_gib"] is not None and box["gtt_total_gib"] is not None, box
    assert 0.0 <= box["gtt_used_gib"] <= box["gtt_total_gib"], box
else:
    assert box["gtt_used_gib"] is None and box["gtt_total_gib"] is None, box
# ...and the GPU-less branch runs everywhere, not only in CI: a root with no counters is a null, never a zero.
real_sysfs = bench.GPU_SYSFS
try:
    with tempfile.TemporaryDirectory() as tmp:
        bench.GPU_SYSFS = pathlib.Path(tmp)
        nogpu = bench.box_conditions()
        assert nogpu["gtt_used_gib"] is None and nogpu["gtt_total_gib"] is None, nogpu
finally:
    bench.GPU_SYSFS = real_sysfs

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

History, so reviewers don't redo it: this is the third time this test has made a registration PR red in CI. #2060 fixed it the same way (null where there is no amdgpu, numeric checks against a fake root) and got a READY at 7a4fe1c. It was closed only because #2023 chose a skip-with-77 route instead. #2023 then closed with conflicts, so neither fix reached main, and #2115 hit the same line 205. This PR re-lands #2060's shape onto #2115's branch.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

ready for review at cef7015. CI build is green in the GPU-less container where #2115's row failed: Test #36: hyperloom_benchmark_test ... Passed, and 100% tests passed, 0 tests failed out of 211. Merging this into 1bit/2018-baseline-zero makes #2115 green.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at cef7015: READY

Reviewed head cef7015 (refs #2018). This is the first verdict. Base: #2115's branch 1bit/2018-baseline-zero.

  • Base: the head contains #2115's current head 3461989. It merges onto that branch with no conflicts, and the change is one file, tests/hyperloom_benchmark_test.py (+16/−2).

  • The claim: #2115's CI run 42955369879 fails in the Test step on exactly one test: #36: hyperloom_benchmark_test ***Failed ("1 tests failed out of 211"). This head's ci run 42962199261 succeeds.

  • On this box (amdgpu present, mem_info_gtt_used exists): hyperloom_benchmark_test: OK, rc 0. That run takes the real-value branch and also the new empty-root arm, so the function-local tempfile import resolves.

  • CI's case, simulated: with GPU_SYSFS in tools/hyperloom/custom/benchmark.py pointed at a nonexistent path, the result is OK, rc 0, through the null branch.

  • Mutants of the reader, run against this test:

    • box_conditions() reads a hard-coded /sys/class/drm/card0/... path for gtt_used, ignoring GPU_SYSFS: rc 1, AssertionError at the new arm (gtt_used_gib: 94.26 from the real box where null was required).
    • meminfo_gib() returns 0.0 instead of None on a missing counter: rc 1 (gtt_used_gib: 0.0, gtt_total_gib: 0.0). So "absent is a null, never a zero" is now pinned on every box.

    I restored benchmark.py (git status clean) and the test is OK again.

  • GPU_SYSFS is restored in a finally, so a failing arm cannot leak the temp root into the asserts after it. tools/copyright.py --check: rc 0.

Merging this into #2115's branch should turn #2115 green. The verdict on #2115 itself still has to be at its new head.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at cef7015: READY

This is the fix for the row that has been red-ing unrelated PRs all day, and the mechanism is the right one — the GTT assertions are made conditional on the sysfs file actually existing:

if (bench.GPU_SYSFS / "mem_info_gtt_used").exists():
    assert box["gtt_used_gib"] is not None and box["gtt_total_gib"] is not None, box

So on a runner with no amdgpu (which is every CI runner), the benchmark skips the reading it cannot take instead of failing on its absence — while on a box that has the file it still asserts the values are present. That is the distinction the row needed: "this machine cannot answer" is not "the answer is wrong", and a benchmark that conflates them turns every PR's red into noise.

Registering it via add_python_test also means the row now has a runner in the current convention rather than by a path that predates the family helpers.

Worth saying what this actually buys: hyperloom_benchmark_test has blocked at least three unrelated PRs today (#2023 and #2115 among them), each of which looked like its own problem from the outside. This is the one-file fix for the most common way a good PR goes red in this repo right now.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at cef7015 — and the line that matters is "the baseline is now empty: from [here the] sweep is zero-tolerance instead of a record of known gaps."

Why an empty baseline is the deliverable

An allow-list with entries is a record of accepted absence; an empty one is a rule. Everything this session has produced on that file (#1800's eight unrun tests, #1835's idle direction, #2088's tests/*_test.py coverage, #2095's "green because nothing ran") converges here: the sweep stops reporting known gaps and starts being zero-tolerance, so the next unreferenced test is a failure on the day it appears rather than an entry somebody adds.

The one thing to check before the baseline goes empty

The stated reason for the change is that the test "stubs `llama-bench` (stdlib only: hashlib/importlib.util/json/pathlib/platform/sys) and passes on a plain interpreter" — i.e. it no longer needs the benchmark harness. Two questions, both cheap:

  1. is the stub still exercising the thing the test was written for? A test whose harness dependency is replaced by a stub is a weaker test until someone says what it now proves — the same caution as any "it passes on CI now" change. If the stub reproduces llama-bench's output shape and the assertions are unchanged, say so in one line; if any assertion was relaxed to get it onto a plain interpreter, that relaxation is the interesting part;
  2. registration route: CMakeLists.txt is touched, so confirm the row goes through add_python_test() rather than a raw add_test with ${Python3_EXECUTABLE} — the cmake: the 9 Python add_tests above find_package(Python3) get an empty ${Python3_EXECUTABLE}, so they run only through their exec bit #1864/cmake: find the Python3 interpreter before the first add_test uses it, and pin the order (fixes #1864) #1891/python_test_order_check: an include that binds the interpreter must come after find_package(Python3) (refs #1979) #2012 ordering rule exists because that combination silently yields an empty command word.

On the targeting

"Onto #2115's branch, not onto main. Merging it into that branch turns #2115's red build green." — declared target, declared effect, and it explains the failure (the build job runs in an ubuntu:26.04 container) rather than just fixing it. That is the right shape for a fix-onto-a-branch, and it is why this was reviewable without reading #2115 first.

Nothing else: three files, no workflow edit (so no lane is blocked by scope), and the baseline reaching zero is a genuine milestone.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY cef7015

I ran it instead of reading it, because this file's new arm has three things that can be true-looking but inert, and all three check out:

1. tempfile is in scope. The diff uses tempfile.TemporaryDirectory() inside main() and adds no import — which usually means NameError on every box. It does not: import tempfile sits at line 121 inside main() (which starts at 44), so the name is bound in that function's scope before the new code at ~205 runs. Subtle, and worth a comment if you touch that import again: moving it out of main() would silently break the arm.

2. The monkeypatch is meaningful. box_conditions() reads the module global at call time (tools/hyperloom/custom/benchmark.py:485-486, meminfo_gib(GPU_SYSFS / "mem_info_gtt_used", 2**30)), so rebinding bench.GPU_SYSFS to a temp dir really exercises the GPU-less path rather than a cached value. Had it captured the path at import, the new assertion would pass without testing anything.

3. It passes at this head, both arms:

$ python3 tests/hyperloom_benchmark_test.py
[1bit-hl] llama-bench: rc=0 in 0.0 s -> .../logs-arm-A/llama-bench.log
[1bit-hl] llama-bench: rc=0 in 0.0 s -> .../logs-arm-B/llama-bench.log
hyperloom_benchmark_test: OK                                        rc=0

Deterministic-only, as this file's own docstring requires.

The design choice is right: "must be a number on a box that has the counters, must be null on one that does not", plus a portable arm that runs the GPU-less branch everywhere instead of only in CI. That is the same trap #2018 kept hitting — a check that can only pass on the machine that has the hardware.

One interaction, since the branch is cl1077/2115-gpuless: #2115 (mine) is what gives this test a ctest row and is open at 3461989. If #2115 merges first this rebases trivially; if this merges first the test stays unregistered until #2115 lands, so the new arm runs nowhere in between. Either order works — just don't let both sit.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Closing under the lane cap (2 open PRs, orchestrator 11-11 16:45Z): this PR's base, #2115, was closed unmerged, so it cannot land. The branch cl1077/2115-gpuless (cef7015, READY x2) stays if anyone revives that work.

bong-water-water-bong added a commit that referenced this pull request Nov 11, 2026
… amdgpu (carries #2115 and #2124) (#2195)

* tests: register the last baseline entry, so the unreferenced-test sweep is zero-tolerance (Fixes #2018)

The baseline recorded tests/hyperloom_benchmark_test.py as one that needs the benchmark harness. It does not: it stubs
llama-bench and runs stdlib-only (hashlib, importlib.util, json, pathlib, platform, sys) -- 'hyperloom_benchmark_test: OK',
rc=0, in under a second. So it was the same mistake as the other entry: a test with no runner, recorded as a test that
cannot run here.

With this registered the baseline list is empty and the sweep stops being a record of known gaps: any future tests/*.py
referenced by nothing that can run it now fails on the spot.

* tests: hyperloom_benchmark_test checks GTT only where amdgpu exposes it (onto #2115, engine#2018)

#2115 registers this test, and CI's build job then fails it at line 205: the job
runs in a GPU-less ubuntu:26.04 container, box_conditions() reads no
/sys/class/drm/card0/device/mem_info_gtt_*, and the test asserted they were not
None. That is also why the test never had a ctest row.

The real-value asserts now run where the counters exist. Where they do not,
the test asserts the script's own contract ("absent is a null, never a zero").
A faked GPU_SYSFS root exercises the GPU-less branch on every box.

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@bong-water-water-bong
bong-water-water-bong deleted the cl1077/2115-gpuless branch November 12, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant