GQA In-Place KV Cache: Investigation Report
Executive Summary
The com.microsoft.GroupQueryAttention (GQA) operator supports in-place KV cache update where past_key and present_key share the same pre-allocated buffer at runtime. This is NOT an ONNX graph-level mechanism — it's a runtime optimization managed by ORT GenAI via IO binding, controlled by the past_present_share_buffer flag in genai_config.json.
Mobius gap: The graph patterns mobius emits are already correct. The only actionable gap is that past_present_share_buffer defaults to False when it should be True for GQA models. There are also minor attribute gaps (local_window_size not propagated through GQA rewrite).
Part 1: GQA's KV Cache Mechanism
1.1 Operator Spec Overview
Source: onnxruntime/docs/ContribOperators.md#GroupQueryAttention
Inputs (7-14):
| # |
Name |
Type |
Description |
| 0 |
query |
T |
(B, S, hidden) or packed QKV |
| 1 |
key |
T (opt) |
(B, kv_S, kv_hidden) |
| 2 |
value |
T (opt) |
(B, kv_S, kv_hidden) |
| 3 |
past_key |
T_CACHE (opt) |
BNSH format: (B, kv_heads, cache_S, head_dim) |
| 4 |
past_value |
T_CACHE (opt) |
BNSH format: (B, kv_heads, cache_S, head_dim) |
| 5 |
seqlens_k |
M (req) |
(B,) — equivalent to total_seq_len - 1 per item |
| 6 |
total_sequence_length |
M (req) |
Scalar — max past_seq + new_seq across batch |
| 7 |
cos_cache |
T (opt) |
(max_S, head_dim/2) |
| 8 |
sin_cache |
T (opt) |
(max_S, head_dim/2) |
| 9 |
position_ids |
int64 (opt) |
(B, S) |
| 10 |
attention_bias |
T (opt) |
`(B |
| 11 |
head_sink |
T (opt) |
(num_heads,) |
| 12 |
k_scale |
T_KV_SCALE (opt) |
KV cache quantization scale |
| 13 |
v_scale |
T_KV_SCALE (opt) |
KV cache quantization scale |
Outputs (3-4):
| # |
Name |
Type |
Description |
| 0 |
output |
T |
(B, S, hidden) |
| 1 |
present_key |
T_CACHE |
BNSH — see buffer modes below |
| 2 |
present_value |
T_CACHE |
BNSH — see buffer modes below |
| 3 |
output_qk |
T (opt) |
QK values for debugging |
1.2 Buffer Sharing Modes
The GQA spec describes two distinct KV cache modes:
Mode A — Separate Buffers (no sharing):
past_key shape: (B, kv_heads, past_seq_len, head_dim)
present_key shape: (B, kv_heads, total_seq_len, head_dim) where total_seq_len = past_seq_len + kv_seq_len
- The kernel internally concatenates
past_key with new keys to produce present_key
- Each decode step allocates a new, larger buffer
Mode B — Shared Buffer (in-place update):
past_key and present_key are the SAME tensor at runtime
- Shape:
(B, kv_heads, max_sequence_length, head_dim) — pre-allocated to max length
- The kernel writes new KV entries at the correct position (determined by
seqlens_k)
- Zero-copy: no allocation, no concatenation per decode step
1.3 Key Attributes
| Attribute |
Type |
Description |
num_heads |
int (req) |
Number of Q heads |
kv_num_heads |
int (req) |
Number of KV heads (GQA ratio) |
scale |
float |
1/√head_dim |
do_rotary |
int |
Fuse RoPE into kernel (0/1) |
rotary_interleaved |
int |
RoPE interleaving pattern (0/1) |
local_window_size |
int |
Sliding window size (-1 = disabled) |
softcap |
float |
Logit soft-capping (0 = disabled) |
smooth_softmax |
int |
Smooth factor in softmax |
Note: There is NO kv_share_buffer attribute on the GQA op itself. Buffer sharing is a runtime behavior.
1.4 How ORT GenAI Manages KV Buffers
The ORT GenAI runtime (C++ side) manages buffer sharing through:
genai_config.json → search.past_present_share_buffer: true
- At initialization, the runtime allocates KV buffers of size
(B, kv_heads, max_seq_len, head_dim)
- Via IO binding, both the past input and present output of each GQA node are bound to the same buffer
- The GQA kernel uses
seqlens_k to know where to write new KV entries
- After each decode step,
seqlens_k is incremented — no reallocation needed
This is the standard pattern for efficient autoregressive inference.
Part 2: ORT GenAI Model Builder Reference
2.1 GQA Selection Logic
File: ort-genai/src/python/py/models/builders/base.py
def make_attention_init(self):
if self.is_gqa_supported():
self.attention_attrs["op_type"] = "GroupQueryAttention"
self.attention_attrs["use_packed_matmul"] = (
self.ep not in ["dml"]
and not self.matmul_attrs["use_lora"]
and not self.attention_attrs["q_norm"]
and not self.attention_attrs["k_norm"]
)
self.attention_attrs["use_rope_in_attn"] = self.ep not in ["dml"]
if self.attention_attrs["use_rope_in_attn"]:
del self.input_names["position_ids"] # GQA handles RoPE internally
# ...
self.past_present_share_buffer = (
self.attention_attrs["op_type"] == "GroupQueryAttention"
)
Key insight: GQA always enables past_present_share_buffer. This is not optional.
2.2 GQA Node Creation
def make_group_query_attention(self, name, **kwargs):
inputs = [
kwargs["q_path"],
kwargs["k_path"],
kwargs["v_path"],
kwargs.get("past_k", ""), # past_key_values.{i}.key
kwargs.get("past_v", ""), # past_key_values.{i}.value
kwargs.get("seqlens_k", ""), # Computed from attention_mask
kwargs.get("total_seq_len", ""), # Computed from attention_mask
kwargs.get("cos_cache", ""),
kwargs.get("sin_cache", ""),
"", # position_ids (unused when do_rotary=1)
"", # attention_bias
kwargs.get("sinks", ""),
]
output = f"{name}/output_0"
outputs = [output, kwargs.get("present_k", ""), kwargs.get("present_v", "")]
self.make_node(
"GroupQueryAttention",
inputs=inputs, outputs=outputs,
name=name, domain="com.microsoft",
num_heads=self.num_attn_heads,
kv_num_heads=self.num_kv_heads,
scale=self.attention_attrs["scale"],
local_window_size=self.window_size, # ← Set from config
softcap=self.attention_attrs["softcap"],
do_rotary=self.attention_attrs["use_rope_in_attn"],
rotary_interleaved=self.rope_attrs["interleaved"],
)
2.3 genai_config Generation
genai_config = {
"model": {
"decoder": {
"inputs": {
"past_key_names": "past_key_values.%d.key",
"past_value_names": "past_key_values.%d.value",
},
"outputs": {
"present_key_names": "present.%d.key",
"present_value_names": "present.%d.value",
},
},
},
"search": {
"past_present_share_buffer": self.past_present_share_buffer, # True for GQA
},
}
2.4 KV Cache Shapes
self.input_shapes = {
"past_key_values.key": ["batch_size", num_kv_heads, "past_sequence_length", head_size],
"past_key_values.value": ["batch_size", num_kv_heads, "past_sequence_length", head_size],
}
self.output_shapes = {
"present.key": ["batch_size", num_kv_heads, "total_sequence_length", head_size],
"present.value": ["batch_size", num_kv_heads, "total_sequence_length", head_size],
}
Note: Shape dimension names are symbolic — actual buffer allocation at runtime uses max_sequence_length when buffer sharing is enabled.
Part 3: Mobius Current State
3.1 KV Cache Architecture
Mobius has two KV cache modes:
Dynamic KV Cache (default)
Graph inputs: past_key_values.{i}.key — shape [B, kv_heads, past_seq, head_dim]
Graph outputs: present.{i}.key — shape inferred during optimization
Flow:
past_key_values.0.key ──┐
├──→ Attention op ──→ present.0.key
past_key_values.0.value ─┘ └──→ present.0.value
After GQA rewrite:
past_key_values.0.key ──┐
├──→ GroupQueryAttention ──→ present.0.key
past_key_values.0.value ─┘ (+ seqlens_k, └──→ present.0.value
total_seq_len,
cos_cache, sin_cache)
Files:
src/mobius/tasks/_causal_lm.py — creates graph inputs/outputs
src/mobius/tasks/_base.py:138-154 — _register_kv_cache_outputs() names outputs
src/mobius/components/_attention.py — _apply_attention() calls op.Attention
Static KV Cache (static_cache=True)
Graph inputs: key_cache.{i}, value_cache.{i}, write_indices, nonpad_kv_seqlen
Graph outputs: updated_key_cache.{i}, updated_value_cache.{i}
Uses TensorScatter for in-place writes to pre-allocated buffers. This is an alternative in-place mechanism that does NOT use GQA's built-in buffer sharing.
3.2 GQA Rewrite Rule
File: src/mobius/rewrite_rules/_group_query_attention.py
Three rewrite rules:
RotaryAttentionToGQA (primary) — matches RotaryEmbedding(q) + RotaryEmbedding(k) + Attention pattern
AttentionToGQA (fallback) — matches any Attention with graph-input past_key/past_value
PackQKVForGQA — fuses separate Q/K/V MatMuls into packed QKV
GQA attributes set:
{
"num_heads": q_num_heads,
"kv_num_heads": kv_num_heads,
"scale": scale,
"do_rotary": 1, # RotaryAttentionToGQA only
"rotary_interleaved": interleaved, # RotaryAttentionToGQA only
"softcap": softcap, # If non-zero (Gemma2)
}
seqlens_k computation (from attention_mask):
seqlens_k = Cast(Sub(ReduceSum(attention_mask, axis=1), 1), to=INT32)
total_seq_len = Cast(Gather(Shape(attention_mask), 1), to=INT32)
3.3 genai_config Generation
File: src/mobius/integrations/ort_genai/genai_config.py
def _default_search_params():
return {
"past_present_share_buffer": False, # ← DEFAULT IS FALSE
# ...
}
def _default_decoder_inputs(*, is_vlm: bool):
return {
"past_key_names": "past_key_values.%d.key",
"past_value_names": "past_key_values.%d.value",
}
def _default_decoder_outputs():
return {
"present_key_names": "present.%d.key",
"present_value_names": "present.%d.value",
}
Part 4: Gap Analysis
4.1 What Mobius Does Right ✅
| Aspect |
Status |
Details |
| KV cache I/O naming |
✅ Correct |
past_key_values.%d.key / present.%d.key matches ORT GenAI |
| GQA node structure |
✅ Correct |
3 outputs (attn_out, present_key, present_value) |
| seqlens_k computation |
✅ Correct |
Derived from attention_mask correctly |
| total_seq_len computation |
✅ Correct |
Derived from attention_mask shape |
| cos/sin cache sharing |
✅ Correct |
Shared across layers via cached values |
| softcap attribute |
✅ Correct |
Propagated from Attention → GQA |
| rotary_interleaved |
✅ Correct |
Propagated from RotaryEmbedding → GQA |
| Graph pattern |
✅ Correct |
past_key → GQA → present_key flow is correct |
4.2 Gaps ⚠️
| Gap |
Severity |
Details |
past_present_share_buffer defaults to False |
High |
Should be True when GQA is used. Without this, ORT GenAI allocates new buffers per decode step → significant memory waste and performance loss |
local_window_size not set on GQA node |
Medium |
The rewrite rule doesn't propagate sliding window config. Models like Mistral/Gemma2 (which use sliding window) won't get the optimization. ORT GenAI builder sets local_window_size=self.window_size |
No position_ids input removal for fused RoPE |
Low |
When do_rotary=1 (RoPE fused into GQA), position_ids graph input is unnecessary. ORT GenAI builder removes it. Mobius keeps it — harmless but wasteful |
No attention_mask removal for GQA |
Low |
GQA uses seqlens_k/total_seq_len instead of attention_mask. The mask input could be removed after GQA rewrite. ORT GenAI builder omits it entirely |
4.3 Not Gaps (Design Differences)
| Aspect |
Mobius |
ORT GenAI Builder |
Assessment |
| Static cache mode |
TensorScatter-based |
Not supported |
Mobius's static cache is for opset-24 Attention op, orthogonal to GQA buffer sharing |
| Graph-level buffer aliasing |
Not done |
Not done either |
Both rely on runtime IO binding for actual buffer sharing |
| KV cache output shapes |
Inferred |
Symbolic dims |
Both valid — shape inference handles it |
Part 5: Specific Changes Needed
Change 1: Enable past_present_share_buffer for GQA (High Priority)
File: src/mobius/integrations/ort_genai/genai_config.py
The GenaiConfigGenerator should detect whether the model uses GQA and set past_present_share_buffer: True.
Option A — Detect from ONNX model:
After optimization (which applies GQA rewrite), inspect the model for GroupQueryAttention nodes. If found, set the flag.
Option B — Detect from EP capabilities:
If the EP optimization pipeline applies GQA rewrite, set the flag. This can be determined from the EP registry.
Option C — Pass through from build pipeline:
build() → optimize_model() → returns whether GQA was applied → propagate to genai_config.
Recommended: Option A — simplest and most reliable. Something like:
# In GenaiConfigGenerator.generate():
has_gqa = any(
node.op_type == "GroupQueryAttention"
for node in model.graph
)
search_params["past_present_share_buffer"] = has_gqa
Change 2: Propagate local_window_size to GQA (Medium Priority)
File: src/mobius/rewrite_rules/_group_query_attention.py
The rewrite rule should accept a local_window_size parameter and set it on the GQA node. This requires the optimization pipeline to pass model config (sliding window size) to the rewrite rule.
Approach:
- Add
local_window_size: int = -1 parameter to RotaryAttentionToGQA.__init__
- Set
gqa_attrs["local_window_size"] = self.local_window_size
- In
_get_optimization_passes(), read sliding_window from the architecture config and pass it to the rule
Change 3 (Optional): Remove unused inputs after GQA rewrite
After GQA rewrite, position_ids (when do_rotary=1) and attention_mask could be removed from graph inputs since GQA doesn't use them directly. However, attention_mask IS used to compute seqlens_k/total_seq_len, so it must remain. Only position_ids removal is safe when RoPE is fused.
This is low priority — the inputs are simply ignored by the runtime.
Part 6: EP Capability Considerations
| EP |
GQA Support |
Buffer Sharing |
local_window_size |
Notes |
| CUDA |
✅ (fp16, bf16) |
✅ |
✅ |
Primary target |
| CPU |
✅ (fp32) |
✅ |
✅ |
For testing/debug |
| DML |
✅ (fp16) |
✅ |
❓ |
No packed QKV, no fused RoPE |
| WebGPU |
✅ (fp16, fp32) |
✅ |
❓ |
Limited feature set |
| TRT-RTX |
✅ (fp16, bf16) |
✅ |
✅ |
Uses SparseAttention for some models |
All EPs that support GQA also support buffer sharing. The past_present_share_buffer flag should be EP-agnostic.
Appendix: Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────┐
│ ONNX Model Graph │
│ │
│ Graph Inputs: Graph Outputs: │
│ ├─ input_ids [B, S] ├─ logits [B, S, V] │
│ ├─ attention_mask [B, total_S] ├─ present.0.key [B, H, ?, D] │
│ ├─ position_ids [B, S] ├─ present.0.value [B, H, ?, D] │
│ ├─ past_key_values.0.key ├─ present.1.key ... │
│ ├─ past_key_values.0.value └─ ... │
│ ├─ past_key_values.1.key │
│ └─ ... │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Layer 0: │ │
│ │ Embedding → LayerNorm → GQA → MLP → LayerNorm │ │
│ │ │ │ │
│ │ past_key_values.0.key ───┤ │ │
│ │ past_key_values.0.value ─┤ │ │
│ │ seqlens_k ───────────────┤ GroupQueryAttention │ │
│ │ total_seq_len ───────────┤ (do_rotary=1, │ │
│ │ cos_cache ───────────────┤ kv_num_heads=N, │ │
│ │ sin_cache ───────────────┘ local_window_size=W) │ │
│ │ │ │ │
│ │ ┌──────┴──────┐ │ │
│ │ │present.0.key│ │ │
│ │ │present.0.val│ │ │
│ │ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ ORT GenAI Runtime (C++) │
│ │
│ When past_present_share_buffer = true: │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Pre-allocated KV Buffer │ │
│ │ Shape: [B, H, max_seq, D] │ │
│ │ │ │
│ │ IO Bind: past_key_values.0.key ───►│ │
│ │ IO Bind: present.0.key ───────────►│ ← Same buffer! │
│ │ │ │
│ │ Step 0: Write at position 0 │ │
│ │ Step 1: Write at position 1 │ │
│ │ Step N: Write at position N │ │
│ │ (seqlens_k tracks current pos) │ │
│ └─────────────────────────────────────┘ │
│ │
│ When past_present_share_buffer = false: │
│ │
│ Step 0: Alloc [B,H,1,D], copy │
│ Step 1: Alloc [B,H,2,D], copy │
│ Step N: Alloc [B,H,N+1,D], copy ← O(N²) memory, O(N) per step │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Summary of Recommendations
- [High Priority] Set
past_present_share_buffer: True in genai_config when GQA nodes are present
- [Medium Priority] Propagate
local_window_size from model config through GQA rewrite rule
- [Low Priority] Consider removing
position_ids from graph inputs when GQA fuses RoPE
- [No Change Needed] Graph I/O patterns and GQA node structure are already correct
GQA In-Place KV Cache: Investigation Report
Executive Summary
The
com.microsoft.GroupQueryAttention(GQA) operator supports in-place KV cache update wherepast_keyandpresent_keyshare the same pre-allocated buffer at runtime. This is NOT an ONNX graph-level mechanism — it's a runtime optimization managed by ORT GenAI via IO binding, controlled by thepast_present_share_bufferflag ingenai_config.json.Mobius gap: The graph patterns mobius emits are already correct. The only actionable gap is that
past_present_share_bufferdefaults toFalsewhen it should beTruefor GQA models. There are also minor attribute gaps (local_window_sizenot propagated through GQA rewrite).Part 1: GQA's KV Cache Mechanism
1.1 Operator Spec Overview
Source:
onnxruntime/docs/ContribOperators.md#GroupQueryAttentionInputs (7-14):
query(B, S, hidden)or packed QKVkey(B, kv_S, kv_hidden)value(B, kv_S, kv_hidden)past_key(B, kv_heads, cache_S, head_dim)past_value(B, kv_heads, cache_S, head_dim)seqlens_k(B,)— equivalent tototal_seq_len - 1per itemtotal_sequence_lengthpast_seq + new_seqacross batchcos_cache(max_S, head_dim/2)sin_cache(max_S, head_dim/2)position_ids(B, S)attention_biashead_sink(num_heads,)k_scalev_scaleOutputs (3-4):
output(B, S, hidden)present_keypresent_valueoutput_qk1.2 Buffer Sharing Modes
The GQA spec describes two distinct KV cache modes:
Mode A — Separate Buffers (no sharing):
past_keyshape:(B, kv_heads, past_seq_len, head_dim)present_keyshape:(B, kv_heads, total_seq_len, head_dim)wheretotal_seq_len = past_seq_len + kv_seq_lenpast_keywith new keys to producepresent_keyMode B — Shared Buffer (in-place update):
past_keyandpresent_keyare the SAME tensor at runtime(B, kv_heads, max_sequence_length, head_dim)— pre-allocated to max lengthseqlens_k)1.3 Key Attributes
num_headskv_num_headsscale1/√head_dimdo_rotaryrotary_interleavedlocal_window_sizesoftcapsmooth_softmaxNote: There is NO
kv_share_bufferattribute on the GQA op itself. Buffer sharing is a runtime behavior.1.4 How ORT GenAI Manages KV Buffers
The ORT GenAI runtime (C++ side) manages buffer sharing through:
genai_config.json→search.past_present_share_buffer: true(B, kv_heads, max_seq_len, head_dim)seqlens_kto know where to write new KV entriesseqlens_kis incremented — no reallocation neededThis is the standard pattern for efficient autoregressive inference.
Part 2: ORT GenAI Model Builder Reference
2.1 GQA Selection Logic
File:
ort-genai/src/python/py/models/builders/base.pyKey insight: GQA always enables
past_present_share_buffer. This is not optional.2.2 GQA Node Creation
2.3 genai_config Generation
2.4 KV Cache Shapes
Note: Shape dimension names are symbolic — actual buffer allocation at runtime uses
max_sequence_lengthwhen buffer sharing is enabled.Part 3: Mobius Current State
3.1 KV Cache Architecture
Mobius has two KV cache modes:
Dynamic KV Cache (default)
Graph inputs:
past_key_values.{i}.key— shape[B, kv_heads, past_seq, head_dim]Graph outputs:
present.{i}.key— shape inferred during optimizationFlow:
After GQA rewrite:
Files:
src/mobius/tasks/_causal_lm.py— creates graph inputs/outputssrc/mobius/tasks/_base.py:138-154—_register_kv_cache_outputs()names outputssrc/mobius/components/_attention.py—_apply_attention()callsop.AttentionStatic KV Cache (
static_cache=True)Graph inputs:
key_cache.{i},value_cache.{i},write_indices,nonpad_kv_seqlenGraph outputs:
updated_key_cache.{i},updated_value_cache.{i}Uses
TensorScatterfor in-place writes to pre-allocated buffers. This is an alternative in-place mechanism that does NOT use GQA's built-in buffer sharing.3.2 GQA Rewrite Rule
File:
src/mobius/rewrite_rules/_group_query_attention.pyThree rewrite rules:
RotaryAttentionToGQA(primary) — matchesRotaryEmbedding(q) + RotaryEmbedding(k) + AttentionpatternAttentionToGQA(fallback) — matches anyAttentionwith graph-input past_key/past_valuePackQKVForGQA— fuses separate Q/K/V MatMuls into packed QKVGQA attributes set:
{ "num_heads": q_num_heads, "kv_num_heads": kv_num_heads, "scale": scale, "do_rotary": 1, # RotaryAttentionToGQA only "rotary_interleaved": interleaved, # RotaryAttentionToGQA only "softcap": softcap, # If non-zero (Gemma2) }seqlens_k computation (from attention_mask):
3.3 genai_config Generation
File:
src/mobius/integrations/ort_genai/genai_config.pyPart 4: Gap Analysis
4.1 What Mobius Does Right ✅
past_key_values.%d.key/present.%d.keymatches ORT GenAI4.2 Gaps⚠️
past_present_share_bufferdefaults toFalseTruewhen GQA is used. Without this, ORT GenAI allocates new buffers per decode step → significant memory waste and performance losslocal_window_sizenot set on GQA nodelocal_window_size=self.window_sizeposition_idsinput removal for fused RoPEdo_rotary=1(RoPE fused into GQA),position_idsgraph input is unnecessary. ORT GenAI builder removes it. Mobius keeps it — harmless but wastefulattention_maskremoval for GQAseqlens_k/total_seq_leninstead ofattention_mask. The mask input could be removed after GQA rewrite. ORT GenAI builder omits it entirely4.3 Not Gaps (Design Differences)
Attentionop, orthogonal to GQA buffer sharingPart 5: Specific Changes Needed
Change 1: Enable
past_present_share_bufferfor GQA (High Priority)File:
src/mobius/integrations/ort_genai/genai_config.pyThe
GenaiConfigGeneratorshould detect whether the model uses GQA and setpast_present_share_buffer: True.Option A — Detect from ONNX model:
After optimization (which applies GQA rewrite), inspect the model for
GroupQueryAttentionnodes. If found, set the flag.Option B — Detect from EP capabilities:
If the EP optimization pipeline applies GQA rewrite, set the flag. This can be determined from the EP registry.
Option C — Pass through from build pipeline:
build()→optimize_model()→ returns whether GQA was applied → propagate to genai_config.Recommended: Option A — simplest and most reliable. Something like:
Change 2: Propagate
local_window_sizeto GQA (Medium Priority)File:
src/mobius/rewrite_rules/_group_query_attention.pyThe rewrite rule should accept a
local_window_sizeparameter and set it on the GQA node. This requires the optimization pipeline to pass model config (sliding window size) to the rewrite rule.Approach:
local_window_size: int = -1parameter toRotaryAttentionToGQA.__init__gqa_attrs["local_window_size"] = self.local_window_size_get_optimization_passes(), readsliding_windowfrom the architecture config and pass it to the ruleChange 3 (Optional): Remove unused inputs after GQA rewrite
After GQA rewrite,
position_ids(whendo_rotary=1) andattention_maskcould be removed from graph inputs since GQA doesn't use them directly. However,attention_maskIS used to computeseqlens_k/total_seq_len, so it must remain. Onlyposition_idsremoval is safe when RoPE is fused.This is low priority — the inputs are simply ignored by the runtime.
Part 6: EP Capability Considerations
All EPs that support GQA also support buffer sharing. The
past_present_share_bufferflag should be EP-agnostic.Appendix: Architecture Diagram
Summary of Recommendations
past_present_share_buffer: Truein genai_config when GQA nodes are presentlocal_window_sizefrom model config through GQA rewrite ruleposition_idsfrom graph inputs when GQA fuses RoPE