fix(install): always keep the SDK's build of the torch an engine pins - #314
Open
tomastola wants to merge 1 commit into
Open
fix(install): always keep the SDK's build of the torch an engine pins#314tomastola wants to merge 1 commit into
tomastola wants to merge 1 commit into
Conversation
Two installers write torch into the same managed runtime. `install sdk` writes TheRock's build; an engine install then writes the build from its own index, pinned to an exact version. Letting either side win outright is wrong, and both failures have been seen on real hardware: - With the engine's build, the runtime can hold a torch that loads against the installed SDK and then enumerates no devices. vLLM resolves no platform and serving dies with "Failed to infer device type", long after the install reported success. - With the SDK's build, the runtime can hold a torch *release* the engine does not accept, which breaks the engine a different way. The release and the build answer different questions, so take them from different places: the release from the engine, which was built against it, and the build from the SDK, which owns the libraries the runtime loads. The SDK's torch is read before the engine install, since that install is what overwrites it, and its build identifier is carried onto the release the engine pins. Where the SDK publishes no such build, the engine's own is kept rather than failing a multi-gigabyte install; where the install fails for any other reason the error is reported as it happened, since calling a network or permission failure a missing wheel sends the reader hunting for something that exists. This applies to every path that installs an engine into a managed runtime, not just the auto-install after `install sdk`. A standalone `rocm engines install <engine> --reinstall` is what someone reaches for when a runtime already looks wrong, so it above all must not be the thing that breaks it; it now re-settles torch and leaves the runtime usable. Environments rocm-cli does not own are left alone, and anyone wanting a different torch can still install it into the environment directly. Reporting is adjusted so the result is legible. A new device check asks the runtime whether torch can actually open a GPU, because a satisfied dependency check does not mean a usable environment -- the broken state reports `is_available() == True` with a device count of zero, which is why it stayed invisible until first serve. The check composes the runtime's recorded library paths, not `rocm_sdk.initialize_process()`: measured on a runtime that serves correctly, the former reports 8 devices and the latter 0, so a probe built on it would condemn healthy runtimes. The dependency check learns to tell a deliberate divergence from a real violation, since after alignment the engine's exact pin is unsatisfied by design and the reinstall remedy no longer applies. An install that leaves a runtime unable to open a device now fails instead of exiting 0, but only on that conjunction: an alignment that could not run may still leave a working environment, and a device count of zero is the right answer wherever no GPU is present. `install sdk` needs the distinction, because it deliberately downgrades engine auto-install failures to a warning so a failed engine install does not discard a good multi-gigabyte SDK -- which is still right, and still what happens for every other engine failure. It was catching this one too, so the single case where the install really did produce something broken kept reporting success, which is the reported symptom exactly. The error says the SDK itself installed fine and what to do next, so a transient index failure does not read as a ruined install. The nightly scenario covering a second SDK install moves with it. It asserted the wording of the dependency check, which a settled runtime no longer produces; it now asserts that the runtime can still open a GPU, which is the outcome it was always trying to protect, while still failing on any genuine unmet requirement. Verified end to end on MI300X: an unmodified `install sdk` realigns torch, reports the runtime usable with 8 devices, and serves a real completion; a subsequent `engines install --reinstall` re-settles it rather than breaking it. Signed-off-by: Tomas Saaristola <tsaarist@amd.com>
tomastola
requested review from
johnl-amd and
volen-silo
and removed request for
johnl-amd
August 26, 2026 14:37
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The symptom
A fresh
rocm install sdkon a gfx94X host produces a managed runtime that cannot serve. The install exits 0 and every health surface reportsready, but the first serve dies with:Earlier in the same log, more plainly:
Triton is installed but 0 active driver(s) found (expected 1).What is actually wrong
Two installers write torch into the same environment.
install sdkwrites TheRock's build; the engine install that follows writes the build from its own index, pinned to an exact version, replacing it:That torch imports cleanly against the installed SDK and then enumerates no devices, so vLLM resolves
UnspecifiedPlatformand refuses to start.Measured by swapping only torch — same environment, SDK, driver and vLLM:
device_count()2.11.0+gitd0c8b1f2.11.0+rocm7.13.0vLLM itself is fine. The served response carried
system_fingerprint: vllm-0.26.0-d391b761, soVLLM_PINNED_SPECdoes not need to change.Why neither obvious fix is correct
Both directions have been observed failing:
2.9.1+rocm7.14.0a…2.10.0+git8514f052.11.0+rocm7.13.02.11.0+gitd0c8b1fSo "the engine's pin always wins" and "the SDK's build always wins" each re-create the other bug. Pinning a different vLLM release is not an option either:
wheels.vllm.ai/rocm/<version>/publishes only therocm723ABI for 0.23.0 through 0.27.0, so working and broken environments alike run arocm723build. The variable is the torch each release pins, not the ABI tag.The rule
Take the torch release from the engine's pin, and the build from the SDK.
They answer different questions. The release is what the engine was built against; the build is what the installed libraries belong to. Applied to the table above, both rows resolve correctly, and the earlier fix's guarantee is preserved rather than reverted.
Where the SDK publishes no build of that release, the engine's own is kept rather than failing a multi-gigabyte install, and the device check below reports the outcome.
Why the build comes from the manifest, not the environment
This is worth calling out because the obvious implementation is wrong.
Reading "the SDK's torch" from the environment just before the engine install seems natural, and it works — exactly once. On any later install the environment already holds the engine's build, so that reading mistakes it for the SDK's, concludes the runtime is already correct, and leaves it broken permanently. Repeat installs are the common case, not the edge case: a refresh, a re-run, or a user reaching for
engines install --reinstallbecause something already looks wrong.The SDK's torch is therefore recorded in the runtime manifest at install time and read back from there. Manifests written before this change fall back to deriving the build from the recorded SDK version. Both paths are covered by tests, and the fallback was exercised end to end against a runtime that was already stuck on the engine's build — it recovered.
Why a device check, and why it looks the way it does
A satisfied dependency check does not mean a usable environment. In the broken state the engine's requirements are fully satisfied — its own pinned torch is installed — and the runtime still cannot serve. That is why this went unnoticed until first serve.
Two details matter:
device_count(), notis_available(). The broken runtime reportsis_available() == Truewith a device count of zero. Only the count distinguishes it.rocm_sdk.initialize_process(). Measured on a runtime that serves correctly: with the SDK library directories on the loader path torch reports 8 devices; withinitialize_process()alone it reports 0. A probe built on the latter would condemn healthy runtimes and send people to reinstall — the operation that breaks them.Why an install can now fail, and only then
If torch could not be settled and the runtime cannot open a device, the install fails instead of exiting 0.
The conjunction is deliberate. An alignment that could not run may still leave a working environment, and a device count of zero is the correct answer wherever no GPU is present — neither alone justifies failing a large install. Together they mean this install produced something that cannot serve, and reporting success for that is the original defect.
The error says the SDK itself installed fine and what to do next, so a transient index failure does not read as a ruined install.
This applies to
install sdktoo, which needed care. That command deliberately catches engine auto-install failures and downgrades them to a warning, so that a failed engine install does not throw away a multi-gigabyte SDK that installed correctly — and that is still right. But it was catching this failure as well, so the one case where the install really did produce something broken kept exiting 0, which is the reported symptom verbatim. The unusable-runtime failure is now a distinct error type, and only that one is propagated; every other engine failure still warns and keeps the successful exit. The SDK's own audit event is recorded before the command fails, because the SDK install did complete.Why failures are not all reported the same way
An earlier draft mapped every realignment failure to "the index has no such build". That is a claim about the index, and it would have been made over DNS failures, permission errors and unreachable networks — sending the reader hunting for a wheel that exists. Unavailability is now claimed only when the resolver actually said so; everything else reports the error verbatim. The classifier is deliberately narrow, so an unrecognised message degrades to the honest answer rather than a confident wrong one.
Why every install path, not just the one
The alignment runs wherever an engine is installed into a managed runtime: the auto-install after
install sdk,rocm engines install, the lazy install before serving, and engine environment resolution. Covering only the first leavesrocm engines install <engine> --reinstallable to re-break a runtime — and that is precisely the command someone runs when a runtime already looks wrong.Environments rocm-cli does not own are left untouched, and anyone who wants a different torch can still install it into the environment directly.
What the output looks like
The divergence is real and intended: the engine's exact pin cannot express "same release, the SDK's build". It is reported as a divergence rather than a violation specifically so the remedy for a genuine violation — reinstalling the engine — is not suggested here. Any unmet requirement on any other package still reports as a violation.
How this was verified
On real hardware (MI300X, gfx942, 8 devices)
The root cause was established by a single-variable swap rather than inference: same environment, same SDK, same driver, same vLLM, changing only which torch was installed. That is the table under "What is actually wrong" — 0 devices and a failed serve on one, 8 devices and a real completion on the other.
Each of these was then run end to end against real GPUs:
install sdkon a clean runtimedevice_check: usable (8 devices), serves a real completionengines install --reinstallafterwardsrocm engines installThe third row matters because it is the state real machines are already in — the fix has to repair them, not just avoid creating new ones.
Unit tests
18 tests, covering the decision surface rather than one happy path:
That last one is worth explaining.
install sdknow takes its engine auto-install as a parameter, so a test can drive both outcomes and assert what the command actually returns, without a multi-gigabyte install behind it. One test pins that an unusable runtime fails it (and that the SDK's own audit record is still written first), one that an ordinary engine-install failure does not, and one that the distinction survives being wrapped in context on the way up — flattening it into a plain message is how this would quietly regress.To confirm that test is load-bearing rather than merely green, the catch was reverted to warning-and-continue and the test was observed to fail. A regression test that cannot fail is decoration.
End-to-end
The nightly scenario covering a second SDK install asserted the wording of the dependency check, which a settled runtime no longer produces. It now asserts the runtime can still open a GPU — the outcome it was always protecting — while still failing on any genuine unmet requirement. That is a stronger assertion than the one it replaces: it is functional rather than a string match, and it is the property that actually broke here.
Static checks
cargo test --workspace --all-targets(500 passing in the CLI crate),cargo clippy --locked --workspace --all-targets -- -D warnings,cargo fmt --check, and the pre-commit hook set all clean.What is not covered
The
install sdkexit code is covered by test rather than on hardware. Reproducing it end to end needs the index reachable for the SDK download and unreachable for the realignment immediately after, and both use the same index — so the honest statement is that the code path is unit-tested and the equivalent path throughrocm engines installwas confirmed on hardware.Known limitation
Only
torchis realigned.torchvision,torchaudioandtritonare left as the engine's install leaves them. That combination served correctly in testing, but the SDK resolves those four as a coordinated set, so the mixed stack has not been validated against the full matrix of supported combinations. Worth a follow-up if the engine's pins and the SDK's ever diverge further than a build identifier.tests/e2e-cucumber/expectations.tomlfor the fixed ticket ID and removed/narrowed any now-stale xfail rows. (No xfail row covered this; the affected scenario is a passing GPU scenario that this PR strengthens.)