Repository navigation
ROCMAI-556: populate the AIM catalog and warn on stale entries - #794
Conversation
nowycondro
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · a2ae9e5
Review — needs work
Full review of the whole change (bbc3a3f...a2ae9e5, 10 files, 2491/-54).
Implements ROCMAI-556 per its own description (catalog population, staleness warning, HF-token gating, gfx/board/multi-GPU checks). Ticket ROCMAI-556 is named but not retrievable from this review environment (no Jira tool available here) — relationship not independently verified against the ticket itself.
Blocking
-
This PR's own change breaks the required "Sphinx docs build (-W)" CI check —
README.md:12322
The new README.md line... (see [docs/aim-catalog.md](docs/aim-catalog.md))is a markdown link, and the live CI run for this exact commit fails withREADME.md:12322: WARNING: 'myst' cross-reference target not found: 'docs/aim-catalog.md'→build finished with problems, 1 warning (with warnings treated as errors)→ exit 1. This is exactly the pitfall this repo's own AGENTS.md §5 documents: README.md/CONTRIBUTING.md are{include}d into the separate Sphinx docs site, where a relative link to a file outside that site failssphinx-build -W; the convention is a bare backtick path instead (which CONTRIBUTING.md's own new paragraph in this same PR correctly uses:see `docs/aim-catalog.md`). Confidence 100 · mechanical · Fix: change the README.md citation to a bare backtick path, matching the CONTRIBUTING.md citation added in the same commit. -
validate_model_recipe_engine_record's "must not be empty" guard was not extended for 5 of the 6 new optional fields —crates/rocm-core/src/model_catalog.rs(final guard invalidate_model_recipe_engine_record, just beforebail!("... must not be empty"))
Of the six new fields (generated_at_unix_ms,supported_gfx_families,profile_ids,requires_hf_token,supported_boards,min_gpu_counts), onlysupported_gfx_familieswas added to the empty-check condition. A minimalModelRecipeEngineRecord { engine: "aim", requires_hf_token: true, ..Default::default() }(or one carrying onlygenerated_at_unix_msorsupported_boards) passes every earlier check and then incorrectlybail!s "must not be empty" even though it carries real information. Not reachable through today's catalog data (every realaimentry also hasnotes/image_digest), but it is a latent trap for the next contributor adding a minimal entry. Confidence 85 · logic · Fix: extend the condition with&& engine_recipe.generated_at_unix_ms.is_none() && engine_recipe.profile_ids.is_empty() && !engine_recipe.requires_hf_token && engine_recipe.supported_boards.is_empty() && engine_recipe.min_gpu_counts.is_empty(). -
The new AIM gfx-family refusal is not skipped for
--device cpu_only, unlike every other GPU-availability check this PR and its neighbors added —apps/rocm/src/serve_cmd.rs(computation ofaim_host_family/ call toengine_recipe_with_aim_gfx_profile, ahead of wherecpu_onlyis parsed) vs themin_gpu_countsenforcement a few hundred lines later, which is explicitly gatedif let (Some(needed), false) = (aim_min_gpus, cpu_only)at both of its call sites
aim_host_family/engine_recipe_with_aim_gfx_profile(which canbail!("the AIM image for ... has no profile for this GPU ...")) run unconditionally, structurally beforedevice_policy/cpu_onlyare even computed, while every sibling GPU-hardware check in the same function is gated on!cpu_only. A user runningrocm serve <model> --engine aim --device cpu_onlyon a host whose GPU family isn't insupported_gfx_familiesis refused with a GPU-compatibility error despite asking not to use the GPU. No test exercisescpu_onlytogether with the gfx-family check. Confidence 72 · logic · Fix: gate the gfx-family check on!cpu_onlythe same way the multi-GPU check is gated, or add an explicit, clearer refusal for--engine aim --device cpu_onlyif AIM can never serve CPU-only at all. -
The new
rocm model --verbosestaleness-warning line has no test confirming it is actually rendered —apps/rocm/src/main.rs(new loop inrender_model_registry_verbose_text_with_context_and_hostthat callsrocm_core::model_recipe_staleness_warningper engine recipe)
model_recipe_staleness_warningitself is well unit-tested as a pure function inmodel_catalog.rs, but no test inmain.rsfeeds a stalegenerated_at_unix_msfixture through the verbose-rendering path and asserts the warning text appears in the output; the one touched verbose-rendering test exercises only the (fresh) built-in catalog. If the rendering loop were deleted or its condition broken, nothing in the suite would fail — the PR text itself says as much ("the rendering hook inrocm modelis not directly tested"). Confidence 78 · mechanical · Fix: add a test mirroring the file's existing pattern of injecting a craftedModelRecipeEngineRecordwith a stale timestamp and asserting the rendered text contains the warning. -
Four of the five new user-observable
rocm servebehaviors have no Gherkin/e2e coverage, and the PR text only justifies the omission for one of them — no changes undertests/e2e-cucumber/features/
This repo's AGENTS.md §3 requires a Gherkin scenario (or an explicit stated reason) for any change altering user-observable CLI behavior — command output, exit codes, or which runtime/engine is selected — and explicitly says a unit test "proves the function, not the behavior." The PR text justifies skipping a scenario only for the staleness warning (no clock injection). It says nothing about why the HF-token refusal, the gfx-family refusal, the board-mismatch warning, or the multi-GPU refusal — all new refusal/warning paths reachable before any network pull, so none obviously need real hardware or a pulled image to exercise end-to-end — have no scenario. Confidence 80 · architectural · Fix: add e2e scenarios for these four paths (they fail-fast before pulling, so a scenario against the catalog data alone should suffice), or state in the PR text why each is exempt.
Non-blocking
docs/aim-catalog.mdstep 2 tells a maintainer to checkAIM_PROFILE_IDinextra_env, but this PR moves that pin toprofile_ids—docs/aim-catalog.md(refresh step 2)
The Qwen3.5-9B entry's profile pin became aprofile_idsentry in this same PR, and new Radeon entries useprofile_idsexclusively, yet the refresh instructions still point a future maintainer atextra_env. Confidence 72 · mechanical · Fix: update step 2 to sayprofile_ids.
Decisions for the author
aim_board_note's board-name matching is a coarse substring check, not scoped to the specific gfx family actually matched — non-blocking-improvement
name_key.contains(&squash(board))could in principle over/under-match a future board whose name is a superset of an existing one, and the warning fires off the wholesupported_boardslist rather than the family that was matched. Today's real board names (W7900/R9700 vs. RX 7900 XTX) are verified correct by the included test, so this is forward-looking rather than a present defect.- Host GPU/gfx state is probed through at least three separate code paths within one
serve()call (detect_host_gfx_target(),detect_host_gpu_summary(), and the existing GPU-index/VRAM probes) — non-blocking-improvement
host_gpu_summaryis already deliberately skipped in some branches for performance, yetaim_gpu_namere-probes it independently whenever an AIM recipe pins profiles. Consider caching one probe perserve()invocation. validate_model_recipe_engine_recorddoes not requiresupported_gfx_familiesto be non-empty whenengine == "aim"— non-blocking-improvement
Today's invariant ("everyaimentry names its platforms") holds only because a test asserts it over the fixed built-in data, not because the schema enforces it. A futureaimrecipe added withoutsupported_gfx_familieswould silently land under whichever platform happens to listaimfirst ("AMD Radeon"), regardless of its real target hardware.- 37 of the 38
aimentries share the identicalgenerated_at_unix_msmillisecond timestamp — tradeoff
Consistent with a single bulk-authoring pass rather than genuinely independent per-image verification against the upstream registry, which sits a little in tension with the field's stated meaning ("when a maintainer last checked this entry"). Worth the author confirming each image really was individually checked rather than stamped in bulk, since the whole staleness mechanism's value depends on that being true.
Positive signals
- Every new refusal/warning helper in
serve_cmd.rs(ensure_aim_hf_token,ensure_aim_gpu_count,aim_min_gpu_count,aim_board_note,engine_recipe_with_aim_gfx_profile) has a dedicated unit test that asserts on the actual branch outcome rather than just string presence, and each would fail if its guard were weakened. validate_model_recipe_engine_recordadds real cross-field schema checks for the new data (profile_ids/min_gpu_countskeys must be a subset ofsupported_gfx_families,min_gpu_countsmust be ≥ 2, the new fields are rejected on non-aimengines) — this is the right place to catch the exact class of key-mismatch bug these new BTreeMaps invite, and it's exercised by its own tests.docs/aim-catalog.mdis a clear, step-by-step maintenance runbook that a future, different maintainer (the PR itself says the AIM relationship owner, not necessarily a rocm-cli maintainer) could follow without additional context.
Deployment notes
- The PR branch currently has real merge conflicts against the current tip of
main(confirmed withgit merge-tree:CONTRIBUTING.md,README.md, andapps/rocm/src/main.rsall conflict), matching GitHub's ownmergeable_state: dirtyfor this PR. It needs a rebase before it can land, independent of the findings above. - This adds
requires_hf_tokenenforcement that is a hard behavior change for 10 existing-catalog-adjacent gated models:rocm servenow refuses them outright withoutHF_TOKEN, where previously (per the PR) nothing enforced this.
What this covered
Every file in the diff, read completely: crates/rocm-core/src/model_catalog.rs, crates/rocm-core/src/model_catalog.json (structurally validated as a whole plus a representative field-by-field sample of new/changed entries), crates/rocm-core/src/lib.rs, apps/rocm/src/serve_cmd.rs, apps/rocm/src/main.rs, docs/aim-catalog.md, docs/manual-testing.md, docs/testing.md, README.md, CONTRIBUTING.md, against bbc3a3f9fb2fc52254df9a59f4f39d95942f67a9...a2ae9e59d1931be7d1bc74a6033455c0782ba867. Two independent fan-out workers each covered one coupled file group (serve_cmd.rs+main.rs; model_catalog.rs+json+lib.rs); the driver (this context) independently cross-read both groups' diffs in full as well as ran the agent-instruction-adherence, impact-analysis, and CI-status passes directly, including fetching the actual failing-check log and running git merge-tree against current main.
Did not run: "prior changes" pass (whether review comments on earlier changes to these files apply here) — not checked, no practical way to retrieve it in this environment; a git log/history pass was run instead and showed only routine prior extraction/refactor commits, nothing contradicting this change. Clippy/full test suite were not re-run locally; CI's own clippy and test jobs are taken at face value (both green) per the project's own CI-authoritative convention, except for the Sphinx job noted above. Not reconciled against any PR discussion or review thread — none was read.
tomastola
left a comment
There was a problem hiding this comment.
Blocking
1. Hugging Face ids that served on vLLM now route to AIM, and on Instinct some are refused. crates/rocm-core/src/model_catalog.json (the 35 new AIM-only recipes plus the renamed Qwen/Qwen3.5-9B); apps/rocm/src/serve_cmd.rs select_serve_engine
Before this PR, ids such as meta-llama/Llama-3.1-8B-Instruct, openai/gpt-oss-20b, Qwen/Qwen3-32B and meta-llama/Llama-3.3-70B-Instruct had no recipe. On an Instinct host, recipe.is_none_or(..) was therefore true, and select_serve_engine picked vLLM. Each of these ids is now the canonical_model_id of a recipe whose preferred_engines and engine_recipes contain only aim. model_recipe_supports_engine(recipe, "vllm") is false, so selection falls through to aim. The effects:
- Six ids are refused on MI300X. Their AIM records cover only
gfx110X-all/gfx120X-all:Qwen/Qwen3.5-9B,meta-llama/Llama-3.1-8B-Instruct,google/gemma-3n-E4B-it,Qwen/Qwen3-VL-8B-Instruct,Qwen/Qwen3.6-35B-A3B,zai-org/GLM-4.7-Flash.rocm serve <id>on MI300X now fails with "no profile for this GPU". - The Instinct-class ids change engine without notice. They move from a vLLM pull of the Hugging Face weights to a Docker AIM image. The gated ones also start requiring
--engine-env HF_TOKEN. - Other callers shift too.
dash.rs,freeform_planner.rsandmodel_readiness.rsall readpreferred_engines.first(). - The description doesn't mention it. It says the default
rocm modellist is unchanged, but not thatrocm serveresolves these ids to a different engine.
Suggested fixes, either one:
- add
vllmto these recipes'preferred_engines/engine_recipes, so the Instinct preference to vLLM still applies; or - key the AIM-only recipes on non-HF ids (e.g.
aim-<model>, with the image name as an alias), so a plain HF id keeps resolving as it did.
Please also add a select_serve_engine test: a newly catalogued HF id on a gfx94X host still selects vLLM.
2. The Sphinx docs build fails on a README link. README.md:803
[docs/aim-catalog.md](docs/aim-catalog.md) sits inside the README range that docs/rocm-docs/commands.md pulls in with {include}. Sphinx docs build (-W) fails on a2ae9e5 with 'myst' cross-reference target not found: 'docs/aim-catalog.md'. AGENTS.md §5 calls out this case for README.md. Use an absolute GitHub URL, as README already does elsewhere.
3. The staleness warning only appears with --verbose, but the text says plain rocm model shows it. apps/rocm/src/main.rs render_model_registry_verbose_text_with_context_and_host; README.md:801-803; docs/aim-catalog.md ("Freshness")
The loop calling model_recipe_staleness_warning exists only in the verbose renderer. The new AIM entries aren't featured, so they never reach the default list anyway. Two ways to make code and text agree:
- emit the warning wherever these entries are used (
rocm serveresolution is the natural place); or - narrow the README and doc text to
rocm model --verbose.
Non-blocking
- The multi-GPU refusal fires late, and its first message misleads. (
serve_cmd.rs, the twoensure_aim_gpu_countcalls)select_auto_gpu_indexreturns at most one GPU, so the second check refuses every multi-GPU AIM image. It only does so afterensure_self_managed_engine_readyand theResolveModelRPC have run, although the outcome is already known at the first check.- The first check's "only N usable GPUs were found" implies that more GPUs would help. They won't, until multi-GPU selection lands.
- Suggest refusing at the first check with the "does not support multi-GPU serving yet" message, and dropping the second check.
min_gpu_mem_gbcontradictsmin_gpu_countsin the same record.openai/gpt-oss-120bgets 281 GB. That is the ×2 rule applied to an MXFP4 model whose AIM profiles run on one MI300X.zai-org/GLM-4.7gets 426 GB, where one gfx950 GPU is implied.- As a result,
rocm model --verboseshows "doesn't fit" on hardware AIM supports. Suggest capping each estimate atmin_gpu_counts× per-GPU VRAM, or leaving it unset where the dtype isn't fp8/bf16.
- A validator error omits a field it checks. The "only apply to aim" error in
model_catalog.rschecksmin_gpu_counts, but the message doesn't list it. - Some doc text doesn't match the code.
docs/aim-catalog.mdstep 2 says to checkAIM_PROFILE_IDinextra_env. No AIM entry uses that any more, and the validator rejects it alongsideprofile_ids.- "Open a PR" is step 5, but steps 6–10 then add more fields.
docs/manual-testing.mdsays--verboseshowsrequires_hf_token, which it never prints.
- The staleness warning sends end users to a repository path. An installed binary prints "see docs/aim-catalog.md to refresh". That's a maintainer action, and the warning also applies to entries from an external signed index. Suggest telling users to update rocm-cli, and keeping the doc pointer for maintainers.
a2ae9e5 to
5846184
Compare
|
Pushed 5846184: the branch is rebased onto current Blocking
Non-blocking, addressed
Tests
Left as is (author decisions)
Local gates: clippy and fmt clean. The pre-push hook's full run failed only on the known-flaky |
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · 5846184
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 (cc56179...5846184, 12 files, +2637/-57), as it stands at this exact head commit.
Implements the staleness-tracking and hand-maintained-update-process scope of ROCMAI-556 ("Define AIM catalog freshness/update mechanism"), plus substantial additional work the ticket's own text does not call out (populating 35 new AIM catalog entries, HF-token gating, gfx-family/board/multi-GPU enforcement at serve time) — fully disclosed in the PR body, not a misstatement.
This commit is a revision of an earlier version of this PR that was reviewed before (at a2ae9e5, by this same automation and by a human reviewer). Several of those earlier findings are fixed here: the README Sphinx-build link, the validator's "must not be empty" guard now covering all six new fields, the --device cpu_only AIM gfx-check now properly skipped, the staleness-warning render path now directly tested, the HF-id-routes-to-AIM-on-Instinct regression now fixed and tested, and two of the four previously-missing Gherkin scenarios now added (with an explicit, code-verified justification for the other two).
Blocking
- CI
clippyfails on this exact commit, caused by code this PR adds —tests/e2e-cucumber/tests/e2e/serving_steps.rs:1364-1366
Theclippycheck's "Clippy (e2e harness + steps)" step (cargo clippy --locked -p e2e-cucumber --test e2e -- -D warnings) fails with two errors, both inside the newassert_no_service_startedstep added in this PR:let records = std::fs::read_dir(&services_dir) .map(|entries| entries.count()) .unwrap_or(0);
clippy::map_unwrap_or("calledmap(<f>).unwrap_or(<a>)on aResultvalue") andclippy::redundant_closure_for_method_calls(the closure can be the method itself). Confirmed directly from the live job log for head commit58461849(job id 124145420973):error: could not compile e2e-cucumber (test "e2e") due to 2 previous errors, exit code 101. The same step passed on the prior base commit, so this is newly introduced by this PR, not a pre-existing flake. The PR body itself flags the risk ("Clippy--all-targetswas clean on the first commit, not re-run after the catalog commit"), which is exactly what happened here. Confidence 100 · mechanical · Fix:.map_or(0, |entries| entries.count())(or.map_or(0, Iterator::count)to also satisfy the redundant-closure lint), per clippy's own suggested edit.
Non-blocking
docs/aim-catalog.md's refresh-process numbered list skips "5." —docs/aim-catalog.md(the ordered list under "Refreshing an entry")
The list runs 1, 2, 3, 4, 6, 7, 8, 9, 10, 11 — item 5 was evidently dropped during editing and the rest never renumbered. Markdown renders ordered lists sequentially regardless of the literal numbers typed (so the rendered page looks fine), but the raw source is misleading to anyone reading the diff, the file in a non-rendering context, or cross-referencing "step N" later. Confidence 90 · mechanical · Fix: renumber 6-11 to 5-10, or restore whatever step was intended at 5.
Decisions for the author
aim_board_note's board match is a case-insensitive alphanumeric substring check, not scoped to the matched gfx family — tradeoff, already disclosed by the author
The PR's own "Known gaps" section states this plainly ("The board check matches the GPU name by substring and only warns; the detected name is the host's primary GPU, not necessarily the one selected with--gpu"), so this isn't a finding so much as a confirmation that the stated limitation matches the code exactly as described.validate_model_recipe_engine_recorddoes not requiresupported_gfx_familiesto be non-empty whenengine == "aim"— non-blocking-improvement
Every current built-inaimentry does carry a non-empty list (confirmed by the newaim_recipes_are_matched_to_the_platforms_their_profiles_supporttest, which asserts this over today's data), but that's a test over the fixed catalog, not a schema rule. A futureaimentry added withoutsupported_gfx_familieswould silently land under whichever platform listsaimfirst and never trigger the gfx refusal at all.- Host GPU/gfx state is still probed through multiple independent code paths within one
serve()call — non-blocking-improvement
detect_host_gfx_target()(foraim_host_family) anddetect_host_gpu_summary()(for the pre-existinghost_gpu_summary, and again foraim_gpu_namewhenhost_gpu_summarywasn't already computed) are each called fromserve(). Consider caching one probe per invocation. - 37 of the 38
aimentries share the identicalgenerated_at_unix_msmillisecond timestamp — tradeoff
Consistent with a single bulk-authoring pass rather than independently verifying each image against the upstream registry at a distinct time, which sits in some tension with the field's stated meaning ("when a maintainer last checked this entry"). Worth the author confirming each was individually checked.
Positive signals
aim_catalogued_hf_ids_still_select_vllm_on_instinct_and_aim_elsewhere(serve_cmd.rs) directly asserts the exact regression a prior round of review raised — that newly-catalogued HF ids (e.g.meta-llama/Llama-3.1-8B-Instruct,openai/gpt-oss-20b) still selectvllmon an Instinct host — by checkingpreferred_enginesnow lists bothaimandvllm, traced end-to-end againstselect_serve_engine.- The
--device cpu_onlyAIM gfx-check skip is now structural (aim_host_familyis filtered to!cpu_onlybefore the gfx-support check ever runs) rather than bolted on, and theNone-family path it relies on is covered byaim_gfx_check_is_skipped_when_the_gpu_is_unknown_or_the_engine_is_not_aim. - The new Gherkin scenarios' skip-justification comment for the gfx-family/board/multi-GPU paths holds up under independent check: unlike
rocm install sdk --family <arch>,rocm serve's host-gfx detection has no override flag, so those three paths genuinely can't be driven from the mock e2e lane the way the HF-token path can. - Every full catalog cross-check I ran (profile_ids/min_gpu_counts keys ⊆ supported_gfx_families, min_gpu_counts ≥ 2, requires_hf_token ⇔ the HF_TOKEN note, aim-only fields never set on non-aim engines) came back with zero violations across all 38
aimengine recipes.
Deployment notes
requires_hf_tokenenforcement is a hard, user-facing behavior change for the same 10 gated models:rocm servenow refuses them outright withoutHF_TOKEN, where nothing enforced this before.- Catalog population changes default engine routing for previously recipe-less ids on non-Instinct hosts: e.g.
rocm serve Qwen/Qwen3-32Bwith no--engineon a Radeon host now resolves toaim(viapreferred_engines.first()) and is refused immediately ("no profile for this GPU") rather than falling through to whatever the old recipe-less default was. - The heavy
Test (affected crates)/build-and-test/windows-build-and-testjobs are skipped on this PR's own CI run and only execute on the merge-queue push (confirmed by comparing this run against a recent merge-queue run of the same workflow) — this is pre-existing, repo-wide CI design, not specific to this PR, but it meansclippyplus the Sphinx/lint/dash-coverage jobs are the only compiler-verified signals available before merge.
What this covered
Every file in the diff, read completely, against cc56179ced9dd1be42a3000b2592ace9394dbff1...5846184904c98b6165b0b9b8db89cdfe0d798946: CONTRIBUTING.md, README.md, apps/rocm/src/examine_report_text.rs, apps/rocm/src/serve_cmd.rs, crates/rocm-core/src/lib.rs, crates/rocm-core/src/model_catalog.json (every one of its 49 recipes and 38 aim engine recipes checked programmatically for cross-field consistency, plus manual sampling), crates/rocm-core/src/model_catalog.rs, docs/aim-catalog.md, docs/manual-testing.md, docs/testing.md, tests/e2e-cucumber/features/model_serving.feature, tests/e2e-cucumber/tests/e2e/serving_steps.rs. CI was read directly for the exact head commit (get_check_runs plus the actual job logs for clippy, the changes job, and Coverage (rocm-dash crates, ratcheted)), and git merge-tree confirmed no merge conflicts against current main.
This ran as a single-context pass rather than a multi-worker fan-out, per explicit instruction for this run to avoid a background-worker-hang failure mode seen in earlier attempts — the review-code skill's own documented fallback for when fan-out is unavailable. Per that skill's measurements, a single-context pass typically surfaces a smaller fraction of the findings a fan-out would; depth was partially compensated for here by running structural/consistency checks programmatically over the full catalog data and by reading CI logs directly rather than taking job conclusions at face value.
Did not run as a strictly blind review: this task required a mandatory pre-review claim-check (get_reviews) that incidentally surfaced the text of two earlier reviews of a prior commit on this same PR before any code was read. Every substantive claim carried forward from that context was independently re-verified against the code, the live catalog data, or the CI logs at this exact head commit before being included above (each finding above states what was actually traced); nothing was taken on the earlier reviews' word alone, and several of their claims were checked and found already fixed. "Prior changes" (review comments on earlier changes to these specific files, independent of this PR's own prior rounds) was not separately checked — no practical way to retrieve that here. Not reconciled against any PR discussion thread beyond the two prior formal reviews surfaced by the mandatory claim-check.
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · 38bd304
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 5846184 (re-derived from git, not taken as a claim: git diff 5846184...38bd3049 shows only tests/e2e-cucumber/features/model_catalog.feature, tests/e2e-cucumber/tests/e2e/model_catalog_steps.rs, tests/e2e-cucumber/tests/e2e/serving_steps.rs changed, +7/-5 total), reviewed fresh in full, plus the files holding that round's still-open findings (docs/aim-catalog.md, apps/rocm/src/serve_cmd.rs, crates/rocm-core/src/model_catalog.rs, crates/rocm-core/src/model_catalog.json), also reviewed fresh in full — plus live CI at this exact head commit.
Implements ROCMAI-556 per the PR's own description (catalog population, staleness warning, HF-token gating, gfx/board/multi-GPU checks). Ticket ROCMAI-556 is named but not retrievable from this review environment (no Jira tool available here) — relationship not independently verified against the ticket itself.
Blocking
- The new AIM gfx-family check runs ahead of the HF-token check, contradicting the feature's own stated contract, and this is now a live, CI-confirmed regression on real hardware —
apps/rocm/src/serve_cmd.rs:812(engine_recipe_with_aim_gfx_profile, which canbail!("... has no profile for this GPU ...")) runs beforeapps/rocm/src/serve_cmd.rs:866(ensure_aim_hf_token, which bails "needs a Hugging Face token")
tests/e2e-cucumber/features/model_serving.feature's own comment on scenarioserve-28(@id:serve-gated-aim-refused-without-hf-token) states the design intent plainly: "the check runs ahead of GPU detection and Docker, so it needs neither and gates every PR." The actual call order inserve()is the opposite: the gfx-family/profile check (line 812) executes and canbail!before the HF-token check (line 866) is ever reached. This is not theoretical — the live CI run for this exact commit fails on both real-hardware Strix Halo lanes with the identical regression:E2E tests (Strix Halo, Windows, current)andE2E tests (Strix Halo, WSL2, current)both reportFAIL: 'serve-gated-aim-refused-without-hf-token' was expected to pass on this host but FAILED — a regression, with the captured outputError: the AIM image for \meta-llama/Llama-3.1-8B-Instruct` has no profile for this GPU (gfx1151); it supports: gfx110X-all, gfx120X-allinstead of the expected HF-token message. Traced in the catalog data:meta-llama/Llama-3.1-8B-Instruct'saimengine record is bothrequires_hf_token: trueand restricted tosupported_gfx_families: ["gfx110X-all", "gfx120X-all"](Strix Halo isgfx1151), so on that real host the gfx check fires first and the HF-token refusal is never reached. Confidence 100 · logic · Fix: reorder so the HF-token gating (which only needs--engine-env/$HF_TOKEN, not host GPU detection) runs ahead ofaim_host_family/engine_recipe_with_aim_gfx_profile`, matching the comment's stated contract — or, if gfx-incompatible-and-gated models are meant to report the GPU error first, update the feature comment and the PR's own claim to say so and pick an e2e fixture model whose profile is valid on every lane CI actually runs the scenario on.
Non-blocking
docs/aim-catalog.md's refresh-process numbered list still skips "5." —docs/aim-catalog.md(ordered list under "Refreshing an entry": raw source runs 1, 2, 3, 4, 6, 7, 8, 9, 10, 11)
Unfixed since the last reviewed commit; re-confirmed fresh by independent read. Markdown auto-renumbers so the rendered page looks fine, but the raw source misleads a diff reader or anyone cross-referencing "step N". Confidence 90 · mechanical · Fix: renumber 6–11 to 5–10, or restore the missing step.
Decisions for the author
validate_model_recipe_engine_recordstill does not requiresupported_gfx_familiesto be non-empty whenengine == "aim"— non-blocking-improvement
Re-confirmed by reading the validator (crates/rocm-core/src/model_catalog.rs) fresh: every current built-inaimentry happens to carry a non-empty list, but nothing in the schema enforces it. A futureaimentry added without it would silently land under whichever platform listsaimfirst and never trigger the gfx refusal.- 37 of the 38
aimentries still share the identicalgenerated_at_unix_msmillisecond timestamp — tradeoff
Re-verified directly againstmodel_catalog.json(37 records at1793272421113, one at1793203564000). Consistent with a single bulk-authoring pass rather than independently verifying each image at its own time, in tension with the field's stated meaning. Worth the author confirming each image really was checked individually.
Positive signals
- The one-commit delta since the last review (
tests/e2e-cucumber/features/model_catalog.feature,model_catalog_steps.rs,serving_steps.rs) is a clean, correct fix-up:.map_or(0, Iterator::count)is behaviorally identical to the.map(...).unwrap_or(0)it replaced and resolves the previously-flagged clippy failure (confirmed: CI'sclippycheck now passes), and thecatalog_lists_aim_recipeassertion rewrite is a genuine tightening — matching the real printedengines=[aim, vllm]line rather than requiring the stricter (and now wrong)engines=[aim]. - The
min_gpu_mem_gbvs.min_gpu_countsself-contradiction raised in an earlier round (e.g. a high VRAM estimate implying one GPU whilemin_gpu_countsimplied several) is no longer present: checked all 19aimentries carryingmin_gpu_countsand every one now hasmin_gpu_mem_gb: null, removing the contradiction entirely.
Deployment notes
requires_hf_tokenenforcement remains a hard, user-facing behavior change for the same 10 gated models:rocm serverefuses them outright withoutHF_TOKEN.- The PR's
mergeable_stateisblocked(two requested reviewers,tomastola/johnl-amd, have not yet approved at this head commit) and two hardware E2E lanes (E2E tests (W7900, current),E2E tests (MI300X, current)) were still queued/not yet run at review time, so hardware CI coverage for this exact commit is incomplete beyond the Strix Halo failure above.
What this covered
Re-verified via git diff 5846184904c98b6165b0b9b8db89cdfe0d798946...38bd304969903cbc6045c33508d5c09c166ab602 that only 3 files changed since the last-reviewed commit (narrowing established from git, not taken on trust); read those 3 files completely plus, fresh and in full, the files holding that round's open findings: docs/aim-catalog.md, apps/rocm/src/serve_cmd.rs (including tracing the exact call order of every AIM refusal check in serve()), crates/rocm-core/src/model_catalog.rs (validator), and crates/rocm-core/src/model_catalog.json (all 38 aim engine recipes, programmatically cross-checked for min_gpu_mem_gb/min_gpu_counts consistency and generated_at_unix_ms distribution). Live CI was read directly for this exact head commit (get_check_runs plus the actual Strix Halo Windows/WSL2 job logs, which is what surfaced the blocking finding above).
Two of four planned fan-out workers hit a concurrent-subagent limit; the docs group and the 3-file e2e delta were each reviewed by an independent worker (both returned clean except the one carried-forward non-blocking doc-numbering item), while serve_cmd.rs and the model_catalog.rs/.json group were reviewed directly by this driver context rather than an independent worker — disclosed as not independent for those two groups specifically.
This was not a strictly blind review: the mandatory pre-review claim-check (get_reviews) surfaced the text of prior rounds' reviews before any code was read. Every finding above was independently re-derived from the code, live catalog data, or CI logs at this exact commit rather than taken on an earlier round's word (the one blocking finding here is new — not raised in either prior round). "Prior changes" (review comments on earlier changes to these specific files, independent of this PR's own history) was not separately checked. Not reconciled against any PR discussion thread.
🤖 Generated with Claude Code
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · 53c72e2
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 38bd304 (re-derived from git, not taken as a claim: two commits, cdba482 and 53c72e2, touching README.md, apps/rocm/src/main.rs, apps/rocmd/src/common.rs, crates/rocm-core/src/model_catalog.json, docs/aim-catalog.md, apps/rocm/src/serve_cmd.rs), reviewed fresh in full, plus apps/rocm/src/serve_cmd.rs — which held last round's one open blocking finding — read completely and blind, tracing serve()'s exact call order from scratch rather than assuming the prior description.
Implements ROCMAI-556 ("Define AIM catalog freshness/update mechanism") — ticket retrieved directly from Jira. The PR does substantially more than the ticket's own stated scope (full catalog population, HF-token gating, gfx/board/multi-GPU enforcement), which the PR body discloses explicitly; not a misstatement.
Blocking
None.
The carried-forward finding is fixed. The prior round's blocking finding was that engine_recipe_with_aim_gfx_profile (which can bail!("...has no profile for this GPU...")) ran ahead of ensure_aim_hf_token in serve(), so a gated model on an unsupported-GPU host was refused for its GPU and never for its missing token — confirmed as a live regression on real Strix Halo hardware (serve-gated-aim-refused-without-hf-token failing). Commit 53c72e2 reorders this: tracing apps/rocm/src/serve_cmd.rs fresh, ensure_aim_hf_token is now called at line 808, immediately after cpu_only is computed (line 802) and before aim_host_family is computed (line 816) or engine_recipe_with_aim_gfx_profile is called (line 823) — i.e. strictly before the GPU-family check can bail. The early call builds its hint from --engine-env alone (engine_recipe_with_extra_env(&selected_engine, None, &engine_env)), matching the documented contract ("only --engine-env can supply it"), and the old call site after the full hint was built (previously right before the aim_gpu_name/board-note logic) was removed rather than duplicated. The later engine_recipe_with_extra_env(&selected_engine, engine_recipe, &engine_env) call (line 876) is unchanged, so --engine-env AIM_PROFILE_ID=... still overrides the catalog's profile pin exactly as before. This is a clean, correctly-scoped fix of exactly the reported defect.
One caveat on verification: the self-hosted E2E self-hosted workflow run for this exact head (53c72e2, run id 41445175195) is still pending with zero jobs started as of this review — the hardware lanes (including the Strix Halo Windows/WSL2 lanes that caught the original regression) have not yet executed for this commit, so the fix is confirmed here by code tracing, not yet by a live hardware re-run. See Deployment notes.
Non-blocking
docs/aim-catalog.md's refresh-process numbered list still skips "5." —docs/aim-catalog.md(ordered list under "Refreshing an entry": raw source still runs 1, 2, 3, 4, 6, 7, 8, 9, 10, 11)
Unfixed across three consecutive reviewed rounds now; re-confirmed fresh by reading the full file at this head. Markdown auto-renumbers so the rendered page looks fine, but the raw source misleads a diff reader or anyone cross-referencing "step N". Confidence 90 · mechanical · Fix: renumber 6–11 to 5–10, or restore the missing step.
Decisions for the author
validate_model_recipe_engine_recordstill does not requiresupported_gfx_familiesto be non-empty whenengine == "aim"— non-blocking-improvement
Re-confirmed by grepping the validator (crates/rocm-core/src/model_catalog.rs, unchanged since the last round): every current built-inaimentry happens to carry a non-empty list, but nothing in the schema enforces it.- 37 of the 38
aimentries still share the identicalgenerated_at_unix_msmillisecond timestamp — tradeoff
Unaffected by this round's changes (neither new commit touches the field). Carried forward from prior rounds; worth the author confirming each image really was checked individually rather than stamped in bulk.
Positive signals
cdba482's "AMD Enterprise AI" → "AMD Inference Microservices (AIM)" rename is complete and consistent: checked every string in the fullmodel_catalog.jsonprogrammatically (zero"Enterprise AI"substrings remain anywhere in the catalog) and readREADME.md,CONTRIBUTING.md,docs/aim-catalog.md, anddocs/manual-testing.mdin full — all now say "AMD Inference Microservices (AIM)" with no stray leftovers, and the README'sdocs/aim-catalog.mdcitation is still the bare backtick-path form (not a Markdown link), so the earlier Sphinx-build fix from an even earlier round is not reintroduced.- Re-ran the full cross-field catalog validation over all 49 recipes / 38
aimengine recipes at this exact head (profile_ids/min_gpu_countskeys ⊆supported_gfx_families,min_gpu_counts≥ 2,requires_hf_token⇔ anHF_TOKENnote): zero violations, same clean result as the prior round, confirming the rename commit touched only string literals and not catalog structure. 53c72e2's fix is minimal and surgical: it moves one function call and updates one doc comment, with no unrelated changes riding along, which is exactly the shape a regression fix for a single reported defect should have.
Deployment notes
requires_hf_tokenenforcement remains a hard, user-facing behavior change for the same 10 gated models:rocm serverefuses them outright withoutHF_TOKEN.- Hardware CI for this exact commit is incomplete. All non-hardware checks are green (23/23 completed check runs from
ci.yml/codeql.yml, includingclippyandSphinx docs build (-W)), but theE2E self-hostedworkflow run for head53c72e2(which is what previously caught the Strix Halo regression) was still queued/pending with no jobs started at review time. Recommend confirming that run completes and thatserve-gated-aim-refused-without-hf-tokenpasses on the Strix Halo lanes before merging, since that is the only direct hardware confirmation of the fix this round addresses. - The PR's
mergeable_stateisblocked(requested reviewerstomastola/johnl-amdhave not yet approved at this head).
What this covered
Read fresh and in full against 38bd304969903cbc6045c33508d5c09c166ab602...53c72e2ec882f1d36d39096414e5279718fdf835 (2 commits): README.md, apps/rocm/src/main.rs, apps/rocmd/src/common.rs, crates/rocm-core/src/model_catalog.json (all 49 recipes / 38 aim engine recipes checked programmatically), docs/aim-catalog.md, apps/rocm/src/serve_cmd.rs (entire file, 2254 lines, including every test in its #[cfg(test)] mod tests). Also read in full for cross-reference: CONTRIBUTING.md, docs/manual-testing.md. crates/rocm-core/src/model_catalog.rs was not re-read in full (untouched by this round's diff and not holding an open finding beyond the two already-tracked decision items, which were spot-checked by grep). Jira ticket ROCMAI-556 was retrieved directly and read in full.
This was a single-context pass, not a multi-worker fan-out: the Agent tool was available, but the round's scope (2 commits, ~190 changed lines across 6 files, one of which — serve_cmd.rs — needed to be read in full regardless) was judged small enough for one reviewer to read completely and verify directly, per the review method's own sizing guidance; not marked degraded.
Live CI was read directly for this exact head commit (get_check_runs plus list_workflow_runs/get_workflow_run for the self-hosted hardware workflow specifically, which surfaced the still-pending hardware run noted above). "Prior changes" (review comments on earlier changes to these specific files, independent of this PR's own history) was not separately checked — no practical way to retrieve that here. Not reconciled against any PR discussion thread; none was read before reaching the findings above.
🤖 Generated with Claude Code
jussielo-amd
left a comment
There was a problem hiding this comment.
Thanks for the work on this. Populating the AIM catalog is useful, and having the token, GPU-family and multi-GPU checks run before anything is pulled is a good design. This review is based on reading the code only (I did not run tests or builds), so please correct me where I've misread something. Requesting changes for the items below.
Blocking
- Default engine selection changes for about 30 Hugging Face ids. 36 recipes now list
aimfirst, so Strix Halo, Radeon, undetected GPUs and native Windows will pickaimfor ids likeQwen/Qwen3-32B, where before they used the platform default.aim_catalogued_hf_ids_still_select_vllm_on_instinct_and_aim_elsewherelocks this in: on gfx1151 it expectsaim, andserve()then refuses with "no profile for this GPU".serve()also has no native-Windows gate for AIM. Couldaimbe selected only when the host family is in the record'ssupported_gfx_familiesand the OS is Linux or WSL? - The board warning can claim a pin that was never applied.
aim_board_noteinapps/rocm/src/serve_cmd.rsfires from catalog fields and the GPU name alone, without checking that a profile was pinned. It says "the pinned AIM profile targets…" even whenAIM_PROFILE_IDwas already passed, family detection failed, or--device cpu_onlywas used. Triggering it from the actual pin outcome would fix this, and AGENTS §3 asks for a test that asserts the message together with the behavior. - The stale-entry warning isn't covered end to end.
model_recipe_staleness_lines_warn_only_for_stale_engine_recipescalls the helper directly, so deleting the call in the verbose renderer leaves every test green. A scenario looks feasible with a fixture index loaded throughROCM_CLI_MODEL_RECIPE_INDEX_PATHand an AIM recipe with an oldgenerated_at_unix_ms, with no clock injection needed. AGENTS §3 asks for a scenario for user-visible behavior. - The new
serverefusals have no scenario, and the existing ones are weak. Removing theensure_aim_multi_gpu_supportedcall leaves its test green. serve-29 still passes with the change reverted, because any unrelated failure satisfies itsThenstep (!contains("needs a Hugging Face token")). serve-28 can't tell a missing services directory from an empty one, so it doesn't prove nothing was pulled. A planted-KFD fixture exists for the first two, and asserting thatfake-dockerwas not invoked would harden the third. - The verbose
supported_gfx_families=…line is untested, and help and README overstate it. No test enters that branch (the only fixture sets the field toVec::new()).--helpand the README say--verboseshows whether a Hugging Face token is required, butrequires_hf_tokenis never printed. Either print it, or soften the wording. - Some new catalog logic isn't pinned by tests. These mutations all leave tests green:
.anyto.allinmodel_recipe_targets_platform,eq_ignore_ascii_caseto==, and flipping the empty-families arm ofengine_recipe_gfx_support.builtin_aim_recipe_carries_a_verification_timestampchecks one recipe, so a future AIM entry can silently omit its timestamp. A table-driven test, plus a loop over everyaimrecord, would cover both. - A few doc statements don't match the code. Step 11 of
docs/aim-catalog.mdnamescargo test -p rocm-core model_recipe, which doesn't runbuiltin_model_catalog_json_parses_and_validates. The README (around the gated-model passage) says settingHF_TOKENin the environment is enough, but--engine aimnow requires--engine-env HF_TOKEN. The doc comments onmin_gpu_counts("refuses to start with fewer usable GPUs") andgenerated_at_unix_ms("rocm modelwarns") overstate what the code does. - The PR description doesn't match the final diff. It describes conditional multi-GPU refusal, but the code refuses unconditionally whenever the family has an entry. It says
rocm modelwarns, but only--verbosedoes. It leaves out the engine-selection change in item 1, the engine description rename inrocm,rocmdandrocm engines, and scenarios serve-28 and serve-29, and its Testing section is out of date.
Non-blocking
- A doc comment landed in the wrong place in
serving_steps.rs(the "Serve with every GPU hidden" comment now precedesGATED_AIM_MODEL), and serve-29 lacks an@requires-os:linuxtag even though the emptyHIP_VISIBLE_DEVICESmask is Linux-only. - About 140 lines of AIM pre-flight logic went into
serve_cmd.rs. A separate module would match AGENTS §6. - Some checks are silently skipped when GPU detection fails or the image ref is a raw tag or digest. They also read device 0 only, which can describe the wrong GPU on iGPU+dGPU hosts.
meta-llama/Llama-3.1-405B-Instructandzai-org/GLM-4.7listgfx950-dcgpubut havemin_gpu_countsonly forgfx94X-dcgpu. Worth re-checking against the profile tables.- Readiness and diagnose paths and the TUI serve wizard don't use the new constraints yet.
- A few statements elsewhere are now stale:
tests/e2e-cucumber/src/capability.rsand the serve-26 comment on Strix Halo, anddocs/manual-testing.md§9. docs/aim-catalog.mdhas a step numbering gap (4 to 6), and one line guesses at a team name, which is better left neutral in a public doc.- The 14 commits include some noise, and
model-catalog-01is broken between690bff7candfeb74e92. The PR also bundles four things: data, serve gating, the staleness warning and the engine rename.
Decisions for the author
- Strix Halo.
rocm serve aim-qwen3.5-9b --engine aimis now refused on a lane that84f951e2records as hardware-verified, andAIM_PROFILE_IDcan't override the refusal. Is that deliberate? The message could at least name the raw-image workaround. - Multi-GPU. An 8-GPU host is refused too, and "on this hardware" reads as if it were short of GPUs.
- Staleness. It shows in
--verboseonly, never inrocm serve, and all 38 entries share one timestamp, so every binary will warn for all of them from about 2027-04-27. - Ordering. The token check runs before the GPU check, which is cheap and host-independent, but the remedy it prints can lead straight to a "no profile for this GPU" refusal.
User experience
- Fresh catalog:
rocm modellooks almost the same, and--verboseadds one line per AIM recipe. - Stale entry: the warning names no fix.
rocm updateupdates ROCm packages, not the CLI, so naming "rerun the installer" in the warning would help. - Gated model without a token: the remedy command works, but it drops flags like
--portand--gpu, andexportis bash-only, which will trip up Windows users. - Refusals: the unsupported-GPU and multi-GPU messages are accurate but name no next step, such as another engine or the raw image reference. An undetected GPU prints nothing.
Coverage gaps
I couldn't check the AIM registry or profile tables offline, and Windows behavior comes from reading the code only. The history and design passes ran only partly.
|
Pushed 53b0f15 addressing the 13:39 review. Mapping to its items: Blocking
Non-blocking, done: the misplaced doc comment in Left unchanged, deliberately
Two unrelated |
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · 53b0f15
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 (df1548a...53b0f15, 18 files, +3194/-70, 15 commits).
Implements ROCMAI-556 per its own description (AIM catalog population, staleness warning, HF-token gating, gfx-family/board/multi-GPU checks, engine selection, verbose rendering). Ticket ROCMAI-556 is named but not retrievable from this review environment (no Jira/tracker tool available here) — the relationship is not independently verified against the ticket itself.
Blocking
rocm model --verbosecan silently droprequires_hf_token,profile_idsandmin_gpu_countsfor an AIM entry —apps/rocm/src/examine_report_text.rs:807-826(append_model_engine_recipe_settings_lines)
The entire line printing these three fields is gated behindif !engine_recipe.supported_gfx_families.is_empty(). Butvalidate_model_recipe_engine_recordincrates/rocm-core/src/model_catalog.rs(the aim-only-fields guard, ~lines 837-850) does not requiresupported_gfx_familiesto be non-empty wheneverrequires_hf_token/profile_ids/min_gpu_countsare set — a schema-valid AIM entry can haverequires_hf_token: truewith an emptysupported_gfx_families, and--verbosewould then never show that it needs a Hugging Face token at all, contradicting this PR's own stated goal ("Verbose listing:rocm model --verboseshows each AIM recipe'ssupported_gfx_families,requires_hf_token,profile_idsandmin_gpu_counts"). No entry in the current catalog triggers this (all 38 AIM entries happen to have non-emptysupported_gfx_familieswheneverrequires_hf_tokenis set), and no test exercises the combination. Confidence 85 · logic · Fix: renderrequires_hf_token/profile_ids/min_gpu_countsunconditionally (or independently ofsupported_gfx_families), or add a validation rule requiringsupported_gfx_familieswhenever any of the other AIM-only fields are set.
Non-blocking
--engine-env HF_TOKEN=(explicit empty value) is indistinguishable from bare--engine-env HF_TOKEN(forward-from-environment) —apps/rocm/src/serve_cmd.rs:708(engine_recipe_with_extra_env,entry.split_once('=').unwrap_or((entry.as_str(), "")))
Both parse to("HF_TOKEN", ""), so a user who explicitly typesHF_TOKEN=expecting "set to empty" instead silently gets "read from my shell environment," with no error or note. Not exercised by any test. Low real-world impact. Confidence 75 · logic · Fix: distinguish "key present, no=" from "key present, empty value after=" if the empty-value case should mean something different.- Module doc comment in
examine_report_text.rsno longer lists every function it owns —apps/rocm/src/examine_report_text.rs:18-43
The module doc exhaustively enumerates its private helpers (it explicitly says "every other item in this list has no caller outside this module and stays private"), but the new privateappend_model_recipe_staleness_lines(added by this PR, called at line ~694) is missing from that list. Confidence 78 · mechanical · Fix: add it to the enumeration. - PR description's AIM-VRAM-estimate counts don't match the catalog — description says "32 new entries get an estimated
min_gpu_mem_gb... DeepSeek-V4-Flash, MiniMax-M3 and GLM-5.2 are left unset." Directly countingcrates/rocm-core/src/model_catalog.jsonat this head: 5 new AIM entries havemin_gpu_mem_gbunset, not 3 —openai/gpt-oss-120bandopenai/gpt-oss-20bare also unset (plausibly the same "other dtype, e.g. MXFP4" exceptiondocs/aim-catalog.mddocuments, but the PR body doesn't name them), so 30 are set, not 32. Confidence 90 (verified by direct count) · Fix: narrow the description's list/count to match, or confirm these two were meant to be included deliberately.
Decisions for the author
- Fail-open vs. fail-closed when GPU-family detection fails — tradeoff
engine_recipe_gfx_support(confirmed via its own test,model_catalog.rs) returnsUnrestrictedwhenever the host's gfx family can't be detected, even for an AIM entry with a non-emptysupported_gfx_families— so an undetected GPU gets no enforcement at all for the gfx-family check or the multi-GPU check (confirmed this does not also suppress the independent HF-token check). This is deliberate and tested, not a bug. The tradeoff: failing open lets a possibly-compatible-but-undetected host still try to serve, at the cost of a potentially confusing in-container failure instead of a clean pre-flight refusal on a host where detection genuinely fails on an incompatible GPU. Worth confirming this default is still the intended one now that AIM's catalog footprint has grown ~5x. - Staleness is surfaced only in
rocm model --verbose, never enforced or warned on byrocm serve— tradeoff, confirmed deliberate (docs/aim-catalog.md, PR description). A user who runsrocm serve <aim-model>directly, without first checking--verbose, gets no warning that the pinned image may have been withdrawn before the pull is attempted. Reasonable split givenrocm serve's pre-flight checks are about hardware fit rather than registry state, but worth the author confirming this asymmetry is acceptable. - The board-name check matches by substring against the host's primary GPU, not the one
--gpuselects — answered-by-intent. The PR's own "Known gaps" section states this exactly: "The board check matches the GPU name by substring and only warns; the detected name is the host's primary GPU, not necessarily the one selected with--gpu." Confirmed accurate against the code (apps/rocm/src/serve_cmd.rs,aim_gpu_name/aim_board_note). Would otherwise have been a blocking finding; already disclosed, so just confirm it is still an acceptable known limitation rather than a target for this PR.
Positive signals
model_recipe_staleness_warning's clock-skew handling (now_ms.checked_sub(generated_at_unix_ms)?returningNonefor a future timestamp) is a clean, deliberate way to avoid a bogus "stale" warning from clock drift, and it's unit-tested at exactly that boundary (model_catalog.rs).- The
model-catalog-03e2e scenario plants a genuinely stale timestamp on a real, signed recipe-index fixture (via the samextask keygen/signpath production uses) rather than mocking the check away, and asserts the literal warning text — solid, non-vacuous coverage of a cross-cutting feature. fake-docker.rs's newFAKE_DOCKER_LOGlogging turns "Docker was never called" from an inferred claim into a directly-checked one; traced end-to-end (PATH install → log file → assertion) and confirmed it isn't a stub coverage claim.
Deployment notes
- GitHub reports this PR's merge state as
CONFLICTING/DIRTYagainstmainas of this review (despite all CI checks, includingSphinx docs build (-W)and the lychee link checker, passing at the reviewed commit) — it will need a rebase/merge-conflict resolution before it can land, independent of the findings above. - This PR roughly triples the number of catalog entries relying on the (pre-existing, unenforced-by-schema) convention that every
aimengine record carries animage_digest; currently all 38 do, but nothing in validation requires it going forward.
What this covered
Read the entire diff (df1548a...53b0f15) completely: crates/rocm-core/src/model_catalog.rs and model_catalog.json (all 49 recipes / 38 AIM entries, both by hand and via scripted structural checks), crates/rocm-core/src/lib.rs, apps/rocm/src/serve_cmd.rs, apps/rocm/src/examine_report_text.rs, tests/e2e-cucumber/features/model_catalog.feature and model_serving.feature, tests/e2e-cucumber/tests/e2e/model_catalog_steps.rs and serving_steps.rs, tests/e2e-cucumber/src/bin/fake-docker.rs, CONTRIBUTING.md, README.md, docs/aim-catalog.md, docs/manual-testing.md, docs/testing.md — each by an independent blind fan-out worker grouped by file coupling (catalog schema/data; serve-command behavior; verbose-rendering; docs), plus my own direct verification of several candidate findings against the source. apps/rocm/src/main.rs (9512 lines) and tests/e2e-cucumber/tests/e2e/diagnose_steps.rs (3272 lines) were read via their diffs plus targeted greps rather than in full, since each had only a handful of lines changed (cosmetic renames and two pub(crate) visibility bumps respectively) disproportionate to their total size; both renames were confirmed applied consistently repo-wide, and the visibility bump was confirmed used by model_catalog_steps.rs in this same PR, not dead.
Did not run: Jira/ticket retrieval for ROCMAI-556 — no tracker tool available in this environment; "prior changes" pass — no accessible way to retrieve past review comments on these specific files distinct from this PR's own discussion, which this method excludes by design. CI results were read via gh pr view --json statusCheckRollup rather than re-run; all checks passed at the reviewed commit. Not reconciled against any prior review discussion on this PR — this review was conducted blind to it by design.
🤖 Generated with Claude Code
jussielo-amd
left a comment
There was a problem hiding this comment.
Review of the whole change at 53b0f159 (full diff against origin/main), done blind from the code. Implements ROCMAI-556 and substantially more than the ticket's freshness/refresh-doc scope; the PR text discloses that. All checks on this head are green, so the points below are things CI does not see. Outcome: needs work, five blocking items.
Blocking
-
The chat planner and the dash TUI take the recipe's first preferred engine without going through the new host gating.
apps/rocm/src/freeform_planner.rs:370-376usesrecipe.preferred_engines.first()and emits it as an explicit--engine, and an explicit--engine aimis never overridden byrecipe_preferred_engine(serve_cmd.rs:104-148). The 36 new ids listaimfirst, so the assistant can now propose an AIM launch on Instinct hosts (whererocm serveitself picks vLLM) and on Strix Halo and native Windows (where AIM is refused). Before this PR these ids had no recipe, so the planner used the host default. In a scratch worktree a probe plannedserve Qwen/Qwen3-32B --engine aim --device gpu_required --managed. The dash serve wizard (apps/rocm/src/dash.rs:190-198,crates/rocm-dash-tui/src/ui/serve_wizard.rs:253-260) takes the same first engine from the unfiltered built-in recipes, but only as a pre-selection the user can change, so that half is milder. Suggested fix: route both throughselect_serve_engine, or omit--enginewhen the recipe's first engine is AIM. -
Five new validation branches in
validate_model_recipe_engine_recordare exercised by no test (crates/rocm-core/src/model_catalog.rs, roughly lines 805-870): the unknown-gfx-family check, theAIM_PROFILE_ID-in-extra_envcheck, therequires_hf_token/supported_boardsterms of the non-aim check, thesupported_gfx_families.containsterm of themin_gpu_countscheck, and thesupported_gfx_families.is_empty()term of the empty-record check. Each was removed or flipped on its own andcargo test -p rocm-core --libstayed green every time. Suggested fix: one table test over the validator with a case per branch, including "an aim record whose only content is a new field is valid". -
The engine-selection arms and the profile-pin wiring have no test that fails when they are broken. Deleting
|| record.supported_gfx_families.is_empty()inrecipe_preferred_engine, and swapping.is_none_or(for.is_some_and(, each left all 42serve_cmdtests green (run). Every built-in AIM record has non-empty families and an AIM record, so only a signed external index reaches those arms. By reading only: discarding the pinned hint returned byengine_recipe_with_aim_gfx_profile(serve_cmd.rsaround 843-878) also keeps every unit test green, and no scenario seesAIM_PROFILE_IDreach the engine, since fake-docker exits 1 and the plan output does not printextra_env. That pin is the stated remedy for unreliable Radeon auto-selection. Suggested fix: a synthetic-recipe test for both arms, and extract the pre-flight (token, family, pin, min-GPU order) into one function so the pin and the ordering are testable. -
aim_gfx_check_is_skipped_when_the_gpu_is_unknown_or_the_engine_is_not_aimclaims a guarantee the code under it does not give (serve_cmd.rs:1693-1705against858-870). Its closure carriespanic!("host GPU must not be probed for other engines"), but inserve()the family is detected eagerly outside the helper, filtered byaim_engine_record(..)and!cpu_only, and the closure passed in is|| aim_host_family.clone(), which probes nothing. The no-probe property comes from the filter inserve(), which no test covers, so the test would pass ifserve()probed unconditionally. Suggested fix: pass the family as a plainOption<&str>and test the filter through the extracted pre-flight. -
User-observable behaviour changed with no Gherkin scenario, and the PR text does not say why there is none (AGENTS.md section 3). Uncovered: which engine
rocm serve <HF id>picks with no--engine(AIM on a supported Linux Radeon, the platform default elsewhere); the AIM board-mismatch note (only unit tests callaim_board_note, nothing reaches theaim_gpu_nameplumbing or the printing); the new verboseengine_recipe aim supported_gfx_families=… requires_hf_token=… profile_ids=… min_gpu_counts=…line (model-catalog-01 only checksengines=[aim); and the new platform grouping (Strix Halo lost AIM, Instinct gained it). serve-28 to serve-31 all pass--engine aimexplicitly, so none of them reaches engine selection. Suggested fix: scenarios on a planted KFD host with no--engine, as serve-30 does, plus an assertion on the verbose line in model-catalog-01; or state in the PR text which lane covers each and why none is needed.
Non-blocking
rocm model --verbosenow runs one uncacheddocker infoper AIM recipe. The path isexamine_report_text.rsappend_model_engine_support_linestomain.rsengine_ruled_out_by_platformtoengines/aim/src/docker.rsdaemon_reachable(2 s timeout), and it covers hidden recipes too. 36 recipes now listaimfirst, so about 36 serial probes; one took 0.6 s on the reviewer's machine and a hung daemon would give about 72 s. Not measured end to end.chat.rsrenders the same text. Suggested fix: probe Docker once per render, or cache it.- The two largest memory estimates contradict step 8 of
docs/aim-catalog.md. Llama-3.1-405B-Instruct (min_gpu_mem_gb486,min_gpu_countsonlygfx94X-dcgpu: 4) and zai-org/GLM-4.7 (384, onlygfx94X-dcgpu: 2) also listgfx950-dcgpu, which has no count and so means one 288 GB GPU, below both figures. The doc's rule governs because it is checkable against the data; either add thegfx950-dcgpucount or cap the memory. The true profile data could not be verified offline. google/gemma-4-31B-it's aim record has norequires_hf_tokenand noHF_TOKENnote, unlike its gemma-3 siblings, and it is not among the ten gated images the PR lists. If its AIM image does need the token, the claim thatrocm serverefuses a gated AIM image before pulling does not hold for it.gated_aim_models_say_to_pass_hf_tokenonly checks that the flag and the note agree, plus a hard-coded id list. The same record's note still says "see the AIM catalog for the GPU memory and GPU count it needs" (also in at least three other notes), which is stale now thatmin_gpu_*are on the record.- The staleness warning's remedy cannot be shown to clear the condition. It says "update rocm-cli (rerun the installer) for a refreshed catalog", but entries are hand-maintained, so a newer release does not clear it by itself, and for an entry from a configured signed index updating the CLI never does. The e2e step pins only the wording (AGENTS.md section 3, remediation shape). Suggest wording it as what is true, or naming
rocm updateif that is the intended remedy. docs/manual-testing.mdoverstates what is refused. Around line 850, "on any other GPU …rocm serverefuses it" is false for other boards in the same gfx family (RX 7900 XTX, RX 9070), where the code only prints the board note. Around line 872, "refused on every host" is false when the family is undetected or with--device cpu_only, where no check runs. The--engine-envhelp text inapps/rocm/src/main.rsdoes not mention that a gated AIM image is refused withoutHF_TOKEN(AGENTS.md section 5).- Family, pin and multi-GPU decisions use one host-level reading rather than the GPU
--gpuwill select.detect_host_gfx_targetresolves the lowest KFD node and ignores--gpuand the visibility mask, and detection runs up to three times per serve from separate snapshots (serve_cmd.rsaround 858-870, 919-926 and 1065-1072). On an APU+dGPU host,--gpu 1would be judged by the wrong card. Read from the code, not run. - Small leftovers: an orphaned comment at
serve_cmd.rs:951-953("CPU-only serving never pins a GPU, so skip GPU resolution entirely…") is stranded above an unrelated block afterlet cpu_onlymoved up; thefamily.as_deref().unwrap_or("unknown")atserve_cmd.rs:245looks unreachable, sinceengine_recipe_gfx_supportreturnsUnsupportedonly when the family isSome;tests/e2e-cucumber/features/model_catalog.feature:16still says GPU-family fit is "not a catalog property", which this PR changes; and the doc comment onrecipe_preferred_enginesays "first preferred engine that can run on this host" while the code only tests.first()and never considersvllmwhen AIM is unusable.
Decisions for the author
- Strix Halo (gfx1151) lost a previously working AIM path. Before,
rocm serve aim-qwen3.5-9b --engine aimpinnedvllm-w7900-bf16-tp1-latencyon every host, and serve-26 records that profile as hardware-verified on a Strix Halo runner. Now the catalog entry is refused on gfx1151 and only the raw-image form works, which the manual says.tests/e2e-cucumber/src/capability.rsstill calls Strix Halo AIM-verified, and the comment inmodel_serving.featurearound serve-26 says both. Refusing guards against an unsupported profile; keeping the pin keeps a verified path. Please confirm the removal is deliberate and reconcilecapability.rs. - When AIM is unusable, selection falls to
default_engine_for_platform()under the label "platform default" and never considers the recipe's own second choicevllm; an undetected GPU skips every AIM check with no output. The PR text discloses the latter ("nothing is enforced"). Failing clearly is stricter than a quiet fallback; please confirm the quiet behaviour is intended. - The token check runs before the GPU-support check. On a Radeon host a user serving an Instinct-only gated image is told to export a token first and learns the image cannot run on their GPU afterwards. The reverse order would show the unfixable refusal first, but needs GPU detection before the token check, which serve-28 deliberately avoids.
- Readiness and assistant text do not know the new refusals yet.
crates/rocm-core/src/model_readiness.rs(used byrocm diagnose --model) is untouched and checks onlymin_gpu_mem_gb, so it can report READY or suggest an AIM alias thatrocm servethen refuses.chat_host_facts.rsstill says "Serving engines that run here: Lemonade, vLLM and AIM", andpreferred_engine_action_targetinexamine_report_text.rssuggests/plan serve X with aimon every host. - The change is two to three changes: the 38-entry data, the staleness feature, serve-time enforcement (token, family, multi-GPU, board), the engine-selection change, the engine rename and harness work. The enforcement and selection policy is the riskiest part and is separable from the data.
serve_cmd.rsgrows by about 170 lines of AIM pre-flight and 350 of tests, anddocs/architecture.mdstill describes it as "serve(), plus engine-recipe overrides".
Positive signals
- The gated-image refusal runs before GPU detection and Docker, and serve-28/29 pair the message with state ("no service was started", fake-docker never called); serve-29 shows the printed
HF_TOKENremedy actually clears the refusal. - The staleness test hits the exact 180-day boundary plus a future timestamp with an injected clock, and the e2e fixture plants a decades-old timestamp so it does not depend on the run date.
- The AIM catalog JSON is internally consistent: no duplicate digests or aliases, image names match model ids, every
profile_ids/min_gpu_countskey is covered bysupported_gfx_families, and the counts in the PR text (38 aim records, 10 gated, 19 multi-GPU, 36 aim-first) match the data.
Deployment notes
- New
ModelRecipeEngineRecordfields are#[serde(default)]with nodeny_unknown_fields, so older CLIs ignore them against a newer signed index; an index naming a gfx family unknown to an older CLI fails that CLI's validation. - The built-in AIM entries start warning in
rocm model --verboseonce they are 180 days old (timestamps are around 2026-10-29), so a hand refresh of all entries is due around April 2027. - Strix Halo users lose catalog-based AIM serving, and
--engine aimon any catalogued id now refuses unsupported families, multi-GPU images, and gated images without a token. The defaultrocm modellist is unchanged.
What this covered
Every file in the change was read completely at 53b0f159 against origin/main, along with callers and sibling code. Run in scratch worktrees: cargo test -p rocm-core --lib (green at head) and cargo test -p rocm --bin rocm serve_cmd (42 green), plus single-branch mutation checks and a temporary planner probe. Not run: the e2e scenarios (judged from the step definitions and fake-docker), the full workspace gate, coverage floors, scripts/smoke_local.py, and anything on real GPU or Docker hardware. CI on this head: all checks pass. This was not reconciled against the earlier review threads or the author's replies.
Add a per-engine-recipe generated_at_unix_ms, warn in `rocm model` once it is over 180 days old, and document the hand-maintained AIM refresh process. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Adds 38 GPU chat AIM images, each pinned to the newest stable tag's digest and stamped with generated_at_unix_ms. All are hidden from the default list. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Name each AIM recipe by its model (e.g. Qwen/Qwen3-VL-8B-Instruct) as the catalog page lists it, with the image as an alias. Models that already have a recipe (Llama-3.2-3B, gemma-4-31B) gain an aim engine record instead of a duplicate recipe. aim-qwen3.5-9b is now Qwen/Qwen3.5-9B and keeps its old id as an alias. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Ten catalog images are gated per the AIM catalog's model pages. Their engine recipe notes now say to export HF_TOKEN and pass --engine-env HF_TOKEN. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
The platform list predates AIM, and the only AIM image at the time was Radeon-targeted. The catalog now carries Instinct-class images. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Record supported_gfx_families per AIM engine recipe from the image's profile table and pin AIM_PROFILE_ID per gfx family for the Radeon images, whose auto-selection is unreliable. rocm serve refuses a GPU with no profile, rocm model places recipes by the platform covering those families, and Strix Halo no longer lists aim since no profile supports gfx1151. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
…r AIM entries Gated AIM images carry requires_hf_token and rocm serve refuses them before launch without HF_TOKEN. Radeon entries record supported_boards and serve warns when the detected GPU is none of them. 32 AIM entries get an estimated min_gpu_mem_gb. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Record min_gpu_counts per gfx family from each image's profile table. rocm serve refuses an image that needs several GPUs when --gpu pins one device, too few GPUs are usable, or serve would select a single GPU, instead of letting the container fail inside the image. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
… and GPU checks - keep vllm as a second preferred engine on AIM-catalogued HF ids so Instinct hosts still select vLLM - refuse multi-GPU AIM images at the first check and drop the late second check - skip the AIM gfx-family check for --device cpu_only - cap/unset min_gpu_mem_gb estimates that contradicted min_gpu_counts - extend the engine-recipe empty check and the aim-only error to the new fields - point end users at updating rocm-cli in the staleness warning - scope README/docs to rocm model --verbose; use a bare path for the Sphinx include - test the staleness rendering helper and select_serve_engine routing Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Signed-off-by: Juho Vainio <juho.vainio@amd.com>
The Hugging Face token check ran after the GPU-family check, so on a host whose GPU the image has no profile for, a gated model was refused for its GPU and never for its missing token. That contradicted the refusal text and left serve-gated-aim-refused-without-hf-token failing on such hosts (for example gfx1151), while passing on CI runners with no detectable GPU. Check the token first, from a hint built from `--engine-env` alone. The real hint is unchanged, so `--engine-env AIM_PROFILE_ID` still overrides the catalog's profile pin. Signed-off-by: juhovainio <juho.vainio@amd.com>
…rage Select aim by default only on Linux/WSL when the detected GPU family is in the image's supported families; pin the board warning to the profile actually applied; print requires_hf_token in --verbose; add scenarios for the stale entry warning and the unsupported-GPU and multi-GPU refusals (Docker call log asserts nothing was pulled); table-driven catalog tests; fix doc drift. Signed-off-by: juhovainio <juho.vainio@amd.com>
…nd verbose listing - chat planner and dash serve wizard no longer take a host-gated AIM first choice as an explicit engine when the recipe lists another one - extract aim_gpu_preflight (family check, profile pin, multi-GPU) from serve() so the pin, ordering and no-probe filter are unit-tested - `rocm model --verbose` prints the AIM host-fit line whenever any of its fields is set, not only when supported_gfx_families is - tests for each validator branch, the recipe_preferred_engine arms and the verbose line in model-catalog-01 - staleness remedy no longer promises a refresh; docs and --engine-env help state what is and is not refused - fix serving_steps.rs left unclosed by the rebase conflict resolution Signed-off-by: juhovainio <juho.vainio@amd.com>
tomastola
left a comment
There was a problem hiding this comment.
Re-review of 53b0f159 against my review of a2ae9e59.
My earlier blocking items
1. Newly catalogued Hugging Face ids moving to AIM: fixed for rocm serve, still open for the planner.
- Fixed in
rocm serve:- Every AIM-first recipe now lists
vllmsecond. On an Instinct host the vLLM preference inselect_serve_enginestill wins, so the six Radeon-only ids are no longer refused on MI300X. recipe_preferred_enginepicksaimonly on Linux/WSL with a supported family.- The family × OS table test covers both points.
- Every AIM-first recipe now lists
- Still open: the "other callers" part of the item.
apps/rocm/src/freeform_planner.rs:370-376still takesrecipe.preferred_engines.first()and writes it into a literal--engineargument.- The function's own doc comment (
:318-326) warns that this argument outranks everything when the generated command runs. - So
serve Qwen/Qwen3.5-9Bon MI300X now plans--engine aim, androcm serverefuses it ("no profile for this GPU"). Before this PR it planned the host default, vLLM. - jussielo-amd's review on this commit reproduced the planner case (
serve Qwen/Qwen3-32B --engine aim). I agree it should block, and the suggested fix works: route throughselect_serve_engine, or leave out--enginewhen the recipe's first engine is AIM. - The dash wizard pre-selection is the milder half: the user can change it.
- The function's own doc comment (
2. Sphinx {include} link: fixed. README now cites docs/aim-catalog.md as a bare backtick path.
3. Staleness warning only with --verbose: fixed. README and docs/aim-catalog.md now say rocm model --verbose, and model-catalog-03 covers it end to end with a signed fixture that has an old timestamp.
My earlier non-blocking items
- Multi-GPU refusal: fixed. It is now a single early check (
ensure_aim_multi_gpu_supported), placed before engine readiness and the resolve RPC. The message no longer suggests that more GPUs would help. min_gpu_mem_gbvsmin_gpu_counts: mostly fixed. Thegpt-ossvalues are unset, andGLM-4.7is capped at 384. Two records still break the rule the PR adds todocs/aim-catalog.mdstep 8 (cap atmin_gpu_counts× the smallest supported GPU's memory):zai-org/GLM-4.7: 384 GB.meta-llama/Llama-3.1-405B-Instruct: 486 GB.- Both list
gfx950-dcgpuwith no count, which implies one 288 GB GPU, so--verbosestill shows "doesn't fit" there. - The reply says gfx950 counts couldn't be checked offline. Fixing either the count or the value would make the record consistent. Non-blocking.
- Validator message lists
min_gpu_counts: fixed. - Docs: fixed. The
AIM_PROFILE_IDtext, step order and therequires_hf_tokenwording now match the code, and--verboseprintsrequires_hf_token=yes|no. - Staleness warning sent end users to a repo path: fixed. It now tells them to update rocm-cli by rerunning the installer.
New since the last review
- The branch conflicts with
maininapps/rocm/src/serve_cmd.rs,apps/rocmd/src/common.rs,tests/e2e-cucumber/features/model_serving.featureandtests/e2e-cucumber/tests/e2e/serving_steps.rs.mainhas since merged #703, which exempts reuse of a running service from the no-GPU refusal insideserve().- After the rebase, check where the new AIM checks (token, family, multi-GPU) sit relative to that exemption, so reusing a running AIM service isn't newly refused.
- I agree with the rest of jussielo-amd's 17:53 review on this commit and won't repeat it: untested validator branches, the selection arms that mutation testing shows are uncovered, the misleading no-probe test, and missing scenarios for default engine selection and the board note.
CI on 53b0f159
- 4 self-hosted E2E lanes are still pending: MI300X, W7900, and Strix Halo Ubuntu release and nightly.
E2E tests (Strix Halo, Windows, current)fails. Its one unexpected failure isruntime-resolve-canonical-release(runtime-02: "no 'Folder:' line in examine output"). That is a runtime-path scenario this PR doesn't touch, so it looks unrelated, but it is red on this head.- The other three failures are expected xfails, and the Lemonade llama.cpp download 404s appear alongside them.
Outcome: needs work. Rebase onto main, fix the planner, and handle the open items from jussielo-amd's review. The rest of what I raised is fixed.
53b0f15 to
1c72890
Compare
|
Pushed Blocking
Non-blocking, done: Left for the author / not changed
Local: |
…ine-env values, fix stale notes - recipe_preferred_engine skips an AIM first choice the host cannot run and takes the next listed engine (vLLM, except on native Windows) - --engine-env KEY= is refused instead of silently meaning the bare KEY - the Instinct AIM notes no longer point at a catalog page for GPU needs - capability.rs states Strix Halo is verified for the raw image only Signed-off-by: juhovainio <juho.vainio@amd.com>
…tradicts it Llama-3.1-405B-Instruct and GLM-4.7 publish MXFP4 tp1 profiles for gfx950-dcgpu, so their fp8-based memory estimates exceed one 288 GB GPU. Signed-off-by: juhovainio <juho.vainio@amd.com>
jussielo-amd
left a comment
There was a problem hiding this comment.
Thanks for the thorough rounds on this. I reviewed the whole diff at 1c728904 from the code and checked the fixes you described against it. The earlier blockers hold up as fixed: the planner no longer emits --engine aim for AIM-first recipes, each validator branch has a test row, recipe_preferred_engine and the extracted aim_gpu_preflight are covered, and the verbose AIM line prints. Hosted CI is green; the self-hosted lanes were still pending.
Nothing blocking from me. Worth a look, before or soon after merge:
model-catalog-01's host-fit assertion matches any line with those four fields, andQwen/Qwen3-VL-8B-Instructalso satisfies them, so it would stay green if the Qwen3.5-9B record lost its fields. Anchoring it to that entry's block would fix it.- "Docker was never called" has no positive control: a missing log reads the same as an empty one, and
fake-dockerswallows write errors. One scenario that must record a call (model-catalog-02already runsdocker info) would show the channel works. Llama-3.1-405B-InstructandGLM-4.7still listgfx950-dcgpuwith no count (one 288 GB card) but need 486 and 384 GB. Still open from the last round; if it can't be checked offline, a note indocs/aim-catalog.mdwould help.- The token-before-GPU order is claimed in a comment, but serve-33 plants no GPU, so swapping the two checks stays green. A gated image on a planted Instinct host would pin it.
- The PR text still names serve-28..31; they are serve-33..36 now.
docs/manual-testing.md(~L969) says the GPU checks are skipped with--device cpu_only, butcpu_onlyis refused outright.apps/rocmd/src/host_snapshot.rsstill describes the engine as "AMD Enterprise AI pre-built Docker serving containers".
Design notes, your call: on Radeon, ids that need several GPUs or a token now get AIM picked and then refused, with no fall-through to vLLM, and rocm diagnose --model can still read READY for them. Both are on your "left as designed" list, so I'm only confirming they're intentional.
Not run: tests (read-only review). The image digests and GPU profile data could not be checked offline.
🤖 by agenthub on AMD AgentHub
|
Pushed Decisions
Changes
Catalog data checked against the AIM profile files in
Local: clippy Still open for the author: per-render |
… text AIM-aware - the /model verbose listing asks 'is Docker reachable' once instead of once per AIM recipe - the engine named in '/plan serve X with <engine>' is the one rocm serve would pick on this host, not the recipe's first choice - the chat host facts say when no catalogued AIM image covers the GPU family - docs: AIM pre-flight in architecture.md, and the host-level GPU limit of the family, multi-GPU and board checks in aim-catalog.md Signed-off-by: juhovainio <juho.vainio@amd.com>
tomastola
left a comment
There was a problem hiding this comment.
Re-review of df87051a and fbd0a832 against my review of 53b0f159 and the follow-up at 1c728904.
The planner fix from 1c728904 holds. The --engine-env KEY= refusal and the catalog corrections (min_gpu_mem_gb unset for Llama-3.1-405B-Instruct and GLM-4.7, notes pointing at rocm model --verbose, the capability.rs comment) look good. One new blocker.
Blocking
The new Windows clause in recipe_preferred_engine changes engine selection for every vLLM-preferred recipe on native Windows, and the Windows unit job fails because of it. (apps/rocm/src/serve_cmd.rs, recipe_preferred_engine)
.find(...)now skipsvllmon Windows for all recipes, not only when falling back past an unusable AIM.- So on native Windows a recipe that lists
vllmfirst, such asqwen32b, now silently selects Lemonade under "platform default". Before, it selected vLLM with the "no automatic fallback" source. That is the automatic fallback the selection message says doesn't happen, and it applies to recipes this PR doesn't otherwise touch. Windows build and test (debug)andwindows-build-and-testfail onfbd0a832in the existing testserve_engine_selection_uses_shared_recipe_when_no_override_exists(serve_cmd.rs:1654): leftlemonade/ "platform default", rightvllm/ "recipe preferred engine…". The new table test didn't catch it because it runs only on Linux.
Suggested fix: limit the Windows skip to the fallback past an unusable AIM first choice, so a recipe whose first engine is vllm behaves as on main. Or drop the clause and let native Windows hit the existing vLLM refusal as before.
Non-blocking
- The planner and
rocm servenow disagree on non-Instinct Linux hosts.- On a Radeon or Strix Halo host where the AIM image can't run,
rocm serve <catalogued id>now picksvllm(the recipe's next engine). planner_recipe_enginestill returnsNonefor an[aim, vllm]recipe, so the planner writes the host default (--engine lemonade), which the recipe doesn't list.- This matches what these ids got on
main, so it isn't a regression. Returning the recipe's next non-AIM engine fromplanner_recipe_enginewould keep the two in step.
- On a Radeon or Strix Halo host where the AIM image can't run,
- The Strix Halo move from Lemonade to vLLM for catalogued ids is disclosed and consistent with how other vLLM-preferred recipes already select vLLM there. As the author notes, it isn't verified on that hardware.
- The note from my last pass still applies: the AIM
HF_TOKENcheck runs before reuse of an already-running service is considered.
|
Pushed
Still not done: Local: clippy |
tomastola
left a comment
There was a problem hiding this comment.
Re-review of 0ce3cc78 against my review of fbd0a832.
0ce3cc78 was pushed just before that review landed and doesn't touch serve_cmd.rs, so the Windows blocker still holds.
Blocking (unchanged)
recipe_preferred_engine skips vllm on native Windows for every recipe.
- A recipe whose first engine is
vllm, such asqwen32b, silently gets Lemonade as the "platform default" on native Windows. Onmainit gets vLLM with "no automatic fallback". Windows build and test (debug)fails again on0ce3cc78inserve_engine_selection_uses_shared_recipe_when_no_override_exists(serve_cmd.rs:1654): leftlemonade/ "platform default", rightvllm/ "recipe preferred engine…".- Suggested fix, as before: apply the Windows skip only when falling back past an unusable AIM first choice, or drop it.
preferred_engine_action_targetin the new commit goes throughselect_serve_enginefor AIM-first recipes, so it picks up the fix automatically.
The new commit
It looks good:
- the Docker probe now runs once per listing (
once_per_call); /plan serve X with <engine>names the enginerocm servewould pick;- the chat host facts say when no catalogued AIM image covers the GPU family;
docs/architecture.mdanddocs/aim-catalog.mdare updated, including the documented host-level GPU reading gap.
The rocm diagnose --model gap is disclosed as a follow-up, which is fine.
recipe_preferred_engine skipped vLLM on native Windows for every recipe, so a recipe listing vLLM first (e.g. qwen32b) got the platform default instead of vLLM. Apply the skip only when falling past an AIM first choice that was not usable. Signed-off-by: juhovainio <juho.vainio@amd.com>
|
Pushed
|
Addressed in a8debdb
Signed-off-by: juhovainio <juho.vainio@amd.com> # Conflicts: # tests/e2e-cucumber/features/model_serving.feature
The platform gate ran 'docker info' before the HF-token, GPU-profile and no-usable-GPU refusals, so a refused AIM serve still reached Docker. Split out an OS-only gate that runs first, and run the Docker-probing gate after the refusals that need no daemon. Signed-off-by: juhovainio <juho.vainio@amd.com>
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · ba41cc9
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. Implements the staleness-tracking and hand-maintained-refresh-process scope of ROCMAI-556 (status "In Progress"), plus substantially more that the ticket itself does not scope (populating 36 AIM catalog entries and the serve-time gating/GPU-matching/profile-pinning/board-check/multi-GPU/engine-selection behavior around them) — in-diff comments attribute that broader half to a sibling ticket (ROCMAI-555). That bundling is disclosed in the PR body and is not itself a defect.
Blocking
-
CI's "E2E tests" check is red on this exact commit: all four new AIM pre-flight scenarios fail.
tests/e2e-cucumber/features/model_serving.feature(serve-gated-aim-refused-without-hf-token,serve-gated-aim-token-remedy-clears-refusal,serve-aim-refused-on-unsupported-gpu,serve-aim-refused-when-it-needs-several-gpus), stepped intests/e2e-cucumber/tests/e2e/serving_steps.rs:2032(assert_docker_never_called).
Confirmed directly from the live CI run (GitHub Actions run41632470077, job125908503059, "E2E tests" = FAILURE):Step panicked. Captured output: a refused serve reached Docker (.../docker-calls.log): info. Root cause traced inapps/rocm/src/serve_cmd.rs:959—ensure_engine_allowed_on_platform(&selected_engine, GatedEngineAction::Serve)?runs before this PR's newensure_aim_hf_token(:989) andaim_gpu_preflight(:995) checks. That function (apps/rocm/src/main.rs:3155engine_ruled_out_by_platform_with) unconditionally callsrocm_engine_aim::builtin_docker_daemon_reachable()(a realdocker infosubprocess,engines/aim/src/docker.rs:394) wheneverengine == "aim", even thoughplatform_refusal(main.rs:3228) discards the result for AIM (AimDockerUnavailable → None). So every--engine aimserve probes Docker once before the new refusals run, which is exactly what these new "Docker was never called" assertions rule out. The PR description's own Testing section claims these scenarios "all pass locally on Linux" — they do not pass in CI.
Confidence 100 · architectural · Fix: either skip the Docker-reachability probe foraiminside the platform gate (sinceserve_cmd.rsnow owns AIM's own pre-flight), reorder so the new checks run first, or relax the assertion to "no pull/run call" rather than "no call at all" if a localdocker infoprobe is accepted as intentional. -
google/gemma-4-31B-it's new AIM engine recipe omitsrequires_hf_token, unlike every structurally identical sibling.crates/rocm-core/src/model_catalog.json,google/gemma-4-31B-it.engine_recipes[aim].
The recipe's ownwarningsfield says "Gemma terms; gated on Hugging Face (accept the license, then authenticate)", and every other Gemma/MedGemma AIM entry added in this same diff (gemma-3-1b-it,gemma-3-27b-it,medgemma-27b-it,gemma-3n-E4B-it) plus the adjacentmeta-llama/Llama-3.2-3B-Instructentry added in the same hunk all setrequires_hf_token: truewith the matching note.ensure_aim_hf_token(apps/rocm/src/serve_cmd.rs:332) is a no-op when the flag is false, sorocm serve google/gemma-4-31b-it --engine aimskips the intended up-front token refusal and proceeds toward pulling a gated image. The catalog-wide consistency test only checks flag/note agreement with each other, not against the model's own stated gated-ness, so it cannot catch this.
Confidence 90 · logic · Fix: setrequires_hf_token: trueand add the standard HF_TOKEN note to this entry. -
The chat/freeform planner can suggest an engine flag the recipe doesn't support, disagreeing with what
rocm serveitself would pick.apps/rocm/src/freeform_planner.rs:1143(planner_recipe_engine, new in this diff).
For an AIM-first recipe with a listed fallback (e.g. the newzai-org/GLM-4.7-Flash,preferred_engines: ["aim","vllm"], only anaimengine_recipesentry,supported_gfx_families: [gfx110X-all, gfx120X-all]),planner_recipe_enginereturnsNone, andbuild_freeform_plan_with_recipes(:371) falls through tohost_default_engine(:250) →rocm_core::default_engine_for_host→preferred_serve_engine_for_therock_family(crates/rocm-core/src/host_gpu.rs:2056), which only maps-dcgpu/legacy gfx906/908/90a families tovllmand returnsNonefor Radeon families, falling back to the hardcoded platform constant"lemonade"(:2018). The generated plan would berocm serve zai-org/GLM-4.7-Flash --engine lemonade, but this recipe has nolemonadeengine_recipes entry at all — whilerocm serveitself (no--engine,select_serve_engine) correctly resolvesaimon that same Radeon host. The one new test for this (planner_does_not_emit_host_gated_aim_as_an_explicit_engine) only exercises an Instinct-class recipe where the coincidental host default (vllm) happens to match, so it cannot catch the Radeon case.
Confidence 82 · architectural · Fix: makeplanner_recipe_engine/host_default_engineconsult the same GPU-family-aware fallback chainselect_serve_engineuses, instead of a dumb platform constant. -
Stale "AMD Enterprise AI" wording left in a third copy this PR's rename swept past.
apps/rocmd/src/host_snapshot.rs:167(rocmd_engine_inventory).
This PR renames theaimengine description from "AMD Enterprise AI pre-built Docker serving containers" to "AMD Inference Microservices (AIM) pre-built Docker serving containers" in two places inapps/rocm/src/main.rs(builtin_engine_inventory,engine_inventory), but this third, structurally identical copy — reachable viabridge_engine_inventory, a user/assistant-facing listing — is untouched. AGENTS.md §5 names exactly this failure mode: "the same behavior claim... repeats across... grep for the claim's wording across all of them."
Confidence 88 · mechanical · Fix: update this string to match. -
A test added for the new
planner_recipe_enginewiring indash.rsdoesn't exercise it.apps/rocm/src/dash.rs:1855(model_recipe_summaries_carry_id_and_engine).
The assertion comparesfirst_summary.preferred_engineagainstrecords[0].preferred_engines.first()— the catalog's first recipe (Qwen/Qwen2.5-1.5B-Instruct,preferred_engines: ["lemonade"]), whose first engine isn't"aim".planner_recipe_engineis a pass-through for non-AIM-first recipes, so this assertion holds identically whetherdash.rs:195callsplanner_recipe_engineor the oldr.preferred_engines.into_iter().next()it replaced — reverting the production change leaves this test green.
Confidence 85 · mechanical · Fix: add a case using an AIM-first-with-fallback recipe and assertpreferred_enginecomes backNone. -
PR description's
min_gpu_mem_gbclaim doesn't match the diff. PR body, "Min VRAM" section, vs.crates/rocm-core/src/model_catalog.json.
The description states "32 new entries get an estimatedmin_gpu_mem_gb... DeepSeek-V4-Flash, MiniMax-M3 and GLM-5.2 are left unset because their parameter counts are not published." Counting directly against the diff: 36 new recipes total, 29 carrymin_gpu_mem_gb, 7 do not —deepseek-ai/DeepSeek-V4-Flash-0731,meta-llama/Llama-3.1-405B-Instruct,MiniMaxAI/MiniMax-M3,openai/gpt-oss-120b,openai/gpt-oss-20b,zai-org/GLM-4.7,zai-org/GLM-5.2. Four of those seven —Llama-3.1-405B-Instruct,gpt-oss-120b,gpt-oss-20b,GLM-4.7— have publicly well-known parameter counts, contradicting the stated reason for omission.
Confidence 90 · documentation · Fix: either add the estimate to the four entries with known parameter counts, or correct the description's count and justification.
Non-blocking
None beyond the items captured under Decisions for the author.
Decisions for the author
- Board-check is warn-only; multi-GPU is a hard refusal — both key off the same single "primary/lowest-KFD-node" GPU reading, but only the board-check's limitation is disclosed. PR's "Known gaps" section discloses "the detected name is the host's primary GPU, not necessarily the one selected with
--gpu" for the board check specifically. The GPU-family refusal and multi-GPU refusal (apps/rocm/src/serve_cmd.rs'saim_gpu_preflight) depend on the same single-reading signal but can outright refuse service, not just warn. Worth confirming whether that's an intentional asymmetry (a warning when uncertain vs. a hard refusal when restrictive) or whether the disclosure should extend to the stronger checks too — tradeoff. min_gpu_mem_gbestimates use a fixed formula (params × 1–2 bytes × 1.2) never checked against a measurement. Reasonable as a stated estimate, but worth a follow-up to spot-check a few against real usage before users rely on them to avoid OOM — non-blocking-improvement.
Positive signals
- Staleness logic (180-day threshold,
checked_subguarding against clock-skew/future timestamps) is a single shared implementation (model_recipe_staleness_warning) reused by bothmodel_catalog.rsand the--verboserenderer, with boundary tests and a genuine end-to-end scenario using a signed fixture index, not just a string-pinning unit test. - All 38 AIM
engine_recipesentries carrygenerated_at_unix_mswith sane, recent, non-future timestamps, and there are no duplicate canonical ids, aliases, or image digests anywhere across the 49-recipe catalog. ensure_aim_hf_token's bare-name-vs-explicit-value env-forwarding distinction is correctly implemented and covered by a dedicated unit test for both forms.- GPU-family matching (
GfxSupport/engine_recipe_gfx_support) is one shared implementation used identically by the serve-time gate and the--verboseplatform-grouping/listing code — not reimplemented twice to drift apart.
Deployment notes
- CI's "E2E tests" check is currently failing on this exact commit (see first Blocking item); this should block merge on its own.
- Gated AIM images require the operator to
export HF_TOKENand pass--engine-env HF_TOKENbeforerocm serve --engine aim; otherwise the service is refused before any pull. - All new AIM entries require a reachable Docker daemon and depend on externally hosted images that, per the PR's own "Not included"/"Known gaps" sections, were not pulled or served on real hardware as part of this change.
What this covered
Full review of all 22 changed files (crates/rocm-core/src/model_catalog.rs/.json, apps/rocm/src/serve_cmd.rs, apps/rocm/src/examine_report_text.rs, apps/rocm/src/freeform_planner.rs, apps/rocm/src/main.rs, apps/rocm/src/dash.rs, crates/rocm-core/src/lib.rs, the e2e-cucumber feature/step files, and docs), base 9875940ebc7d1dbf9db0706e01d29842e8ec1d27 → head ba41cc98731b4485e6e4b3c2d7887b084fdc3ae5 (3691 insertions / 113 deletions, matching what GitHub reports). Fetched read-only (git show/git diff against FETCH_HEAD) — the PR was never checked out. Fanned out into four independent blind workers grouped by coupling (catalog core; serve command logic; rendering & planner; tests & docs), each reading its group completely, plus an independent verification pass by the consolidating context that re-traced the two highest-confidence cross-file findings directly against source and, for the Docker-probe-ordering defect, against the PR's own live CI run.
Did not run: a local cargo build/cargo test — reliance was on the PR's own CI run plus static tracing, consistent with never materializing the PR head into a working tree. ai-instruction-quality was not separately invoked since no agent-instruction file was itself changed by this diff; AGENTS.md was read and applied as the existing rubric instead. History/prior-changes passes ran per file-group inside each fan-out worker and were not independently repeated here.
Not reconciled against any PR discussion, comments, or other reviewers' threads — none was read; all findings were reached blind from the code, then checked against the PR description and ROCMAI-556 only, per the review method's Step 9.
🤖 Generated with Claude Code
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · ee5eec0
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 (base 9875940e…head ee5eec0c). Implements ROCMAI-556 (staleness tracking for AIM catalog entries + documenting the hand-maintained refresh process) and goes substantially further than that ticket's stated scope: it also populates the catalog with 35 new AMD Inference Microservices (AIM) recipes plus GPU-family matching, HF-token gating, profile pinning, board checks, multi-GPU refusal, and engine-selection/naming changes across the CLI. Multi-GPU auto-selection is explicitly deferred to ROCMAI-751, which is a real, already-accepted follow-up ticket that names this PR as its origin.
Blocking
-
Chat planner and dash Serve Wizard pick an AIM-recipe's engine with zero host/GPU awareness, contradicting their own doc comments and this PR's own correct reference implementation —
apps/rocm/src/freeform_planner.rs:1139-1143(planner_recipe_engine),apps/rocm/src/dash.rs:189-195(model_recipe_summaries)
planner_recipe_engine's doc comment claims "AIM is host-gated (GPU family, platform, Hugging Face token), so the planner leaves the engine to the host default" — but the function takes only&ModelRecipeRecord, no host/GPU argument at all, and unconditionally returnsNone(fall through to a generic platform default) whenever a recipe lists any engine afteraim, regardless of whether this host could actually run AIM.dash.rs'smodel_recipe_summariesreuses the same function with an identical comment, feedingModelRecipeSummary.preferred_engine(which the Serve Wizard uses to pre-select an engine). Contrast with this same PR'sapps/rocm/src/examine_report_text.rs:1327-1340(preferred_engine_action_target), which correctly threads real host-GPU info through and callsserve_cmd::select_serve_engine— proving the host-aware path was known and buildable, just not applied here. For a recipe likeQwen/Qwen3.5-9B(preferred_engines: ["aim","vllm"]), this means the chat assistant and the dash wizard can recommend/pre-select an enginerocm serveitself (no--engine) would not have picked, on a host where AIM is in fact usable, or vice versa.
Confidence 85 · architectural · Fix: thread host info through both call sites and callserve_cmd::select_serve_engine, aspreferred_engine_action_targetalready does; a comment fix alone is insufficient since this value is written into a literal--engineargument. -
PR description's claim that
rocmdnow says "AMD Inference Microservices (AIM)" is false for one surface — a third copy of the engine-description table was missed —apps/rocmd/src/host_snapshot.rs:167(rocmd_engine_inventory, untouched by this diff) vs.apps/rocm/src/main.rs:2727,5149(renamed by this diff)
The description's "Naming" section states the engine is now described as "AMD Inference Microservices (AIM)" in "rocm model --help,rocm,rocmdandrocm engines." Both occurrences inmain.rswere renamed;apps/rocmd/src/host_snapshot.rs's own engine-description table —rocmd's equivalent surface, reachable viabridge_engine_inventory()— still reads "AMD Enterprise AI pre-built Docker serving containers" and is the only surviving "Enterprise AI" occurrence repo-wide. AGENTS.md §5 explicitly calls for grepping a changed claim's wording across all its surfaces before treating a rename as done.
Confidence 88 · mechanical · Fix: rename this occurrence to match, or narrow the description to not claimrocmdis updated. -
Two new user-observable
rocm servebehaviors have no Gherkin/e2e scenario, only unit tests —apps/rocm/src/serve_cmd.rs(aim_board_note, the board-name-mismatch warning;recipe_preferred_engine, GPU-aware default-engine auto-selection),tests/e2e-cucumber/features/model_serving.feature
The only AIM-related@id:scenarios present areserve-aim-inference,serve-aim-lifecycle-parity,serve-gated-aim-refused-without-hf-token,serve-gated-aim-token-remedy-clears-refusal,serve-aim-refused-on-unsupported-gpu, andserve-aim-refused-when-it-needs-several-gpus(verified directly against the diff) — none exercises the board-name-mismatch warning text, and none runs a barerocm serve <model>(no--engine) to prove AIM is actually auto-selected on a matching host. This repo's AGENTS.md §3 is specific: "User-observable behavior needs a scenario, not only a unit test… a unit test asserting the internal helper does NOT discharge this," and names "which runtime/engine it selects" as exactly this category. The PR's own "Known gaps"/"Not run" notes don't mention or justify this particular gap.
Confidence 85 · architectural · Fix: add a scenario planting a GPU name absent fromsupported_boardsto assert the warning text, and a scenario with no--engineflag on a planted matching GPU family to assert which engine is actually selected — or state the gap explicitly in the PR text per AGENTS.md §3's own escape hatch. -
New doc text cites real tracked files as bare backtick paths, violating this repo's own link convention, two sentences below where the convention is stated —
CONTRIBUTING.md:96-97,README.md:831
CONTRIBUTING.md:92(pre-existing, unchanged by this PR) states: "cite a specific repository file as a link relative to the citing file… rather than a bare backtick path, which nothing checks." The new "Updating the AIM catalog" section immediately below it cites`crates/rocm-core/src/model_catalog.json`and`docs/aim-catalog.md`as bare backtick paths;README.md:831does the same for`docs/aim-catalog.md`. Lychee/docs-links CI won't catch this by design (bare paths are never checked), so it's a silent convention break introduced in the same file that documents the rule.
Confidence 90 · mechanical · Fix:[model_catalog.json](crates/rocm-core/src/model_catalog.json)/[docs/aim-catalog.md](docs/aim-catalog.md)inCONTRIBUTING.md;[docs/aim-catalog.md](docs/aim-catalog.md)inREADME.md.
Non-blocking
None
Decisions for the author
generated_at_unix_msisn't gated to AIM-only entries, unlike its sibling new fields — non-blocking-improvement.supported_gfx_families/profile_ids/requires_hf_token/supported_boards/min_gpu_countsare all rejected on non-aimengine records; the new timestamp field is not. Low risk today (no such data exists), worth the same guard for symmetry.- Field-name collision:
ModelRecipeIndexDocument.generated_at_unix_ms(pre-existing, "when the index was generated") vs. the newModelRecipeEngineRecord.generated_at_unix_ms("when a maintainer last verified this entry") — tradeoff/non-blocking-improvement. Same name and type, different meaning, same file; doc comments disambiguate but consider renaming the new one (e.g.verified_at_unix_ms). - The staleness e2e coverage only exercises the positive (stale-entry-warns) case — non-blocking-improvement; the negative cases (non-verbose never warns, fresh entry doesn't warn) are unit-tested instead, which is a defensible layering choice but worth a second opinion alongside the finding above.
- Several new recipes list
preferred_engines: ["aim","vllm"]but define no vLLM-specific settings (e.g. notool-call-parser) that comparable existing vLLM recipes carry — tradeoff. Likely intentional (AIM is the supported path, vLLM a best-effort fallback) but worth explicit confirmation for models likemeta-llama/Llama-3.3-70B-Instruct. recipe_preferred_engineignoresmin_gpu_countswhen deciding "AIM is usable" — tradeoff. A single-GPU host can auto-selectaimfor a model needing 2+ GPUs and immediately hit the multi-GPU refusal rather than falling through tovllm. Matches this PR's stated "no automatic fallback" design elsewhere, but worth confirming given it now affects models this PR newly catalogs.- The host GPU is probed up to three times per
serve()call via independent code paths — non-blocking-improvement; not a correctness bug, worth consolidating. aim_board_note's "is this our own pin" check matches by value only, not by family — non-blocking-improvement; harmless under current catalog data (one record'sprofile_idsdoesn't yet reuse a value across families), worth tightening if that ever changes.- The engine-description table is now duplicated in (at least) three places (
main.rs×2,rocmd/host_snapshot.rs) — non-blocking-improvement; this pre-existing duplication is what let therocmdfinding above happen, and is worth consolidating to one source of truth. - README's "AIM images are among the hidden recipes" phrasing is a slight oversimplification — non-blocking-improvement; at least one AIM-capable entry (
google/gemma-4-31B-it) isfeatured. Not technically false, but could mislead. docs/aim-catalog.md'smin_gpu_mem_gbformula doesn't precisely reproduce the catalog's actual values on spot-check — non-blocking-improvement; the doc already hedges these as "estimates," so likely acceptable, but the formula as literally written is looser than the real figures.
Positive signals
model_recipe_staleness_warning's boundary test (staleness_warning_fires_only_past_the_threshold) is exact at the 180-day boundary and useschecked_subso a future timestamp (clock skew) can't panic or underflow.engine_record_validation_covers_each_aim_field_branchis a genuinely mutation-resistant, one-row-per-branch table test over the new AIM fields; several rows verified by hand.examine_report_text.rs'spreferred_engine_action_targetis the correct reference implementation for host-aware AIM engine selection (memoized viaOnceCell, directly regression-tested) — exactly the pattern the planner/dash finding above should have followed.- The ~1,650-line hand-authored JSON catalog addition is unusually clean: independently re-validated by script (49 recipes, 38 AIM entries) with zero schema violations, zero orphaned cross-references, and consistent profile-name/tensor-parallel-count pairings.
render_model_registry_text_with_context_and_host's platform-grouping filter was correctly upgraded from an ad hoc engine-name check to the shared, gfx-family-awaremodel_recipe_targets_platform— a real correctness fix bundled into this change.fake-docker.rs's new call-logging is env-var-gated with zero behavior change when unset, and reuses an existing fixture-installation pattern rather than inventing one; the new e2e scenarios correctly assert the probe closure was never invoked, not just that its result was ignored.
Deployment notes
- The ten newly HF-token-gated models (Llama 3.1/3.2/3.3, Gemma 3/3n, MedGemma, Command A Reasoning) are all newly catalogued by this same PR, so this is not a regression for any previously-servable model — but the first time anyone serves one of these via AIM,
HF_TOKENmust be supplied through--engine-env. - Catalog entries pin specific image digests; refreshing them is a manual, hand-maintained process (
docs/aim-catalog.md), triggered by watching AMD Enterprise AI Suite version bumps — there is no automated staleness remediation yet, by explicit design (ROCMAI-556 defers the hosted signed-index work pending production signing keys). - Per the PR's own description, none of the new images were pulled or served on real hardware before merge; multi-GPU images are refused outright until ROCMAI-751 (confirmed via Jira as a real, accepted follow-up naming this PR) lands GPU auto-selection.
What this covered
Every one of the 22 changed files, read completely pre- and post-change (base 9875940ebc7d1dbf9db0706e01d29842e8ec1d27…head ee5eec0c18e59050354a7c85344ba196ded24fbd), across five independent blind passes grouped by coupling: (1) catalog data/staleness logic + its e2e tests, (2) serve_cmd.rs gating/GPU-matching/profile-pinning/board-check/multi-GPU logic + its e2e tests, (3) UI/naming consumer files + one e2e step file, (4) docs, and (5) a dedicated AGENTS.md-adherence and lightweight git-history pass across the whole diff — plus my own direct re-verification of every reported finding against the source (exact line numbers, the actual @id: scenario list, the actual doc-comment text, the actual rule text in CONTRIBUTING.md) and a design-review pass. CI for the reviewed commit was read and taken at face value: all required checks green (tests, clippy, Windows build, E2E, commit-signature/sign-off, markdown-links, coverage), one advisory-only check (Skill checks (skillscope)) skipped by the repo's own configuration — no blocking CI finding.
Did not run: cargo test/clippy/xtask coverage (no Rust toolchain available in this environment) — all verdicts are from static reading, hand-traced control flow, and one worker's independent script-based re-validation of the JSON catalog, capping confidence at ~90 rather than 100. Prior-changes pass (review comments on earlier changes to these files, outside this PR) did not run — no accessible corpus beyond this PR's own thread, which was deliberately not read. Ticket ROCMAI-556 and the follow-up it names, ROCMAI-751, were both retrieved directly from Jira and read at this step, after findings were settled.
Not reconciled against this PR's own review thread: this PR has 13 prior review submissions at earlier commits, mostly from one reviewer, which were deliberately never read — every finding above was re-derived blind from the code per this review method, independently of what any earlier human or automated reviewer already said about this PR.
🤖 Generated with Claude Code
Implements ROCMAI-556
What
Catalog: adds the AMD Inference Microservices (AIM) images to
model_catalog.json. Recipes use the real model name from the catalog page as the canonical id (e.g.Qwen/Qwen3-VL-8B-Instruct) with the image as an alias; 35 are new recipes, and models that already had a recipe (meta-llama/Llama-3.2-3B-Instruct,google/gemma-4-31B-it) gain anaimengine record instead.aim-qwen3.5-9bis nowQwen/Qwen3.5-9Band keeps the old id as an alias. Where the catalog has both a Radeon and a generic image for one model (Llama-3.1-8B-Instruct), the Radeon image is used. Each is pinned to the digest of its newest stable tag (read withoras) and stamped withgenerated_at_unix_ms. None are featured, so the defaultrocm modellist is unchanged; they resolve viarocm serveand show inrocm model --verbose.Staleness: adds an optional per-engine-recipe
generated_at_unix_ms(when a maintainer last verified the pinned image).rocm model --verbosewarns when it is over 180 days old (the default list does not, androcm servedoes not check it); entries without it are never flagged, but a unit test requires every built-in AIM entry to carry one. The warning says to rerun the installer for a refreshed catalog.Docs: adds docs/aim-catalog.md (hand-maintained refresh process using
oras, AIM releases as the trigger), linked from README and CONTRIBUTING.Gated models: ten images the AIM catalog marks as requiring a Hugging Face token (Llama 3.1/3.2/3.3, Gemma 3/3n, MedGemma, Command A Reasoning) set
requires_hf_token.rocm serverefuses them before anything is pulled unlessHF_TOKENis passed with--engine-env(bare name with the variable exported, orNAME=value), and the error names that command.GPU matching: each AIM engine recipe records
supported_gfx_families, taken from the profile table on the image's model page (Instinct images: MI250X/MI300X/MI325X/MI350X/MI355X; Radeon images: W7900 and R9700 only).rocm serverefuses a detected GPU outside that list with the supported families; if the GPU can't be detected nothing is enforced.rocm modelplaces a recipe under the platform whose gfx families it supports, which also fixes AIM recipes being labelled Strix Halo.Profile pinning: Radeon images get
profile_ids(gfx family toAIM_PROFILE_ID, the lowest-GPU-count optimized latency profile), applied automatically because their auto-selection is unreliable; two need several GPUs and say so in their notes. Instinct images leave AIM's auto-selection alone.--engine-env AIM_PROFILE_ID=...still wins. The Qwen3.5-9Bextra_envpin becameprofile_ids.Board check: Radeon entries record
supported_boards(W7900, R9700). Since a gfx family covers cards the pinned profile was not built for (for example RX 7900 XTX),rocm serveprints a warning, without refusing, when the detected GPU name matches none of them and the catalog's profile is the one pinned (not when the family was undetected,--device cpu_onlyis used, or--engine-env AIM_PROFILE_IDreplaced it).Multi-GPU: 19 images need more than one GPU on at least one gfx family (smallest tensor-parallel size in the family's profiles, recorded as
min_gpu_counts; for example DeepSeek-R1 needs 8 on MI300X, GLM-4.7-Flash needs 2 on W7900 and 4 on R9700).rocm serverefuses such an image whenever the detected family has an entry, before anything is pulled, however many GPUs the host has, because it gives an engine one GPU and the container would otherwise start with too few devices and fail inside the image. It does not add multi-GPU launching.Engine selection: the 36 AIM-only HF ids list
aimfirst andvllmsecond. Without an explicit--engine,aimis chosen only on Linux/WSL when the detected GPU family is in the record'ssupported_gfx_families; otherwise the platform default applies (as before these ids were catalogued), and Instinct hosts keep vLLM.--engine aimis never overridden and gets the refusals above.Min VRAM: 32 new entries get an estimated
min_gpu_mem_gb(total parameters x 1 byte, or 2 when the image has no fp8 profile, +20%, rounded up). The AIM docs publish GPU counts but no VRAM, so these are estimates, stated as such in docs/aim-catalog.md. DeepSeek-V4-Flash, MiniMax-M3 and GLM-5.2 are left unset because their parameter counts are not published.Platforms: AMD Instinct lists
aim(label... (vLLM / AIM)). Strix Halo no longer listsaim: no AIM profile supports gfx1151.Naming: the engine is described as "AMD Inference Microservices (AIM)" in
rocm model --help,rocm,rocmdandrocm engines.Verbose listing:
rocm model --verboseshows each AIM recipe'ssupported_gfx_families,requires_hf_token,profile_idsandmin_gpu_counts.Not included
aim-epyc-*images (rocm-cli never falls back to CPU).task: chatand I did not check that the serve path handles other tasks.0.12.0-previewbecause itsAIM_PROFILE_IDpin is not verified against the newer2026.11.0tag.Known gaps
min_gpu_mem_gbvalues are estimates, not measurements.--gpu.rocm serveselects one GPU. They are refused up front (see Multi-GPU above); multi-GPU selection is tracked in ROCMAI-751.Testing
cargo test -p rocm-core model_catalogandcargo test -p rocm --bin rocm(engine selection table, board note, gating, verbose rendering, table-driven catalog tests); clippy--workspace --all-targets -D warningsis clean.serve-28(gated image refused without a token),serve-29(the printed token remedy clears it and the serve stops at the GPU pre-flight),serve-30(no profile for the detected GPU),serve-31(image needs several GPUs),model-catalog-03(stale entry warning via a signed fixture index), plusmodel-catalog-01/02. serve-29..31 plant a KFD node and run with@requires-os:linux; serve-28..31 assert Docker (a loggingfake-docker) was never called.