Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0db9578
Update GQA to support Gemma4
apsonawane Apr 25, 2026
ec041db
Fix the op
apsonawane Apr 27, 2026
bcd8243
Merge branch 'main' into asonawane/gemma4
apsonawane Apr 27, 2026
7139053
Fix lint error
apsonawane Apr 27, 2026
0bf6394
Fix webgpu build
apsonawane Apr 27, 2026
3c07e1d
fix webgpu build
apsonawane Apr 27, 2026
f905196
Address copilot comments
apsonawane Apr 27, 2026
d2ead38
Make GQA present_key/present_value outputs optional for KV-shared layers
apsonawane Apr 28, 2026
aad74ef
Fix tests
apsonawane Apr 28, 2026
64005dd
Update the docs
apsonawane Apr 29, 2026
2b3a2ce
Address comments
apsonawane May 1, 2026
9a14803
Address copilot comments
apsonawane May 1, 2026
2ef269c
Address comments
apsonawane May 1, 2026
6cbe62c
Fix unit tests
apsonawane May 1, 2026
3db82c6
Fix comments
apsonawane May 1, 2026
bd023f1
Address comments
apsonawane May 4, 2026
f0035aa
Fix unit tests
apsonawane May 4, 2026
0afa1c9
fix cuda tests
apsonawane May 4, 2026
ab0ddfb
address comments
apsonawane May 4, 2026
ec50731
Support KV-shared decode with separate Q/KV sequence lengths
apsonawane May 5, 2026
1069d55
Address copilot comment:
apsonawane May 5, 2026
b1c6271
fix cuda pipeline
apsonawane May 5, 2026
e428155
[GQA] Support KV-shared layers with empty K/V inputs (kv_sequence_len…
apsonawane May 6, 2026
9d4e8c6
Add unit test and fix documentation
apsonawane May 6, 2026
17ec243
Merge branch 'main' into asonawane/gemma4
apsonawane May 7, 2026
4ca15f9
Merge branch 'main' into asonawane/gemma4
apsonawane May 8, 2026
17a155a
Fix comments
apsonawane May 8, 2026
2dd66c4
Fix
apsonawane May 8, 2026
01a1ef6
Apply copilot comments
apsonawane May 8, 2026
24afd1e
revert docs
apsonawane May 8, 2026
29bd4cb
Address comments
apsonawane May 11, 2026
0481093
Merge branch 'main' into asonawane/gemma4
apsonawane May 11, 2026
85209d6
Fix comments
apsonawane May 11, 2026
d819b56
address copilot comments
apsonawane May 11, 2026
3b996e4
Improve
apsonawane May 11, 2026
42af4d7
address comments
apsonawane May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/ContribOperators.md
Original file line number Diff line number Diff line change
Expand Up @@ -2671,14 +2671,14 @@ This version of the operator has been available since version 1 of the 'com.micr
<dd>Scale tensor for past_value.</dd>
</dl>

#### Outputs (3 - 4)
#### Outputs (1 - 4)
Comment thread
apsonawane marked this conversation as resolved.
Outdated

Comment thread
apsonawane marked this conversation as resolved.
<dl>
<dt><tt>output</tt> : T</dt>
<dd>3D output tensor with shape (batch_size, sequence_length, hidden_size)</dd>
<dt><tt>present_key</tt> : T_CACHE</dt>
<dt><tt>present_key</tt> (optional) : T_CACHE</dt>
<dd>present state key with support for format BNSH. When past_key uses same tensor as present_key(k-v buffer), it is of length max_sequence_length... otherwise of length past_sequence_length +kv_sequence_length.</dd>
<dt><tt>present_value</tt> : T_CACHE</dt>
<dt><tt>present_value</tt> (optional) : T_CACHE</dt>
<dd>present state value with support for format BNSH. When past_value uses same tensor as present_value(k-v buffer), it is of length max_sequence_length... otherwise of length past_sequence_length +kv_sequence_length.</dd>
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
<dt><tt>output_qk</tt> (optional) : T</dt>
<dd>Values of QK matrix multiplication, either before or after softmax normalization</dd>
Expand Down
8 changes: 5 additions & 3 deletions onnxruntime/contrib_ops/cpu/bert/gqa_attention_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ class GQAAttentionBase {
if (past_key != nullptr && past_value != nullptr) {
seqlen_past_kv_cache = static_cast<int>(past_key->Shape().GetDims()[2]);
}
int seqlen_present_kv_cache = static_cast<int>(present_key->Shape().GetDims()[2]);
int seqlen_present_kv_cache = present_key != nullptr
? static_cast<int>(present_key->Shape().GetDims()[2])
: parameters.total_sequence_length;

// Compute the attention score.
bool gqa_mlas_supported = MlasGQASupported<T>(CblasNoTrans, CblasTrans) &&
Expand Down Expand Up @@ -175,7 +177,7 @@ class GQAAttentionBase {
const size_t past_buff_chunk_length = past_buffer_sequence_length * head_size; // L x H
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
const size_t present_buff_chunk_length = present_buffer_sequence_length * head_size; // T x H

if (!past_present_share_buffer) {
if (present_key && !past_present_share_buffer) {
Comment thread
tianleiwu marked this conversation as resolved.
memset((void*)present_key,
0,
batch_size * kv_num_heads_ * present_buffer_sequence_length * head_size * sizeof(T));
Expand Down Expand Up @@ -402,7 +404,7 @@ class GQAAttentionBase {
const size_t past_buff_chunk_length = past_buffer_sequence_length * head_size; // L x H
const size_t present_buff_chunk_length = present_buffer_sequence_length * head_size; // T x H

if (!past_present_share_buffer) {
if (present_value && !past_present_share_buffer) {
memset((void*)present_value,
0,
batch_size * kv_num_heads_ * present_buffer_sequence_length * head_size * sizeof(T));
Expand Down
21 changes: 20 additions & 1 deletion onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ Status GroupQueryAttention<T>::Compute(OpKernelContext* context) const {
Tensor* present_k = context->Output(1, present_k_shape);
Tensor* present_v = context->Output(2, present_v_shape);

// present_key and present_value must be both present or both absent.
if ((present_k == nullptr) != (present_v == nullptr)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"present_key and present_value must be both provided or both omitted.");
}

// Optional present outputs are only safe when is_first_prompt
// (sequence_length == total_sequence_length, i.e., no past KV to concatenate).
// When past exists, the attention GEMMs use total_seqlen which requires a
// concatenated past+current KV buffer built by ConcatStateChunkGQA into present.
if ((present_k == nullptr || present_v == nullptr) && !parameters.is_first_prompt) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
"present_key and present_value outputs are required when past state exists "
"(sequence_length != total_sequence_length). Omitting present outputs is only "
"supported for first-prompt inference with no past KV cache.");
}

std::vector<int64_t> output_qk_shape{static_cast<int64_t>(batch_size), static_cast<int64_t>(num_heads_), static_cast<int64_t>(parameters.sequence_length), static_cast<int64_t>(parameters.total_sequence_length)};
Tensor* output_qk = context->Output(3, output_qk_shape);

Expand Down Expand Up @@ -233,7 +250,9 @@ Status GroupQueryAttention<T>::Compute(OpKernelContext* context) const {
const T* head_sink_data = (head_sink != nullptr) ? head_sink->Data<T>() : nullptr;

// Compute the attention score and apply the score to V
return ApplyAttention(q_rotary, packed_qkv ? nullptr : k_rotary, packed_qkv ? nullptr : V.Get<Tensor>().Data<T>(),
const T* k_data = packed_qkv ? nullptr : k_rotary;
const T* v_data = packed_qkv ? nullptr : V.Get<Tensor>().Data<T>();
return ApplyAttention(q_rotary, k_data, v_data,
head_sink_data, attention_bias, past_key, past_value, output, present_k, present_v,
output_qk, seqlens_k, parameters, allocator, context);
Comment thread
apsonawane marked this conversation as resolved.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ Status CheckInputs(const T* query,
int q_hidden_size = 0;
int kv_hidden_size = 0;
int head_size = 0;
const bool is_packed_qkv = key == nullptr;
const bool is_packed_qkv = (key == nullptr);
if (!is_packed_qkv) {
ORT_RETURN_IF_ERROR(Check_Q_K_V(query, key, value, num_heads, kv_num_heads, batch_size, sequence_length,
q_hidden_size, kv_hidden_size, head_size));
Expand Down
50 changes: 40 additions & 10 deletions onnxruntime/contrib_ops/cuda/bert/group_query_attention.cc
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ Status GroupQueryAttention<T, U>::ComputeInternal(OpKernelContext* context) cons
attention_bias,
head_sink,
parameters));

Comment thread
apsonawane marked this conversation as resolved.
parameters.local_window_size = local_window_size_;
parameters.is_unidirectional = is_unidirectional_;
parameters.use_smooth_softmax = use_smooth_softmax_ || head_sink != nullptr;
Expand Down Expand Up @@ -266,6 +267,32 @@ Status GroupQueryAttention<T, U>::ComputeInternal(OpKernelContext* context) cons
Tensor* present_key_output = context->Output(1, present_shape); // present_key
Tensor* present_value_output = context->Output(2, present_shape); // present_value

// present_key and present_value must be both present or both absent.
if ((present_key_output == nullptr) != (present_value_output == nullptr)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"present_key and present_value must be both provided or both omitted.");
}

// Optional present outputs are only safe when is_first_prompt
// (sequence_length == total_sequence_length, i.e., no past KV to concatenate).
if ((present_key_output == nullptr || present_value_output == nullptr) && !parameters.is_first_prompt) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"present_key and present_value outputs are required when past state exists "
"(sequence_length != total_sequence_length). "
"Omitting present outputs is only supported for first-prompt inference.");
}

// When present outputs are omitted, allocate internal scratch buffers so the
// CUDA kernels (flash attention, MEA, unfused) have a valid KV workspace.
// This keeps behavior consistent with the CPU EP.
IAllocatorUniquePtr<void> present_key_scratch;
IAllocatorUniquePtr<void> present_value_scratch;
if (present_key_output == nullptr || present_value_output == nullptr) {
size_t present_kv_bytes = present_shape.Size() * sizeof(U);
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
present_key_scratch = GetScratchBuffer<void>(present_kv_bytes, context->GetComputeStream());
present_value_scratch = GetScratchBuffer<void>(present_kv_bytes, context->GetComputeStream());
}

IAllocatorUniquePtr<void> k_buffer;
IAllocatorUniquePtr<void> v_buffer;
IAllocatorUniquePtr<void> rotary_buffer;
Expand All @@ -291,13 +318,14 @@ Status GroupQueryAttention<T, U>::ComputeInternal(OpKernelContext* context) cons

data.past_key = (past_key == nullptr) ? nullptr : reinterpret_cast<const CudaU*>(past_key->Data<U>());
data.past_value = (past_value == nullptr) ? nullptr : reinterpret_cast<const CudaU*>(past_value->Data<U>());

data.present_key = reinterpret_cast<CudaU*>(present_key_output->MutableData<U>());
data.present_value = reinterpret_cast<CudaU*>(present_value_output->MutableData<U>());

data.present_key = (present_key_output != nullptr)
? reinterpret_cast<CudaU*>(present_key_output->MutableData<U>())
: reinterpret_cast<CudaU*>(present_key_scratch.get());
data.present_value = (present_value_output != nullptr)
? reinterpret_cast<CudaU*>(present_value_output->MutableData<U>())
: reinterpret_cast<CudaU*>(present_value_scratch.get());
// Compute past_present_share_buffer early since it's needed for flash attention path selection.
// This compares the final pointer values after quantization handling.
parameters.past_present_share_buffer = (data.past_key == data.present_key);
parameters.past_present_share_buffer = (data.past_key != nullptr && data.past_key == data.present_key);
Comment thread
apsonawane marked this conversation as resolved.
Outdated

Comment thread
apsonawane marked this conversation as resolved.
bool is_inputs_quantized = (k_quant_type_ != KVQuantizationType::NONE) || (v_quant_type_ != KVQuantizationType::NONE);
constexpr bool is_int8 = std::is_same<U, int8_t>::value;
Expand Down Expand Up @@ -562,10 +590,12 @@ Status GroupQueryAttention<T, U>::ComputeInternal(OpKernelContext* context) cons
}

// Validate past_value pointer consistency (past_present_share_buffer was computed early after pointer setup)
Comment thread
apsonawane marked this conversation as resolved.
if (parameters.past_present_share_buffer) {
ORT_ENFORCE(data.past_value == data.present_value, "past_value and present_value must be the same tensor when past_present_share_buffer is true");
} else {
ORT_ENFORCE(data.past_value != data.present_value, "past_value and present_value must be different tensors when past_present_share_buffer is false");
if (data.present_value != nullptr) {
if (parameters.past_present_share_buffer) {
ORT_ENFORCE(data.past_value == data.present_value, "past_value and present_value must be the same tensor when past_present_share_buffer is true");
Comment thread
apsonawane marked this conversation as resolved.
Outdated
} else {
ORT_ENFORCE(data.past_value != data.present_value, "past_value and present_value must be different tensors when past_present_share_buffer is false");
}
}

data.output = reinterpret_cast<CudaT*>(output->MutableData<T>());
Expand Down
3 changes: 2 additions & 1 deletion onnxruntime/contrib_ops/webgpu/bert/group_query_attention.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "contrib_ops/webgpu/bert/rotary_embedding.h"
#include "contrib_ops/webgpu/bert/flash_attention.h"

#include "core/common/narrow.h"
#include "core/providers/webgpu/webgpu_supported_types.h"
#include "core/providers/webgpu/shader_helper.h"

Expand Down Expand Up @@ -212,7 +213,7 @@ Status GroupQueryAttention::ComputeInternal(onnxruntime::webgpu::ComputeContext&
scale_,
softcap_,
0,
context.DeviceLimits().maxComputeInvocationsPerWorkgroup));
onnxruntime::narrow<int>(context.DeviceLimits().maxComputeInvocationsPerWorkgroup)));
params.use_smooth_softmax = use_smooth_softmax_;
params.rotary_interleaved = rotary_interleaved_;

Expand Down
6 changes: 4 additions & 2 deletions onnxruntime/core/graph/contrib_ops/bert_defs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1323,13 +1323,15 @@ ONNX_MS_OPERATOR_SET_SCHEMA(
"present state key with support for format BNSH. When past_key uses same tensor as present_key"
"(k-v buffer), it is of length max_sequence_length... otherwise of length past_sequence_length +"
"kv_sequence_length.",
"T_CACHE")
"T_CACHE",
OpSchema::Optional)
Comment thread
tianleiwu marked this conversation as resolved.
Outdated
.Output(2,
"present_value",
"present state value with support for format BNSH. When past_value uses same tensor as present_value"
"(k-v buffer), it is of length max_sequence_length... otherwise of length past_sequence_length +"
"kv_sequence_length.",
"T_CACHE")
"T_CACHE",
OpSchema::Optional)
Comment thread
apsonawane marked this conversation as resolved.
Outdated
.Output(3,
"output_qk",
"Values of QK matrix multiplication, either before or after softmax normalization",
Expand Down
Loading
Loading