Repository navigation
Conversation
juhovainio
left a comment
There was a problem hiding this comment.
I went through this against the saved diff and CI. The approach looks sound (header-only reads, the shown-figure predicate driving the verdict, --report staying offline), but there are three things to sort out before merge.
Needs fixing
xtask ascii_output_contractfails on the\u{...}literals inis_safe_listed_name(see inline). This one test is what turns Test (affected crates), windows-build-and-test and Coverage red, and because nextest cancels afterwards, about 167 tests did not run, so the rest of the PR's unit tests are unverified in CI.- CodeQL reports "Uncontrolled allocation size" at the
Vec::with_capacityin the range reader (inline). It looks like a false positive given the bounds, but clamping makes that visible and clears the check. - The description starts with
Closes #781, yet it also says part 4 of #781 (a way forrocm serveto set the context size) is not included. Merging would auto-close the issue with that ask undone.Refs #781/ "Part of #781" fits better unless part 4 lands here.
Worth addressing, not blocking
- A device mask that hides every GPU now yields
Noneinstead ofDedicated(0.0)for curated models, and the new mask note is user-visible too. Neither is listed under "What changes" in the description, and the first is covered by a unit test only. Please list both and say why there is no scenario. - The remote Hugging Face flow (listing, HEAD, range) has no end-to-end coverage through the CLI; the hub stand-in in diagnose-50 only answers 404. The description is honest about this; just keep naming the gap.
- Any non-catalog
owner/repo:tagnow triggers a hub request by default. It is documented in --help and the README, so I read it as deliberate. - At roughly 8.6k lines (about 5k of them tests) this is hard to review in one go; splitting parser, reader and fit would have helped.
I didn't build or run anything locally, and I haven't re-verified the claim that each guard's test fails when the guard is removed. The MI300X, W7900 and Strix Halo Ubuntu lanes were still pending when I looked.
|
Thanks for the careful review. All three blocking points are addressed in 8a3c837 and the description:
The non-blocking points:
|
juhovainio
left a comment
There was a problem hiding this comment.
I re-read this at the current head. Nothing here blocks the merge on correctness, and CI is green, but I'm requesting changes on a few maintainability points that are cheaper to fix now than after the calibration text spreads further. Inline comments below.
- The measured-calibration narrative (the percentages and "fifteen of sixteen loads") is repeated in the gguf_fit.rs module doc, README, docs/testing.md, docs/manual-testing.md and
ESTIMATE_CAVEAT. These will drift independently; state it once and link from the others. - The GGUF header parser keeps two near-identical type-id tables (
Reader::valueandReader::array). A new type id has to be added in both. - The new diagnose glue in
apps/rocm/src/main.rs(mask helpers,ModelQuery, the report note) is about 130 lines of non-test code. A separate module fits the repo's guidance better. attach_context_estimateis about 110 lines doing three jobs (decide, read, rewrite the readiness per outcome). Splitting decide from apply would make it easier to follow.
One thing I could not confirm from the diff: classify_accelerator_memory now returns None for Some(&[]) from usable_amd_gpu_indices(). If that helper returns an empty list for anything other than an explicit device mask (for example a rocminfo failure), those hosts change from "0 GiB" to "no GPU visible". That is probably the right answer, but please confirm, and confirm CUDA_VISIBLE_DEVICES is honoured there as the mask note claims.
Also noting: the e2e scenarios only reach the cannot_estimate branch on GPU-less runners; the PR text already states this gap, so I'm not asking for more there.
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · 8a3c837
This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.
Review — needs work
Full review of the whole change (blind to this PR's existing review discussion). Implements the GGUF header-only context-size/memory estimate from #781 ("diagnose --model: take context size into account, and answer for models outside the catalog"); explicitly leaves part 4 of #781 (giving rocm serve a way to set context size) for a follow-up, matching the PR's own "Part of #781" framing — #781 is still open, so that framing is accurate (no "Closes" mismatch at this head).
Blocking
-
select_gguf_filesilently excludes any exact filename containing "mmproj", contradicting its own documented contract —crates/rocm-core/src/gguf_source.rs:159-166(doc: "An exact file name... picks that one," no stated exception),:168-171(theggufscandidate list drops every name containing"mmproj"before any match is attempted),:173-187(both the exact-name branch and its basename-fallback branch search only within that pre-filteredggufslist).
A repository whose only file matching a user-typed exact name happens to contain "mmproj" (a standard llama.cpp vision-projector filename) can never be selected that way, and the resulting "no GGUF file matching" error's file list (names_for_error(&ggufs)) is built from the same filtered list, so it won't even show the file exists. No test exercises exact-name selection of an mmproj-named file (the one existing mmproj test,a_quantization_label_picks_one_file_and_never_a_lookalike, only checks label-based matching, where excluding mmproj is correct and intentional).
Confidence 85 · logic · Fix: either scope the mmproj exclusion to the label-matching branch only (so an exact name always wins, per the doc comment), or state the exception explicitly in the doc comment and in the error's file listing. -
A test added to guard against context stepping below zero doesn't actually kill removal of the guard it documents —
crates/rocm-core/src/gguf_fit.rs:2696-2710(a_trained_context_below_one_step_that_does_not_fit_gives_no_context), guardingcrates/rocm-core/src/gguf_fit.rs:428(while context > 0 && !self.fits(context, kv, available)).
Hand-traced the test's exact numbers (weights=1000 MiB+0.4,available=2280.5 MiB, trained-context cap250): the loop reachescontext=0via theis_multiple_ofstep-rounding branch, and criticallyself.fits(0, kv, available)independently evaluates true for this data (shown total at zero context is 2280 MiB ≤ 2280.5 MiB available). So removing just the explicitcontext > 0 &&clause and letting the loop fall through tofits(0, …)produces the identical result (None) for this test's inputs — the test does not distinguish "guard present" from "guard absent" here, even though its doc comment ("used to step below zero: a panic in a debug build... a wrap... in a release one") frames it as covering exactly that. It does genuinely kill the separateis_multiple_ofstep-rounding mutation (reverting that to plain subtraction underflows at250-256). The actual dangerous case the explicit guard protects —availablebelow even the zero-context total, sofits(0, …)is also false and the loop would underflow — is never constructed by any test in this file.
Confidence 75 (independently re-derived the same arithmetic by hand against the source; no Rust toolchain was available in this environment to compile and run the mutation directly) · logic/test-quality · Fix: add a case wherefits(0, kv, available)is false (i.e.availablebelow the zero-context shown total) so the loop would try to step below zero without the explicit guard, and confirm that test fails when the guard is removed.
Non-blocking
None
Decisions for the author
- Compute-buffer/runtime-reserve calibration rests on one GPU class (MI300X, a 192 GB data-center card) and one engine build — tradeoff. The motivating use cases named in #781 are mostly low-VRAM consumer Radeon cards, where process overhead as a fraction of available memory could behave differently than on a card with far more headroom. The standing caveat and per-model notes are honest about "another GPU... can differ," so this isn't a defect, but worth confirming that shipping with single-GPU-class calibration is the deliberate v1 scope rather than an oversight, and that a consumer-card data point is tracked for a follow-up.
- No e2e scenario for "a device mask hides every GPU" or "device mask on a host whose GPUs differ in memory" — non-blocking-improvement. The PR description explains the gap (no GPU on hosted e2e runners; no CI lane has a host with differing GPU memory), which satisfies this repo's AGENTS.md ("say why in the PR text"). That rationale doesn't currently appear in
docs/testing.mdor the feature file itself, though — only the PR description, which is harder to find once the PR is closed. A one-line pointer indocs/testing.md's GGUF-estimate section would make the rationale discoverable later. - This PR is large for one review pass — non-blocking-improvement. ~8,124 insertions across three new modules (
gguf.rs1,410 lines,gguf_source.rs1,536 lines,gguf_fit.rs3,185 lines) plus a 932-line change tomodel_readiness.rsand a 446-line change tomain.rs. This review only achieved full coverage by splitting into four independent workers grouped by coupling (header parser + source reader; fit arithmetic; diagnose/CLI integration; docs + e2e). Splitting the PR itself along those same seams (parser, then reader, then fit math) would make future review passes more tractable, as the size alone forced a heavier process here than a typical change needs. AcceleratorMemory::Dedicated(_)is matched inside a branch it can never reach — informational, not a defect.crates/rocm-core/src/gguf_fit.rs:741matchesUnknown | Dedicated(_)inside theelseof alet Some(available_gib) = memory.measured_gib() else { … };measured_gib()(crates/rocm-core/src/model_readiness.rs:128) always returnsSomeforDedicated(_)(including0.0), so that sub-pattern can't be hit under current semantics. Harmless (it would do the right thing if ever reached) and arguably reasonable defensive coding against a future change tomeasured_gib(); flagging only so nobody mistakes it for an independent safeguard against the device-mask regression this PR fixes — that fix lives entirely inmodel_readiness.rs'sclassify_accelerator_memory, which the curated-model path and the GGUF-estimate path both consume from one shared value, so they can't disagree by construction.
Positive signals
crates/rocm-core/src/gguf_fit.rs's single-predicate design (Sizing::fitsdriving the verdict,max_context, and every suggestion) is backed by a property-style test (the_verdict_the_largest_context_and_the_suggestions_are_one_decision, ~line 2498) sweeping many synthetic shapes and boundary memory values — a genuine, not just claimed, guarantee that these three outputs can't contradict each other.- The device-mask "no GPU visible" fix (
classify_accelerator_memory'sfold(0.0, f64::max)→reduce(f64::max)change inmodel_readiness.rs) correlates GPUs byusage.indexrather than position, consistent with this repo's "correlate by id, not position" rule, and both the curated-model and GGUF-estimate code paths read the oneHostFacts.accelerator_memoryvalue computed once, so they can't disagree about GPU visibility. - Untrusted-header handling in
gguf.rs/gguf_source.rsis thorough: every stated bound (64 MiB header, layer/entry/array-depth counts, kept-metadata counts, the 2-minute remote-read budget) is enforced at the point it's used rather than just computed, path-traversal and bidirectional/zero-width filename filtering is layered and tested at both ends of its code-point ranges, and dropped-connection/short-body/wrong-range-start/ignored-range all get distinct, specific error text rather than being coerced into one generic error. diagnose-50's "--reportmakes no hub request" e2e scenario (tests/e2e-cucumber/tests/e2e/diagnose_steps.rs:3578-3589) uses a real request counter against a real stand-in TCP listener, and pairs the "stays at zero" assertion with a complementary "the same question without--reportdoes reach the hub" assertion (:3606-3618) — guarding against the first assertion passing vacuously because nothing was ever wired up to begin with.
Deployment notes
- Any non-catalog
owner/repo:tagpassed to--modelnow triggers a Hugging Face network request (repo listing, and possibly aHEADand range requests) by default, where it previously made none. This is documented in--helpand the README, but an operator who relied onrocm diagnosebeing fully offline for unrecognized models should know the default changed;--reportis the escape hatch. - No migration or new required configuration;
--context-size/--kv-cache-typeare optional with safe, clap-enforced defaults and bounds.
What this covered
Every file in the diff, read completely: README.md, apps/rocm/src/main.rs, crates/rocm-core/src/{gguf,gguf_fit,gguf_source,lib,model_readiness,net}.rs, docs/{architecture,manual-testing,testing}.md, skills/rocm-doctor/reference.md, and the three tests/e2e-cucumber files — plus the surrounding unchanged code needed to judge each (e.g. AcceleratorMemory/measured_gib in model_readiness.rs when reviewing gguf_fit.rs, and the xtask ASCII-output-contract scanner when verifying the bidi/zero-width filter). Reviewed via four independent fan-out workers grouped by coupling (gguf.rs+gguf_source.rs+net.rs; gguf_fit.rs alone, ~3,185 lines, read in full without needing a further split; model_readiness.rs+main.rs+lib.rs+net.rs; docs+e2e), each reporting candidate findings and explicit per-test revert/branch verdicts independently and blind to each other and to this PR's review discussion; none failed or needed respawning. I then independently re-verified the two findings that survive above by re-tracing the relevant source directly (not by trusting the workers' framing), resolving one cross-worker contradiction against the source in the process: a worker scoped to main.rs/model_readiness.rs reported no integration test proves --report skips model assessment, but the diagnose_steps.rs/diagnose.feature worker's scope shows diagnose-50 is exactly that integration test (a real CLI invocation against a real counting stand-in server) — confirmed directly and dropped as refuted. A few other candidates (a docs/architecture.md module-list ordering question, a CodeQL-clamp "already redundant" observation, a missing raw data table in docs/manual-testing.md) were also checked against source and dropped: the architecture.md ordering is explicitly deliberate per the adjacent prose ("the three gguf* modules below"), the CodeQL fix's own commit message already and accurately says the bound pre-existed, and the calibration data the table would have shown is backed by real test assertions in gguf_fit.rs even though no human-readable table duplicates it.
Commits reviewed: 2e514325…8a3c8375 (PR #826 head at review time).
Did not run as dedicated passes: history (git log/git blame) — the three gguf* files are new, and the review budget went to direct source verification of specific candidate findings instead of a separate blame sweep over model_readiness.rs/main.rs/net.rs; prior-changes — this method does not read prior review threads on this or related PRs, so "do earlier review comments apply here" could not be and was not checked. Code-comments was folded into each fan-out worker's per-file pass rather than run separately. CI: all 23 check runs plus the one external status at commit 8a3c8375 are green (including Test (affected crates), windows-build-and-test, Coverage, CodeQL, clippy, and the e2e suite) — no failing or pending checks at the reviewed commit. Ticket #781 was retrieved directly from GitHub (title/body/state); not reconciled against any discussion on PR #826 itself, which was deliberately not read.
🤖 Generated with Claude Code
|
Pushed bd8015e for the four inline points; I've replied on each. On the question in the review body: Can
An unreadable topology with In the second case, Is The mask note does fire for Making the shared helper read bd8015e does not change behaviour. I ran it locally: the workspace tests pass except one |
juhovainio
left a comment
There was a problem hiding this comment.
Looks good to me. This is a diff read; I did not build or run it, and CI was still running on a few lanes when I looked.
It delivers items 1-3 of #781 and is explicit about deferring item 4, APUs and vLLM. Docs, scenarios (diagnose-48 to 50) and file layout follow the repo conventions, and I found nothing blocking. A few non-blocking points:
- The real Hugging Face redirect/range path is only covered by loopback unit tests plus a 404 stand-in in diagnose-50, so nothing in CI reads from the real hub or follows a CDN redirect. Worth a manual check before release.
--reportnow ignores--modelfor curated models too, while the README and PR body describe the change as GGUF-only. Either say so in the docs or confirm it is intended.- The "mask hides every GPU gives no GPU visible" change for curated models is unit-tested only; the PR body justifies the missing scenario, which is fine, just noting it.
- Design questions for you: the estimate sits under
verdict: undetermined, calibration comes from two dense models on one GPU and build, and flash attention is assumed. Those are reasonable for a first cut but worth stating as known limits in the docs. - Minor:
gguf_fit.rsis large and carries about a dozen parallel*_gib/*_bytesOption fields; a small struct could tidy that in a follow-up.
Two small doc nits are inline.
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · bd8015e
This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.
Review — needs work
Review of changes since the review at 8a3c8375 (README.md, apps/rocm/src/diagnose_model.rs [new], apps/rocm/src/main.rs, crates/rocm-core/src/gguf.rs, crates/rocm-core/src/gguf_fit.rs [doc-only diff], crates/rocm-core/src/model_readiness.rs, docs/architecture.md, docs/manual-testing.md, docs/testing.md), plus crates/rocm-core/src/gguf_source.rs and crates/rocm-core/src/gguf_fit.rs read whole and fresh (both carried open findings from the prior round). Implements items 1-3 of #781 ("diagnose --model: take context size into account, and answer for models outside the catalog"); item 4 (giving rocm serve a way to set context size) and APUs/vLLM are explicitly deferred, matching #781's own scope note — #781 stays open, and the PR description's "Part of #781" framing is accurate.
Blocking
-
select_gguf_filesilently excludes any exact filename containing "mmproj", contradicting its own documented contract —crates/rocm-core/src/gguf_source.rs:167-213(doc comment: "Pick the GGUF file avariantnames... An exact file name, or a quantization label," no stated exception). The candidate list (ggufs, line ~171) drops every name containing"mmproj"before either the exact-name branch or the label-matching branch runs, and the "no GGUF file matching" error's file listing (names_for_error(&ggufs)) is built from that same filtered list. A repository whose only file matching a user-typed exact name happens to contain "mmproj" (a standard llama.cpp vision-projector filename) can never be selected that way, and the error won't even show the file exists. The one existing mmproj test (a_quantization_label_picks_one_file_and_never_a_lookalike) only exercises label-based matching, where excluding mmproj is correct; no test exercises exact-name selection of an mmproj-named file. This file was unchanged since the prior round; re-derived fresh by reading the whole file blind.
Confidence 85 · logic · Fix: scope the mmproj exclusion to the label-matching branch only (so an exact name always wins, per the doc comment), or state the exception explicitly in the doc comment and the error's file listing. -
The test guarding "context stepping below zero" doesn't actually kill removal of that guard —
crates/rocm-core/src/gguf_fit.rs:2709-2723(a_trained_context_below_one_step_that_does_not_fit_gives_no_context), guarding thewhile context > 0 && !self.fits(context, kv, available)loop inSizing::max_context(around line 436). Hand-traced the test's own numbers (weights = 1000 MiB + 0.4 bytes,available = 2280.5 MiB, capSome(250)): the loop stepscontextfrom 250 to 0 via theis_multiple_ofrounding branch, andfits(0, kv, available)independently evaluates true here (shown_total(0,…) = 2280 MiB <= 2280.5 MiB), because the test's+0.4byte remainder onweightsrounds away rather than pushingmib_upto the next whole MiB. So removing just thecontext > 0 &&clause produces the identical result for this test (the loop still stops atcontext == 0via the!fits(0,…)becoming false on its own) — the test cannot distinguish guard-present from guard-absent. The genuinely dangerous case (availablebelow even the zero-context shown total, sofits(0,…)is also false and the loop would underflow) requiresshown_total(0,kv)to exceedfixed()viamib_up's rounding up — achievable by givingweights(orcompute_base) a remainder just over a MiB boundary rather than the test's+0.4(which rounds down) — and no test in this file constructs it. This file also changed in the latest commit, but only by added doc comments (verified they don't contradict the code or the sixteen calibration loads they describe); this test and the loop it guards are unchanged since the prior round.
This directly engages AGENTS.md §3 ("A new test is shown to fail when the code it covers is broken... A test that stays green with its target removed is not coverage").
Confidence 80 (hand-traced the exact arithmetic against the source; no Rust toolchain available in this environment to compile and run the mutation) · logic/test-quality · Fix: construct a case wherefits(0, kv, available)is false (give a fixed component a byte remainder that rounds up viamib_up, withavailablebetween the rawfixed()sum and the roundedshown_total(0,kv)), and confirm that test fails when the explicit guard is removed.
Non-blocking
None
Decisions for the author
- Compute-buffer/runtime-reserve calibration rests on one GPU class (MI300X, a 192 GB data-center card) and one engine build — tradeoff. #781's motivating cases are mostly low-VRAM consumer Radeon cards (R9700, 7900 XTX, 5700, 9060 XT, an 890M APU), where process overhead as a fraction of available memory could behave differently than on a card with far more headroom. The module doc, README and
ESTIMATE_CAVEATare all honest that "another GPU... can differ," so this isn't a defect — worth confirming this is the deliberate v1 scope rather than an oversight, and that a consumer-card data point is tracked for a follow-up.
Positive signals
- The latest commit's maintainability refactor is behavior-preserving, confirmed by independent re-reading rather than trusting its "no change in behaviour" claim:
attach_context_estimate's split intoplan_context_estimate/read_context_estimate/apply_context_estimate(model_readiness.rs) traces to an identical set of branches andreadinessmutations as before for every combination of catalog/undetermined-reason/reference-shape state; theapps/rocm/src/main.rs→diagnose_model.rsextraction movedKvCacheTypeArg/ModelQuery/report_ignores_model_note/attach_gguf_estimatewith every call site updated and no leftover dead code or unused imports; andgguf.rs's merge of the two near-identical type-id match tables into onefixed_width()function is exercised correctly for every type id bysigned_values_of_every_width_decode_by_their_own_sign_bit, which checks both ends of every width (1/2/4/8 bytes, signed and unsigned). gguf_fit.rs's single-predicate design (Sizing::fitsdriving the verdict,max_context, and every suggestion) is backed by a genuine property-style sweep (the_verdict_the_largest_context_and_the_suggestions_are_one_decision) over many synthetic shapes and boundary memory values, not just a claim.- Untrusted-header/network handling in
gguf_source.rsis thorough and was independently re-verified:is_safe_listed_name's bidi/zero-width filter is tested at both ends of every excluded code-point range, the 2-minute total-read budget is enforced ahead of each request (not just summed afterward), and a wrong range-start, a short body, and a short-but-consistent body each get distinct, specific error text rather than one generic "truncated."
Deployment notes
- Any non-catalog
owner/repo:tagpassed to--modeltriggers a Hugging Face network request by default (listing, possiblyHEADand range requests), documented in--helpand the README. - No migration or new required configuration.
What this covered
DEGRADED REVIEW (partial fan-out). I attempted a 5-way fan-out (gguf_source.rs; gguf_fit.rs; model_readiness.rs+diagnose_model.rs; main.rs; gguf.rs+docs), but this environment had ~20+ unrelated subagents already running under the same session-level concurrency ceiling before I started (not spawned by this review), so 4 of 5 spawn attempts were rejected outright and the 1 that did launch (gguf.rs + docs) had not returned after 8+ minutes by the time I needed to publish. I read and reviewed all five groups directly in this single top-level context instead — including gguf.rs and the four doc-diff files the unreturned worker was also assigned — so in substance this ran as a single-context review, not an independently cross-checked one; treat it accordingly.
Read completely and fresh: crates/rocm-core/src/gguf_source.rs (1536 lines) and crates/rocm-core/src/gguf_fit.rs (3198 lines) in full, blind to the files' own history; the diff since 8a3c8375 in README.md, apps/rocm/src/diagnose_model.rs, apps/rocm/src/main.rs (plus enough of the surrounding 9723-line file to confirm every call site of the moved items was updated and no import went unused), crates/rocm-core/src/gguf.rs (full file, 1412 lines), crates/rocm-core/src/model_readiness.rs (full file, 2225 lines), and the four changed doc files. Commits reviewed: 8a3c8375…bd8015e3 for the diff, full-file reads at bd8015e3.
One integrity check worth recording: midway through, a locally-cached copy of main.rs was found to contain a different commit's content (likely overwritten by one of the unrelated concurrent agents sharing this environment's tool-result cache) before I had drawn any conclusion from it; it was re-fetched and re-verified against the bd8015e3 SHA before use.
Contamination disclosure. The mandated claim-check (confirming no existing review already covers this exact head) required fetching this PR's review list, which necessarily surfaced the full prior automated review's text, including the verbatim wording of the two findings above, before I re-read gguf_source.rs and gguf_fit.rs myself. I re-derived both independently — tracing select_gguf_file's control flow and hand-computing the max_context test's arithmetic from scratch against the current source, rather than asserting the prior wording — but a fully blind re-derivation this was not, and that limits how much weight "re-found independently" should carry here.
Did not run as dedicated passes: history (git log/git blame) and prior-changes (do earlier review comments apply here) — this method does not read prior review threads, so the latter could not be checked at all. Code-comments was folded into the per-file reads rather than run separately. CI: all 23 check runs (including Test (affected crates), windows-build-and-test, Coverage, CodeQL, clippy, and the e2e suite) are green at bd8015e3, re-confirmed immediately before publishing. Issue #781 was retrieved directly (title/body/state); not reconciled against any discussion on PR #826 itself beyond the mandated claim-check above.
🤖 Generated with Claude Code
jussielo-amd
left a comment
There was a problem hiding this comment.
Requesting changes. The approach is sound and several pieces are well tested (one predicate decides the verdict, the largest context and every suggestion, backed by a sweep test; diagnose-50 pairs "no request under --report" with "the same question without --report does reach the hub"). The blockers are about the branches this PR adds that no test pins, tests that cannot fail on what they claim, scenario coverage for observable behaviour, and one claim in the description.
Reviewed at bd8015e. Line numbers below are at that head. I broke each branch in a scratch copy and ran the lib tests; "survives" below means the whole rocm-core suite (1067 tests) stayed green with the branch broken, including when all the rocm-core mutations were applied at once.
Blocking
1. The description says each guard's test was shown to fail when the guard was removed, and that does not hold. In "Testing". Removing the listing-request timeout (gguf_source.rs:338), the HEAD-request timeout (gguf_source.rs:531) or the percent-encoding of listed file names (gguf_source.rs:493) all survive. The "Untrusted input" bullet presents the two-minute read bound and the file-name handling as guarded. Either add the tests in 2, or narrow the claim.
2. Branches this change adds are pinned by no test. Each of these survives a single-branch mutation:
gguf_source.rs:493:encode_segmentreplaced byto_owned. No fixture name needs encoding, so add a fake-hub name with a space or+and assert the request line.gguf_source.rs:338,:531: timeouts removed.the_whole_read_has_a_deadline_not_only_each_requestonly drivesRangedReader.gguf_source.rs:153:is_boundary(before, &['_'])changed to&[]. Every fixture puts-before the label, somodel_Q4_K_M.ggufis untested.gguf.rs:418:.max()changed to.min()for an array-valuedfeed_forward_length. The compute buffer is sized from it.diagnose_model.rs,device_visibility_mask_is_setandattach_gguf_estimate: deletingCUDA_VISIBLE_DEVICESfrom the list survives, and so does swapping the two calls inattach_gguf_estimate(with the mask note first the estimate is stillNone, so the caveat never fires). The README and--helppromise this note, andthe_unverified_mask_note_reaches_an_estimate_when_the_gpus_differpassesmask_setstraight tonote_mask_for_rows, so it never sees the env reader or the call order. An injectable lookup, plus one test drivingattach_gguf_estimatewith a fakeGgufSourceand two unequal-size rows, would cover both.model_readiness.rs:1037-1041: label forced torecipe note.contains("note: these are estimates")is also a substring ofrecipe note: these are estimates, so assert the exact prefix and!contains("recipe note").model_readiness.rs:702:DEFAULT_CONTEXT_SIZEchanged from 4096 to 2048. The test at:1820compares the constant with itself, while--helpand the README state 4096 independently.gguf_fit.rs:1011:<changed to<=, which hides "largest context that fits" when the request equals the ceiling exactly.gguf_fit.rs:823: the trained-context cap dropped from the smaller-cache suggestion. The test uses 0.6 GiB of room, so the cap never binds.gguf_fit.rs:721:||changed to&&in the missing-width note. No test has exactly one width missing.
The new tests call new API, so reverting the whole change fails to compile. The single-branch mutations above are the check that matters.
3. Three tests cannot fail on what they claim.
- The step
no model weights were fetched(diagnose_steps.rs:2082-2105, used bydiagnose.feature:749and:759) asserts two directories do not exist. A local-file estimate only callsFile::open, so it cannot go red. No scenario drives a successful remote read, as the description admits, so the claim is unasserted where it matters. gguf.rs:1106,a_header_that_spans_more_than_the_size_bound_is_refused, assertscontains("is larger than"), which both theskipandcheck_sizemessages contain. The fixture reachesskipfirst, so thecheck_sizeguard in the name is never exercised.gguf.rs:892,arrays_are_skipped_not_stored...: the name says the array length is what is kept, but it never reads thellama.after_the_arraysvalue it builds.
Please assert specific behaviour, or rename.
4. Observable behaviour that only has unit coverage, with no argument in the description (AGENTS.md section 3). These are deterministic and host-independent, like diagnose-48 and diagnose-49:
--context-sizeor--kv-cache-typewithout--model(exit 2).- The curated-model line saying the context options do not apply.
- A missing local
.ggufpath, which now says "could not be opened" and no longer offers the upstream-tracker route. - Bare
owner/repowith--context-size.
The description argues the mask items and the remote read, and I accept those. Separately, diagnose-49's ESTIMATED label is only reached on lanes that measure GPU memory: I made header_label never return ESTIMATED and both new scenarios stayed green on a GPU-less host. Please name the lane that covers it.
Non-blocking
- Local
.ggufheader parse is about 20 times slower than it needs to be.gguf_source.rs:468-472wraps the file in aBufReader, and the string-array loop atgguf.rs:348-355skips each token with a seek, which drops the buffer every time. I measured a 300k-token header at 5.3 s in a debug build against 0.24 s through aCursor. Real vocabularies are 128k to 262k tokens, sodiagnose --model ./x.ggufstalls for seconds.seek_relative, a larger buffer, or reading the header span into memory would fix it. - A
ROCR_VISIBLE_DEVICESmask can make the estimate compare the wrong card.usable_amd_gpu_indices_fromre-indexes ROCR survivors to0..N(host_gpu.rs:1397-1424), whileclassify_accelerator_memorymatches against amd-smi'susage.index(main.rs:3193-3197). I read that as the physical ordinal but did not check it against amd-smi. If so,ROCR_VISIBLE_DEVICES=1on a 24 GiB plus 8 GiB host picks the 24 GiB row, and the header can readESTIMATED: fitswith the caveat only at the bottom. ConsiderCannotEstimateon a mixed host with ROCR set, or translating the ordinals. - The device-mask variable list is stated in four places (
device_visibility_mask_is_set,MASK_UNVERIFIED_NOTE, the--modelhelp and the README). They agree today; section 3 asks for one shared constant and a test tying producer to consumer. LiveGgufSource::with_hub(gguf_source.rs:315) andRangedReader::fetched(:576) arepubbut only tests call them. Several tests also build a manual temp dir that a failed assertion leaves behind, thoughtempfileis already a dependency.
Decisions for you
- Calibration rests on one GPU class (MI300X) and one llama.cpp build, while #781's audience is mostly consumer Radeon cards. The caveat is honest; please confirm this is the intended v1 scope.
- The suggestions name llama.cpp options, including
--n-gpu-layers, for a knobrocm servedoes not expose yet. That is advisory text, not a silent CPU fallback, but sections 6 and 7 are strict about the no-CPU-fallback policy, so please confirm it is intended. shard_partsandfirst_shard_of_one_setpick a first shard, thengguf_fit.rs:469refuses everysplit.count > 1. The machinery only changes which message the user sees.- The new clamp in
8a3c8375has anelsearm that cannot run, sincewantedis already at mostWINDOW_BYTES. The comment explains it is there for the analyser; I am noting it as a deliberate tradeoff. - A remote read can sit silent for up to two minutes, and
diagnosewas offline before. Section 6 points toAnimatedSpinnerfor progress that goes quiet.
Not covered
I did not run the GPU or Windows lanes, the real Hugging Face path or workspace-wide clippy and coverage, and I relied on CI for those. The calibration-figures duplication I would otherwise have raised is fixed in bd8015e. I did not finish a line-by-line review of the new fixed_width table in gguf.rs or the plan/read/apply split of attach_context_estimate in model_readiness.rs; the existing suite stays green over them, and that is all I checked there. The moved main.rs glue keeps its behaviour and its tests, and none were dropped.
|
@nowycondro, replying to your automated review at 8a3c837: Thanks. Both blocking findings held against the code, and both are fixed in 3cff3aa.
The refusal is a typed error, so the estimate reports it with its own Unit tests cover each of those cases, and I confirmed they fail when the case-insensitive match, the model-only list, the shared-name rule, the typed refusal or the concrete verify line is removed. The new scenario One difference this leaves: The below-zero guard. Confirmed by mutation: with On the rest:
|
juhovainio
left a comment
There was a problem hiding this comment.
Read through the whole diff at this head and found nothing blocking. CI is green across all 24 checks.
What I liked: the GGUF parser bounds every hostile-input axis and never reads tensor data; a single predicate drives the verdict, the largest context and the suggestions, so the output can't contradict itself; and scenario diagnose-50 proves --report stays offline with a request-counting stand-in hub.
Non-blocking notes, none of which need to hold the merge:
- The estimator allowances (runtime reserve, compute buffer) are calibrated on one MI300X with one llama.cpp build, while #781's audience is mostly Radeon cards. The caveat is shown and documented, but a manual check on a Radeon would be a nice follow-up.
- The suggestion text names
llama-server --ctx-size; in recent llama.cpp builds that value is shared across--parallelslots. Right for a single slot, but the report doesn't say so. I haven't verified this against the pinned build. list_filestrusts the?blobs=truesiblings list to be complete; for very large repos that may not hold. Also unverified.- The remote read follows redirects and re-sends the Range header to the CDN. No credentials are sent, just confirm that's intended.
- The device-mask fix for curated models is a separable change that could have been its own PR, though it's called out in the body and unit-tested.
One question inline.
|
@nowycondro, a follow-up on both findings, now in f84afbf.
Why an exact projector name is refused rather than selected. A projector is the vision encoder that ships beside a llama.cpp model. It is not a language model, and this command estimates a language model's weights plus KV cache at a context size.
The zero-context guard. Done as you described. Mutation evidence, rerun at the current head: I removed Only this test fails, so it is the one that pins the guard. The older |
|
@jussielo-amd, thank you, this was a thorough review. Everything below is in f84afbf. I confirmed each finding by mutation before fixing it, and the description is corrected. How I checked. A script applies one mutation at a time to the working tree, runs the whole 1 and 2: branches no test pinned. All twelve you listed survived at
For the last two, the environment lookup is now injectable ( On the description. It was wrong, and it's corrected. The Testing section now says plainly that the earlier "each guard's test was shown to fail" was not true, lists the mutations that were checked and how, and says the other guards were not checked one by one. 3: tests that could not fail.
4: scenarios for observable behaviour. I added four host-independent scenarios. Breaking the behaviour each covers fails it (all four at once, in one build):
The ESTIMATED label in
Non-blocking.
Decisions for me.
|
|
One more push after the replies above: bd91383 answers @juhovainio's inline question on the plan for an estimate that cannot compare (details in that thread). Nothing else changed. |
juhovainio
left a comment
There was a problem hiding this comment.
Re-reviewed at the current head. No blocking findings; steps 1-3 of #781 are in and the PR correctly leaves the issue open for the rocm serve context option.
Non-blocking:
--reportnow skips--modelfor curated models too, though only the GGUF path can touch the network (inline). Worth confirmingreport.modeloutput for a curated--report --modelis unchanged, or calling the change out.- SSM/hybrid models still headline
ESTIMATED: fitswith the recurrent state uncounted (inline). MoE has the same shape: the figure can be low and only a note line carries the caveat. - The PR body still carries review-round history ("an earlier version said... not true"). Per the repo's guidance it should read from the final diff.
- The curated-model empty-mask
classify_accelerator_memorychange and theRelativeSeekBufread optimisation are separable from the estimator and could be their own PRs.
Questions: calibration is two dense models on one GPU and engine build, and the estimate compares against the single largest GPU only. Both are fine for a first cut, but worth stating in the docs as the estimate's limits.
CI had no failures when I looked; the MI300X lane had not yet run, so the compared branch of diagnose-48/49 is unexercised so far.
|
@juhovainio, thanks for the re-review. On the points in your review body (the inline ones are answered in their threads):
|
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · cf654f9
This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.
Review — no blocking findings
Review of changes since the review at bd8015e3 (README.md, apps/rocm/src/diagnose_model.rs, apps/rocm/src/model_readiness.rs, crates/rocm-core/src/gguf.rs, crates/rocm-core/src/gguf_fit.rs, crates/rocm-core/src/gguf_source.rs, docs/manual-testing.md, docs/testing.md, skills/rocm-doctor/reference.md, the three tests/e2e-cucumber files), plus a small diff-with-context read of apps/rocm/src/main.rs. gguf_source.rs and gguf_fit.rs both carried open findings from the prior round and both changed again in this window, so both were read whole and fresh rather than trusted from before. Implements items 1-3 of #781 ("diagnose --model: take context size into account, and answer for models outside the catalog"); item 4 (giving rocm serve a way to set context size), APUs and vLLM are explicitly out of scope and #781 stays open — the PR's "Part of #781" framing is accurate, no "Closes" mismatch.
Blocking
None. Both findings open from the prior round are resolved at this head, independently re-verified against the source rather than taken on trust:
select_gguf_file(crates/rocm-core/src/gguf_source.rs:224-275) now searches the exact-filename branch over every GGUF file including projectors (all_ggufs), filters to non-projectormodelsonly afterward, and returnsProjectorNamedonly when the exact name matched exclusively projector(s) — a model sharing a name with a projector is now selected correctly, and the "no match" error listing also now shows projector files instead of hiding them. Confirmed by direct re-read, not by trusting the commit message.Sizing::max_context's zero-context guard (crates/rocm-core/src/gguf_fit.rs, thewhile context > 0 && !self.fits(...)loop) is now backed by a genuinely discriminating test,when_even_zero_context_does_not_fit_the_search_stops_at_zero, hand-traced end to end: with the guard removed,contextwould reach0u64 - 256, an underflow. The previously-weak test (a_trained_context_below_one_step_that_does_not_fit_gives_no_context) was also updated to exercise the "round onto a whole step directly via integer division" fix rather than a step that happened to not distinguish guard-present from guard-absent.
Non-blocking
UnmeasuredLayout's GiB→MiB conversion omits this file's own established rounding epsilon.crates/rocm-core/src/gguf_fit.rs:795-799computes(available_gib * 1024.0).floor()for the "compare it with this GPU's X MiB yourself" detail string on the SSM/MoE no-verdict path. Every other GiB→MiB floor conversion in this file (e.g.crates/rocm-core/src/gguf_fit.rs:1040,let offered = available.mul_add(1024.0, 1e-6).floor();) uses a1e-6epsilon specifically to stop floating-point representation error from understating the host's memory by 1 MiB — explained by its own adjacent comment ("a host never reads as offering more than it does"). The new line is display-only (not read by any verdict/arithmetic decision) and would only show 1 MiB less than the true figure in a rare float-representation edge case, but it's an unexplained inconsistency against a pattern this same file established for the identical computation.
Confidence 85 · mechanical · Fix: useavailable_gib.mul_add(1024.0, 1e-6).floor()here too, or factor the GiB→MiB-floor step into one shared helper used by both call sites.
Decisions for the author
classify_accelerator_memorydoes not filter byCUDA_VISIBLE_DEVICES, only byHIP_VISIBLE_DEVICES/ROCR_VISIBLE_DEVICES— tradeoff, self-documented.device_visibility_mask_is_set's own doc comment (apps/rocm/src/diagnose_model.rs:149-158) states the HIP runtime honoursCUDA_VISIBLE_DEVICESbut the memory classification doesn't, so when only that variable is set, every amd-smi row is still treated as visible and the largest card on the box can be picked even if that exact card is the one hidden from the runtime.MASK_UNVERIFIED_NOTEalready namesCUDA_VISIBLE_DEVICESexplicitly and warns the compared card "may be that of a different card" whenever a mask is set and GPUs differ in size, which is broad enough to cover this exact failure mode, not just a narrower "wrong card among a known set." This reads as a deliberate, mitigated v1 limitation rather than a silent regression — worth confirming that's the intended scope, since a real fix (actually deriving aCUDA_VISIBLE_DEVICES-filtered index set) is a bigger lift than a quick patch.- Compute-buffer/runtime-reserve calibration still rests on one GPU class (MI300X) and one llama.cpp build — tradeoff, unchanged this round. #781's motivating cases are mostly low-VRAM consumer Radeon cards; the module doc, README and
ESTIMATE_CAVEATare all honest that another GPU or build can differ, so this isn't a defect, just worth reconfirming as the deliberate v1 scope now that the feature is close to merge. - The duplicative wording when a model hits the new SSM/MoE "no verdict" path — non-blocking-improvement. The per-layout
notepushed earlier (crates/rocm-core/src/gguf_fit.rs:727-741, e.g. "the recurrent state they keep is not counted...") and the newdetailmessage (:795-799, "...so the total above can be too low") overlap in what they say without contradicting each other. Not a bug, just mildly redundant output; hownotesvsdetailcompose on screen lives outside this file.
Positive signals
ProjectorNamed's typed-error contract is honored end-to-end: raised at exactly one site (gguf_source.rs:276-281) and downcast at exactly two consumer sites (gguf_fit.rs,model_readiness.rs), verified by a repo-wide grep — no stray handling, no site coercing it into a generic error.- The new
header.ssm || header.moe"no verdict" early-return (gguf_fit.rs:785-800) is correctly sequenced after the figures (weights_gib/total_gib/available_gib) are populated but beforemax_context/suggestionsrun, so it can't leave a stale max-context or a suggestion that contradicts the "no verdict" outcome — confirmed by reading the full function top to bottom, and the pre-existing property-style sweep test's model builders were checked to not set any SSM/MoE metadata, so the new path can't silently interact with that sweep's own assertions. RelativeSeekBuf's claimed perf fix (a 300k-token local header: 342ms → 38ms) is backed by a real behavior-pinning test (a_local_header_is_skipped_through_without_seeking_the_file_per_token) using an instrumented reader that counts real seeks against a 50,000-token synthetic header — reverting the buffered relative-seek branch would drive the seek count far past the asserted threshold, not just leave a string assertion unchanged.main.rs/model_readiness.rsshare oneHostFactsread per diagnose invocation (explicit comment: "Read once: the memory classification and the mask caveat below both need the rows"), so the curated-model and GGUF-estimate paths consume one GPU-visibility classification and cannot disagree about it by construction.- Every new CLI-observable behaviour added since
bd8015e3(projector-by-filename refusal and naming, context-option refusal as a usage error, curated-model context-option no-op, missing-path handling, bare-repo-with-context-size, projector-marked listing) got a new Gherkin e2e scenario in the same round (diagnose-51throughdiagnose-56), not deferred to unit tests alone. - API surface was tightened correctly:
LiveGgufSource::with_hubandRangedReader::fetchedmoved to#[cfg(test)]-gatedpub, confirmed via a repo-wide grep that no production caller depended on either (previously anyrocm-coreconsumer could redirect hub requests to an arbitrary host viawith_hub).
Deployment notes
- Any non-catalog
owner/repo:tagpassed to--modeltriggers a Hugging Face network request by default (listing, possiblyHEADand range requests), documented in--helpand the README; unchanged this round. - No migration or new required configuration.
What this covered
Full diff bd8015e3...cf654f9c (13 files, +1658/-126) reviewed via four independent fan-out workers grouped by coupling: (1) gguf.rs + gguf_source.rs — the latter read whole and fresh (1972 lines) since it carried an open finding; (2) gguf_fit.rs alone, read whole and fresh (3481 lines), also carrying an open finding; (3) apps/rocm/src/diagnose_model.rs + crates/rocm-core/src/model_readiness.rs, both read whole and fresh, plus apps/rocm/src/main.rs's small diff with surrounding context (the 9722-line file itself was not re-read in full, as nothing in it carried an open finding and its delta here was ~13 lines); (4) docs (README.md, docs/testing.md, docs/manual-testing.md, skills/rocm-doctor/reference.md) + e2e (diagnose.feature, diagnose_steps.rs, e2e.rs). Each worker reported independently and blind to the others and to this PR's review discussion; none failed, none needed respawning, none needed to split further. I independently re-verified the two highest-value items myself afterward by re-reading gguf_source.rs:224-275 and the apply_context_estimate/read_gpu_memory_fix branching in model_readiness.rs:855-884, and directly compared gguf_fit.rs:795-799 against :1040 to confirm the epsilon-omission finding, rather than trusting any worker's framing. Files unchanged since bd8015e3 (net.rs, lib.rs, docs/architecture.md) were not re-read; the prior round covered them completely and carried no open finding against them.
Did not run as dedicated passes: history (git log/git blame) — folded into each worker's per-file read instead of a separate blame sweep. prior-changes — this method has no access to review discussion, so "do earlier review comments apply here" could not be checked; note the mandatory claim-check at the start of this process (confirming no review already exists at this exact head) necessarily surfaced prior review metadata and body text as an unavoidable side effect of that administrative check — that text was not used as input to any finding above, and the two previously-open findings re-confirmed here were independently re-derived from source, not quoted from it. Code-comments was folded into each worker's file read. CI: all 26 checks green at commit cf654f9c (gh pr view --json statusCheckRollup), no failing or pending checks. Issue #781 retrieved directly (title/body/state only); not reconciled against any discussion on PR #826 itself.
🤖 Generated with Claude Code
|
@nowycondro, replying to your automated review at cf654f9: The MiB rounding: fixed in 3b6b9f3. Both lines that show the host's memory in MiB now go through one helper, On the decisions:
|
jussielo-amd
left a comment
There was a problem hiding this comment.
Requesting changes, mainly on three points where the PR claims something its code or tests do not back. The ten branches I listed in my earlier review now have tests, and the single predicate behind the verdict, the largest context and the suggestions is a good design. This pass is at 3b6b9f3 and is a read-through: I did not run the tests or mutations, and the MI300X and W7900 lanes had not reported yet.
Blocking
1. A header missing the embedding or feed-forward width still gets a fits / does-not-fit verdict, which contradicts "No verdict where the figure can be too low". gguf_fit.rs:580-585 turns the missing width into 0, and :743-749 then says the compute buffer is "understated and the real figure can be higher". The code goes on to decide Fits or DoesNotFit, and a_header_without_the_widths_the_compute_buffer_needs_says_it_is_understated (:2096) asserts Fits. The note and the verdict disagree, and the rule in the description and in the comment at :785 should govern. Either return MissingMetadata from sizing(), as is done for a missing head or layer count, or state this as a deliberate exception.
2. "No token is sent" is asserted nowhere. It is in --help, the README, the error text at gguf_source.rs:578 and the description. The stand-in hubs record path and Range but no headers, and a_gated_file_is_said_to_be_unreadable_without_offering_a_token pins the string only. This is a promise that something will not happen, so it needs the behaviour asserted with it: record request headers and assert no Authorization or Cookie on the listing, HEAD and range requests, with HF_TOKEN set in the child environment.
3. Branches this PR adds have no test.
gguf.rs:361-378, the sharedfixed_widthtable:signed_values_of_every_width_decode_by_their_own_sign_bitcovers type ids 0, 1, 2, 3, 5 and 11. Scalar bool (7, the type oftokenizer.ggml.add_bos_token), u64 (10) and f64 (12) are never parsed, and most element types are never parsed as array elements. A wrong width on any row desyncs every later key. A table-driven test over ids 0–12, as scalar and as array element, each followed by a kept sentinel key, would cover it.gguf_source.rs:147-155,has_label: no fixture has a letter before the label, such asBF16besideF16, so the before-boundary can be dropped without a failure.gguf_source.rs:779-786and:699-709: a206with noContent-Range, and aHEADwith noContent-Length. Every response in the fake hub carries both.
Non-blocking
- Scenarios.
diagnose-52to-56cover a lot of the new behaviour. Still without one:does_not_fitwith its suggestions and largest-context line, the default-context text, the refused layouts in JSON (unmeasured_layout,split_file,latent_attention,no_attention_layers,missing_metadata) andnot_a_modelin JSON. The description argues only thatdoes_not_fitdepends on the host, and the layouts are reachable with a hand-built local fixture on any lane. Separately, the compared half ofdiagnose-48and-49accepts eitherfitsordoes_not_fit, so a swapped predicate stays green at the e2e level. - Wrong card under
ROCR_VISIBLE_DEVICES.usable_amd_gpu_indices_fromre-indexes ROCR survivors to0..N, whileclassify_accelerator_memory(main.rs:3108-3113) matches them against amd-smi'susage.index. If that index is the physical ordinal,ROCR_VISIBLE_DEVICES=1on a 24 GiB plus 8 GiB host compares the 24 GiB card, and the header can say "ESTIMATED: fits" with the doubt only in the note below it. I read the re-indexing but did not check amd-smi's index. Consider giving the figures with no verdict when a mask is set and the rows differ. shard_parts(gguf_source.rs:312) uses the first-of-, soChain-of-Thought-…-00001-of-00003.ggufreturnsNoneand reports "matches several files".rfindfixes it.- Remediation commands. The
verify:lines (model_readiness.rs:888,:909) arerocm diagnose --model {ref}: they drop--context-sizeand--kv-cache-type, and a local path with a space is not quoted. The description says a GGUF estimate with no GPU still has no plan, but the curated no-GPU answer offersrocm diagnose. - Text that contradicts the code.
gguf_fit.rs:27-32still says mixture-of-experts and state-space models "can miss by more", but the code now withholds their verdict.gguf_fit.rs:999-1001: the doc comment and#[must_use]forevidence_linessit onwhole_mib_offered.model_readiness.rs:707says other answers are "left exactly as it was", butLeaveAsIsadds an evidence line.- The test comment at
gguf_fit.rs:3119says 40% whereweights: 400ofdata: 1000is 60%. docs/manual-testing.md:932says "about 15.8 GiB", but the baselines in the same paragraph give 15854 MiB (15.5 GiB) and 15500 MiB (15.1 GiB).
Questions
- #781 item 3 asks for partial CPU offload as a suggestion, so I take it as intended.
docs/llm-tool-use.mdstill says the assistant "must never suggest CPU fallback" and could relay this output, so please confirm that is the plan. - "Fits" is decided at the point estimate against total VRAM, with no margin, and the header reads an unqualified "ESTIMATED: fits". The free-memory caveat is only in an evidence line. Is that deliberate?
- Does llama.cpp pad the cache to a multiple of 256 under flash attention? If so, a request that is not a multiple of 256 is under-priced by up to 255 tokens, and every calibration load is a multiple of 256. I did not confirm this. Likewise, the smaller-quantization suggestion accepts "fits" with zero margin on an averaged bits-per-weight table; is that intended?
juhovainio
left a comment
There was a problem hiding this comment.
I went through this against the issue's steps 1-3 and the parser, estimator and docs look careful. The verdict, the largest-context figure and every suggestion come from one predicate, and the hostile-input bounds on the parser and the remote reader are good. There is one correctness concern I'd like fixed before merge, plus a few smaller points.
Blocking
- The estimate removes the whole
token_embd.weightfrom the VRAM weights. For tied-embedding models (nooutput.weight, e.g. the Gemma and small Llama/Qwen/Phi families) llama.cpp duplicates that tensor onto the output-layer device, so it does take VRAM. For those models the estimate comes out too low, in the unsafe direction, and can say "fits" when it doesn't. The calibration described in the module docs used two untied models, so it doesn't cover this case. I've argued this from llama.cpp's loader behaviour and haven't run a tied model here. Details in the inline comments.
Non-blocking
- The
evidence_linesdoc comment and#[must_use]ended up abovewhole_mib_offered(inline). - No test exercises a successful remote read end to end. On the real hub
resolve/mainanswers with a redirect to a CDN, andfill()requires a 206 with a matchingContent-Rangeafter following it. Could you confirm with one manual run against huggingface.co that this holds, and that the MI300X run in the manual-testing notes covered a remote ref? - The PR bundles several independent behaviour changes (the device-mask
Dedicated(0.0)fix, the--reportskip, projector handling). They are all described in the body, but each would be easier to review and revert on its own. - MoE and hybrid-SSM models get figures but no verdict. That is honest, but it covers a lot of current models, so it is worth stating plainly in the docs.
CI is green apart from the Strix Halo Ubuntu lanes, where the step was cancelled at about 45 minutes (looks like a runner timeout), and W7900, which was still pending when I looked.
| // The input embedding runs from its offset to the next tensor's, or to the | ||
| // end of the data when it is the last. Clamped so a header that disagrees | ||
| // with the file's size cannot make the weights negative. | ||
| let embedding = header.token_embd.map_or(0, |span| { |
There was a problem hiding this comment.
The whole token_embd.weight span is removed from VRAM weights. For a tied model (no output.weight) llama.cpp duplicates this tensor onto the output-layer device, so it does occupy VRAM; the estimate is then too low in the unsafe direction, and the calibration (two untied models) doesn't cover it. Please keep the embedding in the weights when output.weight is absent, add a test with a tied header, and say in the docs/README which model shapes were calibrated.
There was a problem hiding this comment.
Fixed in 93eef14. The input embedding is now left out of the VRAM weights only when the file has its own output.weight. A tied header keeps it in. the_input_embedding_is_only_left_out_of_the_weights_when_the_header_has_one now has a tied case, and it fails if the rule is undone.
One thing the calibration data shows: the Qwen2.5-1.5B load I calibrated against is itself tied. In llama.cpp's log, its GPU model buffer was the tensor data less one embedding (1564.63 MiB), with no copy. The old estimate came out 0.05% to 2.5% above its measured loads. But the overhead left over for that model was about 180 MiB more than for the Llama-shaped one, and I can't rule out that the output layer's use of the embedding sits there. So I took the side that can't say "fits" wrongly. With the embedding counted, the Qwen-shaped estimates are now 3.6% to 9.5% above its loads, and the calibration test allows 10% for it. The module docs, the README and docs/testing.md now name the two calibrated shapes (Llama-3.1-8B-shaped with its own output layer, Qwen2.5-1.5B-shaped with tied embeddings) and give the new margins.
There was a problem hiding this comment.
A correction to my reply above: the calibration model was not tied. jussielo-amd pointed out, and I've confirmed against the real Q8_0 file, that Qwen2.5-1.5B-Instruct-GGUF carries its own output.weight. My fixture had left it out. So the "180 MiB unexplained overhead" argument and the 3.6% to 9.5% figures came from the fixture, not from llama.cpp.
The rule you asked for stands: with no output.weight, the embedding is counted on the GPU. It is now documented as unmeasured, since both calibration files have their own output layer, and it can only overstate. With the fixture fixed (7b45256), the calibration is back to 0.05% to 5.5% above all sixteen loads.
| param_count = param_count.saturating_add(elements); | ||
| offsets.push(offset); | ||
| reader.check_size()?; | ||
| if name.as_deref() == Some(TOKEN_EMBD_NAME) { |
There was a problem hiding this comment.
Only token_embd.weight is recorded; whether output.weight exists is never captured, which is what sizing needs to decide whether the embedding really stays on the host.
There was a problem hiding this comment.
| }) | ||
| } | ||
|
|
||
| /// The evidence lines for an estimate, in the report's own voice. |
There was a problem hiding this comment.
The evidence_lines doc comment and #[must_use] sit above whole_mib_offered, so evidence_lines is undocumented and whole_mib_offered carries the wrong docs. Move whole_mib_offered above them.
There was a problem hiding this comment.
Fixed in 5a83976. whole_mib_offered now sits above the evidence_lines doc comment and #[must_use], so each has its own docs.
|
Rebased onto
Run locally on the rebased branch: fmt, clippy, xtask, the unit tests, the no-token test and the GGUF e2e scenarios.
|
9617fdc to
631f8c2
Compare
`rocm diagnose --model` answered UNDETERMINED for every model outside the curated catalog, and nothing in it took the context size into account. For a GGUF outside the catalog (a local path, or owner/repo:QUANT on Hugging Face) it now reads the file's header, by HTTP range request for a remote file, without downloading the weights. It estimates the weights, the KV cache at a context size and KV-cache type, the compute buffer and a runtime reserve, and compares them with the dedicated VRAM of the GPU with the most of it. The result is labelled ESTIMATED, never READY or BLOCKED. It gives the largest context that fits, and when the model does not fit, what would: a smaller context, a q8_0/q4_0 KV cache, a smaller quantization, or partial CPU offload. The verdict, the largest context and the suggestions are all decided on the figures as shown, so they cannot contradict each other. New flags: --context-size (default 4096) and --kv-cache-type (f16, q8_0, q4_0). The compute buffer and runtime reserve are calibrated on real loads of two dense models on an MI300X with llama.cpp b9752 and flash attention on. In all sixteen measured loads the estimate was above the real VRAM growth, by 0.05% to 5.5%. The report states this caveat and gives per-model notes where the figure may be off. Split files and latent attention are refused with a reason. The header is untrusted input. Header size, layer count, kept metadata and the remote read time are all bounded. A listed file name that could leave the repository or hide in a terminal is dropped. Under --report the model is not assessed and nothing is fetched; a note on stderr says which flags were ignored. Part of #781: the `rocm serve` context option is left for a follow-up, so #781 stays open. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
The bidirectional and zero-width check in the listed-name filter used
`\u{..}` char literals, which the xtask ASCII output contract rejects.
Compare code points instead, and test both ends of each range.
Clamp the range reader's buffer capacity to the window size where it is
allocated. `wanted` was already bounded by the window, but only through
arithmetic a static analyser does not follow.
Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
…n once No change in behaviour; this answers review points on maintainability. - The calibration figures (which loads, how close the estimate came) are written down only in the gguf_fit module documentation. The README, docs/testing.md and docs/manual-testing.md point there instead of repeating them. - One fixed_width() table of GGUF type ids serves both a scalar value and an array element, so a new type id is added in one place. - attach_context_estimate is now plan, read and apply steps; the plan step is tested on its own. - The binary's diagnose --model glue (the kv-cache-type flag enum, the model query, the --report note, the device-mask caveat) moves out of main.rs into diagnose_model.rs, reached by qualified path. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
A Hugging Face file name that matches only a multimodal projector (mmproj) was filtered out before matching, so it read as a file the repository did not have, and the listing in the error left it out. It now raises a typed refusal: the estimate's reason is not_a_model, the detail names the repository's model files, and the plan asks about the model (by its exact name when there is one) instead of offering to serve the projector. A projector is still never picked as the model, and a bare name a model shares with a projector picks the model. Scenario diagnose-51 covers it against a stand-in hub that serves the listing. max_context's `context > 0` guard had no test that failed without it. The new test puts the card's memory between the exact and shown zero-context totals, where even zero context does not fit. docs/testing.md also records why the device-mask cases are unit-tested only. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
The --model help wrapped a sentence across two doc lines, which clap rendered ragged, and repeated the device-mask detail the README already gives. The rocm-doctor reference opened the model field as curated-only, though the same entry goes on to describe the GGUF estimate. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
A review mutated the estimate's new branches and twelve survived the whole suite. Each now has a test that fails with it applied alone: the percent-encoding of listed names, the deadline on the listing and HEAD requests, `_` as a label boundary, the widest per-layer feed-forward width, the estimate's `note:` label, the 4096 default, a ceiling equal to the request, the trained-context cap on the smaller-cache suggestion, either missing width, the CUDA_VISIBLE_DEVICES mask, and the order of estimate and mask caveat (the environment lookup is now injectable). Three tests could not fail on what they named: the size-bound test now reaches the running header-size check, the skipped-arrays test reads a kept key back after the arrays, and the "no weights fetched" step is dropped from the local-file scenarios, where nothing could download. Scenarios diagnose-52 to 56 cover the refusals and the no-match listing, and diagnose-49's label is now checked against whether memory was compared rather than accepting either. A projector refusal names the file the reference resolved to, and a no-match error lists every GGUF with projectors marked. A local header is read through a buffer that survives the parser's relative seeks, which made a large vocabulary seek the file once per token. with_hub and fetched are test-only. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
A GGUF whose header was sized on a host whose GPU memory could not be read ended with no next step: the estimate cleared the plan, though a curated model on the same host is told to look at amd-smi. It now keeps that plan. No GPU and an APU's unreadable pool still get none, as for a curated model, since no command would change the answer. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
A state-space or hybrid model's recurrent state is not counted, and a mixture-of-experts model's compute buffer was not measured, so the total can be too low; yet the report still headlined "ESTIMATED: fits", with only a note line saying the figure could be higher. A confident fits that is wrong costs a failed load, and a margin with no measurement behind it would be a guess. These layouts now get their figures and this GPU's memory, with no verdict: cannot_estimate, reason unmeasured_layout, and the detail says why. A sliding-window model, whose figure can only be too high, keeps its verdict. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
The no-verdict detail floored the GiB figure without the 1e-6 the evidence line uses, so a figure a hair under a whole MiB could read one MiB lower there than on the line beside it. Both now go through whole_mib_offered, and a test pins that they agree. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
…t was promised A header without the feed-forward or embedding width sized the compute buffer's fixed part as zero, said the figure could be too low, and still gave a fits / does-not-fit verdict. It now gets its figures with no verdict (reason missing_metadata), as a state-space or mixture-of-experts model does. Tests for what the code promised but nothing checked: - `rocm diagnose --model owner/repo:QUANT` against a stand-in hub, with every Hugging Face token variable set, sends no Authorization or Cookie header on the listing, HEAD or range requests. - Every fixed-width GGUF value type is read at its own width, as a scalar and as an array element, kept and skipped. - A 206 with no Content-Range and a HEAD with no Content-Length are refused. - F16 does not match BF16. A shard is now split at the last `-of-`, so a model named `Chain-of-Thought-...-00001-of-00003.gguf` is one split model rather than several matching files. The `rocm diagnose` command a GGUF plan offers keeps --context-size and --kv-cache-type and quotes a path a shell would split. Comments and docs that disagreed with the code are corrected, and docs/llm-tool-use.md says partial GPU offload is not CPU fallback. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
…o-GPU plan A sized GGUF estimate on a host with no visible GPU offered no plan, while a curated model there offers `rocm diagnose` to find out why. Both now share one plan, and the GGUF one asks again with the same flags. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
The weights left the whole input embedding out of VRAM. A model with tied embeddings has no output.weight: llama.cpp uses token_embd.weight for the output layer as well and places that use with the output layer, on the GPU, so leaving it out could understate the figure and say "fits" when it does not. The parser now records whether output.weight exists, and the embedding is left out only when it does. The tied Qwen-shaped calibration model is now estimated 3.6% to 9.5% above its measured loads (was 0.05% to 2.5%); the module documentation, README and docs/testing.md give the new margins and name the two calibrated shapes. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
…nches The `rocm diagnose --model` line a GGUF plan offers was quoted for a POSIX shell on every platform, which breaks in cmd.exe and PowerShell. It now leaves a reference with no shell-special character as it is (so a plain Windows path works in both), and otherwise quotes it with the shared PowerShell or POSIX quoter. A reference starting with `-` is joined to the flag so it is not read as another option. Tests now pin the flags on the amd-smi, no-GPU and projector re-run lines, that a float under a kept key is never read as an integer, and that a tensor after output.weight does not clear it. Comments and docs that still said the input embedding always stays in host memory, gave the old calibration range, or said a no-GPU estimate gets no plan are corrected. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
main added diagnose-48 (the Windows HIP SDK scenario), so this branch's scenarios move up by one: diagnose-49 to diagnose-57. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
… ends The Q2_K row of the bits-per-weight table held 2.63, llama.cpp's published LLaMA-v1-7B size in GiB rather than its bits per weight (about 3.35), so a "Q2_K would fit" suggestion was about 20% light in the tight case where it is offered. A test now holds every K-quant and Q8_0 row to the published sizes. Also: - An unsized local GGUF's plan asks the question again instead of offering `rocm serve <path>`, which takes no path. - The hub listing is read as bytes, so a cut inside a multi-byte character reports the size limit rather than unreadable text. - An exact file name may carry `+`; hub file names do. - File-name segments go through the one percent-encoder in report_delivery. - RelativeSeekBuf's doc says why it tracks its position, and a test pins it. - The assistant skill says partial GPU offload is not CPU fallback, matching docs/llm-tool-use.md, since that skill is the assistant's system prompt. - Tests for a context flag on a reference that is not a GGUF, and a comment on the guard that keeps a recipe-less answer from being estimated. - MissingMetadata's doc covers implausible figures; docs/testing.md points at the module docs for the calibration margins instead of restating them. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
A remote header read (a listing, a HEAD, then range requests, within a two-minute budget) printed nothing until it finished. The shared stderr spinner now covers it, only for a catalog miss named owner/repo:QUANT and only when stderr is a terminal, so --json and piped output are unchanged. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
… file has The Qwen2.5-1.5B Q8_0 GGUF the allowances were calibrated on carries its own output.weight (29 `output.weight` tensor names in the header: 28 attention outputs and the model's own), a second embedding-sized tensor that accounts for its 1 888 581 632 bytes of tensor data. The fixture left it out, so the test treated the model as tied, counted the embedding on the GPU and restated the margins (3.6% to 9.5%) and an "unexplained overhead" around a fixture error. With the tensor in place the estimate is again 0.05% to 5.5% above all sixteen measured loads. The tied-embedding rule stays, and the docs now say it is not measured: both calibration files have their own output layer, and counting the embedding can only overstate. The architecture name, the one header string the report prints, now has its terminal-unsafe characters replaced, and KvCacheType's FromStr is test-only. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
… read An unreadable hub header (offline, gated, misspelt) still offered `rocm serve owner/repo:QUANT`, which would start a multi-GB download to fail the same way. It now asks to check the name and the network and ask again, and says a gated repository can be read by downloading the file and passing its local path. A pasted URL, read as a path, is told which form is read. Also: listed hub names are held to the shared terminal predicates (adding the line separators, invisible operators, soft hyphen and tag characters); the header reads send User-Agent: rocm-cli as the CLI's other downloads do; the re-run command leaves a mid-path `~` (Windows short names) unquoted; the no-token test also checks the estimate it produced; `--context-size 1` is tested as accepted. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
On the Windows lane the scenario's temp path decides whether the command quotes it, so diagnose-55 accepts either form instead of asserting one. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
The APU branch of the memory classification ignored the device-visibility mask, so a masked-out Strix Halo still read as an APU whose pool cannot be read, with no plan, while a discrete card hidden the same way already read as "no GPU is visible". Both now give the answer a command can change. Tests: a hub that redirects every request to another host (as resolve/main does to its CDN) is followed for the listing, the HEAD and the range requests, with Range kept; a state-space, hybrid or mixture-of-experts model with no GPU visible gets "no GPU" with its figures. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
Each new test fails when its guard is removed: the metadata and tensor count bounds (by message), a tensor name past the key-length bound (now refused as a metadata key is), dimensions that overflow, a range answer that runs past the bytes asked for, and a listing that never ends. The sliding- window key no longer takes one of the kept slots, and redirects are bounded at five explicitly. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
…utcome - A compared estimate now says `rocm serve` passes no context size, so a served model gets the engine's default; a test over serve's clap args fails when serve gains one of the options the notes name. - The ESTIMATED label says the allowances were calibrated on one MI300X. - A zero feed-forward or embedding width counts as missing. - A header that could not be read gets a plan that names the possible causes (name, network, busy hub, not a GGUF). - An unsized local GGUF is offered to `rocm serve` again, which does take a local path, quoted for the shell. - A layout with no verdict has its no-plan arm named and an integration test. - diagnose-49/50 derive the outcome they expect from the reported figures instead of accepting either; two vacuous assertions now check something. - Docs: --help example and HF_ENDPOINT; max_context null cases; the module docs of model_fit.rs and diagnose_cmd.rs; gguf* entries in alphabetical order. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
main rewrote the model_readiness.rs entry, and the rebase took its version, dropping the sentence that points at the gguf* modules. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
631f8c2 to
0020cfe
Compare
Part of #781. The part that lets
rocm serveset the context size is left for a follow-up.What changes
rocm diagnose --modelanswered UNDETERMINED for any model outside the curated catalog, and nothing in it took context size into account. For a GGUF model outside the catalog it now gives an estimate instead:Where the model comes from. A local
.ggufpath, orowner/repo:QUANT(orowner/repo:dir/file.gguf) on Hugging Face. For a remote file only the header is read: one repository listing, aHEADrequest where the listing does not give a size, then a few MB by HTTP range request. The weights are never downloaded.HF_ENDPOINTselects a mirror; no token is sent, so gated and private repositories cannot be read.What is estimated. Weights (exact, from the tensor offsets, with the input embedding left in host memory as llama.cpp does, except for a model with tied embeddings, whose output layer uses that tensor on the GPU; that case is not measured and errs high) + KV cache at the requested context size and KV-cache type + compute buffer + a runtime reserve, compared with the dedicated VRAM of the GPU with the most of it that ROCm can see.
New flags.
--context-size <TOKENS>(default 4096, 1 to 16777216) and--kv-cache-type f16|q8_0|q4_0(default f16). For a curated model they change nothing, and the report says so.What is reported. The result is labelled
ESTIMATED(fits / does not fit / cannot estimate), never READY or BLOCKED. It gives the parts of the memory figure, the largest context that fits (never more than the model's trained context), and when the model does not fit, what would make it fit: a smaller context, a q8_0/q4_0 KV cache, a smaller quantization, or partial CPU offload with a layer count. The verdict, the largest context and every suggestion are decided by one predicate on the figures as shown, so they cannot contradict each other.--jsoncarries the same as anestimateobject; the fields are documented inskills/rocm-doctor/reference.md.What it refuses to estimate, with a reason: a split (multi-shard) file, latent attention (MLA), a header missing a figure the formula needs, an unreadable header. When the host is the problem (no GPU visible, GPU memory unreadable, an APU whose memory pool is not readable yet), the figures are still given with nothing to compare them against.
No verdict where the figure can be too low. A state-space or hybrid model's recurrent state is not counted, and a mixture-of-experts model's compute buffer was not measured. These get their figures and this GPU's memory, but no fits / does-not-fit verdict (
cannot_estimate, reasonunmeasured_layout), and the detail says why. A header without the feed-forward or embedding width, which size the compute buffer, is treated the same way (reasonmissing_metadata, with the figures). A sliding-window model, whose figure can only be too high, keeps its verdict.--reportstays offline. Under--reportthe model is not assessed and nothing is fetched, for every model, curated ones included; a note on stderr names the model flags that were ignored. A prepared report never carried the model verdict (it is built from the examination and the matched entry alone), so what--reportprints is unchanged. For a curated model the only differences are the skipped assessment and the new stderr note.A device mask that hides every GPU now gives "no GPU is visible" for a curated model too, the answer
has_usable_amd_gpualready gives. Before, it compared the model with a card of 0 GiB and printed that as a measured figure. The same holds on the one other path where the usable-GPU list comes back empty: a Linux host whereamd-smireported a card but neither the KFD topology nor the DRM cards show an AMD GPU (or a WSL host whose GPU plumbing is not ready). A GPU-requiredrocm servelaunch is already refused there, so "no GPU is visible" now agrees with it. The list is read from sysfs, notrocminfo, so arocminfofailure does not reach it.A note when a device mask is set on a host whose GPUs differ in memory. How
HIP_VISIBLE_DEVICES/ROCR_VISIBLE_DEVICESnumbering maps ontoamd-smi's is not verified on such a host, so the estimate says the card it compared with may be a different one.CUDA_VISIBLE_DEVICESalso triggers the note: the HIP runtime honours it, but the memory comparison (likerocm serve's usable-GPU check) reads only the HIP and ROCr variables, so a figure compared under it is just as open to doubt.Neither has an e2e scenario, only unit tests: the hosted e2e runners have no GPU, so there is no card for a mask to hide, and the note needs a host whose GPUs differ in memory, which no CI lane has.
A multimodal projector named by its file name on Hugging Face (
owner/repo:mmproj-....gguf) is answered as a projector, with the repository's model files named instead, rather than as a file the repository does not have. A projector is never picked as the model: a label never matches one, and a bare name a model shares with a projector picks the model. The estimate gives it a newcannot_estimate_reason,not_a_model, and the plan asks about the model's own file rather than offering to serve the projector. Scenariodiagnose-52covers it against a stand-in hub that serves the listing. A local projector file is not recognised as one and is read like any other GGUF.rocm servedoes not filter projectors, sorocm serve owner/repo:mmproj-....ggufpicks that file as the model; that is unchanged here.A sized estimate on a host whose GPU memory could not be read keeps the
amd-smi metric --jsonplan a curated model gets there. With no GPU visible it gets the plan a curated model gets there (rocm diagnoseto find out why, then ask again). On an APU there is no plan, since no command would change the answer. Where a GGUF plan offersrocm diagnose --model, the command keeps--context-sizeand--kv-cache-typeand quotes the reference for this platform's shell when it needs quoting (PowerShell on Windows, POSIX elsewhere; a plain Windows path is left as it is, socmd.exereads it too). Unit-tested only: no CI lane has a GPU whose memory cannot be read.Reading a local
.ggufkeeps its read buffer across the parser's skips. A 300k-token header read from disk took 342 ms before and 38 ms after here (debug build).Untrusted input. The header comes from a stranger's file, so the reader bounds header size (64 MiB), layer count, kept metadata and the total time of a remote read (two minutes), refuses a listed file name that could leave the repository or carries a control, bidirectional or zero-width character, and reports a dropped connection or an unexpected
206as what it is.How accurate it is
The compute buffer and runtime reserve are calibrated against real loads of two dense full-attention models on one GPU and engine build, with flash attention on: a Llama-3.1-8B-shaped and a Qwen2.5-1.5B-shaped GGUF, each with its own output layer. Across the sixteen loads the estimate was 0.05% to 5.5% above what was used, never below. A file with tied embeddings (no
output.weight) was not measured; its embedding is counted on the GPU, which can only overstate. The loads, and how close the estimate came to them, are recorded once, in thegguf_fit.rsmodule documentation; the README,docs/testing.md,docs/manual-testing.mdand the report's standing caveat point there. The check of the largest context on a nearly full card is indocs/manual-testing.md.Another GPU, engine build or batch size can differ, and so can mixture-of-experts, state-space and sliding-window models. The report says this in a standing caveat and per-model notes. It also says that flash attention is assumed, because without it the compute buffer grows with the context and the figure is too low.
Not in this PR
rocm serveto set the context size, is not here and is left for a follow-up, so diagnose --model: take context size into account, and answer for models outside the catalog #781 stays open. The report saysrocm servedoes not take these options yet, and its suggestions name llama.cpp options rather than arocm servecommand.Testing
cargo test --workspace --all-targets(includingcargo test -p xtask),cargo test -p rocm-core --lib --features e2e-test-hooks,cargo clippy --workspace --all-targets -- -D warnings,cargo fmt --check.apps/rocm/tests/diagnose_model_sends_no_token.rsrunsrocm diagnose --model owner/repo:QUANTagainst a stand-in hub withHF_TOKEN,HUGGING_FACE_HUB_TOKENandROCM_CLI_HUGGINGFACE_TOKENset, and checks that the listing,HEADand range requests all arrive, carry noAuthorizationorCookieheader and sendUser-Agent: rocm-cli, and that the answer is the estimate sized from that header (its source, architecture, layer count and exact KV-cache bytes).Unit tests for the header reader (malformed and hostile input, every fixed-width value type as a scalar and as an array element), the arithmetic (boundaries, the largest context, every suggestion kind, each refused layout, and the sixteen measured loads, which synthetic headers must reproduce), file selection, the listing and
HEADrequests, and the range-request reader against a local stand-in hub.Mutation-checked branches. Each of these has a test that fails when the branch is broken on its own, checked by applying the mutation and running the whole
rocm-corelib suite (or therocmbin suite for the two indiagnose_model.rs):HEADrequest_as a label boundarynote:label on an estimate's warnings.take)CUDA_VISIBLE_DEVICESin the mask listcontext > 0guard in the largest-context search-of-in a shard nameAuthorizationheader on theHEADrequest (the CLI no-token test)Other guards have tests but were not mutation-checked one by one.
e2e scenarios:
diagnose-49anddiagnose-50: an estimate from a GGUF header, in JSON and in text.E2E tests (MI300X, ...)) the first. That lane passed on 3b6b9f3, before the rebase.diagnose-51:--reportmakes no request to a counting stand-in hub, while the same question without--reportdoes.diagnose-52: a projector named by file name.diagnose-53todiagnose-56: the context options refused without--model, a curated model told they do not apply, a missing.ggufpath, and a bareowner/repowith a context size (no request made).diagnose-57: a quantization that matches nothing lists the repository's files, with the projector marked.diagnose-49anddiagnose-50. For a local file it could not fail. The remote case's "header only" property is covered byonly_the_header_is_requested_from_a_file_far_larger_than_it, a unit test against a stand-in hub.A successful remote read (listing,
HEAD, range requests) through the CLI is covered by the no-token integration test above, not by an e2e scenario: the stand-in hubs indiagnose-51,diagnose-52anddiagnose-56answer the listing at most.Checked by hand on a real MI300X, as described in
docs/manual-testing.md; those loads were of local files. A remote read against huggingface.co itself (Qwen/Qwen2.5-0.5B-Instruct-GGUF:Q4_K_M, which redirects to a CDN) was checked by hand on a host without a GPU: the header was read and sized.Locally (Linux, no GPU), one
rocmunit test (storage::tests::prune_removes_the_install_it_named_and_not_a_case_twin_of_it) and seven otherdiagnose-e2e scenarios fail; they fail the same way onmainin this environment. Under a threadedcargo testwithe2e-test-hooks,examine::tests::live_rocm_repo_paths_in_dir_skips_a_real_bak_file_on_diskfailed once and passes on its own; CI runs that suite under nextest, one process per test.