Skip to content

Commit 9d2e2a9

Browse files
authored
[ExecuTorch][WebGPU] Dynamic resize hook for SDPA (live seq-len S)
Differential Revision: D109906097 Pull Request resolved: #20580
1 parent a10622b commit 9d2e2a9

1 file changed

Lines changed: 117 additions & 73 deletions

File tree

  • backends/webgpu/runtime/ops/sdpa

backends/webgpu/runtime/ops/sdpa/Sdpa.cpp

Lines changed: 117 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ static WGPUBuffer record_update_cache_dispatch(
240240
uint32_t kv_dst_offset,
241241
uint64_t cache_numel,
242242
uint32_t uc_wg,
243-
bool dynamic_pos,
243+
bool retain_uniform,
244244
const char* label) {
245245
const uint32_t wgc = utils::compute_1d_workgroup_count(
246246
device, static_cast<uint32_t>(kv_numel), uc_wg, label);
@@ -258,7 +258,7 @@ static WGPUBuffer record_update_cache_dispatch(
258258
sizeof(uc),
259259
wgc,
260260
uc_wg,
261-
dynamic_pos,
261+
retain_uniform,
262262
"update_cache");
263263
return ubuf;
264264
}
@@ -417,7 +417,7 @@ void sdpa_with_kv_cache_impl(WebGPUGraph& graph, const std::vector<int>& args) {
417417
// Dynamic input_pos: the resize hook rewrites these per step.
418418
WGPUBuffer uc_k_buf = nullptr, uc_v_buf = nullptr, qk_buf = nullptr,
419419
softmax_buf = nullptr, av_buf = nullptr;
420-
size_t qk_idx = 0;
420+
size_t qk_idx = 0, uc_k_idx = 0, uc_v_idx = 0, softmax_idx = 0, av_idx = 0;
421421

422422
const WGPUDevice device = graph.device();
423423
const uint32_t uc_wg =
@@ -442,7 +442,7 @@ void sdpa_with_kv_cache_impl(WebGPUGraph& graph, const std::vector<int>& args) {
442442
kv_dst_offset,
443443
numel(k_cache),
444444
uc_wg,
445-
dynamic_pos,
445+
true,
446446
"update_cache(K)");
447447
uc_v_buf = record_update_cache_dispatch(
448448
graph,
@@ -453,8 +453,10 @@ void sdpa_with_kv_cache_impl(WebGPUGraph& graph, const std::vector<int>& args) {
453453
kv_dst_offset,
454454
numel(v_cache),
455455
uc_wg,
456-
dynamic_pos,
456+
true,
457457
"update_cache(V)");
458+
uc_k_idx = graph.num_dispatches() - 2;
459+
uc_v_idx = graph.num_dispatches() - 1;
458460

459461
// FlashDecoding decode (S==1, static pos). Shapes FD can't handle (head dim
460462
// > kSdpaFdMaxHeadDim) fall through to the materialized path below.
@@ -494,7 +496,7 @@ void sdpa_with_kv_cache_impl(WebGPUGraph& graph, const std::vector<int>& args) {
494496
sizeof(p),
495497
wgc,
496498
qk_wg,
497-
dynamic_pos,
499+
true,
498500
"sdpa_compute_attn_weights");
499501
qk_buf = ubuf;
500502
qk_idx = graph.num_dispatches() - 1;
@@ -518,9 +520,10 @@ void sdpa_with_kv_cache_impl(WebGPUGraph& graph, const std::vector<int>& args) {
518520
sizeof(p),
519521
wgc,
520522
0,
521-
dynamic_pos,
523+
true,
522524
"sdpa_softmax");
523525
softmax_buf = ubuf;
526+
softmax_idx = graph.num_dispatches() - 1;
524527
}
525528

526529
// --- Dispatch 5: AV -> out. One thread per TM x TN tile.
@@ -544,77 +547,118 @@ void sdpa_with_kv_cache_impl(WebGPUGraph& graph, const std::vector<int>& args) {
544547
sizeof(p),
545548
wgc,
546549
av_wg,
547-
dynamic_pos,
550+
true,
548551
"sdpa_compute_out");
549552
av_buf = ubuf;
553+
av_idx = graph.num_dispatches() - 1;
550554
}
551555

552-
// Per-step recompute hook; mirrors Vulkan DynamicDispatchNode.
556+
// Per-step recompute: live S (q resize) or input_pos (SymInt); inert if
557+
// static.
558+
const int64_t pos_const = input_pos;
559+
auto sdpa_resize = [q_id,
560+
qn,
561+
S,
562+
out_id,
563+
dynamic_pos,
564+
input_pos_id,
565+
pos_const,
566+
Hq,
567+
Hkv,
568+
D,
569+
Cmax,
570+
g,
571+
scale,
572+
qk_idx,
573+
uc_k_idx,
574+
uc_v_idx,
575+
softmax_idx,
576+
av_idx,
577+
uc_wg,
578+
qk_wg,
579+
av_wg,
580+
uc_k_buf,
581+
uc_v_buf,
582+
qk_buf,
583+
softmax_buf,
584+
av_buf](WebGPUGraph& gr) {
585+
const int64_t s = gr.cur_dims(q_id)[qn - 3];
586+
const int64_t pos = dynamic_pos
587+
? static_cast<int64_t>(gr.read_symint(input_pos_id))
588+
: pos_const;
589+
if (s <= 0 || pos < 0) {
590+
throw std::runtime_error("WebGPU sdpa: invalid live S or input_pos");
591+
}
592+
// Scratch (attn_weights/softmax) is sized at build for S=max; a larger live
593+
// S would overrun it. Make that invariant load-bearing.
594+
if (s > S) {
595+
throw std::runtime_error(
596+
"WebGPU sdpa: live S exceeds the build-time max (scratch capacity)");
597+
}
598+
const int64_t ctx = s + pos;
599+
if (ctx <= 0 || ctx > Cmax) {
600+
throw std::runtime_error(
601+
"WebGPU sdpa: context_len exceeds cache capacity");
602+
}
603+
const uint32_t kv_off = static_cast<uint32_t>(
604+
static_cast<uint64_t>(pos) * static_cast<uint64_t>(Hkv) *
605+
static_cast<uint64_t>(D));
606+
const uint64_t aw_floats = static_cast<uint64_t>(Hq) *
607+
static_cast<uint64_t>(s) * static_cast<uint64_t>(ctx);
608+
if (aw_floats > UINT32_MAX) {
609+
throw std::runtime_error("WebGPU sdpa: Hq*S*context_len exceeds uint32");
610+
}
611+
const uint64_t kv_numel = static_cast<uint64_t>(s) *
612+
static_cast<uint64_t>(Hkv) * static_cast<uint64_t>(D);
613+
if (kv_numel > UINT32_MAX) {
614+
throw std::runtime_error("WebGPU sdpa: S*Hkv*D exceeds uint32");
615+
}
616+
const uint64_t k_cache_numel = static_cast<uint64_t>(Cmax) *
617+
static_cast<uint64_t>(Hkv) * static_cast<uint64_t>(D);
618+
619+
// update_cache K/V: dispatch (kv_numel) + dst offset scale with live S/pos.
620+
UpdateCacheParams uc =
621+
make_update_cache_params(kv_numel, kv_off, k_cache_numel);
622+
wgpuQueueWriteBuffer(gr.queue(), uc_k_buf, 0, &uc, sizeof(uc));
623+
wgpuQueueWriteBuffer(gr.queue(), uc_v_buf, 0, &uc, sizeof(uc));
624+
const uint32_t uc_wgc = utils::compute_1d_workgroup_count(
625+
gr.device(), static_cast<uint32_t>(kv_numel), uc_wg, "uc(resize)");
626+
gr.dispatch_at(uc_k_idx).workgroup_count_x = uc_wgc;
627+
gr.dispatch_at(uc_v_idx).workgroup_count_x = uc_wgc;
628+
629+
// QK: one thread per TM x TN tile; grid = Hq*ceil(S/TM)*ceil(ctx/TN).
630+
AttnWeightsParams qp =
631+
make_attn_weights_params(s, Hq, Hkv, D, ctx, pos, g, scale);
632+
wgpuQueueWriteBuffer(gr.queue(), qk_buf, 0, &qp, sizeof(qp));
633+
const int64_t qk_tiles =
634+
Hq * utils::div_up(s, kSdpaTileM) * utils::div_up(ctx, kSdpaTileN);
635+
gr.dispatch_at(qk_idx).workgroup_count_x =
636+
utils::compute_1d_workgroup_count(
637+
gr.device(), static_cast<uint32_t>(qk_tiles), qk_wg, "QK(resize)");
638+
639+
// softmax: one workgroup per (h,s) row.
640+
SoftmaxParams sp = make_softmax_params(Hq, s, ctx);
641+
wgpuQueueWriteBuffer(gr.queue(), softmax_buf, 0, &sp, sizeof(sp));
642+
gr.dispatch_at(softmax_idx).workgroup_count_x =
643+
utils::compute_1d_workgroup_count(
644+
gr.device(), static_cast<uint32_t>(Hq * s), 1, "softmax(resize)");
645+
646+
// AV: one thread per TM x TN tile; grid = Hq*ceil(S/TM)*ceil(D/TN).
647+
ComputeOutParams op = make_compute_out_params(s, Hq, Hkv, D, ctx, g);
648+
wgpuQueueWriteBuffer(gr.queue(), av_buf, 0, &op, sizeof(op));
649+
const int64_t av_tiles =
650+
Hq * utils::div_up(s, kSdpaTileM) * utils::div_up(D, kSdpaTileN);
651+
gr.dispatch_at(av_idx).workgroup_count_x =
652+
utils::compute_1d_workgroup_count(
653+
gr.device(), static_cast<uint32_t>(av_tiles), av_wg, "AV(resize)");
654+
655+
// Output attn has the same shape as q: [.., S, Hq, D].
656+
gr.set_cur_dims(out_id, gr.cur_dims(q_id));
657+
};
658+
// q and input_pos share one idempotent recompute; a double-fire is harmless.
659+
graph.add_tensor_resize_hook(q_id, sdpa_resize);
553660
if (dynamic_pos) {
554-
graph.add_resize_hook(
555-
input_pos_id,
556-
[input_pos_id,
557-
S,
558-
Hq,
559-
Hkv,
560-
D,
561-
Cmax,
562-
g,
563-
scale,
564-
qk_idx,
565-
qk_wg,
566-
uc_k_buf,
567-
uc_v_buf,
568-
qk_buf,
569-
softmax_buf,
570-
av_buf](WebGPUGraph& gr) {
571-
const int32_t pos = gr.read_symint(input_pos_id);
572-
if (pos < 0) {
573-
throw std::runtime_error(
574-
"WebGPU sdpa: input_pos must be non-negative");
575-
}
576-
const int64_t ctx = S + pos;
577-
if (ctx <= 0 || ctx > Cmax) {
578-
throw std::runtime_error(
579-
"WebGPU sdpa: context_len exceeds cache capacity");
580-
}
581-
const uint32_t kv_off = static_cast<uint32_t>(
582-
static_cast<uint64_t>(pos) * static_cast<uint64_t>(Hkv) *
583-
static_cast<uint64_t>(D));
584-
const uint64_t aw_floats = static_cast<uint64_t>(Hq) *
585-
static_cast<uint64_t>(S) * static_cast<uint64_t>(ctx);
586-
if (aw_floats > UINT32_MAX) {
587-
throw std::runtime_error(
588-
"WebGPU sdpa: Hq*S*context_len exceeds uint32 max");
589-
}
590-
const uint64_t kv_numel = static_cast<uint64_t>(S) *
591-
static_cast<uint64_t>(Hkv) * static_cast<uint64_t>(D);
592-
const uint64_t k_cache_numel = static_cast<uint64_t>(Cmax) *
593-
static_cast<uint64_t>(Hkv) * static_cast<uint64_t>(D);
594-
595-
UpdateCacheParams uc =
596-
make_update_cache_params(kv_numel, kv_off, k_cache_numel);
597-
wgpuQueueWriteBuffer(gr.queue(), uc_k_buf, 0, &uc, sizeof(uc));
598-
wgpuQueueWriteBuffer(gr.queue(), uc_v_buf, 0, &uc, sizeof(uc));
599-
600-
AttnWeightsParams qp =
601-
make_attn_weights_params(S, Hq, Hkv, D, ctx, pos, g, scale);
602-
wgpuQueueWriteBuffer(gr.queue(), qk_buf, 0, &qp, sizeof(qp));
603-
const int64_t qk_tiles = Hq * utils::div_up(S, kSdpaTileM) *
604-
utils::div_up(ctx, kSdpaTileN);
605-
const uint32_t qk_wgc = utils::compute_1d_workgroup_count(
606-
gr.device(),
607-
static_cast<uint32_t>(qk_tiles),
608-
qk_wg,
609-
"QK(resize)");
610-
gr.dispatch_at(qk_idx).workgroup_count_x = qk_wgc;
611-
612-
SoftmaxParams sp = make_softmax_params(Hq, S, ctx);
613-
wgpuQueueWriteBuffer(gr.queue(), softmax_buf, 0, &sp, sizeof(sp));
614-
615-
ComputeOutParams op = make_compute_out_params(S, Hq, Hkv, D, ctx, g);
616-
wgpuQueueWriteBuffer(gr.queue(), av_buf, 0, &op, sizeof(op));
617-
});
661+
graph.add_resize_hook(input_pos_id, sdpa_resize);
618662
}
619663
}
620664

0 commit comments

Comments
 (0)