Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / ops / parallel_histc_ext.py: 73%
48 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 HistcExt operator.
17"""
19from typing import Tuple
21from hyper_parallel.core.dtensor.layout import Layout
22from hyper_parallel.platform import get_platform
23from .parallel_ops import DistributedOp
25platform = get_platform()
28def _normalize_histc_args(x, bins=100, min_val=0, max_val=0):
29 return (x, bins, min_val, max_val), {}
32class HistcExtDistributedOp(DistributedOp):
33 """
34 Distributed implementation for HistcExt operator.
36 HistcExt computes the histogram of a tensor. In distributed setting:
37 - Each device computes a local histogram
38 - Local histograms are aggregated using AllReduce(SUM)
39 - Output is always replicated (1D tensor with shape (bins,))
40 """
42 def __init__(self, op_name: str = "HistcExt") -> None:
43 """Initialize HistcExtDistributedOp."""
44 super().__init__(op_name)
46 def preprocess(self, args: tuple, kwargs: dict) -> tuple:
47 """
48 Preprocess arguments for HistcExt operator.
50 Args:
51 args (tuple): Input arguments, first element is the input tensor.
52 kwargs (dict): Keyword arguments (bins, min, max).
54 Returns:
55 tuple: (local_args, local_kwargs, cache_values)
56 """
57 # Map external API parameter names (min, max) to internal names to avoid
58 # shadowing Python builtins.
59 if "min" in kwargs:
60 kwargs["min_val"] = kwargs.pop("min")
61 if "max" in kwargs:
62 kwargs["max_val"] = kwargs.pop("max")
63 args, kwargs = _normalize_histc_args(*args, **kwargs)
64 x, bins, min_val, max_val = args
65 local_args = (x.to_local(), bins, min_val, max_val)
66 local_kwargs = {}
67 cache_values = [x.layout, bins, min_val, max_val]
68 return local_args, local_kwargs, cache_values
70 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
71 """
72 Infer output layout for HistcExt operator.
74 Rules:
75 1. Input layout must not be None.
76 2. bins must be a positive integer.
77 3. min and max must be numbers with min <= max.
78 4. Output is always a 1D replicated tensor of shape (bins,).
79 5. When input is sharded, output carries Partial(sum) on sharded device axes.
81 Args:
82 cache_values (list): [input_layout, bins, min, max]
84 Returns:
85 tuple: ((output_layout,), None)
87 Raises:
88 ValueError: If any rule above is violated.
89 """
90 x_layout = cache_values[0]
91 bins = cache_values[1]
92 min_val = cache_values[2]
93 max_val = cache_values[3]
95 if not self._allow_partial_inputs:
96 self._check_partial_inputs([x_layout])
98 if x_layout is None or x_layout.mesh_shape is None:
99 raise ValueError(
100 f"For {self.op_name}, input layout should not be None, "
101 f"but got {x_layout}"
102 )
104 if not isinstance(bins, int):
105 raise ValueError(
106 f"For {self.op_name}, bins should be an integer, "
107 f"but got {type(bins).__name__}"
108 )
109 if bins <= 0:
110 raise ValueError(
111 f"For {self.op_name}, bins should be a positive integer, "
112 f"but got {bins}"
113 )
114 if not isinstance(min_val, (int, float)):
115 raise ValueError(
116 f"For {self.op_name}, min should be a number, "
117 f"but got {type(min_val).__name__}"
118 )
119 if not isinstance(max_val, (int, float)):
120 raise ValueError(
121 f"For {self.op_name}, max should be a number, "
122 f"but got {type(max_val).__name__}"
123 )
124 if min_val > max_val:
125 raise ValueError(
126 f"For {self.op_name}, min should be less than or equal to max, "
127 f"but got min={min_val}, max={max_val}"
128 )
130 output_layout = Layout(
131 mesh_shape=x_layout.mesh_shape,
132 alias_name=x_layout.alias_name,
133 rank_list=x_layout.rank_list,
134 )
135 out_layout = output_layout("None",)
137 has_sharding = any(
138 alias is not None and alias != "None"
139 for alias in x_layout.alias_tensor_map
140 )
142 if has_sharding:
143 for alias, tensor_map_val in zip(x_layout.alias_name, x_layout.alias_tensor_map):
144 if tensor_map_val is not None and tensor_map_val != "None":
145 out_layout.set_partial_by_dev_axis(alias, "sum")
147 return ((out_layout,), None)