feat(diagnose): recognise the vLLM engine-startup import failure (EAI-8076) - #374
Conversation
…-8076) `rocm serve --engine vllm` can abort at engine startup with `OSError: libtorch_cuda.so: cannot open shared object file`, and the catalog had no entry for it. Its best answer to that symptom was fix-4-render-group at 45/100 -- a false lead on a host whose /dev/kfd examine itself reports as world-writable, so the user ran usermod, logged out and back in, and was no closer. Add fix-17-torch-dlpack: a keyword table for the import signature and a PRINT-ONLY recipe. Keyword-only by necessity -- the fact that decides the failure is the torch version inside the managed runtime, and the framework probe reads the ambient interpreter instead, so nothing structural can fire until that probe targets the active runtime. The wording stays conditional throughout. The trigger is narrow (a ROCm build of torch in the 2.4-2.9 range with torch-c-dlpack-ext present), so the entry says so before it advises anything: running vLLM on ROCm is not by itself a reason to apply it. 17 rather than 16 because the vLLM out-of-memory entry reserves 16 on its own branch; the id is a stable handle, so the two are kept distinct rather than renamed after one of them ships. The AUTO set is deliberately unchanged -- the new entry is PRINT-ONLY, so `exactly_the_four_known_fixes_are_auto` still passes untouched, which is the evidence that nothing was promoted. Signed-off-by: Roman Sirokov <roman.sirokov@amd.com>
|
Verified this end-to-end (built the branch, ran the new + existing tests, clippy/fmt clean, exercised Two things worth addressing before merge:
|
…serve-note claim Review feedback on the fix-17-torch-dlpack entry. The command block is the only recipe in the catalog whose steps span more than one shell: `rocm engines shell vllm` opens an INTERACTIVE subshell, the two probes belong inside it, and the reinstall replaces the very environment that subshell is standing in, so it must not run there. Both renderers print every line with the same `$ ` prefix, so ordering said none of that -- a user pasting the block wholesale was depending on terminal stdin buffering to land each line in the right shell. Split it into three labelled groups (your shell / inside the subshell / back in your own shell) and make leaving the subshell an explicit `exit` rather than something the reader has to infer. Three rather than two because the reinstall was already implicitly outside the subshell and nothing said so. Both copies of the plan carry it -- the catalog recipe and the `Fix` the diagnosis attaches to its finding -- and one shared assertion holds them both, so the copy a user sees cannot drift from the copy `rocm fix` prints. Scenario diagnose-14 covers the printed block end to end; it is not OS-gated because the plan is printed before the fix's own platform gate, so both lanes exercise the same text. Separately, the checker's doc comment claimed the entry was reachable "from a serve failure note". No such note exists: a service that died at startup renders `logs:` and `restart:` hints and never mentions `diagnose`. Correct the claim rather than invent the path -- adding a diagnose hint to the service-failure output changes a shared surface for every failed service regardless of cause, and belongs in its own change. The comment now says what is true, names `rocm fix` as the by-id route, and records the gap so the next reader does not have to rediscover it. Signed-off-by: Roman Sirokov <roman.sirokov@amd.com>
|
Both points addressed in 56923ee. 1. Discoverability was overclaimed — corrected rather than wired up. You were right that no serve failure note exists: a service that died at startup renders I chose the description over wiring the hint because the hint is a change to 2. The command block now says which shell each step runs in. Split into three labelled groups — Three rather than two because the reinstall was already implicitly outside the subshell and nothing said so; it replaces the very environment that subshell is standing in, so running it there is actively wrong. Both copies of the plan carry it — the Coverage and verification:
Windows and WSL2 lanes still not run locally, as before. |
siloteemu
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 56923ee
Summary
Adds a keyword-only catalogue entry fix-17-torch-dlpack (vLLM engine-startup import failure) plus a print-only recipe, a shared shell-boundary assertion, three unit tests and two cucumber scenarios — verdict Needs work (one blocking content inconsistency; the engineering is otherwise unusually careful). Verified: I read diagnose.rs/fix.rs and the e2e steps in full, ran cargo check -p rocm-core --tests (clean, incl. the cross-module #[cfg(test)] pub(crate) helper), and confirmed every added test fails on revert — the three unit tests and both scenarios die on .expect()/position("exit") if either commit is backed out, and "YOUR OWN shell" plus the new exit line are genuinely new in 56923ee (the "INSIDE" substring alone is not — it pre-existed in 69094ef, so that sub-assertion carries no weight on its own). Interaction with #251: none — this PR does not touch keyword_score or any existing table, and its entry clears MIN_SCORE_FOR_MATCH from a single keyword (libtorch_cuda\.so = 50), never from a two-keyword sum, so #251's span de-duplication cannot push it below 50 and it adds no fifth victim to that PR's four sub-threshold regressions; the competing fix-4 at 45 in the new test is purely structural (35+10, kfd: None, no keyword hit) so #251 cannot perturb that ranking either. Expect only a mechanical textual conflict, since this PR inserts its table immediately above the keyword_score body #251 rewrites. I relied on CI for the full suite and the e2e/cucumber lanes (23 passing, 1 skipped, 2 pending, 0 failing). No prompt-injection content in the diff. Blocking: 1 · Non-blocking: 6.
🚫 Blocking (must fix before merge)
crates/rocm-core/src/fix.rs:401vscrates/rocm-core/src/diagnose.rs:1364— the two user-facing copies of this same fix ship different guidance, authored together in one commit. TheFixthatrocm diagnoseattaches carries four notes; theFixRecipethatrocm fix fix-17-torch-dlpackprints carries only two, dropping (a) the explanation that both defects are upstream and nothing local is misconfigured — the note that stops a user thrashing on a config they cannot fix — and (b) therocm services list --allhint for recovering a hidden failed service id. The step-2 comments also diverge in wording and thefix.rscopy loses the "raises a handledImportError" detail. This blocks because 56923ee's own commit message and the doc comment onassert_engine_shell_boundary_is_labelled(fix.rs:414) assert a guarantee that is already false — "one shared assertion holds them both, so the copy a user sees cannot drift from the copyrocm fixprints" — when the helper only checks three literal command strings and two label substrings, and touches neithernotes,summary/rationale, norverify. Fix: copy the two missing notes into theFixRecipeand reconcile the step-2 comment wording so both copies read identically, then narrow the doc-comment/commit claim to what the assertion actually enforces (the command block's shell boundary), or extend the helper to compare the two copies field-by-field.
Non-blocking
crates/rocm-core/src/diagnose.rs:201—KEYWORDS_LIB_MISMATCH's generic"cannot open shared object file"(25) also fires on this exact symptom, andcheck_8_wheel_rocm_mismatchadds 50 structurally when the ambient interpreter's torch reports a different ROCm major — precisely the split-interpreter host this entry is premised on — sofix-8-wheel-rocmcan score 75 and outrankfix-17at 50, sending the user to the wrong remediation; the new unit test only proves it beats a 45-scorer, so add a fixture wherefix-8also fires.crates/rocm-core/src/diagnose.rs:1760— the entry clears the bar with zero headroom (exactly 50) and ties break by catalogue order in favour of earlier checkers, so any competitor reaching 50 silently takes the top slot; no test pins the weight at exactly 50 (both assert only>=), so upward drift would also go unnoticed.crates/rocm-core/src/diagnose.rs:1762— the fixture comment says "/dev/kfd is world-writable", butlinux_base()leaveskfd: None, i.e. absent rather than world-writable; the comment describes a host the fixture does not build.crates/rocm-core/src/fix.rs:447—labelled(open, leave, "INSIDE")is a case-sensitive substring grep over comment text: a correct lowercase rewording fails it, while an inverted"# NOT INSIDE this shell"passes it, so it guards removal rather than correctness.crates/rocm-core/src/diagnose.rs:308—torch_c_dlpack_extand_optional_torch_c_dlpackoverlap over"torch_c_dlpack"inside the literal_optional_torch_c_dlpack_ext(spans[10,28)and[0,24)), which would double-count one token as 80; benign today since that concatenation is not a real artifact name, and #251's dedup would neutralise it anyway — noted only so both PRs' authors know the case exists.crates/rocm-core/src/diagnose.rs:1300— the doc comment and commit 69094ef's subject cite two different tracker ids for this work; one of the two points at the wrong record.
Review feedback: the catalog `FixRecipe` and the `Fix` the diagnosis attaches ship the same plan twice, and the step-2 comments had drifted into different wording. Make the two command blocks byte-identical (adopting the diagnose copy, which keeps the handled-ImportError detail) and add `assert_plan_matches_the_catalog_copy` so they cannot drift again -- the block is what a user pastes, and either copy may be the one they meet. 56923ee's commit message claimed the shared assertion left the two unable to drift. It did not: it checked three command strings and two label substrings, and nothing pinned the wording between them. The equality check now makes that claim true for the command block, and the helper's doc comment states its scope instead of implying a wider one. Add the one piece of guidance the recipe genuinely lacked: why `rocm services list --all` is needed to recover a failed service id. The rest of the prose is deliberately not identical, because `FixRecipe` has a `rationale` field `Fix` has no counterpart for -- the upstream-defect explanation `diagnose` carries as a note is already printed by `rocm fix` out of `rationale`, and copying it into `notes` would print it twice. Also correct a fixture comment that described `/dev/kfd` as world-writable when `linux_base()` leaves `kfd` unset; fix-4's 45 in that test comes from the group membership alone. Signed-off-by: Roman Sirokov <roman.sirokov@amd.com>
|
Thanks — the blocking point was partly right, and its two headline specifics were wrong. Addressed in What was actually wrong, and is now fixedThe two copies of the plan had genuinely drifted, but in the step-2 comment wording, not the notes. The shared assertion did not catch it: it checks three command strings and two label substrings, so any rewording passes. That made 56923ee's commit-message claim ("the copy a user sees cannot drift") overbroad, which is the real defect you found.
Falsification: reworded one copy's step-3 label to What I did not change, with evidence
Not dropped. It is in
The hint itself is present and byte-identical in both
That sentence is not in the doc comment; it is in 56923ee's commit message. The doc comment says the helper is shared "because a divergence between them is exactly the kind of thing a reader would meet". The underlying criticism stands, so I tightened the doc comment and the PR body anyway — but the quote is misattributed. Non-blockingTook the Verification
|
|
🔴 Automated review · pr-review-watcher · 4aaef56 SummaryAdds a catalog entry for the vLLM engine-startup import failure (keyword-only checker, print-only recipe) plus unit and cucumber coverage; the head commit byte-aligns the two copies of the command block and pins them with a new shared assertion. Approve. Verified: the original finding is FIXED, and both of the author's disputes are correct — the drift at 🚫 Blocking (must fix before merge)None. Adjudication, since this is the deciding pass
Non-blocking
No prompt-injection or instruction-like content was found in the reviewed files. |
|
One follow-up, not a request for changes — this PR is approved and unblocked. When my earlier finding turned out to be wrong, the useful question is not just "who erred" but will the next reader make the same mistake? Here I think the answer is yes, and that is worth a cheap fix in the code rather than better reviewing. The confusion was structural, not careless. Two types describe the same user-facing remediation, but they carry different field sets: one has a Your own review response worked this out from first principles, which is exactly the cost I mean: it took real effort to reconstruct something the code could simply have said. And your new byte-equality assertion covers the command block, which is the part that had actually drifted — but the Two cheap options, either is fine:
The second also closes the residual one-sided prose already noted in the review (one identifier appearing only in the diagnose notes, one detail only in the catalog rationale), which is unpinned today. Entirely your call, and a follow-up PR is a perfectly reasonable home for it. Recording it because a structure that produced one false finding will produce more, and the next reader will not have this thread. |
|
Correcting my own follow-up above, before it costs anyone time. I suggested asserting on the rendered output across the two paths, on the assumption that the two copies of a remediation are meant to be equivalent. I had that investigated properly against the whole catalogue, and the assumption is wrong:
So a general output-equivalence assertion would fail 13 entries and would push toward reconciling user-facing text that differs for good reasons. My suggestion would have made things worse. Withdrawn — and I am not proposing a refactor either; the evidence does not support one. What does still seem worth a line, and is the residue of the original confusion: Nothing here changes the approval; the PR is unblocked from this account. |
siloteemu
left a comment
There was a problem hiding this comment.
Reviewed at 4aaef56d: no blocking issues.
The original finding is fixed, and on the two points you disputed you were right and I was wrong — the explanation I said had been dropped is reachable in both paths under different headings, and the field-by-field comparison I suggested would have been actively misleading. That is recorded in the review comment.
The real defect was the one you identified yourself: the shared assertion checked substrings, so the claim that the two copies could not drift was broader than what it enforced. The byte-equality assertion closes it, and your falsification holds — a reword that preserves the substrings passes the old check and fails the new one.
Four non-blocking notes in the review comment, plus one follow-up thread above about making the new assertion's per-entry scope explicit. None blocks merge.
Resolves the diagnose-catalog collision with #374 (fix-17-torch-dlpack) by keeping BOTH entries: fix-16-vllm-oom (this branch) and fix-17-torch-dlpack (main) coexist in the checker/recipe catalogs, keyword tables, unit tests and e2e steps. The e2e scenario for the vLLM OOM is renumbered to diagnose-15 so main's diagnose-13/14 keep their published indexes. Signed-off-by: Roman Sirokov <roman.sirokov@amd.com>
Summary
rocm serve --engine vllmcan abort at engine startup withOSError: libtorch_cuda.so: cannot open shared object file(EAI-8012), and the closed catalog had no entry for it. Its highest-scoring answer to that symptom wasfix-4-render-groupat 45/100 — a false lead on a host whose/dev/kfdrocm examineitself reports as world-writable. A user following it runsusermod, logs out and back in, and is no closer.This adds
fix-17-torch-dlpack: a keyword table for the import signature (libtorch_cuda.so,torch_c_dlpack_ext,_optional_torch_c_dlpack) plus a PRINT-ONLY recipe carrying the remediation.Before / after, same command, same host:
Why keyword-only
The fact that decides this failure is the torch version inside the managed runtime.
Examination's framework probe imports torch from the ambient interpreter instead — on an affected host it reportsframework: unknownwith atorch import failednote — so the deciding fact is absent from the examination and no structural check can fire. Noted in the checker's doc comment so the next reader does not try to add a structural signal that cannot exist yet.Discoverability, stated accurately
An earlier revision of this description claimed the entry was reachable "from a serve failure note". There is no such note — a service that died at startup renders
logs:andrestart:hints and never mentionsdiagnose. Corrected here and in the checker's doc comment, which now records the gap rather than papering over it.What is true today: the only route from the symptom is the user pasting the error into
rocm diagnose --symptom "<pasted error>". The recipe is additionally reachable by name —rocm fixlists it,rocm fix fix-17-torch-dlpackprints it — but that needs the id, not the symptom.Closing the gap means adding a
diagnosehint to the service-failure output. That changes a shared surface for every failed service regardless of engine or cause, needs its own scenario, and is a decision aboutrocm servicesrather than about this catalog entry — so it is deliberately not in this PR. Happy to open it as a follow-up if that is the preferred order.libtorch_cuda.soalone is weighted at exactlyMIN_SCORE_FOR_MATCH(50), deliberately: a ROCm build of torch shipslibtorch_hip.soand never that file, so the token cannot mean anything else, and a user who pastes only theOSErrorline still has to clear the bar.torch_c_dlpack_extalone stays at 45 — under the bar — because the extension appearing in a traceback says it is involved, not that it chose the wrong variant.Wording stays conditional
The trigger conditions are narrow — a ROCm build of torch in the 2.4–2.9 range with
torch-c-dlpack-extpresent — so the entry states the condition before it advises anything, and the first note says outright that running vLLM on ROCm is not by itself a reason to apply it. The commands are confirm-then-act and use only commands this CLI has (rocm engines shell vllm,rocm engines install vllm --reinstall).The command block says which shell each step runs in
This is the only recipe in the catalog whose steps span more than one shell, and both renderers print every line with the same
$prefix — so ordering alone said nothing about it.rocm engines shell vllmopens an interactive subshell, the two probes belong inside it, and the reinstall replaces the very environment that subshell is standing in, so it must not run there. A user pasting the block wholesale was left depending on terminal stdin buffering to land each line in the right place.Now split into three labelled groups — your shell / inside the subshell / back in your own shell — with leaving the subshell an explicit
exitrather than something to infer. Three rather than two because the reinstall was already implicitly outside the subshell and nothing said so.rocm engines shellhas no non-interactive-- <cmd>form (itbail!s without a TTY), so collapsing this to one context would have meant inventing a command the CLI does not have.Both copies of the plan carry it — the catalog recipe and the
Fixthe diagnosis attaches to its finding. Two shared assertions hold them: one checks the shell boundary is labelled in each, and one pins the two command blocks to each other byte for byte, so the block a user sees cannot drift from the blockrocm fixprints. An earlier revision of this description claimed the single boundary assertion already guaranteed that; it did not — it checked three command strings and two label substrings, and the step-2 wording had in fact drifted between the copies. Both are now identical and the equality check keeps them so.The surrounding prose is deliberately not identical, because
FixRecipehas arationalefield thatFixhas no counterpart for: the upstream-defect explanationdiagnosecarries as a note is printed byrocm fixout ofrationale, so copying it intonoteswould print it twice.verifyis byte-identical in both.This does not fix the upstream defects (
torch-c-dlpack-ext0.1.5 still selects the CUDA variant on ROCm;tvm_ffi's optional-import guard still misses theOSError). It recognises the failure and names the remedy that moves the runtime off the versions with prebuilts.Fix-id numbering
fix-16is reserved by the vLLM out-of-memory entry on #251, so this takes 17. The id is a stable handle external tooling reproduces, so the two are kept distinct rather than one being renamed after it ships. The gap is commented in all three places a reader would meet it.The AUTO set is unchanged — the new entry is PRINT-ONLY, so
exactly_the_four_known_fixes_are_autopasses untouched. That assertion being untouched is the evidence that nothing was promoted to auto-applicable.Test plan
main, the first withleft: "fix-4-render-group", which is precisely the reported false lead. The two shell-boundary tests were falsified the same way: dropping theexitline fails both withthe plan no longer runs 'exit', and dropping the step-3 label fails both withthe block has to say the reinstall runs back in the user's own shell.crates/rocm-core/src/diagnose.rs— a ranking test (the entry must outrank the render-group false lead on a fixture that produces it), a threshold test (the extension name alone must stay belowMIN_SCORE_FOR_MATCH), and a shell-boundary test on the plan the diagnosis attaches to its finding.crates/rocm-core/src/fix.rs— catalog count 15 → 16, plus a shell-boundary test on the catalog recipe. Both boundary tests share one assertion; a second shared assertion,assert_plan_matches_the_catalog_copy, pins the two command blocks to each other line for line. Falsified by rewording one copy's step-3 label: that reworded line still satisfied the boundary check, so the equality check catches drift the boundary check provably cannot.@id:diagnose-recognises-the-engine-import-failure(diagnose-13) covers the user-observable behaviour per AGENTS.md §3. It asserts the entry clears the report's own published threshold rather than that it ranks first — a runner with a real fault of its own legitimately outranks it for any symptom, so a ranking assertion there would test the runner's health instead.@id:diagnose-fix-catalog-is-completecovers the listing viaCATALOG_FIX_IDS.@id:diagnose-fix-says-which-shell-each-step-runs-in(diagnose-14) covers the printed command block, which is the surface the reviewer was reading. Not OS-gated: the plan is printed before the fix's own platform gate is reached, so the Windows lane exercises the same text; the step therefore asserts the block and not the exit code, which does differ (0 where the fix applies, 3 where it does not).cargo test --workspace --all-targets— green (30 test binaries, 0 failures).cargo clippy --workspace --all-targets -- -D warnings— clean.cargo fmt --all -- --check— clean.python3 scripts/smoke_local.py— ok.diagnose.featurerun on Linux: 14 scenarios, 51 steps, all passing.rocm fix fix-17-torch-dlpackread back to confirm the three groups render as intended.Lanes not run locally: Windows (the checker is linux-only, so the entry is inert there) and WSL2 (no lane exists; the catalog is not run on that platform at all).