Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / checkpoint / weight_verifier.py: 90%
384 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +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"""Check live model shards against local HF weights before training updates.
17Inverse weight conversions run symbolically; actual model values are read back
18in bounded tiles. No full model, expert tensor or Host embedding is gathered.
19All ranks must call ``verify_hf_weights``. Run it before the first forward to
20avoid confusing router/buffer updates with weight-loading errors.
22This is a check of live values under the recorded conversion rules, not a
23mathematical oracle for those rules. The reference decoder reuses the shared
24Transformers-based quantized reader; it does not recover pre-quantization FP32 values.
25"""
27from __future__ import annotations
29import csv
30import json
31import logging
32import math
33from collections import defaultdict
34from pathlib import Path
35from typing import Any
37import torch
38import torch.distributed as dist
39from safetensors import safe_open
40from torch import nn
42from hyper_parallel import DTensor
43from hyper_parallel.components.checkpoint.load_groups import (
44 build_load_targets, local_target_tensor, checkpoint_logical_shapes, checkpoint_padding_boxes, reject_partial_layout,
45 resolve_checkpoint_index, target_layout,
46)
47from hyper_parallel.components.checkpoint.quantized_hf_storage import build_quantized_hf_reader
48from hyper_parallel.components.checkpoint.region_tensor import RegionTensor
49from hyper_parallel.components.checkpoint.weight_conversion import (
50 WeightConverter, WeightRenaming, get_model_conversion_mapping, revert_weight_conversion,
51)
52from hyper_parallel.core.distributed_checkpoint.utils import create_chunk_list_for_tensor
53from hyper_parallel.models.external_state import CheckpointTensorShard, get_model_external_state
54from hyper_parallel.components.checkpoint._weight_check_geometry import box_union_volume, local_block_box, tile_box
56logger = logging.getLogger(__name__)
59class _Reference:
60 """Read selected disk tensors without running model-side conversions."""
62 def __init__(self, path: str) -> None:
63 """Read indexed tensor headers and optional quantization metadata."""
64 location = Path(path).expanduser().resolve()
65 if not location.exists():
66 raise ValueError(f"Weight checking requires a local HF checkpoint: {location}")
67 self.index = resolve_checkpoint_index(str(location))
68 self.quantized = build_quantized_hf_reader(location)
69 self.shapes = {}
70 if self.quantized is not None:
71 self.shapes = {
72 name: tuple(entry.size)
73 for name, entry in self.quantized.load_metadata().state_dict_metadata.items()
74 if name in self.index.files_by_key
75 }
76 else:
77 by_file = defaultdict(list)
78 for name, filename in self.index.files_by_key.items():
79 by_file[filename].append(name)
80 for filename, names in by_file.items():
81 with safe_open(str(filename), framework="pt", device="cpu") as reader:
82 for name in names:
83 self.shapes[name] = tuple(reader.get_slice(name).get_shape())
84 if not self.shapes:
85 raise ValueError("No logical HF tensors were selected for checking")
87 def read(self, name: str, offsets: tuple[int, ...], lengths: tuple[int, ...]) -> torch.Tensor:
88 """Decode a bounded source rectangle, keeping ordinary source dtypes."""
89 if self.quantized is not None and len(lengths) == 2:
90 return self.quantized.read_region(name, offsets, lengths)
91 with safe_open(str(self.index.files_by_key[name]), framework="pt", device="cpu") as reader:
92 if not lengths:
93 return reader.get_tensor(name)
94 region = tuple(slice(start, start + count) for start, count in zip(offsets, lengths))
95 return reader.get_slice(name)[region]
98class _InverseModel:
99 """Expose export conversions without mutating the live model's metadata."""
101 def __init__(self, model: nn.Module, conversions: Any) -> None:
102 """Bind one inverse-conversion stage to a read-only model view."""
103 self.model = model
104 self.config = model.config
105 self._weight_conversions = conversions
107 def __getattr__(self, name: str) -> Any:
108 return getattr(self.model, name)
111def _model_shards(model: nn.Module) -> dict[str, CheckpointTensorShard]:
112 """Describe every materialized local model shard for comparison."""
113 logical_shapes = checkpoint_logical_shapes(model)
114 external = get_model_external_state(model)
115 external_shards = {}
116 if external is not None and external.parameters:
117 provider = getattr(external, "checkpoint_tensor_shards", None)
118 if not callable(provider):
119 raise ValueError("External state must expose checkpoint_tensor_shards() for complete weight checking")
120 external_shards = provider()
121 if set(external_shards) != set(external.parameters):
122 raise ValueError("External checkpoint shard descriptors do not cover exactly the external parameters")
123 shards = {}
124 for name, target in build_load_targets(model).items():
125 if name in external_shards:
126 shards[name] = external_shards[name]
127 continue
128 layout = target_layout(target)
129 view = target
130 if layout is not None:
131 reject_partial_layout(name, layout)
132 if not isinstance(view, DTensor):
133 shape = getattr(layout, "tensor_shape", None)
134 view = DTensor.from_local_with_layout(target.detach(), layout, shape=shape)
135 chunks = create_chunk_list_for_tensor(view)
136 if len(chunks) != 1:
137 raise ValueError(f"Weight checking requires one rectangular local shard: {name}")
138 local = local_target_tensor(target)
139 if local.is_meta or tuple(local.shape) != tuple(chunks[0].sizes):
140 raise ValueError(f"Live storage does not match its checkpoint shard metadata: {name}")
141 logical_shape = logical_shapes.get(name)
142 if logical_shape == tuple(view.shape):
143 logical_shape = None
144 shards[name] = CheckpointTensorShard(
145 local, tuple(view.shape), tuple(chunks[0].offsets), logical_shape,
146 )
147 if not shards:
148 raise ValueError("The model has no checkpoint tensors to check")
149 return shards
152def _inverse_regions(model: nn.Module, shards: dict[str, CheckpointTensorShard]) -> dict[str, RegionTensor]:
153 """Reverse recorded load conversions into symbolic HF tensor regions."""
154 regions = {
155 name: RegionTensor.leaf(name, shard.logical_shape or shard.global_shape, shard.tensor.dtype)
156 for name, shard in shards.items()
157 }
158 replacements = getattr(model, "_hp_used_replacement_weight_conversions", None)
159 if replacements is not None:
160 stages = [replacements, getattr(model, "_hp_used_base_weight_conversions", [])]
161 else:
162 conversions = getattr(model, "_weight_conversions", None)
163 if conversions is None:
164 conversions = get_model_conversion_mapping(model, add_legacy=False)
165 stages = [conversions]
166 for conversions in stages:
167 converters = [item for item in conversions if isinstance(item, WeightConverter)]
168 renamings = [item for item in conversions if isinstance(item, WeightRenaming)]
169 if len(converters) + len(renamings) != len(conversions):
170 raise ValueError("Unsupported recorded weight-conversion type")
171 if converters:
172 regions = _reverse_stage(model, regions, converters)
173 for renaming in reversed(renamings):
174 regions = _reverse_stage(model, regions, [renaming])
175 if any(not isinstance(value, RegionTensor) for value in regions.values()):
176 raise ValueError("Inverse conversion produced non-symbolic weights; bounded verification is unsupported")
177 return regions
180def _reverse_stage(model: nn.Module, regions: dict[str, RegionTensor],
181 conversions: list[Any]) -> dict[str, RegionTensor]:
182 """Undo one stage, respecting anchored names relative to the model root.
184 Loading resolves the base-model prefix after applying its renamings. Match
185 full scoped names first, then relative names; symbolic leaves retain the
186 original live FQNs so that changing lookup names cannot change the data read.
187 """
188 prefix = getattr(model, "base_model_prefix", "")
189 prefix = prefix + "." if prefix else ""
190 probes = [conversion.reverse_transform() for conversion in conversions]
191 normalized = {}
192 for name, region in regions.items():
193 lookup = name
194 if (prefix and name.startswith(prefix)
195 and not any(probe.rename_source_key(name)[1] is not None for probe in probes)):
196 relative = name[len(prefix):]
197 if any(probe.rename_source_key(relative)[1] is not None for probe in probes):
198 lookup = relative
199 if lookup in normalized:
200 raise ValueError(f"Ambiguous inverse conversion after resolving base-model prefix: {lookup}")
201 normalized[lookup] = region
202 return revert_weight_conversion(_InverseModel(model, conversions), normalized)
205def _model_box(block: Any, offsets: tuple[int, ...],
206 lengths: tuple[int, ...]) -> tuple[tuple[int, ...], tuple[int, ...]]:
207 """Map a rectangular recovered HF region back to its live model coordinates."""
208 delta = tuple(start - origin for start, origin in zip(offsets, block.offsets))
209 base = tuple(
210 origin + sum(step[dim] * shift for step, shift in zip(block.steps, delta))
211 for dim, origin in enumerate(block.base)
212 )
213 model_lengths = tuple(
214 1 + sum(step[dim] * (size - 1) for step, size in zip(block.steps, lengths))
215 for dim in range(len(base))
216 )
217 return base, model_lengths
220def _live_tile(block: Any, shard: CheckpointTensorShard, offsets: tuple[int, ...],
221 lengths: tuple[int, ...]) -> torch.Tensor:
222 """Read one transformed tile from the live model shard on CPU."""
223 base, _ = _model_box(block, offsets, lengths)
224 tensor = shard.tensor.detach()
225 strides = tensor.stride()
226 tile_strides = tuple(sum(step * stride for step, stride in zip(column, strides)) for column in block.steps)
227 storage_offset = tensor.storage_offset() + sum(
228 (origin - start) * stride for origin, start, stride in zip(base, shard.offsets, strides)
229 )
230 values = tensor.as_strided(lengths, tile_strides, storage_offset).cpu()
231 for operation in block.post:
232 values = operation(values)
233 return values
236def _difference(actual: torch.Tensor, expected: torch.Tensor, storage_dtype: torch.dtype) -> dict[str, Any]:
237 """Count raw and load-dtype mismatches for one pair of tensor tiles."""
238 if actual.is_complex() or expected.is_complex():
239 raise ValueError("Complex weight checking is not supported")
240 if tuple(actual.shape) != tuple(expected.shape):
241 raise ValueError(f"Compared weight tiles have different shapes: {actual.shape}, {expected.shape}")
242 raw_bad = actual != expected
243 if actual.is_floating_point() and expected.is_floating_point():
244 raw_bad |= (actual == 0) & (expected == 0) & (torch.signbit(actual) != torch.signbit(expected))
245 nonfinite = ~(torch.isfinite(actual) & torch.isfinite(expected))
246 raw_count = int(torch.count_nonzero(raw_bad))
247 nonfinite_count = int(torch.count_nonzero(nonfinite))
248 if not raw_count and not nonfinite_count:
249 return {
250 "elements": actual.numel(), "exact_reference_mismatches": 0, "load_dtype_mismatches": 0,
251 "nonfinite_elements": 0, "max_abs_error": 0.0, "max_load_dtype_abs_error": 0.0,
252 "first_difference": None,
253 }
254 adjusted = expected.to(storage_dtype).to(actual.dtype)
255 bad = actual != adjusted
256 if actual.is_floating_point() and expected.is_floating_point():
257 bad |= (actual == 0) & (adjusted == 0) & (torch.signbit(actual) != torch.signbit(adjusted))
258 error = (actual.double() - expected.double()).abs()
259 adjusted_error = (actual.double() - adjusted.double()).abs()
260 first = None
261 mask = bad | nonfinite
262 load_mismatch = bool(torch.any(mask))
263 if not load_mismatch:
264 mask = raw_bad
265 if bool(torch.any(mask)):
266 flat_index = int(torch.argmax(mask.reshape(-1).to(torch.int8)))
267 index = []
268 remainder = flat_index
269 for size in reversed(actual.shape):
270 index.append(remainder % size)
271 remainder //= size
272 first = {
273 "kind": "load_dtype_mismatch" if load_mismatch else "dtype_cast",
274 "tile_index": list(reversed(index)),
275 "actual": _json_number(actual.reshape(-1)[flat_index]),
276 "reference": _json_number(expected.reshape(-1)[flat_index]),
277 "reference_in_load_dtype": _json_number(adjusted.reshape(-1)[flat_index]),
278 }
279 return {
280 "elements": actual.numel(),
281 "exact_reference_mismatches": raw_count,
282 "load_dtype_mismatches": int(torch.count_nonzero(bad)),
283 "nonfinite_elements": nonfinite_count,
284 "max_abs_error": float(error.max()) if error.numel() and bool(torch.isfinite(error).all()) else None,
285 "max_load_dtype_abs_error": (
286 float(adjusted_error.max())
287 if adjusted_error.numel() and bool(torch.isfinite(adjusted_error).all()) else None
288 ),
289 "first_difference": first,
290 }
293def _json_number(value: torch.Tensor) -> float | str:
294 number = float(value)
295 return number if math.isfinite(number) else str(number)
298def _accumulate(record: dict[str, Any], tile: dict[str, Any], offsets: tuple[int, ...]) -> None:
299 """Merge tile comparison counts and retain the first mismatch."""
300 for field in ("elements", "exact_reference_mismatches", "load_dtype_mismatches", "nonfinite_elements"):
301 record[field] = record.get(field, 0) + tile[field]
302 for field in ("max_abs_error", "max_load_dtype_abs_error"):
303 error = tile[field]
304 if error is not None:
305 record[field] = max(record.get(field, 0.0), error)
306 existing = record.get("first_difference")
307 candidate = tile["first_difference"]
308 if candidate is not None and (existing is None or (
309 existing["kind"] == "dtype_cast" and candidate["kind"] == "load_dtype_mismatch")):
310 first = dict(tile["first_difference"])
311 first["hf_index"] = [start + index for start, index in zip(offsets, first.pop("tile_index"))]
312 record["first_difference"] = first
315def _padding_check(name: str, shard: CheckpointTensorShard, limit: int) -> dict[str, Any] | None:
316 """Check that physical padding beyond the logical tensor is zero-filled."""
317 logical = shard.logical_shape
318 if logical is None or logical == shard.global_shape:
319 return None
320 if (len(logical) != len(shard.global_shape)
321 or any(left > right for left, right in zip(logical, shard.global_shape))):
322 raise ValueError(f"Invalid logical checkpoint shape for {name}")
323 record = {"model_key": name, "kind": "padding", "elements": 0}
324 for box in checkpoint_padding_boxes(logical, shard.offsets, tuple(shard.tensor.shape)):
325 for offsets, sizes in tile_box(*box, limit):
326 slices = tuple(slice(left, left + size) for left, size in zip(offsets, sizes))
327 values = shard.tensor.detach()[slices].cpu()
328 _accumulate(record, _difference(values, torch.zeros_like(values), values.dtype), offsets)
329 if record.get("first_difference"):
330 record["first_difference"]["local_index"] = record["first_difference"].pop("hf_index")
331 return record
334def _check_local(model: nn.Module, reference: _Reference, limit: int) -> dict[str, Any]:
335 """Compare one rank's live storage and calculate its source coverage."""
336 shards = _model_shards(model)
337 recovered = _inverse_regions(model, shards)
338 source_coverage = defaultdict(list)
339 model_coverage = defaultdict(list)
340 records = []
341 unmatched = []
342 prefix = getattr(model, "base_model_prefix", "") + "."
343 for number, (recovered_name, region) in enumerate(sorted(recovered.items()), start=1):
344 name = recovered_name
345 if name not in reference.shapes and name.startswith(prefix):
346 name = name[len(prefix):]
347 if name not in reference.shapes:
348 unmatched.append(recovered_name)
349 continue
350 if tuple(region.shape) != reference.shapes[name]:
351 raise ValueError(f"Recovered HF shape mismatch: {name}: {tuple(region.shape)} vs {reference.shapes[name]}")
352 record = {
353 "hf_key": name, "shape": list(region.shape), "elements": 0,
354 "model_keys": sorted(region.sources()),
355 }
356 if math.prod(region.shape) >= 1 << 30 or number % 200 == 0:
357 logger.info("Checking HF tensor %d/%d: %s, shape=%s", number, len(recovered), name, tuple(region.shape))
358 for block in region.blocks:
359 shard = shards[block.source]
360 box = local_block_box(block, shard.offsets, tuple(shard.tensor.shape))
361 if box is None:
362 continue
363 source_coverage[name].append(box)
364 model_coverage[block.source].append(_model_box(block, *box))
365 for offsets, lengths in tile_box(*box, limit):
366 actual = _live_tile(block, shard, offsets, lengths)
367 expected = reference.read(name, offsets, lengths)
368 difference = _difference(actual, expected, shard.tensor.dtype)
369 if difference["first_difference"]:
370 difference["first_difference"].update(
371 model_key=block.source, storage_dtype=str(shard.tensor.dtype),
372 reference_dtype=str(expected.dtype),
373 )
374 _accumulate(record, difference, offsets)
375 if record["elements"]:
376 records.append(record)
377 uncovered = []
378 for name, shard in shards.items():
379 padding = _padding_check(name, shard, limit)
380 padding_elements = 0
381 if padding is not None:
382 records.append(padding)
383 padding_elements = padding["elements"]
384 covered = box_union_volume(model_coverage[name]) + padding_elements
385 if covered != shard.tensor.numel():
386 uncovered.append({"model_key": name, "checked": covered, "local_elements": shard.tensor.numel()})
387 return {
388 "tensors": records, "source_coverage": dict(source_coverage), "unmatched_recovered_keys": unmatched,
389 "uncovered_model_shards": uncovered, "model_tensor_keys": list(shards),
390 }
393def _gather(value: Any) -> list[Any]:
394 if not (dist.is_available() and dist.is_initialized()):
395 return [value]
396 values = [None] * dist.get_world_size()
397 dist.all_gather_object(values, value)
398 return values
401def _summarize(reports: list[dict[str, Any]], shapes: dict[str, tuple[int, ...]]) -> dict[str, Any]:
402 """Combine rank reports into global mismatch and coverage results."""
403 coverage = defaultdict(list)
404 errors = []
405 mismatches = 0
406 raw_mismatches = 0
407 nonfinite = 0
408 for rank, report in enumerate(reports):
409 if report.get("error"):
410 errors.append({"rank": rank, "error": report["error"]})
411 for name, boxes in report.get("source_coverage", {}).items():
412 coverage[name].extend(boxes)
413 if report.get("uncovered_model_shards") or report.get("unmatched_recovered_keys"):
414 errors.append({"rank": rank, "error": "Model weights are not completely covered; see rank report"})
415 mismatches += report.get("load_dtype_mismatches", 0)
416 nonfinite += report.get("nonfinite_elements", 0)
417 raw_mismatches += report.get("exact_reference_mismatches", 0)
418 uncovered = {
419 name: {"checked": box_union_volume(coverage[name]), "expected": math.prod(shape)}
420 for name, shape in shapes.items() if box_union_volume(coverage[name]) != math.prod(shape)
421 }
422 return {
423 "status": "FAIL" if errors or uncovered or mismatches or nonfinite else "PASS",
424 "exact_reference_match": raw_mismatches == 0 and not errors and not uncovered and not nonfinite,
425 "load_dtype_mismatches_including_replicas": mismatches,
426 "exact_reference_mismatches_including_replicas": raw_mismatches,
427 "nonfinite_elements_including_replicas": nonfinite,
428 "uncovered_hf_tensors": uncovered, "errors": errors,
429 "ranks": len(reports), "hf_tensors": len(shapes),
430 "hf_elements": sum(math.prod(shape) for shape in shapes.values()),
431 "model_tensors": len({name for report in reports for name in report.get("model_tensor_keys", [])}),
432 }
435def _tensor_table(reports: list[dict[str, Any]], summary: dict[str, Any]) -> list[dict[str, Any]]:
436 """Build per-tensor rows from rank reports and global coverage."""
437 rows = {}
438 for report in reports:
439 rank = report["rank"]
440 for tensor in report.get("tensors", []):
441 key = tensor.get("hf_key", "<padding>:" + tensor.get("model_key", ""))
442 row = rows.setdefault(key, {"name": key, "kind": tensor.get("kind", "weight"), "ranks_with_mismatch": []})
443 for field in ("elements", "exact_reference_mismatches", "load_dtype_mismatches", "nonfinite_elements"):
444 row[field] = row.get(field, 0) + tensor.get(field, 0)
445 for field in ("max_abs_error", "max_load_dtype_abs_error"):
446 row[field] = max(row.get(field, 0.0), tensor.get(field, 0.0))
447 if tensor.get("load_dtype_mismatches", 0) or tensor.get("nonfinite_elements", 0):
448 row["ranks_with_mismatch"].append(rank)
449 existing, candidate = row.get("first_difference"), tensor.get("first_difference")
450 if candidate is not None and (existing is None or (
451 existing["kind"] == "dtype_cast" and candidate["kind"] == "load_dtype_mismatch")):
452 row["first_difference"] = dict(candidate, rank=rank)
453 for row in rows.values():
454 if row["load_dtype_mismatches"] or row["nonfinite_elements"]:
455 row["result"] = "MISMATCH"
456 elif row["exact_reference_mismatches"]:
457 row["result"] = "DTYPE_CAST"
458 else:
459 row["result"] = "EXACT"
460 for name, counts in summary["uncovered_hf_tensors"].items():
461 row = rows.setdefault(name, {
462 "name": name, "kind": "weight", "elements": 0, "exact_reference_mismatches": 0,
463 "load_dtype_mismatches": 0, "nonfinite_elements": 0, "max_abs_error": 0.0,
464 "max_load_dtype_abs_error": 0.0, "ranks_with_mismatch": [],
465 })
466 row["coverage"] = counts
467 if row.get("result") != "MISMATCH":
468 row["result"] = "INCOMPLETE"
469 order = {"MISMATCH": 0, "INCOMPLETE": 1, "DTYPE_CAST": 2, "EXACT": 3}
470 return sorted(rows.values(), key=lambda row: (order[row["result"]], row["name"]))
473def _print_table(rows: list[dict[str, Any]], summary: dict[str, Any], root: Path) -> None:
474 """Log the human-readable summary table on rank zero."""
475 lines = [
476 f"HF weight check: {summary['status']} | ranks={summary['ranks']} | "
477 f"model tensors={summary['model_tensors']} | HF tensors={summary['hf_tensors']} | "
478 f"weight elements={summary['hf_elements']:,}",
479 f"EXACT={summary['exact_tensors']} DTYPE_CAST={summary['dtype_cast_tensors']} "
480 f"MISMATCH={summary['mismatched_tensors']} INCOMPLETE={summary['incomplete_tensors']} "
481 f"padding checks={summary['padding_tensors']}",
482 "Result | HF tensor / padding table | Checked elems | Mismatches | Max abs error",
483 "------------+---------------------------------------------------+---------------+------------+--------------",
484 ]
485 for row in rows[:50]:
486 error = "nonfinite" if row["nonfinite_elements"] else f"{row['max_abs_error']:.6g}"
487 lines.append(f"{row['result']:<11} | {row['name'][-49:]:<49} | {row['elements']:>13,} | "
488 f"{row['load_dtype_mismatches']:>10,} | {error}")
489 first = row.get("first_difference")
490 if row["result"] == "MISMATCH" and first:
491 index = first.get("hf_index", first.get("local_index"))
492 lines.append(f" {row['name']}: rank={first['rank']} index={index} "
493 f"actual={first['actual']} reference={first['reference']} "
494 f"reference_in_load_dtype={first['reference_in_load_dtype']}")
495 if len(rows) > 50:
496 lines.append(f"Showing 50/{len(rows)} rows (mismatches first); complete table: {root / 'tensors.csv'}")
497 lines.append("Checked elements and mismatch counts in table include replicated rank observations.")
498 lines.append(f"Reports: {root}")
499 logger.info("\n%s", "\n".join(lines))
502def _write_table(rows: list[dict[str, Any]], root: Path) -> None:
503 """Write per-tensor comparison results as a CSV report."""
504 fields = [
505 "name", "kind", "result", "elements", "exact_reference_mismatches", "load_dtype_mismatches",
506 "nonfinite_elements", "max_abs_error", "max_load_dtype_abs_error", "ranks_with_mismatch", "first_difference",
507 "coverage",
508 ]
509 with (root / "tensors.csv").open("w", encoding="utf-8", newline="") as handle:
510 writer = csv.DictWriter(handle, fieldnames=fields)
511 writer.writeheader()
512 for row in rows:
513 writer.writerow({field: json.dumps(row[field]) if isinstance(row.get(field), (list, dict))
514 else row.get(field) for field in fields})
517@torch.no_grad()
518def verify_hf_weights(model: nn.Module, pretrained_path: str, output_dir: str, *,
519 chunk_elements: int = 1 << 20, phase: str = "caller_managed_before_optimizer") -> dict[str, Any]:
520 """Reverse-convert live model weights and compare every element to local HF storage.
522 All ranks must call together, before parameter updates. The automatic builder
523 hook runs before the first forward. The checker never writes model tensors.
524 Quantized source weights are decoded to BF16 using Transformers; ordinary
525 source dtypes are preserved. Both raw equality and exact equality after the
526 declared load-dtype cast are reported. No tolerance is used. Padding is
527 checked against zero separately. Unsupported inverse conversions, missing
528 model/source coverage and nonfinite values fail rather than being skipped.
530 Args:
531 model: Finalized, materialized and weight-loaded model.
532 pretrained_path: Local checkpoint; its index defines the comparison scope.
533 output_dir: Directory for per-rank reports and summary.json.
534 chunk_elements: Maximum number of elements per comparison tile.
535 phase: Description of the invocation point, recorded in the reports.
537 Returns:
538 Global summary on every rank. PASS requires exact load-dtype agreement.
540 Raises:
541 RuntimeError: Any rank finds mismatches, unsupported conversions or missing coverage.
542 """
543 rank = dist.get_rank() if dist.is_available() and dist.is_initialized() else 0
544 root = Path(output_dir).expanduser().resolve()
545 local, shapes = _check_rank_weights(model, pretrained_path, root, rank, chunk_elements, phase)
546 reports = _gather(local)
547 # All ranks use rank zero's reference scope, including when a local reader failed.
548 scopes = _gather(shapes if rank == 0 else None)
549 summary = _summarize(reports, scopes[0] or shapes)
550 summary.update(
551 phase=phase, reference="HF decoded weights; load-dtype equality, no tolerance",
552 reference_scope="The selected HF index; unused modalities or cropped-out layers are not included",
553 )
554 rows = _tensor_table(reports, summary)
555 _add_result_counts(rows, summary)
556 write_error = _write_summary_reports(rank, root, rows, summary)
557 write_errors = _gather(write_error)
558 _raise_check_failure(summary, write_errors, root)
559 logger.info("HF weight check PASS on %d ranks; exact reference match=%s; reports=%s",
560 summary["ranks"], summary["exact_reference_match"], root)
561 return summary
564def _check_rank_weights(model: nn.Module, pretrained_path: str, root: Path, rank: int,
565 chunk_elements: int, phase: str) -> tuple[dict[str, Any], dict[str, tuple[int, ...]]]:
566 """Run one rank's bounded check and capture errors for the later collective."""
567 shapes = {}
568 local = {"rank": rank, "phase": phase, "checkpoint": str(pretrained_path)}
569 try:
570 if chunk_elements < 1:
571 raise ValueError("chunk_elements must be positive")
572 root.mkdir(parents=True, exist_ok=True)
573 reference = _Reference(pretrained_path)
574 shapes = reference.shapes
575 if rank == 0:
576 local["source_files"] = sorted({str(path.resolve()) for path in reference.index.files_by_key.values()})
577 logger.info("Starting HF weight check on rank %d: %d source tensors; phase=%s", rank, len(shapes), phase)
578 local.update(_check_local(model, reference, chunk_elements))
579 for field in ("exact_reference_mismatches", "load_dtype_mismatches", "nonfinite_elements"):
580 local[field] = sum(record.get(field, 0) for record in local["tensors"])
581 (root / f"rank_{rank:05d}.json").write_text(json.dumps(local, indent=2, allow_nan=False), encoding="utf-8")
582 except Exception as exc: # pylint: disable=broad-exception-caught
583 # A local failure must reach the collective so other ranks cannot continue training.
584 local["error"] = f"{type(exc).__name__}: {exc}"
585 try:
586 root.mkdir(parents=True, exist_ok=True)
587 (root / f"rank_{rank:05d}.json").write_text(json.dumps(local, indent=2), encoding="utf-8")
588 except OSError:
589 logger.exception("Cannot write HF weight-check rank report")
590 return local, shapes
593def _add_result_counts(rows: list[dict[str, Any]], summary: dict[str, Any]) -> None:
594 """Add tensor-result totals to the global verification summary."""
595 weight_rows = [row for row in rows if row["kind"] == "weight"]
596 summary.update(
597 exact_tensors=sum(row["result"] == "EXACT" for row in weight_rows),
598 dtype_cast_tensors=sum(row["result"] == "DTYPE_CAST" for row in weight_rows),
599 mismatched_tensors=sum(row["result"] == "MISMATCH" for row in weight_rows),
600 incomplete_tensors=len(summary["uncovered_hf_tensors"]),
601 padding_tensors=len(rows) - len(weight_rows),
602 )
605def _write_summary_reports(rank: int, root: Path, rows: list[dict[str, Any]],
606 summary: dict[str, Any]) -> str | None:
607 """Write rank-zero aggregate reports and return any filesystem error."""
608 if rank != 0:
609 return None
610 try:
611 root.mkdir(parents=True, exist_ok=True)
612 _write_table(rows, root)
613 (root / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
614 _print_table(rows, summary, root)
615 except OSError as exc:
616 return str(exc)
617 return None
620def _raise_check_failure(summary: dict[str, Any], write_errors: list[Any], root: Path) -> None:
621 """Fail collectively when any weight or report check did not pass."""
622 if summary["status"] != "PASS" or any(write_errors):
623 raise RuntimeError(
624 f"HF weight check failed: value mismatches={summary['load_dtype_mismatches_including_replicas']}, "
625 f"nonfinite={summary['nonfinite_elements_including_replicas']}, "
626 f"uncovered HF tensors={len(summary['uncovered_hf_tensors'])}; "
627 f"rank errors={summary['errors'][:3]}, report write errors={write_errors}; inspect {root}"
628 )