diff --git a/docs/ContribOperators.md b/docs/ContribOperators.md
index f85ed1e5f146c..b64641230f249 100644
--- a/docs/ContribOperators.md
+++ b/docs/ContribOperators.md
@@ -1191,17 +1191,17 @@ This version of the operator has been available since version 1 of the 'com.micr
present state for key with shape (batch_size, num_heads, total_sequence_length, head_size). If past_present_share_buffer is set, its shape is (batch_size, num_heads, max_sequence_length, head_size), while effective_seq_length = (past_sequence_length + kv_sequence_length).
present_value (optional) : T
present state for value with shape (batch_size, num_heads, total_sequence_length, head_size). If past_present_share_buffer is set, its shape is (batch_size, num_heads, max_sequence_length, head_size), while effective_seq_length = (past_sequence_length + kv_sequence_length).
-qk (optional) : V
+qk (optional) : QK
normalized Q * K, of shape (batch_size, num_heads, 1, total_sequence_length).
#### Type Constraints
-- V : tensor(float)
-- Constrain qk output types to float32 tensors.
- T : tensor(float), tensor(float16)
- Constrain input and output types to float tensors.
+- QK : tensor(float), tensor(float16)
+- Constrain QK output to float32 or float16 tensors, independent of input type or output type.
- M : tensor(int32)
- Constrain mask index to integer types
@@ -3203,7 +3203,7 @@ This version of the operator has been available since version 1 of the 'com.micr
Whether every token can only attend to previous tokens. Default value is 0.
-#### Inputs (1 - 8)
+#### Inputs (1 - 10)
- query : T
@@ -3219,20 +3219,26 @@ This version of the operator has been available since version 1 of the 'com.micr
- attention_bias (optional) : T
- bias added to QxK' with shape (batch_size or 1, num_heads or 1, sequence_length, total_sequence_length)
- past_key (optional) : T
-- past state for self attention key with shape (batch_size, num_heads, past_sequence_length, head_size)
+- past state for key with shape (batch_size, num_heads, past_sequence_length, head_size) or (batch_size, num_heads, max_sequence_length, head_size) when buffer sharing is used
- past_value (optional) : T
-- past state for self attention value with shape (batch_size, num_heads, past_sequence_length, head_size)
+- past state for value with shape (batch_size, num_heads, past_sequence_length, head_size) or (batch_size, num_heads, max_sequence_length, head_size) when buffer sharing is used
+- past_sequence_length (optional) : M
+- The past_sequence_length buffer sharing is used with
+- cache_indirection (optional) : M
+- A buffer of shape [batch_size, beam_width, max_sequence_length] where an [i, j, k] entry specifieswhich beam the 'k' th token came from for the 'j' th beam for batch 'i' in the current iteration
-#### Outputs (1 - 3)
+#### Outputs (1 - 4)
- output : T
- 3D output tensor with shape (batch_size, sequence_length, v_hidden_size)
- present_key (optional) : T
-- present state for cross attention key with shape (batch_size, num_heads, kv_sequence_length, head_size)or present state for self attention key with shape (batch_size, num_heads, total_sequence_length, head_size)
+- present state for key with shape (batch_size, num_heads, total_sequence_length, head_size) or (batch_size, num_heads, max_sequence_length, head_size) when buffer sharing is used
- present_value (optional) : T
-- present state for cross attention value with shape (batch_size, num_heads, kv_sequence_length, head_size)or present state for self attention value with shape (batch_size, num_heads, total_sequence_length, head_size)
+- present state for value with shape (batch_size, num_heads, total_sequence_length, head_size) or (batch_size, num_heads, max_sequence_length, head_size) when buffer sharing is used
+- qk (optional) : QK
+- normalized Q * K, of shape (batch_size, num_heads, sequence_length, total_sequence_length).
#### Type Constraints
@@ -3240,6 +3246,8 @@ This version of the operator has been available since version 1 of the 'com.micr
- T : tensor(float), tensor(float16)
- Constrain input and output to float tensors.
+- QK : tensor(float), tensor(float16)
+- Constrain QK output to float32 or float16 tensors, independent of input type or output type.
- M : tensor(int32)
- Constrain mask to integer types
diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md
index 1dd145463367b..91c68a1f5afb6 100644
--- a/docs/OperatorKernels.md
+++ b/docs/OperatorKernels.md
@@ -504,7 +504,7 @@ Do not modify directly.*
|CDist|*in* A:**T**
*in* B:**T**
*out* C:**T**|1+|**T** = tensor(double), tensor(float)|
|ConvTransposeWithDynamicPads|*in* X:**T**
*in* W:**T**
*in* Pads:**tensor(int64)**
*in* B:**T**
*out* Y:**T**|1+|**T** = tensor(float)|
|CropAndResize|*in* X:**T1**
*in* rois:**T1**
*in* batch_indices:**T2**
*in* crop_size:**T2**
*out* Y:**T1**|1+|**T1** = tensor(float)
**T2** = tensor(int32)|
-|DecoderMaskedMultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* mask_index:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* beam_width:**M**
*in* cache_indirection:**M**
*in* bias:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**V**|1+|**T** = tensor(float)|
+|DecoderMaskedMultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* mask_index:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* beam_width:**M**
*in* cache_indirection:**M**
*in* bias:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**QK**|1+|**T** = tensor(float)|
|DequantizeLinear|*in* x:**T1**
*in* x_scale:**T2**
*in* x_zero_point:**T1**
*out* y:**T2**|1+|**T1** = tensor(int16), tensor(int32), tensor(int4), tensor(int8), tensor(uint16), tensor(uint4), tensor(uint8)
**T2** = tensor(float)|
|DynamicQuantizeLSTM|*in* X:**T**
*in* W:**T2**
*in* R:**T2**
*in* B:**T**
*in* sequence_lens:**T1**
*in* initial_h:**T**
*in* initial_c:**T**
*in* P:**T**
*in* W_scale:**T**
*in* W_zero_point:**T2**
*in* R_scale:**T**
*in* R_zero_point:**T2**
*out* Y:**T**
*out* Y_h:**T**
*out* Y_c:**T**|1+|**T** = tensor(float)
**T1** = tensor(int32)
**T2** = tensor(int8), tensor(uint8)|
|DynamicQuantizeMatMul|*in* A:**T1**
*in* B:**T2**
*in* b_scale:**T1**
*in* b_zero_point:**T2**
*in* bias:**T1**
*out* Y:**T1**|1+|**T1** = tensor(float)
**T2** = tensor(int8), tensor(uint8)|
@@ -528,7 +528,7 @@ Do not modify directly.*
|MatMulIntegerToFloat|*in* A:**T1**
*in* B:**T2**
*in* a_scale:**T3**
*in* b_scale:**T3**
*in* a_zero_point:**T1**
*in* b_zero_point:**T2**
*in* bias:**T3**
*out* Y:**T3**|1+|**T1** = tensor(int8), tensor(uint8)
**T2** = tensor(int8), tensor(uint8)
**T3** = tensor(float)|
|MatMulNBits|*in* A:**T1**
*in* B:**T2**
*in* scales:**T1**
*in* zero_points:**T3**
*in* g_idx:**T4**
*in* bias:**T1**
*out* Y:**T1**|1+|**T1** = tensor(float), tensor(float16)
**T2** = tensor(uint8)
**T3** = tensor(float), tensor(float16), tensor(uint8)
**T4** = tensor(int32)|
|MaxpoolWithMask|*in* X:**T**
*in* M:**tensor(int32)**
*out* Y:**T**|1+|**T** = tensor(float)|
-|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**|1+|**T** = tensor(float)|
+|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* cache_indirection:**M**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**QK**|1+|**T** = tensor(float)|
|MurmurHash3|*in* X:**T1**
*out* Y:**T2**|1+|**T1** = tensor(double), tensor(float), tensor(int32), tensor(int64), tensor(string), tensor(uint32), tensor(uint64)
**T2** = tensor(int32), tensor(uint32)|
|NGramRepeatBlock|*in* input_ids:**Tid**
*in* scores:**T**
*out* scores_out:**T**|1+|**T** = tensor(float)
**Tid** = tensor(int64)|
|NhwcMaxPool|*in* x:**T**
*out* y:**T**|1+|**T** = tensor(int8), tensor(uint8)|
@@ -906,7 +906,7 @@ Do not modify directly.*
|ComplexMulConj|*in* A:**T**
*in* B:**T**
*out* C:**T**|1+|**T** = tensor(float), tensor(float16)|
|ConvTransposeWithDynamicPads|*in* X:**T**
*in* W:**T**
*in* Pads:**tensor(int64)**
*in* B:**T**
*out* Y:**T**|1+|**T** = tensor(float)|
|DecoderAttention|*in* query:**T**
*in* key:**T**
*in* q_weight:**T**
*in* kv_weight:**T**
*in* bias:**T**
*in* key_padding_mask:**B**
*in* key_cache:**T**
*in* value_cache:**T**
*in* static_kv:**B**
*in* use_past:**B**
*in* has_layer_state:**B**
*in* has_key_padding_mask:**B**
*out* output:**T**
*out* new_key_cache:**T**
*out* new_value_cache:**T**|1+|**T** = tensor(float), tensor(float16)|
-|DecoderMaskedMultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* mask_index:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* beam_width:**M**
*in* cache_indirection:**M**
*in* bias:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**V**|1+|**T** = tensor(float), tensor(float16)|
+|DecoderMaskedMultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* mask_index:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* beam_width:**M**
*in* cache_indirection:**M**
*in* bias:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**QK**|1+|**QK** = tensor(float), tensor(float16)
**T** = tensor(float), tensor(float16)|
|DecoderMaskedSelfAttention|*in* input:**T**
*in* weights:**T**
*in* bias:**T**
*in* mask_index:**M**
*in* past:**T**
*in* attention_bias:**T**
*in* past_sequence_length:**M**
*in* beam_width:**M**
*in* cache_indirection:**M**
*out* output:**T**
*out* present:**T**|1+|**T** = tensor(float), tensor(float16)|
|DequantizeLinear|*in* x:**T1**
*in* x_scale:**T2**
*in* x_zero_point:**T1**
*out* y:**T2**|1+|**T1** = tensor(int8), tensor(uint8)
**T2** = tensor(float16)|
|DequantizeWithOrder|*in* input:**Q**
*in* scale_input:**S**
*out* output:**F**|1+|**F** = tensor(float), tensor(float16)
**Q** = tensor(int8)
**S** = tensor(float)|
@@ -929,7 +929,7 @@ Do not modify directly.*
|MatMulBnb4|*in* A:**T1**
*in* B:**T2**
*in* absmax:**T1**
*out* Y:**T1**|1+|**T1** = tensor(bfloat16), tensor(float), tensor(float16)
**T2** = tensor(uint8)|
|MatMulNBits|*in* A:**T1**
*in* B:**T2**
*in* scales:**T1**
*in* zero_points:**T3**
*in* g_idx:**T4**
*in* bias:**T1**
*out* Y:**T1**|1+|**T1** = tensor(float), tensor(float16)
**T2** = tensor(uint8)|
|MoE|*in* input:**T**
*in* router_probs:**T**
*in* fc1_experts_weights:**T**
*in* fc1_experts_bias:**T**
*in* fc2_experts_weights:**T**
*in* fc2_experts_bias:**T**
*in* fc3_experts_weights:**T**
*in* fc3_experts_bias:**T**
*out* output:**T**|1+|**T** = tensor(float), tensor(float16)|
-|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**|1+|**T** = tensor(float), tensor(float16)|
+|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* cache_indirection:**M**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**QK**|1+|**QK** = tensor(float), tensor(float16)
**T** = tensor(float), tensor(float16)|
|NGramRepeatBlock|*in* input_ids:**Tid**
*in* scores:**T**
*out* scores_out:**T**|1+|**T** = tensor(float)
**Tid** = tensor(int64)|
|NhwcConv|*in* X:**T**
*in* W:**T**
*in* B:**T**
*out* Y:**T**|1+|**T** = tensor(float), tensor(float16)|
|PackedAttention|*in* input:**T**
*in* weights:**T**
*in* bias:**T**
*in* token_offset:**M**
*in* cumulative_sequence_length:**M**
*in* attention_bias:**T**
*out* output:**T**|1+|**T** = tensor(float), tensor(float16)|
@@ -1402,7 +1402,7 @@ Do not modify directly.*
|GroupQueryAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* seqlens_k:**M**
*in* total_sequence_length:**M**
*in* cos_cache:**T**
*in* sin_cache:**T**
*in* position_ids:**tensor(int64)**
*in* attention_bias:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**|1+|**M** = tensor(int32)
**T** = tensor(float), tensor(float16)|
|MatMulIntegerToFloat|*in* A:**T1**
*in* B:**T2**
*in* a_scale:**T3**
*in* b_scale:**T3**
*in* a_zero_point:**T1**
*in* b_zero_point:**T2**
*in* bias:**T3**
*out* Y:**T3**|1+|**T1** = tensor(int8), tensor(uint8)
**T2** = tensor(int8), tensor(uint8)
**T3** = tensor(float), tensor(float16)|
|MatMulNBits|*in* A:**T1**
*in* B:**T2**
*in* scales:**T1**
*in* zero_points:**T3**
*in* g_idx:**T4**
*in* bias:**T1**
*out* Y:**T1**|1+|**T1** = tensor(float), tensor(float16)
**T2** = tensor(uint8)|
-|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**|1+|**M** = tensor(int32)
**T** = tensor(float), tensor(float16)|
+|MultiHeadAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* bias:**T**
*in* key_padding_mask:**M**
*in* attention_bias:**T**
*in* past_key:**T**
*in* past_value:**T**
*in* past_sequence_length:**M**
*in* cache_indirection:**M**
*out* output:**T**
*out* present_key:**T**
*out* present_value:**T**
*out* qk:**QK**|1+|**M** = tensor(int32)
**T** = tensor(float), tensor(float16)|
|NhwcConv|*in* X:**T**
*in* W:**T**
*in* B:**T**
*out* Y:**T**|1+|**T** = tensor(float), tensor(float16)|
|QAttention|*in* input:**T1**
*in* weight:**T2**
*in* bias:**T3**
*in* input_scale:**T3**
*in* weight_scale:**T3**
*in* mask_index:**T4**
*in* input_zero_point:**T1**
*in* weight_zero_point:**T2**
*in* past:**T3**
*out* output:**T3**
*out* present:**T3**|1+|**T1** = tensor(int8), tensor(uint8)
**T2** = tensor(int8), tensor(uint8)
**T3** = tensor(float), tensor(float16)
**T4** = tensor(int32)|
|QLinearAdd|*in* A:**T**
*in* A_scale:**tensor(float)**
*in* A_zero_point:**T**
*in* B:**T**
*in* B_scale:**tensor(float)**
*in* B_zero_point:**T**
*in* C_scale:**tensor(float)**
*in* C_zero_point:**T**
*out* C:**T**|1+|**T** = tensor(int8), tensor(uint8)|
diff --git a/onnxruntime/contrib_ops/cpu/bert/attention.cc b/onnxruntime/contrib_ops/cpu/bert/attention.cc
index ad14fb8258656..de23444e95778 100644
--- a/onnxruntime/contrib_ops/cpu/bert/attention.cc
+++ b/onnxruntime/contrib_ops/cpu/bert/attention.cc
@@ -335,7 +335,7 @@ Status Attention::Compute(OpKernelContext* context) const {
// Compute the attention score and apply the score to V
return ApplyAttention(Q, K, V, mask_index, past, nullptr /* past_key */, nullptr /* past_value */,
- output, nullptr /* present_key */, nullptr /* present_value */,
+ output, nullptr /* present_key */, nullptr /* present_value */, nullptr /* output_qk */,
batch_size, sequence_length, sequence_length,
parameters.head_size, parameters.v_head_size, parameters.v_hidden_size,
attention_bias, context);
diff --git a/onnxruntime/contrib_ops/cpu/bert/attention_base.h b/onnxruntime/contrib_ops/cpu/bert/attention_base.h
index 05756cd54d842..93d35d39390f5 100644
--- a/onnxruntime/contrib_ops/cpu/bert/attention_base.h
+++ b/onnxruntime/contrib_ops/cpu/bert/attention_base.h
@@ -7,6 +7,7 @@
#include "core/common/common.h"
#include "core/framework/op_kernel.h"
#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
namespace onnxruntime {
namespace contrib {
diff --git a/onnxruntime/contrib_ops/cpu/bert/attention_common.h b/onnxruntime/contrib_ops/cpu/bert/attention_common.h
index 97d6cc1ce7d66..243f611da49e1 100644
--- a/onnxruntime/contrib_ops/cpu/bert/attention_common.h
+++ b/onnxruntime/contrib_ops/cpu/bert/attention_common.h
@@ -49,148 +49,10 @@ enum AttentionKernelType {
AttentionKernel_FlashAttention,
AttentionKernel_CudnnFlashAttention,
AttentionKernel_LeanAttention,
+ AttentionKernel_DecoderAttention,
AttentionKernel_Default
};
-// Parameters deduced from node attributes and inputs/outputs.
-struct AttentionParameters {
- int batch_size;
- int sequence_length;
- int kv_sequence_length; // input sequence length of K or V
- int past_sequence_length; // sequence length in past state of K or V
- int total_sequence_length; // total sequence length of K or V
- int max_sequence_length; // max sequence length from 4D mask
- int input_hidden_size; // first dimension of weights for input projection
- int hidden_size; // hidden size of Q or K
- int head_size; // hidden size per head of Q or K
- int v_hidden_size; // hidden size of V
- int v_head_size; // hidden size per head of V
- int num_heads;
- int rotary_embedding;
- bool is_unidirectional;
- bool past_present_share_buffer;
- bool do_rotary;
- bool broadcast_attn_bias_dim_0;
- bool broadcast_attn_bias_dim_1;
- float mask_filter_value;
- float scale;
- bool use_tf32;
- AttentionMaskType mask_type;
- AttentionQkvFormat qkv_format;
-};
-
-struct DecoderMaskedMultiHeadAttentionParams : AttentionParameters {
- int beam_width = 1;
-
- // Only NeoX style rotary embedding is supported
- int rotary_embedding_dim = 0;
- int t_step = 0;
-
- // Whether to use multihead attention(excludes matmul and bias)
- bool is_mha = false;
- bool is_cross_attention = false;
- bool is_packed_qkv = false;
-
- // Useful to better use global memory bandwidth on certain CUDA architectures.
- // Turned off by default for now until we fully understand performance implications
- // for all types of workloads.
- // Can be turned on by appropriate environment variable (see attention_common.h).
- bool kv_data_in_flight = false;
-
- void* q = nullptr;
- void* q_bias = nullptr;
-
- void* k = nullptr;
- void* k_bias = nullptr;
-
- void* v = nullptr;
- void* v_bias = nullptr;
-
- void* attention_bias = nullptr;
-
- void* k_cache = nullptr;
- void* v_cache = nullptr;
-
- void* out = nullptr;
- void* out_qk = nullptr;
-
- const int32_t* cache_indir = nullptr;
- const int32_t* mask = nullptr; // [B, total_sequence_length]
-};
-
-// Parameters deduced from node attributes and inputs/outputs.
-struct PackedAttentionParameters {
- int batch_size;
- int sequence_length;
- int input_hidden_size; // hidden size of input
- int hidden_size; // hidden size of Q or K
- int head_size; // hidden size per head of Q or K
- int v_hidden_size; // hidden size of V
- int v_head_size; // hidden size per head of V
- int num_heads;
- float scale;
- int token_count;
- bool broadcast_attn_bias_dim_0;
- bool broadcast_attn_bias_dim_1;
- bool use_tf32;
-};
-
-// Parameters deduced from node attributes and inputs/outputs.
-struct GroupQueryAttentionParameters {
- int batch_size;
- int sequence_length; // sequence length of input query, key, value
- int seqlen_past_kv_cache; // sequence length of past kv tensor
- int seqlen_present_kv_cache; // sequence length of present kv tensor
- int total_sequence_length; // maximum total sequence length (past_sequence_length + sequence_length) among keys
- int hidden_size;
- int num_heads;
- int head_size;
- int kv_hidden_size;
- int kv_num_heads;
- int num_splits; // number of splits for splitkv
- int rotary_dim; // rotary embedding dimension
- bool is_unidirectional; // causal
- int local_window_size;
- bool kv_share_buffer;
- bool is_packed_qkv;
- bool is_subsequent_prompt; // indicates whether we have past context and seqlen > 1
- bool is_first_prompt; // indicates whether this is first decoding step
- bool do_rotary;
- bool rotary_interleaved;
- bool use_smooth_softmax;
- float scale;
- float softcap;
- AttentionQkvFormat qkv_format;
- AttentionQkvFormat past_kv_format;
- int zeros_count;
- int* zero_ptr;
-};
-
-// Parameters for sparse attention.
-struct SparseAttentionParameters {
- int batch_size; // batch size
- int sequence_length; // sequence length of input query, key, value
- int hidden_size; // hidden size of query
- int num_heads; // number of heads of query
- int head_size; // hidden size per head of query, key or value
- int kv_hidden_size; // hidden size of key or value
- int kv_num_heads; // number of heads of key or value
- bool do_rotary; // whether to use rotary embedding
- bool rotary_interleaved; // whether to use interleaved rotary embedding
- int rotary_dim; // rotary embedding dimension
- int sparse_block_size; // block size for sparse attention
- int num_sparse_layout; // number of sparse layout
- int stride_col_indices; // shape of block_col_indices is [num_sparse_layout, stride_col_indices]
- int stride_row_indices; // shape of block_row_indices is [num_sparse_layout, stride_row_indices]
- float scale; // scaling factor applied prior to softmax
- bool is_packed_qkv; // whether qkv is packed
- int total_sequence_length; // maximum total sequence length (past_sequence_length + sequence_length) among keys
- int max_sequence_length; // max sequence length for sparse layout
- int max_rotary_sequence_length; // max sequence length for rotary cos/sin cache
- int max_cache_sequence_length; // max sequence length for kv cache buffer
- bool past_present_share_buffer; // whether past_key and present_key share buffer, so is past_value and present_value
-};
-
constexpr bool LAYOUT_BSNH = false;
constexpr bool LAYOUT_BNSH = true;
@@ -215,6 +77,7 @@ enum class AttentionBackend : int {
// Experimental kernels
LEAN_ATTENTION = 256,
+ DECODER_ATTENTION = 512, // FasterTransformer's decoder masked multihead attention
};
// Environment variable to enable debug information of attention kernel to be printed. Default is 0 (disabled).
@@ -245,6 +108,9 @@ constexpr const char* kDisableFlashAttention = "ORT_DISABLE_FLASH_ATTENTION";
// Environment variable to enable or disable lean attention. Default is 0 (disabled).
constexpr const char* kEnableLeanAttention = "ORT_ENABLE_LEAN_ATTENTION";
+// Environment variable to enable or disable FasterTransformer's decoder masked multi-head attention. Default is 0 (enabled).
+constexpr const char* kDisableDecoderAttention = "ORT_DISABLE_DECODER_ATTENTION";
+
// Minimum sequence length to perfer memory efficient attention when data type is float32
constexpr const char* kMinSeqLenForEfficientAttentionFp32 = "ORT_MIN_SEQ_LEN_EFFICIENT_ATTENTION_FP32";
diff --git a/onnxruntime/contrib_ops/cpu/bert/attention_cpu_base.h b/onnxruntime/contrib_ops/cpu/bert/attention_cpu_base.h
index 4345675b7e966..4abe986ffa685 100644
--- a/onnxruntime/contrib_ops/cpu/bert/attention_cpu_base.h
+++ b/onnxruntime/contrib_ops/cpu/bert/attention_cpu_base.h
@@ -29,6 +29,7 @@ class AttentionCPUBase : public AttentionBase {
Tensor* output, // output tensor
Tensor* present_key, // present K output tensor (if separating present KV)
Tensor* present_value, // present V output tensor (if separating present KV)
+ Tensor* output_qk, // Q*K output tensor (if returning Q*K value)
int batch_size, // batch size (B)
int sequence_length, // sequence length of Q (S)
int kv_sequence_length, // sequence length of K or V (L)
@@ -37,7 +38,6 @@ class AttentionCPUBase : public AttentionBase {
int v_hidden_size, // hidden size of V (D_v)
const Tensor* attn_bias, // additive bias applied on scaled QK.
OpKernelContext* context,
- Tensor* output_qk = nullptr, // output buffer for QK (if needed)
int past_sequence_length = 0, // sequence length of past state
bool past_present_share_buffer = false) const {
AllocatorPtr allocator;
@@ -109,7 +109,7 @@ class AttentionCPUBase : public AttentionBase {
static_cast(mask_data),
batch_size, sequence_length, kv_sequence_length, past_sequence_length,
qk_head_size == 0 ? v_head_size : qk_head_size, past_data, past_key_data, present_data,
- present_key_data, tp, scale, attn_bias_data, attn_bias_dims, output_qk_data,
+ present_key_data, output_qk_data, tp, scale, attn_bias_data, attn_bias_dims,
past_present_share_buffer, max_sequence_length);
// Compute the attentionScore * Value: out_tmp(B, N, S, H_v) = attention_probs(B, N, S, T) x V(B, N, T, H_v)
@@ -125,6 +125,65 @@ class AttentionCPUBase : public AttentionBase {
return Status::OK();
}
+ // For DecoderMaskedMultiHeadAttention
+ template
+ Status ApplyAttentionWithBeams(const T* Q,
+ const T* K,
+ const T* V,
+ const Tensor* mask_index,
+ const Tensor* past_key,
+ const Tensor* past_value,
+ Tensor* output,
+ Tensor* present_key,
+ Tensor* present_value,
+ int batch_size,
+ int past_sequence_length,
+ int max_sequence_length,
+ int head_size,
+ int v_head_size,
+ const Tensor* attn_bias,
+ bool broadcast_attn_bias_dim_0,
+ bool broadcast_attn_bias_dim_1,
+ const Tensor* cache_indir,
+ OpKernelContext* context,
+ int beam_width,
+ Tensor* output_qk) const {
+ AllocatorPtr allocator;
+ ORT_RETURN_IF_ERROR(context->GetTempSpaceAllocator(&allocator));
+
+ auto* tp = context->GetOperatorThreadPool();
+
+ int total_sequence_length = past_sequence_length + 1; // This is +1 because this is used during token generation via DecoderMaskedMultiHeadAttention
+ size_t bytes = SafeInt(batch_size) * num_heads_ * total_sequence_length * sizeof(T);
+ auto attention_probs = allocator->Alloc(bytes);
+ BufferUniquePtr scratch_buffer(attention_probs, BufferDeleter(allocator));
+
+ const T* past_key_data = past_key != nullptr ? past_key->Data() : nullptr;
+ T* present_key_data = present_key != nullptr ? present_key->MutableData() : nullptr;
+ const T* past_value_data = past_value != nullptr ? past_value->Data() : nullptr;
+ T* present_value_data = present_value != nullptr ? present_value->MutableData() : nullptr;
+ T* output_qk_data = (output_qk != nullptr) ? output_qk->MutableData() : nullptr;
+
+ const int32_t* mask_index_data = mask_index != nullptr ? mask_index->Data() : nullptr;
+ const T* attn_bias_data = attn_bias != nullptr ? attn_bias->Data() : nullptr;
+
+ ComputeAttentionProbsWithBeams(static_cast(attention_probs), Q, K, mask_index_data, batch_size,
+ past_sequence_length, max_sequence_length, head_size, past_key_data,
+ present_key_data, tp, attn_bias_data, broadcast_attn_bias_dim_0,
+ broadcast_attn_bias_dim_1, cache_indir->Data(), beam_width, output_qk_data);
+
+ // Compute the attentionScore * Value: out_tmp(B, N, 1, H_v) = attention_probs(B, N, 1, T) x V(B, N, T, H_v)
+ auto out_tmp_data = allocator->Alloc(SafeInt(batch_size) * num_heads_ * v_head_size * sizeof(T));
+ BufferUniquePtr out_tmp_buffer(out_tmp_data, BufferDeleter(std::move(allocator)));
+
+ ComputeVxAttentionScoreWithBeams(output->MutableData(), static_cast(out_tmp_data),
+ static_cast(attention_probs), V, batch_size,
+ past_sequence_length, max_sequence_length, v_head_size, past_value_data,
+ present_value_data, cache_indir->Data(), beam_width, tp);
+
+ return Status::OK();
+ }
+
private:
// Helper function to compute the attention probs. It does 2 things:
// attention_probs(B, N, S, T) = 1/sqrt(H) x Q(B, N, S, H) x K'(B, N, T, H -> B, N, H, T) +
@@ -144,11 +203,11 @@ class AttentionCPUBase : public AttentionBase {
const T* past_key, // past key only (if not using past state)
T* present, // present state
T* present_key, // present key only (if not using present state)
+ T* output_qk, // Q*K output
ThreadPool* tp, // thread pool
float scale, // scale factor
const T* attn_bias_data, // attention bias
gsl::span attn_bias_dims, // attention bias shape
- T* output_qk_data = nullptr, // scaled output QK buffer
bool past_present_share_buffer = false,
int max_sequence_length = 0) const {
const int total_sequence_length = past_sequence_length + kv_sequence_length; // T = P + L
@@ -253,9 +312,9 @@ class AttentionCPUBase : public AttentionBase {
});
}
- if (output_qk_data != nullptr) {
+ if (output_qk != nullptr) {
// Output the scaled Q*K^T if needed.
- memcpy(output_qk_data, attention_probs,
+ memcpy(output_qk, attention_probs,
SafeInt(batch_size) * num_heads_ * sequence_length * total_sequence_length * sizeof(T));
}
@@ -360,6 +419,200 @@ class AttentionCPUBase : public AttentionBase {
}
});
}
+
+ // Used for DecoderMaskedMultiHeadAttention where sequence_length = 1
+ template
+ void ComputeAttentionProbsWithBeams(T* attention_probs,
+ const T* Q,
+ const T* K,
+ const int32_t* mask_index_data,
+ int batch_size,
+ int past_sequence_length,
+ int max_sequence_length,
+ int head_size,
+ const T* past_key_data,
+ T* present_key_data,
+ ThreadPool* tp,
+ const T* attn_bias_data,
+ bool broadcast_attn_bias_dim_0,
+ bool broadcast_attn_bias_dim_1,
+ const int32_t* cache_indir_data,
+ int beam_width,
+ T* output_qk_data) const {
+ float scale = scale_ == 0.0f ? 1.0f / sqrt(static_cast(head_size)) : scale_;
+
+ TensorOpCost unit_cost;
+ auto total_sequence_length = past_sequence_length + 1;
+ const ptrdiff_t probs_matrix_size = total_sequence_length;
+ const ptrdiff_t probs_matrix_bytes = probs_matrix_size * sizeof(T);
+
+ unit_cost.compute_cycles = static_cast((SafeInt(2) * head_size - 1) * total_sequence_length);
+ unit_cost.bytes_loaded = static_cast(SafeInt(2) * head_size * total_sequence_length * sizeof(T));
+ unit_cost.bytes_stored = static_cast(SafeInt(head_size) * total_sequence_length * sizeof(T));
+
+ if (attn_bias_data != nullptr) {
+ unit_cost.bytes_loaded += static_cast(probs_matrix_bytes) * 2;
+ unit_cost.bytes_stored += probs_matrix_bytes;
+ }
+
+ if (mask_index_data != nullptr) {
+ unit_cost.bytes_stored += probs_matrix_bytes;
+ }
+
+ // Cost of appending current key to present key
+ unit_cost.compute_cycles += static_cast(head_size);
+ unit_cost.bytes_loaded += static_cast(head_size);
+
+ // Parallel for loop
+ const int loop_len = batch_size * num_heads_;
+ ThreadPool::TryParallelFor(tp, loop_len, unit_cost, [&](std::ptrdiff_t begin, std::ptrdiff_t end) {
+ for (std::ptrdiff_t i = begin; i != end; ++i) {
+ const std::ptrdiff_t batch_index = i / num_heads_;
+ const std::ptrdiff_t head_index = i % num_heads_;
+ const std::ptrdiff_t beam_batch_index = batch_index / beam_width;
+ const T* q_vec = Q + i * head_size;
+ const std::ptrdiff_t attn_bias_base_offset = ((broadcast_attn_bias_dim_0 ? 0 : (beam_batch_index * num_heads_)) +
+ (broadcast_attn_bias_dim_1 ? 0 : head_index)) *
+ probs_matrix_size;
+
+ {
+ // Calculate the latest position of the attention_probs
+ // (1, H) x (T, H)^T -> (1, T)
+ // Decompose into T (1, H) x (1, H)^T -> (1, 1) operations
+ auto last_offset = past_sequence_length + i * probs_matrix_size;
+ T* attention_probs_ptr = reinterpret_cast(attention_probs) + last_offset;
+ math::Dot(head_size, q_vec, K + i * head_size, attention_probs_ptr, nullptr);
+
+ *attention_probs_ptr *= scale;
+ // Apply the attention bias and mask
+ if (attn_bias_data != nullptr) {
+ *attention_probs_ptr += attn_bias_data[attn_bias_base_offset + past_sequence_length];
+ }
+ bool is_masked = (mask_index_data != nullptr) &&
+ (mask_index_data[(batch_index + 1) * total_sequence_length - 1] == 0);
+ if (is_masked) {
+ *attention_probs_ptr += mask_filter_value_;
+ }
+ }
+
+ {
+ // Calculate the rest of the attention_probs
+ for (std::ptrdiff_t j = 0; j < past_sequence_length; ++j) {
+ const int* beam_indices = &cache_indir_data[batch_index * max_sequence_length];
+ const std::ptrdiff_t beam_offset = static_cast(beam_indices[j]) * num_heads_ *
+ max_sequence_length * head_size;
+ const std::ptrdiff_t beam_batch_offset = (beam_batch_index * beam_width * num_heads_ + head_index) *
+ max_sequence_length * head_size;
+ const T* past_k_vec = past_key_data + beam_batch_offset + beam_offset + j * head_size;
+ T* output = reinterpret_cast(attention_probs) + j + i * probs_matrix_size;
+ math::Dot(head_size, q_vec, past_k_vec, output, nullptr);
+
+ *output *= scale;
+ // Apply the attention bias and mask
+ if (attn_bias_data != nullptr) {
+ *output += attn_bias_data[attn_bias_base_offset + j];
+ }
+ bool is_masked = (mask_index_data != nullptr) &&
+ (mask_index_data[batch_index * total_sequence_length + j] == 0);
+ if (is_masked) {
+ *output += mask_filter_value_;
+ }
+ }
+ }
+
+ // Append current key to present key (past_present_share_buffer_ is true)
+ memcpy(present_key_data + (i * max_sequence_length + past_sequence_length) * head_size,
+ K + i * head_size, head_size * sizeof(T));
+ }
+ });
+
+ if (output_qk_data != nullptr) {
+ // Output the scaled Q*K^T if needed.
+ memcpy(output_qk_data, attention_probs,
+ SafeInt(batch_size) * num_heads_ * total_sequence_length * sizeof(T));
+ }
+
+ // attention_probs(B, N, 1, T) = Softmax(attention_probs)
+ {
+ const int N = batch_size * num_heads_;
+ const int D = total_sequence_length;
+ ComputeAttentionSoftmaxInplace(attention_probs, N, D, tp);
+ }
+ }
+
+ // Used for DecoderMaskedMultiHeadAttention where sequence_length = 1
+ template
+ void ComputeVxAttentionScoreWithBeams(T* output,
+ T* tmp_buffer,
+ const T* attention_probs,
+ const T* V,
+ int batch_size,
+ int past_sequence_length,
+ int max_sequence_length,
+ int v_head_size,
+ const T* past_value_data,
+ T* present_value_data,
+ const int32_t* cache_indir_data,
+ int beam_width,
+ ThreadPool* tp) const {
+ const int total_sequence_length = past_sequence_length + 1;
+
+ TensorOpCost unit_cost;
+ unit_cost.compute_cycles = static_cast(SafeInt(2) * v_head_size * total_sequence_length);
+ unit_cost.bytes_loaded = static_cast(SafeInt(3) * v_head_size * total_sequence_length * sizeof(T));
+ unit_cost.bytes_stored = static_cast(SafeInt(2) * v_head_size * total_sequence_length * sizeof(T));
+
+ // Cost of appending current value to present value
+ unit_cost.compute_cycles += static_cast(v_head_size);
+ unit_cost.bytes_loaded += static_cast(v_head_size);
+
+ ThreadPool::TryParallelFor(tp, SafeInt(batch_size) * num_heads_, unit_cost, [&](std::ptrdiff_t begin, std::ptrdiff_t end) {
+ for (std::ptrdiff_t i = begin; i != end; ++i) {
+ const std::ptrdiff_t batch_index = i / num_heads_;
+ const std::ptrdiff_t head_index = i % num_heads_;
+ const std::ptrdiff_t beam_batch_index = batch_index / beam_width;
+
+ // Compute the attention score
+ // (1, T) x (T, H_v) -> (1, H_v)
+ // Decompose into T (1, 1) x (1, H_v) -> (1, H_v) operations and accumulate.
+ {
+ const T* attn_probs_ptr = attention_probs + (i + 1) * total_sequence_length - 1;
+ math::Scale(v_head_size,
+ static_cast(*attn_probs_ptr),
+ V + i * v_head_size,
+ output + i * v_head_size,
+ nullptr);
+ }
+ {
+ for (std::ptrdiff_t j = 0; j < past_sequence_length; ++j) {
+ const int* beam_indices = &cache_indir_data[batch_index * max_sequence_length];
+ const std::ptrdiff_t beam_offset = static_cast(beam_indices[j]) * num_heads_ *
+ max_sequence_length * v_head_size;
+ const std::ptrdiff_t beam_batch_offset = (beam_batch_index * beam_width * num_heads_ + head_index) *
+ max_sequence_length * v_head_size;
+ const T* past_value_vec = past_value_data + beam_offset + beam_batch_offset;
+ const T* attn_probs_ptr = attention_probs + j + i * total_sequence_length;
+
+ math::Scale(v_head_size,
+ static_cast(*attn_probs_ptr),
+ past_value_vec + j * v_head_size,
+ tmp_buffer + i * v_head_size,
+ nullptr);
+ math::Add(v_head_size,
+ output + i * v_head_size,
+ tmp_buffer + i * v_head_size,
+ output + i * v_head_size,
+ nullptr);
+ }
+ }
+
+ // Append current value to present value (past_present_share_buffer_ is true)
+ memcpy(present_value_data + (i * max_sequence_length + past_sequence_length) * v_head_size,
+ V + i * v_head_size,
+ v_head_size * sizeof(T));
+ }
+ });
+ }
};
} // namespace contrib
diff --git a/onnxruntime/contrib_ops/cpu/bert/attention_parameters.h b/onnxruntime/contrib_ops/cpu/bert/attention_parameters.h
new file mode 100644
index 0000000000000..417865bb166ec
--- /dev/null
+++ b/onnxruntime/contrib_ops/cpu/bert/attention_parameters.h
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include "contrib_ops/cpu/bert/attention_common.h"
+
+namespace onnxruntime {
+namespace contrib {
+
+// Parameters deduced from node attributes and inputs/outputs.
+struct AttentionParameters {
+ int batch_size;
+ int sequence_length;
+ int kv_sequence_length; // input sequence length of K or V
+ int past_sequence_length; // sequence length in past state of K or V
+ int total_sequence_length; // total sequence length of K or V
+ int max_sequence_length; // max sequence length from 4D mask
+ int input_hidden_size; // first dimension of weights for input projection
+ int hidden_size; // hidden size of Q or K
+ int head_size; // hidden size per head of Q or K
+ int v_hidden_size; // hidden size of V
+ int v_head_size; // hidden size per head of V
+ int num_heads;
+ int num_splits;
+ int rotary_embedding;
+ int beam_width;
+ bool is_unidirectional;
+ bool past_present_share_buffer;
+ bool do_rotary;
+ bool broadcast_attn_bias_dim_0;
+ bool broadcast_attn_bias_dim_1;
+ float mask_filter_value;
+ float scale;
+ bool use_tf32;
+ AttentionMaskType mask_type;
+ AttentionQkvFormat qkv_format;
+};
+
+// Parameters deduced from node attributes and inputs/outputs.
+struct PackedAttentionParameters : AttentionParameters {
+ int token_count;
+};
+
+struct DecoderMaskedMultiHeadAttentionParameters : AttentionParameters {
+ int beam_width = 1;
+
+ // Only NeoX style rotary embedding is supported
+ int rotary_embedding_dim = 0;
+ int t_step = 0;
+
+ // Weather to use multihead attention(excludes matmul and bias)
+ bool is_mha = false;
+ bool is_cross_attention = false;
+ bool is_packed_qkv = false;
+
+ // Useful to better use global memory bandwidth on certain CUDA architectures.
+ // Turned off by default for now until we fully understand performance implications
+ // for all types of workloads.
+ // Can be turned on by appropriate environment variable (see attention_common.h).
+ bool kv_data_in_flight = false;
+
+ void* q = nullptr;
+ void* q_bias = nullptr;
+
+ void* k = nullptr;
+ void* k_bias = nullptr;
+
+ void* v = nullptr;
+ void* v_bias = nullptr;
+
+ void* attention_bias = nullptr;
+
+ void* k_cache = nullptr;
+ void* v_cache = nullptr;
+
+ void* out = nullptr;
+ void* out_qk = nullptr;
+
+ const int32_t* cache_indir = nullptr;
+ const int32_t* mask = nullptr; // [B, total_sequence_length]
+};
+
+// Parameters deduced from node attributes and inputs/outputs.
+struct GroupQueryAttentionParameters : AttentionParameters {
+ int seqlen_past_kv_cache; // sequence length of past kv tensor
+ int seqlen_present_kv_cache; // sequence length of present kv tensor
+ int kv_hidden_size;
+ int kv_num_heads;
+ int num_splits; // number of splits for splitkv
+ int rotary_dim; // rotary embedding dimension
+ int local_window_size;
+ bool kv_share_buffer;
+ bool is_packed_qkv;
+ bool is_subsequent_prompt; // indicates whether we have past context and seqlen > 1
+ bool is_first_prompt; // indicates whether this is first decoding step
+ bool rotary_interleaved;
+ bool use_smooth_softmax;
+ float softcap;
+ AttentionQkvFormat past_kv_format;
+ int zeros_count;
+ int* zero_ptr;
+};
+
+// Parameters for sparse attention.
+struct SparseAttentionParameters : AttentionParameters {
+ int kv_hidden_size; // hidden size of key or value
+ int kv_num_heads; // number of heads of key or value
+ bool do_rotary; // whether to use rotary embedding
+ bool rotary_interleaved; // whether to use interleaved rotary embedding
+ int rotary_dim; // rotary embedding dimension
+ int sparse_block_size; // block size for sparse attention
+ int num_sparse_layout; // number of sparse layout
+ int stride_col_indices; // shape of block_col_indices is [num_sparse_layout, stride_col_indices]
+ int stride_row_indices; // shape of block_row_indices is [num_sparse_layout, stride_row_indices]
+ bool is_packed_qkv; // whether qkv is packed
+ int max_rotary_sequence_length; // max sequence length for rotary cos/sin cache
+ int max_cache_sequence_length; // max sequence length for kv cache buffer
+};
+
+} // namespace contrib
+} // namespace onnxruntime
diff --git a/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.cc b/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.cc
index e6f65f92e14f4..0d2de59c05394 100644
--- a/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.cc
+++ b/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.cc
@@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-#include "attention_cpu_base.h"
-#include "attention_utils.h"
-#include "core/platform/env_var_utils.h"
+#include "contrib_ops/cpu/bert/attention_cpu_base.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
+#include "contrib_ops/cpu/bert/attention_utils.h"
#include "contrib_ops/cpu/bert/multihead_attention_helper.h"
#include "contrib_ops/cpu/bert/decoder_masked_multihead_attention.h"
+#include "core/platform/env_var_utils.h"
using namespace ::onnxruntime::common;
using namespace ONNX_NAMESPACE;
@@ -65,7 +66,7 @@ Status DecoderMaskedMultiHeadAttention::Compute(OpKernelContext* context) con
const Tensor* cache_indir = context->Input(kCacheIndirectionInputIndex);
const Tensor* bias = context->Input(kBiasIndex);
- DecoderMaskedMultiHeadAttentionParams parameters;
+ DecoderMaskedMultiHeadAttentionParameters parameters;
bool is_unidirectional = false;
ORT_RETURN_IF_ERROR(multihead_attention_helper::CheckInputs(query,
@@ -76,6 +77,7 @@ Status DecoderMaskedMultiHeadAttention::Compute(OpKernelContext* context) con
attention_bias,
past_key,
past_value,
+ cache_indir,
past_seq_len,
¶meters,
num_heads_,
@@ -188,9 +190,9 @@ Status DecoderMaskedMultiHeadAttention::Compute(OpKernelContext* context) con
return ApplyAttention(Q.GetMutable()->MutableData(),
key->Data(),
value->Data(),
- mask_index, nullptr /* past */, past_key, past_value, output, present_key, present_value,
+ mask_index, nullptr /* past */, past_key, past_value, output, present_key, present_value, output_qk,
batch_size, 1 /* sequence_length */, parameters.kv_sequence_length,
- head_size, v_head_size, v_hidden_size, attention_bias, context, output_qk);
+ head_size, v_head_size, v_hidden_size, attention_bias, context);
}
OrtValue K, V;
@@ -204,9 +206,9 @@ Status DecoderMaskedMultiHeadAttention::Compute(OpKernelContext* context) con
return ApplyAttention(Q.GetMutable()->MutableData(),
K.GetMutable()->MutableData(),
V.GetMutable()->MutableData(),
- mask_index, nullptr /* past */, past_key, past_value, output, present_key, present_value,
+ mask_index, nullptr /* past */, past_key, past_value, output, present_key, present_value, output_qk,
batch_size, 1 /* sequence_length */, parameters.kv_sequence_length,
- head_size, v_head_size, v_hidden_size, attention_bias, context, output_qk,
+ head_size, v_head_size, v_hidden_size, attention_bias, context,
parameters.past_sequence_length, true /* past_present_share_buffer */);
}
@@ -221,253 +223,5 @@ Status DecoderMaskedMultiHeadAttention::Compute(OpKernelContext* context) con
beam_width_value, output_qk);
}
-template
-Status DecoderMaskedMultiHeadAttention::ApplyAttentionWithBeams(
- const T* Q,
- const T* K,
- const T* V,
- const Tensor* mask_index,
- const Tensor* past_key,
- const Tensor* past_value,
- Tensor* output,
- Tensor* present_key,
- Tensor* present_value,
- int batch_size,
- int past_sequence_length,
- int max_sequence_length,
- int head_size,
- int v_head_size,
- const Tensor* attn_bias,
- bool broadcast_attn_bias_dim_0,
- bool broadcast_attn_bias_dim_1,
- const Tensor* cache_indir,
- OpKernelContext* context,
- int beam_width,
- Tensor* output_qk) const {
- AllocatorPtr allocator;
- ORT_RETURN_IF_ERROR(context->GetTempSpaceAllocator(&allocator));
-
- auto* tp = context->GetOperatorThreadPool();
-
- int total_sequence_length = past_sequence_length + 1;
- size_t bytes = SafeInt(batch_size) * num_heads_ * total_sequence_length * sizeof(T);
- auto attention_probs = allocator->Alloc(bytes);
- BufferUniquePtr scratch_buffer(attention_probs, BufferDeleter(allocator));
-
- T* output_qk_data = (output_qk != nullptr) ? output_qk->MutableData() : nullptr;
-
- const int32_t* mask_index_data = mask_index != nullptr ? mask_index->Data() : nullptr;
- const T* attn_bias_data = attn_bias != nullptr ? attn_bias->Data() : nullptr;
-
- ComputeAttentionProbsWithBeams(static_cast(attention_probs), Q, K, mask_index_data, batch_size,
- past_sequence_length, max_sequence_length, head_size, past_key->Data(),
- present_key->MutableData(), tp, attn_bias_data, broadcast_attn_bias_dim_0,
- broadcast_attn_bias_dim_1, cache_indir->Data(), beam_width, output_qk_data);
-
- // Compute the attentionScore * Value: out_tmp(B, N, 1, H_v) = attention_probs(B, N, 1, T) x V(B, N, T, H_v)
- auto out_tmp_data = allocator->Alloc(SafeInt(batch_size) * num_heads_ * v_head_size * sizeof(T));
- BufferUniquePtr out_tmp_buffer(out_tmp_data, BufferDeleter(std::move(allocator)));
-
- ComputeVxAttentionScoreWithBeams(output->MutableData(), static_cast(out_tmp_data),
- static_cast(attention_probs), V, batch_size,
- past_sequence_length, max_sequence_length, v_head_size, past_value->Data(),
- present_value->MutableData(), cache_indir->Data(), beam_width, tp);
-
- return Status::OK();
-}
-
-template
-void DecoderMaskedMultiHeadAttention::ComputeAttentionProbsWithBeams(
- T* attention_probs,
- const T* Q,
- const T* K,
- const int32_t* mask_index_data,
- int batch_size,
- int past_sequence_length,
- int max_sequence_length,
- int head_size,
- const T* past_key_data,
- T* present_key_data,
- ThreadPool* tp,
- const T* attn_bias_data,
- bool broadcast_attn_bias_dim_0,
- bool broadcast_attn_bias_dim_1,
- const int32_t* cache_indir_data,
- int beam_width,
- T* output_qk_data) const {
- float scale = scale_ == 0.0f ? 1.0f / sqrt(static_cast(head_size)) : scale_;
-
- TensorOpCost unit_cost;
- auto total_sequence_length = past_sequence_length + 1;
- const ptrdiff_t probs_matrix_size = total_sequence_length;
- const ptrdiff_t probs_matrix_bytes = probs_matrix_size * sizeof(T);
-
- unit_cost.compute_cycles = static_cast((SafeInt(2) * head_size - 1) * total_sequence_length);
- unit_cost.bytes_loaded = static_cast(SafeInt(2) * head_size * total_sequence_length * sizeof(T));
- unit_cost.bytes_stored = static_cast(SafeInt(head_size) * total_sequence_length * sizeof(T));
-
- if (attn_bias_data != nullptr) {
- unit_cost.bytes_loaded += static_cast(probs_matrix_bytes) * 2;
- unit_cost.bytes_stored += probs_matrix_bytes;
- }
-
- if (mask_index_data != nullptr) {
- unit_cost.bytes_stored += probs_matrix_bytes;
- }
-
- // Cost of appending current key to present key
- unit_cost.compute_cycles += static_cast(head_size);
- unit_cost.bytes_loaded += static_cast(head_size);
-
- // Parallel for loop
- const int loop_len = batch_size * num_heads_;
- ThreadPool::TryParallelFor(tp, loop_len, unit_cost, [&](std::ptrdiff_t begin, std::ptrdiff_t end) {
- for (std::ptrdiff_t i = begin; i != end; ++i) {
- const std::ptrdiff_t batch_index = i / num_heads_;
- const std::ptrdiff_t head_index = i % num_heads_;
- const std::ptrdiff_t beam_batch_index = batch_index / beam_width;
- const T* q_vec = Q + i * head_size;
- const std::ptrdiff_t attn_bias_base_offset = ((broadcast_attn_bias_dim_0 ? 0 : (beam_batch_index * num_heads_)) +
- (broadcast_attn_bias_dim_1 ? 0 : head_index)) *
- probs_matrix_size;
-
- {
- // Calculate the latest position of the attention_probs
- // (1, H) x (T, H)^T -> (1, T)
- // Decompose into T (1, H) x (1, H)^T -> (1, 1) operations
- auto last_offset = past_sequence_length + i * probs_matrix_size;
- T* attention_probs_ptr = reinterpret_cast(attention_probs) + last_offset;
- math::Dot(head_size, q_vec, K + i * head_size, attention_probs_ptr, nullptr);
-
- *attention_probs_ptr *= scale;
- // Apply the attention bias and mask
- if (attn_bias_data != nullptr) {
- *attention_probs_ptr += attn_bias_data[attn_bias_base_offset + past_sequence_length];
- }
- bool is_masked = (mask_index_data != nullptr) &&
- (mask_index_data[(batch_index + 1) * total_sequence_length - 1] == 0);
- if (is_masked) {
- *attention_probs_ptr += mask_filter_value_;
- }
- }
-
- {
- // Calculate the rest of the attention_probs
- for (std::ptrdiff_t j = 0; j < past_sequence_length; ++j) {
- const int* beam_indices = &cache_indir_data[batch_index * max_sequence_length];
- const std::ptrdiff_t beam_offset = static_cast(beam_indices[j]) * num_heads_ *
- max_sequence_length * head_size;
- const std::ptrdiff_t beam_batch_offset = (beam_batch_index * beam_width * num_heads_ + head_index) *
- max_sequence_length * head_size;
- const T* past_k_vec = past_key_data + beam_batch_offset + beam_offset + j * head_size;
- T* output = reinterpret_cast(attention_probs) + j + i * probs_matrix_size;
- math::Dot(head_size, q_vec, past_k_vec, output, nullptr);
-
- *output *= scale;
- // Apply the attention bias and mask
- if (attn_bias_data != nullptr) {
- *output += attn_bias_data[attn_bias_base_offset + j];
- }
- bool is_masked = (mask_index_data != nullptr) &&
- (mask_index_data[batch_index * total_sequence_length + j] == 0);
- if (is_masked) {
- *output += mask_filter_value_;
- }
- }
- }
- // Append current key to present key (past_present_share_buffer_ is true)
- memcpy(present_key_data + (i * max_sequence_length + past_sequence_length) * head_size,
- K + i * head_size, head_size * sizeof(T));
- }
- });
-
- if (output_qk_data != nullptr) {
- // Output the scaled Q*K^T if needed.
- memcpy(output_qk_data, attention_probs,
- SafeInt(batch_size) * num_heads_ * total_sequence_length * sizeof(T));
- }
-
- // attention_probs(B, N, 1, T) = Softmax(attention_probs)
- {
- const int N = batch_size * num_heads_;
- const int D = total_sequence_length;
- ComputeAttentionSoftmaxInplace(attention_probs, N, D, tp);
- }
-}
-
-template
-void DecoderMaskedMultiHeadAttention::ComputeVxAttentionScoreWithBeams(
- T* output,
- T* tmp_buffer,
- const T* attention_probs,
- const T* V,
- int batch_size,
- int past_sequence_length,
- int max_sequence_length,
- int v_head_size,
- const T* past_value_data,
- T* present_value_data,
- const int32_t* cache_indir_data,
- int beam_width,
- ThreadPool* tp) const {
- const int total_sequence_length = past_sequence_length + 1;
-
- TensorOpCost unit_cost;
- unit_cost.compute_cycles = static_cast(SafeInt(2) * v_head_size * total_sequence_length);
- unit_cost.bytes_loaded = static_cast(SafeInt(3) * v_head_size * total_sequence_length * sizeof(T));
- unit_cost.bytes_stored = static_cast(SafeInt(2) * v_head_size * total_sequence_length * sizeof(T));
-
- // Cost of appending current value to present value
- unit_cost.compute_cycles += static_cast(v_head_size);
- unit_cost.bytes_loaded += static_cast(v_head_size);
-
- ThreadPool::TryParallelFor(
- tp, SafeInt(batch_size) * num_heads_, unit_cost, [&](std::ptrdiff_t begin, std::ptrdiff_t end) {
- for (std::ptrdiff_t i = begin; i != end; ++i) {
- const std::ptrdiff_t batch_index = i / num_heads_;
- const std::ptrdiff_t head_index = i % num_heads_;
- const std::ptrdiff_t beam_batch_index = batch_index / beam_width;
-
- // Compute the attention score
- // (1, T) x (T, H_v) -> (1, H_v)
- // Decompose into T (1, 1) x (1, H_v) -> (1, H_v) operations and accumulate.
- {
- const T* attn_probs_ptr = attention_probs + (i + 1) * total_sequence_length - 1;
- math::Scale(v_head_size,
- static_cast(*attn_probs_ptr),
- V + i * v_head_size,
- output + i * v_head_size,
- nullptr);
- }
- {
- for (std::ptrdiff_t j = 0; j < past_sequence_length; ++j) {
- const int* beam_indices = &cache_indir_data[batch_index * max_sequence_length];
- const std::ptrdiff_t beam_offset = static_cast(beam_indices[j]) * num_heads_ *
- max_sequence_length * v_head_size;
- const std::ptrdiff_t beam_batch_offset = (beam_batch_index * beam_width * num_heads_ + head_index) *
- max_sequence_length * v_head_size;
- const T* past_value_vec = past_value_data + beam_offset + beam_batch_offset;
- const T* attn_probs_ptr = attention_probs + j + i * total_sequence_length;
-
- math::Scale(v_head_size,
- static_cast(*attn_probs_ptr),
- past_value_vec + j * v_head_size,
- tmp_buffer + i * v_head_size,
- nullptr);
- math::Add(v_head_size,
- output + i * v_head_size,
- tmp_buffer + i * v_head_size,
- output + i * v_head_size,
- nullptr);
- }
- }
- // Append current value to present value (past_present_share_buffer_ is true)
- memcpy(present_value_data + (i * max_sequence_length + past_sequence_length) * v_head_size,
- V + i * v_head_size,
- v_head_size * sizeof(T));
- }
- });
-}
-
} // namespace contrib
} // namespace onnxruntime
diff --git a/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.h b/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.h
index d5167e8989669..1a4bc72e2f73a 100644
--- a/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.h
+++ b/onnxruntime/contrib_ops/cpu/bert/decoder_masked_multihead_attention.h
@@ -13,57 +13,6 @@ template
class DecoderMaskedMultiHeadAttention final : public OpKernel, public AttentionCPUBase {
public:
DecoderMaskedMultiHeadAttention(const OpKernelInfo& info);
- Status ApplyAttentionWithBeams(const T* Q,
- const T* K,
- const T* V,
- const Tensor* mask_index,
- const Tensor* past_key,
- const Tensor* past_value,
- Tensor* output,
- Tensor* present_key,
- Tensor* present_value,
- int batch_size,
- int past_sequence_length,
- int max_sequence_length,
- int head_size,
- int v_head_size,
- const Tensor* attn_bias,
- bool broadcast_attn_bias_dim_0,
- bool broadcast_attn_bias_dim_1,
- const Tensor* cache_indir,
- OpKernelContext* context,
- int beam_width,
- Tensor* output_qk = nullptr) const;
- void ComputeAttentionProbsWithBeams(T* attention_probs,
- const T* Q,
- const T* K,
- const int32_t* mask_index_data,
- int batch_size,
- int past_sequence_length,
- int max_sequence_length,
- int head_size,
- const T* past_key,
- T* present_key,
- ThreadPool* tp,
- const T* attn_bias_data,
- bool broadcast_attn_bias_dim_0,
- bool broadcast_attn_bias_dim_1,
- const int32_t* cache_indir_data,
- int beam_width,
- T* output_qk_data = nullptr) const;
- void ComputeVxAttentionScoreWithBeams(T* output,
- T* tmp_buffer,
- const T* attention_probs,
- const T* V,
- int batch_size,
- int past_sequence_length,
- int max_sequence_length,
- int v_head_size,
- const T* past_value,
- T* present_value,
- const int32_t* cache_indir_data,
- int beam_width,
- ThreadPool* tp) const;
Status Compute(OpKernelContext* context) const override;
protected:
diff --git a/onnxruntime/contrib_ops/cpu/bert/gqa_attention_base.h b/onnxruntime/contrib_ops/cpu/bert/gqa_attention_base.h
index ff6cb8edc0231..c8c66c880852f 100644
--- a/onnxruntime/contrib_ops/cpu/bert/gqa_attention_base.h
+++ b/onnxruntime/contrib_ops/cpu/bert/gqa_attention_base.h
@@ -4,10 +4,11 @@
#pragma once
#include "contrib_ops/cpu/bert/attention_base.h"
+#include "contrib_ops/cpu/bert/attention_common.h"
#include "contrib_ops/cpu/bert/attention_helper.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
#include "core/common/common.h"
-#include "contrib_ops/cpu/bert/attention_common.h"
#include "core/common/safeint.h"
#include "core/framework/op_kernel.h"
diff --git a/onnxruntime/contrib_ops/cpu/bert/group_query_attention_helper.h b/onnxruntime/contrib_ops/cpu/bert/group_query_attention_helper.h
index 7bffd768c8f7c..fa0d33e891f46 100644
--- a/onnxruntime/contrib_ops/cpu/bert/group_query_attention_helper.h
+++ b/onnxruntime/contrib_ops/cpu/bert/group_query_attention_helper.h
@@ -6,6 +6,7 @@
#include "core/common/common.h"
#include "core/providers/common.h"
#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
namespace onnxruntime {
namespace contrib {
diff --git a/onnxruntime/contrib_ops/cpu/bert/multihead_attention.cc b/onnxruntime/contrib_ops/cpu/bert/multihead_attention.cc
index ca818f09c4b1e..996f913ef6565 100644
--- a/onnxruntime/contrib_ops/cpu/bert/multihead_attention.cc
+++ b/onnxruntime/contrib_ops/cpu/bert/multihead_attention.cc
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-#include "attention_cpu_base.h"
-#include "multihead_attention.h"
-#include "multihead_attention_helper.h"
-#include "attention_utils.h"
+#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_cpu_base.h"
+#include "contrib_ops/cpu/bert/multihead_attention.h"
+#include "contrib_ops/cpu/bert/multihead_attention_helper.h"
+#include "contrib_ops/cpu/bert/attention_utils.h"
#include "core/common/common.h"
#include "core/framework/tensorprotoutils.h"
@@ -48,6 +49,8 @@ MultiHeadAttention::MultiHeadAttention(const OpKernelInfo& info) : OpKernel(i
l2_cache_size_ = env.GetL2CacheSize();
disable_flash_ = ParseEnvironmentVariableWithDefault(attention::kDisableFlashAttention, false);
+
+ disable_decoder_attention_ = ParseEnvironmentVariableWithDefault(attention::kDisableDecoderAttention, false);
}
template
@@ -60,6 +63,8 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
const Tensor* attn_bias = context->Input(5);
const Tensor* past_key = context->Input(6);
const Tensor* past_value = context->Input(7);
+ const Tensor* past_sequence_length = context->Input(8);
+ const Tensor* cache_indirection = context->Input(9);
if (query->Shape().GetDims().size() == 5) {
ORT_NOT_IMPLEMENTED("Packed QKV of shape (B, L, N, 3, H) not implemented for CPU");
@@ -68,8 +73,12 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
ORT_NOT_IMPLEMENTED("Packed KV not implemented for CPU");
}
+ bool past_present_share_buffer = past_key != nullptr && past_sequence_length != nullptr;
+ if (past_key != nullptr && past_sequence_length != nullptr && cache_indirection != nullptr) {
+ ORT_ENFORCE(past_present_share_buffer);
+ }
+
AttentionParameters parameters = {};
- bool past_present_share_buffer = false;
ORT_RETURN_IF_ERROR(multihead_attention_helper::CheckInputs(query,
key,
value,
@@ -78,7 +87,8 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
attn_bias,
past_key,
past_value,
- nullptr,
+ cache_indirection,
+ past_sequence_length,
¶meters,
num_heads_,
mask_filter_value_,
@@ -86,11 +96,24 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
is_unidirectional_,
past_present_share_buffer,
kMultiHeadAttention));
+ DUMP_CPU_STRING_INIT();
+ DUMP_CPU_STRING("Batch size = ", parameters.batch_size);
+ DUMP_CPU_STRING("Sequence length = ", parameters.sequence_length);
+ DUMP_CPU_STRING("Past sequence length = ", parameters.past_sequence_length);
+ DUMP_CPU_STRING("KV sequence length = ", parameters.kv_sequence_length);
+ DUMP_CPU_STRING("Total sequence length = ", parameters.total_sequence_length);
+ DUMP_CPU_STRING("Max sequence length = ", parameters.max_sequence_length);
+ DUMP_CPU_STRING("Hidden size = ", parameters.hidden_size);
+ DUMP_CPU_STRING("Head size = ", parameters.head_size);
+ DUMP_CPU_STRING("Num heads = ", parameters.num_heads);
+ DUMP_CPU_STRING("Buffer sharing = ", (parameters.past_present_share_buffer == true));
+ DUMP_CPU_STRING("QKV format = ", parameters.qkv_format);
+ DUMP_CPU_STRING("Beam width = ", parameters.beam_width);
const int batch_size = parameters.batch_size;
const int q_sequence_length = parameters.sequence_length;
const int kv_sequence_length = parameters.kv_sequence_length;
- const int total_kv_sequence_length = parameters.total_sequence_length;
+ const int total_sequence_length = parameters.total_sequence_length;
int qk_head_size = parameters.head_size;
int v_head_size = parameters.v_head_size;
int qk_hidden_size = parameters.hidden_size;
@@ -106,17 +129,38 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
const int k_bias_offset = qk_hidden_size;
const int v_bias_offset = 2 * qk_hidden_size;
- // If optional outputs aren't needed, present_k and present_v will be null
- std::vector present_k_shape({static_cast(batch_size),
+ // If optional outputs aren't needed, present_key, present_value, and output_qk will be null
+ std::vector present_key_shape({static_cast(batch_size),
+ static_cast(num_heads_),
+ static_cast(parameters.max_sequence_length),
+ static_cast(qk_head_size)});
+ std::vector present_value_shape({static_cast(batch_size),
+ static_cast(num_heads_),
+ static_cast(parameters.max_sequence_length),
+ static_cast(v_head_size)});
+ std::vector output_qk_shape({static_cast(batch_size),
static_cast(num_heads_),
- static_cast(total_kv_sequence_length),
- static_cast(qk_head_size)});
- std::vector present_v_shape({static_cast(batch_size),
- static_cast(num_heads_),
- static_cast(total_kv_sequence_length),
- static_cast(v_head_size)});
- Tensor* present_k = context->Output(1, present_k_shape);
- Tensor* present_v = context->Output(2, present_v_shape);
+ static_cast(q_sequence_length),
+ static_cast(total_sequence_length)});
+ Tensor* present_key = context->Output(1, present_key_shape);
+ Tensor* present_value = context->Output(2, present_value_shape);
+ Tensor* output_qk = context->Output(3, output_qk_shape);
+
+ bool use_decoder_masked_multihead_attention = false;
+ if (cache_indirection != nullptr) {
+ bool use_dmmha_self_attention = parameters.qkv_format == AttentionQkvFormat::Q_K_V_BSNH &&
+ parameters.past_present_share_buffer &&
+ parameters.past_sequence_length > 0;
+ bool use_dmmha_cross_attention = parameters.qkv_format == AttentionQkvFormat::Q_K_V_BSNH_BNSH_BNSH &&
+ past_key == nullptr && past_value == nullptr && nullptr != past_sequence_length &&
+ parameters.past_sequence_length != *((*past_sequence_length).template Data());
+ use_decoder_masked_multihead_attention = !disable_decoder_attention_ &&
+ (use_dmmha_self_attention || use_dmmha_cross_attention) &&
+ parameters.sequence_length == 1 &&
+ parameters.head_size == parameters.v_head_size &&
+ (parameters.mask_type == AttentionMaskType::MASK_2D_KEY_PADDING || parameters.mask_type == AttentionMaskType::MASK_NONE) &&
+ nullptr != past_sequence_length && nullptr != cache_indirection;
+ }
AllocatorPtr allocator;
ORT_RETURN_IF_ERROR(context->GetTempSpaceAllocator(&allocator));
@@ -125,15 +169,22 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
ORT_RETURN_IF_ERROR(MaybeTransposeToBNSHAndAddBias(
context, allocator, batch_size, num_heads_, q_sequence_length, qk_head_size, query, bias, q_bias_offset, Q));
- if (parameters.qkv_format == Q_K_V_BSNH_BNSH_BNSH) {
+ if (parameters.qkv_format == AttentionQkvFormat::Q_K_V_BSNH_BNSH_BNSH) {
// For cross attention with k and v in BNSH format, we assume that bias for key and value are zeros.
// So we don't need to add bias for key and value here.
assert(past_key == nullptr);
assert(past_value == nullptr);
+
+ if (use_decoder_masked_multihead_attention) {
+ parameters.total_sequence_length = parameters.kv_sequence_length;
+ parameters.max_sequence_length = parameters.kv_sequence_length;
+ }
+
return ApplyAttention(Q.GetMutable()->MutableData(),
key->Data(),
value->Data(),
- key_padding_mask, nullptr /* past */, past_key, past_value, output, present_k, present_v,
+ key_padding_mask, nullptr /* past */, past_key, past_value,
+ output, present_key, present_value, output_qk,
batch_size, q_sequence_length, kv_sequence_length,
qk_head_size, v_head_size, v_hidden_size, attn_bias, context);
}
@@ -152,8 +203,11 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
attn_bias == nullptr &&
past_key == nullptr &&
past_value == nullptr &&
- present_k == nullptr &&
- present_v == nullptr &&
+ past_sequence_length == nullptr &&
+ cache_indirection == nullptr &&
+ present_key == nullptr &&
+ present_value == nullptr &&
+ output_qk == nullptr &&
l2_cache_size_ > 0) {
MlasFlashAttentionThreadedArgs args;
args.batch_size = batch_size;
@@ -209,11 +263,42 @@ Status MultiHeadAttention::Compute(OpKernelContext* context) const {
return Status::OK();
}
+ if (use_decoder_masked_multihead_attention) {
+ // No production use-case will incur this copy cost as the implementation of
+ // DecoderMaskedMultiHeadAttention is written in such a way that the past and present buffers
+ // must be shared to have parity in the outputs.
+ // This is just to circumvent the OpTester's limitation of not being able to bind a specific
+ // buffer to inputs/outputs.
+ auto* past_key_data = (past_key == nullptr) ? nullptr : past_key->Data();
+ auto* past_value_data = (past_value == nullptr) ? nullptr : past_value->Data();
+ auto* present_key_data = (present_key == nullptr) ? nullptr : present_key->MutableData();
+ auto* present_value_data = (present_value == nullptr) ? nullptr : present_value->MutableData();
+
+ if (present_key_data != past_key_data) {
+ DUMP_CPU_STRING("Copying past_key to present_key for OpTester");
+ memcpy(present_key_data, past_key_data, past_key->SizeInBytes());
+ }
+ if (present_value_data != past_value_data) {
+ DUMP_CPU_STRING("Copying past_value to present_value for OpTester");
+ memcpy(present_value_data, past_value_data, past_value->SizeInBytes());
+ }
+
+ return ApplyAttentionWithBeams(Q.GetMutable()->MutableData(),
+ K.GetMutable()->MutableData(),
+ V.GetMutable()->MutableData(),
+ key_padding_mask, past_key, past_value, output, present_key, present_value,
+ batch_size, *((*past_sequence_length).template Data()), parameters.max_sequence_length,
+ qk_head_size, v_head_size, attn_bias, parameters.broadcast_attn_bias_dim_0,
+ parameters.broadcast_attn_bias_dim_1, cache_indirection, context,
+ parameters.beam_width, output_qk);
+ }
+
// Compute the attention score and apply the score to V
return ApplyAttention(Q.GetMutable()->MutableData(),
K.GetMutable()->MutableData(),
V.GetMutable()->MutableData(),
- key_padding_mask, nullptr /* past */, past_key, past_value, output, present_k, present_v,
+ key_padding_mask, nullptr /* past */, past_key, past_value,
+ output, present_key, present_value, output_qk,
batch_size, q_sequence_length, kv_sequence_length,
qk_head_size, v_head_size, v_hidden_size, attn_bias, context);
}
diff --git a/onnxruntime/contrib_ops/cpu/bert/multihead_attention.h b/onnxruntime/contrib_ops/cpu/bert/multihead_attention.h
index 8a9bef1b2bf0d..a420b6a526882 100644
--- a/onnxruntime/contrib_ops/cpu/bert/multihead_attention.h
+++ b/onnxruntime/contrib_ops/cpu/bert/multihead_attention.h
@@ -20,6 +20,7 @@ class MultiHeadAttention final : public OpKernel, public AttentionCPUBase {
float mask_filter_value_;
bool is_unidirectional_;
bool disable_flash_;
+ bool disable_decoder_attention_;
int l2_cache_size_;
};
diff --git a/onnxruntime/contrib_ops/cpu/bert/multihead_attention_helper.h b/onnxruntime/contrib_ops/cpu/bert/multihead_attention_helper.h
index 0cfe90963c334..8af6faadd6e92 100644
--- a/onnxruntime/contrib_ops/cpu/bert/multihead_attention_helper.h
+++ b/onnxruntime/contrib_ops/cpu/bert/multihead_attention_helper.h
@@ -6,6 +6,8 @@
#include "core/common/common.h"
#include "core/providers/common.h"
#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
+#include "contrib_ops/cpu/utils/dump_tensor.h"
namespace onnxruntime {
namespace contrib {
@@ -153,7 +155,7 @@ Status CheckPast(const T* past_key, const T* past_value, const T* past_seq_len,
}
if (past_key_dims[2] != past_value_dims[2]) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
- "Input 'past_key' and 'past_value' shall have same dim 2 (past_sequence_length). ",
+ "Input 'past_key' and 'past_value' shall have same dim 2 (past_sequence_length or max_sequence_length). ",
past_key_dims[2], " vs ", past_value_dims[2]);
}
if (past_key_dims[3] != head_size) {
@@ -233,6 +235,35 @@ AttentionMaskType GetMaskType(const T* key_padding_mask, int batch_size, int seq
return mask_type;
}
+inline Status CheckCacheIndirection(
+ const gsl::span& cache_indir_dims, int batch_beam_size, int& num_beams, int max_sequence_length) {
+ if (cache_indir_dims.size() != 3) {
+ return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
+ "Input 'cache_indirection' is expected to have 3 dimensions, got ",
+ cache_indir_dims.size());
+ }
+ num_beams = static_cast(cache_indir_dims[1]);
+ if (cache_indir_dims[1] == 0) {
+ return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
+ "Input 'cache_indirection' dimension 1 should be num_beams, got ",
+ cache_indir_dims[1]);
+ }
+ if (cache_indir_dims[0] != static_cast(batch_beam_size / num_beams)) {
+ return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
+ "Input 'cache_indirection' dimension 0 should be batch_size, got ",
+ cache_indir_dims[0]);
+ }
+ if (max_sequence_length > 0 && cache_indir_dims[2] != static_cast(max_sequence_length)) {
+ // First condition is to avoid this check for cross attention layers where
+ // past key/past value are passed directly into key/value (which means
+ // that max_sequence_length = 0)
+ return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
+ "Input 'cache_indirection' dimension 2 should be same as (or less than) max_sequence_length, got ",
+ cache_indir_dims[2]);
+ }
+ return Status::OK();
+}
+
template
Status CheckInputs(const T* query,
const T* key,
@@ -242,6 +273,7 @@ Status CheckInputs(const T* query,
const T* attention_bias,
const T* past_key,
const T* past_value,
+ const T* cache_indirection,
const T* past_seq_len,
void* parameters,
int num_heads,
@@ -263,6 +295,7 @@ Status CheckInputs(const T* query,
// L: kv_sequence_length
// T: total_sequence_length = P + L
// M: max_sequence_length of kv cache when past and present share buffer
+ // W: beam_width
// ---------------------------------------------------------------
// MultiHeadAttention inputs:
// ---------------------------------------------------------------
@@ -308,7 +341,8 @@ Status CheckInputs(const T* query,
// Other inputs:
// bias (Q/K/V) : None or (3 * D)
// key_padding_mask (K/V) : None or (B, T)
- // attention_bias : (1, N, S, T), or (B, N, S, T) where only 1 x N x S x T data is used in CUDA.
+ // attention_bias : (B, N, S, T), (1, N, S, T), (B, 1, S, T) or (1, 1, S, T)
+ // cache_indirection : (B, W, M)
//
// The following inputs are not used in cross attention (so they are None for cross attention):
// past_key : (B, N, P, H), or (B, N, M, H) when past_present_share_buffer is True.
@@ -408,6 +442,13 @@ Status CheckInputs(const T* query,
assert(qkv_format != UNKNOWN);
+ gsl::span cache_indir_dims;
+ int num_beams = 0;
+ if (cache_indirection != nullptr) {
+ cache_indir_dims = cache_indirection->Shape().GetDims();
+ ORT_RETURN_IF_ERROR(CheckCacheIndirection(cache_indir_dims, batch_size, num_beams, max_sequence_length));
+ }
+
if (parameters != nullptr) {
AttentionParameters* output_parameters = reinterpret_cast(parameters);
output_parameters->batch_size = batch_size;
@@ -430,6 +471,7 @@ Status CheckInputs(const T* query,
output_parameters->broadcast_attn_bias_dim_0 = attention_bias_dims.size() > 0 && attention_bias_dims[0] == 1;
output_parameters->broadcast_attn_bias_dim_1 = attention_bias_dims.size() > 1 && attention_bias_dims[1] == 1;
output_parameters->qkv_format = qkv_format;
+ output_parameters->beam_width = num_beams;
}
return Status::OK();
@@ -444,6 +486,7 @@ Status CheckInputs(const T* query,
const T* attention_bias,
const T* past_key,
const T* past_value,
+ const T* cache_indirection,
const T* past_seq_len,
void* parameters,
int num_heads,
@@ -457,7 +500,7 @@ Status CheckInputs(const T* query,
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "num_heads should be no larger than ", max_threads_per_block);
}
- return CheckInputs(query, key, value, bias, key_padding_mask, attention_bias, past_key, past_value,
+ return CheckInputs(query, key, value, bias, key_padding_mask, attention_bias, past_key, past_value, cache_indirection,
past_seq_len, parameters, num_heads, mask_filter_value, scale, is_unidirectional,
past_present_share_buffer, operator_type);
}
diff --git a/onnxruntime/contrib_ops/cpu/bert/rotary_helper.h b/onnxruntime/contrib_ops/cpu/bert/rotary_helper.h
index 714d962dfb34e..43415d6053fbb 100644
--- a/onnxruntime/contrib_ops/cpu/bert/rotary_helper.h
+++ b/onnxruntime/contrib_ops/cpu/bert/rotary_helper.h
@@ -5,7 +5,6 @@
#include "core/common/common.h"
#include "core/providers/common.h"
-#include "contrib_ops/cpu/bert/attention_common.h"
namespace onnxruntime {
namespace contrib {
diff --git a/onnxruntime/contrib_ops/cpu/quantization/attention_quant.cc b/onnxruntime/contrib_ops/cpu/quantization/attention_quant.cc
index 2c897f183164f..d369939a861d2 100644
--- a/onnxruntime/contrib_ops/cpu/quantization/attention_quant.cc
+++ b/onnxruntime/contrib_ops/cpu/quantization/attention_quant.cc
@@ -289,7 +289,7 @@ Status QAttention::Compute(OpKernelContext* context) const {
// Compute the attention score and apply the score to V
return ApplyAttention(Q, K, V, mask_index, past_tensor, nullptr /* past_key */, nullptr /* past_value*/,
- output, nullptr /* present_key */, nullptr /* present_value */,
+ output, nullptr /* present_key */, nullptr /* present_value */, nullptr /* output_qk */,
batch_size, sequence_length, sequence_length,
head_size, head_size, hidden_size, nullptr /* rel_pos_bias */, context);
}
diff --git a/onnxruntime/contrib_ops/cpu/skip_layer_norm_helper.h b/onnxruntime/contrib_ops/cpu/skip_layer_norm_helper.h
index 4c901f5650dbd..b70bd317b95b2 100644
--- a/onnxruntime/contrib_ops/cpu/skip_layer_norm_helper.h
+++ b/onnxruntime/contrib_ops/cpu/skip_layer_norm_helper.h
@@ -5,7 +5,6 @@
#include "core/common/common.h"
#include "core/providers/common.h"
-#include "contrib_ops/cpu/bert/attention_common.h"
namespace onnxruntime {
namespace contrib {
diff --git a/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_base.h b/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_base.h
index 37172074e5d86..2c719b3724106 100644
--- a/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_base.h
+++ b/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_base.h
@@ -4,12 +4,13 @@
#pragma once
#include "contrib_ops/cpu/bert/attention_helper.h"
+#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
+#include "contrib_ops/cpu/utils/dump_tensor.h"
#include "core/common/common.h"
-#include "contrib_ops/cpu/bert/attention_common.h"
#include "core/common/safeint.h"
#include "core/framework/op_kernel.h"
-#include "contrib_ops/cpu/utils/dump_tensor.h"
namespace onnxruntime {
namespace contrib {
@@ -159,11 +160,11 @@ class SparseAttentionBase {
int nonzero_elements = block_row_indices[(layout_index + 1) * parameters.stride_row_indices - 1];
int dense_nonzero = (parameters.stride_row_indices * (parameters.stride_row_indices - 1)) / 2;
layout_has_sparse[layout_index] = nonzero_elements < dense_nonzero;
- DUMP_STRING("layout_has_sparse[", layout_index, "]=", layout_has_sparse[layout_index]);
+ DUMP_CPU_STRING("layout_has_sparse[", layout_index, "]=", layout_has_sparse[layout_index]);
}
ThreadPool::TryParallelFor(tp, loop_len, unit_cost, [&](std::ptrdiff_t begin, std::ptrdiff_t end) {
- DUMP_STRING("batch_size=", batch_size, ",num_heads=", num_heads_, ",loop_len=", loop_len, ",begin=", begin, ",end=", end);
+ DUMP_CPU_STRING("batch_size=", batch_size, ",num_heads=", num_heads_, ",loop_len=", loop_len, ",begin=", begin, ",end=", end);
for (std::ptrdiff_t i = begin; i != end; ++i) {
const int batch_index = static_cast(i) / num_heads_;
const int head_index = static_cast(i) % num_heads_;
@@ -199,8 +200,8 @@ class SparseAttentionBase {
q = Q + q_input_chunk_length * i;
}
- DUMP_STRING("i=", i, ",batch_index=", batch_index, ",head_index=", head_index,
- ",past_seq_len=", past_seq_len, ",total_seq_len=", total_seq_len, ",packed_qkv=", packed_qkv);
+ DUMP_CPU_STRING("i=", i, ",batch_index=", batch_index, ",head_index=", head_index,
+ ",past_seq_len=", past_seq_len, ",total_seq_len=", total_seq_len, ",packed_qkv=", packed_qkv);
DUMP_CPU_TENSOR("Q", q, sequence_length, head_size);
DUMP_CPU_TENSOR("K", k, total_seq_len, head_size);
@@ -216,7 +217,7 @@ class SparseAttentionBase {
int layout_id = head_index % parameters.num_sparse_layout;
bool is_sparse_layout = layout_has_sparse[layout_id];
- DUMP_STRING("layout_id=", layout_id, ",is_sparse_layout=", is_sparse_layout);
+ DUMP_CPU_STRING("layout_id=", layout_id, ",is_sparse_layout=", is_sparse_layout);
if (!is_sparse_layout) { // dense
for (int q_id = 0; q_id < sequence_length; q_id++) {
@@ -246,19 +247,19 @@ class SparseAttentionBase {
int nonzero_blocks = end_in_col_indices - start_in_col_indices;
has_sparse = (nonzero_blocks != row_in_sparse_layout + 1);
- DUMP_STRING("q_id=", q_id,
- ",q_abs_position=", q_abs_position,
- ",sparse_block_size=", parameters.sparse_block_size,
- ",row_in_sparse_layout=", row_in_sparse_layout,
- ",start_in_col_indices=", start_in_col_indices,
- ",end_in_col_indices=", end_in_col_indices,
- ",nonzero_blocks=", nonzero_blocks,
- ",has_sparse=", has_sparse);
+ DUMP_CPU_STRING("q_id=", q_id,
+ ",q_abs_position=", q_abs_position,
+ ",sparse_block_size=", parameters.sparse_block_size,
+ ",row_in_sparse_layout=", row_in_sparse_layout,
+ ",start_in_col_indices=", start_in_col_indices,
+ ",end_in_col_indices=", end_in_col_indices,
+ ",nonzero_blocks=", nonzero_blocks,
+ ",has_sparse=", has_sparse);
// Expand attention mask for current row of q_id
if (has_sparse) {
int block_aligned_length = q_abs_position / parameters.sparse_block_size * parameters.sparse_block_size + parameters.sparse_block_size;
- DUMP_STRING("block_aligned_length=", block_aligned_length);
+ DUMP_CPU_STRING("block_aligned_length=", block_aligned_length);
std::fill_n(mask.begin(), block_aligned_length, 0);
for (int j = start_in_col_indices; j < end_in_col_indices; j++) {
@@ -344,7 +345,7 @@ class SparseAttentionBase {
ThreadPool::TryParallelFor(
tp, SafeInt(batch_size) * num_heads_, unit_cost, [&](std::ptrdiff_t begin, std::ptrdiff_t end) {
- DUMP_STRING("batch_size=", batch_size, ",num_heads=", num_heads_, ",begin=", begin, ",end=", end);
+ DUMP_CPU_STRING("batch_size=", batch_size, ",num_heads=", num_heads_, ",begin=", begin, ",end=", end);
for (std::ptrdiff_t i = begin; i != end; ++i) {
const int batch_index = static_cast(i / num_heads_);
@@ -353,8 +354,8 @@ class SparseAttentionBase {
const size_t past_chunk_length = static_cast(past_seq_len) * head_size;
const int total_seq_len = total_key_lengths[batch_index];
- DUMP_STRING("i=", i, ",batch_index=", batch_index, ",head_index=", head_index,
- ",past_seq_len=", past_seq_len, ",total_seq_len=", total_seq_len, ",packed_qkv=", packed_qkv);
+ DUMP_CPU_STRING("i=", i, ",batch_index=", batch_index, ",head_index=", head_index,
+ ",past_seq_len=", past_seq_len, ",total_seq_len=", total_seq_len, ",packed_qkv=", packed_qkv);
const T* v;
if (packed_qkv) {
diff --git a/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_helper.h b/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_helper.h
index ca69370b4ce17..dfb60f635bc33 100644
--- a/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_helper.h
+++ b/onnxruntime/contrib_ops/cpu/sparse/sparse_attention_helper.h
@@ -6,6 +6,7 @@
#include "core/common/common.h"
#include "core/providers/common.h"
#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
namespace onnxruntime {
namespace contrib {
diff --git a/onnxruntime/contrib_ops/cpu/transformers/sequences.h b/onnxruntime/contrib_ops/cpu/transformers/sequences.h
index 7dd1f28d270c7..e2f6b29a77a2c 100644
--- a/onnxruntime/contrib_ops/cpu/transformers/sequences.h
+++ b/onnxruntime/contrib_ops/cpu/transformers/sequences.h
@@ -5,6 +5,7 @@
#include
#include "contrib_ops/cpu/transformers/generation_shared.h"
+#include "contrib_ops/cpu/utils/console_dumper.h"
namespace onnxruntime {
namespace contrib {
diff --git a/onnxruntime/contrib_ops/cpu/utils/debug_macros.h b/onnxruntime/contrib_ops/cpu/utils/debug_macros.h
index d5cbaa0a3e6b7..47d0fc5e4008c 100644
--- a/onnxruntime/contrib_ops/cpu/utils/debug_macros.h
+++ b/onnxruntime/contrib_ops/cpu/utils/debug_macros.h
@@ -15,11 +15,13 @@
#if DUMP_CPU_TENSOR_LEVEL > 0
#define DUMP_CPU_TENSOR_INIT() onnxruntime::contrib::CpuTensorConsoleDumper cpu_dumper
#define DUMP_CPU_TENSOR(...) cpu_dumper.Print(__VA_ARGS__)
-#define DUMP_STRING(...) cpu_dumper.Print(::onnxruntime::MakeString(__VA_ARGS__))
+#define DUMP_CPU_STRING_INIT() DUMP_CPU_TENSOR_INIT()
+#define DUMP_CPU_STRING(...) cpu_dumper.Print(::onnxruntime::MakeString(__VA_ARGS__))
#else
-#define DUMP_CPU_TENSOR_INIT()
+#define DUMP_CPU_TENSOR_INIT(...)
#define DUMP_CPU_TENSOR(...)
-#define DUMP_STRING(...)
+#define DUMP_CPU_STRING_INIT(...)
+#define DUMP_CPU_STRING(...)
#endif
#if DUMP_CPU_TENSOR_LEVEL > 1
@@ -32,9 +34,13 @@
#if DUMP_TENSOR_LEVEL > 0
#define DUMP_TENSOR_INIT() onnxruntime::contrib::cuda::CudaTensorConsoleDumper dumper
#define DUMP_TENSOR(...) dumper.Print(__VA_ARGS__)
+#define DUMP_STRING_INIT() DUMP_TENSOR_INIT()
+#define DUMP_STRING(...) dumper.Print(::onnxruntime::MakeString(__VA_ARGS__))
#else
-#define DUMP_TENSOR_INIT()
+#define DUMP_TENSOR_INIT(...)
#define DUMP_TENSOR(...)
+#define DUMP_STRING_INIT(...)
+#define DUMP_STRING(...)
#endif
#if DUMP_TENSOR_LEVEL > 1
diff --git a/onnxruntime/contrib_ops/cuda/bert/attention_data.h b/onnxruntime/contrib_ops/cuda/bert/attention_data.h
new file mode 100644
index 0000000000000..c7b06d50858b4
--- /dev/null
+++ b/onnxruntime/contrib_ops/cuda/bert/attention_data.h
@@ -0,0 +1,185 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+#include
+#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
+
+namespace onnxruntime {
+namespace contrib {
+namespace cuda {
+
+template
+struct AttentionData {
+ T* gemm_buffer = nullptr;
+ const T* bias = nullptr;
+ int* seqlens_k_total = nullptr;
+
+ const T* query = nullptr;
+ const T* key = nullptr;
+ const T* value = nullptr;
+ const int* mask_index = nullptr;
+ gsl::span mask_index_dims;
+ const T* past = nullptr;
+ const T* past_key = nullptr;
+ const T* past_value = nullptr;
+ const int32_t* cache_indirection = nullptr;
+ const T* attention_bias = nullptr;
+
+ bool has_qkv_workspace = false;
+ T* workspace = nullptr;
+
+ T* output = nullptr;
+ T* present = nullptr;
+ T* present_key = nullptr;
+ T* present_value = nullptr;
+ void* output_qk = nullptr;
+
+ void* fused_runner = nullptr;
+ const void* fused_cross_attention_kernel = nullptr;
+
+ bool use_flash_attention = false;
+ bool use_memory_efficient_attention = false;
+ bool use_decoder_masked_multihead_attention = false;
+
+ const int32_t* cumulated_sequence_length_q_cache = nullptr;
+ const int32_t* cumulated_sequence_length_kv_cache = nullptr;
+
+ // Intermediate data
+ T* q = nullptr;
+ T* k = nullptr;
+ T* v = nullptr;
+ T* scratch = nullptr;
+ AttentionQkvFormat qkv_format = AttentionQkvFormat::UNKNOWN;
+
+ // Flash buffers
+ T* softmax_lse = nullptr;
+ T* softmax_lse_accum = nullptr;
+ T* out_accum = nullptr;
+
+ // Flash Atttention and Lean Attention
+ int num_splits;
+
+ // Lean Attention
+ bool use_lean_attention = false;
+#if USE_LEAN_ATTENTION
+ int grid_dim_z = 0;
+ int max_tiles_per_tb = 0;
+ int high_load_tbs = 0;
+ int tiles_per_head = 0;
+ int* lean_sync_flag = nullptr;
+#endif
+
+ // For Debugging
+ size_t workspace_bytes = 0;
+ bool allow_debug_info = false;
+
+ // For MultiHeadAttention only.
+ AttentionKernelType kernel_type = AttentionKernelType::AttentionKernel_Default;
+ AllocatorPtr allocator = nullptr;
+ bool IsUnfused() const {
+ return kernel_type == AttentionKernelType::AttentionKernel_Unfused;
+ }
+
+ // For DecoderMaskedMultiHeadAttention
+ T* q_bias = nullptr;
+ T* k_bias = nullptr;
+ T* v_bias = nullptr;
+
+ void PrintDebugInfo() const {
+ std::cout << "flash=" << use_flash_attention
+ << ", lean=" << use_lean_attention
+ << ", efficient=" << use_memory_efficient_attention
+ << ", fused_runner=" << (fused_runner != nullptr)
+ << ", fused_cross=" << (fused_cross_attention_kernel != nullptr)
+ << ", bias=" << (bias != nullptr)
+ << ", attn_bias=" << (attention_bias != nullptr)
+ << ", mask_dims=" << mask_index_dims.size()
+ << ", has_qkv_workspace=" << has_qkv_workspace
+ << ", workspace=" << workspace_bytes
+ << ", past=" << (past != nullptr ? 1 : (past_key != nullptr ? 2 : 0))
+ << ", present=" << (present != nullptr ? 1 : (present_key != nullptr ? 2 : 0))
+ << std::endl;
+ }
+};
+
+template
+struct PackedAttentionData {
+ T* gemm_buffer;
+ const T* bias;
+ const T* attention_bias;
+ const int32_t* token_offset;
+ const int32_t* cumulative_sequence_length;
+
+ T* workspace;
+ T* output;
+
+ void* fused_runner;
+
+ bool use_memory_efficient_attention;
+};
+
+template
+struct PackedMultiHeadAttentionData {
+ const T* query;
+ const T* key;
+ const T* value;
+ const T* bias;
+ const T* attention_bias;
+
+ const int32_t* token_offset;
+ const int32_t* cumulative_sequence_length;
+
+ AttentionQkvFormat source_qkv_format;
+
+ bool no_qkv_workspace;
+ T* workspace;
+ T* output;
+
+ void* fused_runner;
+
+ bool use_flash_attention;
+ bool use_memory_efficient_attention;
+};
+
+template
+struct GroupQueryAttentionData {
+ // Input Tensors
+ const T* query = nullptr;
+ const T* key = nullptr;
+ const T* value = nullptr;
+ const T* past_key = nullptr;
+ const T* past_value = nullptr;
+ int* seqlens_k = nullptr;
+ const T* cos_cache = nullptr;
+ const T* sin_cache = nullptr;
+
+ // Flash buffers
+ T* softmax_lse = nullptr;
+ T* softmax_lse_accum = nullptr;
+ T* out_accum = nullptr;
+ int* seqlens_k_buff = nullptr;
+
+ // Memory Efficient buffers
+ T* fmha_buffer = nullptr;
+ T* unpacked_qkv_buffer = nullptr;
+ T* rotary_buffer = nullptr;
+ T* k = nullptr;
+ T* v = nullptr;
+
+ // Output Tensors
+ T* output = nullptr;
+ T* present_key = nullptr;
+ T* present_value = nullptr;
+
+ // Kernel Flags
+ bool use_flash_attention = false;
+ bool use_memory_efficient_attention = false;
+};
+
+} // namespace cuda
+} // namespace contrib
+} // namespace onnxruntime
diff --git a/onnxruntime/contrib_ops/cuda/bert/attention_impl.cu b/onnxruntime/contrib_ops/cuda/bert/attention_impl.cu
index 9e017544d7cff..0209183f46425 100644
--- a/onnxruntime/contrib_ops/cuda/bert/attention_impl.cu
+++ b/onnxruntime/contrib_ops/cuda/bert/attention_impl.cu
@@ -29,18 +29,23 @@ limitations under the License.
#include "core/providers/cuda/cu_inc/common.cuh"
#include "core/providers/cuda/cuda_common.h"
#include "core/providers/cuda/shared_inc/fpgeneric.h"
-#include "contrib_ops/cuda/bert/attention_softmax.h"
-#include "contrib_ops/cuda/bert/transformer_common.h"
-#include "contrib_ops/cuda/bert/tensorrt_fused_multihead_attention/mha_runner.h"
-#include "contrib_ops/cuda/bert/tensorrt_fused_multihead_attention/cross_attention/fmha_cross_attention.h"
+#include "core/platform/env_var_utils.h"
#include "contrib_ops/cpu/bert/attention_base.h"
+#include "contrib_ops/cpu/bert/attention_common.h"
+#include "contrib_ops/cpu/bert/attention_parameters.h"
+#include "contrib_ops/cuda/bert/attention_impl.h"
+#include "contrib_ops/cuda/bert/attention_kv_cache.h"
+#include "contrib_ops/cuda/bert/attention_qk.h"
+#include "contrib_ops/cuda/bert/attention_softmax.h"
#include "contrib_ops/cuda/bert/bert_padding.h"
-#include "contrib_ops/cuda/utils/dump_cuda_tensor.h"
#include "contrib_ops/cuda/bert/cutlass_fmha/memory_efficient_attention.h"
#include "contrib_ops/cuda/bert/cudnn_fmha/cudnn_flash_attention.h"
#include "contrib_ops/cuda/bert/flash_attention/flash_api.h"
#include "contrib_ops/cuda/bert/lean_attention/lean_api.h"
-#include "contrib_ops/cuda/bert/attention_impl.h"
+#include "contrib_ops/cuda/bert/tensorrt_fused_multihead_attention/mha_runner.h"
+#include "contrib_ops/cuda/bert/tensorrt_fused_multihead_attention/cross_attention/fmha_cross_attention.h"
+#include "contrib_ops/cuda/bert/transformer_common.h"
+#include "contrib_ops/cuda/utils/dump_cuda_tensor.h"
using namespace onnxruntime::cuda;
using namespace onnxruntime::contrib::attention_softmax_cuda;
@@ -516,7 +521,7 @@ Status EfficientAttention(
p.key = data.k;
p.value = data.v;
- p.attn_bias = (nullptr == data.attention_bias) ? nullptr : data.attention_bias;
+ p.attn_bias = data.attention_bias;
p.broadcast_attn_bias_dim_0 = parameters.broadcast_attn_bias_dim_0;
p.broadcast_attn_bias_dim_1 = parameters.broadcast_attn_bias_dim_1;
@@ -533,7 +538,138 @@ Status EfficientAttention(
}
#endif
-template
+template
+Status LaunchDecoderMaskedMultiHeadAttention(
+ const DecoderMaskedMultiHeadAttentionParameters& parameters,
+ cudaStream_t stream,
+ const int head_size) {
+
+ DUMP_STRING_INIT();
+ DUMP_STRING("DMMHA parameters...");
+ DUMP_STRING("is_mha = ", (parameters.is_mha == true));
+ DUMP_STRING("is_cross_attention = ", (parameters.is_cross_attention == true));
+ DUMP_STRING("is_packed_qkv = ", (parameters.is_packed_qkv == true));
+ DUMP_STRING("kv_data_in_flight = ", (parameters.kv_data_in_flight == true));
+
+ DUMP_STRING("Batch size = ", parameters.batch_size);
+ DUMP_STRING("Sequence length = ", parameters.sequence_length);
+ DUMP_STRING("Num heads = ", parameters.num_heads);
+ DUMP_STRING("Head size = ", parameters.head_size);
+ DUMP_STRING("Hidden size = ", parameters.hidden_size);
+
+ DUMP_STRING("Past sequence length = ", parameters.past_sequence_length);
+ DUMP_STRING("KV sequence length = ", parameters.kv_sequence_length);
+ DUMP_STRING("Total sequence length = ", parameters.total_sequence_length);
+ DUMP_STRING("Max sequence length = ", parameters.max_sequence_length);
+
+ DUMP_STRING("parameters.k is null = ", (parameters.k == nullptr));
+ DUMP_STRING("parameters.v is null = ", (parameters.v == nullptr));
+ DUMP_STRING("parameters.k_cache is null = ", (parameters.k_cache == nullptr));
+ DUMP_STRING("parameters.v_cache is null = ", (parameters.v_cache == nullptr));
+
+ DUMP_STRING("parameters.q_bias is null = ", (parameters.q_bias == nullptr));
+ DUMP_STRING("parameters.k_bias is null = ", (parameters.k_bias == nullptr));
+ DUMP_STRING("parameters.v_bias is null = ", (parameters.v_bias == nullptr));
+
+ DUMP_STRING("parameters.attention_bias is null = ", (parameters.attention_bias == nullptr));
+ DUMP_STRING("Scale = ", parameters.scale);
+ DUMP_STRING("Mask is null = ", (parameters.mask == nullptr));
+ DUMP_STRING("Mask filter value = ", parameters.mask_filter_value);
+
+ DUMP_STRING("Beam width = ", parameters.beam_width);
+ DUMP_STRING("parameters.cache_indir is null = ", (parameters.cache_indir == nullptr));
+ DUMP_STRING("parameters.out_qk is null = ", (parameters.out_qk == nullptr));
+
+ switch (head_size) {
+ case 32:
+ mmha_launch_kernel(parameters, stream);
+ break;
+
+ case 64:
+ mmha_launch_kernel(parameters, stream);
+ break;
+
+ case 128:
+ mmha_launch_kernel(parameters, stream);
+ break;
+
+ default:
+ return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_IMPLEMENTED,
+ "Unsupported head size in DecoderMaskedMultiHeadAttention. Got head size: ",
+ head_size);
+ }
+
+ return Status::OK();
+}
+
+template
+Status DecoderMaskedMultiHeadAttention(
+ cudaStream_t stream,
+ contrib::AttentionParameters& parameters,
+ AttentionData& data,
+ float scale) {
+ assert(data.qkv_format == AttentionQkvFormat::Q_K_V_BSNH ||
+ data.qkv_format == AttentionQkvFormat::Q_K_V_BSNH_BNSH_BNSH);
+ assert(parameters.mask_type == AttentionMaskType::MASK_NONE ||
+ parameters.mask_type == AttentionMaskType::MASK_2D_KEY_PADDING);
+ assert(parameters.head_size == parameters.v_head_size);
+
+ DecoderMaskedMultiHeadAttentionParameters p;
+ p.is_mha = true;
+ p.is_cross_attention = (data.past_key == nullptr && data.present_key == nullptr);
+ p.is_packed_qkv = false;
+ p.kv_data_in_flight = ParseEnvironmentVariableWithDefault(attention::kDecoderMaskedAttentionLoadKVDataInFlight, false);
+
+ p.batch_size = parameters.batch_size;
+ p.sequence_length = parameters.sequence_length;
+ p.num_heads = parameters.num_heads;
+ p.head_size = parameters.head_size;
+ p.hidden_size = parameters.hidden_size;
+
+ p.past_sequence_length = parameters.past_sequence_length;
+ p.kv_sequence_length = parameters.kv_sequence_length;
+ p.total_sequence_length = p.is_cross_attention ? parameters.kv_sequence_length : parameters.total_sequence_length;
+ p.max_sequence_length = p.is_cross_attention ? parameters.kv_sequence_length : parameters.max_sequence_length;
+
+ p.q = data.q;
+ p.k = p.is_cross_attention ? nullptr : data.k;
+ p.v = p.is_cross_attention ? nullptr : data.v;
+ p.k_cache = p.is_cross_attention ? data.k : data.present_key;
+ p.v_cache = p.is_cross_attention ? data.v : data.present_value;
+
+ p.q_bias = data.q_bias;
+ p.k_bias = data.k_bias;
+ p.v_bias = data.v_bias;
+
+ p.attention_bias = const_cast(data.attention_bias);
+ p.broadcast_attn_bias_dim_0 = parameters.broadcast_attn_bias_dim_0;
+ p.broadcast_attn_bias_dim_1 = parameters.broadcast_attn_bias_dim_1;
+
+ p.scale = scale;
+ p.mask = data.mask_index;
+ p.mask_filter_value = parameters.mask_filter_value;
+
+ p.beam_width = parameters.beam_width;
+ p.cache_indir = data.cache_indirection;
+
+ p.out = data.output;
+ p.out_qk = data.output_qk;
+
+ // DecoderMaskedMultiHeadAttention(T, QK) is defined for:
+ // T = float, QK = float
+ // T = float, QK = half
+ // T = uint16_t, QK = float
+ // T = uint16_t, QK = half
+ if (std::is_same::value) {
+ return LaunchDecoderMaskedMultiHeadAttention(p, stream, parameters.head_size);
+ }
+ if (std::is_same::value) {
+ return LaunchDecoderMaskedMultiHeadAttention(p, stream, parameters.head_size);
+ }
+ return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "DecoderMaskedMultiHeadAttention is only implemented for float32 and float16.");
+}
+
+template
Status UnfusedAttention(
const cudaDeviceProp& device_prop,
cublasHandle_t& cublas,
@@ -624,6 +760,15 @@ Status UnfusedAttention(
mask_index, mask_start, data.attention_bias, broadcast_attn_bias_dim_0, broadcast_attn_bias_dim_1,
data.scratch, scratch2, parameters.is_unidirectional));
} else { // no mask
+ if (nullptr != data.output_qk) {
+ int64_t qk_size = (int64_t)batch_size * num_heads * sequence_length * total_sequence_length;
+ if (std::is_same::value) {
+ cudaMemcpyAsync(data.output_qk, data.scratch, qk_size * sizeof(QK), cudaMemcpyDeviceToDevice, stream);
+ } else {
+ ORT_RETURN_IF_ERROR(
+ (CopyQK(stream, static_cast(qk_size), data.scratch, reinterpret_cast(data.output_qk))));
+ }
+ }
ORT_RETURN_IF_ERROR(
ComputeSoftmax(
stream, total_sequence_length, sequence_length, batch_size, num_heads,
@@ -645,10 +790,170 @@ Status UnfusedAttention(
// Temp_output is BxNxSxH_v, transpose to output BxSxNxH_v
Status result = LaunchTransCtx(stream, sequence_length, batch_size, v_head_size, num_heads,
device_prop.maxThreadsPerBlock, false, temp_output, data.output);
+ DUMP_TENSOR_D("Attention Output", data.output, batch_size, sequence_length, num_heads, v_head_size);
return result;
}
+#ifndef USE_ROCM // exclude the following from hipify since they are not used in ROCM EP
+
template
+Status ConcatPastToPresent(int batch_size, int num_heads, int qk_head_size, int v_head_size,
+ int sequence_length, int total_sequence_length,
+ cudaStream_t stream, int max_threads_per_block,
+ AttentionData& data) {
+ // Concat past key value to present (2xBxNxLxH), where L is kv_sequence_length and T is total_sequence_length.
+ // past_k (BxNxPxH) + k (BxNxLxH) => present_k (BxNxTxH)
+ // past_v (BxNxPxH) + v (BxNxLxH) => present_v (BxNxTxH)
+ // When there is past state, the head size for Q/K/V shall be same: H == H_v.
+
+ if (nullptr != data.present) { // Attention op
+ assert(data.qkv_format == AttentionQkvFormat::Q_K_V_BNSH ||
+ data.qkv_format == AttentionQkvFormat::Q_K_V_BNSH_QKV_BS3NH);
+
+ ORT_RETURN_IF_ERROR(
+ LaunchConcatTensorToTensor(
+ stream, total_sequence_length, sequence_length, batch_size, qk_head_size, num_heads,
+ max_threads_per_block, 2, data.past, data.k, data.present));
+
+
+
+ // Update pointers to present_k and present_v.
+ data.k = data.present;
+ data.v = data.present + batch_size * num_heads * total_sequence_length * qk_head_size;
+ } else { // MultiHeadAttention op
+ if (nullptr != data.present_key) {
+ ORT_ENFORCE(data.qkv_format == AttentionQkvFormat::Q_K_V_BNSH ||
+ data.qkv_format == AttentionQkvFormat::Q_K_V_BSNH_BNSH_BNSH);
+ if (nullptr != data.past_key) {
+ assert(data.past_key != data.k);
+ assert(data.past_value != data.v);
+
+ ORT_RETURN_IF_ERROR(
+ LaunchConcatTensorToTensor(stream, total_sequence_length, sequence_length,
+ batch_size, qk_head_size, num_heads,
+ max_threads_per_block, 1, data.past_key, data.k, data.present_key));
+ ORT_RETURN_IF_ERROR(
+ LaunchConcatTensorToTensor(stream, total_sequence_length, sequence_length,
+ batch_size, v_head_size, num_heads,
+ max_threads_per_block, 1, data.past_value, data.v, data.present_value));
+ // Update pointers to present_k and present_v.
+ data.k = data.present_key;
+ data.v = data.present_value;
+ } else { // nullptr == data.past_key && nullptr != data.present_key
+ if (data.k != data.present_key) {
+ int64_t k_size = (int64_t)batch_size * num_heads * total_sequence_length * qk_head_size;
+ cudaMemcpyAsync(data.present_key, data.k, k_size * sizeof(T), cudaMemcpyDeviceToDevice, stream);
+ }
+
+ if (data.v != data.present_value) {
+ int64_t v_size = (int64_t)batch_size * num_heads * total_sequence_length * v_head_size;
+ cudaMemcpyAsync(data.present_value, data.v, v_size * sizeof(T), cudaMemcpyDeviceToDevice, stream);
+ }
+ }
+ }
+ }
+
+ return CUDA_CALL(cudaGetLastError());
+}
+
+// Template Instantiation
+template Status ConcatPastToPresent(int batch_size, int num_heads, int qk_head_size, int v_head_size,
+ int sequence_length, int total_sequence_length,
+ cudaStream_t stream,
+ int max_threads_per_block,
+ AttentionData& data);
+
+template Status ConcatPastToPresent(int batch_size, int num_heads, int qk_head_size, int v_head_size,
+ int sequence_length, int total_sequence_length,
+ cudaStream_t stream,
+ int max_threads_per_block,
+ AttentionData& data);
+#endif
+
+template
+Status PastPresentBufferShare(int batch_size, int num_heads, int qk_head_size, int v_head_size,
+ int sequence_length, void* fused_runner,
+ contrib::AttentionParameters& parameters,
+ AttentionData& data,
+ cudaStream_t stream,
+ int max_threads_per_block) {
+ ORT_ENFORCE(qk_head_size == v_head_size);
+ assert(data.fused_cross_attention_kernel == nullptr);
+ assert(nullptr == fused_runner || parameters.is_unidirectional);
+ assert(!data.use_memory_efficient_attention);
+ assert(!data.use_flash_attention);
+ assert(data.has_qkv_workspace);
+
+ bool combined_key_value = nullptr != data.present;
+ bool separate_key_value = nullptr != data.past_key && nullptr != data.present_key &&
+ nullptr != data.past_value && nullptr != data.present_value;
+
+ // Return early if buffer sharing is not possible
+ if (!combined_key_value && !separate_key_value) {
+ return Status::OK();
+ }
+
+ if (combined_key_value) { // Attention op
+ assert(data.gemm_buffer != nullptr);
+
+ if (data.present != data.past) {
+ // For easy testing. Production should better avoid this path.
+ int64_t kv_size = 2LL * (int64_t)batch_size * num_heads * parameters.max_sequence_length * qk_head_size;
+ cudaMemcpyAsync(data.present, data.past, kv_size * sizeof(T), cudaMemcpyDeviceToDevice, stream);
+ }
+
+ // For fused causal, bias has been added to gemm_buffer.
+ const T* bias = (nullptr != fused_runner && parameters.is_unidirectional) ? nullptr : data.bias;
+
+ // append last k v to present
+ ORT_RETURN_IF_ERROR(LaunchAddBiasTransAppendKvToPresent(
+ stream, parameters.max_sequence_length, parameters.past_sequence_length, sequence_length,
+ batch_size, qk_head_size, num_heads, max_threads_per_block,
+ bias, data.gemm_buffer, data.present));
+
+ data.k = data.present;
+ data.v = data.present + batch_size * num_heads * parameters.max_sequence_length * qk_head_size;
+ } else if (data.use_decoder_masked_multihead_attention) { // DecoderMaskedMultiHeadAttention op
+ assert(data.qkv_format == AttentionQkvFormat::Q_K_V_BSNH ||
+ data.qkv_format == AttentionQkvFormat::Q_K_V_BSNH_BNSH_BNSH);
+
+ // DecoderMaskedMultiHeadAttention kernel manages the KV caches
+ // so this case is empty
+ } else { // MultiHeadAttention op
+ assert(data.qkv_format == AttentionQkvFormat::Q_K_V_BNSH ||
+ data.qkv_format == AttentionQkvFormat::Q_K_V_BSNH_BNSH_BNSH);
+ assert(data.seqlens_k_total);
+
+ // Using BNSH since AddBiasTranspose has already been applied
+ constexpr bool is_past_kv_bnsh_format = true;
+ constexpr bool is_new_kv_bnsh_format = true;
+ ORT_RETURN_IF_ERROR(LaunchConcatKVInPlace(
+ batch_size, num_heads, qk_head_size, parameters.max_sequence_length,
+ data.seqlens_k_total, nullptr, parameters.sequence_length, data.k, data.v, data.present_key, data.present_value,
+ is_past_kv_bnsh_format, is_new_kv_bnsh_format, stream, max_threads_per_block));
+
+ data.k = data.present_key;
+ data.v = data.present_value;
+ }
+
+ return CUDA_CALL(cudaGetLastError());
+}
+
+template Status PastPresentBufferShare(int batch_size, int num_heads, int qk_head_size, int v_head_size,
+ int sequence_length, void* fused_runner,
+ contrib::AttentionParameters& parameters,
+ AttentionData& data,
+ cudaStream_t stream,
+ int max_threads_per_block);
+
+template Status PastPresentBufferShare(int batch_size, int num_heads, int qk_head_size, int v_head_size,
+ int sequence_length, void* fused_runner,
+ contrib::AttentionParameters& parameters,
+ AttentionData& data,
+ cudaStream_t stream,
+ int max_threads_per_block);
+
+template
Status QkvToContext(
const cudaDeviceProp& device_prop,
cublasHandle_t& cublas,
@@ -669,58 +974,36 @@ Status QkvToContext(
// At most one fused kernel is enabled.
assert((static_cast(data.use_flash_attention) +
static_cast(data.use_memory_efficient_attention) +
+ static_cast(data.use_decoder_masked_multihead_attention) +
static_cast(fused_runner != nullptr) +
static_cast(data.fused_cross_attention_kernel != nullptr) +
static_cast(data.kernel_type == AttentionKernelType::AttentionKernel_CudnnFlashAttention)) <= 1);
+ DUMP_STRING_INIT();
+ DUMP_STRING("Preparing Q, K, V");
ORT_RETURN_IF_ERROR(PrepareQkv(parameters, data, stream, max_threads_per_block));
if (!parameters.past_present_share_buffer) {
- ORT_RETURN_IF_ERROR(ConcatPastToPresent(batch_size, num_heads, qk_head_size, v_head_size,
+ ORT_RETURN_IF_ERROR(ConcatPastToPresent(batch_size, num_heads, qk_head_size, v_head_size,
sequence_length, total_sequence_length,
stream, max_threads_per_block, data));
} else { // past_present_share_buffer
- assert(qk_head_size == v_head_size);
- assert(data.fused_cross_attention_kernel == nullptr);
- assert(nullptr == fused_runner || parameters.is_unidirectional);
- assert(data.gemm_buffer != nullptr);
- assert(!data.use_memory_efficient_attention);
- assert(!data.use_flash_attention);
- assert(data.has_qkv_workspace);
-
- if (nullptr != data.past_key || nullptr != data.present_key) {
- // TODO: support this case.
- ORT_THROW("buffer sharing for no bias case between past and present is not supported yet.");
- }
-
- if (data.present != data.past) {
- // For easy testing. Production should better avoid this path.
- int64_t kv_size = 2LL * (int64_t)batch_size * num_heads * parameters.max_sequence_length * qk_head_size;
- cudaMemcpyAsync(data.present, data.past, kv_size * sizeof(T), cudaMemcpyDeviceToDevice, stream);
- }
-
- // For fused causal, bias has been added to gemm_buffer.
- const T* bias = (nullptr != fused_runner && parameters.is_unidirectional) ? nullptr : data.bias;
-
- // append last k v to present
- ORT_RETURN_IF_ERROR(LaunchAddBiasTransAppendKvToPresent(
- stream, parameters.max_sequence_length, parameters.past_sequence_length, sequence_length,
- batch_size, qk_head_size, num_heads, max_threads_per_block,
- bias, data.gemm_buffer, data.present));
-
- data.k = data.present;
- data.v = data.present + batch_size * num_heads * parameters.max_sequence_length * qk_head_size;
+ ORT_RETURN_IF_ERROR(PastPresentBufferShare(batch_size, num_heads, qk_head_size, v_head_size,
+ sequence_length, fused_runner,
+ parameters, data, stream, max_threads_per_block));
}
// Q, K and V are ready now
if (data.fused_cross_attention_kernel != nullptr) {
- return FusedTrtCrossAttention(stream, parameters, data);
+ DUMP_STRING("FusedTrtCrossAttention");
+ return FusedTrtCrossAttention(stream, parameters, data);
}
// Run TRT fused attention.
if (nullptr != fused_runner) {
- return FusedTrtSelfAttention(stream, parameters, data);
+ DUMP_STRING("FusedTrtSelfAttention");
+ return FusedTrtSelfAttention(stream, parameters, data);
}
// For raw attention mask, the scalar 1/sqrt(H) is moved to combine with softmax computation.
@@ -728,27 +1011,37 @@ Status QkvToContext(
: parameters.scale;
#if USE_LEAN_ATTENTION
if (data.use_lean_attention) {
- return LeanAttention(device_prop, stream, parameters, data, scale);
+ DUMP_STRING("LeanAttention");
+ return LeanAttention