Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / source / resolver.py: 0%

234 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# Licensed under the Apache License, Version 2.0 

3# ============================================================================ 

4"""Locate the real modeling source file for a model id. 

5 

6The generated modeling file is produced from the original ``modeling_*.py`` 

7that the checkpoint actually loads — for native HF models that is the file in 

8the installed transformers package, for remote-code models it is the file 

9shipped inside the checkpoint. Getting the right file matters: the generated 

10bundle must stay importable in the exact environment the model runs in. 

11 

12The resolution chain mirrors what HF actually loads (``from_pretrained``): 

13 

14 pretrained_model_name_or_path -> AutoConfig -> model_type / architectures 

15 -> modeling source file 

16 

17and prefers the *same* source HF would load. The deciding input is 

18``trust_remote_code`` (forwarded from the YAML ``model:`` block): when it is 

19set and the checkpoint declares ``auto_map``, HF loads the checkpoint's own 

20modeling file — remote code wins even over an installed transformers module. 

21Without it, HF uses the installed module (if any), and remote code is only 

22reached as a fallback. ``None`` of the resolution helpers import 

23``transformers`` at module scope: the codegen manager runs before the model is 

24built, and source resolution must stay functional (degrading) in environments 

25without torch/transformers. 

26""" 

27from __future__ import annotations 

28 

29import importlib.util 

30import json 

31import logging 

32import os 

33import re 

34from dataclasses import dataclass 

35from types import SimpleNamespace 

36from typing import Any, Optional 

37 

38from hyper_parallel.codegen.hash import file_line_count, sha256_file 

39 

40logger = logging.getLogger(__name__) 

41 

42 

43@dataclass 

44class ResolvedSource: 

45 """A located modeling source file, ready to be copied into a bundle.""" 

46 

47 module_name: str 

48 file_path: str 

49 architecture: Optional[str] = None 

50 model_type: Optional[str] = None 

51 sha256: Optional[str] = None 

52 line_count: Optional[int] = None 

53 

54 def to_dict(self) -> dict[str, Any]: 

55 """JSON-friendly form for ``CodegenMeta.source``.""" 

56 return { 

57 "module_name": self.module_name, 

58 "file_path": self.file_path, 

59 "architecture": self.architecture, 

60 "model_type": self.model_type, 

61 "sha256": self.sha256, 

62 "line_count": self.line_count, 

63 } 

64 

65 

66def resolve_model_source( 

67 config: Any, model_id: str, *, hf_config: Any = None, 

68) -> Optional[ResolvedSource]: 

69 """Resolve the modeling source for a model id, degrading gracefully. 

70 

71 Resolves ``model_id -> AutoConfig -> architectures/model_type -> modeling 

72 file``, with fallbacks when the environment cannot do the real lookup: 

73 

74 1. ``hf_config`` already resolved by the caller -> resolve against it. 

75 2. ``AutoConfig.from_pretrained(model_id)`` (real HF config, lazy import; 

76 None on any failure — no transformers / no network / bad id). 

77 3. ``resolve_source_identity`` — installed module guessed from the 

78 basename (covers locally-installed model types when AutoConfig is 

79 unavailable). 

80 4. Local checkpoint ``config.json`` -> remote code (``SimpleNamespace`` 

81 mirrors ``AutoConfig`` attribute access). 

82 

83 Returns ``None`` when nothing resolved, so the caller decides whether to 

84 fail (codegen ``gen`` backend must not run without a source) or degrade 

85 (``hf`` backend records the unresolved ``SourceSpec`` projection). 

86 """ 

87 if hf_config is not None: 

88 return resolve_hf_source(config, hf_config) 

89 cfg = _load_auto_config(model_id, config) 

90 if cfg is not None: 

91 try: 

92 return resolve_hf_source(config, cfg) 

93 except FileNotFoundError: 

94 # AutoConfig resolved but no modeling file exists anywhere (not 

95 # even remote code in the checkpoint) — keep degrading. 

96 pass 

97 source = resolve_source_identity(model_id) 

98 if source is not None: 

99 return source 

100 return _resolve_source_from_yaml(config, model_id) 

101 

102 

103def resolve_hf_source(config: Any, hf_config: Any) -> ResolvedSource: 

104 """Resolve the modeling source for a resolved HF config. 

105 

106 Priority mirrors what the hyper trainer actually loads, which is plain 

107 transformers ``from_pretrained`` semantics (see auto_model._init_model -> 

108 _from_pretrained_parent_class, with ``trust_remote_code`` forwarded from the 

109 YAML ``model:`` block). The deciding input is ``trust_remote_code``, NOT 

110 the mere presence of ``auto_map``: 

111 

112 1. ``trust_remote_code`` AND the checkpoint ships remote code (``auto_map``) 

113 -> remote code wins, even when an installed module also exists (this is 

114 what HF loads). Must resolve to a file, else raise. 

115 2. Otherwise an installed transformers module for ``model_type`` -> use it 

116 (this is what HF loads when remote code is not trusted). 

117 3. Otherwise remote code, if any (a checkpoint whose model_type has no 

118 installed module; HF would require trust_remote_code to load it, so we 

119 only reach here to still produce a source for generation). 

120 """ 

121 arch = _first_architecture(hf_config) 

122 model_type = getattr(hf_config, "model_type", None) 

123 has_remote_code = bool(getattr(hf_config, "auto_map", None)) 

124 trust_remote_code = _trust_remote_code(config) 

125 model_path = _model_path_of(config) 

126 

127 module_name = _module_for_model_type(model_type) 

128 installed_path = import_module_file(module_name) if module_name else None 

129 

130 # 1. Trusted remote code is authoritative — HF loads the checkpoint's own 

131 # modeling file over any installed module. 

132 if has_remote_code and trust_remote_code: 

133 remote_path = resolve_remote_code_file(model_path, hf_config) 

134 if remote_path is not None: 

135 logger.debug("source: resolved to trusted remote code %s", remote_path) 

136 return _build_resolved_source(remote_path, "<remote>", arch, model_type) 

137 raise FileNotFoundError( 

138 f"codegen: trust_remote_code is set and the config for " 

139 f"{model_path or model_type!r} declares auto_map " 

140 f"({list(getattr(hf_config, 'auto_map', {}) or {})}), but no " 

141 "modeling_*.py was found in the checkpoint dir or the loaded " 

142 "config's module — remote code is the source HF would load" 

143 ) 

144 

145 # 2. Custom-registered model — what hyper_parallel's CUSTOM/GEN backends 

146 # load for a registered architecture. The registry entry is 

147 # authoritative over an installed transformers module of the same 

148 # model_type: backend resolution prefers the registered class. 

149 custom_module = _custom_registered_module(hf_config) 

150 if custom_module is not None: 

151 custom_path = import_module_file(custom_module) 

152 if custom_path is None: 

153 raise FileNotFoundError( 

154 f"codegen: architecture {arch!r} is registered to module " 

155 f"{custom_module!r} but its file could not be located" 

156 ) 

157 logger.debug( 

158 "source: resolved architecture=%r to registered module %s", 

159 arch, custom_path, 

160 ) 

161 return _build_resolved_source(custom_path, custom_module, arch, model_type) 

162 

163 # 3. Installed transformers module — what HF loads when remote code is not 

164 # trusted (or not present). 

165 if installed_path is not None: 

166 logger.debug( 

167 "source: resolved model_type=%r to installed module %s", 

168 model_type, installed_path, 

169 ) 

170 return _build_resolved_source(installed_path, module_name, arch, model_type) 

171 

172 # 4. Remote code without an installed module (HF would need 

173 # trust_remote_code, but we can still locate the file for generation). 

174 if has_remote_code: 

175 remote_path = resolve_remote_code_file(model_path, hf_config) 

176 if remote_path is not None: 

177 logger.debug( 

178 "source: resolved to remote code %s (no installed module)", 

179 remote_path, 

180 ) 

181 return _build_resolved_source(remote_path, "<remote>", arch, model_type) 

182 

183 raise FileNotFoundError( 

184 f"codegen: cannot resolve modeling source for model_type={model_type!r}, " 

185 f"architecture={arch!r} (no installed transformers module, no remote code)" 

186 ) 

187 

188 

189def _custom_registered_module(hf_config: Any) -> Optional[str]: 

190 """Consult hyper_parallel's custom-model registry for the modeling module. 

191 

192 ``MODEL_ARCH_MAPPING`` is filled lazily: a family's ``registration.py`` 

193 self-registers on first import (directory convention, see 

194 :mod:`hyper_parallel.models.registry`). Call ``get_model_adapter`` first 

195 so the lookup cannot miss an undiscovered family. 

196 """ 

197 arch = _first_architecture(hf_config) 

198 if not arch: 

199 return None 

200 try: 

201 from hyper_parallel.models.registry import ( # pylint: disable=C0415 

202 MODEL_ARCH_MAPPING, 

203 get_model_adapter, 

204 ) 

205 except ImportError: 

206 return None 

207 if MODEL_ARCH_MAPPING.get(arch) is None: 

208 # Either spelling may be the key that discovers the family (mirrors 

209 # get_is_hf_model's identity loop); stop once the architecture is 

210 # registered. The architecture goes first: it is the registry key. 

211 for identity in (arch, getattr(hf_config, "model_type", "")): 

212 if not identity: 

213 continue 

214 get_model_adapter(identity) 

215 if MODEL_ARCH_MAPPING.get(arch) is not None: 

216 break 

217 entry = MODEL_ARCH_MAPPING.get(arch) 

218 return entry[0] if entry is not None else None 

219 

220 

221def resolve_source_identity(model_id: str) -> Optional[ResolvedSource]: 

222 """Best-effort source resolution keyed only on a model id. 

223 

224 Used as a degradation when no ``hf_config`` / ``AutoConfig`` is available. 

225 Derives the module name from the model id's basename: model ids usually 

226 carry the repo name the transformers module is keyed on (``DeepSeek-V3`` 

227 -> ``deepseek_v3``). ``None`` when nothing resolves so the caller falls 

228 back further (or records the unresolved ``SourceSpec``). 

229 """ 

230 if not model_id: 

231 return None 

232 name = os.path.basename(str(model_id).rstrip("/\\")) 

233 safe = "".join(c if c.isalnum() or c == "_" else "_" for c in name).lower() 

234 module_name = _module_for_model_type(safe) 

235 if module_name is None: 

236 return None 

237 file_path = import_module_file(module_name) 

238 if file_path is None: 

239 return None 

240 return ResolvedSource( 

241 module_name=module_name, 

242 file_path=file_path, 

243 architecture=None, 

244 model_type=safe, 

245 sha256=sha256_file(file_path), 

246 line_count=file_line_count(file_path), 

247 ) 

248 

249 

250def resolve_remote_code_file(model_path: str, hf_config: Any) -> Optional[str]: 

251 """Locate the remote-code modeling file for a checkpoint. 

252 

253 Remote-code checkpoints carry ``modeling_*.py`` + ``configuration_*.py`` 

254 next to the weights. Two layouts are handled: 

255 

256 * A local checkpoint directory — scan it. 

257 * A Hub id — the files live in the HF cache, not under ``model_path``. A 

258 config loaded with ``trust_remote_code`` was constructed from the cached 

259 ``configuration_*.py``, so its class's ``__module__`` file locates the 

260 cache dir to scan. 

261 

262 Within a directory, resolve in order of increasing specificity: 

263 

264 1. ``auto_map["AutoModel..."]`` value, e.g. ``"modeling_foo.FooForCausalLM"`` 

265 -> ``<dir>/modeling_foo.py``. 

266 2. The architecture class name, e.g. ``FooForCausalLM`` -> the file whose 

267 basename contains it. 

268 3. The single ``modeling_*.py`` when there is exactly one. 

269 

270 Returns None when no remote code is reachable. 

271 """ 

272 for directory in _remote_code_dirs(model_path, hf_config): 

273 found = _scan_remote_code_dir(directory, hf_config) 

274 if found is not None: 

275 return found 

276 return None 

277 

278 

279def _remote_code_dirs(model_path: str, hf_config: Any) -> list[str]: 

280 """Directories that may hold this checkpoint's remote code, best first.""" 

281 dirs: list[str] = [] 

282 if model_path and os.path.isdir(model_path): 

283 dirs.append(model_path) 

284 # A remote-code config instance is defined in the cached configuration 

285 # module (``.../modules/transformers_modules/<repo>/configuration_x.py``), 

286 # which sits next to the cached modeling file. This is how a Hub id 

287 # resolves without downloading anything extra. 

288 module_name = getattr(type(hf_config), "__module__", "") or "" 

289 if module_name and module_name not in ("builtins", "types"): 

290 origin = import_module_file(module_name) 

291 if origin is None: 

292 module = __import__("sys").modules.get(module_name) 

293 origin = getattr(module, "__file__", None) if module else None 

294 if origin: 

295 cached_dir = os.path.dirname(origin) 

296 if cached_dir and cached_dir not in dirs and os.path.isdir(cached_dir): 

297 dirs.append(cached_dir) 

298 return dirs 

299 

300 

301def _scan_remote_code_dir(model_path: str, hf_config: Any) -> Optional[str]: 

302 """Pick this checkpoint's modeling file out of one directory.""" 

303 try: 

304 candidates = sorted( 

305 name for name in os.listdir(model_path) 

306 if name.startswith("modeling_") and name.endswith(".py") 

307 ) 

308 except OSError: 

309 return None 

310 if not candidates: 

311 return None 

312 

313 auto_map = getattr(hf_config, "auto_map", None) or {} 

314 for value in auto_map.values(): 

315 if not isinstance(value, str) or "." not in value: 

316 continue 

317 # "modeling_foo.FooForCausalLM" -> "modeling_foo.py". Hub auto_map 

318 # values can be "<repo>--modeling_foo.FooForCausalLM"; keep the tail. 

319 module_ref = value.split("--")[-1] 

320 module_file = module_ref.split(".", 1)[0] + ".py" 

321 if module_file in candidates: 

322 return os.path.join(model_path, module_file) 

323 

324 arch = _first_architecture(hf_config) 

325 if arch: 

326 for name in candidates: 

327 if arch in name: 

328 return os.path.join(model_path, name) 

329 if len(candidates) == 1: 

330 return os.path.join(model_path, candidates[0]) 

331 return None 

332 

333 

334def import_module_file(module_name: str) -> Optional[str]: 

335 """Resolve a Python module's ``__file__`` without importing it.""" 

336 try: 

337 spec = importlib.util.find_spec(module_name) 

338 except (ImportError, ModuleNotFoundError, ValueError, AttributeError): 

339 return None 

340 if spec is None or spec.origin is None: 

341 return None 

342 if not spec.origin.endswith(".py"): 

343 return None 

344 return spec.origin 

345 

346 

347def _load_auto_config(model_id: str, config: Any) -> Optional[Any]: 

348 """``AutoConfig.from_pretrained(model_id)`` with lazy import; None on failure. 

349 

350 ``trust_remote_code`` is forwarded so remote-code checkpoints can load 

351 their own config classes — mirroring what ``HyperAutoModel.from_pretrained`` 

352 will do at build time. Any failure (no transformers installed, no network, 

353 unknown id, remote config raising) returns ``None``; the caller degrades. 

354 """ 

355 if not model_id: 

356 return None 

357 try: 

358 from transformers import AutoConfig 

359 except ImportError: 

360 return None 

361 kwargs: dict[str, Any] = {} 

362 if _trust_remote_code(config): 

363 kwargs["trust_remote_code"] = True 

364 try: 

365 return AutoConfig.from_pretrained(model_id, **kwargs) 

366 except Exception: 

367 return None 

368 

369 

370def _resolve_source_from_yaml(config: Any, model_id: str) -> Optional[ResolvedSource]: 

371 """Best-effort source resolution from a local checkpoint's ``config.json``. 

372 

373 Used when ``AutoConfig`` and the basename guess both came up empty: for a 

374 local checkpoint directory, read its ``config.json`` and resolve against 

375 it. ``SimpleNamespace`` attribute access mirrors what 

376 ``AutoConfig.from_pretrained`` produces, without importing transformers. 

377 Returns ``None`` on any failure so the caller degrades. 

378 """ 

379 if not model_id or not os.path.isdir(model_id): 

380 return None 

381 config_path = os.path.join(model_id, "config.json") 

382 if not os.path.isfile(config_path): 

383 return None 

384 try: 

385 with open(config_path, "r", encoding="utf-8") as handle: 

386 raw = json.load(handle) 

387 hf_config = SimpleNamespace(**raw) 

388 return resolve_hf_source(config, hf_config) 

389 except (OSError, ValueError, FileNotFoundError, ImportError): 

390 return None 

391 

392 

393def _module_for_model_type(model_type: Optional[str]) -> Optional[str]: 

394 """Map ``config.model_type`` to a transformers ``modeling_*`` module name.""" 

395 if not model_type: 

396 return None 

397 safe = "".join(c if c.isalnum() or c == "_" else "_" for c in model_type) 

398 return f"transformers.models.{safe}.modeling_{safe}" 

399 

400 

401def _first_architecture(hf_config: Any) -> Optional[str]: 

402 architectures = getattr(hf_config, "architectures", None) or [] 

403 return architectures[0] if architectures else None 

404 

405 

406def _model_path_of(config: Any) -> str: 

407 """Extract the checkpoint dir / HF id from the model target. 

408 

409 Same key order as ``project_source_spec``: most YAMLs and 

410 ``HyperAutoModel.from_pretrained`` configure the id under 

411 ``pretrained_model_name_or_path``, with ``model_name_or_path`` as fallback. 

412 """ 

413 model = getattr(config, "model", None) 

414 if model is None: 

415 return "" 

416 path = getattr(model, "pretrained_model_name_or_path", None) 

417 if path is None: 

418 path = getattr(model, "model_name_or_path", None) 

419 if path is None: 

420 path = getattr(model, "_kwargs", {}).get("pretrained_model_name_or_path", "") 

421 return path or "" 

422 

423 

424def _trust_remote_code(config: Any) -> bool: 

425 """Whether this model is loaded with trusted remote code. 

426 

427 The truth is the YAML's ``model.trust_remote_code`` — it is what the 

428 trainer forwards into ``AutoConfig.from_pretrained`` / the HF 

429 ``from_pretrained`` kwargs, and therefore what decides whether HF loads 

430 the checkpoint's own modeling file or an installed transformers module. 

431 The ``TrainerConfig`` object itself has no such field, so this must read 

432 through the model target. 

433 """ 

434 model = getattr(config, "model", None) 

435 if model is None: 

436 return False 

437 value = getattr(model, "trust_remote_code", None) 

438 if value is None: 

439 value = getattr(model, "_kwargs", {}).get("trust_remote_code", False) 

440 return bool(value) 

441 

442 

443def _relative_imports_of(path: str) -> list[str]: 

444 """Names a file relatively imports from its own package (one level). 

445 

446 Mirrors ``transformers.dynamic_module_utils.get_relative_imports`` without 

447 importing transformers (source resolution must stay dependency-free). Only 

448 single-dot relatives are considered: a sibling ``from .name import x`` / 

449 ``import .name`` resolves within the same package directory, which is what 

450 a ``<remote>`` modeling source ships. Multi-dot relatives (``..x``) do not 

451 resolve against a flat remote code dir and are ignored. 

452 """ 

453 try: 

454 with open(path, "r", encoding="utf-8", errors="replace") as handle: 

455 content = handle.read() 

456 except OSError: 

457 return [] 

458 names = re.findall(r"(?m)^\s*import\s+\.([A-Za-z_][A-Za-z0-9_]*)\s*$", content) 

459 names += re.findall(r"(?m)^\s*from\s+\.([A-Za-z_][A-Za-z0-9_]*)\s+import", content) 

460 return sorted(set(names)) 

461 

462 

463def resolve_remote_siblings(source: ResolvedSource) -> dict[str, str]: 

464 """Transitive closure of a ``<remote>`` source's package siblings. 

465 

466 A trust-remote-code modeling file is compiled as the top-level module of a 

467 synthetic package (loader.import_generated_module), so its single-dot 

468 relative imports must resolve against *siblings in the same directory*. 

469 ``source.module_name == "<remote>"`` marks exactly this case; for an 

470 installed transformers module the source uses absolute imports and this 

471 returns empty. 

472 

473 Returns ``{basename: absolute_path}`` for every sibling reachable through 

474 the source's relative-import closure, e.g. a ``configuration_deepseek.py`` 

475 that ``modeling_deepseek.py`` imports (and any module *it* relatively 

476 imports). The caller copies these into the bundle so the artifact is 

477 self-contained; the loader registers them under the synthetic package. 

478 """ 

479 if isinstance(source, dict): 

480 module_name = source.get("module_name") 

481 file_path = source.get("file_path") 

482 else: 

483 module_name = getattr(source, "module_name", None) 

484 file_path = getattr(source, "file_path", None) 

485 if module_name != "<remote>" or not file_path: 

486 return {} 

487 base_dir = os.path.dirname(os.path.abspath(file_path)) 

488 seen: dict[str, str] = {} 

489 queue: list[str] = [os.path.abspath(file_path)] 

490 while queue: 

491 module_file = queue.pop(0) 

492 for name in _relative_imports_of(module_file): 

493 sibling = os.path.join(base_dir, f"{name}.py") 

494 if not os.path.isfile(sibling): 

495 continue 

496 if sibling in seen: 

497 continue 

498 seen[sibling] = sibling 

499 queue.append(sibling) 

500 return seen 

501 

502 

503def _build_resolved_source( 

504 file_path: str, module_name: str, arch: Optional[str], model_type: Optional[str], 

505) -> ResolvedSource: 

506 return ResolvedSource( 

507 module_name=module_name, 

508 file_path=file_path, 

509 architecture=arch, 

510 model_type=model_type, 

511 sha256=sha256_file(file_path), 

512 line_count=file_line_count(file_path), 

513 ) 

514 

515 

516__all__ = [ 

517 "ResolvedSource", 

518 "import_module_file", 

519 "resolve_hf_source", 

520 "resolve_model_source", 

521 "resolve_remote_code_file", 

522 "resolve_remote_siblings", 

523 "resolve_source_identity", 

524]