Skip to content

Commit 54e4153

Browse files
Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 864a8e9 commit 54e4153

4 files changed

Lines changed: 159 additions & 130 deletions

File tree

superbench/benchmarks/micro_benchmarks/_export_torch_to_onnx.py

Lines changed: 103 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
class 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

superbench/benchmarks/micro_benchmarks/huggingface_model_loader.py

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class HuggingFaceModelLoader:
8585
``False``; enabling this turns ``--model_identifier`` into an RCE
8686
sink, so it is opt-in only.
8787
"""
88+
8889
def __init__(
8990
self,
9091
cache_dir: Optional[str] = None,
@@ -150,19 +151,7 @@ def load_model(
150151
validate_model_identifier(model_identifier)
151152

152153
try:
153-
# Convert torch_dtype string to torch dtype
154-
dtype = self._get_torch_dtype(torch_dtype) if torch_dtype else None
155-
156-
# Prepare loading kwargs
157-
load_kwargs = {'cache_dir': self.cache_dir, 'revision': revision, **kwargs}
158-
159-
# Add token if available
160-
if self.token:
161-
load_kwargs['token'] = self.token
162-
163-
# Add dtype if specified
164-
if dtype:
165-
load_kwargs['torch_dtype'] = dtype
154+
load_kwargs = self._build_load_kwargs(torch_dtype, revision, kwargs)
166155

167156
# Load config (use pre-downloaded config if provided)
168157
if config is None:
@@ -173,35 +162,11 @@ def load_model(
173162
else:
174163
logger.info('Using pre-downloaded model configuration.')
175164

176-
# Load tokenizer (may fail for some models, that's ok)
177-
tokenizer = None
178-
try:
179-
logger.info('Loading tokenizer...')
180-
tokenizer = AutoTokenizer.from_pretrained(
181-
model_identifier, trust_remote_code=self.allow_remote_code, **load_kwargs
182-
)
183-
except Exception as e:
184-
logger.warning(f'Could not load tokenizer: {e}. Continuing without tokenizer.')
165+
tokenizer = self._try_load_tokenizer(model_identifier, load_kwargs)
185166

186167
# Load model
187168
logger.info(f'Loading model weights (dtype={torch_dtype}, device={device})...')
188-
model_kwargs = load_kwargs.copy()
189-
model_kwargs['trust_remote_code'] = self.allow_remote_code
190-
191-
# Handle device mapping for large models
192-
effective_device_map = device_map
193-
if device_map:
194-
model_kwargs['device_map'] = device_map
195-
elif device == 'cuda' and torch.cuda.is_available():
196-
# Don't set device_map if device is explicitly cuda
197-
pass
198-
elif device != 'cpu':
199-
model_kwargs['device_map'] = device
200-
effective_device_map = device
201-
202-
# Pass pre-downloaded config to from_pretrained so any overrides take effect
203-
if config is not None:
204-
model_kwargs['config'] = config
169+
model_kwargs, effective_device_map = self._build_model_kwargs(load_kwargs, device, device_map, config)
205170

206171
try:
207172
model = AutoModel.from_pretrained(model_identifier, **model_kwargs)
@@ -230,6 +195,52 @@ def load_model(
230195
except Exception as e:
231196
raise ModelLoadError(f"Unexpected error loading model '{model_identifier}': {e}") from e
232197

198+
def _build_load_kwargs(self, torch_dtype, revision, extra_kwargs):
199+
"""Assemble the base ``from_pretrained`` kwargs (cache_dir, token, dtype, revision)."""
200+
dtype = self._get_torch_dtype(torch_dtype) if torch_dtype else None
201+
load_kwargs = {'cache_dir': self.cache_dir, 'revision': revision, **extra_kwargs}
202+
if self.token:
203+
load_kwargs['token'] = self.token
204+
if dtype:
205+
load_kwargs['torch_dtype'] = dtype
206+
return load_kwargs
207+
208+
def _try_load_tokenizer(self, model_identifier, load_kwargs):
209+
"""Attempt to load a tokenizer; return None if the model has no associated tokenizer."""
210+
try:
211+
logger.info('Loading tokenizer...')
212+
return AutoTokenizer.from_pretrained(
213+
model_identifier, trust_remote_code=self.allow_remote_code, **load_kwargs
214+
)
215+
except Exception as e:
216+
logger.warning(f'Could not load tokenizer: {e}. Continuing without tokenizer.')
217+
return None
218+
219+
def _build_model_kwargs(self, load_kwargs, device, device_map, config):
220+
"""Build model-loading kwargs and resolve the effective device_map.
221+
222+
Returns:
223+
Tuple[dict, Optional[str]]: ``(model_kwargs, effective_device_map)``.
224+
"""
225+
model_kwargs = load_kwargs.copy()
226+
model_kwargs['trust_remote_code'] = self.allow_remote_code
227+
228+
effective_device_map = device_map
229+
if device_map:
230+
model_kwargs['device_map'] = device_map
231+
elif device == 'cuda' and torch.cuda.is_available():
232+
# Don't set device_map if device is explicitly cuda
233+
pass
234+
elif device != 'cpu':
235+
model_kwargs['device_map'] = device
236+
effective_device_map = device
237+
238+
# Pass pre-downloaded config to from_pretrained so any overrides take effect
239+
if config is not None:
240+
model_kwargs['config'] = config
241+
242+
return model_kwargs, effective_device_map
243+
233244
def load_model_from_config(
234245
self,
235246
config: ModelSourceConfig,

superbench/benchmarks/micro_benchmarks/ort_inference_performance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def _preprocess_huggingface_models(self):
232232
self._hf_config = hf_config
233233

234234
precision_str = self._args.precision.value if self._args.precision != Precision.INT8 else 'float32'
235-
fits, param_m, est_gb, avail_gb = HuggingFaceModelLoader.check_memory_fits(
235+
fits, _, _, _ = HuggingFaceModelLoader.check_memory_fits(
236236
self._args.model_identifier, hf_config, precision_str, mode='inference', token=hf_token
237237
)
238238
if not fits:

tests/benchmarks/micro_benchmarks/test_huggingface_e2e.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
pytest.importorskip('transformers')
1818

19-
from superbench.benchmarks.micro_benchmarks.huggingface_model_loader import HuggingFaceModelLoader
20-
from superbench.benchmarks.micro_benchmarks.model_source_config import ModelSourceConfig
19+
# Imports below this point depend on `transformers` being available, so they
20+
# must be deferred until after the `importorskip` call above.
21+
from superbench.benchmarks.micro_benchmarks.huggingface_model_loader import HuggingFaceModelLoader # noqa: E402
22+
from superbench.benchmarks.micro_benchmarks.model_source_config import ModelSourceConfig # noqa: E402
2123

2224

2325
@pytest.mark.skipif(
@@ -26,6 +28,7 @@
2628
)
2729
class TestHuggingFaceE2E:
2830
"""End-to-end tests for HuggingFace model loading."""
31+
2932
@pytest.fixture
3033
def loader(self, tmp_path):
3134
"""Create a loader instance with an isolated per-test cache dir."""

0 commit comments

Comments
 (0)