Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / checkpoint / huggingface_load_planner.py: 94%

288 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +0800

1# Copyright 2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""Hugging Face checkpoints loaded into a finalized model through distributed checkpoint reads. 

16 

17:class:`HFLoadPlanner` loads what :meth:`HuggingFaceCheckpointer.load` loads, with the same 

18renaming rules, converters and replacement conversions. The difference is in how the values get 

19there. Every conversion is run once on :class:`RegionTensor` inputs while the load is planned, which 

20turns it into the regions of checkpoint tensors each model tensor is copied from. Each rank then reads 

21only the regions its own shards need, and replicated shards are read once and broadcast. A conversion 

22that cannot be traced that way reads its checkpoint tensors whole and runs on them for real, on every 

23rank, as the legacy loader runs every conversion. 

24 

25Rules come from the model's Transformers conversion mapping unless they are passed in, and a subclass 

26can override how the mapping is built, how checkpoint keys are named before the rules see them, and 

27which converter a matched key goes to. See :class:`HFLoadPlanner`. 

28""" 

29import logging 

30from collections.abc import Callable, Mapping, Sequence 

31from copy import deepcopy 

32from dataclasses import dataclass, field 

33from functools import partial 

34from pathlib import Path 

35from typing import Any, Optional 

36 

37import torch 

38import torch.distributed as dist 

39from torch import nn 

40 

41from hyper_parallel import DTensor 

42from hyper_parallel.components.checkpoint.region_tensor import RegionTensor, materialize 

43from hyper_parallel.components.checkpoint.weight_conversion import ( 

44 WeightConverter, 

45 WeightRenaming, 

46 dot_natural_key, 

47 get_model_conversion_mapping, 

48) 

49from hyper_parallel.core.distributed_checkpoint.api import load 

50from hyper_parallel.core.distributed_checkpoint.hf_storage import HuggingFaceStorageReader 

51from hyper_parallel.core.distributed_checkpoint.metadata import Metadata, TensorStorageMetadata 

52from hyper_parallel.core.distributed_checkpoint.remap_planner import DeferredRead, RemapBlock, RemapLoadPlanner 

53from hyper_parallel.core.distributed_checkpoint.utils import all_gather_object, str_to_dtype 

54from hyper_parallel.components.checkpoint.load_groups import ( 

55 LoadGroup, 

56 LoadReport, 

57 SourceModelView, 

58 alias_names_by_target, 

59 base_weights_mapping, 

60 build_load_groups, 

61 build_load_targets, 

62 build_replacement_routes, 

63 convert_group, 

64 copy_into_target, 

65 first_tensor, 

66 local_target_tensor, 

67 reject_partial_layout, 

68 resolve_checkpoint_location, 

69 select_unique_converter, 

70 sorted_checkpoint_keys, 

71 target_layout, 

72 validate_load_result, 

73) 

74 

75logger = logging.getLogger(__name__) 

76 

77Transform = WeightRenaming | WeightConverter 

78 

79 

80def _identity(value: Any) -> Any: 

81 """``value``, handed to a transform as a loader it calls when it converts.""" 

82 return value 

83 

84 

85@dataclass(eq=False) 

86class _Conversion: 

87 """ 

88 One conversion of the load: a transform, and the values it collects in the order it collects them. 

89 

90 Attributes: 

91 template (Transform): The transform, with nothing collected. Every run converts a copy of it. 

92 first_target_name (str): Name the conversion is known by, as its group is. 

93 model (Any): The model, or the view of it, handed to the conversion operations. 

94 inputs (list[tuple[str, Any]]): ``(source pattern, value)`` of every collected tensor, where a 

95 value is a :class:`RegionTensor` or a :class:`_Deferred` output of another conversion. 

96 """ 

97 

98 template: Transform 

99 first_target_name: str 

100 model: Any 

101 inputs: list[tuple[str, Any]] 

102 

103 def sources(self) -> frozenset[str]: 

104 """Names of the checkpoint tensors the conversion reads, through its inputs.""" 

105 return frozenset().union(*(value.sources() for _, value in self.inputs)) 

106 

107 def run(self, values: Sequence[Any]) -> dict[str, Any]: 

108 """ 

109 Convert ``values`` in place of the inputs, on a fresh copy of the transform. 

110 

111 Args: 

112 values (Sequence[Any]): One tensor per input: regions, meta tensors or real tensors. 

113 

114 Returns: 

115 dict[str, Any]: What the transform returns, by model tensor name. 

116 """ 

117 transform = deepcopy(self.template) 

118 for (pattern, _), value in zip(self.inputs, values): 

119 transform.add_tensor( 

120 self.first_target_name, 

121 self.first_target_name, 

122 pattern, 

123 partial(_identity, value) 

124 ) 

125 return convert_group(LoadGroup(self.first_target_name, transform), self.model) 

126 

127 

128@dataclass(frozen=True, eq=False) 

129class _Deferred: 

130 """ 

131 An output of a conversion that could not be traced, computed for real once its sources are read. 

132 

133 Attributes: 

134 shape (tuple[int, ...]): Shape the conversion gives the output, as run on meta tensors. 

135 dtype (torch.dtype): Dtype it gives the output. 

136 conversion (_Conversion): The conversion. 

137 name (str): Name of the output among those of the conversion. 

138 """ 

139 

140 shape: tuple[int, ...] 

141 dtype: torch.dtype 

142 conversion: _Conversion 

143 name: str 

144 

145 def sources(self) -> frozenset[str]: 

146 """Names of the checkpoint tensors the output is computed from.""" 

147 return self.conversion.sources() 

148 

149 

150def _meta_value(value: Any) -> torch.Tensor: 

151 """A meta tensor of the shape and dtype of a planned value.""" 

152 return torch.empty(tuple(value.shape), dtype=value.dtype, device="meta") 

153 

154 

155def _real_value(value: Any, fetched: Mapping[str, torch.Tensor], cache: dict[int, dict[str, Any]]) -> Any: 

156 """The values of a planned value, out of the whole checkpoint tensors it reads.""" 

157 if isinstance(value, RegionTensor): 

158 return materialize(value, fetched) 

159 return first_tensor(_run_real(value.conversion, fetched, cache)[value.name]) 

160 

161 

162def _run_real( 

163 conversion: _Conversion, fetched: Mapping[str, torch.Tensor], cache: dict[int, dict[str, Any]] 

164) -> dict[str, Any]: 

165 """Run ``conversion`` on real tensors, once for however many of its outputs are asked for.""" 

166 key = id(conversion) 

167 if key not in cache: 

168 cache[key] = conversion.run([_real_value(value, fetched, cache) for _, value in conversion.inputs]) 

169 return cache[key] 

170 

171 

172def _complete_conversion( 

173 conversion: _Conversion, 

174 claims: Sequence[tuple[str, str]], 

175 targets: Mapping[str, torch.Tensor], 

176 fetched: Mapping[str, torch.Tensor], 

177) -> None: 

178 """ 

179 Run a conversion that could not be traced, and copy the outputs it was given into their targets. 

180 

181 Args: 

182 conversion (_Conversion): The conversion. 

183 claims (Sequence[tuple[str, str]]): ``(output name, model tensor name)`` of every output to copy. 

184 targets (Mapping[str, torch.Tensor]): Model tensors by name. 

185 fetched (Mapping[str, torch.Tensor]): The whole checkpoint tensors the conversion reads. 

186 """ 

187 outputs = _run_real(conversion, fetched, {}) 

188 for output_name, target_name in claims: 

189 copy_into_target(target_name, first_tensor(outputs[output_name]), targets[target_name]) 

190 

191 

192def _dcp_view(target: torch.Tensor) -> Any: 

193 """ 

194 What the distributed checkpoint load fills for a model tensor. 

195 

196 A DTensor is filled as it is. A parameter that holds its local shard as a plain tensor beside its 

197 layout, which fully sharded parameters do between materialization and their first use, is filled 

198 through a DTensor over the same storage, so that its shard is found in the whole tensor. 

199 """ 

200 if isinstance(target, DTensor): 

201 return target 

202 layout = getattr(target, "_sharding_spec", None) 

203 if layout is not None: 

204 return DTensor.from_local_with_layout(target.detach(), layout) 

205 return target 

206 

207 

208@dataclass 

209class _Plan: 

210 """ 

211 What planning the load has decided so far. 

212 

213 Attributes: 

214 table (dict[str, list[RemapBlock]]): Blocks of every model tensor loaded by region reads, by the 

215 name the load's state dict gives it. 

216 deferred (dict[int, tuple[_Conversion, list[tuple[str, str]]]]): Conversions run for real, by 

217 identity, with the outputs each one copies into model tensors. 

218 loaded_ids (set[int]): Identity of every model tensor something loads. 

219 loaded_keys (set[str]): Every name those tensors are registered under. 

220 unexpected (list[str]): Checkpoint keys and converted names that match no model tensor. 

221 """ 

222 

223 table: dict[str, list[RemapBlock]] = field(default_factory=dict) 

224 deferred: dict[int, tuple[_Conversion, list[tuple[str, str]]]] = field(default_factory=dict) 

225 loaded_ids: set[int] = field(default_factory=set) 

226 loaded_keys: set[str] = field(default_factory=set) 

227 unexpected: list[str] = field(default_factory=list) 

228 

229 

230class HFLoadPlanner(RemapLoadPlanner): 

231 """ 

232 Load planner for a Hugging Face checkpoint read into a finalized model. 

233 

234 Tensors are renamed and converted by ``weights_mapping`` exactly as the legacy loader does it, and a 

235 model whose replacement modules convert their weights again is loaded in the same two stages. Every 

236 rank has to plan the load, and a load that fails to plan on one rank fails on all of them. 

237 

238 The rules, in the order they are tried: 

239 

240 * ``extra_weights_mapping``, ahead of everything else, for rules a model needs on top of the default. 

241 * ``weights_mapping`` when given, which replaces the default. Otherwise :meth:`default_weights_mapping` 

242 builds it: the model's Transformers conversion mapping, with ``key_mapping`` renames first. 

243 

244 A rule listed twice is used once. A subclass can also override: 

245 

246 * :meth:`map_checkpoint_key`, to rename a checkpoint key before any rule sees it, or skip it. 

247 * :meth:`select_converter`, to pick the converter of a key several converters match. 

248 * :meth:`supports_symbolic`, to have a transform always run on real tensors. 

249 

250 Example:: 

251 

252 planner = HFLoadPlanner(model) 

253 load(planner.state_dict_for_load(), storage_reader=HuggingFaceStorageReader(path), planner=planner) 

254 report = planner.finish(path) 

255 """ 

256 

257 def __init__( 

258 self, 

259 model: nn.Module, 

260 *, 

261 weights_mapping: Optional[Sequence[Transform]] = None, 

262 extra_weights_mapping: Sequence[Transform] = (), 

263 key_mapping: Optional[Mapping[str, str]] = None, 

264 strict: bool = True, 

265 broadcast_replicated_tensors: bool = False, 

266 external_target_fqns: frozenset[str] = frozenset(), 

267 claimed_source_keys: frozenset[str] = frozenset(), 

268 ) -> None: 

269 """ 

270 Args: 

271 model (nn.Module): The finalized model, its tensors materialized. 

272 weights_mapping (Optional[Sequence[Transform]]): Rules replacing the default ones. Default None. 

273 extra_weights_mapping (Sequence[Transform]): Rules tried before the others. Default (). 

274 key_mapping (Optional[Mapping[str, str]]): Regex renames of checkpoint keys, applied before the 

275 other rules. Default None. 

276 strict (bool): Fail the load when a model tensor would be left unloaded. Default True. 

277 broadcast_replicated_tensors (bool): See :class:`StandardLoadPlanner`. Default False. 

278 external_target_fqns: Model tensors already materialized externally. 

279 claimed_source_keys: Checkpoint keys already consumed externally. 

280 """ 

281 super().__init__(broadcast_replicated_tensors=broadcast_replicated_tensors) 

282 self.model = model 

283 self.weights_mapping = None if weights_mapping is None else list(weights_mapping) 

284 self.extra_weights_mapping = list(extra_weights_mapping) 

285 self.key_mapping = dict(key_mapping or {}) 

286 self.strict = strict 

287 self.report: Optional[LoadReport] = None 

288 targets = build_load_targets(model) 

289 if not external_target_fqns <= targets.keys(): 

290 raise ValueError("External target is absent from the model") 

291 self._targets = {key: value for key, value in targets.items() if key not in external_target_fqns} 

292 self._claimed_source_keys = claimed_source_keys 

293 self._aliases = alias_names_by_target(self._targets) 

294 self._state_keys: dict[int, str] = {} 

295 for name, target in self._targets.items(): 

296 self._state_keys.setdefault(id(target), name) 

297 self._load_state = {key: _dcp_view(self._targets[key]) for key in self._state_keys.values()} 

298 self._loaded_ids: set[int] = set() 

299 self._used: tuple[list[Transform], Optional[list[Transform]]] = ([], None) 

300 

301 def default_weights_mapping(self, model: nn.Module) -> list[Transform]: 

302 """ 

303 The rules used when none are passed in. 

304 

305 Args: 

306 model (nn.Module): The model being loaded. 

307 

308 Returns: 

309 list[Transform]: The model's Transformers conversion mapping, with ``key_mapping`` first. 

310 """ 

311 return get_model_conversion_mapping(model, key_mapping=self.key_mapping or None, hf_quantizer=None) 

312 

313 def map_checkpoint_key(self, key: str) -> Optional[str]: 

314 """ 

315 The name the rules see a checkpoint key under. 

316 

317 Args: 

318 key (str): The key as the checkpoint stores it. 

319 

320 Returns: 

321 Optional[str]: The key itself by default. None skips the key, which is then neither loaded 

322 nor reported as unexpected. 

323 """ 

324 return None if key in self._claimed_source_keys else key 

325 

326 def select_converter( 

327 self, source_pattern: str, target_name: str, candidates: Sequence[WeightConverter] 

328 ) -> Optional[WeightConverter]: 

329 """ 

330 The converter a checkpoint key matched by ``source_pattern`` belongs to. 

331 

332 Args: 

333 source_pattern (str): The source pattern the key matched. 

334 target_name (str): The model tensor the key was renamed to. 

335 candidates (Sequence[WeightConverter]): Every converter with that source pattern. 

336 

337 Returns: 

338 Optional[WeightConverter]: The only converter scoped to a module holding the target, else the 

339 only unscoped one, as the legacy loader picks it. None fails the load. 

340 """ 

341 return select_unique_converter(source_pattern, target_name, candidates) 

342 

343 def supports_symbolic(self, transform: Transform) -> bool: 

344 """ 

345 Whether a transform may be traced into region reads, rather than always run on real tensors. 

346 

347 Args: 

348 transform (Transform): The transform. 

349 

350 Returns: 

351 bool: True by default. A traced transform is still run on real tensors when tracing fails or 

352 disagrees with torch on the shapes and dtypes of what it returns. 

353 """ 

354 del transform 

355 return True 

356 

357 def state_dict_for_load(self) -> dict[str, Any]: 

358 """ 

359 The state dict to pass to :func:`load` with this planner. 

360 

361 Returns: 

362 dict[str, Any]: Every model tensor once, under the first name it is registered under. 

363 """ 

364 return dict(self._load_state) 

365 

366 def configure_planner(self, state_dict: dict[str, Any], metadata: Metadata, **kwargs: Any) -> None: 

367 """ 

368 Configure the planner, then plan how every model tensor is loaded out of the checkpoint. 

369 

370 Args: 

371 state_dict (dict[str, Any]): What :meth:`state_dict_for_load` returned. 

372 metadata (Metadata): Metadata of the checkpoint, as :class:`HuggingFaceStorageReader` builds it. 

373 **kwargs (Any): As :func:`load` passes them. 

374 

375 Raises: 

376 RuntimeError: If planning fails on another rank. 

377 Exception: Whatever planning raised on this rank. 

378 """ 

379 super().configure_planner(state_dict, metadata, **kwargs) 

380 error = None 

381 try: 

382 self._compile(metadata) 

383 except Exception as exc: # pylint: disable=broad-exception-caught 

384 error = exc 

385 world_size = dist.get_world_size() if dist.is_available() and dist.is_initialized() else 1 

386 message = None if error is None else f"{type(error).__name__}: {error}" 

387 failures = all_gather_object(message, world_size, kwargs.get("use_collectives", False)) 

388 if error is not None: 

389 raise error 

390 failed = [(rank, failure) for rank, failure in enumerate(failures) if failure is not None] 

391 if failed: 

392 raise RuntimeError(f"Planning the pretrained load failed on rank {failed[0][0]}: {failed[0][1]}") 

393 

394 def finish(self, pretrained_path: str = "") -> LoadReport: 

395 """ 

396 Complete a load this planner planned and :func:`load` executed. 

397 

398 Marks the loaded tensors initialized and records the conversions used on the model, where 

399 saving converts back through them, as the legacy loader does. 

400 

401 Args: 

402 pretrained_path (str): Where the checkpoint was loaded from, for the log. Default "". 

403 

404 Returns: 

405 LoadReport: What was loaded, what is missing and what the checkpoint holds beyond the model. 

406 """ 

407 if self.report is None: 

408 raise RuntimeError("HFLoadPlanner.finish() needs a load planned with this planner first") 

409 for target in self._targets.values(): 

410 if id(target) in self._loaded_ids: 

411 target._is_hf_initialized = True # pylint: disable=protected-access 

412 self.apply_used_conversions(self.model) 

413 validate_load_result(self.report.missing_keys, self.report.unexpected_keys, strict=False) 

414 logger.info("Loaded %d model tensors from %s", len(self.report.loaded_keys), pretrained_path) 

415 return self.report 

416 

417 def apply_used_conversions(self, model: nn.Module) -> None: 

418 """ 

419 Record on ``model`` the conversions the load used, where saving in the original format finds them. 

420 

421 Args: 

422 model (nn.Module): The model loaded. 

423 """ 

424 used, used_replacements = self._used 

425 if used_replacements is None: 

426 model._weight_conversions = list(used) # pylint: disable=protected-access 

427 return 

428 model._hp_used_base_weight_conversions = list(used) # pylint: disable=protected-access 

429 model._hp_used_replacement_weight_conversions = list(used_replacements) # pylint: disable=protected-access 

430 model._weight_conversions = list(used) + list(used_replacements) # pylint: disable=protected-access 

431 

432 def _resolved_mapping(self) -> list[Transform]: 

433 """The rules in the order they are tried, each once.""" 

434 if self.weights_mapping is None: 

435 base = list(self.default_weights_mapping(self.model)) 

436 else: 

437 renames = [ 

438 WeightRenaming(source_patterns=source, target_patterns=target) 

439 for source, target in self.key_mapping.items() 

440 ] 

441 base = renames + self.weights_mapping 

442 mapping, seen = [], set() 

443 for transform in (*self.extra_weights_mapping, *base): 

444 if id(transform) not in seen: 

445 seen.add(id(transform)) 

446 mapping.append(transform) 

447 return mapping 

448 

449 def _checkpoint_keys(self, metadata: Metadata) -> dict[str, str]: 

450 """Every checkpoint tensor the rules see, by the name they see it under.""" 

451 keys: dict[str, str] = {} 

452 for key, md in metadata.state_dict_metadata.items(): 

453 mapped = self.map_checkpoint_key(key) if isinstance(md, TensorStorageMetadata) else None 

454 if mapped is None: 

455 continue 

456 if mapped in keys: 

457 raise ValueError(f"Checkpoint keys {keys[mapped]!r} and {key!r} both map to {mapped!r}") 

458 keys[mapped] = key 

459 return keys 

460 

461 def _compile(self, metadata: Metadata) -> None: 

462 """Plan the load of every model tensor, and fill the table and deferred reads it is executed from.""" 

463 keys = self._checkpoint_keys(metadata) 

464 make_loader = partial(self._leaf_loader, metadata, keys) 

465 mapping = self._resolved_mapping() 

466 replacement_mapping = getattr(self.model, "_hp_replacement_weight_conversions", None) 

467 source_shapes = getattr(self.model, "_hp_checkpoint_source_shapes", None) 

468 plan = _Plan() 

469 if replacement_mapping and source_shapes: 

470 self._compile_with_replacements(plan, keys, make_loader, mapping, (replacement_mapping, source_shapes)) 

471 else: 

472 groups, unexpected, mapping = build_load_groups( 

473 self.model, sorted_checkpoint_keys(keys), self._targets, weights_mapping=mapping, 

474 make_loader=make_loader, select_converter=self.select_converter, 

475 ) 

476 plan.unexpected.extend(keys[key] for key in unexpected) 

477 for group in groups: 

478 for name, value in self._plan_conversion(_conversion_of(group, self.model)).items(): 

479 self._claim(plan, name, value) 

480 self._used = ([transform for transform in mapping if transform.was_used()], None) 

481 self._adopt(plan) 

482 

483 def _compile_with_replacements( 

484 self, 

485 plan: _Plan, 

486 keys: dict[str, str], 

487 make_loader: Callable[[str], Any], 

488 mapping: list[Transform], 

489 replacements: tuple[list[Transform], dict[str, tuple[int, ...]]], 

490 ) -> None: 

491 """Plan a model whose replacement modules convert the normalized weights a second time.""" 

492 replacement_mapping, source_shapes = replacements 

493 base_mapping = base_weights_mapping(mapping, replacement_mapping) 

494 source_model = SourceModelView(self.model, source_shapes) 

495 base_groups, unexpected, _ = build_load_groups( 

496 source_model, sorted_checkpoint_keys(keys), source_model.targets, weights_mapping=base_mapping, 

497 make_loader=make_loader, select_converter=self.select_converter, 

498 ) 

499 plan.unexpected.extend(keys[key] for key in unexpected) 

500 routes = build_replacement_routes(self.model, tuple(source_shapes), self._targets, replacement_mapping) 

501 pending: dict[int, list[tuple[str, Any]]] = {} 

502 used_replacements = [] 

503 for group in base_groups: 

504 for source_name, value in self._plan_conversion(_conversion_of(group, source_model)).items(): 

505 route = routes.get(source_name) 

506 if route is None: 

507 self._claim(plan, source_name, value) 

508 continue 

509 state, _, source_pattern = route 

510 pending.setdefault(id(state), []).append((source_pattern, value)) 

511 state.received[source_pattern] += 1 

512 if state.completed or state.received != state.expected: 

513 continue 

514 conversion = _Conversion( 

515 state.group.transform, state.group.first_target_name, self.model, pending.pop(id(state)) 

516 ) 

517 for name, converted in self._plan_conversion(conversion).items(): 

518 self._claim(plan, name, converted) 

519 state.completed = True 

520 used_replacements.append(state.group.transform) 

521 self._used = ([transform for transform in base_mapping if transform.was_used()], used_replacements) 

522 

523 def _leaf_loader(self, metadata: Metadata, keys: dict[str, str], mapped: str) -> Callable[[], RegionTensor]: 

524 """A loader handing a transform the whole of the checkpoint tensor the rules see as ``mapped``.""" 

525 key = keys[mapped] 

526 md = metadata.state_dict_metadata[key] 

527 return partial(_identity, RegionTensor.leaf(key, md.size, str_to_dtype(md.properties.dtype))) 

528 

529 def _plan_conversion(self, conversion: _Conversion) -> dict[str, Any]: 

530 """ 

531 Trace a conversion into regions, or plan it to run on real tensors when it cannot be traced. 

532 

533 The conversion is also run on meta tensors, which both checks what tracing returns against what 

534 torch computes and gives the shapes of what an untraced conversion will return. 

535 

536 Args: 

537 conversion (_Conversion): The conversion. 

538 

539 Returns: 

540 dict[str, Any]: Every output by name: a :class:`RegionTensor` when traced, else a 

541 :class:`_Deferred`. 

542 

543 Raises: 

544 RuntimeError: If the conversion fails on meta tensors as well as when traced. 

545 """ 

546 values = [value for _, value in conversion.inputs] 

547 traced = None 

548 if self.supports_symbolic(conversion.template) and all(isinstance(v, RegionTensor) for v in values): 

549 try: 

550 traced = {name: first_tensor(value) for name, value in conversion.run(values).items()} 

551 except Exception as exc: # pylint: disable=broad-exception-caught 

552 logger.debug("Tracing %s failed, running it on real tensors: %s", conversion.first_target_name, exc) 

553 try: 

554 expected = { 

555 name: first_tensor(value) for name, value in conversion.run([_meta_value(v) for v in values]).items() 

556 } 

557 except RuntimeError: 

558 if traced is not None and all(isinstance(value, RegionTensor) for value in traced.values()): 

559 return traced 

560 raise 

561 if traced is not None and _same_outputs(traced, expected): 

562 return traced 

563 return { 

564 name: _Deferred(tuple(value.shape), value.dtype, conversion, name) for name, value in expected.items() 

565 } 

566 

567 def _claim(self, plan: _Plan, name: str, value: Any) -> None: 

568 """Have ``value`` load the model tensor ``name``, unless something planned earlier already does.""" 

569 target = self._targets.get(name) 

570 if target is None: 

571 plan.unexpected.append(name) 

572 return 

573 target_id = id(target) 

574 if target_id not in plan.loaded_ids: 

575 key = self._state_keys[target_id] 

576 self._check_target(name, target, self._load_state[key], value) 

577 if isinstance(value, RegionTensor): 

578 plan.table[key] = value.remap_blocks() 

579 else: 

580 plan.deferred.setdefault(id(value.conversion), (value.conversion, []))[1].append((value.name, name)) 

581 plan.loaded_ids.add(target_id) 

582 plan.loaded_keys.update(self._aliases[target_id]) 

583 

584 @staticmethod 

585 def _check_target(name: str, target: torch.Tensor, view: Any, value: Any) -> None: 

586 """Refuse a model tensor that cannot take ``value``, as the legacy loader refuses it.""" 

587 if local_target_tensor(target).is_meta: 

588 raise ValueError(f"Target must be materialized before loading: {name}") 

589 layout = target_layout(target) 

590 if layout is not None: 

591 reject_partial_layout(name, layout) 

592 if tuple(value.shape) != tuple(view.shape): 

593 raise ValueError( 

594 f"Shape mismatch for {name}: checkpoint {tuple(value.shape)} vs target {tuple(view.shape)}" 

595 ) 

596 

597 def _adopt(self, plan: _Plan) -> None: 

598 """Take a finished plan on as the table and deferred reads of the load, and report on it.""" 

599 missing = tuple(sorted(set(self._targets) - plan.loaded_keys, key=dot_natural_key)) 

600 unexpected = tuple(sorted(set(plan.unexpected), key=dot_natural_key)) 

601 validate_load_result(missing, (), self.strict) 

602 deferred = [] 

603 for conversion, claims in plan.deferred.values(): 

604 complete = partial(_complete_conversion, conversion, tuple(claims), self._targets) 

605 sources = tuple(sorted(conversion.sources())) 

606 if sources: 

607 deferred.append(DeferredRead(sources, complete)) 

608 else: 

609 # Reads nothing, as a conversion of empty tensors does, so there is nothing to wait for. 

610 complete({}) 

611 self.table = {key: tuple(blocks) for key, blocks in plan.table.items()} 

612 self.deferred = tuple(deferred) 

613 self._loaded_ids = set(plan.loaded_ids) 

614 self.report = LoadReport( 

615 loaded_keys=tuple(sorted(plan.loaded_keys, key=dot_natural_key)), 

616 missing_keys=missing, 

617 unexpected_keys=unexpected, 

618 ) 

619 logger.info( 

620 "Planned %d model tensors as region reads and %d through %d conversions on whole tensors", 

621 len(self.table), sum(len(claims) for _, claims in plan.deferred.values()), len(plan.deferred), 

622 ) 

623 

624 

625def _conversion_of(group: LoadGroup, model: Any) -> _Conversion: 

626 """ 

627 The conversion of a load group, taking over what the group's transform collected. 

628 

629 Args: 

630 group (LoadGroup): A group :func:`build_load_groups` built with loaders handing out regions. 

631 model (Any): The model, or the view of it, handed to the conversion operations. 

632 

633 Returns: 

634 _Conversion: The conversion, whose template is the group's transform, emptied. 

635 """ 

636 inputs = [ 

637 (pattern, loader()) for pattern, loaders in group.transform.collected_tensors.items() for loader in loaders 

638 ] 

639 group.transform.collected_tensors.clear() 

640 return _Conversion(group.transform, group.first_target_name, model, inputs) 

641 

642 

643def _same_outputs(traced: Mapping[str, Any], expected: Mapping[str, torch.Tensor]) -> bool: 

644 """Whether a traced conversion returns what torch computes, by name, shape and dtype.""" 

645 return traced.keys() == expected.keys() and all( 

646 isinstance(value, RegionTensor) 

647 and tuple(value.shape) == tuple(expected[name].shape) 

648 and value.dtype == expected[name].dtype 

649 for name, value in traced.items() 

650 ) 

651 

652 

653class _SingleFileReader(HuggingFaceStorageReader): 

654 """A Hugging Face checkpoint that is one safetensors file, under whatever name it has.""" 

655 

656 def __init__(self, file_path: Path) -> None: 

657 """ 

658 Args: 

659 file_path (Path): The safetensors file. 

660 """ 

661 super().__init__(file_path.parent) 

662 self._file_name = file_path.name 

663 

664 def _weight_files(self) -> list[str]: 

665 """The one file.""" 

666 return [self._file_name] 

667 

668 

669def _load_in_process(state_dict: dict[str, Any], reader: HuggingFaceStorageReader, planner: HFLoadPlanner) -> None: 

670 """Plan and execute a load in a process with no process group, which has nobody to share reads with.""" 

671 metadata = reader.load_metadata() 

672 planner.configure_planner(state_dict, metadata, is_coordinator=True, rank=0, use_collectives=False) 

673 reader.configure_reader(metadata, is_coordinator=True, rank=0) 

674 reader.execute_read(planner.build_local_plan(), planner) 

675 

676 

677def load_hf_checkpoint( 

678 model: nn.Module, 

679 pretrained_path: str, 

680 *, 

681 weights_mapping: Optional[Sequence[Transform]] = None, 

682 strict: bool = True, 

683 planner: Optional[HFLoadPlanner] = None, 

684 **load_kwargs: Any, 

685) -> LoadReport: 

686 """ 

687 Load a Hugging Face checkpoint into a finalized model through distributed checkpoint reads. 

688 

689 Every rank of the default process group has to call this together. Without a process group the 

690 load runs in this process alone. 

691 

692 Args: 

693 model (nn.Module): The finalized model, its tensors materialized. 

694 pretrained_path (str): A safetensors file, a checkpoint directory or a Hub repository id. 

695 weights_mapping (Optional[Sequence[Transform]]): Rules replacing the model's default ones. 

696 Default None. Ignored when ``planner`` is given. 

697 strict (bool): Fail when a model tensor would be left unloaded. Default True. Ignored when 

698 ``planner`` is given. 

699 planner (Optional[HFLoadPlanner]): The planner to load with, for rules customized beyond 

700 ``weights_mapping``. Default None, for ``HFLoadPlanner(model, ...)``. 

701 **load_kwargs (Any): Passed on to :func:`load`, such as ``broadcast_batch_bytes``. 

702 

703 Returns: 

704 LoadReport: What was loaded, what is missing and what the checkpoint holds beyond the model. 

705 """ 

706 location = resolve_checkpoint_location(pretrained_path) 

707 reader = _SingleFileReader(location) if location.is_file() else HuggingFaceStorageReader(location) 

708 if planner is None: 

709 planner = HFLoadPlanner(model, weights_mapping=weights_mapping, strict=strict) 

710 state_dict = planner.state_dict_for_load() 

711 if dist.is_available() and dist.is_initialized(): 

712 load(state_dict, storage_reader=reader, planner=planner, **load_kwargs) 

713 else: 

714 _load_in_process(state_dict, reader, planner) 

715 return planner.finish(pretrained_path)