Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / pipeline_parallel / _microbatch.py: 91%
55 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +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"""pipeline parallel utils"""
16from typing import Optional
18import torch
19from torch import nn
21import hyper_parallel
24class _MicroBatch(nn.Module):
25 """
26 Split inputs into micro_batch in pipeline parallel.
28 Args:
29 micro_batch_num (int): The number of micro-batch.
30 args_batch_dim (list, optional): Specify the batch dim of the args.
31 Default ``None``.
32 kwargs_batch_dim(dict, optional): Specify the batch dim of the kwargs.
33 Default ``None``.
34 Inputs:
35 - **args** (list) - Input args.
36 - **kwargs** (dict) - Input kwargs.
38 Outputs:
39 - **args_after_split** (list) - Input args after split into micro_batches.
40 - **kwargs_after_split** (list) - Input kwargs after split into micro_batches.
41 """
43 def __init__(self, micro_batch_num: int, args_batch_dim: Optional[tuple] = None,
44 kwargs_batch_dim: Optional[dict] = None) -> None:
45 """Store the number of micro-batches and their batch dimensions."""
46 super().__init__()
47 self.micro_batch_num = micro_batch_num
48 self.args_batch_dim = args_batch_dim
49 self.kwargs_batch_dim = kwargs_batch_dim
51 def _split_args_for_micro_batch(self, args: tuple, micro_idx: int) -> list:
52 """Split positional arguments for one micro-batch."""
53 micro_args = []
54 for arg_idx, cur_arg in enumerate(args):
55 cur_arg_batch_dim = 0
56 if self.args_batch_dim and self.args_batch_dim[arg_idx] is not None:
57 cur_arg_batch_dim = self.args_batch_dim[arg_idx].batch_dim
58 if isinstance(cur_arg, hyper_parallel.DTensor):
59 micro_arg = self.split_inputs_with_custom_shard(cur_arg, cur_arg_batch_dim, micro_idx)
60 else:
61 micro_arg = self.split_inputs(cur_arg, cur_arg_batch_dim, micro_idx)
62 micro_args.append(micro_arg)
63 return micro_args
65 def _split_kwargs_for_micro_batch(self, kwargs: dict, micro_idx: int) -> dict:
66 """Split keyword arguments for one micro-batch."""
67 micro_kwargs = {}
68 for key, cur_kwarg in kwargs.items():
69 cur_kwarg_batch_dim = 0
70 if self.kwargs_batch_dim is not None:
71 cur_kwarg_batch_dim = self.kwargs_batch_dim[key].batch_dim
72 if isinstance(cur_kwarg, hyper_parallel.DTensor):
73 micro_kwarg = self.split_inputs_with_custom_shard(cur_kwarg, cur_kwarg_batch_dim, micro_idx)
74 else:
75 micro_kwarg = self.split_inputs(cur_kwarg, cur_kwarg_batch_dim, micro_idx)
76 micro_kwargs[key] = micro_kwarg
77 return micro_kwargs
79 def forward(self, args: tuple, kwargs: dict) -> tuple[list, list]:
80 """forward of _MicroBatch"""
81 args_after_split = []
82 kwargs_after_split = []
83 for micro_idx in range(self.micro_batch_num):
84 args_after_split.append(self._split_args_for_micro_batch(args, micro_idx))
85 kwargs_after_split.append(self._split_kwargs_for_micro_batch(kwargs, micro_idx))
86 return args_after_split, kwargs_after_split
88 def split_inputs_with_custom_shard(
89 self, input_tensor: hyper_parallel.DTensor,
90 cur_arg_batch_dim: int, micro_idx: int) -> hyper_parallel.DTensor:
91 """Split a DTensor input along the batch dimension while preserving its distributed layout."""
92 input_layout = input_tensor.layout
93 func_wrap = hyper_parallel.custom_shard(self.split_inputs,
94 device_mesh=input_layout.mesh,
95 out_placements=(input_layout.placements,),
96 in_placements=(input_layout.placements, None, None)
97 )
98 return func_wrap(input_tensor, cur_arg_batch_dim, micro_idx)
100 def split_inputs(self, input_tensor: torch.Tensor, cur_arg_batch_dim: int, micro_idx: int) -> torch.Tensor:
101 """
102 Split the input along the specified batch_dim and micro_idx
103 """
104 if cur_arg_batch_dim == -1:
105 return input_tensor
106 batch_dim_shape = input_tensor.shape[cur_arg_batch_dim]
107 if batch_dim_shape % self.micro_batch_num != 0:
108 raise ValueError(f"Batch dimension size {batch_dim_shape} is not divisible by \
109 micro_batch_num {self.micro_batch_num}")
110 micro_batch_size = batch_dim_shape // self.micro_batch_num
112 # Calculate start and end idx
113 start = micro_batch_size * micro_idx
114 end = micro_batch_size * (micro_idx + 1)
116 # Create slicing tuple
117 slices = [slice(None)] * input_tensor.ndim
118 slices[cur_arg_batch_dim] = slice(start, end)
119 return input_tensor[tuple(slices)]