Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / custom_shard.py: 100%
55 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 2025-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"""shard"""
17import queue
18from typing import Callable, Tuple, Optional
19from hyper_parallel.core.dtensor.layout import DeviceMesh
20from hyper_parallel.core.dtensor.dtensor import DTensor
21from hyper_parallel.core.dtensor.placement_types import Placement
22from hyper_parallel.platform import get_platform
23platform = get_platform()
24Tensor = platform.Tensor
27def _process_custom_shard_inputs(args, in_placements, redistribute_inputs, device_mesh):
28 """Process input arguments for custom_shard, redistributing DTensors as needed.
30 Args:
31 args: Positional arguments to the wrapped function.
32 in_placements: Expected placements for each input (None entries = non-tensor).
33 redistribute_inputs: Whether to redistribute inputs to required placements.
34 device_mesh: Device mesh for redistribution.
36 Returns:
37 tuple: (local_args, contain_distributed_arg, args_layout_queue)
39 Raises:
40 RuntimeError: If a DTensor arg is found but in_placements is None.
41 TypeError: If a DTensor arg has None in_placements entry, or a non-DTensor
42 arg has a non-None in_placements entry.
43 """
44 local_args = []
45 contain_distributed_arg = False
46 args_layout = queue.Queue(len(args))
48 for i, arg in enumerate(args):
49 if isinstance(arg, DTensor):
50 if in_placements is None:
51 raise RuntimeError("Found Tensor input but in_placements is None")
53 required_in_placement = in_placements[i]
54 if required_in_placement is None:
55 raise TypeError(
56 f"Tensor input at position {i} requires Placement, "
57 "but corresponding in_placements entry is None!"
58 )
60 if redistribute_inputs:
61 arg = arg.redistribute(device_mesh, required_in_placement)
63 args_layout.put(arg.layout)
64 local_tensor = arg.to_local()
65 local_args.append(local_tensor)
66 contain_distributed_arg = True
67 else:
68 if in_placements is not None and in_placements[i] is not None:
69 raise TypeError(
70 f"Non-DTensor input at position {i} requires None in_placements, "
71 f"but received {in_placements[i]}!"
72 )
73 local_args.append(arg)
75 return local_args, contain_distributed_arg, args_layout
78def _wrap_custom_shard_outputs(out, out_placements, contain_distributed_arg, device_mesh):
79 """Wrap output tensors as DTensors using the specified placements.
81 Args:
82 out: Raw output(s) from the wrapped function.
83 out_placements: Placements for each output tensor.
84 contain_distributed_arg: Whether any input was a DTensor.
85 device_mesh: Device mesh for DTensor construction.
87 Returns:
88 DTensor or tuple of DTensors, or the raw output if no distributed args.
90 Raises:
91 TypeError: If a tensor output has None out_placements, or a non-tensor
92 output has non-None out_placements.
93 ValueError: If output count doesn't match out_placements count.
94 """
95 if not contain_distributed_arg:
96 return out
98 out_is_tuple = isinstance(out, tuple)
99 out_tuple = (out,) if not out_is_tuple else out
101 if len(out_tuple) != len(out_placements):
102 raise ValueError(
103 f"Output count {len(out_tuple)} does not match "
104 f"out_placements count {len(out_placements)}!"
105 )
107 dist_output = []
108 for item, out_placement in zip(out_tuple, out_placements):
109 if isinstance(item, Tensor):
110 if out_placement is None:
111 raise TypeError(
112 "Tensor output requires non-None out_placements!"
113 )
114 dist_output.append(
115 DTensor.from_local(item, device_mesh=device_mesh, placements=out_placement)
116 )
117 else:
118 if out_placement is not None:
119 raise TypeError(
120 f"Non-tensor output requires None out_placements, got {out_placement}!"
121 )
122 dist_output.append(item)
124 return dist_output[0] if not out_is_tuple else tuple(dist_output)
127def custom_shard(
128 func: Callable,
129 device_mesh: DeviceMesh,
130 out_placements: Tuple[Tuple[Placement, ...], ...],
131 in_placements: Optional[Tuple[Optional[Tuple[Placement, ...]], ...]] = None,
132 redistribute_inputs: bool = True,
133) -> Callable:
134 """
135 Wraps a function to handle distributed tensor conversions.
137 Args:
138 func (Callable): The function to be wrapped.
139 device_mesh (DeviceMesh): The device mesh for sharding.
140 out_placements (Tuple[Tuple[Placement, ...], ...]): Placements for each output tensor.
141 in_placements (Optional[Tuple[Optional[Tuple[Placement, ...]], ...]], optional):
142 Placements for each input argument. None entries indicate non-tensor inputs.
143 redistribute_inputs (bool): Whether to redistribute inputs to required placements.
145 Returns:
146 Callable: Wrapped function that handles distributed tensors.
148 Examples:
149 >>> mesh = DeviceMesh("npu", (2, 2), mesh_dim_names=("dp", "tp"))
150 >>> @custom_shard(
151 ... device_mesh=mesh,
152 ... out_placements=((Shard(0), Replicate()),),
153 ... in_placements=((Shard(0), Replicate()), (Replicate(), Shard(1)))
154 ... )
155 ... def my_func(x, y):
156 ... return x + y
157 """
158 def wrapped(*args, **kwargs):
159 if in_placements is not None:
160 if len(in_placements) != len(args):
161 raise ValueError(
162 f"in_placements length {len(in_placements)} does not match "
163 f"the number of input args {len(args)}!"
164 )
166 local_args, contain_distributed_arg, _ = _process_custom_shard_inputs(
167 args, in_placements, redistribute_inputs, device_mesh
168 )
169 out = func(*local_args, **kwargs)
170 return _wrap_custom_shard_outputs(out, out_placements, contain_distributed_arg, device_mesh)
172 return wrapped