Repository navigation
feat(vllm): Tackle out of memory errors (EAI-8058) #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
abf6b4b
38257fe
af059ae
956891f
235adb9
77f26cd
e62ee2f
5702b55
8fcbcfa
2b83d44
724ef1c
2909d80
7d22878
2c3a84e
38f3d7b
67105c7
2e692ef
13bd32c
78cdcef
1b147f3
1e23717
0b5d66a
4e81c72
d380768
ba2b741
dffee3e
18b13a3
043e02c
025e2da
7e4f664
fcf9915
e3e3ae2
f34e458
4782444
a874302
909d503
3f2c32f
5a470ab
d35629f
3df1062
83e36d1
b6e0a50
4100187
73b8bfb
2aa6434
ce7a234
ddfea9f
5379d56
99a43b5
c22c1ba
e137c5f
5da11d5
c98d140
7ae1a36
66bcdc8
31f2186
a3b1b8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -574,9 +574,38 @@ const RECIPES: &[FixRecipe] = &[ | |
| applies_on: PRINT_ON_WINDOWS, | ||
| runner: None, | ||
| }, | ||
| // The number is a stable handle, not a position: `fix-16` is reserved by the | ||
| // vLLM out-of-memory entry on its own branch, so this one takes 17 rather | ||
| // than colliding and forcing whichever lands second to rename a published id. | ||
| FixRecipe { | ||
| fix_id: "fix-16-vllm-oom", | ||
| title: "vLLM ran the GPU out of memory at startup", | ||
| rationale: "vLLM reserves a fixed fraction (~90%) of each GPU's TOTAL VRAM for its KV cache by default, regardless of the model size or how much is currently free. Two different faults surface the same OOM, and they need opposite responses. If the GPU is shared or already busy, that reservation collides with memory in use and lowering it (or moving to a less-busy GPU) is the fix. If the model genuinely does not fit in this GPU's VRAM, lowering the reservation only trades an earlier OOM for a later one -- use a smaller or quantized model instead (rocm-cli serves one model on a single GPU; it does not shard a model across GPUs).", | ||
| commands: &[ | ||
| "# Case 1 -- shared/busy GPU (tenancy collision): lower the reservation,", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking, but the guard you added has a hole. These comment lines have drifted from the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 025e2da, both halves, exactly as you prescribed.
I confirmed it bites: reverting either comment line fails the assert. Your later thread on fix-15 (4016732550) is the part still outstanding; answered separately there.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking, and not introduced by this branch — flagging it only because this PR builds the tool that catches it. The guard you just wired here for Nothing catches it.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed, still open, and every part of your analysis reproduces at
The only open question is scope. This is pre-existing on |
||
| "# or steer vLLM onto a less-busy device:", | ||
| "rocm serve <model> --gpu-memory-utilization 0.5", | ||
| "rocm serve <model> --gpu <index>", | ||
| "# Case 2 -- the model genuinely does not fit: the reservation is not the", | ||
| "# problem; pick a smaller or quantized model (single-GPU serving only).", | ||
| ], | ||
| needs_sudo: false, | ||
| needs_reboot: false, | ||
| needs_relogin: false, | ||
| verify: "rocm serve <model> <case-appropriate options above> # re-run and watch for a clean startup", | ||
| notes: &[ | ||
| "Only lower --gpu-memory-utilization when the GPU is shared or already busy; on a GPU dedicated to this server it cannot create the room a too-large model needs.", | ||
| "This entry is keyword-matched from the error text: `rocm diagnose` cannot see per-GPU VRAM or tenancy, so pass the failure with --symptom (or arrive from the `rocm serve` failure note).", | ||
| ], | ||
| // Matches `check_16_vllm_oom`'s `["linux", "wsl"]` registration: WSL2 is | ||
| // the supported way to serve from a Windows host and the failure mode is | ||
| // just as real there, so the fix has to be runnable where the diagnosis | ||
| // is reachable. Pinned by | ||
| // `every_checker_platform_is_covered_by_its_recipe`. | ||
| doc_url: None, | ||
| applies_on: PRINT_ON_LINUX_AND_WSL, | ||
| runner: None, | ||
| }, | ||
| // The number is a stable handle, not a position: `fix-16` is held by the | ||
| // vLLM out-of-memory entry above, so this one takes 17 rather than | ||
| // colliding and forcing whichever lands second to rename a published id. | ||
| FixRecipe { | ||
| fix_id: "fix-17-torch-dlpack", | ||
| title: "Restore the engine's pinned torch (torch-c-dlpack-ext loads the CUDA variant)", | ||
|
|
@@ -976,6 +1005,35 @@ pub(crate) fn torch_rocm_indexes_named_in<'a>( | |
| .collect() | ||
| } | ||
|
|
||
| /// Test-only view of a recipe's actionable remediation: its `verify` command, | ||
| /// its `notes`, and its *executable* command lines (comment/prose lines | ||
| /// stripped). Lets the diagnosis catalog cross-check that the Fix it emits for a | ||
| /// shared `fix_id` has not silently diverged from this recipe. | ||
| #[cfg(test)] | ||
| pub(crate) fn recipe_verify_notes_and_run_commands( | ||
| fix_id: &str, | ||
| ) -> Option<(&'static str, &'static [&'static str], Vec<&'static str>)> { | ||
| find_recipe(fix_id).map(|recipe| { | ||
| let run_commands = recipe | ||
| .commands | ||
| .iter() | ||
| .copied() | ||
| .filter(|line| !line.trim_start().starts_with('#')) | ||
| .collect::<Vec<_>>(); | ||
| (recipe.verify, recipe.notes, run_commands) | ||
| }) | ||
| } | ||
|
|
||
| /// Test-only view of the platform families a recipe is runnable on. | ||
| /// | ||
| /// `None` when no recipe carries `fix_id`. Lets the diagnosis catalog assert | ||
| /// that every platform a checker answers on is a platform `rocm fix` will | ||
| /// actually act on, which `recipe_verify_notes_and_run_commands` cannot see. | ||
| #[cfg(test)] | ||
| pub(crate) fn recipe_applies_on(fix_id: &str) -> Option<Vec<&'static str>> { | ||
| find_recipe(fix_id).map(|recipe| recipe.applies_on.iter().map(|p| p.os).collect()) | ||
| } | ||
|
|
||
| /// Whether `fix_id` names an entry in the catalog. | ||
| /// | ||
| /// A predicate rather than the lookup itself, because the caller that needs it | ||
|
|
@@ -2315,9 +2373,10 @@ mod tests { | |
| let count = ids.len(); | ||
| ids.dedup(); | ||
| assert_eq!(ids.len(), count, "duplicate fix-id in RECIPES"); | ||
| // 18 bare-metal/Windows entries (fix-17, fix-18 and fix-19 among them) | ||
| // plus the 7 WSL ones. | ||
| assert_eq!(count, 25, "expected 25 catalog entries"); | ||
| // 19 bare-metal/Windows entries (fix-16-vllm-oom, fix-17-torch-dlpack, | ||
| // fix-18-comgr-conflict and fix-19-shm-too-small among them) plus the | ||
| // 7 WSL ones. | ||
| assert_eq!(count, 26, "expected 26 catalog entries"); | ||
| } | ||
|
|
||
| /// Restored, not new. This PR made the `wsl` arm of the platform lookup | ||
|
|
@@ -2362,6 +2421,43 @@ mod tests { | |
| /// `apply` reaches its internal-error path. The converse is deliberately | ||
| /// not asserted: a print-only recipe may hold a runner that reports without | ||
| /// writing, which is exactly what `fix-2-unset-override` does on Linux. | ||
| #[test] | ||
| fn the_utilization_hint_example_matches_the_recipe_command() { | ||
| // The shared hint carries a worked value, and `fix-16-vllm-oom` hands | ||
| // the user a command carrying another. They drifted once already: the | ||
| // docs and the recipe moved to 0.5 while the const kept recommending | ||
| // 0.1, so one `rocm diagnose` printed "e.g. 0.1 for a small model" | ||
| // directly above `rocm serve <model> --gpu-memory-utilization 0.5`. | ||
| // Nothing caught it -- the cross-check in `diagnose` exempts the summary | ||
| // as prose framing, and a line-based grep misses the const because the | ||
| // flag and the value sit on different continuation lines. | ||
| const FLAG: &str = "--gpu-memory-utilization"; | ||
| let hint = crate::VLLM_GPU_MEMORY_UTILIZATION_HINT; | ||
| let example = hint | ||
| .split_once("e.g. ") | ||
| .and_then(|(_, rest)| rest.split_whitespace().next()) | ||
| .unwrap_or_else(|| { | ||
| panic!("the shared hint no longer carries a worked `e.g. <value>`:\n{hint}") | ||
| }); | ||
| let recipe = find_recipe("fix-16-vllm-oom").expect("fix-16-vllm-oom is in the catalog"); | ||
| let commanded = recipe | ||
| .commands | ||
| .iter() | ||
| .find_map(|line| line.split_once(FLAG)) | ||
| .and_then(|(_, rest)| rest.split_whitespace().next()) | ||
| .unwrap_or_else(|| { | ||
| panic!( | ||
| "fix-16-vllm-oom no longer commands `{FLAG}`:\n{:#?}", | ||
| recipe.commands | ||
| ) | ||
| }); | ||
| assert_eq!( | ||
| example, commanded, | ||
| "the shared hint recommends `{FLAG} {example}` while `rocm fix fix-16-vllm-oom` \ | ||
| commands `{FLAG} {commanded}`; both can print in a single `rocm diagnose` report" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_recipe_that_acts_has_a_runner() { | ||
| for r in RECIPES { | ||
|
|
@@ -2510,6 +2606,17 @@ mod tests { | |
| ("fix-9-igpu-dgpu", "windows", FixClass::NeedsArgument), | ||
| ] | ||
| ); | ||
| // fix-16-vllm-oom carries a workaround the user must weigh (lowering the | ||
| // VRAM reservation is wrong for a model that genuinely does not fit), so | ||
| // it is deliberately PRINT-ONLY and must never join the AUTO set. | ||
| let oom = find_recipe("fix-16-vllm-oom").expect("OOM recipe is in the catalog"); | ||
| assert!( | ||
| oom.applies_on | ||
| .iter() | ||
| .all(|p| p.class == FixClass::PrintOnly), | ||
| "fix-16-vllm-oom must stay PRINT-ONLY on every platform it applies to" | ||
| ); | ||
| assert!(oom.runner.is_none(), "a PRINT-ONLY recipe has no runner"); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.