Repository navigation
[ML] pytorch_inference OOM-killed (~34 GiB RSS) during ELSER wikipedia-1g reindex in nightly QA (since build 975) #3226
Description
Summary
The nightly custom-ml-cpp QA suite step family/appex-qa-debian-12 [pytorch_tests] has failed every night since build 975 (24 Sep 2026) on a single scenario: test_scenario_inference.py::TestScenarioInference::test_scenario_nightly[wikipedia-1g]. The last green run was build 974 (23 Sep 2026).
Root cause is a host out-of-memory kill of the ELSER pytorch_inference process, not a controlled stop. On an identical 64 GB / swap-less Debian-12 box running the identical workload, build 974 completed the ~2.7 h ELSER reindex with zero OOM kills, whereas the failing builds consistently drive the ELSER process to ~34 GiB RSS and it gets reaped by the kernel mid-reindex. The in-flight/queued inference requests then fail and the test (which asserts 0 failures) goes red.
This looks like a memory-pressure regression introduced between 23 → 24 Sep. Attribution (ml-cpp/libtorch footprint vs. ES adaptive-allocation sizing) is still open — see next steps.
Evidence
Kernel dmesg, build 981 (logs.tar.gz):
[Tue Sep 29 06:56:00 2026] Out of memory: Killed process 7607 (pytorch_inferen)
total-vm:37466804kB, anon-rss:36124772kB ... oom_score_adj:667
oom-kill:constraint=CONSTRAINT_NONE ... global_oom ... task=pytorch_inferen,pid=7607
(anon-rss 36,124,772 kB ≈ 34.5 GiB; box = 67,105,128 kB ≈ 64 GiB, Total swap = 0kB. Three co-resident pytorch_inference procs: ~34.5 GiB + ~7.1 GiB + ~0.55 GiB.)
ES side correlates exactly (build 981, node server log):
[controller/5506] Child process with PID 7607 was terminated by signal 9 (SIGKILL). This is likely due to the OOM killer.
[.elser_model_2_linux-x86_64] inference process crashed ... pytorch_inference/7607 process stopped unexpectedly
[.elser_model_2_linux-x86_64] clearing [12] requests pending results
[inference process] notifying [30] queued requests that have not been processed before shutdown
→ 12 pending + 30 queued = the 42 doc failures the test reports.
Reproduced with the same signature on build 980: pytorch_inferen pid 7664 OOM-killed at anon-rss 34,630,404 kB (≈ 33 GiB), global OOM.
Build history (QA pipeline appex-qa-stateful-custom-ml-cpp-build-testing):
| Build | Date | pytorch_tests | ELSER OOM |
|---|---|---|---|
| 970–974 | 19–23 Sep | passed | none |
| 975–978, 980, 981 | 24–29 Sep | failed | pytorch_inference ~33–34.5 GiB |
Not related
- Independent of the
controller-protocol.versionpackaging fix ([ML] Publish controller-protocol.version in Linux and DRA packaging #3224 / Prune controller-protocol.version from -deps bundle #3225). - The truncated
Fatal error: '[W929 ... Quantizer.cpp:111] Warning: torch.quantize_per_tensor ... deprecated'line in the ES log is a libtorch deprecation warning captured from the process's stderr at startup — a red herring; the actual cause is SIGKILL from the OOM killer. - Side note (not causal): in these runs the controller routes
pytorch_inferencevia the legacy path because the--disableSandboxoperator kill switch is set (legacy_reason":"kill_switch", sandbox2_established:false), so Sandbox2 is currently disabled in this QA lane.
Next steps / investigation
- Confirm attribution by bisecting the ml-cpp / libtorch change merged around 23 → 24 Sep (same QA harness across the pass→fail boundary, so the change is in the nightly-rebuilt artifact and/or the ES snapshot under test).
- Capture the periodic
pytorch_inferenceRSS report (ml-cpp#3160) at a log level that survives into the QA artifacts, so peak footprint is directly observable in future runs. - Check whether the ES snapshot's ML adaptive-allocations / memory sizing changed in that window (could raise the allocation count and hence RSS).
- If footprint growth is expected/unavoidable, revisit the QA box sizing (add swap / more RAM) or cap concurrent deployments for the nightly scenario — but only after (1) rules out a genuine footprint regression.
Activity
edsavage commentedon Sep 30, 2026
Bisection update — RSS reporting (#3160) ruled out
Correlating the
ml-cpp-pytorch-buildsnightly commits with the QA pass/fail boundary:beee73be(= #3160 merge)12c3ebfaSo #3160 "Periodically report pytorch_inference RSS" (merged 22 Sep,
beee73be) shipped in the last green build and is not the regression.The ml-cpp source delta
beee73be..12c3ebfais only two commits:19f76ea2— [ML] Do not treat third-party warnings as errors #3208 Do not treat third-party warnings as errors (build-flag only; very unlikely to affect runtime memory)12c3ebfa— [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187 Linear child ownership and fault-injected lifecycle for Sandbox2 (editsCSandboxedProcessSpawner— the child spawn/reap path)Leading suspect: #3187. It fits the evidence that three
pytorch_inferenceprocesses were co-resident at OOM (34.5 + 7.1 + 0.55 GiB) — a child-ownership/reaping regression leaking/orphaning inference processes would accumulate resident memory until the 64 GB box OOMs.Caveats before calling it confirmed:
--disableSandboxkill switch on →Launching './pytorch_inference' without Sandbox2), whereas [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187 only editslib/sandbox/. Need to check whether [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187 altered shared child-ownership/reaping logic that the legacyCDetachedProcessSpawnerpath also depends on.Next: verify whether the co-resident processes are orphans (reaping regression) vs. expected allocations, and confirm/deny a libtorch delta between the 828 and 829 images.
edsavage commentedon Sep 30, 2026
libtorch moved at the pass→fail boundary — now the leading suspect
The nightly image (
build_pytorch_linux_build_image.sh) does not build the pinnedv2.7.1; it compilespytorch_inferenceagainst the movingviable/strictPyTorch branch, rebuilding whenever that branch HEAD moves. Pulling the PyTorch SHA baked into each nightly:viable/strictbeee73beef7af4063de3047e2b39938d53529e84058f053512c3ebfa4de991c72a8c0c3b9c1646b5865d67b215c33e85So the red build ran a different libtorch than the last green one — 116 PyTorch commits in
ef7af406..4de991c7. The range includes memory/allocator-touching commits (e.g. inductor autotuning allocation aliasing and its revert, "Allocate receive buffers just in time", memory-timeline fusion guard), though most are inductor/AOTI/compile-time paths not exercised by CPU ELSER inference — no single obvious CPU-inference smoking gun, but a large surface.Implication: the regression is now most likely a libtorch bump, not the ml-cpp source delta (#3187 / #3208). This also sits better with the earlier observation that the runtime path was the legacy spawner (Sandbox2 disabled), which #3187 doesn't touch.
Decisive next experiment: hold the ml-cpp source constant and vary only libtorch — build
pytorch_inferenceat the same ml-cpp commit againstef7af406vs4de991c7and run thewikipedia-1gELSER reindex against each, watching peak RSS. That cleanly separates libtorch from the ml-cpp commits. If libtorch is exonerated, fall back to auditing whether #3187 changed shared child-ownership/reaping logic used by the legacyCDetachedProcessSpawnerpath.edsavage commentedon Sep 30, 2026
Green-arm libtorch A/B triggered
To decide between the two boundary hypotheses (libtorch bump vs the ml-cpp delta), I've launched a single green-arm re-run: hold the ml-cpp source at the first red nightly commit and swap only libtorch back to the last-green SHA.
experiment/libtorch-ab-3226off12c3ebfa— which is exactly the [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187 merge ("Linear child ownership and fault-injected lifecycle for Sandbox2"), i.e. the ml-cpp delta at the pass→fail boundary.Dockerfile: newpytorch_pinarg → fetch/checkout an exactpytorch_commitinstead of the movingviable/stricttip.build_pytorch_linux_build_image.sh:PYTORCH_COMMIT_OVERRIDE(pins SHA, bypasses skip-if-unchanged) +IMAGE_TAG_OVERRIDE(distinct tag, doesn't clobberpytorch_latest).build_pytorch_docker_image.yml.sh: flows the pinned tag to the downstreamDOCKER_IMAGEso the QA run pulls the pinned image.PYTORCH_COMMIT_OVERRIDE=ef7af4063de3047e2b39938d53529e84058f0535,IMAGE_TAG_OVERRIDE=pytorch_ab_ef7af406. Builds libtorch @ green SHA, then runs thepytorch_testsQA suite (wikipedia-1gELSER reindex).Read-out:
12c3ebfa(incl. [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187) is fine; the OOM regression is the libtorch bumpef7af406 → 4de991c7. Next: bisect the 116-commit libtorch range.Cleanup afterwards: delete the
experiment/libtorch-ab-3226branch and the throwawaypytorch_ab_ef7af406image tag.edsavage commentedon Sep 30, 2026
libtorch source scan (ef7af406..4de991c7, 116 commits / 512 files): no RSS-relevant source change
Full-coverage audit of the range (paged per-commit to defeat GitHub's 300-file compare cap → 512 unique files):
c10/core/CPUAllocator*,Allocator*).StorageImpl/ resize-realloc changes.c10/utilintrusive_ptr / SmallVector-growth changes.c10/core/TensorImpl.*edits (3 commits) are FakeTensor-only — extend the lazily-allocatedExtraMeta/FakeTensorMode;sizeof(TensorImpl)unchanged, fires only under tracing, not eager inference.AT_VEC_CUSTOM_MATH/FBCODE_CAFFE2— dead code in an OSS libtorch x86_64 build. s390x vector work (#173072) doesn't apply to x86_64.index_put_→index_fill_, lower grain size) — alters task granularity, not steady-state RSS.Implication: a libtorch source memory regression is unlikely. If libtorch is nonetheless the cause, it's more likely a build/config/submodule difference (submodule bump, MKL/oneDNN version, OpenMP thread count, allocator env). The green-arm experiment #836 still discriminates this: checking out
ef7af406reverts pytorch source and its pinned submodules, while the Dockerfile holds MKL (2024.0), compiler and thread settings constant. So:edsavage commentedon Sep 30, 2026
Complementary ml-cpp green-arm triggered — 2×2 factorial now in flight
Added the orthogonal control to isolate the other variable (holding libtorch ~fixed, reverting ml-cpp):
experiment/mlcpp-beee73be-3226offbeee73be— the [ML] Periodically report pytorch_inference RSS #3160 (RSS reporting) merge, i.e. the last-green nightly's ml-cpp commit (pre-[ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187).pytorch_latestimage (latest viable/strict) — no libtorch rebuild, no clobber (skip-if-unchanged), then runspytorch_tests(wikipedia-1g).Full matrix:
ef7af406beee73be(pre-#3187)12c3ebfa(#3187)Joint read-out:
ml-cppproject #837 PASS + Outlier memory usage code adds negative of unsigned number #836 PASS → interaction (needs both changes).ml-cppproject #837 FAIL + Outlier memory usage code adds negative of unsigned number #836 PASS → libtorch sole driver.ml-cppproject #837 PASS + Outlier memory usage code adds negative of unsigned number #836 FAIL → ml-cpp [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187 sole driver.Caveat: #837 uses latest viable/strict (newer than the red
4de991c7), so a #837 PASS is slightly ambiguous (ml-cpp-required vs an upstream libtorch fix landed since4de991c7); a #837 FAIL is unambiguous. Can re-run #837 pinned to4de991c7if we need it airtight.Corroborating context: the libtorch source scan found no RSS-relevant source change in
ef7af406..4de991c7, which lowers the prior on the pure-libtorch-source explanation and slightly favours the #3187 / interaction branches.edsavage commentedon Sep 30, 2026
Upstream search + allocator grounding: mimalloc regression ruled out; glibc-arena is the cheap lever
Searched PyTorch issues/forums for a matching upstream report. No "viable/strict OOM/leak" issue exists (consistent with a manual Google search), but there is a strongly on-theme, recent culprit class for our exact symptom (freed memory not returned → host OOM, tens of GiB RSS):
MIMALLOC_ARENA_RESERVE=0/mi_option_purge_delay=0, fix PR #178851, newtorch.cpu.empty_cache()PR #192078.jemalloc/MALLOC_ARENA_MAX.Why the mimalloc story does NOT apply to us:
ef7af406..4de991c7(source scan).USE_MIMALLOCis gatedWIN32 OR (CPU_AARCH64 AND NOT APPLE)in PyTorchCMakeLists.txt; our failing host is x86_64 Linux → glibc, not mimalloc.Actionable takeaway. Our allocator is glibc, whose classic failure mode is per-thread arenas + non-return of freed pages → large RSS when thread count / alloc pattern shifts. This is cheap to test and plausibly links to #3187 (child/process/thread lifecycle): if #3187 changed pytorch_inference's thread/fork behaviour, glibc arena RSS could balloon into OOM with no leak and no libtorch change.
Planned probe (cheaper than a libtorch bisect), gated on #836/#837: re-run the failing config with
MALLOC_ARENA_MAX=1(and/orMALLOC_TRIM_THRESHOLD_=0), orLD_PRELOAD=jemalloc. If the OOM vanishes → glibc-arena/fragmentation, likely tickled by a thread/lifecycle change (→ candidate fix: set arena cap at pytorch_inference launch /mallopt(M_ARENA_MAX)). Open design point: the env var must reach the controller-spawnedpytorch_inferencevia the ES node environment in the QA harness — need to confirm the QAF harness propagates it.edsavage commentedon Sep 30, 2026
Is Elasticsearch a factor? Third variable confirmed, but no smoking gun in the delta
The
pytorch_testsQA builds ES from source at a specific commit, and that commit moved between the green and red nightly QA runs (both nominallymain/9.6.0-SNAPSHOT):1e7276a414be9bb111a184b6a714d406ae15ee8d40cc061e4a48ddc10a1296624179452277d4f09eSo ES is a genuine third moving variable at the boundary (alongside ml-cpp #3187/#3208 and libtorch) — and it is not controlled by experiments #836/#837, which both float on today's snapshot.
Diff of
1e7276a4..40cc061e= 68 commits. Scanning all 68 subjects + changed files for anything on the ELSER /pytorch_inferencepath (native-process management, trained-model deployment memory, inference queue/concurrency, ML autoscaling) found nothing relevant:[Native]commits (#159442, #159649) are Lucene int8 kNN vector scoring — not the ML native process.inference/EndpointMetadata+ capabilities CSV are inference-API metadata, not memory/concurrency.painless/AllocationEstimators(painless heap accounting),WriteLoadConstraintDecider(shard allocation) — unrelated to ML native memory.Caveat: the numeric ELSER deployment settings (allocations / threads-per-allocation / queue depth / model memory) are not in the Buildkite log — they live in the ES server logs / QAF scenario config — so an ES-driven workload change can't be 100% excluded, only that the ES code delta shows no such change.
Conclusion: ES is uncontrolled but shows no plausible OOM driver in the Sep 23→24 delta. To neutralise it, pin ES across the arms (re-run pinned to red ES
40cc061e) if #836/#837 come back ambiguous.edsavage commentedon Sep 30, 2026
#3187 reaping audit: does NOT touch the legacy CDetachedProcessSpawner path
Question: did #3187 refactor shared child-reaping / ownership logic also used by the legacy
CDetachedProcessSpawner(the path in use when Sandbox2 is disabled)?Answer: No.
lib/sandbox/—CSandboxedProcessSpawner_Linux.cc, its header, CMake, and unit tests. It touches zero files inlib/core/.CDetachedProcessSpawnerhas its own self-contained reaper:::waitpid(-1, &status, WNOHANG)atlib/core/CDetachedProcessSpawner.cc:170— unchanged by [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187.lib/corecontains no reference tosandbox/orCSandboxedProcessSpawner(grep is empty). Per the layered dependency rule (core is lower than sandbox), core cannot depend on sandbox, so [ML] Linear child ownership and fault-injected lifecycle for Sandbox2 #3187's changes are physically unreachable from the legacy path.Implication: the failing QA runtime used the legacy spawner (Sandbox2 disabled via
--disableSandbox), so #3187's code does not execute there — which effectively exonerates #3187 as the OOM cause, conditional on the legacy-path fact. Live candidates now: #3208 (build flags), the libtorch build/config difference, or an ES/interaction effect — to be discriminated by #836 (ml-cpp #3187 + green libtorch) and #837 (pre-#3187 + latest libtorch).edsavage commentedon Sep 30, 2026
Both downstream QA runs (appex-qa #982, #983) failed early, during the local ES distribution build, at:
Zero
wikipedia/reindex/pytorch_inference/OOM lines in either log — they died at deployment creation, long before the workload.Root cause: the experiment branches are old ml-cpp commits (
beee73beSep 22,12c3ebfaSep 23) built against today's ESmain. Today's ES enforces a controller protocol version those old artifacts don't satisfy (theverifyControllerProtocolVersiongate from #3224/#3225). Old ml-cpp + new ES is rejected outright.Consequence — ES is a compatibility constraint, not just a variable: you cannot float ES against old ml-cpp; the two are version-coupled via
verifyControllerProtocolVersion. Both arms floated on today's ES, so they were void regardless of libtorch/ml-cpp.Revised plan — pin ES to a contemporaneous commit. The decisive libtorch test becomes: hold ml-cpp =
12c3ebfa(red) and ES =40cc061e(the red Sep-24 ES commit that ran the red nightly 829) fixed, and vary only libtorch red→green:12c3ebfa+ libtorch4de991c7+ ES40cc061e→ should reproduce the OOM (829).12c3ebfa+ libtorchef7af406+ ES40cc061e→ if it passes, libtorch is the cause; if it OOMs, libtorch exonerated (→ [ML] Do not treat third-party warnings as errors #3208 build flags / ES-workload / interaction).Open question: whether the QAF harness accepts an ES commit SHA (not just a branch) for pinning —
derive_qa_stack_env.shtreatsES_BRANCHas a branch name; the actual checkout is QAF-side. Need to confirm before re-running.[ML] Thread ES_COMMIT through QA triggers for pinned-ES A/B (#3226)
edsavage commentedon Sep 30, 2026
QAF supports pinning ES to a commit (ES_COMMIT) — valid re-run launched
Confirmed the harness supports it:
qaf'sElasticsearchRepoTask.Settingsexposescommitvia theES_COMMITenv var (typer optionenvvar="ES_COMMIT"); when set,update_repo()checks out that exact ES commit instead of the branch tip.Buildkite downstream builds don't inherit env automatically, so I threaded
ES_COMMIT(when set) through both trigger layers onexperiment/libtorch-ab-3226(commit06df572bf): pytorch-image pipeline →ml-cpp-pr-builds, andrun_pytorch_tests→appex-qa. Nightly behaviour unchanged when unset.Valid libtorch A/B re-launched, ml-cpp fixed at
12c3ebfa, ES pinned to40cc061e(the red Sep-24 commit, contemporaneous → passesverifyControllerProtocolVersion):4de991c740cc061eef7af40640cc061eRead-out: CONTROL OOMs + TEST passes → libtorch is the cause. Both OOM → libtorch exonerated → #3208 build flags / ES-workload / glibc-arena. CONTROL passes → OOM is nondeterministic/flaky near capacity (re-assess).
edsavage commentedon Sep 30, 2026
Latest scheduled nightly (#838, Sep 30) did NOT OOM — QA passed
wikipedia-1greindex (~6h),55 passed, 1 skipped, 0 failed, no OOM/kill.Pivot: the OOM has stopped reproducing. Confirmed OOM was Sep 24 (#829 / QA 975); by Sep 30 the same workload passes. Likely transient — tied to a specific libtorch/ES combination that both
viable/strictand ESmainhave since moved past — or flaky near capacity. The intervening nightly failures #830–#837 were most likely theverifyControllerProtocolVersiongate (pre-#3224/#3225), not OOM.Experiment status:
skip_queued_branch_builds(queued [ML] Remove unary_function/binary_function and old-style binders #840 on the same branch a second later).4de991c7+ red ES40cc061e+ ml-cpp12c3ebfa) is now the gating test — does the Sep-24 combo still deterministically OOM?Holding the TEST re-trigger until #840 reports.
[ML] Thread ES_COMMIT through the QA-tests trigger generator (elastic…
edsavage commentedon Oct 1, 2026
Investigation outcome: transient PyTorch
viable/strictregression, not ml-cppReconstructed the full nightly → QA →
{ml-cpp, libtorch}map from node logs,dmesg, and the PyTorch image-build logs. The headline: the OOM is caused by PyTorchviable/strictdrift, not ml-cpp, and it has already self-resolved upstream.Evidence map
beee73beef7af406(Sep 22)12c3ebfa16c5935bbe3ae4842verifyControllerProtocolVersiongatee3ae4842verifyControllerProtocolVersiongateb0c52aa44b0647ed(Sep 27)b0c52aa4f2398806(Sep 28)b0c52aa4ddae06a3(Sep 29)Decisive observation
Holding ml-cpp constant at
b0c52aa4across three consecutive nights, only libtorch changes:4b0647ed→ OOMf2398806→ OOMddae06a3→ passSame ml-cpp binary → 🔴 🔴 ✅. This exonerates ml-cpp and pins the OOM on a PyTorch
viable/strictmemory regression that entered afteref7af406(Sep 22), was present at4b0647ed/f2398806(Sep 27–28), and was fixed byddae06a3(Sep 29).The OOM signature:
pytorch_inferenceRSS grows to ~34–35 GiB over the ~1.5h wikipedia-1g ELSER reindex and is host-OOM-killed (CDetachedProcessSpawner.cc@223: ... terminated by signal 9 (SIGKILL). This is likely due to the OOM killer; corroborated bydmesg:Out of memory: Killed process ... (pytorch_inferen) anon-rss:36124772kB). Route islegacythroughout (so #3187 / Sandbox2 is not involved — it is not on the executed path).Corrections to the original report
model_deployment_timeout_exception), with wikipedia-1g passing. The genuine OOM is only 980/981 (Sep 28–29).verifyControllerProtocolVersionbuild gate (ES main had advanced past the pre-[ML] Publish controller-protocol.version in Linux and DRA packaging #3224/Prune controller-protocol.version from -deps bundle #3225 ml-cpp), never reaching the workload.Recommendation
No ml-cpp change required. The regression is upstream PyTorch and already fixed in
viable/strict. Suggest closing once a couple of clean green nightlies confirm stability (984/Sep 30 is already green on the same ml-cppb0c52aa4). A full QA bisect of the libtorch window was considered but not pursued — it would cost ~7× (image build + ~6h QA) to localise a bug that is already gone.Side benefit
The ability to pin ES to a commit SHA for these experiments has been factored into a standalone PR: #3228 (
ES_COMMITpassthrough to the appex-qa trigger).edsavage commentedon Oct 1, 2026
Closing: root cause is a transient upstream PyTorch regression, no ml-cpp action required
Per the investigation above, the OOM is not an ml-cpp defect. Holding ml-cpp constant at
b0c52aa4across three consecutive nightlies, the result tracks solely with the PyTorchviable/strictlibtorch build:4b0647ed(Sep 27) → OOM (QA 980)f2398806(Sep 28) → OOM (QA 981)ddae06a3(Sep 29) → pass (QA 984)The regression entered
viable/strictafteref7af406(Sep 22, good), was present at4b0647ed/f2398806, and was fixed byddae06a3. The subsequent nightly (984, Sep 30) is green on the same ml-cpp binary, confirming recovery.Also note the earlier framing was imprecise: nightlies 975/976 were an unrelated benign 30s ELSER redeploy timeout (not OOM), and 977/978 failed at the
verifyControllerProtocolVersionbuild gate. The genuine OOM is confined to 980/981.Closing as not planned (external/transient, already resolved upstream). If a future nightly OOMs again we can reopen and bisect the libtorch window with the new
ES_COMMITpinning support (#3228) to keep the ES side reproducible.edsavage commentedon Oct 1, 2026
Reopening — the OOM recurred; the "transient/fixed" conclusion was wrong
The Oct-1 nightly (appex-qa #987) OOM-killed
pytorch_inferenceagain, at ~40.6 GiB RSS (Out of memory: Killed process ... (pytorch_inferen) anon-rss:40650312kB), surfacing aswikipedia-1g: inference task failure count. Same ml-cppb0c52aa4; libtorchbb63328c(viable/strict, Oct 1 — newer than theddae06a3that passed on Sep 30).Corrected sequence, ml-cpp constant at
b0c52aa4:4b0647edf2398806ddae06a3bb63328cThat's 3 OOMs in 4 nightlies across 4 different libtorch commits. The Sep-30 pass now looks like it merely stayed under the 64 GiB ceiling rather than a real fix. So this is not a cleanly-fixed transient libtorch regression — it's a persistent high-RSS condition (~34–40 GiB and trending up) in the
b0c52aa4era that nondeterministically tips the box.Next step
Isolate ml-cpp vs libtorch with a single-variable A/B: hold libtorch fixed (current
bb63328c) and vary ml-cpp — green-erabeee73bevs OOM-erab0c52aa4, both against the same pinned, contemporaneous Elasticsearch (so theverifyControllerProtocolVersiongate passes for both) — and compare peakpytorch_inferenceRSS. ES pinning uses the newES_COMMITsupport (#3228).[ML] RSS probe: make distribution download/unzip cwd-independent (ela…
[ML] RSS probe: move probe body into committed script to survive pipe…
[ML] RSS probe: variable-length requests + heavier parallel load to c…
edsavage commentedon Oct 2, 2026
Nightly OOM history (ml-cpp × libtorch)
Each nightly QA run pulls ml-cpp
mainHEAD plus that night's libtorch, built from PyTorchviable/strict. Correlating the two:viable/strict)pytorch_inferencepeak RSSbeee73beef7af406(Sep 22)12c3ebfa16c5935bbe3ae4842verifyControllerProtocolVersiongate (no QA)e3ae4842b0c52aa44b0647ed(Sep 27)b0c52aa4f2398806(Sep 28)b0c52aa4ddae06a3(Sep 29)b0c52aa4bb63328cKey observations
b0c52aa4(merged ~Sep 28). Everything before it (beee73be,12c3ebfa,16c5935bb) either passed or failed for unrelated reasons (ELSER redeploy 30s timeout, controller-protocol gate void) — none were OOMs.b0c52aa4, the outcome tracks libtorch:4b0647ed→ OOM,f2398806→ OOM,ddae06a3→ pass,bb63328c→ OOM. So it is not purely ml-cpp or purely libtorch; there is at least an interaction, andddae06a3is the one OOM-era libtorch that did not blow up.ddae06a3pass is the counter-example that keeps both suspects live and motivates the isolation testing below.Caveats
beee73beis the last known-good ml-cpp and is used as the "green ml-cpp" arm in the isolation runs.Isolation testing in progress
A standalone RSS probe (no Elasticsearch) drives
pytorch_inferencedirectly with the public ELSER model and a synthetic variable-length BERT request stream, sampling/proc/<pid>RSS, as a throwaway step on the Linux x86_64 build agents (64 GiB). It reuses already-built distributions (no recompile), so single-variable comparisons run in minutes instead of the ~6h QA cycle. Currently comparing identical ml-cppb0c52aa4againstbb63328cvsee0d01e8libtorch under a heavy variable-length load.[ML] RSS probe: fix RssWatcher._stop shadowing Thread._stop (elastic#…