@@ -5,6 +5,7 @@ package xla
55import (
66 "encoding/json"
77 "maps"
8+ "math"
89 "strconv"
910
1011 "github.com/gomlx/compute"
@@ -57,11 +58,12 @@ type fmhaVariant struct {
5758// Bias routing: cfg.Bias non-nil selects the fmhaScaleBias targets. cuDNN's ScaleBias kernel does
5859// not accept seqlen operands, so bias+seqlens returns ErrNotImplemented and the caller falls back
5960// to the decomposed path.
60- func selectFMHAVariant (op string , qkvDType dtypes.DType , causal bool ,
61+ func selectFMHAVariant (op string , qkvDType dtypes.DType ,
6162 cfg * compute.ScaledDotProductAttentionConfig ) (fmhaVariant , error ) {
6263 var v fmhaVariant
6364 hasSeqLens := cfg != nil && cfg .QuerySeqLen != nil && cfg .KeyValueSeqLen != nil
6465 hasBias := cfg != nil && cfg .Bias != nil
66+ causal := cfg != nil && cfg .Causal
6567
6668 switch qkvDType {
6769 case dtypes .BFloat16 :
@@ -262,8 +264,7 @@ func formatScale(scale float64) string {
262264// f16/bf16 (fp8 paused), BSHD-layout, equal-head, on a cuda plugin. Causality and
263265// per-batch seqlen padding are supported (mask_type derives from them in selectFMHAVariant);
264266// an explicit materialized mask is not (use seqlens instead). Anything else -> ErrNotImplemented.
265- func (f * Function ) flashSupported (op string , qkvDType dtypes.DType , mask compute.Value , numHeads , numKVHeads , featureDim int , axesLayout compute.AxesLayout , causal bool , options * compute.ScaledDotProductAttentionConfig ) error {
266- _ = causal
267+ func (f * Function ) flashSupported (op string , qkvDType dtypes.DType , mask compute.Value , numHeads , numKVHeads , featureDim int , axesLayout compute.AxesLayout , options * compute.ScaledDotProductAttentionConfig ) error {
267268 if ! f .builder .backend .plugin .IsCUDA () {
268269 return errors .Wrapf (compute .ErrNotImplemented , "%s: cuDNN flash needs the cuda plugin, have %q" , op , f .builder .backend .pluginName )
269270 }
@@ -283,10 +284,10 @@ func (f *Function) flashSupported(op string, qkvDType dtypes.DType, mask compute
283284 return errors .Wrapf (compute .ErrNotImplemented ,
284285 "%s: cuDNN flash path supports only BSHD or BHSD layouts (got layout %v)" , op , axesLayout )
285286 }
286- if numKVHeads != numHeads {
287+ if numHeads % numKVHeads != 0 {
287288 return errors .Wrapf (compute .ErrNotImplemented ,
288- "%s: cuDNN flash path requires equal number of q/ kv heads (got layout=%v heads=%d/ %d)" ,
289- op , axesLayout , numHeads , numKVHeads )
289+ "%s: cuDNN flash path requires query heads (%d) to be a multiple of kv heads (%d)" ,
290+ op , numHeads , numKVHeads )
290291 }
291292 // One of QuerySeqLen/KeyValueSeqLen set without the other is ambiguous.
292293 if options != nil && (options .QuerySeqLen != nil ) != (options .KeyValueSeqLen != nil ) {
@@ -374,8 +375,12 @@ var bwdResultLayouts = [][]int{{3, 1, 2, 0}, {3, 1, 2, 0}, {3, 1, 2, 0}, {0}}
374375// (BSHD), bf16. It returns the [B,S,H,D] bf16 output and the [B,H,S] f32 softmax statistics
375376// (log-sum-exp) the flash backward needs. The [B,H,S,S] scores never materialize. On non-cuda
376377// plugins or unsupported option combinations it returns ErrNotImplemented.
377- func (f * Function ) FusedScaledDotProductAttention (query , key , value , mask compute.Value , _ , _ int , axesLayout compute.AxesLayout , scale float64 , causal bool , options * compute.ScaledDotProductAttentionConfig ) (output compute.Value , statesForVJP []compute.Value , err error ) {
378+ func (f * Function ) FusedScaledDotProductAttention (query , key , value compute.Value , axesLayout compute.AxesLayout , options * compute.ScaledDotProductAttentionConfig ) (output compute.Value , statesForVJP []compute.Value , err error ) {
378379 op := compute .OpTypeFusedScaledDotProductAttention .String ()
380+ var mask compute.Value
381+ if options != nil {
382+ mask = options .Mask
383+ }
379384 batchSize , seqLen , numHeads , featureDim , err := f .bshdDims (op , query , axesLayout )
380385 if err != nil {
381386 return nil , nil , err
@@ -388,16 +393,35 @@ func (f *Function) FusedScaledDotProductAttention(query, key, value, mask comput
388393 if err != nil {
389394 return nil , nil , err
390395 }
391- if err = f .flashSupported (op , qDType , mask , numHeads , numKVHeads , featureDim , axesLayout , causal , options ); err != nil {
396+ if err = f .flashSupported (op , qDType , mask , numHeads , numKVHeads , featureDim , axesLayout , options ); err != nil {
392397 return nil , nil , err
393398 }
394- variant , err := selectFMHAVariant (op , qDType , causal , options )
399+ variant , err := selectFMHAVariant (op , qDType , options )
395400 if err != nil {
396401 return nil , nil , err
397402 }
403+ var scale float64
404+ if options != nil {
405+ scale = options .Scale
406+ }
407+ if scale == 0 {
408+ scale = 1.0 / math .Sqrt (float64 (featureDim ))
409+ }
410+ broadcastedKey := key
411+ broadcastedValue := value
412+ if numHeads != numKVHeads {
413+ broadcastedKey , err = f .broadcastGQA (key , numHeads , numKVHeads , axesLayout )
414+ if err != nil {
415+ return nil , nil , err
416+ }
417+ broadcastedValue , err = f .broadcastGQA (value , numHeads , numKVHeads , axesLayout )
418+ if err != nil {
419+ return nil , nil , err
420+ }
421+ }
398422 // Operand order cuDNN expects: q, k, v, [bias], [seqQ, seqKV]. Bias goes before seqlens
399423 // (bias and seqlens are mutually exclusive here; see selectFMHAVariant).
400- operands := []compute.Value {query , key , value }
424+ operands := []compute.Value {query , broadcastedKey , broadcastedValue }
401425 operandLayouts := [][]int {{3 , 2 , 1 , 0 }, {3 , 2 , 1 , 0 }, {3 , 2 , 1 , 0 }}
402426 if variant .hasBias {
403427 if err = validateBias ("Bias" , options .Bias , qDType , batchSize , numHeads , seqLen , seqLen ); err != nil {
@@ -446,8 +470,12 @@ func (f *Function) FusedScaledDotProductAttention(query, key, value, mask comput
446470// (statesForVJP[0], from the forward) plus the forward output and the output gradient dOutput into
447471// the cuDNN backward custom-call, so the [B,H,S,S] scores never materialize in the backward either.
448472// Returns dQuery, dKey, dValue as [B,S,H,D] bf16.
449- func (f * Function ) FusedScaledDotProductAttentionVJP (query , key , value , mask compute.Value , _ , _ int , axesLayout compute.AxesLayout , scale float64 , causal bool , options * compute.ScaledDotProductAttentionConfig , output compute.Value , statesForVJP []compute.Value , dOutput compute.Value ) (dQuery , dKey , dValue compute.Value , err error ) {
473+ func (f * Function ) FusedScaledDotProductAttentionVJP (query , key , value compute.Value , axesLayout compute.AxesLayout , options * compute.ScaledDotProductAttentionConfig , output compute.Value , statesForVJP []compute.Value , dOutput compute.Value ) (dQuery , dKey , dValue compute.Value , err error ) {
450474 op := compute .OpTypeFusedScaledDotProductAttentionVJP .String ()
475+ var mask compute.Value
476+ if options != nil {
477+ mask = options .Mask
478+ }
451479 batchSize , seqLen , numHeads , featureDim , err := f .bshdDims (op , query , axesLayout )
452480 if err != nil {
453481 return nil , nil , nil , err
@@ -464,16 +492,35 @@ func (f *Function) FusedScaledDotProductAttentionVJP(query, key, value, mask com
464492 if err != nil {
465493 return nil , nil , nil , err
466494 }
467- if err = f .flashSupported (op , qDType , mask , numHeads , numKVHeads , featureDim , axesLayout , causal , options ); err != nil {
495+ if err = f .flashSupported (op , qDType , mask , numHeads , numKVHeads , featureDim , axesLayout , options ); err != nil {
468496 return nil , nil , nil , err
469497 }
470- variant , err := selectFMHAVariant (op , qDType , causal , options )
498+ variant , err := selectFMHAVariant (op , qDType , options )
471499 if err != nil {
472500 return nil , nil , nil , err
473501 }
502+ var scale float64
503+ if options != nil {
504+ scale = options .Scale
505+ }
506+ if scale == 0 {
507+ scale = 1.0 / math .Sqrt (float64 (featureDim ))
508+ }
509+ broadcastedKey := key
510+ broadcastedValue := value
511+ if numHeads != numKVHeads {
512+ broadcastedKey , err = f .broadcastGQA (key , numHeads , numKVHeads , axesLayout )
513+ if err != nil {
514+ return nil , nil , nil , err
515+ }
516+ broadcastedValue , err = f .broadcastGQA (value , numHeads , numKVHeads , axesLayout )
517+ if err != nil {
518+ return nil , nil , nil , err
519+ }
520+ }
474521 // Operand order cuDNN expects: q, k, v, softmax_sum, dO, [bias], O. Bias goes at index 5,
475522 // before O (bias and seqlens are mutually exclusive; see selectFMHAVariant).
476- operands := []compute.Value {query , key , value , softmaxStats , dOutput }
523+ operands := []compute.Value {query , broadcastedKey , broadcastedValue , softmaxStats , dOutput }
477524 operandLayouts := [][]int {{3 , 2 , 1 , 0 }, {3 , 2 , 1 , 0 }, {3 , 2 , 1 , 0 }, {2 , 1 , 0 }, {3 , 2 , 1 , 0 }}
478525 if variant .hasBias {
479526 if err = validateBias ("Bias" , options .Bias , qDType , batchSize , numHeads , seqLen , seqLen ); err != nil {
@@ -532,5 +579,88 @@ func (f *Function) FusedScaledDotProductAttentionVJP(query, key, value, mask com
532579 dKey = grads [1 ]
533580 dValue = grads [2 ]
534581 }
582+ if numHeads != numKVHeads {
583+ dKey , err = f .reduceSumGQA (dKey , numHeads , numKVHeads , axesLayout )
584+ if err != nil {
585+ return nil , nil , nil , err
586+ }
587+ dValue , err = f .reduceSumGQA (dValue , numHeads , numKVHeads , axesLayout )
588+ if err != nil {
589+ return nil , nil , nil , err
590+ }
591+ }
535592 return dQuery , dKey , dValue , nil
536593}
594+
595+ func (f * Function ) broadcastGQA (x compute.Value , numQueryHeads , numKVHeads int , layout compute.AxesLayout ) (compute.Value , error ) {
596+ groupSize := numQueryHeads / numKVHeads
597+ if groupSize == 1 {
598+ return x , nil
599+ }
600+ shape , err := f .Shape (x )
601+ if err != nil {
602+ return nil , err
603+ }
604+ dtype := shape .DType
605+ dims := shape .Dimensions
606+ if len (dims ) != 4 {
607+ return nil , errors .Errorf ("broadcastGQA: expected rank-4 tensor, got rank-%d (shape %v)" , len (dims ), dims )
608+ }
609+
610+ if layout == compute .AxesLayoutBSHD {
611+ b , s , _ , d := dims [0 ], dims [1 ], dims [2 ], dims [3 ]
612+ reshaped , err := f .Reshape (x , b , s , numKVHeads , 1 , d )
613+ if err != nil {
614+ return nil , err
615+ }
616+ broadcastShape := shapes .Make (dtype , b , s , numKVHeads , groupSize , d )
617+ broadcasted , err := f .BroadcastInDim (reshaped , broadcastShape , []int {0 , 1 , 2 , 3 , 4 })
618+ if err != nil {
619+ return nil , err
620+ }
621+ return f .Reshape (broadcasted , b , s , numQueryHeads , d )
622+ } else {
623+ b , _ , s , d := dims [0 ], dims [1 ], dims [2 ], dims [3 ]
624+ reshaped , err := f .Reshape (x , b , numKVHeads , 1 , s , d )
625+ if err != nil {
626+ return nil , err
627+ }
628+ broadcastShape := shapes .Make (dtype , b , numKVHeads , groupSize , s , d )
629+ broadcasted , err := f .BroadcastInDim (reshaped , broadcastShape , []int {0 , 1 , 2 , 3 , 4 })
630+ if err != nil {
631+ return nil , err
632+ }
633+ return f .Reshape (broadcasted , b , numQueryHeads , s , d )
634+ }
635+ }
636+
637+ func (f * Function ) reduceSumGQA (dx compute.Value , numQueryHeads , numKVHeads int , layout compute.AxesLayout ) (compute.Value , error ) {
638+ groupSize := numQueryHeads / numKVHeads
639+ if groupSize == 1 {
640+ return dx , nil
641+ }
642+ shape , err := f .Shape (dx )
643+ if err != nil {
644+ return nil , err
645+ }
646+ dims := shape .Dimensions
647+ if len (dims ) != 4 {
648+ return nil , errors .Errorf ("reduceSumGQA: expected rank-4 tensor, got rank-%d (shape %v)" , len (dims ), dims )
649+ }
650+
651+ if layout == compute .AxesLayoutBSHD {
652+ b , s , _ , d := dims [0 ], dims [1 ], dims [2 ], dims [3 ]
653+ reshaped , err := f .Reshape (dx , b , s , numKVHeads , groupSize , d )
654+ if err != nil {
655+ return nil , err
656+ }
657+ return f .ReduceSum (reshaped , 3 )
658+ } else {
659+ b , _ , s , d := dims [0 ], dims [1 ], dims [2 ], dims [3 ]
660+ reshaped , err := f .Reshape (dx , b , numKVHeads , groupSize , s , d )
661+ if err != nil {
662+ return nil , err
663+ }
664+ return f .ReduceSum (reshaped , 2 )
665+ }
666+ }
0 commit comments