-
Notifications
You must be signed in to change notification settings - Fork 498
Expand file tree
/
Copy pathhf_checkpoint_utils.py
More file actions
167 lines (137 loc) · 6.59 KB
/
Copy pathhf_checkpoint_utils.py
File metadata and controls
167 lines (137 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Hugging Face checkpoint utility."""
import json
import os
import shutil
from pathlib import Path
import torch
from huggingface_hub import snapshot_download
from huggingface_hub.errors import LocalEntryNotFoundError
from safetensors.torch import safe_open
from tqdm import tqdm
_HF_HUB_OFFLINE_TRUE_VALUES = {"1", "ON", "YES", "TRUE"}
def _is_hf_hub_offline() -> bool:
return os.environ.get("HF_HUB_OFFLINE", "").strip().upper() in _HF_HUB_OFFLINE_TRUE_VALUES
def _copy_python_files(source_dir: Path, save_dir: Path) -> None:
for py_file in source_dir.glob("*.py"):
shutil.copy2(py_file, save_dir / py_file.name)
def copy_hf_ckpt_remote_code(
pretrained_model_path: str | os.PathLike, save_directory: str | os.PathLike
):
"""Copy remote code from pretrained model to save directory.
For models that keep configuration and modeling files as part of the checkpoint,
we need to copy them to the export directory for seamless integration with inference
frameworks.
If ``pretrained_model_path`` is a local directory, Python files are copied directly.
If it's a HF Hub model ID (e.g. ``nvidia/NVIDIA-Nemotron-Nano-12B-v2``), the Hub
snapshot is resolved first and Python files are copied from that snapshot. When
``HF_HUB_OFFLINE`` is set, the snapshot must already be available in the local
Hugging Face cache.
Args:
pretrained_model_path: Local path to the pretrained model or HuggingFace Hub model ID.
save_directory: Path to the save directory.
"""
hf_checkpoint_path = Path(pretrained_model_path)
save_dir = Path(save_directory)
save_dir.mkdir(parents=True, exist_ok=True)
if hf_checkpoint_path.is_dir():
_copy_python_files(hf_checkpoint_path, save_dir)
else:
local_files_only = _is_hf_hub_offline()
try:
source_dir = Path(
snapshot_download(
repo_id=str(pretrained_model_path),
allow_patterns=["*.py"],
local_files_only=local_files_only,
)
)
except LocalEntryNotFoundError as exc:
if local_files_only:
raise RuntimeError(
f"Could not copy Python sidecar files for {pretrained_model_path!r} because "
"HF_HUB_OFFLINE is enabled and the files are not available in the local "
"Hugging Face cache. Populate the cache with the model's *.py files or pass "
"a local pretrained model directory."
) from exc
raise
_copy_python_files(source_dir, save_dir)
def load_multimodal_components(
pretrained_model_path: str | os.PathLike,
prefixes: tuple[str, ...] = ("multi_modal_projector", "vision_model"),
) -> dict[str, torch.Tensor]:
"""Load multimodal components from safetensors file.
Args:
pretrained_model_path: Path to the pretrained model.
prefixes: Tensor key prefixes to select. Defaults to the LLaVA-style
``multi_modal_projector`` / ``vision_model`` prefixes. Pass
``("model.visual.",)`` for Qwen3-VL checkpoints.
Returns:
A dictionary of multimodal components.
"""
hf_checkpoint_path = Path(pretrained_model_path)
if not hf_checkpoint_path.is_dir():
raise ValueError(
f"Invalid pretrained model path: {pretrained_model_path}. It should be a directory."
)
safetensors_file = Path(hf_checkpoint_path) / "model.safetensors"
safetensors_index_file = Path(hf_checkpoint_path) / "model.safetensors.index.json"
multimodal_state_dict = {}
if safetensors_file.is_file():
print(f"Loading multimodal components from single file: {safetensors_file}")
with safe_open(safetensors_file, framework="pt") as f:
multimodal_keys = [
key
for key in f.keys() # noqa: SIM118
if key.startswith(prefixes)
]
for key in tqdm(multimodal_keys, desc="Loading multimodal tensors"):
multimodal_state_dict[key] = f.get_tensor(key)
elif safetensors_index_file.is_file():
print(f"Loading multimodal components from sharded model: {hf_checkpoint_path}")
with open(safetensors_index_file) as f:
safetensors_index = json.load(f)
all_shard_files = sorted(set(safetensors_index["weight_map"].values()))
for shard_file in all_shard_files:
safetensors_filepath = Path(hf_checkpoint_path) / shard_file
with safe_open(safetensors_filepath, framework="pt") as f:
for key in f.keys(): # noqa: SIM118
if key.startswith(prefixes):
multimodal_state_dict[key] = f.get_tensor(key)
else:
print(f"Warning: No safetensors files found in {hf_checkpoint_path}")
print(f"Successfully loaded {len(multimodal_state_dict)} multimodal tensors")
return multimodal_state_dict
def copy_non_safetensor_files_from_ckpt(src: str | os.PathLike, dst: str | os.PathLike):
"""Copy every non-safetensors file from a local HF checkpoint dir verbatim.
Use as a baseline so tokenizer files, remote_code ``*.py``, README, LICENSE, etc.
are preserved from the source. The caller is expected to overwrite the files
modelopt owns (``config.json``, ``generation_config.json``, ``hf_quant_config.json``,
``preprocessor_config.json``) after this step.
Args:
src: Source HF checkpoint directory. Must be a local path.
dst: Destination directory; created if missing.
"""
if not os.path.isdir(src):
raise ValueError(f"Invalid source path: {src}. It should be a directory.")
os.makedirs(dst, exist_ok=True)
for entry in os.listdir(src):
sp = os.path.join(src, entry)
if not os.path.isfile(sp):
continue
if entry.endswith(".safetensors") or entry == "model.safetensors.index.json":
continue
shutil.copy2(sp, dst)