@@ -3961,7 +3961,11 @@ def aten_ops_linear(
39613961 )
39623962
39633963
3964- def _attention_qkv_shapes_supported (node : Node ) -> bool :
3964+ def scaled_dot_product_attention_validator (
3965+ node : Node , settings : Optional [CompilationSettings ] = None
3966+ ) -> bool :
3967+ enable_gqa = node .kwargs .get ("enable_gqa" , False )
3968+
39653969 query_shape , key_shape , value_shape = None , None , None
39663970 if "val" in node .args [0 ].meta :
39673971 query_shape = node .args [0 ].meta ["val" ].size ()
@@ -3980,38 +3984,46 @@ def _attention_qkv_shapes_supported(node: Node) -> bool:
39803984 )
39813985 return False
39823986
3983- # TensorRT IAttention layer supports different sequence lengths for query and key/value
3984- # ([B, Nq, Sq, H] vs [B, Nkv, Skv, H]), but K and V must still agree on all dims.
3985- seq_dim = len (query_shape ) - 2
3986- for dim , (query_dim , key_dim , value_dim ) in enumerate (
3987- zip (query_shape , key_shape , value_shape )
3988- ):
3989- if dim == seq_dim :
3990- if key_dim != value_dim :
3991- _LOGGER .debug (
3992- "key and value must have the same sequence length. Please try setting decompose_attention=True in the compilation settings."
3993- )
3994- return False
3995- else :
3996- if query_dim != key_dim or query_dim != value_dim or key_dim != value_dim :
3987+ if key_shape != value_shape :
3988+ _LOGGER .debug (
3989+ "key and value have different shapes, which is not supported. Please try setting decompose_attention=True in the compilation settings."
3990+ )
3991+ return False
3992+
3993+ ndim = len (query_shape )
3994+ num_heads_dim = 1
3995+ seq_len_dim = ndim - 2
3996+ if enable_gqa :
3997+ # IAttentionLayer natively supports GQA: Q and K/V may differ on the
3998+ # head dim (dim 1) as long as Hq is divisible by Hkv.
3999+ # Check batch (dim 0) and head_dim (last dim) match;
4000+ # skip seq_len (dim -2) for decode phase and num_heads (dim 1).
4001+ for i in range (ndim ):
4002+ if i in (num_heads_dim , seq_len_dim ):
4003+ continue
4004+ if query_shape [i ] != key_shape [i ]:
39974005 _LOGGER .debug (
3998- " query, key, and value differ on a non-sequence dimension. Please try setting decompose_attention =True in the compilation settings ."
4006+ f"GQA: query and key mismatch on dim { i } when enable_gqa =True."
39994007 )
40004008 return False
4009+ num_q_heads = query_shape [num_heads_dim ]
4010+ num_kv_heads = key_shape [num_heads_dim ]
4011+ if num_q_heads % num_kv_heads != 0 :
4012+ _LOGGER .debug (
4013+ f"GQA: num_q_heads={ num_q_heads } is not divisible by num_kv_heads={ num_kv_heads } when enable_gqa=True."
4014+ )
4015+ return False
4016+ else :
4017+ # IAttentionLayer supports decode-phase (seq_q != seq_k).
4018+ # Check all dims except the seq_len dim.
4019+ if any (query_shape [i ] != key_shape [i ] for i in range (ndim ) if i != seq_len_dim ):
4020+ _LOGGER .debug (
4021+ "query and key have incompatible shapes (batch, num_heads, or head_dim mismatch). Please try setting decompose_attention=True in the compilation settings."
4022+ )
4023+ return False
40014024 return True
40024025
40034026
4004- def scaled_dot_product_attention_validator (
4005- node : Node , settings : Optional [CompilationSettings ] = None
4006- ) -> bool :
4007- if node .kwargs .get ("enable_gqa" , False ):
4008- _LOGGER .debug (
4009- "enable_gqa is not yet supported by the converter. Please try setting decompose_attention=True in the compilation settings."
4010- )
4011- return False
4012- return _attention_qkv_shapes_supported (node )
4013-
4014-
40154027@dynamo_tensorrt_converter (
40164028 torch .ops .aten .scaled_dot_product_attention .default ,
40174029 supports_dynamic_shapes = True ,
@@ -4047,7 +4059,64 @@ def scaled_dot_product_flash_attention_validator(
40474059 if args_bounds_check (node .args , 5 , False ):
40484060 _LOGGER .debug ("return_debug_mask is not yet supported." )
40494061 return False
4050- return _attention_qkv_shapes_supported (node )
4062+
4063+ query_shape , key_shape , value_shape = None , None , None
4064+ if "val" in node .args [0 ].meta :
4065+ query_shape = node .args [0 ].meta ["val" ].size ()
4066+ if "val" in node .args [1 ].meta :
4067+ key_shape = node .args [1 ].meta ["val" ].size ()
4068+ if "val" in node .args [2 ].meta :
4069+ value_shape = node .args [2 ].meta ["val" ].size ()
4070+
4071+ # If shape metadata is unavailable, defer to runtime/converter checks.
4072+ if query_shape is None or key_shape is None or value_shape is None :
4073+ return True
4074+
4075+ if len (query_shape ) != len (key_shape ) or len (query_shape ) != len (value_shape ):
4076+ _LOGGER .debug (
4077+ "query, key, and value must have the same rank. Please try setting decompose_attention=True in the compilation settings."
4078+ )
4079+ return False
4080+
4081+ if key_shape != value_shape :
4082+ _LOGGER .debug (
4083+ "key and value have different shapes, which is not supported. Please try setting decompose_attention=True in the compilation settings."
4084+ )
4085+ return False
4086+
4087+ ndim = len (query_shape )
4088+ num_heads_dim = 1
4089+ seq_len_dim = ndim - 2
4090+ num_q_heads = query_shape [num_heads_dim ]
4091+ num_kv_heads = key_shape [num_heads_dim ]
4092+ is_gqa = num_q_heads != num_kv_heads
4093+ if is_gqa :
4094+ # IAttentionLayer natively supports GQA: Q and K/V may differ on the
4095+ # head dim (dim 1) as long as Hq is divisible by Hkv.
4096+ # Check batch (dim 0) and head_dim (last dim) match;
4097+ # skip seq_len (dim -2) for decode phase and num_heads (dim 1).
4098+ for i in range (ndim ):
4099+ if i in (num_heads_dim , seq_len_dim ):
4100+ continue
4101+ if query_shape [i ] != key_shape [i ]:
4102+ _LOGGER .debug (
4103+ f"GQA: query and key mismatch on dim { i } when enable_gqa=True."
4104+ )
4105+ return False
4106+ if num_q_heads % num_kv_heads != 0 :
4107+ _LOGGER .debug (
4108+ f"GQA: num_q_heads={ num_q_heads } is not divisible by num_kv_heads={ num_kv_heads } when enable_gqa=True."
4109+ )
4110+ return False
4111+ else :
4112+ # IAttentionLayer supports decode-phase (seq_q != seq_k).
4113+ # Check all dims except the seq_len dim.
4114+ if any (query_shape [i ] != key_shape [i ] for i in range (ndim ) if i != seq_len_dim ):
4115+ _LOGGER .debug (
4116+ "query and key have incompatible shapes (batch, num_heads, or head_dim mismatch). Please try setting decompose_attention=True in the compilation settings."
4117+ )
4118+ return False
4119+ return True
40514120
40524121
40534122@dynamo_tensorrt_converter (
@@ -4084,7 +4153,51 @@ def scaled_dot_product_efficient_attention_validator(
40844153 if args_bounds_check (node .args , 4 , False ):
40854154 _LOGGER .debug ("compute_log_sumexp is not yet supported." )
40864155 return False
4087- return _attention_qkv_shapes_supported (node )
4156+
4157+ query_shape , key_shape , value_shape = None , None , None
4158+ if "val" in node .args [0 ].meta :
4159+ query_shape = node .args [0 ].meta ["val" ].size ()
4160+ if "val" in node .args [1 ].meta :
4161+ key_shape = node .args [1 ].meta ["val" ].size ()
4162+ if "val" in node .args [2 ].meta :
4163+ value_shape = node .args [2 ].meta ["val" ].size ()
4164+
4165+ # If shape metadata is unavailable, defer to runtime/converter checks.
4166+ if query_shape is None or key_shape is None or value_shape is None :
4167+ return True
4168+
4169+ if len (query_shape ) != len (key_shape ) or len (query_shape ) != len (value_shape ):
4170+ _LOGGER .debug (
4171+ "query, key, and value must have the same rank. Please try setting decompose_attention=True in the compilation settings."
4172+ )
4173+ return False
4174+
4175+ if key_shape != value_shape :
4176+ _LOGGER .debug (
4177+ "key and value have different shapes, which is not supported. Please try setting decompose_attention=True in the compilation settings."
4178+ )
4179+ return False
4180+
4181+ # Note1: GQA (Hq != Hkv) is intentionally not supported here.
4182+ # PyTorch's eager _scaled_dot_product_efficient_attention kernel rejects
4183+ # non-equal head counts at runtime, so no valid reference output exists for comparison.
4184+ # In practice, GQA models on CUDA dispatch to _scaled_dot_product_flash_attention (FP16/BF16)
4185+ # or decompose into matmul+_safe_softmax (FP32) — this op never appears with GQA shapes in
4186+ # a real FX graph. GQA is handled by the flash attention validator instead.
4187+ #
4188+ # Note2: IAttentionLayer does support decode-phase (seq_q != seq_k), so only the
4189+ # sequence dimension is skipped in the shape check below.
4190+ seq_len_dim = len (query_shape ) - 2
4191+ if any (
4192+ query_shape [i ] != key_shape [i ]
4193+ for i in range (len (query_shape ))
4194+ if i != seq_len_dim # skip the seq_len dim
4195+ ):
4196+ _LOGGER .debug (
4197+ "query and key have incompatible shapes (batch, num_heads, or head_dim mismatch). Please try setting decompose_attention=True in the compilation settings."
4198+ )
4199+ return False
4200+ return True
40884201
40894202
40904203@dynamo_tensorrt_converter (
0 commit comments