@@ -243,9 +243,14 @@ vkapi::ShaderInfo pick_sdpa_qk_shader(
243243 const ValueRef q_projected = args.at (1 ).refs .at (0 );
244244 const ValueRef k_cache = args.at (1 ).refs .at (1 );
245245 const bool is_gemv = is_single_token (graph, q_projected);
246+ // An additive attn_mask is bound as a third input: q, k, attn_mask.
247+ const bool has_mask = args.at (1 ).refs .size () >= 3 ;
246248
247249 std::string shader_name = " sdpa_compute_attn_weights" ;
248250 shader_name += is_gemv ? " _coop" : " _tiled" ;
251+ if (has_mask) {
252+ shader_name += " _mask" ;
253+ }
249254 add_storage_type_suffix (shader_name, graph->storage_type_of (q_projected));
250255 add_storage_type_suffix (shader_name, graph->storage_type_of (k_cache));
251256 add_dtype_suffix (shader_name, graph->dtype_of (q_projected));
@@ -481,11 +486,12 @@ void add_sdpa_kv_cache_update_node(
481486}
482487
483488// Unified QK node (attn_weights = scale * Q @ K^T [+ bias]).
484- // LLM: pass input_pos_symint (real symint), attn_mask = kDummyValueRef.
489+ // LLM: pass input_pos_symint (real symint), and attn_mask = a 2D [S, C]
490+ // additive mask that replaces the causal mask, or kDummyValueRef.
485491// FUSED: pass input_pos_symint = kDummyValueRef, attn_mask = valid ref or
486492// kDummyValueRef to indicate no bias. scale_val is always passed as
487- // a spec const; the LLM path computes it per head_dim and FUSED may
488- // inherit from the caller-supplied scale.
493+ // a spec const; both modes default it to 1/sqrt( head_dim) and use the
494+ // caller-supplied scale when there is one .
489495void add_sdpa_compute_attn_weights_node (
490496 ComputeGraph& graph,
491497 const ValueRef q,
@@ -503,7 +509,8 @@ void add_sdpa_compute_attn_weights_node(
503509
504510 if (mode == SDPAMode::LLM ) {
505511 param_ubos.append (graph.get_or_create_int_param_buffer (input_pos_symint));
506- } else if (is_valid (attn_mask)) {
512+ }
513+ if (is_valid (attn_mask)) {
507514 param_ubos.append (graph.sizes_ubo (attn_mask));
508515 read_inputs.push_back (attn_mask);
509516 }
@@ -640,6 +647,33 @@ void add_sdpa_compute_out_node(
640647 resize_sdpa_out_node));
641648}
642649
650+ // For a masked LLM SDPA, sets out_symint = C - S (the number of keys in k
651+ // minus the number of queries), which the SDPA shaders read in place of
652+ // input_pos so that they attend to every key.
653+ void resize_attn_mask_input_pos_node (
654+ ComputeGraph* graph,
655+ const std::vector<ArgGroup>& args,
656+ const std::vector<ValueRef>& resize_args) {
657+ (void )args;
658+ const ValueRef q = resize_args.at (0 );
659+ const ValueRef k = resize_args.at (1 );
660+ const ValueRef out_symint = resize_args.at (2 );
661+ graph->set_symint (
662+ out_symint,
663+ graph->size_at <int32_t >(-3 , k) - graph->size_at <int32_t >(-3 , q));
664+ }
665+
666+ ValueRef add_attn_mask_input_pos_node (
667+ ComputeGraph& graph,
668+ const ValueRef q,
669+ const ValueRef k) {
670+ const ValueRef out_symint = graph.add_symint (
671+ graph.size_at <int32_t >(-3 , k) - graph.size_at <int32_t >(-3 , q));
672+ graph.execute_nodes ().emplace_back (
673+ new ExecuteNode (resize_attn_mask_input_pos_node, {q, k, out_symint}));
674+ return out_symint;
675+ }
676+
643677//
644678// High level operator impl
645679//
@@ -696,11 +730,30 @@ void sdpa_impl(ComputeGraph& graph, const std::vector<ValueRef>& args) {
696730 VK_CHECK_COND (
697731 graph.val_is_none (dropout_p) ||
698732 graph.extract_scalar <double >(dropout_p) == 0 );
699- VK_CHECK_COND (graph.val_is_none (scale));
700- // is_causal is assumed to be true in the current implementation.
701- VK_CHECK_COND (
702- graph.val_is_none (is_causal) || graph.extract_scalar <bool >(is_causal));
703- VK_CHECK_COND (graph.val_is_none (attn_mask));
733+
734+ // As in the CPU op, an attn_mask replaces the causal mask: it is a 2D
735+ // [S, C] additive mask over every key in k_cache, and cannot be combined
736+ // with is_causal. Without one, the attention is assumed to be causal.
737+ const bool has_mask = graph.val_is_not_none (attn_mask);
738+ if (has_mask) {
739+ VK_CHECK_COND (
740+ graph.val_is_none (is_causal) || !graph.extract_scalar <bool >(is_causal),
741+ " attn_mask and is_causal cannot both be set" );
742+ VK_CHECK_COND (graph.dim_of (attn_mask) == 2 );
743+ VK_CHECK_COND (
744+ graph.size_at <int64_t >(-2 , attn_mask) ==
745+ graph.size_at <int64_t >(-3 , q_projected));
746+ VK_CHECK_COND (
747+ graph.size_at <int64_t >(-1 , attn_mask) ==
748+ graph.size_at <int64_t >(-3 , k_cache));
749+ VK_CHECK_COND (graph.dtype_of (attn_mask) == graph.dtype_of (q_projected));
750+ VK_CHECK_COND (
751+ graph.storage_type_of (attn_mask) == graph.storage_type_of (q_projected));
752+ VK_CHECK_COND (graph.packed_dim_of (attn_mask) == WHCN ::kWidthDim );
753+ } else {
754+ VK_CHECK_COND (
755+ graph.val_is_none (is_causal) || graph.extract_scalar <bool >(is_causal));
756+ }
704757
705758 const int64_t num_q_heads = graph.size_at <int64_t >(-2 , q_projected);
706759 int64_t max_seq_len = graph.size_at <int64_t >(-3 , q_projected);
@@ -763,14 +816,23 @@ void sdpa_impl(ComputeGraph& graph, const std::vector<ValueRef>& args) {
763816 utils::kWidthPacked );
764817
765818 const int32_t head_dim_size = graph.size_at <int32_t >(-1 , q_projected);
766- const float scale_val = 1 .0f / std::sqrt (static_cast <float >(head_dim_size));
819+ const float scale_val = graph.val_is_none (scale)
820+ ? 1 .0f / std::sqrt (static_cast <float >(head_dim_size))
821+ : graph.extract_scalar <float >(scale);
822+
823+ // The SDPA shaders attend to the first input_pos + S keys. A masked
824+ // attention attends to all C keys in k_cache, so give them C - S in place of
825+ // input_pos.
826+ const ValueRef attn_input_pos = has_mask
827+ ? add_attn_mask_input_pos_node (graph, q_projected, k_cache)
828+ : input_pos_symint;
767829
768830 add_sdpa_compute_attn_weights_node (
769831 graph,
770832 q_projected,
771833 k_cache,
772- input_pos_symint ,
773- /* attn_mask= */ kDummyValueRef ,
834+ attn_input_pos ,
835+ has_mask ? attn_mask : kDummyValueRef ,
774836 scale_val,
775837 attn_weights,
776838 SDPAMode::LLM );
@@ -780,7 +842,7 @@ void sdpa_impl(ComputeGraph& graph, const std::vector<ValueRef>& args) {
780842 attn_weights,
781843 q_projected,
782844 k_cache,
783- input_pos_symint ,
845+ attn_input_pos ,
784846 attn_weights_softmax,
785847 SDPAMode::LLM );
786848
@@ -790,7 +852,7 @@ void sdpa_impl(ComputeGraph& graph, const std::vector<ValueRef>& args) {
790852 v_cache,
791853 q_projected,
792854 /* k=*/ kDummyValueRef ,
793- input_pos_symint ,
855+ attn_input_pos ,
794856 out,
795857 SDPAMode::LLM );
796858}
0 commit comments