Skip to content

Commit e9cb41b

Browse files
committed
[ExecuTorch][WebGPU] Route exact prefill shapes to the BK64 GEMM
Pull Request resolved: #21130 Llama prefill drives the ordinary quantized-linear projections at a small set of fixed batch-row counts, and the generic Steel schedule leaves throughput on the table for them. This adds a frozen BK64 Steel quantized-GEMM route for the exact accepted Llama ordinary-projection shapes at live M128, M508, and M512, selected only when the capability and dynamic-route guards all pass. M511 and other prefill sizes stay on the generic Steel schedule and M1 stays on bicol decode, so the route fails closed outside its accepted shapes. Mirrors Vulkan xplat/executorch/backends/vulkan/runtime/graph/ops/glsl/q4gsw_linear_gemm__w_4x8.glsl (the 4-bit grouped-symmetric-weight GEMM), specialized here for the BK64 tile. Key changes: - runtime/ops/quantized_linear/q4gsw_steel_bk64.wgsl (+ generated header): the BK64-tiled q4gsw prefill GEMM kernel. - QuantizedLinear.cpp, WebGPUUtils.h: exact-shape predicate, capability guard, and dynamic re-entry into and out of the BK64 route. ghstack-source-id: 411961445 @exported-using-ghexport Differential Revision: [D113171739](https://our.internmc.facebook.com/intern/diff/D113171739/)
1 parent c09b197 commit e9cb41b

15 files changed

Lines changed: 1064 additions & 169 deletions

backends/webgpu/runtime/WebGPUDispatchMath.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,28 @@ constexpr bool should_record_sdpa_dual_route(
100100
return fd_eligible && has_dynamic_sequence;
101101
}
102102

103+
constexpr bool is_q4gsw_bk64_eligible(
104+
uint32_t k,
105+
uint32_t n,
106+
uint32_t group_size,
107+
bool has_bias,
108+
bool shader_f16_supported,
109+
uint32_t max_invocations,
110+
uint32_t max_workgroup_storage_bytes) {
111+
constexpr uint32_t kRequiredInvocations = 256u;
112+
constexpr uint32_t kRequiredStorageBytes = 2u * 64u * 64u * sizeof(uint16_t);
113+
const bool ordinary_llama_projection = (k == 2048u && n == 8192u) ||
114+
(k == 8192u && n == 2048u) || (k == 2048u && n == 2048u);
115+
return ordinary_llama_projection && k % 64u == 0u && group_size == 64u &&
116+
!has_bias && shader_f16_supported &&
117+
max_invocations >= kRequiredInvocations &&
118+
max_workgroup_storage_bytes >= kRequiredStorageBytes;
119+
}
120+
121+
constexpr bool is_q4gsw_bk64_live_m(uint32_t m) {
122+
return m == 128u || m == 508u || m == 512u;
123+
}
124+
103125
class DispatchRouteRegistry {
104126
public:
105127
template <typename IsCompute>

backends/webgpu/runtime/WebGPUGraph.cpp

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,24 +1325,29 @@ void WebGPUGraph::build(
13251325
auto mit = qkv_member.find(i);
13261326
if (mit != qkv_member.end()) {
13271327
QkvFusionGroup& g = qkv_groups[mit->second];
1328-
const size_t di = num_dispatches() - 1; // this linear's dispatch
1328+
const utils::DispatchRange dispatch_range = {
1329+
dispatch_begin, num_dispatches()};
1330+
if (dispatch_range.begin == dispatch_range.end) {
1331+
throw std::runtime_error(
1332+
"WebGPU QKV fusion member emitted no dispatch");
1333+
}
13291334
if (i == g.op_idx[0]) {
1330-
g.sep_dispatch[0] = di;
1335+
g.sep_dispatch[0] = dispatch_range;
13311336
// Emit the fused dispatch RIGHT AFTER the q-linear (the anchor) so
13321337
// at M>1 it writes q/k/v BEFORE any consumer. q/k/v may be
13331338
// interleaved with rope in the chain, so emitting it at the LAST
13341339
// triple op would let a consumer (rope-q) read still-unwritten
13351340
// fresh_q -> garbage.
13361341
add_qkv_fused_dispatch(g);
13371342
} else if (i == g.op_idx[1]) {
1338-
g.sep_dispatch[1] = di;
1343+
g.sep_dispatch[1] = dispatch_range;
13391344
} else {
1340-
g.sep_dispatch[2] = di;
1345+
g.sep_dispatch[2] = dispatch_range;
13411346
}
13421347
}
13431348
auto lit = qkv_last.find(i);
13441349
if (lit != qkv_last.end()) {
1345-
// All 3 sep dispatch indices + the fused index are now known.
1350+
// All 3 separate route ranges + the fused index are now known.
13461351
add_qkv_fused_hook(qkv_groups[lit->second]);
13471352
}
13481353
}
@@ -1670,8 +1675,9 @@ void WebGPUGraph::add_qkv_fused_dispatch(QkvFusionGroup& g) {
16701675
g.fused_params = uniform_buffer;
16711676
}
16721677

1673-
// M-gate coordinator: registered at the LAST triple op (all dispatch indices
1674-
// known). Prefill (M>1): run the fused GEMM, zero the 3 separate linears.
1678+
// M-gate coordinator: registered at the LAST triple op (all dispatch ranges
1679+
// known). Prefill (M>1): run the fused GEMM, zero every route of the 3 separate
1680+
// linears.
16751681
// Decode (M==1): zero the fused, leave the 3 coop4 GEMVs (their own hooks set
16761682
// the decode wg) -- the fused 64x64 tile wastes 63/64 rows at M=1. Recomputes
16771683
// live M + the 3 output cur_dims + fused params. Inert on a static graph; a
@@ -1681,8 +1687,9 @@ void WebGPUGraph::add_qkv_fused_hook(const QkvFusionGroup& g) {
16811687
out_v_id = g.out_v;
16821688
const uint32_t K = g.K, Kp = g.K_packed, gs = g.group_size, Nq = g.Nq,
16831689
Nk = g.Nk, Nv = g.Nv, Nf = g.Nq + g.Nk + g.Nv;
1684-
const size_t fused_idx = g.fused_dispatch, sep0 = g.sep_dispatch[0],
1685-
sep1 = g.sep_dispatch[1], sep2 = g.sep_dispatch[2];
1690+
const size_t fused_idx = g.fused_dispatch;
1691+
const std::array<utils::DispatchRange, 3> separate_ranges = {
1692+
g.sep_dispatch[0], g.sep_dispatch[1], g.sep_dispatch[2]};
16861693
WGPUBuffer params_buf = g.fused_params;
16871694
auto update_route = [input_id,
16881695
out_q_id,
@@ -1696,9 +1703,7 @@ void WebGPUGraph::add_qkv_fused_hook(const QkvFusionGroup& g) {
16961703
Nv,
16971704
Nf,
16981705
fused_idx,
1699-
sep0,
1700-
sep1,
1701-
sep2,
1706+
separate_ranges,
17021707
params_buf](WebGPUGraph& gr) {
17031708
const auto& d = gr.cur_dims(input_id);
17041709
uint64_t numel = 1;
@@ -1729,12 +1734,12 @@ void WebGPUGraph::add_qkv_fused_hook(const QkvFusionGroup& g) {
17291734
const uint32_t nbM2 = (m + 63u) / 64u;
17301735
gr.dispatch_at(fused_idx).workgroup_count_x = nbN2 * nbM2;
17311736
gr.dispatch_at(fused_idx).workgroup_count_y = 1u;
1732-
gr.dispatch_at(sep0).workgroup_count_x = 0u;
1733-
gr.dispatch_at(sep0).workgroup_count_y = 0u;
1734-
gr.dispatch_at(sep1).workgroup_count_x = 0u;
1735-
gr.dispatch_at(sep1).workgroup_count_y = 0u;
1736-
gr.dispatch_at(sep2).workgroup_count_x = 0u;
1737-
gr.dispatch_at(sep2).workgroup_count_y = 0u;
1737+
for (const auto& range : separate_ranges) {
1738+
for (size_t i = range.begin; i < range.end; i++) {
1739+
gr.dispatch_at(i).workgroup_count_x = 0u;
1740+
gr.dispatch_at(i).workgroup_count_y = 0u;
1741+
}
1742+
}
17381743
} else {
17391744
gr.dispatch_at(fused_idx).workgroup_count_x = 0u;
17401745
gr.dispatch_at(fused_idx).workgroup_count_y = 0u;

backends/webgpu/runtime/WebGPUGraph.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,10 +674,10 @@ class WebGPUGraph {
674674
uint32_t K = 0, K_packed = 0, group_size = 0, num_groups = 0;
675675
uint32_t padded_N_q = 0, padded_N_k = 0, padded_N_v = 0;
676676
unsigned op_idx[3] = {0, 0, 0}; // the 3 q/k/v linear op-chain indices
677-
size_t sep_dispatch[3] = {
678-
0,
679-
0,
680-
0}; // their dispatch indices (filled in build())
677+
utils::DispatchRange sep_dispatch[3] = {
678+
{0, 0},
679+
{0, 0},
680+
{0, 0}}; // each linear's complete route range (filled in build())
681681
size_t fused_dispatch = 0; // the fused GEMM dispatch index
682682
WGPUBuffer fused_params =
683683
nullptr; // the fused params UBO (rewritten by the hook)

backends/webgpu/runtime/WebGPUShaderRegistry.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
#include <executorch/backends/webgpu/runtime/ops/quantized_linear/q4gsw_linear_gemm_steel_wgsl.h>
100100
#include <executorch/backends/webgpu/runtime/ops/quantized_linear/q4gsw_linear_wgsl.h>
101101
#include <executorch/backends/webgpu/runtime/ops/quantized_linear/q4gsw_requant_wgsl.h>
102+
#include <executorch/backends/webgpu/runtime/ops/quantized_linear/q4gsw_steel_bk64_wgsl.h>
102103
#include <executorch/backends/webgpu/runtime/ops/reduce/reduce_wgsl.h>
103104
#include <executorch/backends/webgpu/runtime/ops/relu/relu_wgsl.h>
104105
#include <executorch/backends/webgpu/runtime/ops/repeat/repeat_wgsl.h>
@@ -147,7 +148,7 @@
147148
namespace executorch::backends::webgpu {
148149
namespace {
149150

150-
constexpr std::array<WebGPUShaderInfo, 129> kShaderRegistry = {{
151+
constexpr std::array<WebGPUShaderInfo, 130> kShaderRegistry = {{
151152
{
152153
"abs",
153154
kAbsWGSL,
@@ -757,6 +758,13 @@ constexpr std::array<WebGPUShaderInfo, 129> kShaderRegistry = {{
757758
kQ4gswRequantWorkgroupSizeY,
758759
kQ4gswRequantWorkgroupSizeZ,
759760
},
761+
{
762+
"q4gsw_steel_bk64",
763+
kQ4gswSteelBk64WGSL,
764+
kQ4gswSteelBk64WorkgroupSizeX,
765+
kQ4gswSteelBk64WorkgroupSizeY,
766+
kQ4gswSteelBk64WorkgroupSizeZ,
767+
},
760768
{
761769
"q8ta_add",
762770
kQ8taAddWGSL,

0 commit comments

Comments
 (0)