Repository navigation
feat(bench): monotonic OOM backstop and persistent OOM record for decode benchmark sweeps - #808
Conversation
GB10 CUDA runs that land on a cell certain to OOM have repeatedly wedged the driver and required a reboot, so those cells should never be launched in the first place rather than discovered by crashing. scripts/bench_longprompt.sh now applies two empirical skip layers: a monotonic within-sweep backstop keyed per model that stops probing larger context sizes once one has already OOM'd in the current run, and a persistent cross-run OOM record at benchmarks/.oom-record that pre-skips cells known to OOM from prior sweeps (overridable via MLXCEL_BENCH_IGNORE_OOM_RECORD=1). Both layers are documented in the script's header comment block and its usage() text. An MLXCEL_BENCH_FAKE_OOM testing hook synthesizes SKIP:oom cells without triggering a real OOM, so the backstop and record logic can be exercised safely. scripts/bench_decode.sh hardens is_oom_failure with three CUDA-specific markers (CUDA_ERROR_OUT_OF_MEMORY, cudaErrorMemoryAllocation, cuMemAlloc failed) so CUDA OOM failures are reliably detected and feed the new skip layers. .gitignore adds an entry for benchmarks/.oom-record since it is a locally accumulated record, not sweep output to commit. Verified with bash -n and shellcheck on both scripts (no new warnings). Rust build/test was not run since this is a shell-only change. Refs #807
Implementation Review SummaryIntent
Findings AddressedNone. No CRITICAL/HIGH/MEDIUM findings; no code changes were required. Verification (all empirically exercised on this branch, shell-only, no cargo)
Remaining ItemsNone. |
Two review findings from the OOM backstop/record work on bench_longprompt.sh. The associative arrays (declare -A OOM_MIN_LEN / FAKE_OOM_SET) added for the OOM avoidance state are a bash 4+ feature, but the script explicitly supports Darwin/Metal (it detects M1 Ultra and M5 Max) and stock macOS ships bash 3.2 as /bin/bash, where declare -A aborts the whole script at startup including --help. Add an early, bash-3-compatible version guard that re-execs under a newer bash if one is on PATH (Homebrew /opt/homebrew/bin or /usr/local/bin) and otherwise exits with an actionable "install a newer bash" message instead of the cryptic "declare: -A: invalid option" failure. The guard only re-execs into a bash whose major version is >= 4, so it cannot loop. load_oom_record validated only the first two fields (non-empty model, numeric length), so a truncated or hand-edited entry like "model,32768" was accepted and pre-skipped indefinitely, contradicting the header comment that promises wrong-field-count lines are skipped with a warning. Validate the full field count (>= 6, the documented model,prompt_target_len,date,commit,peak_bytes,source_csv format) so truncated records are rejected; a source path containing a comma only inflates the count and stays accepted. Verified with bash -n and shellcheck (clean), and by re-running the fake-OOM backstop, the persistent-record pre-skip, and the malformed-line path: the two-field truncated line is now warned and skipped while a valid six-field line still loads. Refs #807
Follow-up: supplementary review findingsA supplementary automated review pass surfaced three additional items. Disposition below; two are fixed in Fixed (pushed in 4bfd870)
Reported, not changed
Re-verified after the fixes with |
…them The monotonic backstop and the persistent-record pre-skip both gate on `[[ "$len" -ge N ]]`, an arithmetic comparison. A non-numeric --lengths entry (a typo such as "2o48" or "32k") makes that arithmetic error out and silently evaluate false, so a cell the backstop was supposed to skip is launched anyway and the OOM classification is never recorded. Launching a near-OOM cell is precisely what leaves the GB10 driver wedged (issue #807), so the safety net silently failing open on a config typo defeats the feature's purpose. Reject any ladder length that is not a plain non-negative integer up front, before the output CSV is truncated, with a message naming the offending token. A malformed length is an unambiguous config error that the operator wants surfaced immediately rather than after a partial sweep. Verified in an isolated sandbox: "512 2o48" and "32k" now exit 1 before any launch or file truncation, while a valid "512 2048 8192" ladder still fires the backstop as before. bash -n and shellcheck are clean. Refs #807
Summary
On the GB10 (DGX Spark, CUDA unified memory) machine, benchmark cells that OOM are worse than wasted time: near-OOM CUDA runs have repeatedly left the driver in a degraded state (NVRM NV_ERR_NO_MEMORY accumulating for hours, eventually wedging the kernel and requiring a reboot), so cells that are certain to OOM should never be launched at all. This PR adds two purely empirical, reactive-only OOM-avoidance layers to
scripts/bench_longprompt.sh, plus a small hardening item inscripts/bench_decode.sh, plus three review-driven hardening fixes to the OOM-avoidance layers themselves.What changed
scripts/bench_longprompt.sh: monotonic within-sweep backstop, keyed per model, that skips every remaining cell at length>=an observedSKIP:oomlength for the rest of the run, emitting an explicitSKIP:oom_backstopCSV row plus a stderr message.scripts/bench_longprompt.sh: persistent cross-run OOM record at gitignoredbenchmarks/.oom-record(model,prompt_target_len,date,commit,peak_bytes,source_csv,peak_bytesalways blank today). On later runs, any cell whose model has a recorded OOM at a length<=the cell's length is pre-skipped and logged asSKIP:oom_record, with the matching record line echoed to stderr.MLXCEL_BENCH_IGNORE_OOM_RECORD=1disables the pre-skip for one run (newSKIP:oomcells are still appended). Missing/empty record files and malformed lines are tolerated with a stderr warning, not a hard failure.scripts/bench_longprompt.sh:MLXCEL_BENCH_FAKE_OOM="model:len[,model:len...]"testing hook that skips invoking the runner for a listed cell and synthesizes aSKIP:oomrow, so the backstop and record logic can be exercised deterministically without a real OOM.scripts/bench_longprompt.sh: only the exactSKIP:oomclassification feeds either layer;FAIL:bench, timeout (exit 124),SKIP:oom_estimate, a missing./modelssymlink, and a missing model directory never do, since the gate is a strict string-equality check on the trailing classification token.scripts/bench_longprompt.sh: the record-file format, pre-skip semantics, the override env var, and staleness/pruning guidance (the PR fix(cuda/attention): bound long-prompt prefill memory for flash-ineligible models #676 chunked-prefill fix is used as the concrete precedent: a record taken before that merge is stale and should be pruned withgrep -v) are documented in both the script's header comment block and itsusage()text.scripts/bench_decode.sh: extended theis_oom_failurepattern list with three CUDA-specific allocator markers (CUDA_ERROR_OUT_OF_MEMORY,cudaErrorMemoryAllocation,cuMemAlloc failed), kept alongside the existing generic and Metal patterns under the same "specific and grounded" rule..gitignore: added/benchmarks/.oom-recordsince it is a locally accumulated, host-specific record and not sweep output meant to be committed.Review-driven hardening (3 follow-up fixes)
scripts/bench_longprompt.sh: bash-version guard. TheOOM_MIN_LEN/FAKE_OOM_SETassociative arrays (declare -A) are a bash 4+ feature, but the script explicitly supports Darwin/Metal and stock macOS ships bash 3.2 as/bin/bash, wheredeclare -Aaborts the whole script at startup (including--help). The script now re-execs into a bash>= 4found onPATH(Homebrew/opt/homebrew/binor/usr/local/bin) if one exists, and otherwise exits with an actionable "install a newer bash" message instead of the crypticdeclare: -A: invalid optionfailure; the guard cannot loop since it only re-execs into a version that passes the check.scripts/bench_longprompt.sh:load_oom_recordfield-count validation. It previously validated only the first two fields (non-empty model, numeric length), so a truncated or hand-edited entry likemodel,32768was accepted and pre-skipped indefinitely, contradicting the header comment's promise that wrong-field-count lines are skipped with a warning. It now validates the full field count (>= 6, the documentedmodel,prompt_target_len,date,commit,peak_bytes,source_csvformat) so truncated records are rejected while asource_csvpath that itself contains a comma stays accepted.scripts/bench_longprompt.sh:--lengthsladder validation. Both the monotonic backstop and the persistent-record pre-skip gate on[[ "$len" -ge N ]], an arithmetic comparison; a non-numeric--lengthsentry (a typo such as2o48or32k) made that comparison error out and silently evaluate false, so the cell the backstop was supposed to skip launched anyway and the OOM was never recorded, i.e. the safety net could fail open on a config typo. Every ladder length is now rejected up front, before the output CSV is truncated, if it is not a plain non-negative integer, naming the offending token.Successful-row CSV output is unchanged; the trailing classification token remains an unnamed field after
prompt_target_len, consistent with the existingSKIP:oom_estimate/SKIP:oom/FAIL:benchconvention inbench_decode.sh.Test plan
bash -n scripts/bench_longprompt.shandbash -n scripts/bench_decode.sh(syntax check)shellcheck scripts/bench_longprompt.sh scripts/bench_decode.sh(no new warnings; one pre-existing unrelated SC2002 note inbench_decode.sh)MLXCEL_BENCH_FAKE_OOM="qwen2.5-0.5b-4bit:512"with--lengths "512 2048 8192"producedSKIP:oomat 512 andSKIP:oom_backstopat 2048/8192, with no real launch for the backstopped cells;benchmarks/.oom-recordgained exactly one correctly-formatted linellama-3.2-1b-4bit) in a separate sweep produced a normal successful row and left the first model's.oom-recordentry untouched, confirming the backstop and record are keyed per model(qwen2.5-0.5b-4bit, 512)record line pre-skipped all three ladder lengths asSKIP:oom_recordwith no launch and left the record file unchanged; re-running withMLXCEL_BENCH_IGNORE_OOM_RECORD=1executed the cell for real instead of pre-skipping it--modelsat a nonexistent model directory produced the existing[miss]skip path with zero CSV rows and nobenchmarks/.oom-recordfile createdmodel,32768) appended to.oom-recordis skipped with an explicit stderr warning and does not count toward loaded entries, while the valid six-field line alongside it still loads and pre-skips--lengths "512 2o48"exits 1 with an explicit "must be space-separated non-negative integers" message before any cell launches or the output CSV is truncated, while a valid ladder still runs and the backstop still firesbenchmarks/*.csvfiles were left untouched throughout, and the test-onlybenchmarks/.oom-recordfile was removedCloses #807