Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 142 additions & 39 deletions onnxruntime/contrib_ops/webgpu/bert/flash_attention.cc

Large diffs are not rendered by default.

30 changes: 18 additions & 12 deletions onnxruntime/contrib_ops/webgpu/bert/flash_attention.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,24 @@

class CopyKVCacheProgram final : public Program<CopyKVCacheProgram> {
public:
CopyKVCacheProgram(const std::string& kernel_name, bool has_past, bool kv_BNSH, bool past_present_share_buffer)
: Program{kernel_name}, has_past_(has_past), kv_BNSH_(kv_BNSH), past_present_share_buffer_(past_present_share_buffer) {
CopyKVCacheProgram(const std::string& kernel_name, bool has_past, bool kv_BNSH, bool past_present_share_buffer,
bool prepare_indirect_dispatch = false)
: Program{kernel_name}, has_past_(has_past), kv_BNSH_(kv_BNSH), past_present_share_buffer_(past_present_share_buffer), prepare_indirect_dispatch_(prepare_indirect_dispatch) {
}

Status GenerateShaderCode(ShaderHelper& sh) const override;

WEBGPU_PROGRAM_DEFINE_UNIFORM_VARIABLES({"copy_size", ProgramUniformVariableDataType::Uint32},
{"past_sequence_length", ProgramUniformVariableDataType::Uint32});
{"total_sequence_length", ProgramUniformVariableDataType::Uint32},
{"kv_sequence_length", ProgramUniformVariableDataType::Uint32},
{"tile_size", ProgramUniformVariableDataType::Uint32},
{"num_heads", ProgramUniformVariableDataType::Uint32});

private:
bool has_past_;
bool kv_BNSH_;
bool past_present_share_buffer_;
bool prepare_indirect_dispatch_;
};

class FlashAttentionProgram final : public Program<FlashAttentionProgram> {
Expand Down Expand Up @@ -75,8 +80,8 @@
class FlashAttentionDecodeQKTProgram final : public Program<FlashAttentionDecodeQKTProgram> {
public:
FlashAttentionDecodeQKTProgram(const std::string& kernel_name,
bool has_attention_bias, uint32_t tile_size)
: Program{kernel_name}, has_attention_bias_(has_attention_bias), tile_size_(tile_size) {
bool has_attention_bias, uint32_t tile_size, bool use_indirect_dispatch)
: Program{kernel_name}, has_attention_bias_(has_attention_bias), tile_size_(tile_size), use_indirect_dispatch_(use_indirect_dispatch) {
}

Status GenerateShaderCode(ShaderHelper& sh) const override;
Expand All @@ -86,19 +91,19 @@
{"alpha", ProgramUniformVariableDataType::Float32},
{"present_sequence_length", ProgramUniformVariableDataType::Uint32},
{"n_reps", ProgramUniformVariableDataType::Uint32},
{"num_total_seq_length_tile", ProgramUniformVariableDataType::Uint32},
{"num_present_sequence_length_tile", ProgramUniformVariableDataType::Uint32},
{"num_heads", ProgramUniformVariableDataType::Uint32});

private:
bool has_attention_bias_;
uint32_t tile_size_;
bool use_indirect_dispatch_;
};

class FlashAttentionDecodeSplitVxProgram final : public Program<FlashAttentionDecodeSplitVxProgram> {
public:
FlashAttentionDecodeSplitVxProgram(const std::string& kernel_name, uint32_t tile_size, int head_size_vec)
: Program{kernel_name}, tile_size_(tile_size), head_size_vec_(head_size_vec) {
FlashAttentionDecodeSplitVxProgram(const std::string& kernel_name, uint32_t tile_size, int head_size_vec, bool use_indirect_dispatch)
: Program{kernel_name}, tile_size_(tile_size), head_size_vec_(head_size_vec), use_indirect_dispatch_(use_indirect_dispatch) {
}

Status GenerateShaderCode(ShaderHelper& sh) const override;
Expand All @@ -107,19 +112,19 @@
{"head_size_vec", ProgramUniformVariableDataType::Uint32},
{"present_sequence_length", ProgramUniformVariableDataType::Uint32},
{"n_reps", ProgramUniformVariableDataType::Uint32},
{"num_total_seq_length_tile", ProgramUniformVariableDataType::Uint32},
{"num_present_sequence_length_tile", ProgramUniformVariableDataType::Uint32},
{"num_heads", ProgramUniformVariableDataType::Uint32});

private:
uint32_t tile_size_;
int head_size_vec_;
bool use_indirect_dispatch_;
};

class FlashAttentionDecodeVxReduceProgram final : public Program<FlashAttentionDecodeVxReduceProgram> {
public:
FlashAttentionDecodeVxReduceProgram(const std::string& kernel_name, uint32_t tile_size)
: Program{kernel_name}, tile_size_(tile_size) {
FlashAttentionDecodeVxReduceProgram(const std::string& kernel_name, uint32_t tile_size, bool use_indirect_dispatch)

Check warning on line 126 in onnxruntime/contrib_ops/webgpu/bert/flash_attention.h

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <string> for string [build/include_what_you_use] [4] Raw Output: onnxruntime/contrib_ops/webgpu/bert/flash_attention.h:126: Add #include <string> for string [build/include_what_you_use] [4]
: Program{kernel_name}, tile_size_(tile_size), use_indirect_dispatch_(use_indirect_dispatch) {
}

Status GenerateShaderCode(ShaderHelper& sh) const override;
Expand All @@ -132,11 +137,12 @@

private:
uint32_t tile_size_;
bool use_indirect_dispatch_;
};

Status ApplyFlashAttention(const Tensor* Q, const Tensor* K, const Tensor* V, const Tensor* attention_bias,
Tensor* output, const Tensor* past_key, Tensor* present_key, const Tensor* past_value, Tensor* present_value,
const WebgpuAttentionParameters& parameters, onnxruntime::webgpu::ComputeContext& context);
const WebgpuAttentionParameters& parameters, onnxruntime::webgpu::ComputeContext& context, const Tensor* seqlen_k = nullptr);

bool CanApplyFlashAttention(const Tensor* bias, const Tensor* present_key, const Tensor* present_value,
const WebgpuAttentionParameters& parameters, onnxruntime::webgpu::ComputeContext& context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#param tile_size
#param tile_size_k_vec
#param sub_tile_count
#param use_indirect_dispatch

// Note that this shader adopts similar algorithm with dp4a generation shader.
//
Expand Down Expand Up @@ -48,10 +49,15 @@ var<workgroup> tile_qk: array<q_element_t, tile_size>;
$MAIN {
let local_row = u32(local_idx / tile_size_k_vec);
let local_col = local_idx % tile_size_k_vec;
let total_seq_offset = (workgroup_idx % uniforms.num_total_seq_length_tile) * tile_size;
let head_idx = u32(workgroup_idx / uniforms.num_total_seq_length_tile);
#if use_indirect_dispatch
let total_sequence_length = u32(seqlens_k[0]) + 1u;
#else
let total_sequence_length = uniforms.total_sequence_length;
#endif
let num_total_seq_length_tile = (total_sequence_length + tile_size - 1) / tile_size;
let total_seq_offset = (workgroup_idx % num_total_seq_length_tile) * tile_size;
let head_idx = u32(workgroup_idx / num_total_seq_length_tile);
let q_offset = head_idx * uniforms.head_size_vec;
var total_sequence_length = uniforms.total_sequence_length;
let present_offset = u32(head_idx / uniforms.n_reps) * uniforms.present_sequence_length * uniforms.head_size_vec;
for (var k: u32 = 0u; k < uniforms.head_size_vec; k += tile_size_k_vec) {
if (local_idx < tile_size_k_vec && k + local_idx < uniforms.head_size_vec) {
Expand Down Expand Up @@ -95,7 +101,7 @@ $MAIN {
for (var i = 0u; i < tile_size && (total_seq_offset + i) < total_sequence_length; i++) {
l_sum += exp(f32(tile_qk[i]) - l_max);
}
let meta_offset = head_idx * uniforms.num_present_sequence_length_tile + workgroup_idx % uniforms.num_total_seq_length_tile;
let meta_offset = head_idx * uniforms.num_present_sequence_length_tile + workgroup_idx % num_total_seq_length_tile;
metadata[meta_offset] = metadata_value_t(l_max, l_sum);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#param head_size_vec
#param tile_size_k_vec
#param sub_tile_count
#param use_indirect_dispatch

// Note that this shader adopts similar algorithm with dp4a generation shader.
//
Expand Down Expand Up @@ -40,22 +41,27 @@ var<workgroup> qkv_values: array<array<present_value_value_t, tile_size_k_vec>,
$MAIN {
let local_row = u32(local_idx / tile_size_k_vec);
let local_col = local_idx % tile_size_k_vec;
let total_seq_offset = (workgroup_idx % uniforms.num_total_seq_length_tile) * tile_size;
let head_idx = u32(workgroup_idx / uniforms.num_total_seq_length_tile);
var total_sequence_length = uniforms.total_sequence_length;
#if use_indirect_dispatch
let total_sequence_length = u32(seqlens_k[0]) + 1u;
#else
let total_sequence_length = uniforms.total_sequence_length;
#endif
let num_total_seq_length_tile = (total_sequence_length + tile_size - 1) / tile_size;
let total_seq_offset = (workgroup_idx % num_total_seq_length_tile) * tile_size;
let head_idx = u32(workgroup_idx / num_total_seq_length_tile);
let present_offset = u32(head_idx / uniforms.n_reps) * head_size_vec * uniforms.present_sequence_length;

// Calculate the global max and sum in qk.
if (head_idx < uniforms.num_heads)
{
var g_max = f32(-3.402823e+38f);
var g_sum = f32(0);
for (var i = 0u; i < uniforms.num_total_seq_length_tile; i++)
for (var i = 0u; i < num_total_seq_length_tile; i++)
{
let meta_offset = head_idx * uniforms.num_present_sequence_length_tile + i;
g_max = max(g_max, metadata[meta_offset].x);
}
for (var i = 0u; i < uniforms.num_total_seq_length_tile; i++)
for (var i = 0u; i < num_total_seq_length_tile; i++)
{
let meta_offset = head_idx * uniforms.num_present_sequence_length_tile + i;
let m_value = metadata[meta_offset];
Expand Down Expand Up @@ -95,7 +101,7 @@ $MAIN {
}

for (var i = local_idx; i < head_size_vec; i += workgroup_size_x) {
let out_offset = head_idx * uniforms.num_present_sequence_length_tile * head_size_vec + (workgroup_idx % uniforms.num_total_seq_length_tile) * head_size_vec + i;
let out_offset = head_idx * uniforms.num_present_sequence_length_tile * head_size_vec + (workgroup_idx % num_total_seq_length_tile) * head_size_vec + i;
out_split_vx[out_offset] = tile_output[i];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

#param tile_size
#param use_indirect_dispatch

// Inputs are splits of the GQA output, split into num_total_seq_length_tiles
// rows. This shader needs to add these splits across the row dimension to
Expand All @@ -23,10 +24,16 @@ $MAIN {
var value = output_value_t(0);
let local_row = u32(local_idx / tile_size);
let local_col = local_idx % tile_size;
#if use_indirect_dispatch
let total_sequence_length = u32(seqlens_k[0]) + 1u;
let num_total_seq_length_tile = (total_sequence_length + 63u) / 64u;
#else
let num_total_seq_length_tile = uniforms.num_total_seq_length_tile;
#endif

if (head_size_offset + local_col < uniforms.head_size_vec) {
for (var r = 0u; r < uniforms.num_total_seq_length_tile; r += tile_size) {
if (r + local_row < uniforms.num_total_seq_length_tile) {
for (var r = 0u; r < num_total_seq_length_tile; r += tile_size) {
if (r + local_row < num_total_seq_length_tile) {
value += input[in_offset + (r + local_row) * uniforms.head_size_vec + head_size_offset + local_col];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Status GroupQueryAttention::ComputeInternal(onnxruntime::webgpu::ComputeContext&
!use_sliding_window &&
CanApplyFlashAttention(attention_bias, present_key, present_value, parameters, context)) {
return ApplyFlashAttention(query, key, value, attention_bias, output, past_key, present_key, past_value,
present_value, parameters, context);
present_value, parameters, context, seqlen_k);
}

Tensor qSplit;
Expand Down
2 changes: 1 addition & 1 deletion onnxruntime/core/providers/webgpu/allocator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void* GpuBufferAllocator::Alloc(size_t size) {
stats_.num_allocs++;

wgpu::BufferUsage usage = mapped_at_creation_ ? wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapWrite
: wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
: wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Indirect;

return buffer_manager_.Create(size, usage);
}
Expand Down
5 changes: 4 additions & 1 deletion onnxruntime/core/providers/webgpu/compute_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <utility>

#include "core/framework/execution_provider.h"
#include "core/providers/webgpu/webgpu_execution_provider.h"

#include "core/providers/webgpu/program.h"
#include "core/providers/webgpu/webgpu_context.h"
Expand All @@ -16,7 +17,6 @@
namespace onnxruntime {

class Tensor;
class WebGpuExecutionProvider;

namespace webgpu {

Expand All @@ -42,6 +42,9 @@ class ComputeContext {
inline bool HasFeature(wgpu::FeatureName feature) const {
return webgpu_context_.DeviceHasFeature(feature);
}
inline bool IsGraphCaptureEnabled() const {
return ep_.IsGraphCaptureEnabled();
}
#if !defined(__wasm__)
inline const wgpu::AdapterPropertiesSubgroupMatrixConfigs& SubgroupMatrixConfigs() const {
return webgpu_context_.SubgroupMatrixConfigs();
Expand Down
50 changes: 42 additions & 8 deletions onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ Status BinaryElementwiseProgram::GenerateShaderCode(ShaderHelper& shader) const

shader.MainFunctionBody() << shader.GuardAgainstOutOfBoundsWorkgroupSizes("uniforms.vec_size");


Comment thread
qjia7 marked this conversation as resolved.
Outdated
if (c.NumComponents() == 1) {
shader.MainFunctionBody() << "let outputIndices = " << c.OffsetToIndices("global_idx") << ";\n"
<< "let offset_a = " << a.BroadcastedIndicesToOffset("outputIndices", c) << ";\n"
<< "let offset_b = " << b.BroadcastedIndicesToOffset("outputIndices", c) << ";\n"
<< "let a = " << a.GetByOffset("offset_a") << ";\n"
<< "let b = " << b.GetByOffset("offset_b") << ";\n"
<< c.SetByOffset("global_idx", expression_);
return Status::OK();
}

// check whether can use element-wise mode.
// If either A or B is scalar, or A and B have the same shape, element-wise mode can be used.
// In element-wise mode, no indices calculation is needed.
Expand Down Expand Up @@ -133,10 +144,38 @@ Status BinaryElementwise::ComputeInternal(ComputeContext& context) const {
return Status::OK();
}

std::string additional_impl;
if (get_additional_impl_) {
additional_impl = get_additional_impl_(lhs_tensor->GetElementType(), rhs_tensor->GetElementType());
}

bool is_broadcast = lhs_shape != rhs_shape;
bool is_lhs_scalar = lhs_shape.IsScalar();
bool is_rhs_scalar = rhs_shape.IsScalar();


Comment thread
qjia7 marked this conversation as resolved.
Outdated
if (output_tensor->DataType() == DataTypeImpl::GetType<int64_t>()) {
BinaryElementwiseProgram program{kernel_name_,
expression_,
additional_impl,
is_broadcast,
is_lhs_scalar,
is_rhs_scalar,
false,
false,
false};
program
.SetDispatchGroupSize((size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE)
.AddUniformVariables({
{static_cast<uint32_t>(size)},
})
.AddOutput({output_tensor, ProgramTensorMetadataDependency::TypeAndRank});
program
.AddInputs({{lhs_tensor, ProgramTensorMetadataDependency::TypeAndRank},
{rhs_tensor, ProgramTensorMetadataDependency::TypeAndRank}});
return context.RunProgram(program);
}

// Check if either input is boolean
// For boolean inputs, we need to handle them differently in the shader. This is because `bool` is not a valid type in
// storage buffer. We have to use a `u32` to represent 4 boolean values.
Expand Down Expand Up @@ -175,11 +214,6 @@ Status BinaryElementwise::ComputeInternal(ComputeContext& context) const {

uint32_t vec_size = onnxruntime::narrow<uint32_t>((size + 3) / 4);

std::string additional_impl;
if (get_additional_impl_) {
additional_impl = get_additional_impl_(lhs_tensor->GetElementType(), rhs_tensor->GetElementType());
}

BinaryElementwiseProgram program{kernel_name_,
expression_,
additional_impl,
Expand Down Expand Up @@ -312,9 +346,9 @@ WEBGPU_BINARY_VERSIONED_KERNEL(Mul, 13, 13, Mul, WebGpuSupportedNumberTypes())
WEBGPU_BINARY_KERNEL(Mul, 14, Mul, WebGpuSupportedNumberTypes())

WEBGPU_BINARY_IMPL(Sub, "a - b")
WEBGPU_BINARY_VERSIONED_KERNEL(Sub, 7, 12, Sub, WebGpuSupportedNumberTypes())
WEBGPU_BINARY_VERSIONED_KERNEL(Sub, 13, 13, Sub, WebGpuSupportedNumberTypes())
WEBGPU_BINARY_KERNEL(Sub, 14, Sub, WebGpuSupportedNumberTypes())
WEBGPU_BINARY_VERSIONED_KERNEL(Sub, 7, 12, Sub, WebGpuSupportedNumberTypesExtended())
WEBGPU_BINARY_VERSIONED_KERNEL(Sub, 13, 13, Sub, WebGpuSupportedNumberTypesExtended())
WEBGPU_BINARY_KERNEL(Sub, 14, Sub, WebGpuSupportedNumberTypesExtended())

std::string GetPowImpl(int lhs_element_type, int /* rhs_element_type */) {
SS(s, 1024);
Expand Down
6 changes: 6 additions & 0 deletions onnxruntime/core/providers/webgpu/program.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ ProgramBase::ProgramBase(std::string_view name, ProgramMetadata&& metadata)
dispatch_group_size_x_{0},
dispatch_group_size_y_{0},
dispatch_group_size_z_{0},
indirect_dispatch_tensor_{nullptr},
workgroup_size_x_{0},
workgroup_size_y_{0},
workgroup_size_z_{0} {
Expand Down Expand Up @@ -359,6 +360,11 @@ ProgramBase& ProgramBase::SetDispatchGroupSize(uint32_t x, uint32_t y, uint32_t
return *this;
}

ProgramBase& ProgramBase::SetIndirectDispatchTensor(const Tensor* indirect_dispatch_tensor) {
indirect_dispatch_tensor_ = indirect_dispatch_tensor;
return *this;
}

ProgramBase& ProgramBase::SetWorkgroupSize(uint32_t x) {
return SetWorkgroupSize(x, 1, 1);
}
Expand Down
7 changes: 7 additions & 0 deletions onnxruntime/core/providers/webgpu/program.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ class ProgramBase {
// set the size of dispatch groups.
ProgramBase& SetDispatchGroupSize(uint32_t x, uint32_t y, uint32_t z);

// set indirect dispatch tensor for indirect dispatch
ProgramBase& SetIndirectDispatchTensor(const Tensor* indirect_dispatch_tensor);

// set the size of a workgroup grid. Y and Z are 1 if not specified.
ProgramBase& SetWorkgroupSize(uint32_t x);
// set the size of a workgroup grid. Z is 1 if not specified.
Expand Down Expand Up @@ -348,6 +351,8 @@ class ProgramBase {
inline uint32_t DispatchGroupSizeX() const { return dispatch_group_size_x_; }
inline uint32_t DispatchGroupSizeY() const { return dispatch_group_size_y_; }
inline uint32_t DispatchGroupSizeZ() const { return dispatch_group_size_z_; }
inline const Tensor* IndirectDispatchTensor() const { return indirect_dispatch_tensor_; }
inline bool UseIndirectDispatch() const { return indirect_dispatch_tensor_ != nullptr; }
inline uint32_t WorkgroupSizeX() const { return workgroup_size_x_; }
inline uint32_t WorkgroupSizeY() const { return workgroup_size_y_; }
inline uint32_t WorkgroupSizeZ() const { return workgroup_size_z_; }
Expand All @@ -374,6 +379,8 @@ class ProgramBase {
uint32_t dispatch_group_size_y_;
uint32_t dispatch_group_size_z_;

const Tensor* indirect_dispatch_tensor_;

uint32_t workgroup_size_x_;
uint32_t workgroup_size_y_;
uint32_t workgroup_size_z_;
Expand Down
Loading
Loading