Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / ops / parallel_repeat.py: 100%
54 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-06 05:41 +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"""
16Distributed implementation for Repeat operator.
17"""
19from typing import Tuple
21from hyper_parallel.core.dtensor.layout import Layout
22from .parallel_ops import DistributedOp
25def _normalize_repeat_args(x, *sizes):
26 return (x,) + sizes, {}
29class RepeatDistributedOp(DistributedOp):
30 """Distributed implementation for torch.Tensor.repeat."""
32 def preprocess(self, args: tuple, kwargs: dict) -> tuple:
33 """
34 Preprocess arguments for torch.Tensor.repeat.
36 Args:
37 args (tuple): Input tensor followed by repeat sizes.
38 kwargs (dict): Keyword arguments.
40 Returns:
41 tuple: (local_args, local_kwargs, cache_values)
42 """
43 args, _ = _normalize_repeat_args(*args, **kwargs)
44 input_tensor = args[0]
45 sizes = args[1:]
47 local_args = (input_tensor.to_local(),) + sizes
48 local_kwargs = {}
49 cache_values = [input_tensor.layout, sizes]
50 return local_args, local_kwargs, cache_values
52 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]: # pylint: disable=W0221
53 """
54 Infer output layout for torch.Tensor.repeat.
56 PyTorch semantics:
57 - Repeats this tensor along the specified dimensions.
58 - If the number of repeat dimensions is larger than the tensor dimensions,
59 the tensor is implicitly unsqueezed at the front.
60 - The number of repeat dimensions cannot be smaller than the tensor dimensions.
61 - Dimensions being repeated (>1 or 0) MUST be unsharded.
63 Rules:
64 1. Input must not have Partial status.
65 2. Repeat sizes must be provided.
66 3. New prepended dimensions are unsharded.
67 4. Existing dimensions with repeat size 1 preserve the input sharding.
68 5. Existing dimensions with repeat size other than 1 must not be sharded.
70 Args:
71 cache_values (list): [input_layout, repeat_sizes].
73 Returns:
74 tuple: ((output_layout,), None)
76 Raises:
77 ValueError: If input layout is missing, input has Partial status, repeat sizes
78 are missing, or a sharded dimension is repeated.
79 """
80 if not cache_values or cache_values[0] is None:
81 raise ValueError(
82 f"For {self.op_name}, repeat requires a valid input tensor layout."
83 )
85 input_layout = cache_values[0]
86 self._check_partial_inputs([input_layout])
88 in_tensor_map = input_layout.tensor_map
89 in_alias_map = input_layout.alias_tensor_map
90 input_ndim = len(in_tensor_map)
92 if len(cache_values) < 2 or not cache_values[1]:
93 raise ValueError(
94 f"For {self.op_name}, repeat requires repeat sizes in cache_values."
95 )
97 # Robustly handle sizes unpacking (e.g., if args are packed as a single tuple)
98 repeat_sizes = cache_values[1]
99 if len(repeat_sizes) == 1 and isinstance(repeat_sizes[0], (tuple, list)):
100 flat_args = repeat_sizes[0]
101 else:
102 flat_args = repeat_sizes
104 # Normalize repeat sizes to tuple of ints
105 repeats = []
106 for arg in flat_args:
107 if not isinstance(arg, int):
108 arg = int(arg)
109 repeats.append(arg)
110 repeats = tuple(repeats)
111 output_ndim = len(repeats)
113 num_new_dims = output_ndim - input_ndim
114 output_map = []
116 # Rule 1: New prepended dimensions are always unsharded
117 for _ in range(num_new_dims):
118 output_map.append("None")
120 # Rule 2: Process existing dimensions
121 for i in range(input_ndim):
122 repeat_idx = num_new_dims + i
123 repeat_times = repeats[repeat_idx]
125 if repeat_times == 1:
126 # If the dimension is not repeated, keep the original sharding
127 output_map.append(in_alias_map[i])
128 else:
129 # If the dimension is repeated (or zeroed), it cannot be currently sharded
130 mapping = in_alias_map[i]
131 if mapping != "None":
132 raise ValueError(
133 f"For {self.op_name}, Cannot repeat dimension {i} which is sharded. "
134 f"Please redistribute (unshard) the tensor along this dimension first."
135 )
136 # Repeated dimension remains unsharded in output
137 output_map.append("None")
139 # Construct output layout mapping
140 mesh_shape = input_layout.mesh_shape
141 alias_name = input_layout.alias_name
142 rank_list = input_layout.rank_list
144 # Instantiate new layout
145 output_layout = Layout(
146 mesh_shape=mesh_shape,
147 alias_name=alias_name,
148 rank_list=rank_list
149 )
150 output_layout = output_layout(*output_map)
152 return ((output_layout,), None)