1818
1919class torch2onnxExporter ():
2020 """PyTorch model to ONNX exporter."""
21+
2122 def __init__ (self ):
2223 """Constructor."""
2324 from transformers import BertConfig , GPT2Config , LlamaConfig
@@ -314,95 +315,15 @@ def export_huggingface_model(self, model, model_name, batch_size=1, seq_length=5
314315 is_vision_model = main_input == 'pixel_values'
315316
316317 if is_vision_model :
317- # Vision models: use pixel_values (batch_size, channels, height, width)
318- # Derive C/H/W from model config rather than hard-coding 3x224x224
319- num_channels = getattr (model .config , 'num_channels' , 3 )
320- image_size = getattr (model .config , 'image_size' , 224 )
321- if isinstance (image_size , (list , tuple )):
322- img_h , img_w = image_size [0 ], image_size [1 ]
323- else :
324- img_h , img_w = image_size , image_size
325-
326- dummy_input = torch .randn (batch_size , num_channels , img_h , img_w , dtype = model_dtype , device = device )
327- input_names = ['pixel_values' ]
328- dynamic_axes = {'pixel_values' : {0 : 'batch_size' }, 'output' : {0 : 'batch_size' }}
329-
330- # Wrapper for vision models
331- class VisionModelWrapper (torch .nn .Module ):
332- def __init__ (self , model ):
333- super ().__init__ ()
334- self .model = model
335-
336- def forward (self , pixel_values ):
337- outputs = self .model (pixel_values = pixel_values )
338- if hasattr (outputs , 'logits' ):
339- return outputs .logits
340- elif hasattr (outputs , 'last_hidden_state' ):
341- return outputs .last_hidden_state
342- else :
343- return outputs [0 ] if isinstance (outputs , (tuple , list )) else outputs
344-
345- wrapped_model = VisionModelWrapper (model )
346- export_args = (dummy_input , )
318+ wrapped_model , export_args , input_names , dynamic_axes = self ._build_vision_export_inputs (
319+ model , batch_size , model_dtype , device
320+ )
347321 else :
348- # NLP models: use input_ids and attention_mask
349- dummy_input = torch .ones ((batch_size , seq_length ), dtype = torch .int64 , device = device )
350- attention_mask = torch .ones ((batch_size , seq_length ), dtype = torch .int64 , device = device )
351- input_names = ['input_ids' , 'attention_mask' ]
352- dynamic_axes = {
353- 'input_ids' : {
354- 0 : 'batch_size' ,
355- 1 : 'seq_length'
356- },
357- 'attention_mask' : {
358- 0 : 'batch_size' ,
359- 1 : 'seq_length'
360- },
361- 'output' : {
362- 0 : 'batch_size' ,
363- 1 : 'seq_length'
364- },
365- }
322+ wrapped_model , export_args , input_names , dynamic_axes = self ._build_nlp_export_inputs (
323+ model , batch_size , seq_length , device
324+ )
366325
367- # Wrapper for NLP models
368- class NLPModelWrapper (torch .nn .Module ):
369- def __init__ (self , model ):
370- super ().__init__ ()
371- self .model = model
372-
373- def forward (self , input_ids , attention_mask ):
374- outputs = self .model (input_ids = input_ids , attention_mask = attention_mask )
375- if hasattr (outputs , 'logits' ):
376- return outputs .logits
377- elif hasattr (outputs , 'last_hidden_state' ):
378- return outputs .last_hidden_state
379- else :
380- return outputs [0 ] if isinstance (outputs , (tuple , list )) else outputs
381-
382- wrapped_model = NLPModelWrapper (model )
383- export_args = (dummy_input , attention_mask )
384-
385- # Export to ONNX for large models (>2GB), use external data format
386- model_size_gb = sum (p .numel () * p .element_size () for p in model .parameters ()) / (1024 ** 3 )
387- use_external_data = model_size_gb > 2.0
388-
389- if use_external_data :
390- logger .info (f'Model size is { model_size_gb :.2f} GB, using external data format for ONNX export' )
391-
392- export_kwargs = {
393- 'opset_version' : 14 ,
394- 'do_constant_folding' : True ,
395- 'input_names' : input_names ,
396- 'output_names' : ['output' ],
397- 'dynamic_axes' : dynamic_axes ,
398- }
399- if use_external_data :
400- # PyTorch 2.8+ renamed 'use_external_data_format' to 'external_data'
401- sig = inspect .signature (torch .onnx .export )
402- if 'external_data' in sig .parameters :
403- export_kwargs ['external_data' ] = True
404- else :
405- export_kwargs ['use_external_data_format' ] = True
326+ export_kwargs = self ._build_onnx_export_kwargs (model , input_names , dynamic_axes )
406327
407328 torch .onnx .export (
408329 wrapped_model ,
@@ -412,7 +333,7 @@ def forward(self, input_ids, attention_mask):
412333 )
413334
414335 # Clean up
415- del dummy_input
336+ del export_args
416337 if torch .cuda .is_available ():
417338 torch .cuda .empty_cache ()
418339
@@ -422,3 +343,97 @@ def forward(self, input_ids, attention_mask):
422343 logger .error (f'Failed to export HuggingFace model to ONNX: { str (e )} ' )
423344 logger .error (traceback .format_exc ())
424345 return ''
346+
347+ def _build_vision_export_inputs (self , model , batch_size , model_dtype , device ):
348+ """Build the dummy inputs and wrapper module for exporting a vision HuggingFace model."""
349+ # Vision models: use pixel_values (batch_size, channels, height, width)
350+ # Derive C/H/W from model config rather than hard-coding 3x224x224
351+ num_channels = getattr (model .config , 'num_channels' , 3 )
352+ image_size = getattr (model .config , 'image_size' , 224 )
353+ if isinstance (image_size , (list , tuple )):
354+ img_h , img_w = image_size [0 ], image_size [1 ]
355+ else :
356+ img_h , img_w = image_size , image_size
357+
358+ dummy_input = torch .randn (batch_size , num_channels , img_h , img_w , dtype = model_dtype , device = device )
359+ input_names = ['pixel_values' ]
360+ dynamic_axes = {'pixel_values' : {0 : 'batch_size' }, 'output' : {0 : 'batch_size' }}
361+
362+ class VisionModelWrapper (torch .nn .Module ):
363+
364+ def __init__ (self , model ):
365+ super ().__init__ ()
366+ self .model = model
367+
368+ def forward (self , pixel_values ):
369+ outputs = self .model (pixel_values = pixel_values )
370+ if hasattr (outputs , 'logits' ):
371+ return outputs .logits
372+ elif hasattr (outputs , 'last_hidden_state' ):
373+ return outputs .last_hidden_state
374+ else :
375+ return outputs [0 ] if isinstance (outputs , (tuple , list )) else outputs
376+
377+ return VisionModelWrapper (model ), (dummy_input , ), input_names , dynamic_axes
378+
379+ def _build_nlp_export_inputs (self , model , batch_size , seq_length , device ):
380+ """Build the dummy inputs and wrapper module for exporting an NLP HuggingFace model."""
381+ # NLP models: use input_ids and attention_mask
382+ dummy_input = torch .ones ((batch_size , seq_length ), dtype = torch .int64 , device = device )
383+ attention_mask = torch .ones ((batch_size , seq_length ), dtype = torch .int64 , device = device )
384+ input_names = ['input_ids' , 'attention_mask' ]
385+ dynamic_axes = {
386+ 'input_ids' : {
387+ 0 : 'batch_size' ,
388+ 1 : 'seq_length'
389+ },
390+ 'attention_mask' : {
391+ 0 : 'batch_size' ,
392+ 1 : 'seq_length'
393+ },
394+ 'output' : {
395+ 0 : 'batch_size' ,
396+ 1 : 'seq_length'
397+ },
398+ }
399+
400+ class NLPModelWrapper (torch .nn .Module ):
401+
402+ def __init__ (self , model ):
403+ super ().__init__ ()
404+ self .model = model
405+
406+ def forward (self , input_ids , attention_mask ):
407+ outputs = self .model (input_ids = input_ids , attention_mask = attention_mask )
408+ if hasattr (outputs , 'logits' ):
409+ return outputs .logits
410+ elif hasattr (outputs , 'last_hidden_state' ):
411+ return outputs .last_hidden_state
412+ else :
413+ return outputs [0 ] if isinstance (outputs , (tuple , list )) else outputs
414+
415+ return NLPModelWrapper (model ), (dummy_input , attention_mask ), input_names , dynamic_axes
416+
417+ def _build_onnx_export_kwargs (self , model , input_names , dynamic_axes ):
418+ """Assemble torch.onnx.export kwargs, enabling external-data format for >2GB models."""
419+ model_size_gb = sum (p .numel () * p .element_size () for p in model .parameters ()) / (1024 ** 3 )
420+ use_external_data = model_size_gb > 2.0
421+
422+ if use_external_data :
423+ logger .info (f'Model size is { model_size_gb :.2f} GB, using external data format for ONNX export' )
424+
425+ export_kwargs = {
426+ 'opset_version' : 14 ,
427+ 'do_constant_folding' : True ,
428+ 'input_names' : input_names ,
429+ 'output_names' : ['output' ],
430+ 'dynamic_axes' : dynamic_axes ,
431+ }
432+ if use_external_data :
433+ # PyTorch 2.8+ renamed 'use_external_data_format' to 'external_data'
434+ sig = inspect .signature (torch .onnx .export )
435+ if 'external_data' in sig .parameters :
436+ export_kwargs ['external_data' ] = True
437+ else :
438+ export_kwargs ['use_external_data_format' ] = True
439+ return export_kwargs
0 commit comments