Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / ops / parallel_scatter_update.py: 83%
58 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 ScatterUpdate operator.
17"""
19from typing import Tuple
21from hyper_parallel.core.dtensor.layout import Layout
22from .parallel_ops import DistributedOp
25def _normalize_scatter_update_args(input_tensor, indices, updates):
26 return (input_tensor, indices, updates), {}
29class ScatterUpdateDistributedOp(DistributedOp):
30 """Distributed implementation for ScatterUpdate operator."""
32 def __init__(self, op_name: str):
33 super().__init__(op_name)
34 self._allow_partial_inputs = True
36 def preprocess(self, args: tuple, kwargs: dict) -> tuple:
37 """
38 Preprocess arguments for ScatterUpdate operator.
40 Args:
41 args (tuple): Input arguments containing input_tensor, indices, and updates.
42 kwargs (dict): Keyword arguments (none expected).
44 Returns:
45 tuple: (local_args, local_kwargs, cache_values)
46 """
47 args, kwargs = _normalize_scatter_update_args(*args, **kwargs)
48 x, indices, updates = args
50 local_args = (
51 x.to_local() if hasattr(x, '_layout') else x,
52 indices.to_local() if hasattr(indices, '_layout') else indices,
53 updates.to_local() if hasattr(updates, '_layout') else updates,
54 )
56 cache_values = [
57 x.layout if hasattr(x, '_layout') else None,
58 indices.layout if hasattr(indices, '_layout') else None,
59 updates.layout if hasattr(updates, '_layout') else None,
60 ]
61 local_kwargs = kwargs
62 return local_args, local_kwargs, cache_values
64 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
65 """
66 Infer output layout for ScatterUpdate operator.
68 Rules:
69 1. All three inputs must be DTensors (layout not None).
70 2. Input first dimension (indexed/scatter dim) cannot be sharded.
71 3. Indices must be all-Replicate on every dimension.
72 4. updates[0:indices_ndim] prefix dimensions cannot be sharded.
73 5. updates rank must equal indices_ndim + input_ndim - 1.
74 6. updates[indices_ndim:] sharding must match input[1:] sharding.
75 7. Output layout inherits input layout (including Partial status).
77 Args:
78 cache_values (list): [input_layout, indices_layout, updates_layout]
80 Returns:
81 tuple: ((output_layout,), None)
83 Raises:
84 ValueError: If any validation rule above is violated.
85 """
86 input_layout, indices_layout, updates_layout = cache_values
88 if input_layout is None:
89 raise ValueError(
90 f"For {self.op_name}, input layout should not be None"
91 )
93 if indices_layout is None:
94 raise ValueError(
95 f"For {self.op_name}, indices must be a DTensor when input is a DTensor"
96 )
98 if updates_layout is None:
99 raise ValueError(
100 f"For {self.op_name}, updates must be a DTensor when input is a DTensor"
101 )
103 # Partial inputs are intentionally allowed. The output inherits Partial status
104 # from the input layout (see lines below), making this a Partial-preserving op.
105 if not self._allow_partial_inputs:
106 self._check_partial_inputs([input_layout])
108 self._validate_strategy(input_layout, indices_layout, updates_layout)
110 output_layout = Layout(
111 mesh_shape=input_layout.mesh_shape,
112 alias_name=input_layout.alias_name,
113 rank_list=input_layout.rank_list,
114 )
115 output_layout = output_layout(*input_layout.alias_tensor_map)
117 for i, partial_op in enumerate(input_layout.partial):
118 if partial_op is not None:
119 dev_axis_name = input_layout.alias_name[i]
120 output_layout.set_partial_by_dev_axis(dev_axis_name, partial_op)
122 return ((output_layout,), None)
124 def _validate_strategy(self, input_layout, indices_layout, updates_layout):
125 """Validate sharding strategy for ScatterUpdate."""
126 input_map = input_layout.alias_tensor_map
127 indices_map = indices_layout.alias_tensor_map
128 updates_map = updates_layout.alias_tensor_map
130 if not input_map:
131 raise ValueError(
132 f"For {self.op_name}, input tensor map should not be empty"
133 )
135 if input_map[0] != "None":
136 raise ValueError(
137 f"For {self.op_name}, first dimension of input cannot be sharded, "
138 f"but it is sharded on '{input_map[0]}'"
139 )
141 for i, axis in enumerate(indices_map):
142 if axis != "None":
143 raise ValueError(
144 f"For {self.op_name}, indices cannot be sharded, "
145 f"but dimension {i} is sharded on '{axis}'"
146 )
148 indices_ndim = len(indices_map)
149 for i in range(indices_ndim):
150 if i >= len(updates_map):
151 raise ValueError(
152 f"For {self.op_name}, updates rank is smaller than indices rank"
153 )
154 if updates_map[i] != "None":
155 raise ValueError(
156 f"For {self.op_name}, first {indices_ndim} dimensions of updates "
157 f"cannot be sharded, but dimension {i} is sharded on '{updates_map[i]}'"
158 )
160 expected_updates_ndim = indices_ndim + len(input_map) - 1
161 if len(updates_map) != expected_updates_ndim:
162 raise ValueError(
163 f"For {self.op_name}, updates rank mismatch. "
164 f"Expected {expected_updates_ndim}, got {len(updates_map)}"
165 )
167 for i in range(1, len(input_map)):
168 updates_idx = indices_ndim + i - 1
169 if input_map[i] != updates_map[updates_idx]:
170 raise ValueError(
171 f"For {self.op_name}, updates sharding must match input[1:]. "
172 f"Mismatch at input dim {i}: '{input_map[i]}' != '{updates_map[updates_idx]}'"
173 )