1- # Copyright 2023-2025 , NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+ # Copyright 2023-2026 , NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22#
33# Redistribution and use in source and binary forms, with or without
44# modification, are permitted provided that the following conditions
@@ -323,13 +323,23 @@ def __generate_vllm_model(self, huggingface_id: str):
323323
324324 def __generate_ngc_model (self , name : str , source : str ):
325325 engines_path = ENGINE_DEST_PATH + "/" + source
326+ # Find the actual engine directory that contains config.json
327+ actual_engine_dir = self .__find_engine_directory (engines_path )
326328 parse_and_substitute (
327- str (self .repo ), name , engines_path , engines_path , "auto" , dry_run = False
329+ str (self .repo ),
330+ name ,
331+ actual_engine_dir ,
332+ actual_engine_dir ,
333+ "auto" ,
334+ dry_run = False ,
328335 )
329336
330337 def __generate_trtllm_model (self , name : str , huggingface_id : str ):
331338 engines_path = ENGINE_DEST_PATH + "/" + name
332- engines = [engine for engine in Path (engines_path ).glob ("*.engine" )]
339+ # Search for engine files recursively since they might be in subdirectories
340+ engines = list (Path (engines_path ).glob ("*.engine" )) + list (
341+ Path (engines_path ).glob ("*/*.engine" )
342+ )
333343 if engines :
334344 logger .warning (
335345 f"Found existing engine(s) at { engines_path } , skipping build."
@@ -343,21 +353,64 @@ def __generate_trtllm_model(self, name: str, huggingface_id: str):
343353 p .start ()
344354 p .join ()
345355
356+ # Find the actual engine directory that contains config.json
357+ # When using workspace parameter, TRT-LLM creates a subdirectory
358+ actual_engine_dir = self .__find_engine_directory (engines_path )
359+
346360 # NOTE: In every case, the TRT LLM template should be filled in with values.
347361 # If the model exists, the CLI will raise an exception when creating the model repo.
348362 # If a user clears the model repo, they won't need to re-build the engines,
349363 # but they will still need to modify the TRT LLM template.
350364 parse_and_substitute (
351365 triton_model_dir = str (self .repo ),
352366 bls_model_name = name ,
353- engine_dir = engines_path ,
354- token_dir = engines_path ,
367+ engine_dir = actual_engine_dir ,
368+ token_dir = actual_engine_dir ,
355369 token_type = "auto" ,
356370 dry_run = False ,
357371 )
358372
373+ def __find_engine_directory (self , workspace_path : str ) -> str :
374+ """
375+ Find the actual engine directory that contains config.json.
376+ When using the workspace parameter, TRT-LLM creates a subdirectory structure.
377+ This method searches for config.json and returns its parent directory.
378+ """
379+ workspace_path = Path (workspace_path )
380+
381+ # First check if config.json exists directly in the workspace path
382+ if (workspace_path / "config.json" ).exists ():
383+ return str (workspace_path )
384+
385+ # Search for config.json in subdirectories (up to 2 levels deep)
386+ for config_file in workspace_path .glob ("*/config.json" ):
387+ logger .info (f"Found engine directory at { config_file .parent } " )
388+ return str (config_file .parent )
389+
390+ for config_file in workspace_path .glob ("*/*/config.json" ):
391+ logger .info (f"Found engine directory at { config_file .parent } " )
392+ return str (config_file .parent )
393+
394+ # If no config.json found, return the original path and let the error surface
395+ logger .warning (
396+ f"Could not find config.json in { workspace_path } or its subdirectories. "
397+ f"Returning original path."
398+ )
399+ return str (workspace_path )
400+
359401 def __build_trtllm_engine (self , huggingface_id : str , engines_path : Path ):
360- from tensorrt_llm import LLM , BuildConfig
402+ # Ensure engines_path is a Path object
403+ engines_path = Path (engines_path )
404+
405+ # Import from _tensorrt_engine to force TensorRT backend (not PyTorch)
406+ # The PyTorch backend doesn't support workspace parameter
407+ try :
408+ from tensorrt_llm ._tensorrt_engine import LLM
409+ except ImportError :
410+ # Fallback to regular import for newer versions
411+ from tensorrt_llm import LLM
412+
413+ from tensorrt_llm import BuildConfig
361414
362415 # NOTE: Given config.json, can read from 'build_config' section and from_dict
363416 config = BuildConfig ()
@@ -367,13 +420,31 @@ def __build_trtllm_engine(self, huggingface_id: str, engines_path: Path):
367420 # config.max_seq_len = 8192
368421 # config.max_batch_size = 256
369422
370- engine = LLM (huggingface_id , build_config = config )
371- # TODO: Investigate if LLM is internally saving a copy to a temp dir
372- engine .save (str (engines_path ))
423+ # Create the workspace directory if it doesn't exist
424+ # TensorRT-LLM will create a temp subdir inside this workspace
425+ engines_path .mkdir (parents = True , exist_ok = True )
426+
427+ # Build engine to target directory using workspace parameter (TensorRT backend only)
428+ engine = LLM (huggingface_id , build_config = config , workspace = str (engines_path ))
429+
430+ # For newer API versions with save() method, call it to ensure engine is properly saved
431+ # In older versions, the workspace parameter should have already placed the engine there
432+ if hasattr (engine , "save" ) and callable (getattr (engine , "save" )):
433+ try :
434+ engine .save (str (engines_path ))
435+ logger .debug (
436+ f"Called save() method to ensure engine is at { engines_path } "
437+ )
438+ except Exception as e :
439+ # If save fails, workspace parameter should have already placed it correctly
440+ logger .debug (
441+ f"save() call failed (engine may already be in workspace): { e } "
442+ )
373443
374444 # The new trtllm(v0.17.0+) requires explicit calling shutdown to shutdown
375445 # the mpi blocking thread, or the engine process won't exit
376- engine .shutdown ()
446+ if hasattr (engine , "shutdown" ) and callable (getattr (engine , "shutdown" )):
447+ engine .shutdown ()
377448
378449 def __create_model_repository (
379450 self , name : str , version : int = 1 , backend : str = None
0 commit comments