Skip to content

[CUDA] Extract GQA seq-free route eligibility into shared helpers - #33001

Merged
Chi Lo (chilo-ms) merged 2 commits into
mainfrom
chilo/gqa-route-eligibility-helper
Oct 7, 2026
Merged

Chi Lo (chilo-ms) merged 2 commits into
mainfrom
chilo/gqa-route-eligibility-helper

Conversation

@chilo-ms

Copy link
Copy Markdown
Contributor

Description

Factors the sequence-length-independent parts of the CUDA GroupQueryAttention backend-selection logic out of GroupQueryAttention::ComputeInternal into a new header-only module, group_query_attention_eligibility.h. It provides four pure "seq-free" route-eligibility predicates:

Helper Route
IsGQAXqaEligibleSeqFree<U> XQA (Extreme Query Attention) decode kernel
IsGQACudnnSdpaCoreEligibleSeqFree<T, U> cuDNN SDPA
IsGQAFlashEligibleSeqFree<T> FlashAttention
IsGQAMemoryEfficientEligibleSeqFree<T> CUTLASS memory-efficient attention

A seq-free predicate depends only on partition-time facts (geometry, device capability, quantization, feature flags) and not on the per-step sequence length or phase (prompt vs decode). The runtime keeps the sequence-length-dependent gates and dispatch priorities at the call sites:

  • XQA — phase gates (is_first_prompt / sequence_length == 1 / kv_sequence_length) and the per-node shared-memory capability probe.
  • cuDNN — cudnn_sdpa::is_stable() and the seq-dependent is_supported(...) probe.
  • Flash / MEA — the !use_xqa / !use_cudnn_sdpa / !use_flash_attention dispatch priority.

No functional change

The eligibility booleans are computed from the same expressions in the same short-circuit-equivalent order. Every moved term is a side-effect-free read, so the selected backend is identical for every input. The GQA operator tests exercise all four routes and validate this (a wrong predicate would dispatch the wrong kernel and fail the tests).

Why header-only

All four helpers are templated on the Q/KV element types (T/U), so the module is header-only rather than the usual .h/.cc split.

Motivation

This is a small NFC step toward policy-based, capacity-aware CPU offloading (see the design doc in #32944). The shared seq-free core is what the Level-1 workspace estimator will later reuse, so runtime route selection and workspace estimation cannot drift.

Relationship to sibling PRs

Factor the sequence-length-independent parts of the CUDA GroupQueryAttention
backend-selection logic (XQA, cuDNN SDPA, FlashAttention, memory-efficient
attention) out of GroupQueryAttention::ComputeInternal into a new header-only
module, group_query_attention_eligibility.h.

Each helper is a pure "seq-free" predicate: it depends only on partition-time
facts (geometry, device capability, quantization, feature flags) and not on the
per-step sequence length or phase. The runtime keeps the sequence-length-
dependent gates and dispatch priorities at the call sites:
  - XQA:   phase gates (is_first_prompt / sequence_length == 1 / kv_sequence_length)
           and the per-node shared-memory capability probe.
  - cuDNN: cudnn_sdpa::is_stable() and the seq-dependent is_supported(...) probe.
  - Flash/MEA: the !use_xqa / !use_cudnn_sdpa / !use_flash_attention priority.

This is a no-functional-change refactor: the eligibility booleans are computed
from the same expressions in the same short-circuit-equivalent order (all the
moved terms are side-effect-free reads), so the selected backend is identical
for every input. The GQA operator tests exercise all four routes and validate
this.

The helpers are templated on the Q/KV element types, so the module is
header-only. This shared definition is the seq-free core that the Level-1
workspace estimator will later reuse so runtime route selection and workspace
estimation cannot drift.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
@chilo-ms
Chi Lo (chilo-ms) marked this pull request as ready for review September 30, 2026 22:08
Copilot AI balanced review requested due to automatic review settings September 30, 2026 22:08

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.

Copilot review overview

🟡 Changes recommended

Runtime-only aliasing is included in a partition-time helper, and the new predicates lack direct regression tests.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Extracts CUDA GQA backend eligibility into reusable header-only predicates for future workspace estimation.

Changes:

  • Adds predicates for XQA, cuDNN SDPA, FlashAttention, and MEA.
  • Refactors runtime dispatch to use the predicates.
File Description
group_query_attention.cc Uses shared eligibility helpers.
group_query_attention_eligibility.h Defines backend eligibility predicates.

💡 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 head 860b3906df9962c596c62770837a12f7ce5cd7f9 against base 967a8e064d08c98b910ed13e9f05604342327df1, in the context of #32944.

The runtime extraction preserves the phase gates, shared-memory fallback, cuDNN probes, and dispatch priority. I did not establish a current runtime route regression. Two actionable issues remain:

1. Minor — the shared helper's partition-time contract includes runtime aliasing

Location: onnxruntime/contrib_ops/cuda/bert/group_query_attention_eligibility.h:10-18,55,70-78.

The header defines "seq-free" as depending only on partition-time facts, but past_present_share_buffer is actual K/V pointer equality calculated during ComputeInternal(). L1 cannot generally know that fact.

This is not evidence of current L1 underestimation: the estimator does not call the helper in this PR. It is a reuse-contract problem. A future caller that leaves unknown aliasing at the default false could incorrectly exclude a reachable XQA route.

Please keep actual aliasing beside the runtime phase gates, or explicitly distinguish runtime eligibility from AOT alias possibility. Also document that these geometry arguments are exact, validated values. Exact eligibility at componentwise maximum geometry is not a proof of reachability over a bounded domain; smaller supported head sizes or groups may still be reachable.

2. Minor — add direct predicate regression tests

Location: group_query_attention_eligibility.h:70-188; this PR adds no direct predicate tests.

Output-only attention tests can pass through a different correct backend after an eligibility regression. Please add table-driven positive cases and individual rejection gates for the four predicates, including:

  • Nonquantized group 5 versus quantized group 5.
  • FP8 SM80 rejection versus SM89/SM90 support, under the appropriate build guards.
  • Bias, quantization, QK normalization, and smooth-softmax restrictions.
  • cuDNN type/window restrictions and Flash/MEA feature gates.

Scope and recommendation

This is runtime-only extraction, not yet a shared bounded L1 reachability implementation. Please address the contract wording/boundary and focused test coverage before using it as that implementation. No build, GPU execution, numerical, or plugin/minimal-build validation was performed for this PR.

Keep actual cache aliasing at runtime, document exact validated geometry rather than bounded reachability, and cover all four eligibility predicates and quantized architecture gates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37ad7066-fcd3-45df-891e-44bee9e1cf04
@chilo-ms

Copy link
Copy Markdown
Contributor Author

Thanks Ti-Tai Wang (@titaiwangms) — addressed both points in 7b19676.

  1. Actual past/present aliasing now stays at the runtime call site alongside the phase gates, outside the seq-free inputs. The header documents exact, validated positive geometry fitting the runtime int32 parameters, with a valid head ratio. It explicitly states that this is not bounded-domain L1 reachability: rejecting componentwise maximum geometry cannot exclude smaller supported shapes. A regression case demonstrates that distinction for XQA head size and group size.
  2. Added direct table-driven/typed tests for all four predicates, covering the requested group-5 distinction, FP8 SM80 rejection versus SM89/SM90 support under build guards, individual bias/quantization/QK-Norm/smooth-softmax gates, cuDNN type/window restrictions, and Flash/MEA feature gates.

Validation: the updated GQA dispatch and all predicate tests compiled with Flash/MEA/FP8 enabled. The host-only predicate suites passed 15 tests with Flash disabled, 13 with Flash and FP8 disabled, and 11 with Flash/MEA/FP8 disabled. Full Flash test execution remains unverified because the standalone Windows harness failed before Google Test startup; no GPU numerical or plugin/minimal-build validation is claimed.

@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.

Follow-up review at 7b19676: both issues from my previous review are addressed.

Actual past/present aliasing is now checked at the runtime dispatch site rather than included in the seq-free predicate inputs. The new direct eligibility tests cover independent feature gates, storage types, architecture differences, and supported/unsupported geometry.

The header now explicitly states that these predicates take exact, validated geometry and are not a bounded-domain Level-1 reachability implementation. That distinction is important: rejecting a componentwise maximum does not exclude a route reachable at smaller supported geometry, and the added test documents this nonmonotonicity.

No additional actionable findings from this updated source review. This slice looks ready from the code-review perspective; the checks currently have no failed or pending entries. This COMMENT is not a formal approval.

Validation scope: static review of the updated implementation and tests, plus inspection of CI status; no new local build or runtime execution at this head.

@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 7b19676. The previous aliasing and direct eligibility-test concerns are addressed; no remaining actionable findings from this updated source review. Approval is limited to this exact-geometry refactor, not bounded-domain estimator reachability. No new local build/runtime execution was performed.

@chilo-ms
Chi Lo (chilo-ms) merged commit ec82e2a into main Oct 7, 2026
@chilo-ms
Chi Lo (chilo-ms) deleted the chilo/gqa-route-eligibility-helper branch October 7, 2026 16:27
Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 7, 2026
…tor (reader-only) (#33014)

## What

Adds the session option
`ep.cuda.gqa_workspace_max_total_sequence_length` and threads its value
into the CUDA GroupQueryAttention (GQA) Level-1 workspace estimator.

`total_sequence_length` (accumulated past + current KV tokens) is a
**runtime scalar input** whose value cannot be recovered from graph
shapes. For the non-windowed KV cache, the backend workspace scales
linearly with it, so the L1 estimator currently returns `std::nullopt`
for that case (it cannot bound the value). This knob lets the user hand
the estimator that **KV-length envelope** directly, for capacity-aware
partitioning (#31962).

## No functional change (reader-only)

This is a **pure NFC reader**:

- The value is parsed (`WorkspaceEstimatorConfig` →
`GQAWorkspaceEstimateConfig::max_total_sequence_length`) and **stored**,
but **not consumed**.
- `BuildBounds` still returns `std::nullopt` for the non-windowed case,
so **the estimate output is unchanged** for all inputs.
- A follow-up PR (PR 4) consumes the field to produce the non-windowed
bound.

The plumbing mirrors the existing `ep.cuda.fpa_intb_*` knobs (config key
→ `WorkspaceEstimatorConfig` → CUDA EP parse → estimator param).

## Test

Extends the existing
`RealAccountantTest.FactoryRetainsNarrowWorkspaceEstimatorConfig`
framework test to validate the reader end-to-end (config string →
`WorkspaceEstimatorConfig` field) in CPU CI.

## Context

Part of the policy-based workspace-estimation PR chain (design doc:
`docs/annotated_partitioning/policy_based_workspace_estimation.md`).
This is **PR 3 (envelope plumbing)** in the roadmap:

- PR 1 — GQA scratch sizing consolidation (#32945)
- PR 2 — route-eligibility extraction (#33001)
- **PR 3 — envelope plumbing (this PR)**
- PR 4 — route-aware, envelope-bounded estimate (consumes this field)

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Copilot-Session: 37ad7066-fcd3-45df-891e-44bee9e1cf04
Tianlei Wu (tianleiwu) pushed a commit that referenced this pull request Oct 7, 2026
…on (#33179)

## Summary

Restore the function-local `is_int8` cache-type predicate in CUDA
GroupQueryAttention, next to `is_fp8`.

This fixes the main-branch Windows CUDA Plugin CI failure:

https://github.com/microsoft/onnxruntime/actions/runs/37663718452/job/112937456604

```text
group_query_attention.cc(595): error C2065: 'is_int8': undeclared identifier
```

## Why the prerequisite PRs passed CI

This is an integration regression between #33001 and #32945, not a
plugin-only feature-guard issue.

- #32945's tested head `fcbdcf7722` had the local predicate. Its Windows
and Linux plugin CI runs passed on October 6:
  https://github.com/microsoft/onnxruntime/actions/runs/37501448666
  https://github.com/microsoft/onnxruntime/actions/runs/37501449256
- #33001 subsequently merged and removed the predicate while extracting
XQA eligibility into a helper.
- #32945 then merged and introduced another use of the predicate in
workspace-recipe bit-width normalization. The resulting combined
main-branch file had the reference but no declaration.

The one-line restoration preserves the intended INT8/FP8 normalization
and does not change dispatch, allocation formulas, or lifetimes.

## Validation

- Reproduced the exact C2065 failure from the pre-fix main source with
the actual plugin adapter force-includes and compile definitions.
- Compiled the corrected GQA translation unit with MSVC `/W4 /WX` in the
plugin configuration (`BUILD_CUDA_EP_AS_PLUGIN`,
`ORT_USE_EP_API_ADAPTERS=1`, `ORT_API_MANUAL_INIT`; adapters included
before `cuda_kernel_adapter.h`).
- Compiled the same corrected source in the standard shared-provider
configuration.
- `clang-format --dry-run --Werror` and `git diff --check` passed.

These are fresh targeted translation-unit builds, not a full plugin DLL
build or GPU inference run. The PR's CI covers the full build.

Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
eserscor pushed a commit that referenced this pull request Oct 7, 2026
…3001)

## Description

Factors the **sequence-length-independent** parts of the CUDA
`GroupQueryAttention` backend-selection logic out of
`GroupQueryAttention::ComputeInternal` into a new header-only module,
`group_query_attention_eligibility.h`. It provides four pure "seq-free"
route-eligibility predicates:

| Helper | Route |
|---|---|
| `IsGQAXqaEligibleSeqFree<U>` | XQA (Extreme Query Attention) decode
kernel |
| `IsGQACudnnSdpaCoreEligibleSeqFree<T, U>` | cuDNN SDPA |
| `IsGQAFlashEligibleSeqFree<T>` | FlashAttention |
| `IsGQAMemoryEfficientEligibleSeqFree<T>` | CUTLASS memory-efficient
attention |

A **seq-free** predicate depends only on partition-time facts (geometry,
device capability, quantization, feature flags) and *not* on the
per-step sequence length or phase (prompt vs decode). The runtime keeps
the sequence-length-dependent gates and dispatch priorities at the call
sites:

- **XQA** — phase gates (`is_first_prompt` / `sequence_length == 1` /
`kv_sequence_length`) and the per-node shared-memory capability probe.
- **cuDNN** — `cudnn_sdpa::is_stable()` and the seq-dependent
`is_supported(...)` probe.
- **Flash / MEA** — the `!use_xqa` / `!use_cudnn_sdpa` /
`!use_flash_attention` dispatch priority.

## No functional change

The eligibility booleans are computed from the same expressions in the
same short-circuit-equivalent order. Every moved term is a
side-effect-free read, so the selected backend is identical for every
input. The GQA operator tests exercise all four routes and validate this
(a wrong predicate would dispatch the wrong kernel and fail the tests).

## Why header-only

All four helpers are templated on the Q/KV element types (`T`/`U`), so
the module is header-only rather than the usual `.h`/`.cc` split.

## Motivation

This is a small NFC step toward **policy-based, capacity-aware CPU
offloading** (see the design doc in #32944). The shared seq-free core is
what the Level-1 workspace estimator will later reuse, so runtime route
selection and workspace estimation cannot drift.

## Relationship to sibling PRs

- #32944 — design doc (policy-based workspace estimation).
- #32945 — consolidates GQA **scratch sizing** into recipe helpers. This
PR is the sibling that consolidates **route eligibility**. Both are
independent NFC refactors; whichever merges second will need a trivial
merge in the XQA/Flash region of `group_query_attention.cc`.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Copilot-Session: 37ad7066-fcd3-45df-891e-44bee9e1cf04
eserscor pushed a commit that referenced this pull request Oct 7, 2026
…tor (reader-only) (#33014)

## What

Adds the session option
`ep.cuda.gqa_workspace_max_total_sequence_length` and threads its value
into the CUDA GroupQueryAttention (GQA) Level-1 workspace estimator.

`total_sequence_length` (accumulated past + current KV tokens) is a
**runtime scalar input** whose value cannot be recovered from graph
shapes. For the non-windowed KV cache, the backend workspace scales
linearly with it, so the L1 estimator currently returns `std::nullopt`
for that case (it cannot bound the value). This knob lets the user hand
the estimator that **KV-length envelope** directly, for capacity-aware
partitioning (#31962).

## No functional change (reader-only)

This is a **pure NFC reader**:

- The value is parsed (`WorkspaceEstimatorConfig` →
`GQAWorkspaceEstimateConfig::max_total_sequence_length`) and **stored**,
but **not consumed**.
- `BuildBounds` still returns `std::nullopt` for the non-windowed case,
so **the estimate output is unchanged** for all inputs.
- A follow-up PR (PR 4) consumes the field to produce the non-windowed
bound.

The plumbing mirrors the existing `ep.cuda.fpa_intb_*` knobs (config key
→ `WorkspaceEstimatorConfig` → CUDA EP parse → estimator param).

## Test

Extends the existing
`RealAccountantTest.FactoryRetainsNarrowWorkspaceEstimatorConfig`
framework test to validate the reader end-to-end (config string →
`WorkspaceEstimatorConfig` field) in CPU CI.

## Context

Part of the policy-based workspace-estimation PR chain (design doc:
`docs/annotated_partitioning/policy_based_workspace_estimation.md`).
This is **PR 3 (envelope plumbing)** in the roadmap:

- PR 1 — GQA scratch sizing consolidation (#32945)
- PR 2 — route-eligibility extraction (#33001)
- **PR 3 — envelope plumbing (this PR)**
- PR 4 — route-aware, envelope-bounded estimate (consumes this field)

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Copilot-Session: 37ad7066-fcd3-45df-891e-44bee9e1cf04
eserscor pushed a commit that referenced this pull request Oct 7, 2026
…on (#33179)

## Summary

Restore the function-local `is_int8` cache-type predicate in CUDA
GroupQueryAttention, next to `is_fp8`.

This fixes the main-branch Windows CUDA Plugin CI failure:

https://github.com/microsoft/onnxruntime/actions/runs/37663718452/job/112937456604

```text
group_query_attention.cc(595): error C2065: 'is_int8': undeclared identifier
```

## Why the prerequisite PRs passed CI

This is an integration regression between #33001 and #32945, not a
plugin-only feature-guard issue.

- #32945's tested head `fcbdcf7722` had the local predicate. Its Windows
and Linux plugin CI runs passed on October 6:
  https://github.com/microsoft/onnxruntime/actions/runs/37501448666
  https://github.com/microsoft/onnxruntime/actions/runs/37501449256
- #33001 subsequently merged and removed the predicate while extracting
XQA eligibility into a helper.
- #32945 then merged and introduced another use of the predicate in
workspace-recipe bit-width normalization. The resulting combined
main-branch file had the reference but no declaration.

The one-line restoration preserves the intended INT8/FP8 normalization
and does not change dispatch, allocation formulas, or lifetimes.

## Validation

- Reproduced the exact C2065 failure from the pre-fix main source with
the actual plugin adapter force-includes and compile definitions.
- Compiled the corrected GQA translation unit with MSVC `/W4 /WX` in the
plugin configuration (`BUILD_CUDA_EP_AS_PLUGIN`,
`ORT_USE_EP_API_ADAPTERS=1`, `ORT_API_MANUAL_INIT`; adapters included
before `cuda_kernel_adapter.h`).
- Compiled the same corrected source in the standard shared-provider
configuration.
- `clang-format --dry-run --Werror` and `git diff --check` passed.

These are fresh targeted translation-unit builds, not a full plugin DLL
build or GPU inference run. The PR's CI covers the full build.

Copilot-Session: 7fa98d07-aec3-41b7-92b8-b0ec317a95bf
Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 9, 2026
…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
Chi Lo (chilo-ms) added a commit that referenced this pull request Oct 9, 2026
…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
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
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