fix(install): restore the engine's pinned deps after an SDK install - #264
Conversation
The managed runtime venv has two owners. `rocm install sdk` writes TheRock's torch stack into it, and the vLLM engine installs into the same interpreter, pinning a torch build from its own index that no TheRock build satisfies. A second `install sdk` therefore wrote the SDK's torch back over the engine's pin. The auto-install that runs afterwards should have restored it, but its short-circuit tested whether vLLM resolved rather than whether vLLM's requirements were met. Only torch had moved, so vLLM still resolved and the pin was never re-asserted. Nothing downstream noticed: runtime validation checks paths and the rocm_sdk probe, so every surface kept reporting the runtime ready, and the first symptom was a serve failure whose traceback named neither torch nor the SDK. Gate the short-circuit on the engine's own `Requires-Dist` instead, via a `uv pip check` primitive in rocm-core, so a violated pin falls through to an install that restores it. The repair targets the interpreter that was assessed, which is not always the one the other resolver would pick. The check runs with colour disabled because uv colourises its findings under FORCE_COLOR even into a pipe, and an escape prefix would read as "could not verify" and silently restore the old behaviour. `install sdk` now states the outcome as `dependency_check:` and records it in the audit log, on every auto-install outcome including a failed one, where a violated pin is most likely. It stays exit-0 when a violation survives: the SDK installed correctly, and the warning names the one command that clears it. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
42459aa to
596c5be
Compare
tomastola
left a comment
There was a problem hiding this comment.
I reported this one, so treating it mostly as: does this close the failure I hit.
The root cause matches. The short-circuit testing "does vLLM resolve" rather than "are vLLM's requirements met" is exactly the gap — the second install sdk moved only torch, so vLLM kept resolving and the install pass that would have re-pinned it never ran.
I checked the part the fix hinges on, since it isn't obvious from reading: install_vllm_with_uv runs a plain uv pip install vllm==<pin> on the repair path (reinstall is false there), which looks like a no-op given vLLM is already present. It isn't — uv's satisfaction check is recursive over the dependency closure, so a violated transitive pin does get restored. Worth a word in the code comment; a future reader will very reasonably assume that call does nothing and "simplify" the repair into a real no-op.
A few things before I'd sign off.
1. The printed remedy skips the safeguard this PR just added. On a violation the tool prints action: rocm engines install {engine} --reinstall. But reinstall: true forces resolved = None in install_response, so assessed is None, and the install falls back to resolve_managed_runtime_python — candidates.into_iter().next(), first candidate unconditionally, under prefix matching in runtime_matches. That's the resolver the comment above assessed says must be avoided, for the reason given there: "installing into a different interpreter than the one found broken would leave the broken one broken and report it fixed." The printed command also carries no runtime scoping, so it resolves the default candidate.
Since install sdk deliberately exits 0 on a surviving violation, this line is the user's only recourse — on the multi-candidate hosts the comment says routinely occur, following it can repair a healthy environment and report success. Either thread assessed through the reinstall: true path too, or scope the printed remedy to the runtime that was checked. (The string predates this PR, but this is the first place the tool actively directs users to it in a situation where the distinction is known to matter.)
2. The coverage note undersells the gap. The description says the report is wired to the SDK auto-install path only, so a host where vLLM "was installed by hand" is unprotected. Accurate, but maybe_auto_install_sdk_preferred_engine early-returns whenever there is no preferred engine, and preferred_serve_engine_for_therock_family returns vllm only for families ending -dcgpu or in ["gfx906","gfx908","gfx90a"], never on native Windows. So none of the new behaviour runs on consumer/RDNA families — gfx1201 normalizes to gfx120X-all and matches neither condition, including on this project's own non-dcgpu E2E lanes. Fine as a scope boundary, but one sentence naming it would stop reviewers from reading "installed by hand" as a corner case.
3. The decision the PR changes has no coverage in default CI. The new unit tests all exercise pure helpers — rendering, parse_dependency_violations, repair_from_violations, assessed_python_for_repair. None drives the install_response match that is the actual fix, and check_dependencies' branching (exit-0 vs non-zero-with-findings vs non-zero-empty) is untested. With the scenario tagged @nightly @requires-gpu, nothing in the PR lane covers the gate. ROCM_CLI_UV_BINARY already exists — a stub emitting canned stderr and exit codes would cover all three branches and the gate itself, no GPU needed. Given the parser is acknowledged as brittle and uv_version() defaults to latest, that seems worth having.
4. assert_engine_still_ready can't fail. It asserts engines list contains runtime: ready — the signal the description identifies as staying green while the runtime was broken, and whose repair is explicitly deferred. It would have passed before this change. Not harmful, but it reads as corroboration the scenario doesn't provide; the dependency_check: satisfied assertion above it is doing all the work.
Two things that check out, since they're easy to doubt: the colorized path does degrade safely — a colorized finding parses empty, the exit is non-zero, so check_dependencies bails and it surfaces as not_verified, never a false satisfied. And requiring both halves of the frame to exclude the broken-WHEEL / Requires-Python / multiple-distribution shapes is right; none of those is fixable by reinstalling the requirer.
On CI: the red E2E tests (Strix Halo, Windows) looks pre-existing rather than yours — it fails on CURL code: 35 fetching llama-server.exe from the lemonade-sdk release, and the same lane fails identically on a same-day Dependabot PR that only bumps action SHAs. The two CANCELLED jobs stalled inside actions/checkout and hit the job time cap; windows-build-and-test did run cargo test --workspace --all-targets and smoke_local.py green, so the suite isn't actually unrun.
Not approving yet only because the original reproduction hasn't been run on gfx94X — you flag that yourself. Once the install sdk → install sdk → serve sequence has been through on that hardware, and (1) is resolved either way, this looks good to me.
tomastola
left a comment
There was a problem hiding this comment.
Ran the reproduction on a gfx942 (MI300X) host, which closes the gap noted as unverified in the description. Three passes against one runtime (release-wheel-gfx94x-dcgpu-7-13-0), the first two on the merge base and the third on this branch.
Baseline, first install sdk — engine auto-install selects vLLM (reason: detected ROCm GPU family prefers vllm (gfx94X-dcgpu)), environment lands on vLLM's pins with torch 2.11.0+gitd0c8b1f.
Baseline, second install sdk — the bug, verbatim:
torch 2.11.0+gitd0c8b1f -> 2.11.0+rocm7.13.0
Found 3 incompatibilities
The package `vllm` requires `torch==2.11.0+gitd0c8b1f`, but `2.11.0+rocm7.13.0` is installed
The package `vllm` requires `torchvision==0.24.1+d801a34`, but `0.26.0+rocm7.13.0` is installed
The package `vllm` requires `torchaudio==2.9.0+eaa9e4e`, but `2.11.0+rocm7.13.0` is installed
reinstall: false, exit 0, no dependency warning. Silently unusable runtime.
This branch, install sdk against that same environment — the gate fires, the install runs, every pin is restored:
warning: the runtime environment did not satisfy vLLM's pinned dependencies; vLLM was reinstalled to restore them (...)
warning: if this recurs after `rocm install sdk`, the SDK torch stack is being written over vLLM's pinned torch
dependency_check: satisfied
torch 2.11.0+gitd0c8b1f · torchvision 0.24.1+d801a34 · torchaudio 2.9.0+eaa9e4e
All installed packages are compatible
Worth noting that pass covers both cases at once: install sdk overwrote the torch stack again on the way in, and the gate caught and repaired it — so it exercises prevention on a repeat SDK install as well as healing an already-broken environment. It also confirms the point I raised earlier about the plain uv pip install vllm==<pin> not being a no-op: the recursive satisfaction check restored all three distributions, not just the one.
Two things from the run:
It is three packages, not one. torchvision and torchaudio are violated alongside torch, and every test and example in the PR shows only torch. The repair handles all three, but the violated-case note concatenates them into a single ~380-character warning line, which is unpleasant in a terminal. The violated branch of render_engine_dependency_check already prints one violation: line per finding — worth doing the same for the engine-side note rather than joining on ; . Reasonable to widen a test fixture to a multi-violation body too, since that is what the real failure looks like.
Not covered by this run: I did not start a server, so this establishes dependency consistency, not that the runtime serves.
Approving on that basis. My earlier point about the printed remedy — rocm engines install {engine} --reinstall routing through resolve_managed_runtime_python rather than the assessed interpreter — still stands and I would not want it lost, but it is advisory output on the path where the automatic repair has already failed, and the string predates this PR, so it does not seem worth holding a verified fix for. Happy to see it as a follow-up.
Review follow-ups, all three from the same reading of the repair path. A forced reinstall skipped resolution entirely, so no environment was ever assessed and the install fell back to the first prefix-matching candidate. On the multi-candidate hosts that fallback exists for, `--reinstall` could rebuild a healthy environment and report success while the broken one stayed broken — the exact failure the assessed-interpreter comment warns about, reachable through the remedy the tool prints. Resolution now always runs; only the short-circuit is gated on `reinstall`. Report one finding per violated pin. The real failure moves torch, torchvision and torchaudio together, so joining them produced a single ~380-character line no terminal shows usefully. This matches the per-finding lines the CLI-side renderer already emits, and the new test uses a real three-package body rather than the single-pin fixture. Drop the "engine is still ready to serve" assertion. It checked that `engines list` reports ready — the surface that stays green while the runtime is broken, which is what this scenario exists to catch — so it would have passed before the fix as readily as after. The remaining dependency-check assertion is the falsifiable one; the precondition helper keeps its use and now records why it has no Then counterpart. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
|
Pushed 62f6d73 addressing the three points from your review. The approval survives ( 1. The printed remedy no longer routes to the wrong interpreter. Taken the first of your two options — threading the assessed environment through the forced-reinstall path — rather than only rescoping the printed string, since that fixes the behaviour instead of the advice about it. 2. One note per violated pin. Your run is the fixture — the new test uses the real three-package body (torch, torchvision, torchaudio) instead of the single-pin one, asserts each finding gets its own note, and guards the line length so a future change cannot quietly re-join them. 3. Dropped Also took your point about the description underselling the coverage gap: it now names the family gating explicitly rather than leaving it as "installed by hand". Not done, deliberately: the
|
Summary
rocm install sdkcould leave the managed runtime holding a torch build the installed vLLM engine does not accept, while every status surface still reported the runtimeready. The first symptom a user gets is a serve failure whose traceback names neither torch nor the SDK.Root cause. The runtime venv has two owners.
install sdkwrites TheRock's torch stack into it, and the vLLM engine installs into the same interpreter, pinning a torch build from its own index. A secondinstall sdkwrites the SDK's torch back over that pin. The engine auto-install that runs immediately afterwards should have restored it, but its short-circuit tested whether vLLM resolved rather than whether vLLM's requirements were met — and since only torch had moved, vLLM still resolved, so the pin was never re-asserted.The two torch builds cannot be reconciled by picking differently on the SDK side: the engine pins an exact local version (
+git<sha>) that no TheRock build satisfies under PEP 440. The engine deliberately wins; the bug is that the SDK silently un-wins it.What changed
rocm-coreoveruv pip check: report the unsatisfiedRequires-Distentries in an environment, filtered to the distribution that declares them.rocm engines install vllmself-heal too, and leavesreinstall: falsemeaning "don't redownload a healthy engine".install sdkstates the outcome asdependency_check: satisfied | violated | not_verifiedand records it in the audit log — on every auto-install outcome, including a failed one, which is where a violated pin is most likely.Non-obvious decisions
--color never.uvcolourises its findings underFORCE_COLOR/CLICOLOR_FORCEeven when stderr is a pipe. The escape prefix pushes every finding past the parser, which reads as "could not verify" and silently restores the old behaviour — i.e. the fix would quietly do nothing on such a host. There is a regression test for the colourised line specifically.uv pip checkreports several other conditions under the sameThe package \x`opening — a missingWHEEL/METADATAfile, an unsatisfiedRequires-Python, multiple installed distributions. None is aRequires-Dist` that reinstalling the requirer can clear, so accepting them would force a futile multi-gigabyte reinstall and print a remedy that cannot help.install sdkstays exit-0 when a violation survives. The SDK itself installed correctly, and abandoning a multi-gigabyte install costs the user more than the warning, which names the one command that clears it. Say so if you'd rather it were fatal.Scope boundaries
Deliberately not included: teaching
examine/runtimes list/engines listto stop reporting a runtime with a violated engine pin asready. That is tracked separately; the primitive added here is what that work will build on.Also known and not addressed here: the report is wired into the SDK auto-install path only. That path early-returns when there is no preferred engine, and vLLM is preferred only for
-dcgpufamilies andgfx906/gfx908/gfx90a, never on native Windows — so none of this behaviour runs on consumer/RDNA hardware, including this project's own non-dcgpu E2E lanes. A host outside those families that installed vLLM by hand still gets the overwrite with no repair and no report. Happy to fold that in if reviewers would rather have it in one change.Test plan
uv pip checkbodies, the clean body, a colourised line, the four other diagnostic shapes that share the opening frame), the repair predicate, the assessed-interpreter selection, and each rendereddependency_checkoutcome.@id:runtime-sdk-reinstall-keeps-engine-consistentcovering a secondinstall sdkon a runtime that already has the engine. Tagged@requires-gpu @requires-engine:vllm @nightly: it needs a real SDK install, a real engine install and a second SDK install, so it cannot run in default CI — it runs on the nightly GPU lane. vLLM is gated because only it shares the runtime environment; Lemonade manages its own.cargo test --workspace,cargo clippy --workspace --all-targets,cargo fmt,prekall clean locally.uvbehaviours the change depends on were probed directly against uv 0.9.30 rather than assumed: the stdout/stderr split and exit codes, all five line shapes, that--color neverdefeatsFORCE_COLOR, and that a plainuv pip install <pkg>==<pin>does restore a dependency that was downgraded underneath it (uv's satisfaction fast-path is recursive over the closure, so the repair is not a no-op).Not verified here: the original reproduction needs a gfx94X host. The
install sdk→install sdk→ compare-against-Requires-Distsequence, the follow-up serve, and the new scenario all still need a run on that hardware.Risk
Medium. On a healthy host the short-circuit behaves exactly as before, so
install sdkis unchanged; only an already-violated environment takes the new path. The real exposure is that a violated pin now always triggers auvresolve where previously nothing ran — on a host where that resolve fails, a repeatinstall sdkthat used to be quiet will now surface the engine-install failure. That failure is reported rather than swallowed, which is the intended direction, but it is a visible behaviour change.