Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / ops / parallel_stack.py: 98%
48 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-13 05:07 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-13 05:07 +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 Stack operator.
17"""
19from typing import Tuple
21from hyper_parallel.core.dtensor.layout import Layout
22from .parallel_ops import DistributedOp
24# pylint: disable=unused-argument
27def _normalize_stack_args(tensors, dim=0, **kwargs):
28 """
29 Normalize arguments for torch.stack.
30 """
31 return (tensors,), {'dim': dim}
34class StackDistributedOp(DistributedOp):
35 """Distributed implementation for Stack operator."""
37 def preprocess(self, args, kwargs):
38 """
39 Preprocess input arguments and extract local components.
41 Normalizes args, explicitly extracts parameters, and prepares
42 local tensors and cache values without validation logic.
43 """
44 args, kwargs = _normalize_stack_args(*args, **kwargs)
46 # Explicit parameter extraction
47 tensors = args[0]
48 dim = kwargs['dim']
50 # Extract local tensors and layouts
51 local_tensors = tuple(t.to_local() if hasattr(t, "to_local") else t for t in tensors)
52 layouts = [getattr(t, "layout", None) for t in tensors]
54 # Construct local args and kwargs for the inner op execution
55 local_args = (local_tensors,)
56 local_kwargs = {'dim': dim}
58 # Flatten layouts and append dim for caching and inference
59 cache_values = layouts + [dim]
61 return local_args, local_kwargs, cache_values
63 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
64 """
65 Infer output layout for Stack operator.
67 Rules:
68 1. At least one input DTensor is required.
69 2. All input tensors must have identical layouts.
70 3. A new Replicate dimension is inserted at the stack position.
72 Args:
73 cache_values (list): [layout_0, ..., layout_n, dim]
75 Returns:
76 tuple: ((output_layout,), None)
78 Raises:
79 ValueError: If any rule above is violated.
80 """
81 layouts = cache_values[:-1]
82 dim = cache_values[-1]
84 if not layouts:
85 raise ValueError(
86 f"For {self.op_name}, stack requires at least one input tensor."
87 )
89 valid_layouts = [lyt for lyt in layouts if lyt is not None]
91 if not valid_layouts:
92 raise ValueError(
93 f"For {self.op_name}, stack requires at least one input DTensor."
94 )
96 if not self._allow_partial_inputs:
97 self._check_partial_inputs(valid_layouts)
99 # Reference layout to validate consistency across all input tensors
100 base_layout = valid_layouts[0]
101 for layout in valid_layouts[1:]:
102 if layout != base_layout:
103 raise ValueError(
104 f"For {self.op_name}, all input tensors must have the same layout, "
105 f"but got base: {base_layout} and mismatched: {layout}"
106 )
108 ndim = len(base_layout.tensor_map)
110 # Normalize and validate the dimension. For stack, valid range is [-ndim - 1, ndim]
111 actual_dim = dim if dim >= 0 else dim + ndim + 1
112 if actual_dim < 0 or actual_dim > ndim:
113 raise ValueError(
114 f"For {self.op_name}, dimension out of range, "
115 f"expected to be in range of [{-ndim - 1}, {ndim}], but got {dim}"
116 )
118 # 2. Layout Inference Logic
119 in_tensor_map = base_layout.tensor_map
121 # Insert an unsharded mapping (-1) at the newly created dimension
122 output_tensor_map = list(in_tensor_map)
123 output_tensor_map.insert(actual_dim, -1)
125 mesh_shape = base_layout.mesh_shape
126 alias_name = base_layout.alias_name
127 rank_list = base_layout.rank_list
129 def idx_to_alias(idx, aliases):
130 """Map tensor_map index back to the alias string."""
131 if idx == -1:
132 return "None"
133 # Reverse indexing mapped to the framework's layout design
134 return aliases[len(aliases) - idx - 1]
136 output_alias_map = tuple(idx_to_alias(idx, alias_name) for idx in output_tensor_map)
138 # Reconstruct the output layout
139 output_layout = Layout(
140 mesh_shape=mesh_shape,
141 alias_name=alias_name,
142 rank_list=rank_list
143 )
145 # Apply the placement mappings via the __call__ method
146 output_layout = output_layout(*output_alias_map)
148 return ((output_layout,), None)