Skip to content

[CUDA] Bound GQA non-windowed workspace via the KV-length envelope (route-aware L1 estimate) - #33016

Merged
Chi Lo (chilo-ms) merged 8 commits into
mainfrom
chilo/gqa-workspace-nonwindowed-estimate
Oct 15, 2026
Merged

Chi Lo (chilo-ms) merged 8 commits into
mainfrom
chilo/gqa-workspace-nonwindowed-estimate

Conversation

@chilo-ms

@chilo-ms Chi Lo (chilo-ms) commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds opt-in, route-aware Level-1 workspace estimation for non-windowed CUDA GroupQueryAttention using the declared total-KV envelope from ep.cuda.gqa_workspace_max_total_sequence_length.

The present-cache capacity bound is max(allocated past-cache capacity, declared total-KV envelope). Static caches may be larger than the active sequence: GQA permits total_sequence_length <= past_sequence_length and retains that allocated capacity. Bounding only the envelope would underestimate MEA expansion and partial-alias preservation.

The existing graph-free aggregate sizes reachable routes and admitted prompt/decode phase corners; this PR does not introduce a second sizing engine.

Envelope validation and caller contract

  • Explicit values are parsed once during session initialization and must be decimal integers in [0, INT32_MAX], matching the runtime ABI. Malformed, negative, overflowing, and greater-than-INT32_MAX values fail initialization.
  • Zero remains the unspecified value. With the envelope unset or zero, non-windowed estimation is unavailable; windowed estimation continues to use its cache-shape bound.
  • The envelope must cover the query-length bound, and the resulting cache-capacity bound must fit the runtime int32 ABI.
  • The query/max-shape and total-KV declarations are estimation inputs, not runtime-enforced admission limits or a no-OOM guarantee. The proof applies only within the caller-declared workload envelope; runtime enforcement remains follow-up work.

Workspace accounting and proof policy

  • Capacity-sensitive preparation and MEA expansion cover the full static-cache extent, even when it exceeds the active total-KV envelope.
  • Partial aliasing copies the full past tensor. Its preservation scratch coexists with the chosen route and is added above the per-route maximum, bounded by the full cache-capacity bound.
  • Preparation recipes already account for sequence-length vectors; there is no new missing-vector gap.
  • Reachable cuDNN without a workspace oracle, unsupported attention-bias cases, and otherwise unprovable route domains conservatively make the estimator unavailable.
  • For capacity-aware CUDA placement, every GQA candidate must have a successful analytical estimate. If proof is unavailable, CUDA assignment for that node is declined.
  • The generic accountant fallback is an ad hoc safety margin, not a proven GQA bound. Profile data is empirical and likewise cannot substitute for the missing analytical proof. Unrelated operators retain their existing generic fallback behavior.

Regression coverage

  • Accepted boundary value INT32_MAX; rejected INT32_MAX + 1, INT64_MAX, decimal overflow, malformed text, and negative values.
  • Unset-envelope behavior and monotonicity when the envelope increases.
  • Static past capacity 1024 with total-KV envelope 512, checking full preservation and MEA expansion against complete concrete runtime recipes.
  • An envelope below the query-length bound still declines, even when the static cache is larger.
  • Static capacity outside the runtime int32 ABI declines.
  • Parsed-envelope plumbing and unchanged windowed estimates.
  • Additive full-past preservation above the per-route maximum.
  • Capacity-aware CUDA placement declines GQA when the analytical estimator is unavailable, including when profile data exists; unrelated operators retain generic fallback.

Dependencies

Targets main, which now includes #33118's shared preparation-sizing implementation. The duplicated sizing commits were dropped during the post-merge rebase; this PR contains only its envelope-estimation, hardening, test, and documentation changes.

Part of the policy-based workspace-estimation series described in #32944. Committed per-(node, phase) route policy and runtime workload-envelope enforcement remain follow-up work.

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.

🟡 Changes recommended

Valid static-cache executions can exceed the estimate when allocated past capacity is larger than the declared total-length envelope.

1 open finding
What changed in this PR

Adds opt-in, envelope-bounded workspace estimation for non-windowed CUDA GroupQueryAttention.

Changes:

  • Uses the configured KV-length envelope for non-windowed estimates.
  • Accounts for partial-alias past-cache preservation scratch.
  • Adds envelope and monotonicity tests.
File Description
group_query_attention_workspace_estimate_test.cc Tests non-windowed envelope behavior and preservation accounting.
cuda_execution_provider.cc Updates envelope-consumption commentary.
group_query_attention_workspace_estimate.h Documents the active envelope configuration.
group_query_attention_workspace_estimate.cc Builds non-windowed bounds from the envelope.
group_query_attention_workspace_bounds.h Adds preservation-scratch accounting state.
group_query_attention_workspace_bounds.cc Adds preservation bytes to aggregate workspace.
onnxruntime_session_options_config_keys.h Documents the session option’s behavior.
resource_accountant.h Removes obsolete reader-only wording.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@titaiwangms Ti-Tai Wang (titaiwangms) 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.

Reviewed at a17ecc6de4ee533fbdf58ee42edfaf4fca226210.

No blocking source finding established. The bound now accounts for allocated cache capacity independently of the active KV envelope, and adds partial-alias preservation on top of the backend maximum. The explicit L1-only scope and the absence of runtime envelope enforcement are appropriate for this slice; missing L2 forwarding is not a blocker for this PR.

Two small documentation updates would help:

  • The bounds header still says callers must separately account for partial-alias preservation, although the new flag makes the aggregate account for it. See the inline comment.
  • Please update docs/annotated_partitioning/attention_workspace_estimation.md, which still describes successful graph-adapter estimation as sliding-window-only. Describe positive-envelope non-windowed eligibility, remaining decline conditions such as a reachable cuDNN route without an oracle, and the unchanged generic accountant fallback when estimation is unavailable.

Validation evidence: In the existing exact-head Linux CUDA CI, the static-cache preservation and MEA-expansion tests, plus the envelope tests, executed and passed. I did not run a local ORT build or tests. This review does not establish a runtime-enforced capacity or no-OOM guarantee, nor does it validate a combined tree with #32071.

@chilo-ms

Copy link
Copy Markdown
Contributor Author

Thanks for the exact-head review. Both documentation findings are addressed in the prepared local hardening stack at 1f175d33b3:

  • the bounds contract now says the aggregate assumes both K/V pairs alias unless account_partial_alias_preservation is enabled, in which case it adds the worst-case preservation copy;
  • the Attention roadmap now describes positive-envelope non-windowed eligibility, max(allocated cache capacity, envelope), remaining unavailable cases (including reachable cuDNN without an oracle), and the final proof-unavailable policy.

The final policy intentionally requires a proven GQA bound for capacity-aware CUDA placement: unavailable GQA estimation declines assignment, while generic fallback remains unchanged for unrelated operators and profile data cannot substitute for the missing proof. The update is committed locally but will be published after #33118 merges so the old duplicated sizing stack can be dropped cleanly. The public head remains a17ecc6de4ee533fbdf58ee42edfaf4fca226210 until then; no re-review is requested yet.

@chilo-ms
Chi Lo (chilo-ms) force-pushed the chilo/gqa-workspace-nonwindowed-estimate branch from a17ecc6 to 1f175d3 Compare October 9, 2026 22:56
Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 9, 2026
## Summary

Complete the preparation-sizing half of Stage 1 in the policy-based,
route-aware workspace-estimation design (#32944). This combines the
agreed QKV-preprocessing refactor and the remaining cache/sequence
preparation sizing into one PR.

Builds on #32945, now merged into `main`, which consolidates MEA,
unfused, XQA and Flash backend scratch sizing.

- Share checked cache-row, past-preservation, staging, compaction,
sequence-vector and QKV-preprocessing calculations between
`ComputeInternal` and `GetGQAPreparationRecipe`.
- Remove the duplicate `GQABufferRequirements` implementation.
- Keep the existing separate allocations, ownership, pointer wiring,
dispatch priorities and lifetimes.
- Preserve the preparation recipe's aligned layout and full validation;
the live kernel calls narrow sizing helpers rather than inheriting
unrelated recipe-admission restrictions.

This is sizing-code consolidation, not a missing-buffer fix. It does not
activate dispatch policies, exclude routes from L1, coalesce
allocations, or change static-preallocation strategy.

## Compatibility details

- Cache sizing runs before backend selection and retains the original
present-cache capacity. Windowed staging still changes the runtime
extent from `C` to `C + S`.
- Past preservation uses the aliased tensor's physical row width;
staging uses the physical output row width; compaction retains its
packed-row interpretation. INT4 rows are not packed a second time.
- QKV sizing runs after the existing backend selection and receives the
effective post-staging capacity. cuDNN retains the existing generic
Q-materialization behavior.
- Flash fast decode still reuses the device sequence-length input
instead of allocating sequence vectors, and still suppresses the
QKV-preprocessing buffer.
- Existing healthy-input byte counts are retained. Arithmetic overflow
and invalid sizing inputs produce explicit sizing statuses at the
runtime boundary.

## Coverage and validation

- Fresh standalone build of the existing preparation Google Test source:
**50 tests passed**, including the legacy QKV feature matrix,
dense/byte/INT4 cache geometry, physical packed rows, sequence-vector
suppression, invalid dimensions and overflow.
- MSVC `/W4 /WX` compilation of the actual CUDA GQA runtime translation
unit in both shared-provider and plugin-adapter configurations, with
Flash, MEA, FP8 and INT4 enabled.
- MSVC `/W4 /WX` compilation of the graph-time estimator and XQA/Flash
sizing-test translation units.
- MSVC `/W4 /WX` compilation of the CUDA GQA operator-test translation
unit.
- Four new CUDA regressions compare packed and separate QKV on
unfused/Flash paths, including windowed staging. They reuse the aliasing
helper, which disables CPU fallback and verifies actual backend
dispatch.
- `clang-format --dry-run --Werror` and `git diff --check` passed.

**Full CUDA inference execution is not validated locally against a
freshly linked provider.** The new operator regressions and existing GQA
tests need full CUDA build/test CI.

## Stack

Base: `main`. The conflict-resolution merge retains the early
preparation-sizing initialization, the upstream seq-free eligibility
helpers, and the INT8/FP8 storage-width normalization.

The envelope and policy-reader PRs (#33014, #33016 and #33042) remain
independent of this refactor. Committed-policy resolution,
coverage/envelope enforcement, bounded fallback and policy-bound
estimates remain follow-up work.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Chi Lo (chilo-ms) and others added 4 commits October 9, 2026 16:12
…oute-aware L1 estimate)

Make the GQA Level-1 workspace estimator produce a sound bound for the
non-windowed KV-cache case instead of declining. Previously BuildBounds
returned nullopt for every non-windowed node because total_sequence_length
is a runtime scalar not recoverable from shapes. It now uses the declared
KV-length envelope (config.max_total_sequence_length, from session option
ep.cuda.gqa_workspace_max_total_sequence_length, plumbed in the prior PR)
as present_kv_cache_capacity_bound and evaluates the existing route-aware,
phase-cornered aggregate over it.

Opt-in and default-off: when the envelope is unset (== 0) the non-windowed
path still returns nullopt, so behavior is byte-identical to today. The
windowed path is untouched -- every change is guarded by the windowed flag.

Soundness:
- Backend workspace is non-decreasing in KV length and the runtime sizes
  non-windowed scratch from total_sequence_length <= envelope
  (GetGQAEffectiveWorkspaceKvLength), so capacity_bound = envelope is an
  upper bound.
- Non-windowed partial aliasing (exactly one past/present K/V pair shared)
  copies the full past cache into preservation scratch that coexists with
  the backend workspace. This is now charged on top of the per-route
  maximum via GQAWorkspaceBounds::account_partial_alias_preservation,
  bounded by the envelope and head_size_bound. Windowed GQA rejects
  non-shared buffers, so it never incurs this copy.
- cuDNN (when reachable) and attention bias conservatively decline (nullopt),
  keeping the estimate an upper bound. The small seq_lens buffer remains an
  unmodeled pre-existing gap shared with the windowed path.

Tests (onnxruntime_provider_test): non-windowed without an envelope still
declines; with an envelope it is bounded and monotonic in the envelope; the
preservation copy is additive on top of the selected route.

Part of the policy-based workspace estimation effort; see
docs/annotated_partitioning/policy_based_workspace_estimation.md and the PR
chain (#32944, #32945, #33001, #33014). Base retargets to main after #33014
merges.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Use the larger of allocated past capacity and the total-KV envelope to cover MEA expansion and full partial-alias preservation. Keep the query-length envelope check independent and add static-cache and runtime-ABI regressions.

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

Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Reject out-of-ABI envelopes during initialization, require a proven route-aware GQA estimate for capacity-aware CUDA placement, and keep the caller-declared envelope contract explicit. Share the validated parser for Level-2 reuse and cover invalid values plus fail-closed placement.

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

@titaiwangms Ti-Tai Wang (titaiwangms) 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.

Reviewed the four-commit delta from the approved #33118 head (80292cc) to 1f175d3. The branch contains that exact preparation baseline; the divergent main comparison results from the squash merge, not missing preparation changes.

No blocking source findings. The non-windowed estimate covers the greater of allocated cache capacity and the declared envelope, plus worst-case partial-alias preservation. The shared parser preserves zero/unset semantics while rejecting values outside the runtime int32 ABI. In the in-tree capacity-aware CUDA path, an unavailable GQA estimate now declines assignment before generic fallback or profile accounting; ordinary placement without an accountant is unchanged. The corresponding placement and parser boundary tests are present, and the previously noted stale envelope documentation has been updated.

Nonblocking documentation follow-up: please qualify the new fail-closed guarantee as applying to the in-tree CUDA estimation path. BUILD_CUDA_EP_AS_PLUGIN still excludes the GQA Level-1 estimation block and its integration tests, so this PR does not establish the same guarantee for CUDA-plugin placement.

Validation caveat: at the latest check snapshot for this exact head, 27 checks succeeded, one was neutral, 57 were pending, and none had failed. The CUDA jobs were still running, so I have not verified execution of the new placement/static-cache boundary tests on this revision. This is a source-review approval, not an assertion that CI is complete or that the declared envelope is runtime-enforced. No local build or tests were run.

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

Copilot-Session: c132130a-b0c7-422c-9ff7-75826b6ea3fc

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.

🟡 Changes recommended

CUDA plugin accounting bypasses the new fail-closed GQA estimation requirement and still uses the generic fallback.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Chi Lo and others added 2 commits October 14, 2026 09:16
Reject new capacity-aware CUDA plugin GQA assignments until analytical estimates reach host accounting, including fused groups. Preserve normal placement and previously accounted nodes, document the limitation, and add regression coverage for generic and profiled accounting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
Use IndexedSubGraph::IsAccountingEnabled() instead of accessing its private resource_accountant member. Preserve the accounting assertion while fixing the cross-platform CI compilation failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9

@titaiwangms Ti-Tai Wang (titaiwangms) 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.

Reviewed the isolated change from 11cba206fbff12b570264ac8e188cf3841cd9487 to 83019a940215caaff5694eaec613df1ad8237e3c with the five-role review team. No introduced Major/Critical blocker was established.

The admission behavior is appropriately fail-closed: unavailable analytical GQA proof is rejected before profile/accountant selection, and the plugin path rejects the entire proposed CUDA group rather than partially accepting it. The bound accounts for allocated cache capacity and partial-alias preservation, not just active KV length. The real CUDA plugin uses the canonical provider name matched by this guard.

Non-blocking suggestion: add an in-tree CUDA placement regression combining an available low-cost CSV row with unavailable analytical proof. The production ordering is correct, but the plugin regression exercises a different implementation path.

Validation evidence: actual logs from exact-head jobs 115974061041 and 116000568459 show passes for PluginExecutionProviderTest.GetCapability_CudaGqaRequiresProvenEstimate, RealAccountantTest.FactoryParsesGqaWorkspaceEnvelope, and RealAccountantTest.FactoryRejectsInvalidGqaWorkspaceEnvelope. These framework/plugin results are not evidence that the in-tree CUDA workspace tests executed. No local build, Run test, sanitizer, benchmark, concurrent-Run stress, or physical-memory measurement was performed.

The envelope remains a caller-declared workload bound, not a runtime sequence-length clamp or a whole-session no-OOM guarantee. CUDA placement rejection may leave a model without a supported CPU fallback; this review does not promise successful fallback for every dtype/cache configuration.

Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 15, 2026
### Description
<!-- Describe your changes. -->

Make `NchwcOptimizerTests.ConvWithZeroInputChannelsIsNotTransformed` a
graph-only regression instead of running the zero-channel convolution
through `NchwcOptimizerTester`.

- Build and resolve the same zero-input-channel Conv with and without
bias.
- Assign the Conv to the CPU EP and apply `NchwcTransformer` directly,
following the adjacent rank-validation regression's pattern.
- Assert that the transformer reports no modification, the ordinary Conv
remains, and no NCHWc Conv, ReorderInput, or ReorderOutput nodes are
inserted.
- Explicitly skip platforms without NCHWc support.

No production optimizer or kernel changes. The zero-output-channel
regression and other numerical optimizer tests are unchanged.

Validation: whitespace (`git diff --check`) and changed-range
clang-format checks passed. No local build or C++ test run, as
requested.

### Motivation and Context
<!-- - Why is this change required? What problem does it solve?
- If it fixes an open issue, please link to the issue here. -->

Follow-up to #33346. The new regression currently executes the
degenerate Conv at optimization levels 2 and 3 and compares the outputs.
macOS arm64 CI reports NaN-versus-zero mismatches at
`nchwc_optimizer_test.cc:235`, despite this test's intended contract
being that NCHWc conversion is skipped.

The same assertion is confirmed independently on upstream `main` and
multiple PRs:

- [main at 9cdf0f1, macOS WebGPU arm64
Release](https://github.com/microsoft/onnxruntime/actions/runs/38675468798/job/116227944699)
- [#33016, macOS WebGPU arm64
Debug](https://github.com/microsoft/onnxruntime/actions/runs/38599291872/job/116132732574)
- [#33347, macOS WebGPU arm64
Debug](https://github.com/microsoft/onnxruntime/actions/runs/38707806696/job/116332127965)

Check the optimizer's graph contract directly without making this
regression depend on zero-channel Conv execution. This is a test-only CI
fix, not a correction to CPU Conv's zero-channel numerical behavior.

Co-authored-by: Chi Lo <lochi@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
@chilo-ms
Chi Lo (chilo-ms) enabled auto-merge (squash) October 15, 2026 20:46
Include the graph-only zero-input-channel NCHWc regression fix from PR #33398 while preserving the GQA workspace estimation hardening.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
kunal-vaishnavi pushed a commit that referenced this pull request Oct 15, 2026
### Description
<!-- Describe your changes. -->

Make `NchwcOptimizerTests.ConvWithZeroInputChannelsIsNotTransformed` a
graph-only regression instead of running the zero-channel convolution
through `NchwcOptimizerTester`.

- Build and resolve the same zero-input-channel Conv with and without
bias.
- Assign the Conv to the CPU EP and apply `NchwcTransformer` directly,
following the adjacent rank-validation regression's pattern.
- Assert that the transformer reports no modification, the ordinary Conv
remains, and no NCHWc Conv, ReorderInput, or ReorderOutput nodes are
inserted.
- Explicitly skip platforms without NCHWc support.

No production optimizer or kernel changes. The zero-output-channel
regression and other numerical optimizer tests are unchanged.

Validation: whitespace (`git diff --check`) and changed-range
clang-format checks passed. No local build or C++ test run, as
requested.

### Motivation and Context
<!-- - Why is this change required? What problem does it solve?
- If it fixes an open issue, please link to the issue here. -->

Follow-up to #33346. The new regression currently executes the
degenerate Conv at optimization levels 2 and 3 and compares the outputs.
macOS arm64 CI reports NaN-versus-zero mismatches at
`nchwc_optimizer_test.cc:235`, despite this test's intended contract
being that NCHWc conversion is skipped.

The same assertion is confirmed independently on upstream `main` and
multiple PRs:

- [main at 9cdf0f1, macOS WebGPU arm64
Release](https://github.com/microsoft/onnxruntime/actions/runs/38675468798/job/116227944699)
- [#33016, macOS WebGPU arm64
Debug](https://github.com/microsoft/onnxruntime/actions/runs/38599291872/job/116132732574)
- [#33347, macOS WebGPU arm64
Debug](https://github.com/microsoft/onnxruntime/actions/runs/38707806696/job/116332127965)

Check the optimizer's graph contract directly without making this
regression depend on zero-channel Conv execution. This is a test-only CI
fix, not a correction to CPU Conv's zero-channel numerical behavior.

Co-authored-by: Chi Lo <lochi@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
(cherry picked from commit 1988019)
@chilo-ms
Chi Lo (chilo-ms) merged commit 327d11a into main Oct 15, 2026
@chilo-ms
Chi Lo (chilo-ms) deleted the chilo/gqa-workspace-nonwindowed-estimate branch October 15, 2026 22:53
Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 16, 2026
… estimator (reader-only)

Adds the hardware-neutral session option session.attention_dispatch_policy and
threads its value through to the CUDA GroupQueryAttention Level-1 workspace
estimator. This is the Tier-1 intent knob from the policy-based workspace
estimation design: "latency" favors high-workspace fast routes, "memory"
favors low-workspace routes (so more nodes fit on GPU), and "safe" assumes the
bounded fallback everywhere. Unset (default) or any unrecognized value means
"auto" and keeps the current route-aware behavior.

This is a pure no-functional-change reader. The raw string is parsed
(WorkspaceEstimatorConfig::attention_dispatch_policy ->
AttentionDispatchPolicy enum via ParseAttentionDispatchPolicy) and stored on
GQAWorkspaceEstimateConfig::dispatch_policy, but the estimator does not yet
consume it, so every policy value produces the identical estimate. A follow-up
PR biases route selection on the committed policy (coverage validation and the
bounded fallback land in their own PRs).

Plumbing mirrors the ep.cuda.gqa_workspace_max_total_sequence_length reader.
The enum/parser live in the CUDA GQA estimator header because it is the only
consumer today; hoist to a neutral header when a second op consumes it.

Tests: extends the FactoryRetainsNarrowWorkspaceEstimatorConfig framework test
(config string -> WorkspaceEstimatorConfig field, CPU CI) and adds a
provider-test asserting the token parsing (exact-match, unknown -> Auto) and
the reader-only invariant (no policy value changes the estimate).

Part of the policy-based workspace estimation effort; see
docs/annotated_partitioning/policy_based_workspace_estimation.md and the PR
chain (#32944, #32945, #33001, #33014, #33016). Base retargets toward main as
the stack merges.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
Akshay Sonawane (apsonawane) pushed a commit that referenced this pull request Oct 19, 2026
### Description
<!-- Describe your changes. -->

Make `NchwcOptimizerTests.ConvWithZeroInputChannelsIsNotTransformed` a
graph-only regression instead of running the zero-channel convolution
through `NchwcOptimizerTester`.

- Build and resolve the same zero-input-channel Conv with and without
bias.
- Assign the Conv to the CPU EP and apply `NchwcTransformer` directly,
following the adjacent rank-validation regression's pattern.
- Assert that the transformer reports no modification, the ordinary Conv
remains, and no NCHWc Conv, ReorderInput, or ReorderOutput nodes are
inserted.
- Explicitly skip platforms without NCHWc support.

No production optimizer or kernel changes. The zero-output-channel
regression and other numerical optimizer tests are unchanged.

Validation: whitespace (`git diff --check`) and changed-range
clang-format checks passed. No local build or C++ test run, as
requested.

### Motivation and Context
<!-- - Why is this change required? What problem does it solve?
- If it fixes an open issue, please link to the issue here. -->

Follow-up to #33346. The new regression currently executes the
degenerate Conv at optimization levels 2 and 3 and compares the outputs.
macOS arm64 CI reports NaN-versus-zero mismatches at
`nchwc_optimizer_test.cc:235`, despite this test's intended contract
being that NCHWc conversion is skipped.

The same assertion is confirmed independently on upstream `main` and
multiple PRs:

- [main at 9cdf0f1, macOS WebGPU arm64
Release](https://github.com/microsoft/onnxruntime/actions/runs/38675468798/job/116227944699)
- [#33016, macOS WebGPU arm64
Debug](https://github.com/microsoft/onnxruntime/actions/runs/38599291872/job/116132732574)
- [#33347, macOS WebGPU arm64
Debug](https://github.com/microsoft/onnxruntime/actions/runs/38707806696/job/116332127965)

Check the optimizer's graph contract directly without making this
regression depend on zero-channel Conv execution. This is a test-only CI
fix, not a correction to CPU Conv's zero-channel numerical behavior.

Co-authored-by: Chi Lo <lochi@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 21, 2026
### Description
<!-- Describe your changes. -->

Add reader-only plumbing for the hardware-neutral session option
`session.kernel_dispatch_policy`.

The intent is generic across operators and EPs. `KernelDispatchPolicy`
and the shared, header-only `TryParseKernelDispatchPolicy` live in
`core/framework/kernel_dispatch_policy.h`, with no CUDA or graph
dependencies. CUDA GQA is the first consumer, not the owner of the
policy.

For valid values, **no policy changes runtime dispatch, workspace
estimates, or partitioning** in this PR. `latency`, `memory`, and `safe`
remain intent metadata for future policy resolution. In particular,
`memory` does not reduce the reservation, and `safe` does not select a
bounded fallback or provide a no-OOM guarantee.

#### Reader contract

- Unset, empty, and explicit `auto` resolve to `Auto`. The other valid
tokens are exactly `latency`, `memory`, and `safe`.
- Unknown, differently cased, whitespace-only, and whitespace-padded
non-empty values fail session initialization with `INVALID_ARGUMENT`;
they never silently resolve to `Auto`. Validation runs even when
partitioning is disabled.
- The accountant factory retains the validated raw optional string
without changing other settings. The CUDA consumer enforces successful
parsing rather than silently accepting an invalid configuration.
- `GetGroupQueryAttentionWorkspaceEstimateConfig` accepts
`dispatch_policy = KernelDispatchPolicy::Auto` alongside the envelope
and initializes both fields in one place. The node-estimator overload
and CUDA test bridge forward the policy through that helper.
- Setting the policy alone does not enable partitioning or non-windowed
GQA estimation without a KV envelope.
- This replaces the earlier, unreleased attention-specific option name;
there is no compatibility alias.

#### Integration and regression coverage

Preserve #33014/#33016's envelope validation against `[0, INT32_MAX]`,
invalid-value diagnostics, and fail-closed GQA placement when no proven
estimate is available. Reuse the exported graph-config helper instead of
duplicating node parsing. Keep envelopes, eligibility, and sizing local
to GQA; other operators and EPs can adopt the shared intent
independently.

Framework regressions cover valid-token parsing, rejected tokens
(including `Memory`, `SAFE`, `memroy`, and ` memory `), unchanged parser
output on failure, validation with partitioning enabled and disabled,
raw-token retention, unset defaults, and policy-only configuration not
enabling partitioning. Existing KV-envelope boundary tests remain
intact.

CUDA metadata regressions cover default and explicit policy forwarding
through the node-config helper/test bridge, identical bytes/backend sets
for windowed and non-windowed estimation, and unchanged decline without
a non-windowed envelope.

#### Validation for this review update

- Changed-file lintrunner/clang-format check: passed for all 10 edited
C++ files.
- `git diff --check`: passed.
- No full ORT build or C++ regression suites were run locally for this
update, as requested. Earlier-head build evidence does not validate this
head; full framework/CUDA validation is left to CI.

### Motivation and Context
<!-- - Why is this change required? What problem does it solve?
- If it fixes an open issue, please link to the issue here. -->

Provide a generic policy representation and reader without coupling
other consumers to GQA or CUDA. Rejecting typos now prevents future
enforcement (including #33316's committed policies) from silently
falling back to `Auto` and bypassing the requested restrictions.

This PR builds on #33016, which is now merged, and targets `main`.
Committed policies, operator/EP-specific resolution, route restrictions,
runtime coverage/envelope enforcement, bounded fallback, and reduced
policy-bound estimates remain separate follow-ups.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Chi Lo <lochi@microsoft.com>
Copilot-Session: b95559c4-5cbe-4787-8dd7-ffa45a2157f9
Copilot-Session: 24b20465-fd4d-4a64-8174-aa0622c5164d
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.

3 participants