Skip to content

feat(memory): add process memory manager (Phase 5) - #1426

Closed
justinchuby wants to merge 3 commits into
justinchuby-fix-phase4-device-loss-settlementfrom
justinchuby-phase-5-process-memory-manager
Closed

justinchuby wants to merge 3 commits into
justinchuby-fix-phase4-device-loss-settlementfrom
justinchuby-phase-5-process-memory-manager

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

Introduces the process-level ProcessMemoryManager and manager-issued scoped memory bindings for Phase 5 of #1186.

The manager coordinates registration, mechanism selection, canonical authority and holder identity, parent-quota delegation, allocation transactions, device-loss publication, and accounting settlement. It does not absorb policy or device ordering:

  • BindingRegistry remains the only registration/selection/allocation-generation ledger.
  • Governors remain the authority for budgets, leases, and mapped-growth grants.
  • Holders remain the only components that choose and reclaim their own victims.
  • Execution providers retain stream, copy, commit/decommit, fence, and deferred-release ordering.

Native CUDA decoder, pipeline decoder, prompt/every-step components, and proposer sessions now share the engine/server process manager. Plain construction still creates a local unlimited parent manager, preserving current defaults.

Part of #1186 — Phase 5 only

Stacked on the final reviewed Phase-4 corrective branch / PR #1349 (justinchuby-fix-phase4-device-loss-settlement).

Architecture and ownership

flowchart TD
    PMM[ProcessMemoryManager]
    REG[BindingRegistry<br/>single registration + lifecycle ledger]
    GOV[MemoryGovernor / authority<br/>budget and lease policy]
    HOLDER[Holder / PressureResponder<br/>victim choice + reclaim]
    BIND[ScopedMemoryBinding<br/>device + mechanism + context + authority]
    TX[Allocation transaction<br/>reserve → allocate → commit/map → publish]
    OWN[ManagedAllocation<br/>owning allocation + settlement token]
    EP[Execution provider context<br/>streams + copies + fences + deferred queue]
    Q[Phase-4 structured release / quarantine]

    PMM --> REG
    PMM --> GOV
    PMM -. pressure request .-> HOLDER
    REG --> BIND
    GOV --> TX
    BIND --> TX
    TX --> OWN
    OWN --> EP
    EP --> Q
    Q -. actual outcome .-> PMM
    HOLDER -. released bytes only .-> GOV
Loading

Source of truth

Component Owns Explicitly does not own
ProcessMemoryManager Process quota/delegation, canonical authority views, holder identity, context operation gates, transaction orchestration, settlement observation Eviction victims, model contents, stream/fence ordering, allocator internals
BindingRegistry Context/authority/mechanism registration, current selection, binding/allocation generations, lifecycle, queued/quarantined ownership Budgets, leases, pressure policy, process transactions
Governor / authority Local limits, leases, mapped allowance/growth, oversubscription truth Victim selection, device calls
Holder What it may evict and how it releases it Granting capacity to itself
EP context Allocation mechanism execution, copy/commit/decommit, stream fences, deferred release, CUDA context loss Model-specific victim policy, process quota

The manager keeps policy metadata keyed by opaque registry identities, but no second context/mechanism membership ledger. Mechanism snapshots come directly from BindingRegistry::snapshots().

Scoped identity and lifecycle

  • One binding names exactly one device, mechanism, provider context, canonical authority, binding ID, and binding generation.
  • Allocations add a never-pointer-derived allocation generation.
  • Mechanism switches affect only later bindings; old allocations keep their original allocator/context/authority release path.
  • Same-device sessions remain isolated by holder, binding, authority, and allocation generation.
  • UMA/shared views may register device-specific authority views while retaining one canonical ProcessAuthorityId and one governor.
  • Device loss is broadcast to every same-device provider queue before registry invalidation.
  • Context lifecycle is monotonic: active → retiring/lost → terminated. Allocation commit/publication and weight page-in hold context-operation tokens; termination waits for those tokens with no manager/registry/governor lock held.

Lock order

  1. Manager registration gate serializes non-callback registration/removal and device-loss generation publication.
  2. Manager policy/book locks are copied and dropped before registry, governor, holder, allocator, queue, or device calls.
  3. Registry lock and per-mechanism locks keep their Phase-3 rule: never nested.
  4. Process quota and authority claims are independent; rollback releases each exactly once.
  5. Holder pressure callbacks, allocator/capability calls, CUDA event recording, observer settlement, and waits run without manager/registry/governance locks.
  6. CUDA queue execution uses a reentrant owner/depth gate so release actions may enqueue reservation teardown while device-loss publication remains serialized.

No strong manager cycle is introduced: queue cleanup captures a weak manager, registry/context pins do not point back to the manager, and allocator teardown completion is signaled only after queue-producing destructors return.

Transaction and rollback matrix

Stage Failure handling Charge result Physical result
Preflight / pressure No transaction reservation exists during callbacks Unchanged Unchanged
Process reserve Checked add; refusal mutates nothing Unchanged Unchanged
Authority reserve Process token drops on refusal Exact process rollback Unchanged
Allocate Both provisional tokens drop Exact rollback No allocation escaped
Allocate with unidentified retained ownership Provisional tokens are retained fail-closed No false refund Provider-owned residual remains unreachable
Commit / map Owning allocation releases through Phase-4 structured path Complete release refunds; quarantine retains full independent charge axes Partial failure uses structured residual; no free-list reuse
Publish validation Same structured rollback as commit failure Exact or retained, never success-shaped Same
Deferred complete Settlement token consumes actual outcome once Full managed charge returned Released/pooled per allocator outcome
Deferred quarantine / enqueue refusal / abandonment Charge is retained; mapped attribution changes only by actual unmapped_bytes No allocation-length-derived refund Residual remains quarantined
Device invalidation Identity/lifecycle only No refund No allocator/device retry
Confirmed context termination Waits for context and registry quiescence, then no-driver reconciliation Manager charges, mapped allowances, residency leases, pool leases, and fixed delegation discharge Terminated context proves physical ownership gone

Finite process limits either reserve the full per-allocation authority maximum or use a fixed full-capacity authority delegation. Lowering a parent below live slices is rejected, and a local authority whose extent outgrows its slice cannot grant another transaction.

Accounting axes

Axis Meaning
Authority charged bytes Canonical governor usage; authoritative for grants, not residency
Allocation-attributed charge Charge attributable to currently manager-published allocations; may exclude retained authority pools
Process-reserved bytes Live parent quota not already covered by a fixed authority slice
Known physical bytes Only backing with explicit physical facts; shared identities deduplicate aliases
Mapped bytes Mapping attribution; aliases may legitimately sum above physical ownership
Unattributed bytes Compatibility allocations with known presence but no authority-visible charge
Unknown physical allocations Allocations where the mechanism cannot honestly expose pool/granule ownership

CUDA VMM now carries additional_owned_bytes separately from newly_mapped_bytes. Pool-backed allocations do not claim allocation-span bytes as physical residency. Quarantine also never uses allocation length to refund rounded authority/process charges.

Migration and adapters

  • onnx-runtime-session workspace preparation uses allocate_workspace / replace_workspace transactions.
  • CUDA workspaces carry ManagedAllocation inside DeviceBuffer; charges settle only after the deferred Phase-4 outcome.
  • Workspace replacement and new-step allocation wait behind a persistent old-release barrier with a finite deadline.
  • DeviceBuffer extraction returns BoundBufferOwnership, preserving manager settlement at compile time instead of panicking or stripping it.
  • CudaExecutionProvider::with_memory(Arc<dyn DeviceAllocator>) remains supported. Replacement registers/selects through the manager; retired mechanisms remain tracked until their old allocations and teardown complete.
  • Erased third-party allocators still implement only DeviceAllocator; the manager wraps registration and transaction identity.
  • Remaining legacy/unguverned paths are labeled compatibility/unattributed rather than reported as zero charge or zero residency.
  • No plugin ABI is frozen and no Phase-6 work is included.

Validation

  • Core all-target suite: 993 passed, 0 failed, 464 ignored.
  • New manager suite: multi-session/device/authority isolation, UMA aliases, mechanism switch, reserve/allocate/commit/publish failures, quarantine rollback, checked arithmetic, fixed delegation, teardown, device loss, reentrant callbacks, erased allocator, and deterministic stress.
  • Deterministic campaigns: 25 iterations each of concurrent grants, rollback, and context-termination/publication races.
  • Production integration: runtime-session governed workspace preparation and server shared-authority load path.
  • Review-fix regressions: the allocation-specific workspace barrier settles on the same queue while an unrelated release remains pending; timeout retry succeeds; concurrent workspace identities cannot overwrite each other; failed compatibility deallocation retains its charge in observable quarantine.
  • Final polish validation (governor + EP API + CUDA EP only, per re-review scope): 583 passed, 0 failed, 394 ignored; scoped Clippy clean. Added direct wrapper coverage, real deallocate_workspace success/error coverage, and waiter-safe quarantined snapshot retention including the context-termination race.
  • Scoped Clippy: changed non-CUDA packages pass -D warnings; CUDA library passes -D warnings after allowing only exact inherited lint classes.
  • Rustdoc: changed packages pass -D warnings after allowing only inherited broken/private/redundant link warnings in unchanged files.
  • CUDA: all onnx-runtime-ep-cuda --features cuda --all-targets test binaries compile; hardware tests were compile-only / ignored, not executed on this non-CUDA host.
  • git diff --check passes.

Exact base comparison

The exact #1349 head (9ac2b41b) was tested before edits:

  • Baseline targeted tests: 848 passed, 0 failed, 396 ignored.
  • Global fmt already failed only in unchanged onnx-genai-server/src/routes/completions.rs.
  • Strict Clippy already failed on unchanged onnx-runtime-ep-cpu lints; additional CUDA strict failures are likewise in unchanged kernel tests.
  • Strict rustdoc already failed on unchanged governor links; final strict output adds only unchanged EP/API/engine link warnings when those packages are included.
  • CUDA honesty script findings are confined to unchanged base test targets (deferred_release_queue, matmul_nbits_marlin_numerics, vmm_release_quarantine).

Scope guard

This PR does not choose weight/KV victims, inspect model-specific contents, reclaim unrelated external processes, add a dynamic plugin ABI, remove eager CUDA, flip defaults, or change scheduler/model behavior. It does not weaken or bypass the Phase-4 ownership, deferred-release, quarantine, exact-once, or device-loss state machines.

This PR must remain open for human review. Do not merge, auto-merge, enqueue, or stack-merge it.

justinchuby and others added 3 commits August 18, 2026 23:48
Centralize memory registration and scoped allocation transactions while preserving governor, holder, and execution-provider ownership boundaries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Wait only for the exact managed workspace allocation, keep timeout retries recoverable, and quarantine compatibility charges on failed deallocation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep quarantined charges visible with active waiters and exercise the production workspace barrier and deallocation branches directly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.74576% with 190 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.01%. Comparing base (9ac2b41) to head (c65cf03).

Files with missing lines Patch % Lines
crates/onnx-runtime-ep-api/src/provider.rs 65.93% 86 Missing and 7 partials ⚠️
...tes/onnx-runtime-cuda-memory/src/virtual_memory.rs 0.00% 65 Missing ⚠️
crates/onnx-runtime-memory-governor/src/lib.rs 75.00% 16 Missing and 7 partials ⚠️
crates/onnx-runtime-ep-plugin/src/device.rs 42.85% 3 Missing and 1 partial ⚠️
...ates/onnx-runtime-session/src/executor/bindings.rs 33.33% 1 Missing and 1 partial ⚠️
...ates/onnx-runtime-cuda-memory/src/vmm_allocator.rs 0.00% 1 Missing ⚠️
crates/onnx-runtime-memory-api/src/binding.rs 95.45% 0 Missing and 1 partial ⚠️
crates/onnx-runtime-session/src/executor/mod.rs 50.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                                Coverage Diff                                @@
##           justinchuby-fix-phase4-device-loss-settlement    #1426      +/-   ##
=================================================================================
+ Coverage                                          79.52%   80.01%   +0.48%     
=================================================================================
  Files                                                365      366       +1     
  Lines                                             162327   164950    +2623     
  Branches                                          162327   164950    +2623     
=================================================================================
+ Hits                                              129096   131980    +2884     
+ Misses                                             28513    28150     -363     
- Partials                                            4718     4820     +102     
Flag Coverage Δ
mlas 85.22% <ø> (+0.16%) ⬆️
offline 79.91% <59.74%> (+0.49%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
crates/onnx-runtime-memory-api/src/allocator.rs 92.30% <ø> (ø)
crates/onnx-runtime-memory-governor/src/manager.rs 74.75% <ø> (ø)
...ates/onnx-runtime-session/src/executor/dispatch.rs 69.67% <100.00%> (+0.50%) ⬆️
crates/onnx-runtime-session/src/executor/state.rs 83.33% <ø> (ø)
...ates/onnx-runtime-cuda-memory/src/vmm_allocator.rs 16.55% <0.00%> (-0.01%) ⬇️
crates/onnx-runtime-memory-api/src/binding.rs 77.39% <95.45%> (+2.55%) ⬆️
crates/onnx-runtime-session/src/executor/mod.rs 56.11% <50.00%> (ø)
...ates/onnx-runtime-session/src/executor/bindings.rs 59.81% <33.33%> (+0.31%) ⬆️
crates/onnx-runtime-ep-plugin/src/device.rs 66.16% <42.85%> (-0.15%) ⬇️
crates/onnx-runtime-memory-governor/src/lib.rs 82.94% <75.00%> (-0.32%) ⬇️
... and 2 more

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

🔴 Benchmark Regression Detected

Comparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).

ℹ️ Absolute times are informational only — they vary with runner load. The % change column is the reliable signal because both sides ran under identical conditions.

Status Scenario Base PR Change
🔴 qwen3_sampling_processors/top_k_partial_selection 143.51 µs 209.59 µs +46.0%
🔴 gather/small_bf16_threads=1-internal/4096 451.0 ns 635.9 ns +41.0%
🔴 matmul/large_generic_f32_threads=8/32x1024x1024 5.22 ms 7.35 ms +40.9%
🔴 qwen3_sampling_processors/top_k_full_sort_baseline 2.21 ms 3.07 ms +39.2%
🔴 kv_cache/alloc_dealloc_pages 38.63 µs 52.21 µs +35.2%
🔴 gather/large_f16_threads=1-internal/131072 13.78 µs 18.38 µs +33.3%
🔴 qwen3_sampling_processors/top_k_top_p_full_sort_baseline 5.68 ms 7.49 ms +31.8%
🔴 qwen3_sampling_processors/top_p_full_sort_after_top_k_baseline 3.58 ms 4.70 ms +31.4%
⚠️ gather/medium_f32_threads=1-internal/32768 3.54 µs 4.41 µs +24.6%
⚠️ matmul/large_generic_f16_threads=1/32x1024x1024 87.12 µs 105.87 µs +21.5%
⚠️ gather/medium_bf16_threads=1-internal/32768 2.24 µs 2.69 µs +20.2%
⚠️ add/large_f32_threads=1-internal/4194304 647.91 µs 770.49 µs +18.9%
⚠️ qwen3_sampling_processors/top_p_fast_after_top_k 540.29 µs 638.22 µs +18.1%
⚠️ reduce_mean/small_f32_threads=1-internal/4096 14.10 µs 16.59 µs +17.6%
⚠️ gather/medium_f16_threads=1-internal/32768 2.26 µs 2.65 µs +17.5%
⚠️ gather/large_bf16_threads=1-internal/131072 15.95 µs 18.48 µs +15.9%
⚠️ logit_processing/seven_processor_chain_per_step 365.96 µs 423.88 µs +15.8%
⚠️ gather/small_f16_threads=1-internal/4096 442.4 ns 512.3 ns +15.8%
✅ qwen3_sampling_processors/top_k_top_p_fast 686.96 µs 788.55 µs +14.8%
✅ add/medium_f32_threads=1-internal/262144 22.55 µs 25.79 µs +14.4%
✅ add/large_bf16_threads=1-internal/4194304 1.55 ms 1.75 ms +13.2%
✅ add/small_bf16_threads=1-internal/1024 417.1 ns 471.8 ns +13.1%
✅ matmul/medium_generic_f32_threads=1/32x512x512 2.45 ms 2.77 ms +12.9%
✅ gather/large_f32_threads=1-internal/131072 40.40 µs 45.30 µs +12.1%
✅ add/medium_bf16_threads=1-internal/262144 96.99 µs 108.46 µs +11.8%
✅ matmul/small_generic_f32_threads=8/1x256x256 51.75 µs 57.79 µs +11.7%
✅ matmul/small_generic_f16_threads=1/1x256x256 33.53 µs 37.42 µs +11.6%
✅ add/large_f16_threads=1-internal/4194304 1.55 ms 1.72 ms +10.8%
✅ matmul/small_generic_f32_threads=1/1x256x256 45.36 µs 49.97 µs +10.1%
✅ gather/small_f32_threads=1-internal/4096 668.9 ns 735.9 ns +10.0%
✅ reduce_mean/medium_f32_threads=1-internal/65536 240.22 µs 261.45 µs +8.8%
✅ sampling_latency/top_p_per_token 403.03 µs 438.61 µs +8.8%
✅ matmul/medium_generic_bf16_threads=1/32x512x512 577.12 µs 620.81 µs +7.6%
✅ add/small_f16_threads=1-internal/1024 435.0 ns 465.4 ns +7.0%
✅ sampling_latency/min_p_per_token 208.60 µs 222.85 µs +6.8%
✅ add/small_f32_threads=1-internal/1024 192.4 ns 203.8 ns +5.9%
✅ block_quantized_matmul_cached_dense/mxfp4_preexpanded_dense_oncelock_like_proxy/1x1024x1024 99.13 µs 104.97 µs +5.9%
✅ matmul/small_generic_bf16_threads=8/1x256x256 39.31 µs 41.49 µs +5.5%
✅ block_quantized_moe_cached_dense/mxfp4_uncached_expert_dequant_each_call/rows=1,H=256,I=256,E=4,top_k=1 593.38 µs 624.24 µs +5.2%
✅ matmul/medium_generic_f32_threads=8/32x512x512 1.75 ms 1.83 ms +4.6%
✅ matmul/large_generic_f32_threads=1/32x1024x1024 9.97 ms 10.43 ms +4.6%
✅ block_quantized_matmul_cached_dense/mxfp4_cached_dense_repeated_call/1x1024x1024 125.43 µs 130.19 µs +3.8%
✅ matmul/large_generic_bf16_threads=1/32x1024x1024 2.14 ms 2.20 ms +2.8%
✅ sampling_latency/top_k_per_token 53.87 µs 55.17 µs +2.4%
✅ matmul/medium_generic_f16_threads=1/32x512x512 38.12 µs 38.99 µs +2.3%
✅ matmul/medium_generic_f16_threads=8/32x512x512 43.76 µs 44.35 µs +1.4%
✅ matmul/large_generic_f16_threads=8/32x1024x1024 100.81 µs 101.71 µs +0.9%
✅ matmul/small_generic_bf16_threads=1/1x256x256 38.55 µs 38.88 µs +0.9%
✅ matmul/small_generic_f16_threads=8/1x256x256 39.23 µs 39.53 µs +0.8%
✅ matmul/medium_generic_bf16_threads=8/32x512x512 691.19 µs 684.73 µs -0.9%
✅ block_quantized_moe_cached_dense/mxfp4_cached_dense_expert_repeated_call/rows=1,H=256,I=256,E=4,top_k=1 140.03 µs 137.22 µs -2.0%
✅ block_quantized_matmul_cached_dense/mxfp4_uncached_dequant_each_call/1x1024x1024 1.29 ms 1.25 ms -3.4%
✅ sampling_latency/greedy_per_token 3.99 µs 3.67 µs -8.1%
✅ reduce_mean/large_f32_threads=1-internal/262144 1.16 ms 1.06 ms -8.9%
✅ add/medium_f16_threads=1-internal/262144 120.67 µs 107.59 µs -10.8%
✅ grammar_masking/llguidance_compute_mask/32 112.83 µs 99.59 µs -11.7%
✅ matmul/large_generic_bf16_threads=8/32x1024x1024 2.71 ms 2.36 ms -12.9%
🟢 tokenization/encode_tokens_per_second 540.26 µs 445.14 µs -17.6%
🟢 tokenization/decode_tokens_per_second 9.54 ms 7.29 ms -23.6%

Visual flags: ⚠️ ≥ 15% slower, 🔴 ≥ 30% slower — calibrated against measured runner noise (~27% worst-case on multi-threaded matmul)

Host info
CPU: Apple M1 (Virtual)
Cores: 3
OS: Darwin 25.5.0 arm64
Rust: rustc 1.97.1 (8bab26f4f 2026-07-14)
Load avg: { 7.60 5.29 6.47 }
What this cannot catch
  • Regressions in code paths not covered by these benchmarks (e.g., end-to-end decode with a real model)
  • Sub-threshold regressions that compound over multiple PRs
  • Performance changes that only manifest under GPU execution
  • Latency changes in the ORT integration path (these benchmarks exercise the native Rust kernels)

@justinchuby
justinchuby requested a lite review from Copilot August 19, 2026 14:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a process-level ProcessMemoryManager and migrates key allocation flows (notably session workspaces and native CUDA components) to manager-issued scoped bindings/transactions, improving identity, settlement, and device-loss handling without centralizing policy.

Changes:

  • Introduces ProcessMemoryManager public API surface and extensive governor-level tests for process transactions, settlement, device loss, and reentrancy.
  • Migrates session workspace allocation/deallocation to WorkspaceAllocation + replace_workspace/deallocate_workspace APIs.
  • Integrates CUDA EP with the process manager, including context operation gating, managed settlement tokens, and post-drain cleanup hooks.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docs/memory/MEMORY_ARCHITECTURE.md Documents Phase 5 process manager responsibilities, transaction model, and accounting axes.
crates/onnx-runtime-session/src/executor/state.rs Switches workspace representation to WorkspaceAllocation.
crates/onnx-runtime-session/src/executor/mod.rs Updates imports and ensures workspace deallocation uses deallocate_workspace.
crates/onnx-runtime-session/src/executor/dispatch.rs Replaces manual reserve/allocate/lease flow with replace_workspace.
crates/onnx-runtime-session/src/executor/bindings.rs Updates persistent/step workspace replacement + release to new workspace APIs.
crates/onnx-runtime-memory-governor/tests/process_memory_manager.rs Adds comprehensive tests for manager behavior (transactions, settlement, device loss, concurrency).
crates/onnx-runtime-memory-governor/src/lib.rs Exposes manager module and adds mapped-attribution context-termination + retained-growth settlement.
crates/onnx-runtime-memory-governor/Cargo.toml Adds thiserror dependency for new manager/error types.
crates/onnx-runtime-memory-api/src/binding.rs Adds register_allocator_with_device and snapshots() for process-manager integration.
crates/onnx-runtime-memory-api/src/allocator.rs Extends MappedAllocation with additional_owned_bytes.
crates/onnx-runtime-ep-plugin/src/device.rs Handles both binding-owned and manager-owned buffer releases.
crates/onnx-runtime-ep-cuda/src/weight_paging.rs Adds context-scope gating + termination reconciliation to residency/page-in paths.
crates/onnx-runtime-ep-cuda/src/provider.rs Integrates CUDA EP allocation/release with process manager and workspace settlement barrier.
crates/onnx-runtime-ep-cuda/src/deferred_release.rs Adds re-entrant execution gate and a drain callback hook for cleanup after queue drain.
crates/onnx-runtime-ep-api/src/provider.rs Introduces managed buffer ownership, WorkspaceAllocation, and workspace allocation/deallocation APIs.
crates/onnx-runtime-ep-api/src/lib.rs Re-exports new workspace/ownership types.
crates/onnx-runtime-cuda-memory/src/vmm_allocator.rs Plumbs additional_owned_bytes out of CUDA VMM allocation commits.
crates/onnx-runtime-cuda-memory/src/virtual_memory.rs Adds context-identity tracking + external context-termination reconciliation for physical pools.
crates/onnx-genai-server/src/state.rs Adds a shared ProcessMemoryManager to server memory authority provider.
crates/onnx-genai-engine/src/pipeline/routing.rs Loads native CUDA pipeline components using shared authority + process manager.
crates/onnx-genai-engine/src/pipeline/mod.rs Threads CUDA manager/governor/policy through component session construction.
crates/onnx-genai-engine/src/pipeline/flat_autoregressive.rs Threads process manager into native CUDA component loads.
crates/onnx-genai-engine/src/pipeline/decoder_component.rs Threads process manager into native decoder construction.
crates/onnx-genai-engine/src/native_decode/proposer.rs Adds load_with_cuda_memory that uses a shared manager.
crates/onnx-genai-engine/src/native_decode/load.rs Threads process manager through native decode CUDA EP initialization.
crates/onnx-genai-engine/src/native_component.rs Adds governed CUDA component loader using shared manager.
crates/onnx-genai-engine/src/memory_authority.rs Extends authority provider trait to supply a process manager.
crates/onnx-genai-engine/src/lib.rs Re-exports ProcessMemoryManager.
crates/onnx-genai-engine/src/engine/speculative_load.rs Loads speculative proposer with shared manager under CUDA.
crates/onnx-genai-engine/src/engine/load.rs Threads process manager into native decode and speculative proposer loading.
crates/onnx-genai-engine/src/engine/governor.rs Stores a process manager in governor and threads reservation bytes separately.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +333 to +348
impl Drop for ExecutionGateGuard<'_> {
fn drop(&mut self) {
let current = std::thread::current().id();
let mut state = self
.gate
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
debug_assert_eq!(state.owner.as_ref(), Some(&current));
state.depth = state.depth.saturating_sub(1);
if state.depth == 0 {
state.owner = None;
self.gate.wake.notify_all();
}
}
}
Comment on lines +2270 to +2278
assert!(
lease.is_none(),
"CUDA manager-backed workspace must keep accounting in ManagedAllocation, not an outer \
compatibility lease"
);
assert!(
captured,
"CUDA workspace must carry allocation-specific manager settlement"
);
Comment on lines +450 to +465
static QUARANTINED_WORKSPACE_LEASES: std::sync::OnceLock<std::sync::Mutex<Vec<MemoryLease>>> =
std::sync::OnceLock::new();

fn quarantine_failed_workspace_lease(lease: MemoryLease) {
eprintln!(
"execution provider workspace deallocation failed before physical release was proven; \
retaining its {} byte {:?} lease in compatibility quarantine",
lease.bytes(),
lease.tier()
);
QUARANTINED_WORKSPACE_LEASES
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.push(lease);
}
Comment on lines +403 to 420
pub fn into_raw_with_owner(self) -> (*mut c_void, Option<BoundBufferOwnership>) {
let ptr = self.ptr.as_ptr();
match self.owner {
BufferOwner::Bound(owner) => (ptr, Some(BoundBufferOwnership::Binding(*owner))),
BufferOwner::Managed(owner) => (ptr, Some(BoundBufferOwnership::Managed(*owner))),
_ => (ptr, None),
}
}

/// Consume the buffer into its raw address and complete binding ownership.
pub fn into_raw_with_bound_ownership(self) -> (*mut c_void, Option<BoundBufferOwnership>) {
let ptr = self.ptr.as_ptr();
match self.owner {
BufferOwner::Bound(owner) => (ptr, Some(*owner)),
BufferOwner::Bound(owner) => (ptr, Some(BoundBufferOwnership::Binding(*owner))),
BufferOwner::Managed(owner) => (ptr, Some(BoundBufferOwnership::Managed(*owner))),
_ => (ptr, None),
}
}
justinchuby added a commit that referenced this pull request Aug 22, 2026
> **A100 safety update (head `13c37a7f`):** expected shared-prefix
admission failures are now all-or-private: K/V commit is transactional,
a failed V share rolls K back before enqueue, and only a successful
rollback permits private-KV fallback. Fatal kernel faults remain
process-fatal; the new cross-process VMM probe proves a worker fault
does not affect an actively executing peer, while the current server is
still in-process and does not yet consume physical shared-prefix
metadata. See the [final verified
conclusion](#1579 (comment)).

Collapses phases 1–7 of the #1186 memory architecture rework onto
current `main` as a single merge. The stack forked 270 commits ago (176
of them touching `crates/`), so rebasing it layer by layer would mean
solving the same 17 conflicts fourteen times, with no reviewer ever
looking at the intermediate states.

Closes the stack: #1252 #1263 #1279 #1283 #1301 #1341 #1349 #1426 #1440
#1448 #1454 #1462 #1465 #1468 #1533.

**A review guide is in the first comment.** It is the part worth reading
— this diff is 103 files, but only three decisions in it are ones a
compiler cannot check.

## What was already reviewed, and what wasn't

Every phase was reviewed and approved by a session that was not its
author, under the rejection-lockout rule (a rejected author never writes
the next revision).

| Phase | PR | Verdict |
|---|---|---|
| 1–5 | #1252 → #1426 | approved |
| 6 | #1440, #1448, #1454 | **rejected** ×3 |
| 6 | #1462 | approved |
| 7 | #1465 | **rejected** |
| 7 | #1468 | approved |
| 7 test fixes | #1533 | approved, A100-verified |

The four rejected rounds were not replaced — the later rounds are
stacked **on top of** them. So the tree here is the approved state, but
the history contains the rejected commits. **#1440 / #1448 / #1454 /
#1465 should not be reviewed individually**; they close automatically.

**Not reviewed anywhere:** the conflict resolutions themselves. That is
what this PR is for.

## Verification

Run here, on this merged tree:

- `cargo check --workspace --all-targets` — clean
- `cargo check -p onnx-genai-engine --features cuda,native-backend
--all-targets` — clean. Worth calling out: the default feature set does
**not** compile the `cfg(cuda)` code, which is where the riskiest edits
are. Checking only the default set would have missed a real break (see
the guide).
- `cargo test` over 7 crates — **1105 passed, 2 failed, 88 ignored**
- `cargo clippy --workspace --all-targets`
- `cargo fmt`

**The 2 failures and the 1 clippy error are pre-existing and proven so,
not merge damage:**

-
`platform_capacity::{disk_capacity_is_measured_for_the_working_directory,
an_explicit_byte_limit_is_honored_without_a_device_query}` —
`platform_capacity.rs` is byte-identical to `main` (`git diff
origin/main HEAD -- ` that file is empty). The cause is a macOS-only FFI
layout bug: `fsblkcnt_t` is 4 bytes on macOS (confirmed: `sizeof(struct
statvfs)=64`, `sizeof(fsblkcnt_t)=4`) while the Rust struct declares
`f_blocks`/`f_bfree`/`f_bavail` as `u64`. CI is Linux, where it is 8
bytes. Left alone: it is a real bug but not this PR's.
- `optimizer.rs` clippy `approx_constant` — present on both parents; the
merged file is byte-identical to `main`.
- Three files had `rustfmt` drift already present on `main`
(`matmul_nbits.rs`, `normalization.rs`, `optimizer.rs`). Reverted rather
than swept in, to keep the diff readable.

**Final A100 revalidation (head `13c37a7f`):** full CUDA-memory GPU
suite passed; CUDA EP default-parallel lib suite passed (**488 passed /
17 ignored**); real fp16 GQA covered three requests × two interleaved
decode steps with two shared peers and one transactional private
fallback, byte-identical to independent GPU and CPU references. A
device-started/event-gated worker remained healthy through a peer
process `CUDA_ERROR_ILLEGAL_ADDRESS`, owner exit, replacement worker,
and process restart. Governor tests, targeted Miri, CI Clippy, fmt, and
CUDA honesty also passed.

## Still open after this merges

- `memory-plugin-provider-wiring` — the back half of Phase 6 criterion 8
(provider/context pinning), which fell in the gap between phases.
**#1186 must not be closed until it lands.** This merge makes it more
tractable, not less: see decision 3 in the guide.
- `memory-deferred-invariant-asserts` — two surviving mutants found by
#1462's reviewer, adjudicated NON-BLOCKING and not defects.
- #1533's CUDA honesty guard warns on a legitimately portable anchor.
Should be moved out of the CUDA test binary or allow-listed rather than
left warning.

---------

Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Copilot-Session: 46c5d75b-8146-489c-b82f-08ee29c27ce4
Copilot-Session: 39ff6824-d35f-4d3b-8f5f-043a7119a100
Copilot-Session: c80f8522-983c-47f7-8241-2155a823aabe
@justinchuby

Copy link
Copy Markdown
Owner Author

Closing: superseded by #1579, which collapsed phases 1–7 into a single merge — landed as a36964280 (squash, 126 files, +43956 −3286).

This PR was approved by a session that was not its author, and its work is in main.

Containment verified, not assumed:

$ git merge-base --is-ancestor c65cf0367 ebceab071   # ebceab071 = #1579 head
→ ancestor

Every one of the 15 stack heads (#1252 → #1533) is a literal ancestor of the merged head, and I separately confirmed the merged head's content reached main: of the 126 files #1579 touched, exactly one differs from main — crates/onnx-runtime-session/src/executor/tests.rs, where main carries 120 extra lines from #1703 (Expand-broadcast mask tests). That is the conflict resolution correctly preserving the other side, not content loss.

Why this didn't close itself: #1579's body says Closes the stack: #1252 #1263 …, which GitHub does not parse — the closing keyword must be immediately followed by the reference, and in any case closing keywords only ever close issues, never pull requests. So all 15 stayed open by mechanism, not by intent.

#1186 stays open: #1579 explicitly gates it on memory-plugin-provider-wiring (the back half of Phase 6 criterion 8, provider/context pinning), which has not landed.

justinchuby added a commit that referenced this pull request Aug 22, 2026
…ees were destroyed (#1717)

Two decision records existed **only on this machine** — written into
`.squad/decisions/inbox/` but never committed. Recovered here.

The inbox README already names this exact failure mode as the reason the
directory was made tracked on 2026-07-29:

> **Drops survive worktree deletion.** Previously the inbox was
gitignored, so drops existed only on the machine that wrote them.
Removing a worktree before Scribe ran destroyed unmerged drops — this
cost real records more than once.

The policy fixed the *gitignore* half of the problem. It cannot fix the
half where a drop is written and simply never `git add`ed, which is what
happened to both of these. I found them while cleaning up 11 stale
worktrees; one was inside a worktree I was about to delete.

## What is being recovered

**1. `Copilot-phase-5-process-memory-manager-ownership-boundarie.md`**

Written in the `justinchuby-phase-5-process-memory-manager` worktree (PR
#1426, now closed as superseded by #1579). Copied out immediately before
that worktree was removed.

Verified it is not a duplicate before adding it:

```
$ git grep 'Phase 5 process memory manager ownership boundaries' origin/main -- .squad
(no match)
```

So the Scribe never merged it — it was not sitting redundantly next to
an existing entry in `.squad/decisions.md`.

Content-wise this one is not merely historical. It records the Phase 5
ownership split: `ProcessMemoryManager` owns process quota, canonical
authority/view mapping, holder identity, context transaction gates, loss
broadcast and settlement coordination, while **governors keep budget
policy, holders keep victim choice, and EP contexts keep
stream/copy/commit/decommit/fence/deferred-release ordering**. It also
records two invariants that are easy to get wrong later — quarantine
never infers a refund from allocation length, and confirmed context
termination is the sole device-loss discharge boundary.

That boundary is still load-bearing after #1579, and **#1186 is still
open precisely on the provider/context-pinning half of it**, so losing
this note would have removed context from work that has not happened
yet.

**2.
`coordinator-criterion-failure-is-structural-not-probabilistic.md`**

Written in this worktree. References #1579, #1586, #1533, #1600 and
three sessions that converged on it independently.

## Scope

No code changes; two files under `.squad/decisions/inbox/`. Per the
inbox contract these are transient — the Scribe merges them into
`.squad/decisions.md` and deletes the drops.

## Worth considering separately

Nothing detects an uncommitted drop. Both of these survived by
coincidence, because a human happened to be cleaning worktrees and
looked at `git status` before running `git worktree remove --force`. A
pre-removal check for untracked files under `.squad/decisions/inbox/`
would close that gap; I have not written one here.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c80f8522-983c-47f7-8241-2155a823aabe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants