Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / checkpoint / _weight_check_geometry.py: 96%
51 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"""Bounded rectangles for checking inverse-converted distributed weights."""
17from __future__ import annotations
19import math
20from collections.abc import Iterator, Sequence
21from typing import Any
23Box = tuple[tuple[int, ...], tuple[int, ...]]
26def tile_box(offsets: tuple[int, ...], lengths: tuple[int, ...], limit: int) -> Iterator[Box]:
27 """Split a rectangle into non-overlapping tiles of at most ``limit`` elements."""
28 if limit < 1:
29 raise ValueError("Tile limit must be positive")
30 if any(size == 0 for size in lengths):
31 return
32 if math.prod(lengths) <= limit:
33 yield offsets, lengths
34 return
35 axis = next((axis for axis, size in enumerate(lengths) if size > 1), 0)
36 step = max(1, limit // math.prod(lengths[axis + 1:]))
37 for start in range(0, lengths[axis], step):
38 child_offsets = list(offsets)
39 child_lengths = list(lengths)
40 child_offsets[axis] += start
41 child_lengths[axis] = min(step, lengths[axis] - start)
42 yield from tile_box(tuple(child_offsets), tuple(child_lengths), limit)
45def local_block_box(block: Any, shard_offsets: tuple[int, ...], shard_shape: tuple[int, ...]) -> Box | None:
46 """Intersect an inverse conversion block with one live model shard.
48 Permutations, splits and stacks map each varying model axis to at most one
49 output axis. More complex affine maps fail explicitly instead of skipping data.
50 """
51 low = [0] * len(block.lengths)
52 high = list(block.lengths)
53 for size, column in zip(block.lengths, block.steps):
54 if size > 1 and sum(step != 0 for step in column) != 1:
55 raise ValueError("Inverse conversion has a non-rectangular shard mapping")
56 for dim, origin in enumerate(block.base):
57 steps = tuple(column[dim] for column in block.steps)
58 moving = [(axis, step) for axis, step in enumerate(steps) if step]
59 start, stop = shard_offsets[dim], shard_offsets[dim] + shard_shape[dim]
60 if not moving:
61 if not start <= origin < stop:
62 return None
63 continue
64 if len(moving) != 1 or moving[0][1] != 1:
65 raise ValueError("Inverse conversion has a non-rectangular shard mapping")
66 axis, _ = moving[0]
67 low[axis] = max(low[axis], start - origin)
68 high[axis] = min(high[axis], stop - origin)
69 if any(left >= right for left, right in zip(low, high)):
70 return None
71 return (
72 tuple(origin + left for origin, left in zip(block.offsets, low)),
73 tuple(right - left for left, right in zip(low, high)),
74 )
77def box_union_volume(boxes: Sequence[Box]) -> int:
78 """Count unique elements covered by rectangular shards, including replicas."""
79 unique = set((tuple(offsets), tuple(lengths)) for offsets, lengths in boxes if all(lengths))
80 if not unique:
81 return 0
82 if not next(iter(unique))[0]:
83 return 1
84 endpoints = sorted({point for offsets, lengths in unique for point in (offsets[0], offsets[0] + lengths[0])})
85 return sum(
86 (right - left) * box_union_volume([
87 (offsets[1:], lengths[1:]) for offsets, lengths in unique
88 if offsets[0] <= left and offsets[0] + lengths[0] >= right
89 ])
90 for left, right in zip(endpoints, endpoints[1:])
91 )