1212import numpy as np
1313
1414from superbench .common .utils import logger
15- from superbench .benchmarks import BenchmarkRegistry , Platform , Precision
15+ from superbench .benchmarks import BenchmarkRegistry , Platform , Precision , ReturnCode
1616from superbench .benchmarks .micro_benchmarks import MicroBenchmark
1717from superbench .benchmarks .micro_benchmarks .model_source_config import ModelSourceConfig
18- from superbench .benchmarks .micro_benchmarks .huggingface_model_loader import HuggingFaceModelLoader
18+ from superbench .benchmarks .micro_benchmarks .huggingface_model_loader import (
19+ HuggingFaceModelLoader ,
20+ validate_model_identifier ,
21+ )
1922
2023
2124class ORTInferenceBenchmark (MicroBenchmark ):
@@ -42,6 +45,9 @@ def __init__(self, name, parameters=''):
4245 ]
4346 self .__graph_opt_level = None
4447 self .__model_cache_path = Path (torch .hub .get_dir ()) / 'checkpoints'
48+ # Stashed HF config (populated in _preprocess_huggingface_models) so that
49+ # __inference() can derive vocab_size / dynamic input shapes from it.
50+ self ._hf_config = None
4551
4652 def add_parser_arguments (self ):
4753 """Add the specified arguments."""
@@ -124,6 +130,24 @@ def add_parser_arguments(self):
124130 help = 'Sequence length for transformer models.' ,
125131 )
126132
133+ self ._parser .add_argument (
134+ '--require_cuda' ,
135+ action = 'store_true' ,
136+ default = False ,
137+ required = False ,
138+ help = 'Fail if CUDAExecutionProvider is not available. '
139+ 'Default: warn and fall back to other registered ORT providers (CPU/ROCm/etc.).' ,
140+ )
141+
142+ self ._parser .add_argument (
143+ '--allow_remote_code' ,
144+ action = 'store_true' ,
145+ default = False ,
146+ required = False ,
147+ help = 'Allow HuggingFace to execute model-repo Python (trust_remote_code=True). '
148+ 'SECURITY: enables RCE from --model_identifier. Pin --revision <sha> when used.' ,
149+ )
150+
127151 def _preprocess (self ):
128152 """Preprocess/preparation operations before the benchmarking.
129153
@@ -179,8 +203,19 @@ def _preprocess_huggingface_models(self):
179203
180204 if not self ._args .model_identifier :
181205 logger .error ('--model_identifier is required when using --model_source huggingface' )
206+ self ._result .set_return_code (ReturnCode .MICROBENCHMARK_EXECUTION_FAILURE )
182207 return False
183208
209+ # Reject malformed / path-like identifiers up front, before any network or disk activity.
210+ try :
211+ validate_model_identifier (self ._args .model_identifier )
212+ except ValueError as e :
213+ logger .error (str (e ))
214+ self ._result .set_return_code (ReturnCode .MICROBENCHMARK_EXECUTION_FAILURE )
215+ return False
216+
217+ allow_remote_code = bool (getattr (self ._args , 'allow_remote_code' , False ))
218+
184219 try :
185220 logger .info (f'Loading HuggingFace model: { self ._args .model_identifier } ' )
186221
@@ -190,13 +225,18 @@ def _preprocess_huggingface_models(self):
190225 load_kwargs = {}
191226 if hf_token :
192227 load_kwargs ['token' ] = hf_token
193- hf_config = AutoConfig .from_pretrained (self ._args .model_identifier , trust_remote_code = True , ** load_kwargs )
228+ hf_config = AutoConfig .from_pretrained (
229+ self ._args .model_identifier , trust_remote_code = allow_remote_code , ** load_kwargs
230+ )
231+ # Stash for __inference() to read vocab_size / other model metadata later.
232+ self ._hf_config = hf_config
194233
195234 precision_str = self ._args .precision .value if self ._args .precision != Precision .INT8 else 'float32'
196235 fits , param_m , est_gb , avail_gb = HuggingFaceModelLoader .check_memory_fits (
197236 self ._args .model_identifier , hf_config , precision_str , mode = 'inference' , token = hf_token
198237 )
199238 if not fits :
239+ self ._result .set_return_code (ReturnCode .MICROBENCHMARK_EXECUTION_FAILURE )
200240 return False
201241
202242 # Step 2: Proceed with model download and ONNX export
@@ -217,7 +257,7 @@ def _preprocess_huggingface_models(self):
217257 )
218258
219259 # Load model from HuggingFace on CPU
220- loader = HuggingFaceModelLoader ()
260+ loader = HuggingFaceModelLoader (allow_remote_code = allow_remote_code )
221261 hf_model , _ , _ = loader .load_model_from_config (model_config , device = 'cpu' )
222262 from superbench .benchmarks .micro_benchmarks ._export_torch_to_onnx import torch2onnxExporter
223263 exporter = torch2onnxExporter ()
@@ -237,6 +277,15 @@ def _preprocess_huggingface_models(self):
237277 export_precision = self ._args .precision .value
238278 model_name_with_precision = f'{ model_name } .{ export_precision } '
239279
280+ # Defense-in-depth: confirm the resolved output path stays inside the rank
281+ # directory even though validate_model_identifier already rejected '..' / '\\'.
282+ proc_root = proc_output_path .resolve ()
283+ resolved_out = (proc_output_path / f'{ model_name_with_precision } .onnx' ).resolve ()
284+ if proc_root not in resolved_out .parents :
285+ logger .error (f'Refusing to write ONNX outside rank dir: { resolved_out } not under { proc_root } ' )
286+ self ._result .set_return_code (ReturnCode .MICROBENCHMARK_EXECUTION_FAILURE )
287+ return False
288+
240289 # Export directly to final destination to avoid path issues with external data
241290 onnx_path = exporter .export_huggingface_model (
242291 model = hf_model ,
@@ -248,6 +297,7 @@ def _preprocess_huggingface_models(self):
248297
249298 if not onnx_path :
250299 logger .error (f'Failed to export { self ._args .model_identifier } to ONNX' )
300+ self ._result .set_return_code (ReturnCode .MICROBENCHMARK_EXECUTION_FAILURE )
251301 return False
252302
253303 # Apply INT8 quantization if requested (matching in-house model behavior)
@@ -268,25 +318,32 @@ def _preprocess_huggingface_models(self):
268318 logger .error (f'Failed to prepare HuggingFace model: { str (e )} ' )
269319 import traceback
270320 logger .error (traceback .format_exc ())
321+ self ._result .set_return_code (ReturnCode .MICROBENCHMARK_EXECUTION_FAILURE )
271322 return False
272323
273324 def _benchmark (self ):
274325 """Implementation for benchmarking."""
275326 import onnxruntime as ort
276327 precision_metric = {'float16' : 'fp16' , 'float32' : 'fp32' , 'int8' : 'int8' }
277328
278- # Require CUDAExecutionProvider — this benchmark targets GPU inference
279329 available = ort .get_available_providers ()
280- if 'CUDAExecutionProvider' not in available :
281- logger .error (f'CUDAExecutionProvider is not available (available: { available } ).' )
282- return False
330+ cuda_available = 'CUDAExecutionProvider' in available
331+ if not cuda_available :
332+ msg = f'CUDAExecutionProvider is not available (available providers: { available } ).'
333+ if getattr (self ._args , 'require_cuda' , False ):
334+ logger .error (msg + ' --require_cuda was set, aborting.' )
335+ return False
336+ logger .warning (
337+ msg + ' Falling back to registered providers; pass --require_cuda to fail instead.'
338+ )
339+ providers = ['CUDAExecutionProvider' ] if cuda_available else available
283340
284341 for model in self ._args .pytorch_models :
285342 sess_options = ort .SessionOptions ()
286343 sess_options .graph_optimization_level = self .__graph_opt_level [self ._args .graph_opt_level ]
287344 file_name = '{model}.{precision}.onnx' .format (model = model , precision = self ._args .precision )
288345 ort_sess = ort .InferenceSession (
289- f'{ self .__model_cache_path / file_name } ' , sess_options , providers = [ 'CUDAExecutionProvider' ]
346+ f'{ self .__model_cache_path / file_name } ' , sess_options , providers = providers
290347 )
291348
292349 elapse_times = self .__inference (ort_sess )
@@ -318,18 +375,30 @@ def __inference(self, ort_sess):
318375 """
319376 precision = np .float16 if self ._args .precision == Precision .FLOAT16 else np .float32
320377
321- # Get input names from the ONNX session to determine input format
322- input_names = [input .name for input in ort_sess .get_inputs ()]
378+ # Get input metadata from the ONNX session to determine input format and shapes
379+ ort_inputs = ort_sess .get_inputs ()
380+ input_names = [inp .name for inp in ort_inputs ]
323381
324382 # Determine input format based on what the model expects
325383 if 'pixel_values' in input_names :
326- # Vision model: use pixel_values (batch_size, 3, 224, 224)
327- pixel_values = np .random .randn (self ._args .batch_size , 3 , 224 , 224 ).astype (dtype = precision )
384+ # Vision model: derive (C, H, W) from the exported ONNX graph so that models
385+ # with non-default shapes (e.g. 384x384 ViT, 1-channel medical models) work.
386+ # Fall back to (3, 224, 224) only for dynamic / unknown axes.
387+ meta = next (inp for inp in ort_inputs if inp .name == 'pixel_values' )
388+ dims = [d if isinstance (d , int ) else None for d in (meta .shape or [])]
389+ # Expected layout is (N, C, H, W); pad to length 4 if shorter.
390+ dims = (dims + [None ] * 4 )[:4 ]
391+ _ , c , h , w = dims
392+ c , h , w = c or 3 , h or 224 , w or 224
393+ pixel_values = np .random .randn (self ._args .batch_size , c , h , w ).astype (dtype = precision )
328394 inputs = {'pixel_values' : pixel_values }
329395 elif 'input_ids' in input_names :
330- # NLP model: use input_ids and attention_mask
396+ # NLP model: use input_ids and attention_mask. Cap token IDs at the model's
397+ # actual vocab_size to avoid out-of-range embedding lookups (undefined behavior
398+ # on CUDA — silent NaNs / device-side asserts).
331399 seq_len = getattr (self ._args , 'seq_length' , 512 )
332- input_ids = np .random .randint (0 , 30000 , (self ._args .batch_size , seq_len )).astype (np .int64 )
400+ vocab_size = getattr (self ._hf_config , 'vocab_size' , None ) or 30000
401+ input_ids = np .random .randint (0 , vocab_size , (self ._args .batch_size , seq_len )).astype (np .int64 )
333402 attention_mask = np .ones ((self ._args .batch_size , seq_len ), dtype = np .int64 )
334403 inputs = {'input_ids' : input_ids , 'attention_mask' : attention_mask }
335404 else :
0 commit comments