Diff Coverage

Diff: origin/r1.0.0...HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
hyper_parallel/core/dtensor/device_mesh.py 100%  
hyper_parallel/core/dtensor/dtensor.py 100%  
hyper_parallel/core/shard/_op_dispatch.py 85.7% 772,789-791
hyper_parallel/core/shard/ops/parallel_argmax_with_value_ops.py 96.9% 101
hyper_parallel/core/shard/ops/parallel_argsort.py 100%  
hyper_parallel/core/shard/ops/parallel_atleast_1d.py 42.9% 26,46-47,49,53,55,60,142
hyper_parallel/core/shard/ops/parallel_cell_backward_hook.py 50.0% 51
hyper_parallel/core/shard/ops/parallel_chunk_view.py 81.4% 51-53,55-56,58-59,124
hyper_parallel/core/shard/ops/parallel_expand.py 75.7% 26,30,47-52,94,101,107,197,225-230
hyper_parallel/core/shard/ops/parallel_histc_ext.py 72.9% 29,59-68,115,120
hyper_parallel/core/shard/ops/parallel_inplace_scatter_value.py 69.2% 27,44-45,47,54,59-60,117
hyper_parallel/core/shard/ops/parallel_isin.py 100%  
hyper_parallel/core/shard/ops/parallel_masked_scatter.py 61.1% 25,45-47,52-53,58
hyper_parallel/core/shard/ops/parallel_matmul.py 47.4% 92,110-115,139,145,155,185,203-208,235,242,267,288,360,378-383,411,416,428,441,460-465,495,500,524
hyper_parallel/core/shard/ops/parallel_mhc_pre_sinkhorn.py 25.0% 78-80,83-94
hyper_parallel/core/shard/ops/parallel_ms_flash_attention_score.py 52.0% 336-337,343,350,354,358,821-822,942-948,952,955-956,965,987,995,1004,1028-1032,1034-1036,1038-1042,1044-1045,1047-1048,1052-1055,1057,1062,1071-1072,1089
hyper_parallel/core/shard/ops/parallel_nonzero.py 100%  
hyper_parallel/core/shard/ops/parallel_norm.py 73.0% 30,39,56-61,101,143-144,149,152,159,161-162,193
hyper_parallel/core/shard/ops/parallel_npu_flash_attention_score.py 64.1% 110,157-159,161,166,168-170,172-173,366-367,373,380,384,388,611,906-907,1013,1121-1122
hyper_parallel/core/shard/ops/parallel_one_hot_ext.py 59.4% 29,46-47,49-51,53-54,56-59,103
hyper_parallel/core/shard/ops/parallel_ops.py 100%  
hyper_parallel/core/shard/ops/parallel_outer.py 96.3% 32
hyper_parallel/core/shard/ops/parallel_reduce.py 89.4% 135,141,162,167,379,414,428
hyper_parallel/core/shard/ops/parallel_scaled_dot_product_attention.py 82.9% 36,70-72,74-75,79,88,90,95,345,550
hyper_parallel/core/shard/ops/parallel_scatter.py 100%  
hyper_parallel/core/shard/ops/parallel_scatter_update.py 64.0% 47-48,50,56,61-62,89,106,131
hyper_parallel/core/shard/ops/parallel_slice_ext.py 74.1% 26,43-48
hyper_parallel/core/shard/ops/parallel_split.py 47.7% 27,31,35,56-62,93,95,123-129,149-151,153-154,156-157,159-162,167-168,173,190-191,193-194,196,198-201,233,263-269,300,302,330-336,356-358,360-361,363-364,366-369,374-375,380,412-414,416-419
hyper_parallel/core/shard/ops/parallel_stack.py 83.3% 92
hyper_parallel/core/shard/ops/parallel_topk.py 97.2% 86
hyper_parallel/core/shard/ops/parallel_transpose.py 91.2% 68,114,130
hyper_parallel/core/shard/ops/parallel_unbind.py 97.1% 83
hyper_parallel/platform/mindspore/dtensor.py 57.1% 68-70
hyper_parallel/core/shard/_op_dispatch.py
768
769
770
771
772
773
774
775
776

        self._check_ce_op_without_loss_parallel_context(op_name, args)

        if not is_loss_parallel_op(op_name):
            raise RuntimeError(
                f"Operator {op_name} does not contain parallel layout infer func. "
                f"DTensor dispatch requires explicit layout inference registration. "
                f"Please register a distributed operator for '{op_name}' or use local tensors."
            )
785
786
787
788
789
790
791
792
793
794
795
            logits = gathered_args[0]
            targets = gathered_args[1]
            if isinstance(logits, Tensor) and isinstance(targets, Tensor):
                if logits.ndim > 2 and targets.ndim > 1 and targets.ndim == logits.ndim - 1:
                    vocab_size = logits.shape[-1]
                    gathered_args[0] = logits.reshape(-1, vocab_size)
                    gathered_args[1] = targets.reshape(-1)

        return op_call(*gathered_args, **gathered_kwargs)

    def _dispatch_layout_infer(
hyper_parallel/core/shard/ops/parallel_argmax_with_value_ops.py
 97
 98
 99
100
101
102
103
104
105
        # Use alias_tensor_map to support StridedShard multi-axis mappings.
        alias_map = input_layout.alias_tensor_map
        mapping = alias_map[axis]
        if isinstance(mapping, tuple):
            is_sharded = any(m != "None" for m in mapping)
        else:
            is_sharded = mapping != "None"

        if is_sharded:
hyper_parallel/core/shard/ops/parallel_atleast_1d.py
22
23
24
25
26
27
28
29
30
from .parallel_ops import DistributedOp


def _normalize_atleast_1d_args(*tensors):
    return tensors, {}


class Atleast1DDistributedOp(DistributedOp):
    """Distributed implementation for torch.atleast_1d."""
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_atleast_1d_args(*args, **kwargs)
        tensors = args

        local_args = tuple(
            t.to_local() if hasattr(t, 'to_local') else t
            for t in tensors
        )
        local_kwargs = {}

        cache_values = [
            t.layout if hasattr(t, 'layout') else None
            for t in tensors
        ]

        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layouts for atleast_1d operator.
138
139
140
141
142
        # If there are multiple inputs, return a tuple of Layouts.
        if len(output_layouts) == 1:
            return ((output_layouts[0],), None)

        return (tuple(output_layouts), None)
hyper_parallel/core/shard/ops/parallel_cell_backward_hook.py
47
48
49
50
51
52
53
        if output_layouts[0] is None:
            return py_output
        # Use from_local_with_layout to reuse the pre-computed Layout
        # instead of rebuilding it in DTensor.__init_data__.
        return DTensor.from_local_with_layout(
            py_output, output_layouts[0]
        )
hyper_parallel/core/shard/ops/parallel_chunk_view.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_chunk_view_args(*args, **kwargs)
        input_tensor, chunks, dim = args
        input_shape = input_tensor.shape

        local_args = (input_tensor.to_local(), chunks, dim)
        local_kwargs = {}

        cache_values = [input_tensor.layout, chunks, dim, input_shape]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layouts for ChunkView operator.
120
121
122
123
124
125
126
127
128
            )

        mapping = alias_map[dim]
        if isinstance(mapping, (list, tuple)):
            is_sharded = any(m != "None" for m in mapping)
        else:
            is_sharded = mapping != "None"

        if is_sharded:
hyper_parallel/core/shard/ops/parallel_expand.py
22
23
24
25
26
27
28
29
30
31
32
33
34
from .parallel_ops import DistributedOp


def _normalize_expand_args(input_tensor, *sizes):
    return (input_tensor, *sizes), {}


def _normalize_expand_as_args(input_tensor, target_tensor):
    return (input_tensor, target_tensor), {}


class ExpandDistributedOp(DistributedOp):
    """Distributed implementation for torch.Tensor.expand."""
43
44
45
46
47
48
49
50
51
52
53
54
55
56

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_expand_args(*args, **kwargs)
        input_tensor = args[0]
        sizes = tuple(args[1:])
        local_args = (input_tensor.to_local(), *sizes)
        cache_values = [input_tensor.layout, input_tensor.shape, sizes]
        return local_args, {}, cache_values

    @staticmethod
    def _validate_input_layouts(
        cache_values: list,
90
91
92
93
94
95
96
97
            )

        input_shape = cache_values[1] if len(cache_values) > 1 else None
        if not isinstance(input_shape, tuple):
            raise ValueError(
                f"For {op_name}, input_shape should be a tuple, "
                f"but got {type(input_shape)}."
            )
 97
 98
 99
100
101
102
103
104
105
            )

        sizes = cache_values[2] if len(cache_values) > 2 else None
        if sizes is None or len(sizes) < 1:
            raise ValueError(
                f"For {op_name}, sizes should be a non-empty tuple of ints, "
                f"but got {sizes}."
            )
        for i, sz in enumerate(sizes):
103
104
105
106
107
108
109
110
                f"but got {sizes}."
            )
        for i, sz in enumerate(sizes):
            if not isinstance(sz, int):
                raise ValueError(
                    f"For {op_name}, elements in sizes should be int, "
                    f"but got {type(sz)} at position {i}."
                )
193
194
195
196
197
198
199
200
                        f"got mapping {in_alias_map[i]}."
                    )
                output_map.append("None")
            else:
                raise ValueError(
                    f"For {self.op_name}, cannot expand dimension {i} "
                    f"from size {input_size} to {requested_size}."
                )
221
222
223
224
225
226
227
228
229
230
231
232
233
234

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_expand_as_args(*args, **kwargs)
        input_tensor = args[0]
        target_tensor = args[1]
        local_args = (input_tensor.to_local(), target_tensor.to_local())
        cache_values = [input_tensor.layout, input_tensor.shape, target_tensor.shape]
        return local_args, {}, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for expand_as.
hyper_parallel/core/shard/ops/parallel_histc_ext.py
25
26
27
28
29
30
31
32
33
platform = get_platform()


def _normalize_histc_args(x, bins=100, min_val=0, max_val=0):
    return (x, bins, min_val, max_val), {}


class HistcExtDistributedOp(DistributedOp):
    """
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
            tuple: (local_args, local_kwargs, cache_values)
        """
        # Map external API parameter names (min, max) to internal names to avoid
        # shadowing Python builtins.
        if "min" in kwargs:
            kwargs["min_val"] = kwargs.pop("min")
        if "max" in kwargs:
            kwargs["max_val"] = kwargs.pop("max")
        args, kwargs = _normalize_histc_args(*args, **kwargs)
        x, bins, min_val, max_val = args
        local_args = (x.to_local(), bins, min_val, max_val)
        local_kwargs = {}
        cache_values = [x.layout, bins, min_val, max_val]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for HistcExt operator.
111
112
113
114
115
116
117
118
119
120
121
122
123
124
                f"For {self.op_name}, bins should be a positive integer, "
                f"but got {bins}"
            )
        if not isinstance(min_val, (int, float)):
            raise ValueError(
                f"For {self.op_name}, min should be a number, "
                f"but got {type(min_val).__name__}"
            )
        if not isinstance(max_val, (int, float)):
            raise ValueError(
                f"For {self.op_name}, max should be a number, "
                f"but got {type(max_val).__name__}"
            )
        if min_val > max_val:
hyper_parallel/core/shard/ops/parallel_inplace_scatter_value.py
23
24
25
26
27
28
29
30
31


def _normalize_inplace_scatter_value_args(input_tensor, dim, index, value):
    """Normalize InplaceScatterValue arguments to positional args + empty kwargs."""
    return (input_tensor, dim, index, value), {}


class InplaceScatterValueDistributedOp(DistributedOp):
    """Distributed implementation for InplaceScatterValue operator."""
40
41
42
43
44
45
46
47
48
49
50
51

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_inplace_scatter_value_args(*args, **kwargs)
        input_tensor, dim, index, value = args

        local_args = (
            input_tensor.to_local() if hasattr(input_tensor, '_layout') else input_tensor,
            dim,
            index.to_local() if hasattr(index, '_layout') else index,
            value,
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
            index.to_local() if hasattr(index, '_layout') else index,
            value,
        )

        cache_values = [
            input_tensor.layout if hasattr(input_tensor, '_layout') else None,
            index.layout if hasattr(index, '_layout') else None,
            dim,
        ]
        local_kwargs = kwargs
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for InplaceScatterValue operator.
113
114
115
116
117
118
119
120
                f"For {self.op_name}, dim {original_dim} is out of bounds for tensor with {ndim} dims"
            )

        if len(input_map) != len(index_map):
            raise ValueError(
                f"For {self.op_name}, input and index must have the same number of dimensions, "
                f"but got input rank={len(input_map)}, index rank={len(index_map)}"
            )
hyper_parallel/core/shard/ops/parallel_masked_scatter.py
21
22
23
24
25
26
27
28
29
from .parallel_ops import DistributedOp


def _normalize_masked_scatter_args(input_tensor, mask, source):
    return (input_tensor, mask, source), {}


class MaskedScatterDistributedOp(DistributedOp):
    """Distributed implementation for torch.Tensor.masked_scatter."""
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_masked_scatter_args(*args, **kwargs)
        input_tensor, mask, source = args[0], args[1], args[2]
        local_args = (
            input_tensor.to_local(),
            mask.to_local(),
            source.to_local(),
        )
        local_kwargs = {}
        cache_values = [
            input_tensor.layout,
            mask.layout,
            source.layout,
        ]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for torch.Tensor.masked_scatter.
hyper_parallel/core/shard/ops/parallel_matmul.py
88
89
90
91
92
93
94
95
96
            out_layout.set_partial_by_dev_axis(axis_alias, op)


def _normalize_matmul_ext_args(x, w):
    return (x, w), {}


class MatMulExtDistributedOp(DistributedOp):
    """Distributed implementation for MatMul operator."""
106
107
108
109
110
111
112
113
114
115
116
117
118
119
        Returns:
            tuple: (local_args, local_kwargs, cache_values) where local_args contains
                local tensors for x and w; cache_values contains [x_layout, w_layout].
        """
        args, kwargs = _normalize_matmul_ext_args(*args, **kwargs)
        x_tensor, w_tensor = args[0], args[1]
        local_args = (x_tensor.to_local(), w_tensor.to_local())
        local_kwargs = {}
        cache_values = [x_tensor.layout, w_tensor.layout]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for MatMul operator (output = x @ w).
135
136
137
138
139
140
141
142
143
        """
        x_layout = cache_values[0]
        w_layout = cache_values[1]
        if not x_layout or not w_layout:
            raise ValueError(
                f"For {self.op_name}, x_layout: {x_layout}, w_layout: {w_layout}"
            )
        x_mesh_shape = x_layout.mesh_shape
        w_mesh_shape = w_layout.mesh_shape
141
142
143
144
145
146
147
148
            )
        x_mesh_shape = x_layout.mesh_shape
        w_mesh_shape = w_layout.mesh_shape
        if x_mesh_shape != w_mesh_shape:
            raise ValueError(
                f"For {self.op_name}, inputs must have same mesh_shape, "
                f"but got x: {x_mesh_shape} and w: {w_mesh_shape}"
            )
151
152
153
154
155
156
157
158
        w_map = w_layout.alias_tensor_map
        contract_dim = len(x_map) - 1
        w_contract_dim = len(w_map) - 2
        if x_map[contract_dim] != w_map[w_contract_dim]:
            raise ValueError(
                f"For {self.op_name}, contracting dimensions must have same layout, "
                f"but got x: {x_map[contract_dim]} and w: {w_map[w_contract_dim]}"
            )
181
182
183
184
185
186
187
188
189
        return ((out_layout,), None)


def _normalize_matmul_args(x, w, transpose_a=False, transpose_b=False):
    return (x, w, transpose_a, transpose_b), {}


class MatMulDistributedOp(DistributedOp):
    """Distributed implementation for MatMul operator."""
199
200
201
202
203
204
205
206
207
208
209
210
211
212
        Returns:
            tuple: (local_args, local_kwargs, cache_values) where local_args contains
                local tensors for x and w; cache_values contains [x_layout, w_layout, transpose_a, transpose_b].
        """
        args, kwargs = _normalize_matmul_args(*args, **kwargs)
        x_tensor, w_tensor, transpose_a, transpose_b = args
        local_args = (x_tensor.to_local(), w_tensor.to_local(), transpose_a, transpose_b)
        local_kwargs = {}
        cache_values = [x_tensor.layout, w_tensor.layout, transpose_a, transpose_b]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for MatMul operator (output = x @ w, with possible transpose).
231
232
233
234
235
236
237
238
239
        transpose_a = cache_values[2]
        transpose_b = cache_values[3]

        if not x_layout or not w_layout:
            raise ValueError(
                f"For {self.op_name}, x_layout: {x_layout}, w_layout: {w_layout}"
            )

        x_mesh_shape = x_layout.mesh_shape
238
239
240
241
242
243
244
245

        x_mesh_shape = x_layout.mesh_shape
        w_mesh_shape = w_layout.mesh_shape
        if x_mesh_shape != w_mesh_shape:
            raise ValueError(
                f"For {self.op_name}, inputs must have same mesh_shape, "
                f"but got x: {x_mesh_shape} and w: {w_mesh_shape}"
            )
263
264
265
266
267
268
269
270
            w_contract_dim = len(w_map) - 2  # Second to last dimension

        # Validate contracting dimensions
        if x_map[x_contract_dim] != w_map[w_contract_dim]:
            raise ValueError(
                f"For {self.op_name}, contracting dimensions must have same layout, "
                f"but got x: {x_map[x_contract_dim]} and w: {w_map[w_contract_dim]}"
            )
284
285
286
287
288
289
290
291
292
        # Set partial status
        if x_map[x_contract_dim] != "None":
            if isinstance(x_map[x_contract_dim], tuple):
                for axis in x_map[x_contract_dim]:
                    out_layout.set_partial_by_dev_axis(axis, 'sum')
            else:
                out_layout.set_partial_by_dev_axis(x_map[x_contract_dim], 'sum')

        return ((out_layout,), None)
356
357
358
359
360
361
362
363
364
        return output_layout


def _normalize_batch_matmul_ext_args(x, w):
    return (x, w), {}


class BatchMatMulExtDistributedOp(BaseBatchMatMulDistributedOp):
    """Distributed implementation for BatchMatMulExt operator."""
374
375
376
377
378
379
380
381
382
383
384
385
386
387
        Returns:
            tuple: (local_args, local_kwargs, cache_values) where local_args contains
                local tensors for x and w; cache_values contains [x_layout, w_layout].
        """
        args, kwargs = _normalize_batch_matmul_ext_args(*args, **kwargs)
        x_tensor, w_tensor = args[0], args[1]
        local_args = (x_tensor.to_local(), w_tensor.to_local())
        local_kwargs = {}
        cache_values = [x_tensor.layout, w_tensor.layout]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for BatchMatMulExt operator (output = x @ w).
407
408
409
410
411
412
413
414
415
416
417
418
419
        x_layout = cache_values[0]
        w_layout = cache_values[1]

        if not x_layout or not w_layout:
            raise ValueError(
                f"For {self.op_name}, x_layout: {x_layout}, w_layout: {w_layout}"
            )

        if x_layout.mesh_shape != w_layout.mesh_shape:
            raise ValueError(
                f"For {self.op_name}, inputs must have same mesh_shape, "
                f"but got x: {x_layout.mesh_shape} and w: {w_layout.mesh_shape}"
            )
424
425
426
427
428
429
430
431
        # contracting dims
        x_contract = x_map[-1]
        w_contract = w_map[-2]
        if x_contract != w_contract:
            raise ValueError(
                f"For {self.op_name}, contracting (M) dim layouts must match, "
                f"but got x: {x_contract} and w: {w_contract}"
            )
437
438
439
440
441
442
443
444
445
        return ((self._build_output_layout(x_layout, w_layout, merged_batch, x_n, w_p, x_contract),), None)


def _normalize_batch_matmul_args(x, w, transpose_a=False, transpose_b=False):
    return (x, w, transpose_a, transpose_b), {}


class BatchMatMulDistributedOp(BaseBatchMatMulDistributedOp):
    """Distributed implementation for BatchMatMul operator."""
456
457
458
459
460
461
462
463
464
465
466
467
468
469
            tuple: (local_args, local_kwargs, cache_values) where local_args contains
                local tensors for x and w; cache_values contains
                [x_layout, w_layout, transpose_a, transpose_b].
        """
        args, kwargs = _normalize_batch_matmul_args(*args, **kwargs)
        x_tensor, w_tensor, transpose_a, transpose_b = args
        local_args = (x_tensor.to_local(), w_tensor.to_local(), transpose_a, transpose_b)
        local_kwargs = {}
        cache_values = [x_tensor.layout, w_tensor.layout, transpose_a, transpose_b]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for BatchMatMul operator (output = x @ w, with possible transpose).
491
492
493
494
495
496
497
498
499
500
501
502
503
        transpose_a = cache_values[2]
        transpose_b = cache_values[3]

        if not x_layout or not w_layout:
            raise ValueError(
                f"For {self.op_name}, x_layout: {x_layout}, w_layout: {w_layout}"
            )

        if x_layout.mesh_shape != w_layout.mesh_shape:
            raise ValueError(
                f"For {self.op_name}, inputs must have same mesh_shape, "
                f"but got x: {x_layout.mesh_shape} and w: {w_layout.mesh_shape}"
            )
520
521
522
523
524
525
526
527
            w_contract = w_map[-2]
            w_p = w_map[-1]

        if x_contract != w_contract:
            raise ValueError(
                f"For {self.op_name}, contracting (M) dim layouts must match, "
                f"but got x: {x_contract} and w: {w_contract}"
            )
hyper_parallel/core/shard/ops/parallel_mhc_pre_sinkhorn.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98


def _normalize_mhc_pre_clamp_sinkhorn_args(*args, **kwargs):
    """Normalize npu_mhc_pre_clamp_sinkhorn arguments."""
    values = dict(_MHC_PRE_CLAMP_DEFAULTS)
    if len(args) > len(_MHC_PRE_CLAMP_ARG_NAMES):
        raise TypeError(
            f"npu_mhc_pre_clamp_sinkhorn expected at most {len(_MHC_PRE_CLAMP_ARG_NAMES)} arguments"
        )
    for name, value in zip(_MHC_PRE_CLAMP_ARG_NAMES, args):
        values[name] = value
    for name, value in kwargs.items():
        if name not in _MHC_PRE_CLAMP_ARG_NAMES:
            raise TypeError(f"npu_mhc_pre_clamp_sinkhorn got an unexpected keyword argument '{name}'")
        if name in _MHC_PRE_CLAMP_ARG_NAMES[:len(args)]:
            raise TypeError(f"npu_mhc_pre_clamp_sinkhorn got multiple values for argument '{name}'")
        values[name] = value
    missing = [name for name in _MHC_PRE_CLAMP_ARG_NAMES[:4] if name not in values]
    if missing:
        raise TypeError(f"npu_mhc_pre_clamp_sinkhorn missing required arguments: {missing}")
    return tuple(values[name] for name in _MHC_PRE_CLAMP_ARG_NAMES), {}


# Validation rules table for npu_mhc_pre_sinkhorn
# Key: tensor_map length (format identifier)
hyper_parallel/core/shard/ops/parallel_ms_flash_attention_score.py
332
333
334
335
336
337
338
339
340
341
        #
        # kv_seq_split_num > 1 is blocked by a guard in
        # _compute_adjusted_sparse_params before reaching this function,
        # so local_kv_len == global_kv_len is guaranteed here.
        local_q_len = query.shape[seq_dim_idx]
        local_kv_len = key.shape[seq_dim_idx]

        if sparse_mode in (SPARSE_DEFAULT_MASK, SPARSE_BAND):
            new_pre_tokens = pre_tokens
            new_next_tokens = next_tokens
339
340
341
342
343
344
345
346
347
        if sparse_mode in (SPARSE_DEFAULT_MASK, SPARSE_BAND):
            new_pre_tokens = pre_tokens
            new_next_tokens = next_tokens
        else:
            new_pre_tokens = local_kv_len
            new_next_tokens = 0

        new_sparse_mode = SPARSE_BAND if sparse_mode != SPARSE_DEFAULT_MASK else sparse_mode
        update_mode = SPARSE_MODE_UPDATE_MAP[sparse_mode]
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
        new_sparse_mode = SPARSE_BAND if sparse_mode != SPARSE_DEFAULT_MASK else sparse_mode
        update_mode = SPARSE_MODE_UPDATE_MAP[sparse_mode]

        if update_mode == LEFT_UP_TO_LEFT_UP:
            offset = -split_id * local_q_len
            new_pre_tokens = new_pre_tokens + offset
            new_next_tokens = new_next_tokens - offset
        elif update_mode == LEFT_UP_TO_RIGHT_DOWN:
            offset = local_kv_len - (split_id + 1) * local_q_len
            new_pre_tokens = new_pre_tokens + offset
            new_next_tokens = new_next_tokens - offset
        elif update_mode == RIGHT_DOWN_TO_RIGHT_DOWN:
            offset = (split_num - split_id - 1) * local_q_len
            new_pre_tokens = new_pre_tokens + offset
            new_next_tokens = new_next_tokens - offset

        return new_sparse_mode, new_pre_tokens, new_next_tokens
817
818
819
820
821
822
823
824
825
826
        is_dynamic: bool,
    ) -> Tuple[int, int, int]:
        """Compute adjusted sparse parameters based on dynamic or static shape."""
        if is_dynamic:
            if kv_seq_split_num > 1:
                raise NotImplementedError(
                    f"For {self.op_name}, dynamic shape with KV sequence sharding "
                    f"(kv_seq_split_num={kv_seq_split_num}) is not yet supported. "
                    f"The dynamic path currently uses local KV length directly, "
                    f"while the static path multiplies by kv_seq_split_num to obtain "
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
            # Scalar parameters from pyboost may arrive as Tensor objects.
            # Arithmetic and comparison operations on such Tensor scalars
            # would trigger device kernel calls, so convert them to Python
            # native types early.
            head_num = int(self._to_python_scalar(p_head_num))
            keep_prob = self._to_python_scalar(p_keep_prob)
            scale_value = self._to_python_scalar(p_scale_value)
            pre_tokens = int(self._to_python_scalar(p_pre_tokens))
            next_tokens = int(self._to_python_scalar(p_next_tokens))
            inner_precise = int(self._to_python_scalar(p_inner_precise))
            sparse_mode = int(self._to_python_scalar(p_sparse_mode))

            # Ensure the runtime input_layout matches the cached value used
            # for sharding derivation and validation.
            runtime_input_layout = _resolve_input_layout(
                self._to_python_scalar(p_input_layout)
            )
            if runtime_input_layout != input_layout:
                raise ValueError(
                    f"For {self.op_name}, runtime input_layout {runtime_input_layout!r} "
                    f"does not match the cached input_layout {input_layout!r} "
                    f"used for sharding inference. This may indicate an incorrect "
                    f"dispatcher cache key."
961
962
963
964
965
966
967
968
969
                )

            is_varlen = input_layout == "TND" and actual_seq_qlen is not None
            self._validate_attn_mask(attn_mask, sparse_mode, input_layout, is_varlen)
            FlashAttentionScoreDistributedOp._validate_real_shift_configuration(
                real_shift, sparse_mode)

            split_info = self._get_split_info(query_layout, input_layout)
            head_split_num = split_info["head"]
983
984
985
986
987
988
989
990
991
                return FlashAttentionScoreDistributedOp._truncate_result(result)

            adjusted_head_num = self._adjust_head_num(head_num, head_split_num)

            (adjusted_sparse_mode, adjusted_pre_tokens, adjusted_next_tokens,
             adjusted_actual_seq_qlen, adjusted_actual_seq_kvlen) = self._apply_seq_split_adjustments(
                query, key, query_layout, key_layout, input_layout,
                sparse_mode, pre_tokens, next_tokens,
                actual_seq_qlen, actual_seq_kvlen,
991
992
993
994
995
996
997
998
999
                actual_seq_qlen, actual_seq_kvlen,
                seq_split_num, lb_split_id, lb_split_num,
            )

            result = func(
                query, key, value,
                real_shift, drop_mask, padding_mask, attn_mask, prefix,
                adjusted_actual_seq_qlen, adjusted_actual_seq_kvlen,
                int(adjusted_head_num), keep_prob, scale_value,
1000
1001
1002
1003
1004
1005
1006
1007
1008
                int(adjusted_pre_tokens), int(adjusted_next_tokens), inner_precise,
                p_input_layout, int(adjusted_sparse_mode),
            )

            return FlashAttentionScoreDistributedOp._truncate_result(result)

        return _expanded_impl

    def _apply_seq_split_adjustments(  # pylint: disable=too-many-arguments,too-many-locals
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
        Returns:
            Tuple of (adjusted_sparse_mode, adjusted_pre_tokens, adjusted_next_tokens,
                      adjusted_actual_seq_qlen, adjusted_actual_seq_kvlen).
        """
        adjusted_sparse_mode = sparse_mode
        adjusted_pre_tokens = pre_tokens
        adjusted_next_tokens = next_tokens
        adjusted_actual_seq_qlen = actual_seq_qlen
        adjusted_actual_seq_kvlen = actual_seq_kvlen

        if seq_split_num > 1 or lb_split_id is not None:
            dynamic_info = self._get_dynamic_shape_info(query, key, input_layout)
            is_dynamic = dynamic_info.get('is_dynamic', False)

            if lb_split_id is not None:
                if lb_split_num is None:
                    raise ValueError("lb_split_num must not be None when lb_split_id is set")
                split_id = lb_split_id
                seq_split_num = lb_split_num
            else:
                split_id = self._get_split_id(query_layout, input_layout)
            seq_dim_idx = self._get_seq_dim_idx(self._layout_dims.get(input_layout, {}))

            if seq_dim_idx is None:
                raise ValueError(
                    f"Cannot infer seq/total dim for input_layout={input_layout}"
                )

            kv_seq_split_num = 1
            if key_layout is not None:
                kv_split_info = self._get_split_info(key_layout, input_layout)
                kv_seq_split_num = kv_split_info["seq"]

            self._check_seq_sharding_compatibility(
                query_layout, key_layout, input_layout,
                seq_dim_idx, seq_split_num, kv_seq_split_num
            )

            (adjusted_sparse_mode,
             adjusted_pre_tokens,
             adjusted_next_tokens) = self._compute_adjusted_sparse_params(
                query, key,
                sparse_mode, pre_tokens, next_tokens,
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
                split_id, seq_split_num, seq_dim_idx,
                kv_seq_split_num, is_dynamic,
            )

            if input_layout == "TND":
                (adjusted_sparse_mode,
                 adjusted_pre_tokens,
                 adjusted_next_tokens,
                 adjusted_actual_seq_qlen,
                 adjusted_actual_seq_kvlen) = self._adjust_tnd_layout_params(
1085
1086
1087
1088
1089
1090
1091
1092
1093
                        kv_seq_split_num=kv_seq_split_num, is_dynamic=is_dynamic,
                    ),
                )

        return (adjusted_sparse_mode, adjusted_pre_tokens, adjusted_next_tokens,
                adjusted_actual_seq_qlen, adjusted_actual_seq_kvlen)

    def _get_seq_dim_idx(self, dims: dict) -> Optional[int]:
        """Get the sequence dimension index."""
hyper_parallel/core/shard/ops/parallel_norm.py
26
27
28
29
30
31
32
33
34
    """Normalize RmsNorm args to positional form.

    MindSpore Primitive RmsNorm receives (x, gamma, epsilon) as positional arguments.
    """
    return (x, gamma, epsilon), {}


def _normalize_layernorm_args(input_tensor, normalized_shape, weight=None, bias=None, eps=1e-5):
    """Normalize layer_norm args to positional form.
35
36
37
38
39
40
41
42
43

    torch.nn.functional.layer_norm(input_tensor, normalized_shape, weight=None, bias=None, eps=1e-5)
    has no keyword-only parameters, so everything stays positional.
    """
    return (input_tensor, normalized_shape, weight, bias, eps), {}


class NormDistributedOp(DistributedOp):
    """Distributed implementation for RmsNorm operator."""
52
53
54
55
56
57
58
59
60
61
62
63
64
65

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_rmsnorm_args(*args, **kwargs)
        x, gamma, epsilon = args
        local_args = (x.to_local(), gamma.to_local(), epsilon)
        local_kwargs = {}
        cache_values = [x.layout, gamma.layout]
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layouts for RmsNorm operator.
 97
 98
 99
100
101
102
103
104
105
            raise ValueError(f"{self.op_name} inputs must have same mesh_shape")
        x_alias_map = x_layout.alias_tensor_map
        gamma_alias_map = gamma_layout.alias_tensor_map
        if len(gamma_alias_map) > len(x_alias_map):
            raise ValueError(
                f"For {self.op_name}, gamma ndim {len(gamma_alias_map)} cannot exceed "
                f"input ndim {len(x_alias_map)}."
            )
        begin_norm_axis = len(x_alias_map) - len(gamma_alias_map)
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_layernorm_args(*args, **kwargs)
        input_tensor, normalized_shape, weight, bias, eps = args

        # Normalize normalized_shape: int → (int,), list → tuple
        if isinstance(normalized_shape, int):
            normalized_shape = (normalized_shape,)
        elif isinstance(normalized_shape, list):
            normalized_shape = tuple(normalized_shape)

        local_args = [
            input_tensor.to_local(),
            normalized_shape,
            weight.to_local() if weight is not None and hasattr(weight, 'to_local') else weight,
            bias.to_local() if bias is not None and hasattr(bias, 'to_local') else bias,
155
156
157
158
159
160
161
162
163
164
165
166
            weight.to_local() if weight is not None and hasattr(weight, 'to_local') else weight,
            bias.to_local() if bias is not None and hasattr(bias, 'to_local') else bias,
            eps,
        ]
        local_kwargs = {}

        cache_values = [input_tensor.layout, normalized_shape]
        return tuple(local_args), local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for layer_norm operator.
189
190
191
192
193
194
195
196
        if not self._allow_partial_inputs:
            self._check_partial_inputs([input_layout])

        if normalized_shape is None:
            raise ValueError(f"{self.op_name} requires normalized_shape.")

        if not isinstance(normalized_shape, tuple):
            raise ValueError(f"normalized_shape must be int, list, or tuple, got {type(normalized_shape)}")
hyper_parallel/core/shard/ops/parallel_npu_flash_attention_score.py
106
107
108
109
110
111
112
113
114

    Returns:
        tuple: (positional_args_tuple, empty_kwargs_dict)
    """
    return (
        query, key, value, head_num, input_layout,
        pse, padding_mask, atten_mask,
        scale, keep_prob, pre_tockens, next_tockens,
        inner_precise, prefix, actual_seq_qlen, actual_seq_kvlen,
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_npu_fusion_attention_args(*args, **kwargs)
        query, key, value = args[0], args[1], args[2]
        input_layout = args[4]

        local_args = (
            query.to_local() if hasattr(query, '_layout') else query,
            key.to_local() if hasattr(key, '_layout') else key,
            value.to_local() if hasattr(value, '_layout') else value,
        ) + args[3:]
        local_kwargs = {}

        query_layout = query.layout if hasattr(query, "_layout") else None
        key_layout = key.layout if hasattr(key, "_layout") else None
        value_layout = value.layout if hasattr(value, "_layout") else None

        cache_values = [query_layout, key_layout, value_layout, input_layout]
        return local_args, local_kwargs, cache_values

    def _is_dynamic_shape(self, tensor: Tensor, dim: int) -> bool:
        """Check if tensor has dynamic shape at given dimension."""
        try:
362
363
364
365
366
367
368
369
370
371
        #
        # kv_seq_split_num > 1 is blocked by a guard in
        # _compute_adjusted_sparse_params before reaching this function,
        # so local_kv_len == global_kv_len is guaranteed here.
        local_q_len = query.shape[seq_dim_idx]
        local_kv_len = key.shape[seq_dim_idx]

        if sparse_mode in (SPARSE_DEFAULT_MASK, SPARSE_BAND):
            new_pre_tockens = pre_tockens
            new_next_tockens = next_tockens
369
370
371
372
373
374
375
376
377
        if sparse_mode in (SPARSE_DEFAULT_MASK, SPARSE_BAND):
            new_pre_tockens = pre_tockens
            new_next_tockens = next_tockens
        else:
            new_pre_tockens = local_kv_len
            new_next_tockens = 0

        new_sparse_mode = SPARSE_BAND if sparse_mode != SPARSE_DEFAULT_MASK else sparse_mode
        update_mode = SPARSE_MODE_UPDATE_MAP[sparse_mode]
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
        new_sparse_mode = SPARSE_BAND if sparse_mode != SPARSE_DEFAULT_MASK else sparse_mode
        update_mode = SPARSE_MODE_UPDATE_MAP[sparse_mode]

        if update_mode == LEFT_UP_TO_LEFT_UP:
            offset = -split_id * local_q_len
            new_pre_tockens = new_pre_tockens + offset
            new_next_tockens = new_next_tockens - offset
        elif update_mode == LEFT_UP_TO_RIGHT_DOWN:
            offset = local_kv_len - (split_id + 1) * local_q_len
            new_pre_tockens = new_pre_tockens + offset
            new_next_tockens = new_next_tockens - offset
        elif update_mode == RIGHT_DOWN_TO_RIGHT_DOWN:
            offset = (split_num - split_id - 1) * local_q_len
            new_pre_tockens = new_pre_tockens + offset
            new_next_tockens = new_next_tockens - offset

        return new_sparse_mode, new_pre_tockens, new_next_tockens
607
608
609
610
611
612
613
614
615
        )

        attention_out_layout = copy.deepcopy(query_layout)
        if attention_out_layout.placements is None and attention_out_layout.tensor_map is not None:
            attention_out_layout.tensor_map_to_placement()

        softmax_layout = self._infer_softmax_layout_by_input_layout(
            query_layout, input_layout_str, ""
        )
902
903
904
905
906
907
908
909
910
911
        is_dynamic: bool,
    ) -> Tuple[int, int, int]:
        """Compute adjusted sparse parameters based on dynamic or static shape."""
        if is_dynamic:
            if kv_seq_split_num > 1:
                raise NotImplementedError(
                    f"For {self.op_name}, dynamic shape with KV sequence sharding "
                    f"(kv_seq_split_num={kv_seq_split_num}) is not yet supported. "
                    f"The dynamic path currently uses local KV length directly, "
                    f"while the static path multiplies by kv_seq_split_num to obtain "
1009
1010
1011
1012
1013
1014
1015
1016
1017
        query_layout = cache_values[0]
        key_layout = cache_values[1]

        if query_layout is None:
            return None

        def _expanded_impl(  # pylint: disable=R0913
            query,
            key,
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
            dynamic_info = self._get_dynamic_shape_info(query, key, input_layout)
            is_dynamic = dynamic_info.get('is_dynamic', False)

            if lb_split_id is not None:
                if lb_split_num is None:
                    raise ValueError(
                        "lb_split_num must not be None when lb_split_id is set"
                    )
                split_id = lb_split_id
                seq_split_num = lb_split_num
hyper_parallel/core/shard/ops/parallel_one_hot_ext.py
25
26
27
28
29
30
31
32
33
platform = get_platform()


def _normalize_one_hot_ext_args(indices, num_classes, on_value, off_value, axis):
    return (indices, num_classes, on_value, off_value, axis), {}


class OneHotExtDistributedOp(DistributedOp):
    """Distributed implementation for OneHotExt operator."""
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_one_hot_ext_args(*args, **kwargs)
        indices, num_classes, on_value, off_value, axis = args

        indices_local = indices.to_local()
        on_value_local = on_value.to_local() if hasattr(on_value, '_layout') else on_value
        off_value_local = off_value.to_local() if hasattr(off_value, '_layout') else off_value

        on_value_layout = on_value.layout if hasattr(on_value, '_layout') else None
        off_value_layout = off_value.layout if hasattr(off_value, '_layout') else None

        local_args = (indices_local, num_classes, on_value_local, off_value_local, axis)
        local_kwargs = {}
        cache_values = [indices.layout, on_value_layout, off_value_layout, num_classes, axis]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
 99
100
101
102
103
104
105
106
107
        axis = self._validate_axis(axis)

        in_tensor_map = indices_layout.tensor_map
        if not in_tensor_map:
            raise ValueError(
                f"For {self.op_name}, indices tensor_map is empty."
            )

        self._validate_multi_dim_restriction(in_tensor_map, axis, indices_layout)
hyper_parallel/core/shard/ops/parallel_outer.py
28
29
30
31
32
33
34
35
36

def _get_alias_shard_set(dim_alias):
    if isinstance(dim_alias, str):
        return {dim_alias} if dim_alias != "None" else set()
    return set(dim_alias)


class OuterDistributedOp(DistributedOp):
    """Distributed implementation for torch.outer."""
hyper_parallel/core/shard/ops/parallel_reduce.py
131
132
133
134
135
136
137
138
139
        dim = cache_values[1]
        keepdim = cache_values[2]

        if x_layout is None or x_layout.mesh_shape is None:
            raise ValueError(
                f"For {self.op_name}, input layout cannot be None."
            )

        # Check partial inputs
137
138
139
140
141
142
143
144
145
            )

        # Check partial inputs
        if not self._allow_partial_inputs:
            self._check_partial_inputs([x_layout])

        if dim is not None and not isinstance(dim, (int, tuple, list)):
            raise TypeError(
                f"For {self.op_name}, the `dim` argument should be `None`, `int`, "
158
159
160
161
162
163
164
165
166
167
168
169
170
171
            return self._handle_all_axis_reduce(x_layout, keepdim)

        # Case 2: dim is an empty tuple/list — reduce no dimensions, output layout equals input.
        if isinstance(dim, (tuple, list)) and len(dim) == 0:
            output_layout = Layout(
                mesh_shape=x_layout.mesh_shape,
                alias_name=x_layout.alias_name,
                rank_list=x_layout.rank_list
            )
            return output_layout(*x_layout.alias_tensor_map)

        # Case 3: dim is int, tuple, or list with at least one element.
        output_layout = Layout(
            mesh_shape=x_layout.mesh_shape,
375
376
377
378
379
380
381
382
            )
        # torch.max(x) global reduction: only pass the tensor so the call
        # remains torch.max(local_x), not torch.max(local_x, None, False).
        if dim is None:
            local_args = (input_tensor.to_local(),)
        else:
            local_args = (input_tensor.to_local(), dim, keepdim)
        local_kwargs = {}
410
411
412
413
414
415
416
417
418
        # Element-wise mode: two Layout objects in cache_values.
        if len(cache_values) == 2 and hasattr(cache_values[1], "mesh_shape"):
            # Check partial inputs
            if not self._allow_partial_inputs:
                self._check_partial_inputs(cache_values)
            return ((deepcopy(cache_values[0]),), None)

        x_layout = cache_values[0]
        dim = cache_values[1]
424
425
426
427
428
429
430
431
432
            )

        # Check partial inputs
        if not self._allow_partial_inputs:
            self._check_partial_inputs([x_layout])

        if dim is not None and not isinstance(dim, (int, tuple, list)):
            raise TypeError(
                f"For {self.op_name}, the `dim` argument should be `None`, `int`, "
hyper_parallel/core/shard/ops/parallel_scaled_dot_product_attention.py
32
33
34
35
36
37
38
39
40


def _normalize_sdpa_args(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None,
                         enable_gqa=False):
    return (query, key, value, attn_mask, dropout_p, is_causal, scale), {'enable_gqa': enable_gqa}


class ScaledDotProductAttentionDistributedOp(DistributedOp):
    """Distributed operator for torch.nn.functional.scaled_dot_product_attention.
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
        Returns:
            tuple: (local_args, local_kwargs, cache_values) where local_args contains
                local tensors and runtime scalars, and cache_values contains Layout objects.
        """
        args, kwargs = _normalize_sdpa_args(*args, **kwargs)
        query, key, value, attn_mask, dropout_p, is_causal, scale = args
        enable_gqa = kwargs['enable_gqa']

        if hasattr(attn_mask, '_layout'):
            raise NotImplementedError(
                f"For {self.op_name}, DTensor attn_mask is not supported yet."
            )

        local_args = (
            query.to_local() if hasattr(query, '_layout') else query,
            key.to_local() if hasattr(key, '_layout') else key,
            value.to_local() if hasattr(value, '_layout') else value,
            attn_mask,
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
            dropout_p,
            is_causal,
            scale,
        )
        local_kwargs = {'enable_gqa': enable_gqa}

        cache_values = [
            query.layout if hasattr(query, '_layout') else None,
            key.layout if hasattr(key, '_layout') else None,
            value.layout if hasattr(value, '_layout') else None,
        ]
        return local_args, local_kwargs, cache_values

    @staticmethod
    def _normalize_dim_map(dim_map):
        """Normalize dim_map to string representation."""
341
342
343
344
345
346
347
348
349
                f"Query ndim: {query_ndim}\n"
                f"Key ndim: {len(key_layout.alias_tensor_map)}"
            )
        if value_layout is not None and len(value_layout.alias_tensor_map) != query_ndim:
            raise ValueError(
                f"For {op_name}, Query, Key and Value must have the same rank.\n"
                f"Query ndim: {query_ndim}\n"
                f"Value ndim: {len(value_layout.alias_tensor_map)}"
            )
546
547
548
549
550
551
552
553
554
                if lb_split_id is not None:
                    if lb_split_num is None:
                        raise ValueError("lb_split_num must not be None when lb_split_id is set")
                    split_id = lb_split_id
                    seq_split_num = lb_split_num
                else:
                    split_id = self._get_split_id(query_layout, dims)
                local_q_len = query.shape[dims["seq"]]
                global_kv_len = key.shape[dims["seq"]]
hyper_parallel/core/shard/ops/parallel_scatter_update.py
43
44
45
46
47
48
49
50
51
52
53
54

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_scatter_update_args(*args, **kwargs)
        x, indices, updates = args

        local_args = (
            x.to_local() if hasattr(x, '_layout') else x,
            indices.to_local() if hasattr(indices, '_layout') else indices,
            updates.to_local() if hasattr(updates, '_layout') else updates,
        )
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
            indices.to_local() if hasattr(indices, '_layout') else indices,
            updates.to_local() if hasattr(updates, '_layout') else updates,
        )

        cache_values = [
            x.layout if hasattr(x, '_layout') else None,
            indices.layout if hasattr(indices, '_layout') else None,
            updates.layout if hasattr(updates, '_layout') else None,
        ]
        local_kwargs = kwargs
        return local_args, local_kwargs, cache_values

    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
        Infer output layout for ScatterUpdate operator.
85
86
87
88
89
90
91
92
93
        """
        input_layout, indices_layout, updates_layout = cache_values

        if input_layout is None:
            raise ValueError(
                f"For {self.op_name}, input layout should not be None"
            )

        if indices_layout is None:
102
103
104
105
106
107
108
109
110

        # Partial inputs are intentionally allowed. The output inherits Partial status
        # from the input layout (see lines below), making this a Partial-preserving op.
        if not self._allow_partial_inputs:
            self._check_partial_inputs([input_layout])

        self._validate_strategy(input_layout, indices_layout, updates_layout)

        output_layout = Layout(
127
128
129
130
131
132
133
134
135
        indices_map = indices_layout.alias_tensor_map
        updates_map = updates_layout.alias_tensor_map

        if not input_map:
            raise ValueError(
                f"For {self.op_name}, input tensor map should not be empty"
            )

        if input_map[0] != "None":
hyper_parallel/core/shard/ops/parallel_slice_ext.py
22
23
24
25
26
27
28
29
30
from .parallel_ops import DistributedOp


def _normalize_slice_ext_args(x, axis, begin, end, step):
    return (x, axis, begin, end, step), {}


class SliceExtDistributedOp(DistributedOp):
    """Distributed implementation for SliceExt operator."""
39
40
41
42
43
44
45
46
47
48
49
50
51
52

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_slice_ext_args(*args, **kwargs)
        input_tensor, axis, begin, end, step = args
        local_args = (input_tensor.to_local(), axis, begin, end, step)
        local_kwargs = {}
        cache_values = [input_tensor.layout, axis]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
hyper_parallel/core/shard/ops/parallel_split.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from .parallel_ops import DistributedOp


def _normalize_split_with_size_args(x, split_sections, dim):
    return (x, split_sections, dim), {}


def _normalize_split_args(x, split_size_or_sections, dim=0):
    return (x, split_size_or_sections, dim), {}


def _normalize_split_tensor_args(x, split_size, dim):
    return (x, split_size, dim), {}


def _normalize_tensor_split_args(x, indices_or_sections, dim=0):
    return (x, indices_or_sections, dim), {}
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_split_with_size_args(*args, **kwargs)
        input_tensor, split_sections, dim = args
        output_num = len(split_sections)
        local_args = (input_tensor.to_local(), split_sections, dim)
        local_kwargs = {}
        cache_values = [input_tensor.layout, dim, output_num]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
89
90
91
92
93
94
95
96
97
98
        in_tensor_map = layout.alias_tensor_map
        ndim = len(in_tensor_map)

        if dim < 0:
            dim = ndim + dim
        if not 0 <= dim < ndim:
            raise ValueError(
                f"For {self.op_name}, dimension should be in range [0, {ndim}), "
                f"but got {dim}."
            )
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_split_with_size_args(*args, **kwargs)
        input_tensor, split_sections, dim = args
        output_num = len(split_sections)
        local_args = (input_tensor.to_local(), split_sections, dim)
        local_kwargs = {}
        cache_values = [input_tensor.layout, dim, output_num]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

        Raises:
            ValueError: If any rule above is violated.
        """
        layout = cache_values[0]
        dim = cache_values[1]
        output_num = cache_values[2]

        if not self._allow_partial_inputs:
            self._check_partial_inputs([layout])

        in_tensor_map = layout.alias_tensor_map
        ndim = len(in_tensor_map)

        if dim < 0:
            dim = ndim + dim
        if not 0 <= dim < ndim:
            raise ValueError(
                f"For {self.op_name}, dimension should be in range [0, {ndim}), "
                f"but got {dim}."
            )

        if in_tensor_map[dim] != "None":
            raise ValueError(
                f"For {self.op_name}, can not split tensor at sharded axis[{dim}], "
                f"but got layout: {layout}."
            )

        return (tuple(copy.deepcopy(layout) for _ in range(output_num)), None)


class SplitDistributedOp(DistributedOp):
    """Distributed implementation for Split operator (MindSpore Split and torch.split)."""
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_split_args(*args, **kwargs)
        input_tensor, split_size_or_sections, dim = args

        if isinstance(split_size_or_sections, int):
            output_num = math.ceil(input_tensor.shape[dim] / split_size_or_sections)
        else:
            output_num = len(split_size_or_sections)

        local_args = (input_tensor.to_local(), split_size_or_sections, dim)
        local_kwargs = {}
        cache_values = [input_tensor.layout, dim, output_num]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
229
230
231
232
233
234
235
236
237
        in_tensor_map = layout.alias_tensor_map
        ndim = len(in_tensor_map)

        if dim < 0:
            dim = ndim + dim
        if not 0 <= dim < ndim:
            raise ValueError(
                f"For {self.op_name}, dimension should be in range [0, {ndim}), "
                f"but got {dim}."
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_split_tensor_args(*args, **kwargs)
        input_tensor, split_size, dim = args
        output_num = math.ceil(input_tensor.shape[dim] / split_size)
        local_args = (input_tensor.to_local(), split_size, dim)
        local_kwargs = {}
        cache_values = [input_tensor.layout, dim, output_num]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
296
297
298
299
300
301
302
303
304
305
        in_tensor_map = layout.alias_tensor_map
        ndim = len(in_tensor_map)

        if dim < 0:
            dim = ndim + dim
        if not 0 <= dim < ndim:
            raise ValueError(
                f"For {self.op_name}, dimension should be in range [0, {ndim}), "
                f"but got {dim}."
            )
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

        Returns:
            tuple: (local_args, local_kwargs, cache_values)
        """
        args, kwargs = _normalize_split_tensor_args(*args, **kwargs)
        input_tensor, split_size, dim = args
        output_num = math.ceil(input_tensor.shape[dim] / split_size)
        local_args = (input_tensor.to_local(), split_size, dim)
        local_kwargs = {}
        cache_values = [input_tensor.layout, dim, output_num]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384

        Raises:
            ValueError: If any rule above is violated.
        """
        layout = cache_values[0]
        dim = cache_values[1]
        output_num = cache_values[2]

        if not self._allow_partial_inputs:
            self._check_partial_inputs([layout])

        in_tensor_map = layout.alias_tensor_map
        ndim = len(in_tensor_map)

        if dim < 0:
            dim = ndim + dim
        if not 0 <= dim < ndim:
            raise ValueError(
                f"For {self.op_name}, dimension should be in range [0, {ndim}), "
                f"but got {dim}."
            )

        if in_tensor_map[dim] != "None":
            raise ValueError(
                f"For {self.op_name}, can not split tensor at sharded axis[{dim}], "
                f"but got layout: {layout}."
            )

        return (tuple(copy.deepcopy(layout) for _ in range(output_num)), None)


class TensorSplitDistributedOp(DistributedOp):
    """Distributed implementation for tensor_split operator."""
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
                f"For {self.op_name}, indices_or_sections must be an integer, "
                f"list, tuple, or 1D tensor."
            )

        local_indices = indices_or_sections
        if hasattr(indices_or_sections, "_layout"):
            local_indices = indices_or_sections.to_local()

        local_args = (input_tensor.to_local(), local_indices, dim)
        local_kwargs = {}
        cache_values = [input_tensor.layout, dim, output_num]
        return local_args, local_kwargs, cache_values

    # pylint: disable=W0237
    def infer_layout(self, cache_values: list) -> Tuple[tuple, None]:
        """
hyper_parallel/core/shard/ops/parallel_stack.py
88
89
90
91
92
93
94
95
96

        valid_layouts = [lyt for lyt in layouts if lyt is not None]

        if not valid_layouts:
            raise ValueError(
                f"For {self.op_name}, stack requires at least one input DTensor."
            )

        if not self._allow_partial_inputs:
hyper_parallel/core/shard/ops/parallel_topk.py
82
83
84
85
86
87
88
89
90

        if dim is None:
            dim = -1
        if not isinstance(dim, int):
            raise ValueError(
                f"For {self.op_name}, dimension should be int, but got {type(dim)}"
            )

        alias_map = layout.alias_tensor_map
hyper_parallel/core/shard/ops/parallel_transpose.py
64
65
66
67
68
69
70
71
72
            local_args = (input_tensor.to_local(), dim0, dim1)
            local_kwargs = {}
            cache_values = [input_tensor.layout, dim0, dim1]
        else:
            raise ValueError(
                f"For TransposeDistributedOp, unsupported op_name: {self.op_name}. "
                f"Expected 'Transpose', 'transpose', 'permute', "
                f"'TransposeView', or 'TransposeExtView'."
            )
110
111
112
113
114
115
116
117
        if self.op_name in ("Transpose", "permute", "TransposeView"):
            axis = cache_values[1]

            if not isinstance(axis, (list, tuple)):
                raise ValueError(
                    f"For {self.op_name}, axis should be a list or tuple, "
                    f"but got {type(axis)}."
                )
126
127
128
129
130
131
132
133
134
            # check if axis is a permutation
            seen = set()
            for v in axis:
                if not isinstance(v, int):
                    raise ValueError(
                        f"For {self.op_name}, axis elements must be integers, "
                        f"but got {type(v)}."
                    )
                if v < 0 or v >= ndim or v in seen:
hyper_parallel/core/shard/ops/parallel_unbind.py
79
80
81
82
83
84
85
86
87
        alias_tensor_map = layout.alias_tensor_map
        ndim = len(shape)

        if not isinstance(dim, int):
            raise ValueError(
                f"For {self.op_name}, dimension should be int, but got {type(dim)}"
            )

        if dim < -ndim or dim >= ndim:
hyper_parallel/platform/mindspore/dtensor.py
64
65
66
67
68
69
70
71
72
73
74
        if not isinstance(placements, _LayoutInit):
            if local_tensor.has_init:
                local_tensor.init_device = npu_device
            else:
                dev = local_tensor.device
                if dev != "meta" and not dev.startswith(npu_device):
                    local_tensor = local_tensor.to(npu_device)

        t = Tensor._make_subclass(cls, local_tensor)
        t.__init_data__(local_tensor, device_mesh, placements)
        return t