@@ -49,12 +49,27 @@ def _parse_args() -> argparse.Namespace:
4949 help = "Pass enable_thinking=False to the tokenizer chat template." ,
5050 )
5151 parser .add_argument ("--device" , default = "cuda" , help = "Torch device used for inference." )
52+ parser .add_argument (
53+ "--device-map" ,
54+ choices = ("auto" , "balanced" , "balanced_low_0" , "sequential" ),
55+ help = "Optional Hugging Face device-map strategy for sharded model loading." ,
56+ )
5257 parser .add_argument (
5358 "--dtype" ,
5459 choices = ("bfloat16" , "float16" , "float32" ),
5560 default = "bfloat16" ,
5661 help = "Model loading dtype." ,
5762 )
63+ parser .add_argument (
64+ "--autocast" ,
65+ action = "store_true" ,
66+ help = "Run generation under autocast using the model loading dtype." ,
67+ )
68+ parser .add_argument (
69+ "--require-gpu-only" ,
70+ action = "store_true" ,
71+ help = "Fail if any model shard is placed on CPU or disk." ,
72+ )
5873 args = parser .parse_args ()
5974 if args .max_new_tokens <= 0 :
6075 parser .error ("--max-new-tokens must be positive" )
@@ -122,6 +137,23 @@ def _validate_loading_info(loading_info: dict[str, Any]) -> None:
122137 raise RuntimeError (f"Exported Hugging Face checkpoint did not reload strictly: { details } " )
123138
124139
140+ def _validate_gpu_only_placement (model : Any ) -> None :
141+ """Require every model shard to be resident on CUDA devices."""
142+ device_map = getattr (model , "hf_device_map" , None )
143+ if device_map is None :
144+ if getattr (model .device , "type" , str (model .device ).split (":" , 1 )[0 ]) != "cuda" :
145+ raise RuntimeError (f"Model is not GPU-only: device={ model .device } " )
146+ return
147+
148+ non_gpu = {
149+ name : str (device )
150+ for name , device in device_map .items ()
151+ if not isinstance (device , int ) and str (device ).split (":" , 1 )[0 ] != "cuda"
152+ }
153+ if non_gpu :
154+ raise RuntimeError (f"Model has non-GPU placements: { non_gpu } " )
155+
156+
125157def _load_runtime (args : argparse .Namespace ) -> tuple [Any , Any , Any ]:
126158 """Load torch, the selected HF auto-model, and its tokenizer or processor."""
127159 import torch
@@ -137,27 +169,53 @@ def _load_runtime(args: argparse.Namespace) -> tuple[Any, Any, Any]:
137169
138170 processor = AutoTokenizer .from_pretrained (args .hf_model , trust_remote_code = args .trust_remote_code )
139171 model_cls = AutoModelForCausalLM
140- model , loading_info = model_cls .from_pretrained (
141- args .hf_model ,
142- dtype = dtype ,
143- trust_remote_code = args .trust_remote_code ,
144- output_loading_info = True ,
145- )
172+ model_kwargs = {
173+ "dtype" : dtype ,
174+ "trust_remote_code" : args .trust_remote_code ,
175+ "output_loading_info" : True ,
176+ }
177+ if args .device_map :
178+ model_kwargs ["device_map" ] = args .device_map
179+ model , loading_info = model_cls .from_pretrained (args .hf_model , ** model_kwargs )
146180 _validate_loading_info (loading_info )
147- model = model .to (args .device ).eval ()
181+ if not args .device_map :
182+ model = model .to (args .device )
183+ model = model .eval ()
184+ if args .require_gpu_only :
185+ _validate_gpu_only_placement (model )
186+ LOG .info ("Strict HF reload complete (%d model modules)" , sum (1 for _ in model .modules ()))
148187 return torch , model , processor
149188
150189
190+ def _model_input_device (model : Any ) -> Any :
191+ """Return the device holding the model's input embeddings."""
192+ get_input_embeddings = getattr (model , "get_input_embeddings" , None )
193+ if get_input_embeddings is not None :
194+ embeddings = get_input_embeddings ()
195+ weight = getattr (embeddings , "weight" , None )
196+ if weight is not None and getattr (weight .device , "type" , None ) != "meta" :
197+ return weight .device
198+ return model .device
199+
200+
151201def main () -> int :
152202 """Run one bounded greedy generation and print its completion."""
153203 args = _parse_args ()
154204 torch , model , processor = _load_runtime (args )
155205 tokenizer = getattr (processor , "tokenizer" , processor )
156- inputs = _prepare_inputs (processor , args ).to (model .device )
206+ input_device = _model_input_device (model )
207+ inputs = _prepare_inputs (processor , args ).to (input_device )
157208 prompt_length = inputs ["input_ids" ].shape [1 ]
158209 pad_token_id = tokenizer .pad_token_id if tokenizer .pad_token_id is not None else tokenizer .eos_token_id
159210
160- with torch .inference_mode ():
211+ with (
212+ torch .inference_mode (),
213+ torch .autocast (
214+ device_type = torch .device (input_device ).type ,
215+ dtype = getattr (torch , args .dtype ),
216+ enabled = args .autocast ,
217+ ),
218+ ):
161219 output = model .generate (
162220 ** inputs ,
163221 do_sample = False ,
0 commit comments