@@ -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