Skip to content

feat(install): make the ROCm compiler toolchain opt-in - #167

Merged
rominf merged 17 commits into
mainfrom
feat/devel-opt-in
Sep 29, 2026
Merged

rominf merged 17 commits into
mainfrom
feat/devel-opt-in

Conversation

@rominf

@rominf rominf commented Aug 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Make the ROCm compiler toolchain opt-in. rocm install sdk now installs rocm[libraries] by default, and --devel adds the toolchain for people who build GPU code.

Because a --devel install is a separate runtime from a default one, this also teaches rocm storage remove-old-installs not to let one evict the other, and gives rocm examine the toolchain field rocm runtimes list already had.

Root cause

Every wheel SDK install pulled in the devel extra — headers, static libraries, hipcc, and the full LLVM toolchain — whether or not the user would ever compile anything. It is roughly half the download:

target [libraries] [libraries,devel] saved
gfx110X-dgpu, Linux 0.56 GiB 2.04 GiB 1.48 GiB (72%)
gfx110X-dgpu, Windows 0.78 GiB 1.97 GiB 1.19 GiB (60%)
gfx94X-dcgpu, Linux 1.70 GiB 3.06 GiB 1.36 GiB (44%)
gfx1151, Linux 0.47 GiB 1.81 GiB 1.34 GiB (74%)

Compressed wheel sizes at ROCm 7.10.0; unpacked is larger. The two extras are disjoint, so dropping devel cannot remove anything needed to run a model.

Nothing in this repository needs it at run time. The SDK probe already tolerates its absence and backfills paths from the runtime packages, and runtime_only_rocm_sdk_probe_validates_without_devel_root already covered that path before this change. No codepath compiles against ROCm headers: the vLLM install is a plain wheel install, Lemonade downloads prebuilt backends, and ComfyUI has no custom-node build path. hipcc is only ever an existence marker for detecting a system ROCm install.

Technical decisions

A positive flag, not a negative one. --devel matches the dominant convention for this command (--reinstall, --replace, --dkms); the codebase has exactly one negative flag, so --no-devel would match the outlier.

devel and the device payload are independent axes. Since this branch was opened, main made every wheel requirement carry a device-<target> extra selecting the GPU payload. The two compose: a default install asks for rocm[libraries,device-gfx1200] and --devel makes it rocm[libraries,devel,device-gfx1200], with torch/torchvision unaffected either way. The e2e assertions pin all four requirements as one line so a change to one axis cannot quietly drop the other.

The toolchain state is read back out of the recorded install, not stored twice. main records wheel_composition.package_specs verbatim and derives the device target from them, on the stated grounds that one stored value cannot drift from a second field. InstalledRuntimeManifest::includes_devel() follows that: it reads the rocm[...] extras from those specs, and falls back to the devel field for tarball installs and manifests written before compositions existed. A manifest older than both defaults to true, since every install predating the flag shipped the toolchain — defaulting to false would silently strip it on the next rocm update.

For an adopted environment, the probe's CMake path tells us whether the toolchain is present, so adoption records what the environment actually has. This also gives cmake_path its first reader.

A --devel install is a separate runtime, and the rest of the CLI had to learn that. wheel_runtime_key fingerprints the requested package_specs, so rocm install sdk and rocm install sdk --devel at one version produce two side-by-side runtimes rather than one growing a toolchain. Three consequences follow, all handled here:

  • Prune. storage::retention_group keyed on (channel, format, family), so the two competed for the same --keep slots and pure recency decided the winner — a later runtime-only install could silently delete a multi-gigabyte toolchain the user had explicitly asked for. They are not newer and older versions of the same thing, so the group gains a toolchain axis. Only a non-active devel runtime was ever exposed (active, default, previous and marked runtimes are held unconditionally), which is exactly the case nothing else protected. a_runtime_only_install_never_evicts_the_toolchain_install_it_sits_beside fails without the axis, and is written with --keep 1 so it also proves the axis does not make --keep inert inside either bucket.
  • Visibility. rocm examine now prints active_runtime_toolchain: included|excluded, matching the toolchain= field rocm runtimes list reports per runtime; both read the same toolchain_state_text helper so the two cannot drift into different words. rocm examine is where someone looks when a build cannot find hipcc, and that is now a state the CLI can produce by design.
  • Documentation. The README and the --devel help text now say the second install is a second runtime, not a bigger first one, and name the commands that show which is which and remove the unwanted one. This surprised a reviewer; it will surprise users.

SdkInstallRequest groups the install arguments; the eighth positional parameter crossed the clippy threshold and the call sites were getting hard to read.

Version resolution asks for the same extras the install will. Choosing versions and composing the install are two separate code paths: on the ROCm 10 (next) layout, uv pip compile runs first and decides which versions exist, and only then are the install specs composed. Threading --devel through the second alone left the first hardcoded to rocm[libraries,devel,device-<target>], so a default rocm install sdk --version 10.x still constrained the version choice by the resolvability of a toolchain the user declined — and could fail outright with a toolchain-resolution error — while the plan it then printed said rocm[libraries,device-...]. Both paths now build their rocm extras from therock_sdk_extras. The requirement lines are composed once and the same value is both sent to uv and carried back on the resolution, so the preview's new version_resolution_specs: line reports what was sent rather than a re-derivation that could drift from it.

Tests

The flag reaches uv and the manifest through install_wheel_runtime, which has one caller, no test callers, and needs uv plus a live index plus a real probe — so the interesting question was not "is there a test" but "does a test fail if the default is reverted". Each of these was confirmed by mutation:

mutation caught by lane
hardcode the extras in therock_pip_package_specs wheel_composition_requests_the_toolchain_only_when_asked, pip_runtime_omits_devel_extra_by_default every PR
hardcode the field in the CLI→request mapping install_sdk_request_forwards_the_parsed_devel_flag, parsed_install_sdk_arguments_reach_the_request_with_devel_intact every PR
hardcode include_devel at the install_wheel_runtime composition call therock-next-02 every PR (required E2E tests)
hardcode the uv pip compile requirements to rocm[libraries,devel,...] version_resolution_requests_the_toolchain_only_when_asked, resolution_and_install_name_the_same_rocm_extras every PR
hardcode include_devel at the install_wheel_runtime resolution call therock-next-02 every PR (required E2E tests)
hardcode include_devel = false inside install_wheel_runtime, making --devel a silent no-op therock-next-10 / @id:therock-next-09-wheel-devel-adds-the-toolchain every PR (required E2E tests)

The scenario rows are the ones that matter: no unit test can reach those lines, because every unit test supplies the flag literally and so pins the helper rather than what install_wheel_runtime passes it. therock-next-02 previews a default install; therock-next-10 (@id:therock-next-09-wheel-devel-adds-the-toolchain) is its --devel counterpart. Neither carries @requires-gpu or @nightly, so both run on the required lane, and each now asserts the resolved extras (version_resolution_specs:) alongside the planned ones (package_specs:) as whole lines. therock-next-08 previews an update to a runtime whose recorded specs contain the toolchain and expects it preserved — the assertion for includes_devel().

Also: legacy_manifest_without_devel_field_is_treated_as_having_it, manifest_reads_devel_back_out_of_the_recorded_composition (including the case where the field contradicts the specs), manifest_without_a_composition_falls_back_to_the_devel_field, and runtime_list_reports_whether_the_toolchain_is_installed.

For the two consequences above: a_runtime_only_install_never_evicts_the_toolchain_install_it_sits_beside (prune) and examine_runtime_state_reports_whether_the_active_runtime_has_the_toolchain (both polarities, the excluded one written through the recorded specs rather than the fallback field). Both were mutation-checked — reverting the retention group to its three-key form, and pinning the examine field to included, each turn their test red.

Scenario coverage was split rather than narrowed. runtime-01 stays engine-agnostic — installs, registers, activates, includes an inference engine, excludes the toolchain — so the default-install acceptance criterion runs on every GPU lane instead of only where vLLM is the effective engine. The the runtime includes an inference engine step is restored. The vLLM serve-and-chat half is now runtime-18 under @requires-engine:vllm. runtime-01 also gained And the inspection reports the active runtime has no compiler toolchain, so the scenario asserts the same fact from the two surfaces that can disagree: what the install recorded, and what the diagnostic tells the user it recorded.

Scenarios that only run on a gated lane. Two, both on the nightly self-hosted GPU lane: runtime-18 / @id:runtime-install-sdk-serves-without-toolchain (@requires-gpu @requires-engine:vllm @nightly), and runtime-01 / @id:runtime-install-sdk-active (@requires-gpu @nightly), which carries the new rocm examine assertion. The included polarity of that field is pinned by a unit test on every PR. Every other scenario this PR adds or changes runs on the required mock E2E tests lane.

The display index of the serve-and-chat scenario has now moved twice as main advanced (runtime-11 → runtime-16 → runtime-18); its @id: has never changed, and that is the identifier worth quoting. The --devel preview scenario has since moved the same way: rebasing onto #224's base put #416's live vLLM discovery scenario at therock-next-09, so this one is now displayed as therock-next-10 while keeping @id:therock-next-09-wheel-devel-adds-the-toolchain.

Verified on real hardware

Both open questions from the original description were settled on MI300X against the live release index (run 32126407107):

  • Transitive resolution. A real uv pip install --dry-run resolved 18 packages with no rocm-sdk-devel; the rocm[libraries,devel] control resolved 19, adding exactly rocm-sdk-devel.
  • Triton runtime compilation. The nightly scenario installs from an empty registry, confirms rocm-sdk-devel is absent from installed distribution metadata, then starts vLLM and completes an inference request from that runtime. 1/1, zero unexpected failures.

Gaps, stated

  • scripts/therock_sdk_install_test.py is invoked by no workflow — re-confirmed by grepping all of .github/workflows/, which has zero references; its only callers are the manual commands documented in docs/testing.md and docs/manual-testing.md. Its --devel passthrough and verify_manifest_devel are the most rigorous assertions in this PR and they protect nothing automatically; they have to be run by hand, and I have not run them in this round (no uv, live index or GPU host available here).
  • Reinstall drift. Install with --devel, later reinstall without it: the files stay on disk while the manifest records the runtime-only specs, and the next rocm update drops the toolchain. That reflects what the most recent install asked for, which is arguably correct, but it is a real edge worth knowing. rocm runtimes list reports toolchain=included|excluded and rocm examine reports active_runtime_toolchain for the active runtime, so the state is visible from both.
  • Adoption derives the toolchain state from probe.cmake_path.is_some(), and the probe falls back to root_path/lib/cmake when that directory merely exists. If rocm-sdk-core ships one, a runtime-only environment would be recorded as a toolchain install. I could not verify that offline, so it is flagged rather than changed.
  • --devel with --format tarball is a no-op, since tarballs always ship the full SDK. Explained in the help text rather than rejected.

Risk: medium. It is a default-behaviour change, but the removed extra cannot affect running a model, the choice is preserved across updates, and both polarities are now pinned on the required lane.

Fixes #164

@rominf
rominf requested a review from a team as a code owner August 3, 2026 12:31
@volen-silo

Copy link
Copy Markdown
Collaborator

Review: feat(install): make the ROCm compiler toolchain opt-in

Change type/scope: behaviour change to rocm install sdk defaults, plus a manifest schema addition and an argument-struct refactor. 6 files, +204/−47, single commit on d17fc0c.

Overall assessment: needs work. The core change is well-reasoned, well-tested at the unit level, and the PR body is unusually honest about its own gaps. Two concrete follow-ons were missed, and the CI evidence is weaker than it appears.

Executed here: cargo fmt --all -- --check and cargo clippy --locked --workspace --all-targets -- -D warnings both clean; cargo test --workspace --all-targets --no-fail-fast passes except two known-local WSL2 process-tree tests unrelated to this PR (#168/#169); cargo xtask manifest --check clean.


Blocking

1. scripts/therock_sdk_install_test.py still asserts the old default and will now fail every run

scripts/therock_sdk_install_test.py:29:

THEROCK_SDK_PACKAGE_SPEC = "rocm[libraries,devel]"

asserted at line 499:

assert_contains(install_output, f"{THEROCK_SDK_PACKAGE_SPEC}==", "sdk install")

The script's own rocm install sdk invocation never passes --devel, and the script has no --devel option at all.

Built the PR binary and ran the dry-run it exercises:

$ ./target/debug/rocm install sdk --family gfx110X-all --dry-run
  package_specs: rocm[libraries]==7.13.0 torch==2.11.0+rocm7.13.0 ...

rocm[libraries,devel]== is not present anywhere in the output, so this assertion fails deterministically.

This is not merely adjacent — this PR edited the documentation of this exact script. docs/testing.md:167-169, changed in this diff, now reads:

a single TheRock-index pip install plan for pinned rocm[libraries], torch, torchvision, and torchaudio versions (--devel adds the compiler toolchain, giving rocm[libraries,devel])

The doc says one thing; the script it documents asserts the other. The script is not CI-gated, which is why checks are green — but it is the repo's documented live acceptance test for SDK installs, invoked from docs/testing.md:181,187,194,201 and docs/manual-testing.md:134.

Suggested fix: split the constant (THEROCK_SDK_PACKAGE_SPEC_BASE / ..._DEVEL), assert the base spec for the default invocation, and add a --devel flag to the script with a matching assertion so the opt-in path stays covered.

2. The repo's documented vLLM from-source build needs the toolchain, and its docs were not updated

docs/vllm.md:16-19 (unchanged by this PR):

For rocm-cli managed TheRock runtimes, prefer building vLLM from source against the existing TheRock PyTorch stack.

and docs/vllm.md:61-75:

current vLLM source required the GPTQ compatibility guard in csrc/libtorch_stable/quantization/gptq/compat.cuh to include HIP 7.13 … Without that patch, q_gemm.hip fails to compile because TheRock 7.13 headers do not expose the half/half2 atomicAdd overloads …

docs/testing.md:897-900 repeats it. Compiling .hip sources against TheRock headers is precisely what the devel extra provides. After this change, a user following the repo's own recommended vLLM setup on a default install has no headers and no compiler, and nothing in the output or docs tells them to pass --devel.

This qualifies rather than refutes the PR body's "No codepath compiles against ROCm headers" — true of rocm-cli's own code, but the repo documents a user-facing build workflow that does. docs/vllm.md and the vLLM section of docs/testing.md should say --devel is required for the from-source path. (git diff d17fc0c..HEAD -- docs/vllm.md docs/testing.md touches only line 167-169 of testing.md.)

3. Stale user-facing error message still names rocm[libraries,devel] on the default path

apps/rocm/src/therock.rs:1255-1260, in resolve_pip_runtime_from_index:

format!(
    "no mutually compatible TheRock rocm[libraries,devel], torch, torchvision, and torchaudio versions were found for {requested} in {index_url}"
)

Reached unconditionally from install_wheel_runtime (therock.rs:1179) regardless of include_devel, so a user who runs plain rocm install sdk and hits a resolution failure is told about a spec they did not request. Every other user-facing string in the same file was correctly parameterised on include_devel (therock.rs:889-897, 967-971), so this reads as a missed spot. One-line fix; blocking only because it is an objective factual inaccuracy introduced by this change.

The resolver itself is fine — version selection queries the plain rocm package from the simple index and never checks devel-extra availability, so only the message needed changing.


Design review

CI green does not settle the two risks the PR body flags

The PR body says a runtime-only vLLM serve run is needed to confirm Triton still works without the devel tree. All checks are green including the three GPU lanes. That is not evidence for the new default:

  1. The GPU lane's install never ran under the new default. .github/workflows/ci.yml:880 gates the pre-warm on if [ ! -d "$E2E_SHARED_RUNTIMES_DIR/registry" ], and the comment at ci.yml:855-856 says it plainly: "Persisted across runs on RUNNER_WORKSPACE, so after the first run ever the pre-warm below is a no-op." The e2e-gpu log for head 1ae9877 shows shared runtime already present … — skipping pre-warm. The serve scenarios ran against a runtime installed by an earlier, pre-PR run — a rocm[libraries,devel] install. There is no flag- or version-based cache invalidation.
  2. The vLLM inference scenarios are not gating. tests/e2e-cucumber/expectations.toml:41-46 and :76-81 register serve-vllm-inference and serve-readiness-contract as flaky = true xfail under EAI-7333 when effective_engine = "vllm". They XPASSed, but a genuine toolchain regression could sit inside that tolerated bucket without turning the build red.

Also, --devel has zero E2E coverage — no scenario passes it. The only scenario doing a genuinely fresh install sdk under the new default (runtime-install-sdk-active) is @nightly-gated, skipped per-PR, and asserts nothing about toolchain presence.

The transitive-rocm-sdk-devel question (PR risk #1) cannot be settled from the repo: the dry-run path only echoes a constructed uv pip install string and never invokes a real resolver.

Recommendation: before trusting this, either force a pre-warm invalidation for this change so one GPU lane actually installs under the new default and then serves, or run the confirmation manually and record it. Landing on green checks alone would be landing on evidence that does not cover the change.

The devel flag records intent, but nothing keeps it aligned with reality

  • runtime_key (therock.rs:3129-3141) excludes devel, so devel and non-devel installs of the same version share one install_root and one manifest.
  • ensure_uv_venv (therock.rs:2275-2294) reuses an existing venv, and uv_pip_install_base (crates/rocm-core/src/uv.rs:82-89) issues a plain uv pip install with no --reinstall/sync — installs are strictly additive.
  • save_runtime_manifest (therock.rs:3159-3181) unconditionally overwrites, with devel: include_devel (therock.rs:1000).

So a user who has the toolchain and re-runs the plain rocm install sdk (the command the README documents) keeps hipcc/headers on disk but silently has their manifest rewritten to devel: false. The next rocm update, which builds a new environment at a newer version, then omits the toolchain — silently. Nothing warns, and neither rocm runtimes list nor rocm examine prints devel (render_runtimes_text, main.rs:5628-5748; append_examine_runtime_state, main.rs:11074-11167), so the state driving the next update is invisible.

The counter-argument is real — the user did omit --devel — which is why this is not blocking. But it undercuts the PR's own stated goal ("reinstalls what the user picked rather than silently adding or dropping the toolchain"), so it deserves a deliberate decision. Options: make the flag sticky (a plain reinstall can add devel but never clear it), derive it from the probe uniformly, or surface it in rocm runtimes list so drift is visible.

devel: probe.cmake_path.is_some() has a confirmed false-negative path

main.rs:6674-6676 infers the flag for adopted environments from probe.cmake_path. In the embedded probe (therock.rs:2432-2481), cmake_path is set in the from rocm_sdk import _devel branch (2445) or by a fallback checking root_path/lib/cmake (2478-2481).

  • False negative (confirmed reachable): the pre-existing test runtime_only_rocm_sdk_probe_validates_without_devel_root (therock.rs:4284-4330) shows a probe carrying root_path_error: "ModuleNotFoundError: …" is an accepted, validating state. There cmake_path stays None unless some other package root happens to expose lib/cmake — so an adopted environment that genuinely has the toolchain can be recorded devel: false.
  • False positive (plausible, unconfirmed): the fallback would fire if any runtime-only package root ships lib/cmake. I could not confirm this without inspecting real TheRock wheels.

Consequential rather than inert: neither select_runtime_update_source (main.rs:13237-13256) nor current_runtime_manifest (main.rs:6806-6819) filters on manifest.read_only, so an adopted manifest can be the source for rocm update and a wrong inference flows straight into include_devel. A comment noting the heuristic's limits, or gating on root_path_error, would help.


Non-blocking

  • README.md:258-260 — the rocm install sdk flag synopsis is the primary user-facing flags reference and does not list --devel.
  • --devel is silently ignored with --format tarball. Executed: rocm install sdk --family gfx110X-all --format tarball --devel --dry-run succeeds with no mention of the flag; install_sdk (therock.rs:475-493) only threads include_devel into the wheel branch. The sibling flag one line above hard-errors instead: bail!("specific TheRock version selection is only supported for wheel installs"). Behaviour is safe (tarballs ship the full SDK), but the asymmetry is confusing — a note in the help ("implied for --format tarball") or a matching diagnostic would close it.
  • Help text omits the cost. main.rs:517: "Also install the ROCm compiler and headers, for building GPU code." The roughly-doubled download — the entire motivation for the change — is the fact a user needs at the moment they decide. The after_help EXAMPLES block (main.rs:493-497) shows no --devel example either.
  • render_install_sdk_dry_run_for_args hand-rolls flag parsing. main.rs:10512: args.iter().any(|arg| arg == "--devel"), where the file's convention is chat_cli_has_flag (main.rs:9334, used at 9211, 9222, 14633, 14646) — and the sibling values in the same function use chat_cli_arg_value. Unreachable today (providers.rs's install_sdk_dry_run schema has no devel property), but a landmine if that parameter is added.
  • docs/wsl.md:110 — the edit to rocm[libraries] is accurate for that section (it is scoped to running pre-built HIP apps). Unlike the other two edited docs it never mentions --devel, so a WSL reader who does need to build has no signpost.
  • Stale CI comments. .github/workflows/ci.yml:39 calls the nightly scenario a "cold devel install"; ci.yml:852 and tests/e2e-cucumber/tests/e2e.rs:131 cite "an ~8.8 GiB devel tarball" as the pre-warm cost driver. Both now misdescribe the default. Informational — the real effect is a CI speedup.
  • Test gaps. Nothing asserts adopt_runtime_from_probe's new devel inference (runtime_adopt_records_read_only_manifest_from_probe, ~main.rs:22254, builds a probe with cmake_path: None and never checks adopted.devel — one assert_eq! away); nothing covers the CLI-level --devel wiring; nothing covers the install-then-reinstall manifest-overwrite path above.

Tradeoffs

  • devel excluded from runtime_key. Collapsing devel and non-devel into one runtime matches the additive install model and avoids doubling disk usage for what users perceive as one install. Including it would eliminate the manifest drift at the root, at the cost of two registered runtimes. The choice looks right; the drift issue is a consequence of it that wants handling separately.
  • #[derive(Default)] on SdkInstallRequest<'a> with &str fields. channel/format default to "", which hits TheRockChannel::parse("") → Err("unsupported TheRock channel: ") before anything else, so it fails loudly. All four call sites set both explicitly. The stringly-typed boundary (format_name at main.rs:2021-2033 stringifies the typed InstallFormat enum to fit the struct) is inherited from the pre-change 6-arg signature, not introduced here.
  • Direct default flip vs. opt-out first. Repo precedent is on this PR's side: 9ab576b (pin release signing key, verify by default) and 4108ea7 (deployment summary by default) both flipped user-visible defaults directly in one PR with docs updated alongside, and there is no CHANGELOG. It is the docs-updated-alongside half of that precedent that is incomplete (blocking item 2).

Positive signals

  • devel_default_for_legacy_manifest (therock.rs:255-262) is a named function with a comment explaining why the default is true rather than a bare #[serde(default)] — exactly the failure mode (silently stripping the toolchain on the next update) a bare default would have caused.
  • Both new tests are real, not tautological. Mutation-tested (then reverted): forcing extras = "libraries,devel" unconditionally fails pip_runtime_omits_devel_extra_by_default; flipping the legacy default to false fails legacy_manifest_without_devel_field_is_treated_as_having_it.
  • The SdkInstallRequest refactor is proportionate — it converts a growing positional list at the point a 7th argument would have crossed the readability threshold, rather than as speculative cleanup.
  • The PR body's "Not yet verified" section is candid and specific. That is what let this review target the CI-evidence question directly.

Not verified

  • No real (non-dry-run) SDK install and no GPU work. Every install-path claim is from dry-run output plus source tracing. I never observed a rocm[libraries] environment actually running a model.
  • PR risk Let Lemonade auto-select its llama.cpp backend #1 (transitive rocm-sdk-devel via the torch wheels) is unresolved — needs a live resolve on a runner.
  • PR risk Enable native-certs for ureq across all crates #2 (Triton runtime compilation under vLLM without the devel tree) is unresolved, and the green GPU lanes do not settle it.
  • I did not run scripts/therock_sdk_install_test.py end to end — venv creation failed here (missing python3.12-venv). Its breakage is proven from the CLI's dry-run output plus the script's literal assertion, not from a script run.
  • I did not inspect real TheRock wheel contents, so the cmake_path false-positive scenario stays plausible-unconfirmed.
  • I did not run the e2e-cucumber GPU scenarios locally — CI claims come from reading the e2e-gpu log for head 1ae9877.

@juhovainio juhovainio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I reviewed this PR and found two issues worth fixing before merge — left as inline comments below. Both are real regressions caused by the new opt-in-devel default, not style nitpicks.

I also looked into a few other concerns that have come up around this change (the from-source vLLM docs not mentioning --devel is now needed, CI not really exercising the new default because of GPU-lane pre-warm caching, and a claimed bug in cmake_path.is_some()), but none of them held up as clear bugs worth blocking on — the cmake_path one in particular looks like it's based on a test that actually shows the check working correctly, not failing.

Comment thread apps/rocm/src/therock.rs
pub family_override: Option<&'a str>,
pub dry_run: bool,
/// Install the compiler and headers alongside the runtime libraries.
pub include_devel: bool,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

include_devel now defaults to false, which flips the SDK's default extras from rocm[libraries,devel] to rocm[libraries].

scripts/therock_sdk_install_test.py (not touched by this PR) installs without --devel but still asserts the output contains rocm[libraries,devel]== (THEROCK_SDK_PACKAGE_SPEC at line 29, asserted at line 499). This documented manual smoke test (linked from docs/testing.md and docs/manual-testing.md) will now fail every time it's run.

Either pass --devel in the script's install invocation, or update THEROCK_SDK_PACKAGE_SPEC to "rocm[libraries]".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in df19fb0 — I reproduced it before changing anything, and you were right that it fails on every run.

I took your second option, because this PR's own doc edits had already settled the direction: docs/testing.md and docs/manual-testing.md now describe the script as verifying pinned rocm[libraries]. So THEROCK_SDK_PACKAGE_SPEC is gone and the expected spec comes from therock_sdk_package_spec(args.devel), defaulting to the runtime-only extras.

Two things beyond the minimum, since the flag this PR adds had no acceptance coverage at all:

  • a --devel passthrough, so the opt-in path can be exercised rather than merely left unbroken, with a negative assertion on each side (a default install must not plan rocm[libraries,devel], and a --devel install must not plan rocm[libraries]==). Those two lines are the only thing that would catch package_specs: and package_policy: drifting apart, since they are independent format sites.
  • an assertion that the manifest records the devel value actually requested. apply_runtime_update reinstalls from that field, so a wrong value there silently adds or drops the toolchain on the next update.

I have not run the script end to end — it downloads multiple GB of wheels. The spec assertion sits before the --dry-run early return, so --dry-run --family <family> reaches it cheaply if you want to confirm without the download.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Re-checked after today's rebase onto the current base tip (1f6ba03b): the answer still holds, and the mechanism is unchanged.

THEROCK_SDK_PACKAGE_SPEC is still gone; therock_sdk_package_spec(include_devel) (scripts/therock_sdk_install_test.py:33) still derives the expected spec, the install assertion at :541 still reads therock_sdk_package_spec(args.devel), and the --devel passthrough at :510 plus verify_manifest_devel at :378/:612 are intact. The resolution-path change since my earlier reply touched published_pip_requirements in apps/rocm/src/therock.rs, not this script, and did not reintroduce a hardcoded spec anywhere the script asserts against.

One thing I should state plainly rather than leave implied, since you are the person most likely to run this script: no workflow invokes it. I grepped all of .github/workflows/ just now and there are zero references — its only callers are the documented manual commands in docs/testing.md and docs/manual-testing.md. So the --devel passthrough and verify_manifest_devel are the most rigorous assertions in this PR and nothing runs them automatically; they protect this change only when someone runs them by hand. That gap is named in the PR description under "Gaps, stated". I have not run the script in this round (it needs uv, a live index and a real GPU host, none of which I have here) — what I verified is that the code it asserts against is unchanged.

Leaving this open for your verification.

Comment thread apps/rocm/src/therock.rs
"install TheRock SDK with the compiler toolchain, torch stack, and resolved dependencies"
} else {
"install TheRock SDK, torch stack, and resolved dependencies"
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is the pattern used everywhere else in the file to make user-facing strings reflect include_devel — but resolve_pip_runtime_from_index's error message (around line 1257) doesn't receive include_devel at all, and always says:

no mutually compatible TheRock rocm[libraries,devel], torch, torchvision, and torchaudio versions were found for {requested} in {index_url}

Someone who didn't pass --devel and hits this failure gets an error naming a toolchain they never asked for.

Thread include_devel into resolve_pip_runtime_from_index (or its caller) and branch the message the same way this block does.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in df19fb0. include_devel now reaches that message.

Threading it as a plain parameter pushed resolve_pip_runtime_from_index to 8 arguments and tripped clippy's too_many_arguments, so it goes through a PipRuntimeQuery struct instead — the same argument-struct pattern this PR already introduced for SdkInstallRequest. That also let resolve_pip_runtime and resolve_pip_runtime_with_timeout collapse into a single function, and the previously implicit None timeout is now explicit at the call site.

The message itself is built by no_compatible_pip_versions_message, which takes the extras from the same therock_sdk_extras helper as the install plan and the progress text. That branch had been written out three times, which is what let this one drift in the first place; there is now one copy.

resolve_latest_for_manifest passes manifest.devel. That matters beyond wording: apply_runtime_update reinstalls from the same field, so the update check and the update apply now provably name the same extras.

Covered by no_compatible_versions_message_names_only_the_requested_extras. I verified it fails against the old hardcoded string and passes after. Worth being explicit about a limit, though: it tests the helper in isolation, not the wiring through PipRuntimeQuery into the with_context closure — testing the composed failure would need a fake index. Only one bool is in scope at that call site, so I judged the isolation test a fair trade rather than an oversight.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Correction, after rebasing onto current main: the mechanism I described here no longer exists, so please re-read this one rather than trusting my earlier reply.

main fixed the same complaint from the other direction — its message names no extras at all (no mutually compatible TheRock rocm, torch, torchvision, and torchaudio versions were found for ...), which cannot go stale as the extras change. Keeping both left no_compatible_pip_versions_message unreachable, so 2e50032f deleted it along with its test, and PipRuntimeQuery went with it. Your concern is still addressed — nobody is told about a toolchain they did not ask for — just not by the code I pointed at.

What the deletion also removed was the load-bearing part of the doc comment on therock_sdk_extras, which still claimed "the install plan, the progress text, and the resolution failure" all shared it when only the install plan did. That prose is what made the real bug beneath this easy to miss: the requirements string handed to uv pip compile for version resolution was still hardcoded to rocm[libraries,devel,{device_extra}], so a default rocm install sdk --version 10.x resolved its versions against an extra the user declined. Fixed in 1e1f72c7 with tests in e0db526c; details in the PR comment above.

Leaving this open for your verification.

@rominf
rominf force-pushed the feat/devel-opt-in branch 2 times, most recently from 3a7b768 to df19fb0 Compare August 12, 2026 16:21
@rominf

rominf commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Both inline comments are addressed in df19fb0, replied to individually in their threads. Branch is rebased onto current main; the fix is a separate commit on top so the delta since your review is easy to read. Threads left open for you to close.

Three things beyond the two comments, so nothing is a surprise.

The from-source vLLM doc gap — I fixed it. You raised this in the review body and did not block on it, but it held up when I checked: docs/vllm.md recommends building vLLM from source against the TheRock stack, that build compiles HIP sources, and it now needs a flag the page never mentioned. docs/testing.md had the same gap next to the q_gemm.hip note. Both now say the toolchain is opt-in via rocm install sdk --devel. It is a gap this PR created, so it seemed to belong here rather than in a follow-up.

A related path is still untested, and I would rather flag it than quietly leave it. For adopted read-only runtimes, devel is derived as probe.cmake_path.is_some(). select_runtime_update_source does not exclude adopted manifests and apply_runtime_update reinstalls from that field, so a wrong derivation there would silently drop the compiler toolchain on the next update. Neither branch had coverage. I added both — runtime_adopt_records_read_only_manifest_from_probe now asserts the no-CMake case, and a new runtime_adopt_records_devel_when_the_probe_finds_cmake covers the other. I checked they actually pin the behaviour: forcing the derivation to false fails exactly the second, forcing it to true fails exactly the first. This is adjacent to your cmake_path.is_some() note — you were right that the existing test showed the check working; the gap was that nothing asserted the flag it produces.

Verification. cargo fmt --all -- --check, cargo clippy --locked --workspace --all-targets -- -D warnings, and cargo clippy --locked -p e2e-cucumber --test e2e -- -D warnings are clean; 439 rocm unit tests pass. One caveat worth stating plainly: the suite has to be run with --test-threads=1 locally to be meaningful, because two tests in therock.rs overwrite the process-global PATH while a sibling test shells out to tar. That race predates this PR and exists on main — I mention it only so a red local run is not mistaken for this change.

@rominf
rominf dismissed juhovainio’s stale review August 13, 2026 06:40

Fixed, please re-review.

@rominf
rominf requested a review from a team August 13, 2026 06:40

@juhovainio juhovainio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed this as making the ROCm compiler toolchain (devel) opt-in on rocm install sdk. The refactor itself is solid: grouping the install args into SdkInstallRequest/PipRuntimeQuery cleans up what were 7-8 positional parameters, backward compatibility for existing manifests is handled the right way round (a missing devel field defaults to true, with a test proving it), and the doc/script updates are consistent — I checked and there's no stale rocm[libraries,devel] reference left anywhere in the repo outside this diff.

The thing holding me back from approving is the same thing flagged in the PR description under "Not yet verified": this flips a default for every existing user, not just people who pass a new flag, and two real risks haven't actually been checked on a GPU/index lane — whether pip's resolver pulls rocm-sdk-devel back in transitively via the torch wheels, and whether vLLM's Triton runtime kernel compile silently breaks without the devel tree. I'd want one of those confirmed, or take the opt-out-first rollout the author already floated, before this lands. Left as an inline comment below.

Also left a small note about --devel being a silent no-op for tarball installs.

Comment thread apps/rocm/src/main.rs
family: Option<String>,
/// Also install the ROCm compiler and headers, for building GPU code.
#[arg(long)]
devel: bool,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This flag flips the default for everyone who runs rocm install sdk without it — previously rocm[libraries,devel], now rocm[libraries]. The PR body itself lists two effects that haven't been verified yet: whether pip's resolver pulls rocm-sdk-devel back in transitively through the torch wheel dependencies, and whether vLLM's Triton runtime kernel compilation silently breaks without the devel tree. Since this is a default-behavior change (not gated behind opt-in), I'd want at least one of those checked on a real GPU/index lane before merge, or land this as opt-out first and flip the default in a follow-up once verified.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Verified on the real release index and MI300X in 2c52384.

For dependency resolution, the PR selected the gfx94X-dcgpu 7.13.0 stack (rocm[libraries]==7.13.0, torch/torchaudio 2.11.0, torchvision 0.26.0). A real uv pip install --dry-run resolved 18 packages with no rocm-sdk-devel; the rocm[libraries,devel] control resolved 19, adding exactly rocm-sdk-devel==7.13.0.

For runtime behavior, the permanent nightly scenario now starts from an isolated empty registry, performs a default SDK install, checks both manifest.devel == false and installed distribution metadata for absence of rocm-sdk-devel, then starts vLLM and completes an inference request from that same runtime. The targeted MI300X dispatch passed 1/1 with zero unexpected failures: https://github.com/ROCm/rocm-cli/actions/runs/32126407107

Leaving this open for your verification.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

One pointer update after the rebase, since the scenario named above moved: main took indexes 11 through 15 in runtime_setup.feature, so the permanent nightly scenario is now runtime-16. Same @id:runtime-install-sdk-serves-without-toolchain, same @requires-gpu @requires-engine:vllm @nightly tags, same steps — only the display index changed, to keep scenario_names_are_indexed_sequentially_per_feature green.

The MI300X evidence in my earlier reply is unchanged and I have not re-run it; it was against the canonical release index, which this rebase does not touch.

One thing that does bear on your concern, though. The default-behaviour change was only half-wired on the ROCm 10 (next) layout: version resolution still asked uv pip compile for rocm[libraries,devel,...] regardless of the flag, so rocm install sdk --version 10.x without --devel still had its version choice constrained by the toolchain — and could fail outright with a toolchain-resolution error. Fixed in 1e1f72c7. That path is exercised on the required mock lane against a loopback index fixture, not on real hardware; I have not run it against the live ROCm 10 index.

Leaving this open for your verification.

Comment thread apps/rocm/src/therock.rs
read_only: false,
imported_from: None,
// Tarball artifacts ship the whole SDK; the extra is a wheel concept.
devel: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

--devel is silently a no-op when combined with --format tarball — the manifest always records devel: true here regardless of what was passed, since tarballs always ship the full SDK. That's the right end result, but nothing tells a user who explicitly passes --devel --format tarball that the flag had no effect. Worth a line in the --devel help text, or a warning when the two are combined.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 2c52384. The --devel help now says the compiler, headers, and static libraries are already included by --format tarball, and also states that opting into them roughly doubles the wheel download. I kept tarball behavior unchanged because the resulting full SDK is correct; the flag is simply redundant for that format. Leaving this open for your verification.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Re-checked after today's rebase onto the current base tip (1f6ba03b): the answer still holds, and the behaviour you flagged is unchanged.

The tarball path still hardcodes devel: true (apps/rocm/src/therock.rs:2913, with the comment "Tarball artifacts ship the whole SDK; the extra is a wheel concept"), and the --devel help text still says the compiler, headers, and static libraries are "already included by --format tarball". I extended that help text in this round for an unrelated reason — it now also explains that --devel creates a second side-by-side runtime rather than adding the toolchain to an existing one — and I checked that the tarball sentence survives verbatim; rocm install sdk --help renders both.

Two things this round add to the picture, both consistent with what you asked for:

  • rocm examine now reports active_runtime_toolchain: included|excluded for the active runtime, alongside the toolchain= field rocm runtimes list already had. A tarball runtime reports included, which is the honest answer for it.
  • A tarball manifest records wheel_composition: None, so includes_devel() falls back to the devel field — exactly the devel: true on this line. I also stopped an e2e step from unwrapping that None (it now names the precondition instead of crashing), since tarball installs and adopted runtimes are the reachable cases.

I still kept tarball behaviour unchanged rather than warning on the combination, for the reason given before: the resulting full SDK is correct and the flag is merely redundant there. If you would rather see an explicit warning when --devel --format tarball are combined, say so and I will add it — it is a small change and I would rather follow your preference than re-argue the earlier call.

Leaving this open for your verification.

@rominf
rominf force-pushed the feat/devel-opt-in branch from df19fb0 to 0219342 Compare August 18, 2026 08:44
@rominf

rominf commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up on the remaining Non-blocking items from the review — the two comments above only covered the inline blocking items. Addressed in b25c63e:

  • README flag synopsis now lists --devel, and the prose paragraph above it states the toolchain/headers tradeoff (roughly doubles the download).
  • docs/wsl.md now distinguishes running a pre-built HIP app (rocm[libraries] is enough) from building one from source (needs rocm install sdk --devel).
  • after_help EXAMPLES in main.rs now includes a --devel example alongside the others.
  • Stale CI comments — e2e-selfhosted.yml and e2e.rs no longer say every install unpacks an "~8.8 GiB devel tarball"; both now say that's what --devel adds on top of the base install.
  • render_install_sdk_dry_run_for_args's hand-rolled parsing now goes through chat_cli_has_flag, matching every other flag read in the file — the landmine you flagged (a future non-boolean devel-adjacent param) is closed.
  • Test gaps — added install_sdk_devel_flag_defaults_off_and_wires_through_when_passed (asserts the clap-parsed --devel default and wiring, not just the internal SdkInstallRequest logic) and reinstall_without_devel_overwrites_manifest_devel_flag (pins that a plain reinstall overwrites devel in the manifest rather than merging — the drift risk you called out in the design review, left as documented behavior rather than changed).

Verification: cargo fmt --all -- --check, cargo clippy -p rocm --all-targets -- -D warnings, cargo clippy -p e2e-cucumber --test e2e -- -D warnings, cargo xtask manifest --check, and the full rocm unit suite (469 passed, 1 pre-existing ignore) all clean.

@rominf

rominf commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Corrections to my previous comment — three claims in it were wrong or overstated. Fixed in f23fc41 where code was involved.

1. The manifest test did not cover what I said it did. I described reinstall_without_devel_overwrites_manifest_devel_flag as pinning that a plain reinstall overwrites devel. It never called install_sdk — it hand-built a manifest with devel: false, saved it, and asserted the reload. I mutation-tested the real write site (therock.rs:1030, devel: include_devel → devel: true, which is exactly the regression the test's own comment described): the test passed, and so did the other 468. So the gap you raised in the design review is still uncovered.

That path needs uv, a live index, and a real rocm_sdk probe, so it is not reachable from a unit test without a seam I did not think was worth adding here. Rather than delete the test, I renamed it to save_runtime_manifest_replaces_devel_rather_than_merging — the replace-vs-merge behaviour it genuinely pins — and stated the uncovered path in the doc comment. That narrowed claim is mutation-checked: making save_runtime_manifest preserve a prior devel: true fails it.

2. "matching every other flag parse in the file" was false. The chat_cli_has_flag swap is still right, but main.rs retains hand-rolled args.iter().any(|arg| arg == "--flag") reads at 9708, 9752, 9804, 15550, and 19993. Your original wording — that chat_cli_has_flag is the convention, used at 9211/9222/14633/14646 — was accurate; mine inflated it into a completeness claim. I left those five alone as out of scope for this PR.

3. The ~8.8 GiB figure was re-attributed without verification. The comments in e2e-selfhosted.yml and e2e.rs previously credited that size to the post-install probe; I rewrote both to credit it to --devel. That is a plausible reading of a stale comment, not something I measured, and it now reads as fact in two places. Happy to drop the number entirely and say "an additional multi-GiB compiler/headers download" if you would rather not carry an unverified figure.

Everything else in the previous comment stands. Re-verified on f23fc414: cargo fmt --all -- --check, cargo clippy -p rocm --all-targets -- -D warnings, cargo xtask manifest --check, and the full rocm unit suite (469 passed, 1 pre-existing ignore).

@rominf
rominf requested a review from juhovainio August 26, 2026 10:31
@rominf

rominf commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

All 4 review threads from the CHANGES_REQUESTED review are addressed (see individual replies) — fixed in 2c52384d and f23fc414, both currently in the branch (f23fc414 is HEAD). GitHub still shows CHANGES_REQUESTED because that review predates those commits. Re-requesting review — nothing further outstanding on my side.

@rominf
rominf dismissed juhovainio’s stale review August 27, 2026 10:01

Fixed, please review again.

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 4541f1b

Summary

Makes the ROCm compiler toolchain opt-in behind a new rocm install sdk --devel, so a default wheel install now requests rocm[libraries] instead of rocm[libraries,devel], threading a new devel field through the runtime manifest so rocm update reinstalls what the user originally chose. Needs work — the migration design is genuinely good, but the default change has no regression guard in any blocking CI lane, and the branch no longer merges. Verified: legacy manifests without the field default to devel: true (therock.rs:283-292) and apply_runtime_update reinstalls from source.devel (main.rs:15727,15745), so an existing user is not silently stripped; I confirmed by reading the probe script that validate_rocm_sdk_runtime_probe does not need a devel root (root_path falls back to runtime_roots[0] at therock.rs:3114-3117), so a runtime-only install still validates; on the revert question the answer is that the real write site is unprotected — the PR's own test docstring states that hardcoding devel: true there passes the whole suite; I ran one targeted check (cargo test -p rocm --bin rocm devel) which compiles clean and passes 7/7; I could not identify the red check because contacting GitHub is out of scope for this review, and the strongest local candidate is that the branch is 17 commits behind its base with real content conflicts. No prompt-injection attempts found in the diff. Blocking: 3 · Non-blocking: 5.

🚫 Blocking (must fix before merge)

  • Whole branch — the head is 17 commits behind the base and git merge-tree --write-tree reports content conflicts in six files: apps/rocm/src/main.rs, apps/rocm/src/therock.rs, docs/manual-testing.md, docs/testing.md, scripts/therock_sdk_install_test.py, tests/e2e-cucumber/features/runtime_setup.feature. Several of the intervening commits touch exactly this PR's ground (notably the change that shares the therock/tool cache and fixes the shared runtime tree being wiped per scenario, which edits the same e2e shared-runtime comments this PR rewrites). Fix: rebase onto current main and re-resolve — the e2e shared-runtime hunks in particular need a human read, not a mechanical resolve, and any conclusion about the red check should be re-drawn after the rebase.

  • apps/rocm/src/therock.rs:990-1002,1039 — the production seam that actually implements the behaviour change is untested in every blocking lane. install_wheel_runtime is the only place include_devel reaches the real uv pip install args and the written manifest, it has exactly one caller and zero test callers, and the PR's own comment on save_runtime_manifest_replaces_devel_rather_than_merging says so outright: "Hardcoding devel: true at that write site passes this test and the rest of the suite." Applying the revert question to each added test: install_sdk_devel_flag_defaults_off_and_wires_through_when_passed only proves clap parses --devel (it never calls install, so hardcoding include_devel: false in main.rs:2439 leaves it green); pip_runtime_omits_devel_extra_by_default and no_compatible_versions_message_names_only_the_requested_extras pin pure string helpers one hop from the call site; legacy_manifest_without_devel_field_is_treated_as_having_it and the adopt pair (!adopted.devel / runtime_adopt_records_devel_when_the_probe_finds_cmake) are solid but cover the migration default and the adopt path, not a fresh install. The two tests with real teeth run nowhere on a PR: the cucumber scenario is @requires-gpu @requires-engine:vllm @nightly and E2E_INCLUDE_NIGHTLY is only set unconditionally by the non-blocking nightly workflow, and scripts/therock_sdk_install_test.py has zero hits across .github/workflows/*.yml — it is a manual script, so its new verify_manifest_devel and assert_not_contains("rocm[libraries,devel]") protect nothing automatically. Fix: extract the manifest construction and/or the install-args assembly from install_wheel_runtime into a pure function taking include_devel and unit-test both polarities, and add a test that the CLI→SdkInstallRequest mapping in install() actually forwards devel — so that reverting the default breaks a check that runs on every PR.

  • tests/e2e-cucumber/features/runtime_setup.feature:6-16 — the scenario change removes coverage rather than adding it. Adding @requires-engine:vllm to @id:runtime-install-sdk-active means the new engine-independent assertion (the runtime excludes the compiler toolchain, which reads the manifest and shells into the venv to confirm rocm-sdk-devel is absent) now resolves to skip on the three Strix nightly lanes, where vLLM is not the effective engine and the scenario previously ran; and the old step the runtime includes an inference engine is deleted with no replacement anywhere (grep: no other use). Fix: split it — keep an engine-agnostic scenario asserting install → registered → active → toolchain excluded (and restore an inference-stack assertion), and put the vLLM serve/chat half in a separate @requires-engine:vllm scenario so a vLLM-unavailable host does not silently drop the default-install acceptance criterion.

Non-blocking

  • apps/rocm/src/main.rs:9062-9067 — adoption derives devel from probe.cmake_path.is_some(), but the probe script falls back to root_path/lib/cmake when that directory merely exists (therock.rs:3118-3121), so a runtime-only environment that ships any cmake config would be recorded as a toolchain install and the next rocm update would add it; I could not verify offline whether rocm-sdk-core ships lib/cmake, so this is a flagged risk, not a confirmed bug.
  • apps/rocm/src/therock.rs:4552-4566 — the reinstall-drift case (installed with --devel, later reinstalls plain → files stay, manifest flips to false, next update drops the toolchain) is documented only inside a test docstring; it belongs in the PR text and user docs, since a maintainer reading the docs will never see it.
  • apps/rocm/src/main.rs:6642-6760, 13475-13519 — neither rocm runtimes list nor rocm examine surfaces the new devel state, so a user whose install lacks the toolchain has no CLI-visible signal; the guidance naming --devel lives only in docs/vllm.md.
  • scripts/therock_sdk_install_test.py — its new --devel path and manifest check are the most rigorous assertions in the PR, and they are invoked by no workflow; worth saying so explicitly in the PR text per the repo's own rule about gaps that cannot run in default CI.
  • apps/rocm/src/main.rs:603-607 — --devel combined with --format tarball is silently a no-op (the tarball path hardcodes devel: true); the help text explains it, but accepting a flag that does nothing invites a bug report.

@juhovainio juhovainio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Solid PR, well-reasoned. --devel flag, opt-in, defaults preserved via SdkInstallRequest struct (also fixes an 8-param clippy threshold issue).

Legacy manifest handling is correct: a missing devel field defaults to true since old installs always included the toolchain, avoiding a silent strip on update. The adoption path derives devel from the probe's cmake_path presence, with both positive and negative test cases.

The one CI failure, E2E tests (Strix Halo, Ubuntu), is a 35-min timeout rather than a test failure. This branch predates 03c46b5 which bumped that lane's timeout, so it looks like stale infra rather than a code problem.

Approving. Please rebase onto main to pick up the timeout fix and confirm CI goes green. Also curious if you've since gathered data on the two open questions from the PR description (transitive rocm-sdk-devel pull-in via torch wheels, and Triton's runtime-only toolchain behavior under vLLM) since those would be good to close out even if non-blocking.

@siloteemu

Copy link
Copy Markdown

🔴 Automated review · pr-review-watcher · 4541f1b

This is a re-statement, not a new gate. The change request we filed on 2026-09-09 is still live and is the only objection we hold here. Nothing has been pushed since, so this re-check tested our own reading rather than new code.

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.

Summary

Forced re-check of the change request filed on 2026-09-09. The head has not moved (4541f1b), so this is a re-examination of our own reasoning, not of new code. I read our original text verbatim and tried to refute each count against the tree rather than confirm it. All three counts still hold; none was retracted, and the code says what we said it said. One count is sharpened and one is narrowed in scope (details below). CI conclusions I worked from: 26 success, 1 cancelled, 0 failure — a cancelled job is not a failure, and I am not attributing that cancellation to any named lane.

The design of this change remains good: the legacy-manifest default, the SdkInstallRequest grouping, the update path reinstalling source.devel, and the user-facing documentation are all sound, and the docs were re-checked line-by-line against the code they describe (see Positive signals). The objection is not to the feature. It is that the one production seam implementing the behaviour change has no check that runs on a pull request, and that the acceptance scenario meant to cover it was narrowed onto a lane subset. Blocking: 3 · Non-blocking: 4.

The branch is also conflicting with its base and needs a rebase from the author regardless of this review.


Per-count outcome

Count 1 — branch no longer merges → still holds (B), and has degraded

Original claim: the head is 17 commits behind the base with content conflicts in six files.

At this commit the branch is 39 commits behind its base. git merge-tree --write-tree against the current base head reports content conflicts in seven files — the six we listed plus README.md, which now conflicts as well:

  • README.md
  • apps/rocm/src/main.rs
  • apps/rocm/src/therock.rs
  • docs/manual-testing.md
  • docs/testing.md
  • scripts/therock_sdk_install_test.py
  • tests/e2e-cucumber/features/runtime_setup.feature

What resolves it: rebase onto the current base and re-resolve. The runtime_setup.feature and therock.rs hunks want a human read rather than a mechanical resolve, since intervening work touched the same shared-runtime ground this PR rewrites.


Count 2 — the production seam is untested in every blocking lane → still holds (B), unchanged

The sentence we are testing, verbatim from our review:

"install_wheel_runtime is the only place include_devel reaches the real uv pip install args and the written manifest, it has exactly one caller and zero test callers"

Verified directly. install_wheel_runtime appears exactly twice in the tree: its definition (apps/rocm/src/therock.rs:822) and its single call site inside install_sdk (therock.rs:507). No test calls it. Inside it, include_devel reaches the real install arguments (therock.rs:942-946, 985, 993-996) and the manifest (therock.rs:1039, devel: include_devel). The only test that calls install_sdk at all is install_sdk_rejects_tarball_on_windows_before_resolution (therock.rs:5787), which is #[cfg(windows)] and takes the tarball branch — it never reaches the wheel wiring.

Branch-level mutation results (per standing focus (a) — not just wholesale revert):

Mutation Caught? By what, in which lane
Hardcode devel: true at the manifest write site (therock.rs:1039) No test fails —
Hardcode include_devel: false in the CLI→SdkInstallRequest mapping (main.rs:2447) No test fails —
Revert the extras helper so the default emits rocm[libraries,devel] again Caught pip_runtime_omits_devel_extra_by_default (therock.rs:4519) and pip_runtime_installs_pinned_devel_and_torch_stack_from_therock_index (therock.rs:4452), which run in a blocking PR lane

This matches our original scoping precisely: we said those two tests "pin pure string helpers one hop from the call site", and that is exactly right — the extras string is covered on every PR; the wiring from the flag to the install arguments and the manifest is not covered anywhere.

The PR's own comment corroborates this rather than contradicting it. therock.rs:4562-4566 states:

"LIMIT: this covers save_runtime_manifest only. It does NOT reach install_sdk, which is what actually threads include_devel into the manifest — that call needs uv, a live index, and a real rocm_sdk probe, so it has no unit coverage here. Hardcoding devel: true at that write site passes this test and the rest of the suite."

I checked that sentence against the code (this repository's standing defect class is prose stating a false fact) and it is true. Commit 4541f1b added it deliberately, replacing a comment that implied broader coverage. That is honest and welcome — but weighing the remediation as strictly as original code (standing focus (b)): the remediation documented the gap, it did not close it. The blocking condition is unchanged.

Where the two checks with real teeth run:

  • The cucumber scenario is @requires-gpu @requires-engine:vllm @nightly. E2E_INCLUDE_NIGHTLY is set unconditionally only in the schedule/dispatch-only nightly workflow. The self-hosted workflow does run on pull requests, but there the value is "${{ inputs.include_nightly && '1' || '' }}", and inputs.include_nightly exists only for workflow_dispatch — so it is empty on a pull request. Every lane in that workflow is additionally continue-on-error: true and the file documents itself as non-blocking.
  • scripts/therock_sdk_install_test.py has zero references in .github/workflows/ (I grepped the whole tree: the only references are two documentation files). Its verify_manifest_devel is also behind the dry-run early return, so it needs a full real install to fire at all.

New at this commit, folded into this count. The one artefact that gestures at wiring coverage misnames itself. install_sdk_devel_flag_defaults_off_and_wires_through_when_passed (main.rs:24210) only calls Cli::try_parse_from and matches on the parsed InstallTarget::Sdk { devel, .. }. It never calls install(), so it cannot observe the wiring its name claims. The commit message for 4670da6 repeats the claim — "add a CLI-parse test pinning --devel's default and wiring". A test that does not test the thing it names is the exact failure mode this matters for: a maintainer scanning names will read this as the wiring being covered.

This also runs against the project's own documented rule (AGENTS.md:95): "a unit test asserting the internal helper does NOT discharge this; it proves the function, not the behavior", and (AGENTS.md:85) "if an e2e cannot run in default CI, state the gap in PR text and cover at another CI level" — the second half, covering at another CI level, is what is missing.

What resolves it (any one of these, all landing in a lane that runs on every PR — ci.yml runs cargo nextest run and cargo test --workspace --all-targets on pull requests, so a unit test is sufficient):

  1. Extract the manifest construction and/or the install-argument assembly out of install_wheel_runtime into a pure function taking include_devel, and unit-test both polarities; and
  2. Add a test that install()'s CLI→SdkInstallRequest mapping actually forwards devel (today mutating that line is invisible); and
  3. Rename install_sdk_devel_flag_defaults_off_and_wires_through_when_passed to say what it does (it pins the clap default), so the name stops claiming coverage that does not exist.

Count 3 — the scenario change narrows coverage → still holds (B), with one nuance we got wrong

The sentence we are testing, verbatim:

"Adding @requires-engine:vllm to @id:runtime-install-sdk-active means the new engine-independent assertion ... now resolves to skip on the three Strix nightly lanes, where vLLM is not the effective engine and the scenario previously ran; and the old step the runtime includes an inference engine is deleted with no replacement anywhere"

Every falsifiable part checks out:

  • The skip is real and silent. tests/e2e-cucumber/src/expectation.rs:430-441: the effective engine is the @requires-engine pin if present, else the host default; if decl.requires_gpu && !cap.engine_available(engine) returns Expectation::Skip. tests/e2e-cucumber/tests/e2e.rs turns that into run = false, so the scenario is filtered out before execution and cannot fail the job.
  • vLLM is not the effective engine on Strix. tests/e2e-cucumber/src/capability.rs:58-64 (family_prefers_vllm) restricts vLLM preference to *-dcgpu and gfx906/908/90a; gfx1151 falls through to lemonade. Pinned by strix_halo_defaults_to_lemonade (capability.rs:568-577) and by vllm_pinned_scenario_skips_where_vllm_cannot_start (expectation.rs:857-875), which asserts a @requires-engine:vllm scenario resolves to Skip on a Strix host.
  • Three Strix nightly lanes set E2E_INCLUDE_NIGHTLY: "1" (native Linux, Windows, WSL). Before this change the scenario carried only @requires-gpu @nightly, so it resolved against the host default engine and ran there.
  • Nothing catches the loss. tests/e2e-cucumber/expectations.toml has no entry for runtime-install-sdk-active, and the file's own header states that a scenario skipped for an unmet @requires-* tag is "never listed here". So the skip is by design invisible to the expectation matrix.
  • The old step really is gone with no replacement. the runtime includes an inference engine matches nothing in the tree. The step function was rewritten in place rather than left dangling, so there is no dead step definition — that part is clean.

Where our original wording overstated. We said the change "removes coverage rather than adding it". That is too flat. The same scenario also gains a genuinely stronger check — a real serve plus chat completion plus model-identity assertion — which is far better evidence that an inference engine works than the deleted stdout.contains("torch") || stdout.contains("vllm") substring test ever was. The accurate statement is: coverage is gained on vLLM-capable hosts and lost entirely on the three Strix lanes, where a weak-but-present check became no check at all. The blocking substance is unchanged, but the finding should be stated that way.

Separately, and new at this commit: the comment block added above the scenario says the fresh install "must omit the compiler toolchain and still support vLLM's runtime compilation and inference from that same isolated environment." The scenario does not exercise or assert any compilation — serve_gpu_model runs rocm serve <model> --engine vllm --managed against a vLLM the adapter did not install, and asserts readiness, a chat reply and the model name. The phrase "runtime compilation" appears nowhere else in the repository. This is the standing defect class, and it matters here because this sentence is the stated justification for the tag that causes the Strix coverage loss — a false rationale is holding up a real narrowing.

What resolves it: split the scenario. Keep an engine-agnostic one asserting install → registered → active → toolchain excluded (and restore an inference-stack assertion so the Strix lanes keep a signal), and move the vLLM serve/chat half into its own @requires-engine:vllm scenario. Then correct the comment to describe what the scenario actually proves — that a runtime-only install can serve an already-built vLLM — which is exactly what docs/vllm.md already says.


Non-blocking (carried forward, re-verified)

  • apps/rocm/src/main.rs:9062-9067 — adoption derives devel from probe.cmake_path.is_some(), but the probe falls back to root_path/lib/cmake whenever that directory merely exists (therock.rs:3118-3121). A runtime-only environment shipping any cmake config would be recorded as a toolchain install, and the next rocm update would add one. Still a flagged risk, not a confirmed bug — whether the runtime packages ship lib/cmake cannot be determined from the tree.
  • apps/rocm/src/therock.rs:4552-4566 — the reinstall-drift case (install with --devel, reinstall plain → files stay, manifest flips to false, next update drops the toolchain) is documented only inside a test doc comment. It belongs in the PR text and user docs, where a maintainer will actually see it.
  • apps/rocm/src/main.rs:6642-6760, 13475-13519 — neither rocm runtimes list nor rocm examine surfaces the new devel state, so a user whose install lacks the toolchain has no CLI-visible signal. This is also what makes the drift case above invisible.
  • apps/rocm/src/main.rs:603-607 — --devel with --format tarball is silently a no-op (the tarball path hardcodes devel: true at therock.rs:1206). The help text and the code comment agree with each other, so this is not a false-prose issue; accepting a flag that does nothing still invites a bug report.
  • Minor, new: render_install_sdk_dry_run_for_args (main.rs:12983) gained a --devel parse with no test of its own.
  • Minor, new: the shared-runtime comments in tests/e2e-cucumber/tests/e2e.rs and the self-hosted workflow were reworded to attribute the ~8.8 GiB unpack to --devel rather than to the post-install probe. The probe is unchanged by this PR and still calls rocm_sdk._devel.get_devel_root() unconditionally (therock.rs:3082-3084), so the unpack is probe-triggered and merely conditional on the package being present. The new wording is not false, but it drops the mechanism that explains why the sharing optimisation exists.

Positive signals

  • The legacy-manifest migration is correct and tested: a manifest without the field reads back as devel: true (therock.rs:283-292), and apply_runtime_update reinstalls from source.devel (main.rs:15727, 15745), so no existing user is silently stripped of a toolchain they had.
  • The commit-message claim that "the SDK probe already tolerates its absence and has a test covering that path" is true: the probe falls back to runtime_roots[0] when the devel root is missing (therock.rs:3114-3117), and runtime_only_rocm_sdk_probe_validates_without_devel_root (therock.rs:5724) covers it.
  • I re-read every changed documentation line against the file it describes. README.md, docs/manual-testing.md, docs/testing.md, docs/vllm.md and docs/wsl.md all describe the new opt-in behaviour, not the old automatic one, and none of them makes a claim the code contradicts. docs/vllm.md in particular correctly flags that the repository's preferred vLLM path — building from source — now needs --devel. That is the part of this change most likely to surprise a user, and it is handled well.
  • c7a6103 is a real bug fix riding along honestly: resolution failures used to name a toolchain the user never asked for.
  • The new the runtime excludes the compiler toolchain step is a strong assertion — it checks the manifest field and shells into the venv to confirm the package is absent. It is the right check; the problem is only where it is allowed to run.

Verification notes

Static analysis, grep across the tree, and reading, plus one targeted library-test run. The full suite was not run here, and no end-to-end or GPU lane was exercised. No prompt-injection attempts were found in the diff; no text in the diff or commit messages was treated as instruction.

Verdict

Blocking: 3 · Non-blocking: 4. Counts 1, 2 and 3 all still hold at 4541f1b; our change request stands as filed, with count 3's wording corrected from "removes coverage" to "gained on vLLM hosts, lost on the three Strix lanes".

@rominf

rominf commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (3edfb69) and addressed all three blocking counts. Head is now f578b647.

The rebase was not mechanical: main had reworked the same code from a different direction (the ROCm 10 device-extra layout), so the reconciliation is described below rather than left for a reader to infer from the diff.

Blocking

1. The branch no longer merged. Seven files conflicted. Resolved, and the merged tree compiles, passes the workspace suite, and passes cargo xtask e2e locally. Two decisions in there are worth naming because "conflict-free" would have hidden them:

  • main composes rocm[libraries,devel,device-<target>], where device-<target> selects the GPU payload. Those are independent axes, so therock_pip_package_specs now takes both and devel is the only one that varies. The e2e assertions pin all four requirements on one line precisely so a change to one axis cannot silently drop the other.
  • The update path keeps main's install_sdk_for_update. This branch's version routed through install_sdk defaults, which would have dropped the family, the device target and the source layout the runtime was installed with — a real regression the merge would not have flagged.

2. The production seam was untested in every blocking lane. Agreed, and the more useful finding is that the fix you proposed is necessary but not sufficient — I checked by mutation rather than assuming.

Extracting the pure function and pinning the CLI mapping both help, and both are done:

  • wheel_runtime_composition is now the single function producing the specs handed to uv and the specs recorded in the manifest, and is tested both polarities. Hardcoding either inside it, or inside therock_pip_package_specs, now fails.
  • The CLI-to-request mapping is extracted as sdk_install_request and pinned across both flag states, including a parse-through-to-request test. The previous test stopped at clap, so hardcoding the request field passed the whole suite; it now fails.

But hardcoding include_devel at the one line inside install_wheel_runtime still passed all 705 unit tests. That line cannot be unit-tested — the function needs uv, a live index and a real probe — so the guard had to come from somewhere else.

It comes from therock-next-02, which previews a default install and asserts the whole package_specs line. It carries no @requires-gpu, so it runs on the required E2E tests lane, not the nightly one. Mutation-verified: with include_devel hardcoded, that scenario is the one additional failure, and reverting the mutation clears it. therock-next-09 is its --devel counterpart on the same lane.

therock-next-08 now expects the toolchain, because it updates a runtime whose recorded specs contain it. That is the assertion for the next point.

Manifest drift closed rather than tested around. main records wheel_composition.package_specs verbatim and reads the device target back out of them, with a comment giving the reason: one stored value cannot drift from a second field. InstalledRuntimeManifest::includes_devel() now does the same for the toolchain — the devel field is the fallback for tarball installs and for manifests written before compositions existed, and true for ones older still. The recorded specs outrank a contradicting field, and there is a test for that.

3. The scenario change removed coverage. Correct on both halves, and split as you suggested:

  • runtime-01 is engine-agnostic again — installs, registers, activates, includes an inference engine, excludes the toolchain — so the default-install acceptance criterion runs on every GPU lane instead of only where vLLM is the effective engine.
  • the runtime includes an inference engine is restored; it had been deleted with no replacement.
  • The vLLM serve and chat half is now its own @requires-engine:vllm scenario.

The toolchain assertion also reads the recorded package_specs, not just the devel field. Those two agreeing is the whole point of includes_devel, so asserting only the field would have missed the case it exists to prevent.

Non-blocking

  • rocm runtimes list now reports toolchain=included|excluded. It was the only runtime property with no CLI surface, while rocm update silently reinstalls from it. rocm examine still does not show it.
  • Adoption deriving devel from probe.cmake_path.is_some() is unchanged. Your reasoning holds — the probe falls back to root_path/lib/cmake when that directory merely exists — but I still cannot verify offline whether rocm-sdk-core ships one, and guessing wrong in either direction is worse than leaving a known risk documented.
  • The reinstall-drift case is in the PR text now, not only a test docstring.
  • scripts/therock_sdk_install_test.py is invoked by no workflow. Still true, now said in the PR text. Its --devel passthrough and manifest check remain manual.
  • --devel with --format tarball is explained in the help text; tarball behaviour is unchanged because the resulting full SDK is correct.

Withdrawn from my own earlier work

PipRuntimeQuery and no_compatible_pip_versions_message are gone. They existed so a user who never passed --devel would not be told a compiler toolchain could not be resolved. main fixed that same complaint from the other side — its message names no extras at all, which cannot go stale as the extras change — and its resolution path no longer iterates candidate indexes, so the struct had nothing left to carry. Keeping both would have been two mechanisms for one problem.

Verification

cargo fmt --all --check, both clippy invocations CI runs, and cargo test --workspace --all-targets are clean apart from failures that reproduce on an unmodified main checkout: the rocm-dash-tui agent OAuth unit tests, and in cargo xtask e2e the two dash-gen-tps scenarios, which are intermittent and unrelated to this change.

Three mutations were each killed by their intended test: hardcoding include_devel in the specs builder, in the CLI mapping, and at the install_wheel_runtime call site.

Not run: the GPU nightly scenarios and scripts/therock_sdk_install_test.py, neither of which this host can execute.

@rominf

rominf commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

@juhovainio — your approval was at 4541f1b4, and the rebase onto current main changed real behaviour since, so please re-look rather than let it carry over. Head is f578b647.

Your four threads are still open and still yours to close, but the code two of them point at has moved. Where it went:

  • THEROCK_SDK_PACKAGE_SPEC in the smoke script. Still fixed, but the expected spec is now a prefix, not a whole spec: main appends a device-<arch> extra, so the closing bracket is no longer in a fixed place. The default expects rocm[libraries,device- and --devel expects rocm[libraries,devel,.
  • Threading include_devel into resolve_pip_runtime_from_index's error message. I have withdrawn the fix I described to you there. PipRuntimeQuery and no_compatible_pip_versions_message are both gone. main fixed the same complaint from the other side — its message now names no extras at all, so it cannot name a toolchain you did not ask for, and cannot go stale as the extras change. Keeping my version would have been a second mechanism for a solved problem. The argument-struct refactor went with it, since its only reason for existing was carrying that flag.
  • The default-behaviour change needing real-GPU verification. The MI300X evidence from 2c52384d stands. What is new is that the default is now also pinned on the required E2E tests lane, not just the nightly one: therock-next-02 previews a default install and asserts the full package_specs line, with therock-next-09 doing the --devel side.
  • --devel with --format tarball being a silent no-op. Unchanged — still documented in the help text.

Other things worth a second opinion:

  • The manifest now has two ways to answer "does this runtime have the toolchain". includes_devel() prefers the recorded wheel_composition.package_specs and falls back to the devel field. That follows the pattern main set with wheel_composition_device_target, but it does mean the field you reviewed is no longer the primary source.
  • The e2e scenario is split. runtime-01 is engine-agnostic again so the default-install criterion runs on every GPU lane; the vLLM half is runtime-11. The @requires-engine:vllm you would have seen on runtime-01 is gone.
  • rocm runtimes list gained a toolchain= field, so this state is finally visible somewhere.

@siloteemu
siloteemu dismissed their stale review September 17, 2026 12:17

Superseded: re-reviewed at the current head, and all three counts are discharged by real work. The branch is rebased to zero commits behind with a clean merge and the reconciliation decisions written down; the default is now guarded by a composition helper and a CLI mapping that are unit-tested in both polarities, plus an untagged scenario on a check that runs before merge — confirmed by mutation rather than from the description; and the scenario split is the one we asked for, with the engine-agnostic assertion no longer pinned to a single engine. A fresh review at the current commit follows with one remaining defect on the version-resolution path.

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 37f5024

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.

Summary

The PR makes the ROCm compiler toolchain (devel wheel extra) opt-in behind a new rocm install sdk --devel, records the choice in the runtime manifest, and reads it back so rocm update reinstalls what was chosen. All three of our prior counts are now resolved by real work — the branch is rebased and conflict-free, the default is guarded by a scenario on a required per-PR check, and the e2e scenario is split so the engine-agnostic assertion is no longer pinned to one engine — but one defect remains: the ROCm 10 version-resolution query still asks the index for the toolchain unconditionally, so on that path the resolver and the install disagree about what was requested. Verified: derived the merge base myself and measured 0 commits behind with git merge-tree --write-tree reporting no conflicts; read all ten commit messages whole from the raw objects; confirmed from the workflow path filters and the scenario tags that the blocking, GPU-less mock E2E job runs for this change and executes the untagged preview scenarios; and mutation-tested in a scratch copy — baseline devel tests pass, hardcoding the composition helper turns two of them red, while hardcoding the single line inside install_wheel_runtime leaves all 705 unit tests green, confirming only the preview scenario covers that line; the full test suite, the e2e lanes and clippy were not run here, and I was working from CI conclusion counts of 13 success, 1 neutral, 0 failure with 4 checks still pending, so the CI picture is incomplete. Blocking: 1 · Non-blocking: 5.

Previously raised

  • COUNT 1 (17 commits behind, content conflicts in six files) — RESOLVED: the head is now 0 commits behind its base, and git merge-tree --write-tree against the base tip returns a clean tree with no conflict report. The reconciliation was done by hand, not mechanically: one commit documents the decisions taken where the base had reworked the same code from a different direction.
  • COUNT 2 (the production seam is untested in every blocking lane) — RESOLVED, with the mechanism verified rather than taken on trust. The composition helper and the CLI-to-request mapping were extracted and unit-tested in both polarities; I confirmed by mutation that hardcoding either one turns unit tests red. The remaining line inside install_wheel_runtime is still invisible to unit tests — my mutation of it left the whole unit suite green — but it is now covered by an untagged preview scenario that asserts the entire package_specs: line, and I confirmed from the source that this line is printed from the value that mutation produces, before the dry-run branch. That scenario carries no GPU or nightly tag and its file is in the path filter for the blocking mock job, so reverting the default breaks a required check.
  • COUNT 3 (the scenario change removes coverage) — RESOLVED: the engine tag was not added to the engine-agnostic scenario, which keeps the runtime includes an inference engine and gains the toolchain-exclusion assertion, so it still resolves to run on every GPU lane. The vLLM serve/chat half is now its own scenario carrying the engine tag. This is the split we asked for, implemented as suggested.

🚫 Blocking (must fix before merge)

  • apps/rocm/src/therock.rs:2724-2726 — the requirements string handed to uv pip compile for version resolution is hardcoded to rocm[libraries,devel,{device_extra}]. This is the ROCm 10 layout path, reachable by rocm install sdk --version 10.x without --devel, and it is the very path this PR spent a commit reconciling. The line is pre-existing and was correct while the toolchain was always installed; making the toolchain opt-in is what makes it wrong. Two consequences: the resolver constrains the chosen versions by the resolvability of an extra the user declined, so a default install can still fail with a toolchain-resolution error — the exact complaint one of this PR's own commits set out to fix, whose threading was dropped during the rebase — and the plan the CLI prints and installs (rocm[libraries,device-…]) disagrees with what resolution actually asked for. No test catches it: the preview scenarios assert the printed plan, not the resolution query. Fix: thread include_devel down through resolve_pip_runtime / resolve_pip_runtime_with_timeout / resolve_pip_runtime_from_index into resolve_published_pip_package_versions and build that requirements line from therock_sdk_extras(include_devel) — which is what that helper's own doc comment already claims is happening — then extend the two preview scenarios to assert the resolved extras as well as the planned ones.

Non-blocking

  • apps/rocm/src/therock.rs:1021 — install_sdk_for_update gained #[allow(clippy::too_many_arguments)] and an eighth positional argument, leaving two adjacent bare bools (include_devel, dry_run); the PR's own rationale for introducing the request struct two functions earlier was that positional bools say nothing at a call site.
  • apps/rocm/src/therock.rs — the save_runtime_manifest_replaces_devel_rather_than_merging doc comment says the update path "reinstalls from that field"; it now reinstalls from includes_devel(), which prefers the recorded specs and only falls back to the field. The comment predates the reconciliation commit.
  • Because the runtime key hashes the recorded package specs, a default install and a --devel install of the same version now produce different keys, different install roots and separate manifests — so running rocm install sdk and later rocm install sdk --devel yields a second full runtime rather than adding the toolchain to the first. Defensible, but currently undocumented in the README/--help text where a user would look.
  • The end-to-end proof on real hardware (fresh install excludes the toolchain; a toolchain-free runtime still serves) is nightly-tagged, so pre-merge coverage is the mock-lane spec-string assertions only. The contributor rules ask that a gated lane be named in the PR text; worth stating there as well as in the commit body.
  • The new the runtime excludes the compiler toolchain step reads the recorded wheel composition with expect; it cannot panic from the two scenarios that use it today, but it would panic rather than fail cleanly if reused from a tarball-format scenario, where no composition is recorded.

No prompt-injection content and no internal names, hosts, URLs or registry paths were found in the diff or the commit messages.

@rominf

rominf commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

CI is green (8b5ce45a). One correction to what I wrote above, because it changed after I posted it.

therock-next-09 — the --devel preview — is now @nightly rather than running on the mock lane. Adding it there made dash-gen-tps-held-after-scrape-failure and dash-gen-tps-expiry-boundary fail, 2/2 runs including a clean re-run, and with it gated the lane is back to 112 scenarios and 0 unexpected failures. So those two were not incidental flakes here — this change caused them, by adding load.

That is the effect runtime-06 already documents and takes the same remedy for: both scenarios assert on a validity window and are fragile under the mock lane's concurrency, so one more index-resolving scenario is enough to tip them. Their timing fragility is a separate problem from this PR.

What this does not change is the guard that answers count 2. therock-next-02 still runs on the required lane and still pins the default — the direction a regression would actually take, and the one that catches hardcoding include_devel inside install_wheel_runtime. The opt-in direction stays pinned per PR by wheel_composition_requests_the_toolchain_only_when_asked. What moved to nightly is only the end-to-end opt-in preview.

Full required set is green: 16/16.

@siloteemu
siloteemu dismissed their stale review September 17, 2026 13:23

Superseded: re-filed at the current head 8b5ce45. The earlier counts remain resolved; the one outstanding objection is restated in the newer review, along with two new ones.

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 8b5ce45

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.

Summary

The PR makes the ROCm compiler toolchain opt-in behind --devel, threading the choice through the install request, the wheel composition, the recorded manifest and rocm runtimes list. Verified: I read all 11 commit messages whole from the raw git objects, diffed against both the merge base and the base tip (identical file sets), traced the include_devel thread through every function signature on both the resolution and the install paths, read the scenario tags and the nightly gating mechanism in the workflows and the harness, and ran one targeted unit test (wheel_composition_requests_the_toolchain_only_when_asked, passes) — the full test suite and the e2e suites were not run here. Confirmed load-bearing: the version-resolution requirements string is still hardcoded; therock-next-02 is still untagged and still asserts the whole package_specs: line; therock-next-09 is now nightly-only. Working from check-run conclusions of 19 success, 0 failure, 0 pending, 0 skipped. Blocking: 3 · Non-blocking: 3.

Previously raised

  • OUR BLOCKING CLAIM — STILL STANDS. At this head, apps/rocm/src/therock.rs:2724-2726 reads verbatim "rocm[libraries,devel,{device_extra}]=={rocm_version}\ntorch[{device_extra}]\ntorchvision[{device_extra}]\ntorchaudio\n" — still hardcoded, not built from the flag. I read the signatures of all four functions named in the claim: resolve_pip_runtime, resolve_pip_runtime_with_timeout, resolve_pip_runtime_from_index and resolve_published_pip_package_versions take no include_devel parameter. The thread stops inside install_wheel_runtime, which calls resolve_pip_runtime at line 1659 without it and first uses include_devel at line 1682, after resolution has already returned. A separate reading of this branch asserts the flag "threads correctly end to end"; that is true of the install/composition/manifest path only. Version resolution and install composition are two different code paths — resolution runs first and decides which versions exist, composition runs second and decides what is installed and recorded — so a correct thread through the second says nothing about the first. Our earlier review already declared the second resolved. The claim is unchanged and unaddressed.

  • COUNT 2 COVERAGE — REMAINS RESOLVED for the default path; the opt-in direction has regressed. therock-next-02 (the default-path scenario count 2 was about) carries no @nightly and no @requires-gpu, and its step the preview requests the gfx1200 device extras asserts the entire line package_specs: rocm[libraries,device-gfx1200]==10.0.0 torch[...] torchvision[...] torchaudio==..., printed from wheel_composition.package_specs — the value produced at therock.rs:1682 — before the dry-run branch. The heavy path filter in the CI workflow matches **/*.rs, **/*.feature and tests/e2e-cucumber/**, so this PR triggers it, and the mock E2E job is a required check that does not set E2E_INCLUDE_NIGHTLY. Reverting the default still breaks a required check. What the head commit moved was therock-next-09, a different scenario covering the opt-in direction — see blocking item 2.

🚫 Blocking (must fix before merge)

  • apps/rocm/src/therock.rs:2724-2726 — the requirements string handed to uv pip compile for version resolution is hardcoded to rocm[libraries,devel,{device_extra}]. This is the ROCm 10 layout path, reached via resolve_pip_runtime_from_index when the pinned major is 10 or newer, so rocm install sdk --version 10.x without --devel still constrains the chosen versions by the resolvability of an extra the user declined — a default install can fail with a toolchain-resolution error — and the plan the CLI then prints and installs (rocm[libraries,device-...]) disagrees with what resolution actually asked for. Fix: thread include_devel down through resolve_pip_runtime / resolve_pip_runtime_with_timeout / resolve_pip_runtime_from_index into resolve_published_pip_package_versions and build that line from therock_sdk_extras(include_devel), then extend the preview scenarios to assert the resolved extras as well as the planned ones.

  • tests/e2e-cucumber/features/therock_next_generation.feature:137 — tagging therock-next-09 @nightly removes the only per-PR coverage of the opt-in direction at the one seam that needs it. @nightly scenarios are skipped unless E2E_INCLUDE_NIGHTLY=1, which only the scheduled nightly workflow and an explicit manual dispatch set; the blocking mock job does not. The consequence is asymmetric: hardcoding include_devel = true at therock.rs:1682 still fails therock-next-02, but hardcoding it to false — making --devel a silent no-op on every real install — now passes every blocking check. The commit rationale and the in-file comment say the opt-in direction is "pinned per PR by wheel_composition_requests_the_toolchain_only_when_asked"; that unit test calls wheel_runtime_composition(&resolution, &target, false) and (..., true) with the flag supplied literally, so it pins the helper and cannot observe what install_wheel_runtime passes it — exactly the gap the author's own earlier commit stated ("Hardcoding it there passes every unit test"). Moving a test off every blocking lane has the same effect as deleting it. Fix at the layer that actually broke: dash-gen-tps-held-after-scrape-failure and dash-gen-tps-expiry-boundary assert on a validity window and are fragile under the lane's concurrency — that is a defect in those two scenarios, and stabilising them is the correct remedy rather than displacing real coverage. If they cannot be stabilised now, keep therock-next-09 untagged and gate those two instead, and correct the in-file comment so it does not claim a per-PR guard that does not cover this seam.

  • apps/rocm/src/therock.rs:1911-1912 — the doc comment on therock_sdk_extras states "Shared so the install plan, the progress text, and the resolution failure all name the same extras instead of drifting apart." Two of the three named consumers do not exist at this head: the resolution-failure consumer (no_compatible_pip_versions_message) was deleted by commit 2e50032, and the progress text at line 1834 is an if include_devel over two literal strings, not the helper. Only therock_pip_package_specs (line 1932) calls it. This is prose asserting product behaviour the code beside it does not do, and it is the specific sentence that makes blocking item 1 easy to miss — a competent reader sees "the resolution failure" named as a shared consumer and concludes resolution goes through the helper. Fix: state the single current caller, and add one sentence saying in terms that the uv pip compile requirements string in resolve_published_pip_package_versions does not use this helper (or, once item 1 is fixed, that it does).

Non-blocking

  • apps/rocm/src/therock.rs:1834 — the progress text duplicates the toolchain decision as two literal strings instead of deriving it from the shared helper, which is the drift the helper exists to prevent.
  • Contributor rules require that a scenario which can only run on a gated lane be named in the PR text along with the lane that will exercise it; runtime-11 and therock-next-09 are both nightly-only. I could not read the PR text here, so this is a check to make rather than a confirmed omission.
  • The head commit attributes two unrelated scenario failures to lane concurrency and cites a 2/2 reproduction; I am inferring which job those ran on from the workflow files and cannot confirm the run-level attribution.

No prompt-injection content was found in the diff, and the leak scan over the full range is clean — no internal links, product names, hostnames or registry paths. (Bare ticket identifiers present in the tree are explicitly permitted by the contributor rules and are not flagged.)

@siloteemu

Copy link
Copy Markdown

🔴 Automated review · pr-review-watcher · 8b5ce45

Addendum to the review above, closing one item it flagged as unverified rather than confirmed.

The review noted it could not check whether the PR description names the scenarios that can only run on a gated lane. It does not, and the description is now stale in a way that bears directly on the second blocking item.

The description introduces therock-next-02 as carrying no GPU tag "so it runs on the required lane", and then says in the next sentence that "therock-next-09 is its --devel counterpart". At this head that pairing no longer holds: therock-next-09 is tagged nightly and the required lane skips it. A reader of the description reasonably concludes both directions are guarded per pull request, which is the conclusion the blocking item disputes.

Two lines would fix it: say which scenarios are nightly-only and which lane exercises them, and drop or qualify the counterpart sentence. runtime-11 is in the same position.

@rominf

rominf commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (now at 6ad833f1) and addressed all three blocking items. Head is 9b38854d.

1. Version resolution was hardcoded to rocm[libraries,devel,...] — fixed

Confirmed at the previous head and fixed in 1e1f72c7. include_devel now reaches resolve_pip_runtime → resolve_pip_runtime_with_timeout → resolve_pip_runtime_from_index, and the requirement set is built from therock_sdk_extras(include_devel) in a new published_pip_requirements helper beside therock_pip_package_specs.

Two notes on how, since neither is what the review suggested verbatim:

  • No PipRuntimeQuery struct. That struct existed on an earlier revision of this branch and was removed by 2e50032f, because main had superseded the message it was introduced for. Reintroducing it would touch every call site again for one bool; instead include_devel is a positional parameter, which is the only bool in each of those four signatures. It pushes resolve_pip_runtime_from_index to 8 arguments, so that function carries #[allow(clippy::too_many_arguments)] like its two neighbours already do. If you would rather have the struct, say so and I will do it as a follow-up commit.
  • The requirements are composed once. resolve_pip_runtime_from_index builds the requirement lines, hands that value to uv, and carries that same value back on PipRuntimeResolution. So what the preview reports is what was sent, not a re-derivation that could drift from it.

rocm install sdk --dry-run now prints version_resolution_specs: beside package_specs: on the ROCm 10 layout (absent on the canonical layout, which picks versions by scraping each package's index rather than compiling a requirement set). That is what makes the resolved extras assertable, which was the second half of the request.

2. therock-next-09 back on the per-PR lane

9b38854d reverts the @nightly tag. The reason for that tag is gone: dash-gen-tps-held-after-scrape-failure and dash-gen-tps-expiry-boundary now hold their observation clock instead of racing the host (#412, merged to main since this branch was last pushed), which is the layer that was actually broken. Your position was right and main fixed it there.

The in-file comment no longer claims a per-PR unit-test guard for this seam, because there is not one — wheel_composition_requests_the_toolchain_only_when_asked supplies the flag literally and so cannot observe what install_wheel_runtime passes it.

Mutation evidence, run locally:

injected regression result
hardcode the uv pip compile requirements to rocm[libraries,devel,...] 2 new unit tests FAIL
hardcode only the resolution argument in install_wheel_runtime to true therock-next-02 FAILS (it passed with this exact bug present before)
hardcode include_devel = false in install_wheel_runtime (--devel a silent no-op) therock-next-09 FAILS

The third row is the asymmetry the review identified; it is closed only because the scenario is back on the blocking lane.

3. therock_sdk_extras doc comment

Rewritten in 1e1f72c7. It now names the two callers that exist — therock_pip_package_specs and published_pip_requirements — says why those two in particular must agree (resolution and composition are separate paths), and states that everything else which mentions the toolchain reads it back out of the composed specs rather than re-deciding it.

Non-blocking

  • Progress text at the old line 1834. Now derived: it reads the toolchain back out of the specs uv is being handed on that very call (wheel_composition_includes_devel) instead of branching on the flag a second time.
  • Gated-lane scenarios named in the PR text. Done, and the list is now one entry: runtime-16 (@requires-gpu @requires-engine:vllm @nightly, nightly self-hosted GPU lane). It was runtime-11 before the rebase; main had taken indexes 11–15 in that feature, so it was renumbered. therock-next-09 is no longer gated at all.
  • Run-level attribution of the two dash failures. I cannot confirm it either — I did not re-run those jobs. It is moot now: test(dash): hold the observation clock so gen_tps scenarios cannot race the host #412 fixed those scenarios at their own layer, so this PR no longer relies on that attribution for anything.

What I ran, and what I did not

Locally on Linux (WSL), against the rebased tree: cargo check --workspace --all-targets, cargo clippy --workspace --all-targets -- -D warnings (clean, after touching every source file so the cached lint results could not mask anything), cargo fmt --all --check, cargo test -p rocm (754 passed), python3 scripts/smoke_local.py, and the full mock e2e suite (cargo xtask e2e): 120 scenarios, 118 passed, 2 expected-fail, 0 unexpected failures.

Not run here: the GPU and nightly lanes, scripts/therock_sdk_install_test.py end to end, and anything against the live ROCm 10 index — the resolution assertions above are against the loopback fixture the mock lane serves, which declares libraries, devel and device-gfx1200 as real extras on its rocm package. Three rocm-dash-tui unit tests fail on this machine with cannot create token cache dir: Permission denied; that is a sandbox quirk of my environment, not this branch, and those tests touch nothing this PR changes.

Every wheel SDK install pulled in the devel extra — headers, static
libraries, and the full LLVM toolchain — whether or not the user would
ever build GPU code. It is roughly half the download (1.2-1.5 GiB
compressed, depending on target) and nothing in this repository needs it
at run time: the SDK probe already tolerates its absence and has a test
covering that path.

Default to rocm[libraries] and add --devel for people who do build
against ROCm.

Record the choice in the runtime manifest so an update reinstalls what
the user picked rather than silently adding or dropping the toolchain.
Manifests written before this change have no such field and were all
toolchain installs, so a missing field reads back as present.

Group the install arguments into SdkInstallRequest, which keeps the
argument count within the clippy threshold and makes the call sites
readable.

Closes #164

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Resolution failures always read `rocm[libraries,devel]`, so anyone who
never passed `--devel` was told a compiler toolchain could not be
resolved — sending them after a problem they do not have. Thread the
requested extras through the resolver and share one helper with the
install plan and the progress text, so the three cannot drift again.

The documented manual acceptance test asserted the devel spec while
installing without `--devel`, so it failed on every run. Derive the
expected spec from a new `--devel` passthrough, which also gives the
opt-in path its own coverage.

Building vLLM from source compiles HIP sources and so needs the
toolchain, which docs/vllm.md recommended without mentioning the flag
that now installs it.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
- README and docs/wsl.md now mention --devel where relevant
- fix stale CI comments still describing devel as the default
- render_install_sdk_dry_run_for_args now uses chat_cli_has_flag,
  matching every other flag parse in the file
- add a CLI-parse test pinning --devel's default and wiring
- add a test pinning that a plain reinstall overwrites devel in the
  manifest, since nothing asserted that behaviour before

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
The test never reached install_sdk, so it did not cover the write site
that threads include_devel into the manifest — hardcoding devel: true
there passes it and the rest of the suite. Renamed and re-scoped to the
save_runtime_manifest replace-vs-merge behaviour it actually pins, with
the uncovered path stated in the doc comment.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
The rebase needed real decisions, not a mechanical resolve, because main
reworked the same code from a different direction.

- `therock_pip_package_specs` now composes both axes: main's mandatory
  `device-<target>` extra (which payload the wheels carry) and this
  branch's optional `devel` (whether the toolchain comes with them).
- `wheel_runtime_composition` takes `include_devel`, so the specs that go
  to `uv` and the specs recorded in the manifest are one value, not two
  that can disagree.
- `InstalledRuntimeManifest::includes_devel()` reads the answer back out
  of those recorded specs, mirroring `wheel_composition_device_target`,
  and falls back to the `devel` field and then to `true` for manifests
  written before either existed.
- The update path keeps main's `install_sdk_for_update`, which preserves
  the family, device target and source layout the runtime was installed
  with; the branch's version routed through `install_sdk` defaults and
  would have dropped all three.
- `PipRuntimeQuery` and `no_compatible_pip_versions_message` are dropped:
  main fixed that message by naming no extras at all, which cannot drift,
  and its resolution path no longer iterates candidate indexes.

The e2e scenario is split so the engine-agnostic acceptance criterion --
installs, activates, has an engine, has no toolchain -- runs on every GPU
lane again, with the vLLM serve half as its own scenario. Restores the
`the runtime includes an inference engine` step this branch had deleted.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
The flag reached the package specs and the manifest through
`install_wheel_runtime`, which has one caller, no test callers, and needs
uv plus a live index plus a real probe to run — so nothing in a blocking
lane noticed if the value stopped being forwarded.

Two seams now have tests, both polarities each:

- `wheel_runtime_composition`, the pure function that produces the specs
  handed to uv AND the specs recorded in the manifest. Hardcoding either
  polarity inside it, or inside `therock_pip_package_specs`, now fails.
  The device extra is asserted alongside so a fix to one axis cannot
  quietly drop the other.
- the CLI-to-request mapping, extracted from the `install sdk` arm as
  `sdk_install_request`. The existing test stopped at clap parsing, so
  hardcoding the request field passed the whole suite; it now fails, as
  does a parse-through-to-request test across both flag states.

Also asserts the recorded `package_specs` in the e2e toolchain check.
The `devel` field alone could agree while the specs handed to uv did not,
which is the drift `includes_devel` reading from the specs exists to stop.

Remaining gap, stated rather than papered over: the single line inside
`install_wheel_runtime` that passes `include_devel` into the composition
is still only covered by the GPU nightly scenario. Hardcoding it there
passes every unit test. Closing that needs the real install path.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
`no_compatible_pip_versions_message` existed so a user who never passed
`--devel` would not be told a compiler toolchain could not be resolved.
Main fixed the same complaint from the other side: its message names no
extras at all, which cannot go stale as the extras change. Keeping both
left the helper unreachable, so it and its test go.

`therock_sdk_extras` stays — the package specs still need it.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
The preview scenarios assert the whole `package_specs` line, so making
the toolchain opt-in changed what two of them expect. Updating them is
what turns them into the guard this change was missing.

`therock-next-02` previews a default install and now expects
`rocm[libraries,device-gfx1200]`. Hardcoding `include_devel` at the one
line inside `install_wheel_runtime` that no unit test can reach fails
exactly this scenario -- verified by mutation -- and it carries no
`@requires-gpu`, so it runs on the required E2E lane rather than the GPU
nightly one.

`therock-next-09` is its opt-in counterpart: same preview with `--devel`,
expecting `devel` added to the rocm extras and the device payload
untouched on all four requirements.

`therock-next-08` previews an update to a runtime whose recorded specs
include the toolchain, so it now expects the toolchain too. That is the
assertion for `includes_devel`: an update reinstalls what was installed,
not whatever the current default happens to be.

`rocm runtimes list` also reports `toolchain=included|excluded`. It was
the one runtime property with no CLI surface, while `rocm update`
silently reinstalls from it.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
ruff-check fixes this automatically, which fails the prek gate.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Adding it to the no-GPU mock lane made `dash-gen-tps-held-after-scrape-failure`
and `dash-gen-tps-expiry-boundary` fail 2/2 CI runs. That is the effect
runtime-06 already documents and takes the same remedy for: those two
assert on a validity window and are fragile under the lane's concurrency,
so one more index-resolving scenario is enough to tip them.

Not a coverage loss where it counts. therock-next-02 still pins the
default on the mock lane -- the direction a regression would actually
take, and the one that catches hardcoding `include_devel` inside
`install_wheel_runtime` -- and the opt-in direction is pinned per PR by
`wheel_composition_requests_the_toolchain_only_when_asked`.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
`uv pip compile` was handed `rocm[libraries,devel,device-<target>]` no
matter what the install requested, so `rocm install sdk --version 10.x`
without `--devel` still constrained the version choice by the
resolvability of a toolchain the user declined -- and could fail outright
with a toolchain-resolution error on a default install. The plan the CLI
then printed named `rocm[libraries,device-...]`, disagreeing with what
resolution had actually asked for.

Version resolution and install composition are separate code paths:
resolution runs first and decides which versions exist, composition runs
second and decides what is installed. `include_devel` was threaded
through the second only. It now reaches the first as well, and both build
their `rocm` extras from `therock_sdk_extras`.

The requirement lines are composed once, in
`resolve_pip_runtime_from_index`, and the same value is both sent to `uv`
and carried back on the resolution, so what the preview reports is what
was sent rather than a re-derivation that could drift. The preview prints
it as `version_resolution_specs:` beside `package_specs:`, which is what
lets an acceptance scenario hold the two to the same extras.

The progress line now reads the toolchain back out of the composed specs
instead of branching on the flag a second time, and the doc comment on
`therock_sdk_extras` names its two real callers rather than one that was
deleted and one that never used it.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
The existing coverage could not see the resolution path at all. The unit
tests pass `include_devel` literally, so they pin the helpers rather than
what `install_wheel_runtime` passes them, and the preview scenarios only
asserted `package_specs` -- which is composed after resolution has
already chosen the versions.

Three assertions now close that:

- `version_resolution_requests_the_toolchain_only_when_asked` pins both
  polarities of the requirement set handed to `uv pip compile`.
- `resolution_and_install_name_the_same_rocm_extras` pins the two paths
  against each other, which is the actual failure mode: each was
  self-consistent while they disagreed with one another.
- the preview scenarios assert `version_resolution_specs:` alongside
  `package_specs:`, so the wiring is covered and not only the helpers.

Verified by injecting each regression and watching the right test go red:
hardcoding the requirements to `rocm[libraries,devel,...]` fails both new
unit tests; hardcoding only the resolution argument inside
`install_wheel_runtime` fails therock-next-02, which previously passed
with that exact bug present.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
This reverts commit 2f23310.

The two dash scenarios that gating was meant to protect --
`dash-gen-tps-held-after-scrape-failure` and
`dash-gen-tps-expiry-boundary` -- now hold their observation clock
instead of racing the host (#412, on main). That is the layer that was
actually broken, so displacing real coverage to work around it is no
longer warranted.

Gating this scenario made the guard asymmetric: hardcoding
`include_devel = true` inside `install_wheel_runtime` still fails
therock-next-02, but hardcoding it to FALSE -- making `--devel` a silent
no-op on every real install -- passed every blocking check. Verified by
injecting exactly that: with therock-next-09 back on the mock lane, it
fails.

The comment no longer claims a per-PR unit-test guard for this seam.
`wheel_composition_requests_the_toolchain_only_when_asked` supplies the
flag literally, so it cannot observe what `install_wheel_runtime` passes.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
Making the compiler opt-in turned `rocm install sdk` and `rocm install sdk
--devel` at one version into two separate runtimes, because the runtime key
hashes the requested package specs. `remove-old-installs` grouped by channel,
format and family only, so those two competed for the same `--keep` slots and
recency decided which survived: a later runtime-only install could evict a
multi-gigabyte toolchain the user had explicitly asked for, with nothing in the
output naming it as a choice. They are not newer and older versions of the same
thing, so they no longer share a bucket.

The active, default and rollback runtimes were already held unconditionally, so
only a non-active devel runtime was ever exposed — which is exactly the case
nothing else protected.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
`rocm runtimes list` reports `toolchain=included|excluded` per runtime, but
`rocm examine` — the command a user runs first when something ROCm is wrong —
said nothing about it. With the compiler now opt-in, "my build cannot find
hipcc" is a state the CLI can produce by design, and the primary diagnostic
could not answer it.

`runtime-01` gains the user-visible half of the assertion it already made
against the manifest, so a recorded-correctly/reported-silently split fails.

Also stop unwrapping `wheel_composition` in the toolchain-exclusion step.
Absent is a reachable manifest state — tarball installs and adopted runtimes
have none — so reusing the step outside a wheel install reported a legitimate
precondition mismatch as a missing-field crash.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
A runtime is keyed by the packages it was installed from, so `rocm install sdk`
followed by `rocm install sdk --devel` leaves two side-by-side runtimes and
activates the second. Nothing said so: a user expecting the toolchain to be
added to the runtime they already had gets a second multi-gigabyte install
instead, and only finds out from the disk usage.

Stated where the flag is introduced (README and its --help) and where
side-by-side runtimes are the subject (runtime management), with the commands
that show which runtime is which and how to remove the one that is not wanted.

Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
@rominf

rominf commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 8788394c (#224). Force-pushed 1f6ba03b → 92f363fa; the only content change is the conflict resolution below.

What moved over. Six commits from main. #224 is the one that overlaps this branch: it rewrote the WSL branch of rocm install driver in apps/rocm/src/main.rs, repointed the WSL diagnose/fix recipes at rocm install driver, and deleted scripts/wsl_setup_rocdxg.sh. All of it survives intact — this branch's diff against main deletes no ROCDXG or driver-plan line, the script stays deleted, and the only remaining mentions of its path anywhere in the tree are #224's own explanatory comments and its negative assertion in driver_steps.rs.

Conflicts: three hunks, all in tests/e2e-cucumber/features/therock_next_generation.feature. Resolved by keeping both sides in each case:

  • therock-next-08's Then line: kept this branch's "with the toolchain" form and its comment.
  • A collision on index 09. feat(vllm): auto-discover ROCm 10.x vLLM/flash-attn/amd-aiter wheels (EAI-8270) #416 landed therock-next-09 - Installing vLLM against a live ROCm 10 preview runtime reports the discovery pins; this branch had also written a therock-next-09. Both are kept, and this branch's is now therock-next-10 - A pinned ROCm 10 wheel install adds the toolchain when asked. Its @id:therock-next-09-wheel-devel-adds-the-toolchain is deliberately unchanged — the id is the stable identifier and the display index is not — with a comment beside the scenario saying why it reads -09-. The two references to the old index in the PR description are updated.
  • The third hunk was the intermediate @nightly commit and its revert re-conflicting over the same region. The end state is the reverted one (no @nightly), i.e. what the branch carried before the rebase.

runtime-18 did not need renumbering this time: main still tops out at runtime-17 in runtime_setup.feature.

Verified locally on the rebased tree. No conflict markers anywhere; cargo check --workspace --all-targets; cargo check -p e2e-cucumber --test e2e separately, since that target sets test = false and --all-targets skips it; both CI clippy invocations with the sources touched first, plus cargo fmt --check; feature_naming 4/4; cargo test for rocm, e2e-cucumber, xtask and the remaining workspace crates.

Not run locally, stated rather than implied. The rocm-dash-tui tests — this machine denies the token-cache directory they create, and this branch touches no file in that crate; CI's Test (affected crates) and Coverage (rocm-dash crates, ratcheted) are both green on this commit. And scripts/therock_sdk_install_test.py, which still needs uv, a live index and a GPU host, as "Gaps, stated" already says.

CI on 92f363fa. All 16 required checks green, E2E tests, windows-build-and-test and Commit signatures + sign-off among them.

Advisory lanes: E2E tests (Strix Halo, Windows) and (Strix Halo, WSL2) green. E2E tests (Strix Halo, Ubuntu) failed at its "GPU preflight (bounded wait for an available GPU)" step, before any code from this branch ran; the same lane failed on each of the six most recent main commits, main's current tip included, so it is not something this PR introduces. E2E tests (MI300X), (MI350P) and (rad3 R9700) were still queued behind other runs on the same hardware at the time of writing.

The rebase changed no code that any of the four open review threads points at, so I have not added replies to them.

@siloteemu

Copy link
Copy Markdown

🔴 Automated review · pr-review-watcher · 92f363f

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.

Summary

Makes the ROCm compiler toolchain opt-in behind rocm install sdk --devel, threads that choice through version resolution, install composition, the runtime manifest, the prune grouping and two diagnostic surfaces, and covers it with unit tests plus per-PR and nightly scenarios — No blocking findings. Verified: read the naming guard's actual rules and ran only that focused test (4/4 green, including the renumbered scenario); the therock:: unit module was run (149 passed) and ran single-branch mutations in scratch copies — forcing the extras helper to drop the devel axis, and flattening the includes_devel() fallback, each reddened the test that claims to guard it; confirmed independently that resolution extras and install extras come from one helper and one include_devel value rather than two that merely agree today, that the runtime key hashes the package specs (so --devel really does yield a side-by-side runtime as the docs now say), that the prune axis cannot let a runtime escape pruning and that --keep stays live inside each bucket, that no workflow invokes the manual Python acceptance script, and that the tree has no conflict markers and no internal-content leaks. The full test suite, the e2e suite, and the GPU and Windows lanes were not run here. Checks at review start: 24 success, 1 failure, 2 pending; re-read at publication on the same head: 25 success, 1 failure, 1 pending. Blocking: 0 · Non-blocking: 5.

🚫 Blocking (must fix before merge)

None.

Non-blocking

  • apps/rocm/src/main.rs:32304, apps/rocm/src/main.rs:35501 — the runtime-only fixtures set devel: false and specs without the devel extra, so the neighbouring comment's "written through the recorded specs, which is what includes_devel actually reads" does not discriminate: swapping either call site to read manifest.devel directly keeps both tests green. Setting devel: true in the two fixtures makes the claim load-bearing; the semantic itself is already pinned by the contradictory-fixture test in therock.rs.
  • xtask/src/e2e_prewarm.rs:436 — doc comment still says "per channel/format/family" after the same flag's user-facing help gained the toolchain axis.
  • apps/rocm/src/main.rs:15350 — the natural-language dry-run path reads --devel via chat_cli_has_flag with no extracted seam or test, unlike the clap path which got sdk_install_request precisely to make that testable.
  • apps/rocm/src/therock.rs:2047 — unwrap_or(include_devel) is unreachable in practice, since the composition always emits a parseable rocm[...] spec first; harmless, but it reads as live logic.
  • Design point for the author: --devel over an existing plain install silently creates a second multi-gigabyte runtime and activates it. That is now stated in the README and the --help, but nothing says it at install time, which is where a surprised user actually is — a one-line notice before the download would close it.

Prior round

Discharged, verified by mechanism rather than by the author's account: the merge base now equals the base-branch tip, so the branch carries the base's therock-next-09 and renumbered its own to therock-next-10; the tree has no conflict markers; scenario indexes are sequential per feature and unique suite-wide; and reading the naming guard's own assertions shows it requires the @id: to carry the feature-key prefix only, never to match the display index — so the deliberately unrenumbered @id: is accepted, which the focused 4/4 green run confirms. Our earlier wording was not available verbatim while this review ran, so the prior objection is restated in substance rather than quoted.

@siloteemu siloteemu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Automated review · pr-review-watcher · 92f363f

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.

Summary

Makes the ROCm compiler toolchain opt-in behind rocm install sdk --devel, threads that choice through version resolution, install composition, the runtime manifest, the prune grouping and two diagnostic surfaces, and covers it with unit tests plus per-PR and nightly scenarios — No blocking findings. Verified: read the naming guard's actual rules and ran only that focused test (4/4 green, including the renumbered scenario); the therock:: unit module was run (149 passed) and ran single-branch mutations in scratch copies — forcing the extras helper to drop the devel axis, and flattening the includes_devel() fallback, each reddened the test that claims to guard it; confirmed independently that resolution extras and install extras come from one helper and one include_devel value rather than two that merely agree today, that the runtime key hashes the package specs (so --devel really does yield a side-by-side runtime as the docs now say), that the prune axis cannot let a runtime escape pruning and that --keep stays live inside each bucket, that no workflow invokes the manual Python acceptance script, and that the tree has no conflict markers and no internal-content leaks. The full test suite, the e2e suite, and the GPU and Windows lanes were not run here. Checks at review start: 24 success, 1 failure, 2 pending; re-read at publication on the same head: 25 success, 1 failure, 1 pending. Blocking: 0 · Non-blocking: 5.

🚫 Blocking (must fix before merge)

None.

Non-blocking

  • apps/rocm/src/main.rs:32304, apps/rocm/src/main.rs:35501 — the runtime-only fixtures set devel: false and specs without the devel extra, so the neighbouring comment's "written through the recorded specs, which is what includes_devel actually reads" does not discriminate: swapping either call site to read manifest.devel directly keeps both tests green. Setting devel: true in the two fixtures makes the claim load-bearing; the semantic itself is already pinned by the contradictory-fixture test in therock.rs.
  • xtask/src/e2e_prewarm.rs:436 — doc comment still says "per channel/format/family" after the same flag's user-facing help gained the toolchain axis.
  • apps/rocm/src/main.rs:15350 — the natural-language dry-run path reads --devel via chat_cli_has_flag with no extracted seam or test, unlike the clap path which got sdk_install_request precisely to make that testable.
  • apps/rocm/src/therock.rs:2047 — unwrap_or(include_devel) is unreachable in practice, since the composition always emits a parseable rocm[...] spec first; harmless, but it reads as live logic.
  • Design point for the author: --devel over an existing plain install silently creates a second multi-gigabyte runtime and activates it. That is now stated in the README and the --help, but nothing says it at install time, which is where a surprised user actually is — a one-line notice before the download would close it.

Prior round

Discharged, verified by mechanism rather than by the author's account: the merge base now equals the base-branch tip, so the branch carries the base's therock-next-09 and renumbered its own to therock-next-10; the tree has no conflict markers; scenario indexes are sequential per feature and unique suite-wide; and reading the naming guard's own assertions shows it requires the @id: to carry the feature-key prefix only, never to match the display index — so the deliberately unrenumbered @id: is accepted, which the focused 4/4 green run confirms. Our earlier wording was not available verbatim while this review ran, so the prior objection is restated in substance rather than quoted.

@juhovainio juhovainio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the full diff (all 18 files) and ran local verification against this exact commit.

This makes the ROCm compiler toolchain opt-in: rocm install sdk now installs rocm[libraries] by default instead of rocm[libraries,devel], with --devel bringing back the compiler/headers/static libs for cases like building vLLM from source or WSL HIP compilation. A --devel install is tracked as a distinct runtime rather than an in-place upgrade of a plain install.

What stood out as good engineering here:

  • A single therock_sdk_extras(include_devel) helper is the one source of truth for both what actually gets installed and what version resolution asks uv to resolve against. This closes a real bug: resolve_published_pip_package_versions previously hardcoded devel into version resolution regardless of what was actually being installed, which could break installs or let resolution and the install plan silently disagree. resolution_and_install_name_the_same_rocm_extras pins this.
  • Backward compatibility is handled carefully: a manifest with no recorded devel field defaults to true (every install before this PR had the toolchain), not false, avoiding a silent toolchain strip on someone's next update.
  • Retention/pruning now treats "has devel" as part of a runtime's identity, so a runtime-only install can no longer evict a full toolchain install of the same channel/format/family. There's a dedicated test for this.
  • The new e2e scenario for this change (therock-next-10) is deliberately not @nightly-gated, with an inline comment explaining why: gating it would let a hardcoded include_devel = false regression slip through every blocking check, since the unit tests only cover the standalone helpers, not what the actual install path forwards to them.

I didn't find any discrepancies between the PR description's claims and the actual code.

Local verification against this commit:

  • cargo test -p rocm --bin rocm full suite: 774 passed, 0 failed, 1 ignored (an intentional disk-fill test).
  • cargo clippy -p rocm --all-targets --all-features: clean.
  • CI is green except for "E2E tests (Strix Halo, Ubuntu)". I pulled that job's log directly: it's the same GPU-preflight VRAM-floor timeout as PR #337's failing lane (a stuck serve holding the GPU on the self-hosted runner), unrelated to this PR's code.

No findings to request changes on. Approving.

@rominf
rominf added this pull request to the merge queue Sep 24, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 24, 2026
@rominf
rominf added this pull request to the merge queue Sep 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 28, 2026
@rominf
rominf added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 42e6df9 Sep 29, 2026
28 of 29 checks passed
@rominf
rominf deleted the feat/devel-opt-in branch September 29, 2026 05:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Install the ROCm compiler toolchain only when asked (--devel)

4 participants