Repository navigation
fix(miopen): guard AI heuristic fdeep model caches with a mutex (cherry-pick #9131) - #9149
Merged
marbre merged 1 commit intoJul 7, 2026
Conversation
JIRA ID: ROCM-27477 ## Motivation `GetFdeepModel` and `GetModel` in `projects/miopen/src/conv/heuristics/ai_heuristics.cpp` cache loaded AI heuristic models in `static std::map` containers with no synchronization. When convolutions run concurrently from multiple threads on the same device (e.g. per-thread MIOpen handles), two callers can race on the same cache key: both find the key missing, both build a model, and both insert. In `GetFdeepModel` the second insert (`models[key] = std::move(model)`) move-assigns over the first thread's `unique_ptr<fdeep::model>`, destroying the model object while the first thread is still using the reference it was handed — a use-after-free that segfaults deep inside `fdeep`'s prediction code. This was surfaced by the PyTorch test `test_cuda.py::TestCuda::test_cudnn_multiple_threads_same_device` on ROCm 7.14.0-RC1 / gfx942 (MI300 series), with an observed crash rate of roughly 3-17% per triggering run. The exposing commit (#7109, which enabled the 2D AI heuristic default path for gfx942/gfx950) predates the last known-good nightly, but the underlying missing-lock defect itself is older and not a regression introduced within the RC window. The race is warm-up-bounded: it can only occur during first concurrent population of a given cache key, since entries are never overwritten afterward. A runtime workaround exists without rebuilding: `MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_FWD_XDLOPS_AI_HEUR=0` disables the AI heuristic path entirely, but that is not a substitute for fixing the underlying race. ## Technical Details The fix mirrors the already-correct sibling `GetCandidateSelectionModel` in `ai_candidate_selection.cpp`, which guards its structurally identical cache with a `static std::mutex` + `std::lock_guard`. - `GetModel(arch, solver)`: added a `static std::mutex models_mutex` and moved the `models.find(solver)` lookup inside a `std::lock_guard` that also covers the insert-on-miss path, so find+insert is atomic with respect to other callers. The function now returns the cached `std::shared_ptr<Model>` by value while still holding the lock, which is safe since `shared_ptr` copies are cheap and the returned value keeps the model alive independently of the map. - `GetFdeepModel(path, key)`: added a `static std::mutex models_mutex` guarding find+insert. Critically, the insertion path was changed from `models[key] = std::move(model); return ref;` (which risks a move-assign clobbering an existing entry on a race, and previously took the returned reference *before* the map write) to `it = models.emplace(key, std::move(model)).first;` followed by `return *(it->second);`. Because entries are only ever inserted once and never reassigned, a reference into the map obtained after the lock is released remains valid for the lifetime of the process — the lock only needs to serialize the population itself, not every subsequent read. No public API, call signatures, or heuristic selection logic changed; this is purely a thread-safety fix around existing caching behavior. ## Risk Assessment **Risk Level: 🟢 Low** The change is a single-file, 27-line diff that adds standard mutex-guarded caching to two functions, following the exact pattern already used correctly elsewhere in the same file. It does not alter model selection, loading logic, or any public interface. ### Impacted Components - `projects/miopen/src/conv/heuristics/ai_heuristics.cpp`: `GetModel` (used by the general AI heuristic model cache) and `candidate_selection::GetFdeepModel` (used by the AI candidate selection path). Any convolution solver selection path that goes through the AI heuristics (currently defaulted on for gfx942/gfx950 2D convolutions) is indirectly affected. ### Potential Side Effects - Serializing model cache population introduces a mutex acquisition on every call to `GetModel`/ `GetFdeepModel`, not only on cache misses. Since these are in-process, uncontended locks after warm-up (steady-state calls hit the fast `find` path while holding a brief, uncontended lock), the performance impact is expected to be negligible relative to the cost of running the model itself, but reviewers should watch for any contention under very high thread-count concurrent convolution workloads. - No behavioral change to which model is loaded or which heuristic decision is made — only the concurrency safety of caching is affected. ### Mitigation Steps - The fix follows an existing, proven pattern (`GetCandidateSelectionModel`) already in production in the same file, minimizing the chance of a novel defect. - The runtime env var workaround (`MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_FWD_XDLOPS_AI_HEUR=0`) remains available as an immediate mitigation if an unexpected issue surfaces post-merge, without requiring a revert or rebuild. - Revert plan: the diff is small and self-contained to one file, making a straightforward revert possible if needed. ## Test Plan This will be validated manually on gfx942 hardware using the PyTorch repro test (`test_cuda.py::TestCuda::test_cudnn_multiple_threads_same_device`) that originally surfaced the crash, running it repeatedly to build confidence that the segfault no longer reproduces. In addition, unit tests have been added that reproduce the race condition. These have been run manually, and will also run as part of the standard MIOpen test category. ## Test Result - [x] manual validation on gfx942 via the PyTorch repro - [x] CI passes --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
BrianHarrisonAMD
approved these changes
Jul 7, 2026
marbre
approved these changes
Jul 7, 2026
marbre
left a comment
Member
There was a problem hiding this comment.
Looks go from the perspective that it cherry-picks a commit that already made it into develop. One of the miopen shards failed tests (I retriggered) but I assume this is flaky. Build tests for gfx950 are unrelated and build passed on the original PR (so did tests). The risk here however is that the cherry-picked commit did not yet propagate into TheRock (this needs another submodule bump) and therefore we didn't had a nightly build yet.
Approving for DevOps though as we could catch regressions either on the submodule bump or on the RC build.
astyrrian1
approved these changes
Jul 7, 2026
Member
|
gfx94X went through, gfx950 went through on the original PR. The prior build failure was unrelated and can therefore be ignored. |
marbre
deleted the
users/jlichtne/ROCM-27477-fix-ai-heuristic-segfault-cherry-pick
branch
July 7, 2026 20:56
marbre
added a commit
to ROCm/TheRock
that referenced
this pull request
Jul 8, 2026
Bumps rocm-libraries and rocm-systems to include the following cherry-picks: * ROCm/rocm-libraries#9149, JIRA ID ROCM-27477 * ROCm/rocm-systems#8194, JIRA ID ROCM-27078 * ROCm/rocm-systems#8212, JIRA ID ROCM-27116
JonathanLichtnerAMD
added a commit
that referenced
this pull request
Jul 15, 2026
JIRA ID: ALMIOPEN-2312 ## Motivation `projects/miopen/CHANGELOG.md`'s "MIOpen 3.5.2 for ROCm 7.14.0" section only had 4 entries, but 68 commits touched `projects/miopen` between the `therock-7.13` tag and `release/therock-7.14`. Auditing that commit range (via `git log --oneline therock-7.13..remotes/origin/release/therock-7.14 -- projects/miopen`) found 18 additional user-facing changes — new gfx950/gfx1250 architecture support, SystemDB refreshes, AI-heuristic improvements, several correctness/crash bug fixes, and OpenCL solver removals — that were never documented. This PR backfills those entries so the 7.14.0 changelog accurately reflects what shipped. ## Technical Details Added 24 lines to the existing "MIOpen 3.5.2 for ROCm 7.14.0" section of `projects/miopen/CHANGELOG.md`, merging new bullets into the existing `Added`/`Changed`/`Resolved Issues` sub-sections (no new entries were duplicated or reworded) and adding a new `Removed` sub-section for solver removals. No existing content was altered. Each new bullet was traced to a specific commit + PR: | Entry | Commit | PR | |---|---|---| | Added gfx950 (MI350X/MI355X) SystemDB | `60d8e1e5175` | #8081 | | Added initial gfx1250 architecture support | `d3f057b2f22` | #7587 | | Added JSON-formatted performance logs (`MIOPEN_PERFORMANCE_LOGS`) | `ffbde871260` | #5736 | | Refreshed gfx942 SystemDB | `faf754a9ef6` | #8174 | | Refined 2D AI heuristics (Tunanet/KTN) for gfx942 & gfx950 | `c24ad415884` | #7109 | | Fixed thread-safety issue in AI-heuristic model caches | `cd957402309` | #9149 (cherry-pick of #9131) | | Fixed CK grouped-conv solvers not always registered in host | `c971d54d7ae` | #9105 | | Fixed legacy CK buffer resource descriptor (gfx1250) | `1dee011b8b0` | #8758 | | Removed stale gfx942 SystemDB naive-solver entries | `f42668f6316` | #7028 | | Fixed softmax NaN when `beta=0` | `fb8157c8512` | #8426 | | Fixed BN backward spatial variant 1 vectorized memory access bug | `1351a7e8b10` | #6765 | | Fixed use-after-free of `kernel_id` in `InitInvokerFactoryNCHW` | `394ebffb50d` | #7922 | | Fixed SoftMarginLoss forward reduce-kernel kernarg corruption | `1e65a31570f` | #8065, #8135 | | Fixed build with `MIOPEN_ENABLE_AI_KERNEL_TUNING=OFF` | `e6288c3ca61` | #6800 | | Fixed install RPATH for MIOpenDriver / CK backend libs | `ef8a4cf2f93` | #7485 | | Fixed dangling lambda causing log-level 6 segfault | `190f9791737` | #6953 | | Enabled Windows long-path support for `addkernels` | `1f9d69724cf` | #7387 | | Removed OCL solver `ConvOclDirectFwd1x1` | `126c720cadd` | #6491 | | Removed OCL solver `conv_ocl_dir2Dfwdgen` | `2d499ddf709` | #6633 | | Removed OCL solvers `ConvOclBwdWrW1x1`, `ConvOclBwdWrW2` | `b5d43a33dc6` | #6859 | | Removed OCL solvers `OpTensorLeadingOnes`, `Op5dTensorGeneric` | `8642c83b7de` | #7347 | The remaining ~40 commits in the 68-commit range were deliberately excluded (tests/CI/build/docs/internal refactors/1:1 OCL→HIP swaps with no behavior change, per each commit's own stated scope) and are not represented here. ## Risk Assessment **Risk Level: 🟢 Low** This is a documentation-only change to a single Markdown file. No code, build configuration, tests, or public API surface is touched. ### Impacted Components - `projects/miopen/CHANGELOG.md` only. ### Potential Side Effects None expected. This does not change any runtime behavior; it only documents changes that already shipped on `release/therock-7.14`. ### Mitigation Steps None required beyond normal review — a reviewer familiar with the 7.14 release can spot-check a few entries against the linked commit hashes/PR numbers for accuracy. ## Test Plan Not applicable (no code changes). Verification was done by cross-referencing every added bullet against a specific commit hash and PR number pulled from `git log`, and by diffing the edited section against `git show remotes/origin/release/therock-7.14:projects/miopen/CHANGELOG.md` to confirm no existing entry was duplicated or altered. ## Test Result All 21 new bullets map 1:1 to a real commit/PR in the `therock-7.13..release/therock-7.14` range for `projects/miopen`. No pre-existing changelog text was modified — `git diff` shows a pure addition (24 insertions, 0 deletions). Two borderline commits (`a67834413e8` "Standalone build", `7c7cd5774a9` "enable warnings about deprecated API/features") were deliberately left out pending author judgment on whether they warrant an entry.
pschang-phy
pushed a commit
that referenced
this pull request
Jul 17, 2026
JIRA ID: ALMIOPEN-2312 ## Motivation `projects/miopen/CHANGELOG.md`'s "MIOpen 3.5.2 for ROCm 7.14.0" section only had 4 entries, but 68 commits touched `projects/miopen` between the `therock-7.13` tag and `release/therock-7.14`. Auditing that commit range (via `git log --oneline therock-7.13..remotes/origin/release/therock-7.14 -- projects/miopen`) found 18 additional user-facing changes — new gfx950/gfx1250 architecture support, SystemDB refreshes, AI-heuristic improvements, several correctness/crash bug fixes, and OpenCL solver removals — that were never documented. This PR backfills those entries so the 7.14.0 changelog accurately reflects what shipped. ## Technical Details Added 24 lines to the existing "MIOpen 3.5.2 for ROCm 7.14.0" section of `projects/miopen/CHANGELOG.md`, merging new bullets into the existing `Added`/`Changed`/`Resolved Issues` sub-sections (no new entries were duplicated or reworded) and adding a new `Removed` sub-section for solver removals. No existing content was altered. Each new bullet was traced to a specific commit + PR: | Entry | Commit | PR | |---|---|---| | Added gfx950 (MI350X/MI355X) SystemDB | `60d8e1e5175` | #8081 | | Added initial gfx1250 architecture support | `d3f057b2f22` | #7587 | | Added JSON-formatted performance logs (`MIOPEN_PERFORMANCE_LOGS`) | `ffbde871260` | #5736 | | Refreshed gfx942 SystemDB | `faf754a9ef6` | #8174 | | Refined 2D AI heuristics (Tunanet/KTN) for gfx942 & gfx950 | `c24ad415884` | #7109 | | Fixed thread-safety issue in AI-heuristic model caches | `cd957402309` | #9149 (cherry-pick of #9131) | | Fixed CK grouped-conv solvers not always registered in host | `c971d54d7ae` | #9105 | | Fixed legacy CK buffer resource descriptor (gfx1250) | `1dee011b8b0` | #8758 | | Removed stale gfx942 SystemDB naive-solver entries | `f42668f6316` | #7028 | | Fixed softmax NaN when `beta=0` | `fb8157c8512` | #8426 | | Fixed BN backward spatial variant 1 vectorized memory access bug | `1351a7e8b10` | #6765 | | Fixed use-after-free of `kernel_id` in `InitInvokerFactoryNCHW` | `394ebffb50d` | #7922 | | Fixed SoftMarginLoss forward reduce-kernel kernarg corruption | `1e65a31570f` | #8065, #8135 | | Fixed build with `MIOPEN_ENABLE_AI_KERNEL_TUNING=OFF` | `e6288c3ca61` | #6800 | | Fixed install RPATH for MIOpenDriver / CK backend libs | `ef8a4cf2f93` | #7485 | | Fixed dangling lambda causing log-level 6 segfault | `190f9791737` | #6953 | | Enabled Windows long-path support for `addkernels` | `1f9d69724cf` | #7387 | | Removed OCL solver `ConvOclDirectFwd1x1` | `126c720cadd` | #6491 | | Removed OCL solver `conv_ocl_dir2Dfwdgen` | `2d499ddf709` | #6633 | | Removed OCL solvers `ConvOclBwdWrW1x1`, `ConvOclBwdWrW2` | `b5d43a33dc6` | #6859 | | Removed OCL solvers `OpTensorLeadingOnes`, `Op5dTensorGeneric` | `8642c83b7de` | #7347 | The remaining ~40 commits in the 68-commit range were deliberately excluded (tests/CI/build/docs/internal refactors/1:1 OCL→HIP swaps with no behavior change, per each commit's own stated scope) and are not represented here. ## Risk Assessment **Risk Level: 🟢 Low** This is a documentation-only change to a single Markdown file. No code, build configuration, tests, or public API surface is touched. ### Impacted Components - `projects/miopen/CHANGELOG.md` only. ### Potential Side Effects None expected. This does not change any runtime behavior; it only documents changes that already shipped on `release/therock-7.14`. ### Mitigation Steps None required beyond normal review — a reviewer familiar with the 7.14 release can spot-check a few entries against the linked commit hashes/PR numbers for accuracy. ## Test Plan Not applicable (no code changes). Verification was done by cross-referencing every added bullet against a specific commit hash and PR number pulled from `git log`, and by diffing the edited section against `git show remotes/origin/release/therock-7.14:projects/miopen/CHANGELOG.md` to confirm no existing entry was duplicated or altered. ## Test Result All 21 new bullets map 1:1 to a real commit/PR in the `therock-7.13..release/therock-7.14` range for `projects/miopen`. No pre-existing changelog text was modified — `git diff` shows a pure addition (24 insertions, 0 deletions). Two borderline commits (`a67834413e8` "Standalone build", `7c7cd5774a9` "enable warnings about deprecated API/features") were deliberately left out pending author judgment on whether they warrant an entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JIRA ID: ROCM-27477
Motivation
The AI convolution heuristics in MIOpen cache loaded models in process-global static maps (
GetModelandGetFdeepModelinai_heuristics.cpp). When convolutions run concurrently from multiple threads on the same device (for example, per-thread MIOpen handles each running a convolution), those callers race on the same cache key during first population. This unguarded find-then-insert was a data race that could corrupt the map and produce a use-after-free deep inside fdeeppredict, segfaulting the process (SIGSEGV/SIGABRT). The crash was first surfaced viaConvHipImplicitGemmGroupFwdXdlops'sHeuristicInit.This is a cherry-pick / backport of the already-merged PR #9131 onto the
release/therock-7.14branch.Technical Details
projects/miopen/src/conv/heuristics/ai_heuristics.cpp:#include <mutex>.GetModel(arch, solver): added astatic std::mutex models_mutexand astd::lock_guardheld across thefind+ insert so cache population is serialized. Moved themodels.findlookup to occur after the lock is acquired (and after the gfx950 to gfx942 arch remap). Collapsed the if/else into a single trailingreturn it->second.GetFdeepModel(path, key): changed the cache fromstd::map<std::string, std::unique_ptr<fdeep::model>>tostd::map<std::string, std::shared_ptr<fdeep::model>>and changed the return type fromconst fdeep::model&tostd::shared_ptr<fdeep::model>. Added astatic std::mutex models_mutexguarding find + insert. Returning byshared_ptr(rather than a reference into the map) keeps the model alive in the caller independently of the cache, so it stays valid even if the entry were ever replaced or evicted, mirroringGetModel.EncodeInputFeaturesWithFdeepandEncodeKernelConfigsWithFdeepto use the returnedshared_ptr(->predict(...),->predict_multi(...)) instead of a reference into the map.Trade-off: serializing the cache with a mutex adds contention only on the first-population path; subsequent lookups are cheap hash-map reads under a short-held lock. Returning by
shared_ptradds a refcount bump per call, which is negligible relative to model inference cost.projects/miopen/test/gtest/conv_ai_heuristics_threading.cpp(new, 173 lines): a gtest that hammersGetFdeepModelfrom 32 threads over 8 iterations on a cold cache, each thread owning its ownCandidateSelectionModel(mirroring per-thread handles), lined up with astd::latchto maximize the first-population overlap. Success requires no crash, no thrown exception, and identical non-empty encodings across all threads (a corrupted/half-built model would diverge or throw). Gated to Instinct/CDNA devices (candidate-selection KTN models ship only for gfx942/gfx950) and skips when model files are absent. Compiled underMIOPEN_ENABLE_AI_KERNEL_TUNING.Risk Assessment
Risk Level: 🟢 Low
The change is a small, contained thread-safety fix (39 lines changed in one source file plus a new test file). It adds locking to two cache accessors and shifts one cache from
unique_ptrtoshared_ptr; it does not alter heuristic logic, model outputs, or any public API. The only behavioral change is correctness under concurrency.Impacted Components
projects/miopen/src/conv/heuristics/ai_heuristics.cpp(GetModel,GetFdeepModel,EncodeInputFeaturesWithFdeep,EncodeKernelConfigsWithFdeep).projects/miopen/test/gtest/conv_ai_heuristics_threading.cpp.Potential Side Effects
GetFdeepModelnow returns ashared_ptrrather than a reference; the two internal call sites were updated accordingly. The function is internal to thecandidate_selectionnamespace, so no external callers depend on the old signature.Mitigation Steps
ConcurrentGetFdeepModel) that reproduces the original race on a cold cache and would also trip ThreadSanitizer.Test Plan
GPU_AIHeuristicsThreadSafety_FP32.ConcurrentGetFdeepModel(parameterized over gfx942 and gfx950) on an Instinct/CDNA device with candidate-selection models shipped. The test spawns 32 threads x 8 iterations against a cold cache.Test Result
Upstream PR #9131 is merged and reviewed: before the fix the concurrent burst crashes (SIGSEGV/SIGABRT) or trips ThreadSanitizer; with it, all threads return identical, non-empty encodings with no crash or exception. This cherry-pick applies as a single commit with no additional modifications. Final validation is expected via the MIOpen gtest suite on the release/therock-7.14 CI targets.