Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / ops / parallel_pad.py: 92%
40 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 Pad operator.
17"""
19from typing import Tuple
21from .parallel_ops import DistributedOp
24def _normalize_pad_args(x, pad, mode='constant', value=None):
25 return (x, pad, mode, value), {}
28class PadDistributedOp(DistributedOp):
29 """Distributed implementation for Pad operator."""
31 def preprocess(self, args: tuple, kwargs: dict) -> tuple:
32 """
33 Preprocess arguments for Pad operator.
35 Args:
36 args (tuple): Input tensor followed by pad arguments.
37 kwargs (dict): Keyword arguments for pad.
39 Returns:
40 tuple: (local_args, local_kwargs, cache_values)
41 """
42 args, _ = _normalize_pad_args(*args, **kwargs)
43 input_tensor = args[0]
44 pad, mode, value = args[1], args[2], args[3]
46 local_args = (input_tensor.to_local(), pad, mode, value)
47 local_kwargs = {}
48 cache_values = [input_tensor.layout, pad]
49 return local_args, local_kwargs, cache_values
51 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]: # pylint: disable=W0221
52 """
53 Infer output layout for Pad operator.
55 Rules:
56 1. Input must not have Partial status.
57 2. pad must be a tuple or list with even length.
58 3. The number of padded dimensions must not exceed input ndim.
59 4. Any dimension with non-zero padding must not be sharded.
60 5. Output layout is identical to the input layout.
62 Args:
63 cache_values (list): [input_layout, pad].
65 Returns:
66 tuple: ((output_layout,), None)
68 Raises:
69 ValueError: If input has Partial status, pad is invalid, or padding is
70 attempted on a sharded dimension.
71 """
72 if len(cache_values) != 2:
73 raise ValueError(
74 f"For {self.op_name}, cache_values length should be 2, but got {len(cache_values)}"
75 )
77 input_layout, pad = cache_values[0], cache_values[1]
78 if input_layout is None:
79 raise ValueError(f"For {self.op_name}, pad requires a valid input tensor layout.")
81 self._check_partial_inputs([input_layout])
83 tensor_map = input_layout.alias_tensor_map
84 ndim = len(tensor_map)
86 if not isinstance(pad, (tuple, list)):
87 raise ValueError(
88 f"For {self.op_name}, expected pad tuple or list, but got {type(pad)}"
89 )
91 pad_len = len(pad)
93 if pad_len % 2 != 0:
94 raise ValueError(f"For {self.op_name}, Pad tuple length must be even, but got {pad_len}")
96 # Pytorch pad tuple format: (last_dim_left, last_dim_right, 2nd_last_left, 2nd_last_right, ...)
97 # We need to check if any dimension being padded is currently sharded.
98 num_padded_dims = pad_len // 2
99 if num_padded_dims > ndim:
100 raise ValueError(
101 f"For {self.op_name}, Padding {num_padded_dims} dimensions but tensor only has {ndim} dimensions."
102 )
104 for i in range(num_padded_dims):
105 # Calculate the dimension index in the tensor (from 0 to ndim-1)
106 # pad index 0,1 -> last dimension (ndim - 1)
107 # pad index 2,3 -> second to last dimension (ndim - 2)
108 dim_index = ndim - 1 - i
110 pad_left = pad[2 * i]
111 pad_right = pad[2 * i + 1]
113 # If padding is applied on this dimension
114 if pad_left != 0 or pad_right != 0:
115 axis_alias = tensor_map[dim_index]
116 is_sharded = (any(alias != "None" for alias in axis_alias)
117 if isinstance(axis_alias, (tuple, list))
118 else axis_alias != "None")
120 if is_sharded:
121 raise ValueError(
122 f"For {self.op_name}, Distributed Pad operator does not support padding "
123 f"on a sharded dimension. "
124 f"Dimension {dim_index} (alias: {axis_alias}) is sharded. "
125 f"Please redistribute the tensor to Replicate status on this dimension before padding."
126 )
128 # If no sharded dimension is padded, the output layout is identical to the input layout.
129 # The local tensor shape changes, but the mapping from device mesh to tensor dimensions remains valid.
130 return ((input_layout,), None)
132 # Note: get_expand_impl is not overridden because we default to returning None.
133 # OpDispatcher will use the original function (e.g., torch.nn.functional.pad) on the local tensor.
134 # Since we ensured the padded dimensions are Replicated, local padding is mathematically correct.