@@ -63,22 +63,26 @@ def forward(self, features):
6363class Encode (nn .Module ):
6464 """Adapt the stateless Transformers network to the native streaming ABI."""
6565
66- def __init__ (self , model ):
66+ def __init__ (self , model , * , expand_attention_mask = False ):
6767 super ().__init__ ()
6868 self .model = model .model
6969 self .classifier = model .classifier
7070 self .dtype = model .dtype
71+ self .expand_attention_mask = expand_attention_mask
7172
7273 def forward (self , embeddings , lengths ):
7374 positions = torch .arange (embeddings .shape [1 ], device = embeddings .device )
7475 mask = (positions [None , :] < lengths [:, None ])[:, None , None , :]
76+ if self .expand_attention_mask :
77+ # CUDA SDPA requires the full query dimension in the mask.
78+ mask = mask .expand (- 1 , - 1 , embeddings .shape [1 ], - 1 )
7579 encoded = self .model (
7680 inputs_embeds = embeddings .to (self .dtype ), attention_mask = mask
7781 )
7882 return self .classifier (encoded .last_hidden_state ).sigmoid ().float ()
7983
8084
81- def capture_model (model , feature_extractor ):
85+ def capture_model (model , feature_extractor , * , expand_attention_mask = False ):
8286 """Capture backend-independent programs; streaming state stays in the runner."""
8387 audio = model .config .audio_config
8488 head = model .config .head_config
@@ -145,7 +149,7 @@ def capture_model(model, feature_extractor):
145149 strict = False ,
146150 ),
147151 "encode" : export (
148- Encode (model ),
152+ Encode (model , expand_attention_mask = expand_attention_mask ),
149153 (
150154 torch .zeros (1 , 128 , audio .hidden_size ),
151155 torch .tensor ([128 ], dtype = torch .int64 ),
@@ -169,27 +173,49 @@ def export_model(
169173 from executorch .backends .mlx .partitioner import MLXPartitioner
170174 from executorch .backends .mlx .passes import get_default_passes
171175
172- partitioners = [MLXPartitioner ()]
176+ partitioners = { name : [MLXPartitioner ()] for name in ( "pre_encode" , "encode" )}
173177 passes = get_default_passes ()
174178 elif backend == "xnnpack" :
175- partitioners = [XnnpackPartitioner (enable_bf16 = dtype == torch .bfloat16 )]
179+ partitioners = {
180+ name : [XnnpackPartitioner (enable_bf16 = dtype == torch .bfloat16 )]
181+ for name in ("pre_encode" , "encode" )
182+ }
183+ elif backend == "cuda" :
184+ from executorch .backends .cuda .cuda_backend import CudaBackend
185+ from executorch .backends .cuda .cuda_partitioner import CudaPartitioner
186+ from executorch .exir .backend .compile_spec_schema import CompileSpec
187+
188+ # The custom Triton SDPA kernel supports BF16 only.
189+ triton_mode = b"ON" if dtype == torch .bfloat16 else b"OFF"
190+ partitioners = {
191+ name : [
192+ CudaPartitioner (
193+ [
194+ CudaBackend .generate_method_name_compile_spec (name ),
195+ CompileSpec ("triton_kernel_mode" , triton_mode ),
196+ ]
197+ )
198+ ]
199+ for name in ("pre_encode" , "encode" )
200+ }
176201 else :
177202 raise ValueError (f"Unknown backend: { backend } " )
203+ partitioners ["preprocessor" ] = [XnnpackPartitioner ()]
178204
179205 model = Nemotron3DiarizationForAudioFrameClassification .from_pretrained (
180206 model_id , revision = revision , dtype = dtype
181207 )
182208 processor = Nemotron3DiarizationProcessor .from_pretrained (
183209 model_id , revision = revision
184210 )
185- programs , metadata = capture_model (model , processor .feature_extractor )
211+ programs , metadata = capture_model (
212+ model ,
213+ processor .feature_extractor ,
214+ expand_attention_mask = backend == "cuda" ,
215+ )
186216 edge = to_edge_transform_and_lower (
187217 programs ,
188- partitioner = {
189- "preprocessor" : [XnnpackPartitioner ()],
190- "pre_encode" : partitioners ,
191- "encode" : partitioners ,
192- },
218+ partitioner = partitioners ,
193219 transform_passes = {
194220 "preprocessor" : [],
195221 "pre_encode" : passes ,
@@ -212,6 +238,9 @@ def export_model(
212238 with path .open ("wb" ) as output :
213239 program .write_to_file (output )
214240 print (f"Saved { path } ({ path .stat ().st_size / 2 ** 20 :.1f} MiB)" )
241+ if backend == "cuda" :
242+ program .write_tensor_data_to_file (str (output_dir ))
243+ print (f"Saved { output_dir / 'aoti_cuda_blob.ptd' } " )
215244 return path
216245
217246
@@ -223,7 +252,7 @@ def main():
223252 help = "Hugging Face model ID or local Transformers checkpoint directory" ,
224253 )
225254 parser .add_argument ("--revision" , help = "Hugging Face model revision" )
226- parser .add_argument ("--backend" , choices = ("mlx" , "xnnpack" ), default = "mlx" )
255+ parser .add_argument ("--backend" , choices = ("mlx" , "xnnpack" , "cuda" ), default = "mlx" )
227256 parser .add_argument ("--output-dir" , type = Path , default = Path ("nemotron_exports" ))
228257 parser .add_argument (
229258 "--dtype" ,
0 commit comments