Skip to content

Skip redundant present_key/value copy when aliased to external KV cache (follow-up to #29715) - #31150

Open
Ti-Tai Wang (titaiwangms) wants to merge 7 commits into
mainfrom
copilot/cudnn-sdpa-decode-phase3
Open

Skip redundant present_key/value copy when aliased to external KV cache (follow-up to #29715)#31150
Ti-Tai Wang (titaiwangms) wants to merge 7 commits into
mainfrom
copilot/cudnn-sdpa-decode-phase3

Conversation

@titaiwangms

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #29715 (cuDNN SDPA decode tier for the ONNX standard Attention CUDA kernel).

On the external-KV-cache path (4-D BNSH, nonpad_kv_seqlen), all four CUDA attention backends
(Flash, cuDNN SDPA, Memory-Efficient, unfused) unconditionally cudaMemcpyAsync'd the entire K/V
cache into present_key/present_value, even when the caller IOBinds present_key/present_value
to the SAME device buffer as the K/V cache input — the documented TensorScatter + IOBinding
production pattern (mirroring TensorScatter's own .MayInplace(0, 0) self-copy skip and
GroupQueryAttention's past_key==present_key aliasing). This contradicted the file's own
PERFORMANCE NOTE and was technically undefined behavior (cudaMemcpyAsync requires non-overlapping
src/dst; a full self-copy is maximal overlap).

What changed

  • Added llm_attention_detail::CopyKVToPresent(src, dst, stream), a shared helper that skips the
    D2D copy via a pointer-equality check when present_* aliases the K/V cache buffer, with a
    greppable VERBOSE log tag (present_copy_skipped) for test observability. Applied at all 8 call
    sites (K+V × 4 backends), only in the 4-D BNSH branches (3-D BSNH always needs a layout-changing
    transpose and can never alias).
  • Added a defensive size-equality ORT_ENFORCE inside the helper: proven safe today (present
    shape only equals K/V's shape when past_sequence_length == 0), but guards against a future
    caller reusing this helper outside that precondition.
  • TestAttentionPresentKVCopySkip (8 parameterized tests) covers all 4 backends × aliased/
    non-aliased, asserting both the copy-skip fires (or doesn't) via the log tag AND that the
    intended backend actually dispatched (not a silent MATH fallback), plus output/present_key/
    present_value correctness in both cases.

Notes

Also includes (unpushed until now) a NOTE documenting the Phase 3 (cuDNN SDPA prefill chunking)
investigation and its abandonment — chunking added complexity without a clear latency win at the
shapes profiled, so it was not pursued further.

Testing

  • Full test_onnx_attention suite: 342/342 passed (334 pre-existing + 8 new).
  • New test class re-verified individually with -v; confirmed via dispatch-log scraping that
    flash/efficient/cudnn/math parameterizations each hit their intended backend on an A100 (SM80),
    not a MATH fallback.
  • lintrunner clean.

This PR went through an internal multi-agent review pass (readability, correctness, adversarial,
spec/invariant, cross-module integration, and QA execution) before being opened; findings from
that pass are already incorporated.

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

This PR improves the CUDA implementation of the ONNX-domain Attention op (opset 24 external KV-cache path) by avoiding a redundant—and in the aliased case undefined—device-to-device cudaMemcpyAsync when present_key/present_value are bound to the same device buffer as the K/V cache. It also adds Python coverage that attempts to prove the copy-skip behavior via a stable log tag plus backend-dispatch observability.

Changes:

  • Add llm_attention_detail::CopyKVToPresent() helper in attention.cc that skips the present-cache D2D copy when src and dst alias, and logs a greppable present_copy_skipped tag.
  • Apply the helper at all 4 backends’ 4-D BNSH present-cache population sites (Flash, cuDNN SDPA, MEA, unfused).
  • Add parameterized Python tests to validate both aliased vs non-aliased behavior across the 4 backends and to assert the intended backend dispatch via attention debug info.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
onnxruntime/core/providers/cuda/llm/attention.cc Introduces CopyKVToPresent and routes 4-D BNSH present population through it across all CUDA backends; updates performance/behavior notes.
onnxruntime/test/python/transformers/test_onnx_attention/test_tensorscatter_attention.py Adds new parameterized tests that exercise alias vs non-alias present-cache binding and assert copy-skip via logs + backend dispatch via debug output.

@titaiwangms
Ti-Tai Wang (titaiwangms) marked this pull request as ready for review July 30, 2026 16:03
@titaiwangms Ti-Tai Wang (titaiwangms) added the ep:CUDA issues related to the CUDA execution provider label Jul 30, 2026

@tianleiwu Tianlei Wu (tianleiwu) 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.

Overall

Nice, well-scoped optimization. I verified the past_sequence_length == 0 precondition the helper depends on actually holds at all 8 call sites (Flash/MEA via present_kv_already_populated == false, cuDNN via the explicit ORT_ENFORCE at the top of RunCudnnSdpaAttention, unfused via present_already_populated == false) — so present_* really is shape-identical to K/V there and the skip is a faithful identity, not just a size coincidence. Restricting to the !is_bsnh branches is right. Collapsing 8 duplicated cudaMemcpyAsync blocks into one helper is a readability win on its own.

Also a genuine plus on the test side: asserting the dispatched tier via SdpaKernel=... instead of trusting numeric correctness alone. Every non-MATH parameterization ORs in a MATH fallback bit, so without that assertion three of the four cases could silently degrade and still pass green.

Verdict: COMMENT. One issue that makes half the new coverage vacuous, plus a few suggestions.

Highest-priority item

The non-aliased branch reads io_binding.get_outputs()[1]/[2]. IOBinding::GetOutputs() returns outputs_, which is populated in bind order (onnxruntime/core/session/IOBinding.cc:90-96), not graph-output order — and this helper binds output, updated_key_cache, updated_value_cache, present_key, present_value. So indices 1/2 are the TensorScatter outputs. Since updated_key_cache holds the scattered cache (== ref_present_k), the assertions pass vacuously and test_copy_still_runs_when_present_not_aliased never validates the D2D copy result — the one thing it exists to check. present_k_ort/present_v_ort are already in scope; read from them directly the way the other helpers in this file do (lines 804-806). Replied with detail on the existing thread rather than opening a duplicate.

Inline suggestions

Left five inline comments covering: the LOGS_DEFAULT vs. session-logger choice (which also unblocks a non-flaky test assertion), ORT_ENFORCE in a Status-returning helper, the volume of Phase 2/3 retrospective prose in ComputeInternal, the process-global negative log assertion, and the backend-availability predicates.

Nitpicks (no action required)

  • The dst == nullptr early return in CopyKVToPresent is dead code — all 8 call sites already gate on present_key != nullptr / present_value != nullptr.
  • Pointer equality only detects exact aliasing; a same-size, partially-overlapping dst would still reach cudaMemcpyAsync with overlapping ranges. Essentially unreachable through IOBinding, and the size check narrows it further — worth one honest clause in the comment rather than a code change.
  • ~30 lines of comment preamble on a 15-line helper is disproportionate; the argument compresses to ~5 lines plus a link to this PR.
  • The VERBOSE log fires twice per decode step (K and V) on the steady-state path.
  • finally restores set_default_logger_severity(_ORT_LOG_SEVERITY_WARNING) — a hardcoded constant rather than the severity in effect on entry. Moot if the global mutation goes away (see the inline comment on the negative assertion).

Comment thread onnxruntime/core/providers/cuda/llm/attention.cc Outdated
Comment thread onnxruntime/core/providers/cuda/llm/attention.cc Outdated
Comment thread onnxruntime/core/providers/cuda/llm/attention.cc Outdated
Ti-Tai Wang (titaiwangms) pushed a commit that referenced this pull request Aug 3, 2026
From tianleiwu's review plus a follow-up internal review pass:

- Fix the test's non-aliased-branch assertion, which read
  io_binding.get_outputs()[1]/[2] (TensorScatter's updated_key_cache/
  updated_value_cache due to IOBinding's bind-order semantics, not
  present_key/present_value), making the D2D-copy-still-runs check pass
  vacuously. Read from present_k_ort/present_v_ort directly instead.
- Replace ORT_ENFORCE with ORT_RETURN_IF_NOT in CopyKVToPresent, which
  returns Status.
- Move the present_copy_skipped log off the process-global default
  logger onto the session logger via a new KernelSessionLogger helper
  (OpKernelContext::Logger() isn't reachable from the CUDA EP's
  shared-provider bridge), and anchor the test's log assertions on
  session_options.logid + the log line's delimited logger_id field
  instead of a bare substring match.
- Force ORT_DISABLE_FLASH_ATTENTION/ORT_DISABLE_MEMORY_EFFICIENT_ATTENTION
  off before the flash/efficient cases so an observed MATH fallback in
  _check_dispatched_tier can only mean "not compiled into this build",
  not "disabled via env var" — narrowing (though not eliminating) the
  chance a real dispatch regression is mistaken for a skip.
- Trim the Phase 2/3 investigation notes in ComputeInternal to a few
  lines each, pointing to issue #29714 for the full writeup.
- Document a known limitation of KernelSessionLogger: it assumes the
  EP instance is exclusively owned by one session, which holds for the
  standard CUDA EP registration path but not for a manually shared
  IExecutionProvider (as XNNPACK's EP supports).

Rebuilt incrementally and reran the full test_onnx_attention suite
(TestAttentionPresentKVCopySkip: 8/8; whole file: 121/121). clang-format
and ruff clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d745b4ab-b999-4523-b05c-7c4f463a0cab
@titaiwangms

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up commit (f5ba8ea) addressing Tianlei Wu (@tianleiwu)'s review plus findings from an additional internal review pass:

Highest-priority fix (confirmed real bug)

  • The non-aliased-branch test assertion read io_binding.get_outputs()[1]/[2], which — due to IOBinding::GetOutputs()'s bind-order semantics — actually read TensorScatter's updated_key_cache/updated_value_cache, not present_key/present_value. This made test_copy_still_runs_when_present_not_aliased pass vacuously (never validating the D2D copy path it exists to check). Now reads from present_k_ort/present_v_ort directly.

Other findings addressed

  • ORT_ENFORCEORT_RETURN_IF_NOT in CopyKVToPresent (a Status-returning helper).
  • LOGS_DEFAULT(VERBOSE) (process-global) → session-scoped logging via a new KernelSessionLogger helper, since OpKernelContext::Logger() isn't reachable through the CUDA EP's shared-provider bridge. Test log assertions now anchor on session_options.logid + the log line's delimited logger_id field (verified against ostream_sink.cc's actual format), replacing the racy process-global substring match and the global set_default_logger_severity(...) mutation.
  • TestAttentionPresentKVCopySkip's dispatch-tier assertion now distinguishes "not compiled into this build" from a hard failure via skipTest, with ORT_DISABLE_FLASH_ATTENTION/ORT_DISABLE_MEMORY_EFFICIENT_ATTENTION forced off beforehand so an observed MATH fallback can't be attributed to an ambient env-var disable.
  • Trimmed the Phase 2/3 investigation notes in ComputeInternal to a few lines each, pointing to [Perf] Add a cuDNN SDPA decode tier to the ONNX standard Attention CUDA kernel #29714 for the full writeup.
  • Documented a known limitation of KernelSessionLogger: it assumes the EP instance is exclusively owned by one session's lifetime (true for the standard CUDA EP registration path; would not hold for a manually shared IExecutionProvider, as e.g. XNNPACK's EP supports).

Validation: incremental rebuild; TestAttentionPresentKVCopySkip 8/8 passed, whole test_tensorscatter_attention.py 121/121 passed on an A100. clang-format/ruff clean.

Ti-Tai Wang (titaiwangms) pushed a commit that referenced this pull request Aug 10, 2026
From tianleiwu's review plus a follow-up internal review pass:

- Fix the test's non-aliased-branch assertion, which read
  io_binding.get_outputs()[1]/[2] (TensorScatter's updated_key_cache/
  updated_value_cache due to IOBinding's bind-order semantics, not
  present_key/present_value), making the D2D-copy-still-runs check pass
  vacuously. Read from present_k_ort/present_v_ort directly instead.
- Replace ORT_ENFORCE with ORT_RETURN_IF_NOT in CopyKVToPresent, which
  returns Status.
- Move the present_copy_skipped log off the process-global default
  logger onto the session logger via a new KernelSessionLogger helper
  (OpKernelContext::Logger() isn't reachable from the CUDA EP's
  shared-provider bridge), and anchor the test's log assertions on
  session_options.logid + the log line's delimited logger_id field
  instead of a bare substring match.
- Force ORT_DISABLE_FLASH_ATTENTION/ORT_DISABLE_MEMORY_EFFICIENT_ATTENTION
  off before the flash/efficient cases so an observed MATH fallback in
  _check_dispatched_tier can only mean "not compiled into this build",
  not "disabled via env var" — narrowing (though not eliminating) the
  chance a real dispatch regression is mistaken for a skip.
- Trim the Phase 2/3 investigation notes in ComputeInternal to a few
  lines each, pointing to issue #29714 for the full writeup.
- Document a known limitation of KernelSessionLogger: it assumes the
  EP instance is exclusively owned by one session, which holds for the
  standard CUDA EP registration path but not for a manually shared
  IExecutionProvider (as XNNPACK's EP supports).

Rebuilt incrementally and reran the full test_onnx_attention suite
(TestAttentionPresentKVCopySkip: 8/8; whole file: 121/121). clang-format
and ruff clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d745b4ab-b999-4523-b05c-7c4f463a0cab
@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the copilot/cudnn-sdpa-decode-phase3 branch from f5ba8ea to bdc6871 Compare August 10, 2026 18:06
Copilot AI and others added 5 commits August 31, 2026 21:23
…ion and abandonment

Phase 2 prototyped cuDNN SDPA dispatch for the ONNX Attention op's internal
past_key/present_key cache contract, but was abandoned after review: the
op's present_key/value shape grows by exactly one token per decode step
(no capacity concept), which defeats cuDNN's graph-cache (frontend build
once, run many times) model and causes a full graph rebuild on every
decode step. Measured on A100/cuDNN 9.8: ~260-330ms/step vs ~165-175us/step
for Flash/MEA in a realistic single-session growing-cache decode loop, a
~1600x regression, in a tier dispatched above Flash/MEA in the cascade.

See issue #29714 for the full investigation writeup and benchmark data.
…ion and abandonment

Phase 3 prototyped cuDNN SDPA dispatch for prefill via fixed-size, left-padded
query-row chunking to preserve cuDNN's graph-plan-cache-key stability. The
design passed two full independent review rounds with zero correctness
defects (incl. a 2246-configuration sweep of the causal-frontier algebra),
but was not merged for a value-proposition reason: measured speedup over the
existing MATH fallback was only ~0.94x-1.15x on A100 (worse than MATH for
short prompts), roughly at parity with Flash Attention on symmetric shapes,
and the auto-enable gate (mirroring GroupQueryAttention's own cuDNN prefill
path) only activates on SM>=90 (Hopper/Blackwell) - hardware this
investigation had no access to, so the benefit case where the feature would
actually run by default was never validated.

See issue #29714 for the full investigation writeup, chunk-size sweep, and
benchmark data before reviving this path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1011908-d09e-411a-8979-fcf0dc18e20e
On the external-KV-cache (4-D BNSH, nonpad_kv_seqlen) path, all four
attention backends (Flash, cuDNN SDPA, Memory-Efficient, unfused)
unconditionally cudaMemcpyAsync'd the entire K/V cache into
present_key/present_value, even when the caller binds present_key/
present_value to the SAME device buffer as the K/V cache input (the
documented TensorScatter + IOBinding production pattern, mirroring
TensorScatter's own .MayInplace(0, 0) self-copy skip and GQA's
past_key==present_key aliasing). This contradicted the file's own
PERFORMANCE NOTE claiming the copy overhead is eliminated on this path.

Add llm_attention_detail::CopyKVToPresent(src, dst, stream), a small
shared helper performing a pointer-equality check before the D2D copy,
with a greppable VERBOSE log tag (present_copy_skipped) for test
observability. Apply it at all 8 call sites (K+V x 4 backends) in the
4-D BNSH (!is_bsnh) branches only; the 3-D BSNH branches always need a
layout-changing transpose and can never alias, so they are unchanged.
Update the stale PERFORMANCE NOTE and RunCudnnSdpaAttention comments
that claimed present_key/value are "not aliases".

Add TestAttentionPresentKVCopySkip covering all four backends: the
skip fires (log tag observed) and output/present_key/present_value
stay correct when present_* aliases the cache buffer, and the copy
still runs (no tag, still correct) when it does not.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1011908-d09e-411a-8979-fcf0dc18e20e
From a parallel 6-agent review round on f7a4542:

- CopyKVToPresent: add a defensive ORT_ENFORCE that src/dst byte sizes
  match before the pointer-equality check. The invariant is proven safe
  today (present_key/value's shape only equals K/V's shape when
  past_sequence_length == 0, per ComputeOutputShapeForAttention), but
  the helper itself asserted nothing, so a future caller (e.g. a
  revived internal-cache/Phase-2 cuDNN path) could silently under-copy
  or wrongly skip if it reused this helper outside that precondition.
  Expanded the doc comment to state the precondition and cite the ONNX
  reference semantics (present_key == Identity(K) when there is no
  past) that make the skip a faithful identity, not just an
  optimization.
- Consolidated the four per-call-site comments (three verbose/stale,
  one missing entirely in RunUnfusedAttention) into one consistent
  one-line pointer at all 8 sites, and removed a stale hardcoded
  cross-file line-number reference.
- TestAttentionPresentKVCopySkip: the test only asserted the
  present_copy_skipped tag and output correctness, never which backend
  actually dispatched. Since three of the four provider_options OR in
  a MATH fallback bit, a regression that broke the Flash/cuDNN/MEA
  call sites while leaving the unfused ones intact would still have
  passed all 8 cases. Route _run_tensorscatter_attention_4d through
  the same ORT_ENABLE_ATTENTION_KERNEL_DEBUG_INFO capture the rest of
  this file uses, and assert the expected SdpaKernel=... tier per case
  (gated the same way the existing cuDNN decode tests are, so this
  degrades to a no-op assertion rather than a hard failure on HW/builds
  where a given backend is unsupported).
- Fixed a real global-state leak: set_default_logger_severity(VERBOSE)
  and the ORT_ENABLE_ATTENTION_KERNEL_DEBUG_INFO env var were set
  before session creation but only reset in a try/finally that started
  after it, so a session-creation failure would leak VERBOSE severity
  (and the debug-info env var) into unrelated later tests. Both
  mutations and their restoration now share one try/finally that spans
  session creation through the run.
- Moved the new sdpa_kernel bitmask constants next to the existing
  bitmask block so the whole AttentionBackend mapping stays in one
  place instead of split ~1200 lines apart.

Rebuilt, reran the full test_onnx_attention suite (342/342 passed) and
the new class individually with -v (8/8, confirmed via dispatch log
scraping that flash/efficient/cudnn/math all hit their intended
backend on this A100 box, not a MATH fallback). lintrunner clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a1011908-d09e-411a-8979-fcf0dc18e20e
From tianleiwu's review plus a follow-up internal review pass:

- Fix the test's non-aliased-branch assertion, which read
  io_binding.get_outputs()[1]/[2] (TensorScatter's updated_key_cache/
  updated_value_cache due to IOBinding's bind-order semantics, not
  present_key/present_value), making the D2D-copy-still-runs check pass
  vacuously. Read from present_k_ort/present_v_ort directly instead.
- Replace ORT_ENFORCE with ORT_RETURN_IF_NOT in CopyKVToPresent, which
  returns Status.
- Move the present_copy_skipped log off the process-global default
  logger onto the session logger via a new KernelSessionLogger helper
  (OpKernelContext::Logger() isn't reachable from the CUDA EP's
  shared-provider bridge), and anchor the test's log assertions on
  session_options.logid + the log line's delimited logger_id field
  instead of a bare substring match.
- Force ORT_DISABLE_FLASH_ATTENTION/ORT_DISABLE_MEMORY_EFFICIENT_ATTENTION
  off before the flash/efficient cases so an observed MATH fallback in
  _check_dispatched_tier can only mean "not compiled into this build",
  not "disabled via env var" — narrowing (though not eliminating) the
  chance a real dispatch regression is mistaken for a skip.
- Trim the Phase 2/3 investigation notes in ComputeInternal to a few
  lines each, pointing to issue #29714 for the full writeup.
- Document a known limitation of KernelSessionLogger: it assumes the
  EP instance is exclusively owned by one session, which holds for the
  standard CUDA EP registration path but not for a manually shared
  IExecutionProvider (as XNNPACK's EP supports).

Rebuilt incrementally and reran the full test_onnx_attention suite
(TestAttentionPresentKVCopySkip: 8/8; whole file: 121/121). clang-format
and ruff clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d745b4ab-b999-4523-b05c-7c4f463a0cab
@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the copilot/cudnn-sdpa-decode-phase3 branch from bdc6871 to b75d33f Compare August 31, 2026 21:23

@tianleiwu Tianlei Wu (tianleiwu) 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.

Overall

The exact-alias copy skip is correct at all eight 4-D BNSH call sites, and the latest revision addresses the earlier separate-buffer, error-path, session-attribution, and backend-routing issues. One supported configuration still needs correction: in the CUDA Plugin EP path, the new accessor does not reach the session's logger, so the tag falls back to the plugin-wide logger and the session-scoped observability contract does not hold. I also left a focused test-coverage suggestion for independently proving both the K and V skips.

Verdict: REQUEST CHANGES because the logger path should work consistently in both legacy/shared-provider and CUDA Plugin EP builds.

// pointer that each RegisterExecutionProvider call overwrites. Closing that gap would require
// exposing OpKernelContext::Logger() through the shared-library provider bridge.
inline const logging::Logger& KernelSessionLogger(const OpKernelInfo& info) {
const logging::Logger* logger = info.GetExecutionProvider()->GetLogger();

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.

This does not retrieve the session logger in a CUDA Plugin EP build. In that path, the adapter's OpKernelInfo::GetExecutionProvider() returns KernelInfoCache::ep_impl_ (include/onnxruntime/ep/adapter/op_kernel_info.h:51,114), which is the internal CUDA shim created by CreateCudaPluginProvider. The session OrtLogger is instead stored on CudaEp::logger_ (cuda_ep.cc:120-127), and no SetLogger call copies it onto the shim. Consequently GetLogger() is null here and the fallback is the plugin-wide default logger: SessionOptions.log_severity_level = VERBOSE will not enable this tag, and the session logid will not identify it, so the new aliased tests fail if run against the plugin EP and users cannot control the message per session.

Please use the plugin's KernelInfo_GetLogger/KernelContext_GetLogger path (the adapter exposes the former through Info().GetKernelInfo().GetLogger()), while retaining an appropriate legacy/shared-provider path, or add one common accessor that returns the actual session logger in both build modes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d296155. CUDA Plugin EP builds now retrieve the session logger through KernelInfo_GetLogger rather than the internal CUDA shim. Retrieval failures suppress only this diagnostic, and emission uses ORT_CXX_LOGF_NOEXCEPT; legacy/shared-provider builds retain the EP session-logger path.

)

self._check_dispatched_tier(name, expected_kernel, sdpa_kernel, is_supported)
self.assertRegex(

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.

Each run calls CopyKVToPresent twice (K and V), but assertRegex passes after only one matching tag. If a later change restores the unconditional self-copy at just the value call site, the key call still emits this tag and every numeric assertion remains green because a self-copy is value-preserving. That leaves half of the eight claimed backend/copy call sites unproven. Please count the session-anchored matches and require exactly two, or label the helper calls as key/value and assert that both labels appear.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d296155. The aliased test now counts session-logid-anchored matches and requires exactly two records (K and V); the non-aliased test explicitly requires zero.

Use the kernel-info logger in CUDA plugin builds without allowing diagnostic logging failures to affect inference. Strengthen the copy-skip tests to require both key and value log records.

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

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

The hot path adds uncached logging API overhead, while backend tests can skip genuine routing regressions.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

onnxruntime/core/providers/cuda/llm/attention.cc:1766

  • This NOTE narrates the abandoned prototype and profiling history, which repository guidance assigns to the issue or PR (AGENTS.md:57-61). Retain the durable decode-only rationale without embedding the experiment record in the dispatch implementation.
  // NOTE (Phase 3 — cuDNN SDPA prefill via fixed-size query-row chunking — investigated and
  // abandoned): A chunked cuDNN prefill dispatch (fixed-size, left-padded query-row chunks, needed
  // to keep cuDNN's graph-plan cache key stable) was prototyped and found correct, but measured only
  // ~0.94x-1.15x versus the existing MATH fallback on A100, and the auto-enable gate only fires on
  // SM>=90 hardware unavailable for this investigation — not worth the complexity. See issue #29714
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread onnxruntime/core/providers/cuda/llm/attention.cc Outdated
Comment thread onnxruntime/core/providers/cuda/llm/attention.cc Outdated
Cache plugin logging state outside the decode hot path, make backend routing checks distinguish unavailable kernels from regressions, and retain only durable dispatch rationale in source comments.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

🟢 Approval recommended

The optimization is correctly guarded and thoroughly covered across CUDA attention backends.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@titaiwangms

Copy link
Copy Markdown
Contributor Author

cc Tianlei Wu (@tianleiwu)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ep:CUDA issues related to the CUDA execution provider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants