1515from __future__ import annotations
1616
1717import dataclasses
18+ import logging
1819from functools import cached_property , partial
1920from pathlib import Path
2021from typing import TYPE_CHECKING , Any , Generic , Iterable , List , Optional , Type , TypeVar , Union
3031from megatron .core .transformer .module import MegatronModule
3132from megatron .core .transformer .transformer_config import MLATransformerConfig , TransformerConfig
3233from modelopt .torch .quantization .utils import is_quantized
34+ from safetensors .torch import save_file
3335from transformers .configuration_utils import PretrainedConfig
3436from typing_extensions import Unpack
3537
4749from megatron .bridge .models .model_provider import GetModelKwargs , ModelParallelKwargs , ModelProviderMixin
4850
4951
52+ logger = logging .getLogger (__name__ )
53+
5054MegatronModelT = TypeVar ("MegatronModelT" , bound = MegatronModule )
5155DataclassT = TypeVar ("DataclassT" )
5256
@@ -577,29 +581,65 @@ def save_hf_pretrained(
577581 saves the configuration files, while weight saving is coordinated
578582 across all ranks.
579583 """
580- if not isinstance (self .hf_pretrained , PreTrainedCausalLM ):
581- raise ValueError (
582- "save_hf_pretrained requires a pretrained HuggingFace model. "
583- "AutoBridge.from_hf_config() creates a config-only bridge; "
584- "use AutoBridge.from_hf_pretrained(...) instead."
585- )
584+ if not isinstance (self .hf_pretrained , (PreTrainedCausalLM , PretrainedConfig )):
585+ raise ValueError ("save_hf_pretrained requires a pretrained HuggingFace model or config." )
586+ is_config_only = isinstance (self .hf_pretrained , PretrainedConfig )
587+
588+ def _save_artifacts ():
589+ if is_config_only :
590+ import json
591+
592+ # Config-only path: write config.json and download modeling files from Hub.
593+ Path (path ).mkdir (parents = True , exist_ok = True )
594+ config_dict = getattr (self .hf_pretrained , "_bridge_original_dict" , None )
595+ if config_dict is None :
596+ config_dict = self .hf_pretrained .to_dict ()
597+ # Apply dtype/model_type fixups directly here
598+ if "model_type" in self .hf_pretrained .__dict__ :
599+ config_dict ["model_type" ] = self .hf_pretrained .__dict__ ["model_type" ]
600+ if "dtype" in config_dict and config_dict .get ("torch_dtype" ) is None :
601+ config_dict ["torch_dtype" ] = config_dict .pop ("dtype" )
602+
603+ with open (Path (path ) / "config.json" , "w" ) as _f :
604+ json .dump (config_dict , _f , indent = 2 , sort_keys = True , allow_nan = True )
605+
606+ # Download custom modeling files so the output is loadable with from_pretrained().
607+ hub_repo = getattr (self , "_auto_config_source_repo" , None )
608+ if hub_repo :
609+ try :
610+ from huggingface_hub import hf_hub_download , list_repo_files
611+
612+ repo_files = list_repo_files (hub_repo )
613+ py_files = [f for f in repo_files if f .endswith (".py" )]
614+ for py_file in py_files :
615+ hf_hub_download (
616+ repo_id = hub_repo ,
617+ filename = py_file ,
618+ local_dir = path ,
619+ )
620+ except Exception as exc :
621+ logger .error (
622+ "Could not download modeling files from %s: %s. "
623+ "The exported model may not be loadable with from_pretrained()." ,
624+ hub_repo ,
625+ exc ,
626+ )
586627
587- # Get bridge-level ADDITIONAL_FILE_PATTERNS if configured
588- additional_files = None
589- if hasattr (self ._model_bridge , "ADDITIONAL_FILE_PATTERNS" ) and self ._model_bridge .ADDITIONAL_FILE_PATTERNS :
590- additional_files = self ._model_bridge .ADDITIONAL_FILE_PATTERNS
591-
592- if dist .is_available () and dist .is_initialized ():
593- # Distributed training, only rank 0 saves artifacts
594- if dist .get_rank () == 0 :
628+ else :
629+ # Get bridge-level ADDITIONAL_FILE_PATTERNS if configured
630+ mb = self ._model_bridge
631+ additional_files = None
632+ if hasattr (mb , "ADDITIONAL_FILE_PATTERNS" ) and mb .ADDITIONAL_FILE_PATTERNS :
633+ additional_files = mb .ADDITIONAL_FILE_PATTERNS
595634 self .hf_pretrained .save_artifacts (
596635 path , original_source_path = source_path , additional_files = additional_files
597636 )
637+
638+ if dist .is_available () and dist .is_initialized ():
639+ if dist .get_rank () == 0 :
640+ _save_artifacts ()
598641 else :
599- # No distributed training, save artifacts
600- self .hf_pretrained .save_artifacts (
601- path , original_source_path = source_path , additional_files = additional_files
602- )
642+ _save_artifacts ()
603643
604644 self .save_hf_weights (
605645 model ,
@@ -701,7 +741,29 @@ def _filter_quant(gen):
701741 save_every_n_ranks = save_every_n_ranks ,
702742 )
703743 else :
704- raise ValueError ("The state source is not a SafeTensorsStateSource, cannot save in streaming mode." )
744+ # Config-only path: shard and write safetensors directly
745+ import json
746+
747+ from huggingface_hub import split_torch_state_dict_into_shards
748+
749+ # NOTE: Collects the full state dict into CPU memory before sharding.
750+ # For very large models (>70B), this may require significant host RAM.
751+ state_dict = {name : tensor .contiguous ().cpu () for name , tensor in generator }
752+
753+ is_distributed = dist .is_available () and dist .is_initialized ()
754+ rank = dist .get_rank () if is_distributed else 0
755+
756+ if rank == 0 :
757+ plan = split_torch_state_dict_into_shards (state_dict )
758+ safe_dir = Path (path )
759+ safe_dir .mkdir (parents = True , exist_ok = True )
760+ for filename , tensors in plan .filename_to_tensors .items ():
761+ shard = {k : state_dict [k ] for k in tensors }
762+ save_file (shard , safe_dir / filename )
763+ if plan .is_sharded :
764+ index = {"metadata" : plan .metadata , "weight_map" : plan .tensor_to_filename }
765+ with open (safe_dir / "model.safetensors.index.json" , "w" ) as f :
766+ json .dump (index , f , indent = 2 )
705767
706768 # Save quantizer/amax sidecar after the main generator is consumed (rank 0 only).
707769 if quant_tensors :
@@ -899,6 +961,11 @@ def import_ckpt(
899961 hf_tokenizer_kwargs = None
900962 if hasattr (bridge ._model_bridge , "get_hf_tokenizer_kwargs" ):
901963 hf_tokenizer_kwargs = bridge ._model_bridge .get_hf_tokenizer_kwargs ()
964+ # Forward trust_remote_code to the tokenizer (needed for repos with custom code)
965+ if kwargs .get ("trust_remote_code" ):
966+ if hf_tokenizer_kwargs is None :
967+ hf_tokenizer_kwargs = {}
968+ hf_tokenizer_kwargs .setdefault ("trust_remote_code" , True )
902969 bridge .save_megatron_model (
903970 megatron_model ,
904971 megatron_path ,
@@ -921,8 +988,6 @@ def export_ckpt(
921988 This is a convenience method that loads a Megatron checkpoint and
922989 exports it to HuggingFace format. This is useful for sharing trained
923990 models or deploying them with HuggingFace inference tools.
924- Requires a bridge created with `from_hf_pretrained` so the tokenizer
925- and other HuggingFace artifacts can be saved alongside the weights.
926991
927992 Args:
928993 megatron_path: Directory path where the Megatron checkpoint is stored
@@ -941,7 +1006,6 @@ def export_ckpt(
9411006 ... "./megatron_checkpoints/my_model",
9421007 ... "./hf_exports/my_model"
9431008 ... )
944-
9451009 >>> # Export with specific settings
9461010 >>> bridge.export_ckpt(
9471011 ... "./megatron_checkpoints/my_model",
@@ -953,12 +1017,6 @@ def export_ckpt(
9531017 >>> from transformers import AutoModelForCausalLM
9541018 >>> hf_model = AutoModelForCausalLM.from_pretrained("./hf_exports/my_model")
9551019 """
956- if not isinstance (self .hf_pretrained , PreTrainedCausalLM ):
957- raise ValueError (
958- "export_ckpt requires a pretrained HuggingFace model. "
959- "AutoBridge.from_hf_config() creates a config-only bridge; "
960- "use AutoBridge.from_hf_pretrained(...) instead."
961- )
9621020 try :
9631021 from megatron .bridge .training .model_load_save import temporary_distributed_context
9641022 except ImportError :
@@ -1295,7 +1353,7 @@ def mla_transformer_config(self) -> MLATransformerConfig:
12951353
12961354 return self ._create_config_from_provider (_model_provider , MLATransformerConfig )
12971355
1298- @property
1356+ @cached_property
12991357 def _model_bridge (self ) -> "MegatronModelBridge" :
13001358 hf_config = getattr (self .hf_pretrained , "hf_config" , None )
13011359 if hf_config is None :
0 commit comments