Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / modules / shared_expert.py: 0%
123 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"""MoE shared expert module."""
17from __future__ import annotations
19from collections.abc import Mapping
20from functools import partial
21from typing import Any, Optional
23import torch # pylint: disable=forbidden-backend-import
24from torch import nn # pylint: disable=forbidden-backend-import
25from transformers.activations import ACT2FN
26from hyper_parallel.components.checkpoint.weight_conversion import (
27 WeightConverter,
28 WeightRenaming,
29)
31from hyper_parallel.components.checkpoint import ConcatenateWithSections
32from hyper_parallel.models.replacement import module_replacement
33from hyper_parallel.components.functional import swiglu
36class LinearWithMatmul(nn.Linear):
37 """Aligned with ColumnParallelLinear (single device, no parallelism).
39 name: ColumnParallelLinear
40 forward path: LinearWithGradAccumulationAndAsyncCommunication.forward
41 → output = torch.matmul(input, weight.t())
42 → output = output + bias (bias explicitly added separately, not addmm fused)
44 Used for: linear_fc1, linear_fc2
46 skip_bias_add=True: don't add bias, return (output, bias) for external addition.
47 skip_bias_add=False: bias explicitly added in forward, return (output, None).
49 NPU bf16 note: matmul runs in @custom_fwd autograd Function context,
50 NPU automatically selects the same kernel for non-contiguous weight.t() as weight.t().contiguous().
51 calling directly in forward does not trigger this behavior, so explicit .contiguous() is needed.
52 """
54 def __init__(
55 self,
56 in_features: int,
57 out_features: int,
58 bias: bool = True,
59 skip_bias_add: bool = True,
60 device: torch.device | str | None = None,
61 dtype: torch.dtype | None = None,
62 ) -> None:
63 """Initialize the linear parameters and bias-return behavior."""
64 super().__init__(in_features, out_features, bias=bias, device=device, dtype=dtype)
65 self.skip_bias_add = skip_bias_add
67 def forward( # pylint: disable=redefined-builtin
68 self, input: torch.Tensor
69 ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
70 """Apply matmul and optionally return bias for external addition."""
71 output = torch.matmul(input, self.weight.t().contiguous())
72 if self.skip_bias_add:
73 return output, self.bias
74 if self.bias is not None:
75 output = output + self.bias
76 return output, None
79@module_replacement
80class SharedExpert(nn.Module):
81 """Transformers-compatible parameter-owning MoE shared expert.
83 Args:
84 module: Source Transformers shared-expert module.
85 module_fqn: Fully qualified name supplied by the replacement framework.
86 context: Additional replacement context.
87 """
89 @staticmethod
90 def _separate_projections(module: nn.Module) -> tuple[nn.Linear, nn.Linear, nn.Linear]:
91 """Validate and return separate source projections."""
92 source_linears = (module.gate_proj, module.up_proj, module.down_proj)
93 if any(not isinstance(layer, nn.Linear) for layer in source_linears):
94 raise TypeError("SharedExpert source projections must be nn.Linear instances")
95 gate_proj, up_proj, down_proj = source_linears
96 if gate_proj.in_features != up_proj.in_features:
97 raise ValueError("SharedExpert gate and up projections must have the same input size")
98 if gate_proj.out_features != up_proj.out_features:
99 raise ValueError("SharedExpert gate and up projections must have the same output size")
100 if down_proj.in_features != gate_proj.out_features:
101 raise ValueError("SharedExpert down projection input size is incompatible with gate/up")
102 if down_proj.out_features != gate_proj.in_features:
103 raise ValueError("SharedExpert down projection output size is incompatible with gate/up")
104 if gate_proj.weight.requires_grad != up_proj.weight.requires_grad:
105 raise ValueError("SharedExpert gate and up weights must use the same training policy")
106 if (gate_proj.bias is None) != (up_proj.bias is None):
107 raise ValueError("SharedExpert gate and up projections must use the same bias policy")
108 if gate_proj.bias is not None and gate_proj.bias.requires_grad != up_proj.bias.requires_grad:
109 raise ValueError("SharedExpert gate and up biases must use the same training policy")
110 return gate_proj, up_proj, down_proj
112 def _initialize_fused_source(self, module: nn.Module) -> None:
113 """Reuse an already fused source module without converting parameters."""
114 self.linear_fc1 = module.linear_fc1
115 self.linear_fc2 = module.linear_fc2
116 if not all(hasattr(layer, "skip_bias_add") for layer in (self.linear_fc1, self.linear_fc2)):
117 raise TypeError("fused SharedExpert projections must return output and bias separately")
118 if not hasattr(module, "activation_func"):
119 raise TypeError("fused SharedExpert source module is missing activation_func")
120 self.activation_func = module.activation_func
121 self._return_bias_tuple = True
122 self._gate_size = 0
123 self._up_size = 0
124 self.train(module.training)
126 def _initialize_separate_linears(
127 self,
128 gate_proj: nn.Linear,
129 up_proj: nn.Linear,
130 down_proj: nn.Linear,
131 ) -> None:
132 """Construct fused linear layers from separate source projections."""
133 self._gate_size = gate_proj.out_features
134 self._up_size = up_proj.out_features
135 self.linear_fc1 = LinearWithMatmul(
136 gate_proj.in_features,
137 self._gate_size + self._up_size,
138 bias=gate_proj.bias is not None,
139 device=gate_proj.weight.device,
140 dtype=gate_proj.weight.dtype,
141 )
142 self.linear_fc1.weight.requires_grad_(gate_proj.weight.requires_grad)
143 if gate_proj.bias is not None:
144 self.linear_fc1.bias.requires_grad_(gate_proj.bias.requires_grad)
145 self.linear_fc2 = LinearWithMatmul(
146 down_proj.in_features,
147 down_proj.out_features,
148 bias=down_proj.bias is not None,
149 device=down_proj.weight.device,
150 dtype=down_proj.weight.dtype,
151 )
152 self.linear_fc2.weight.requires_grad_(down_proj.weight.requires_grad)
153 if down_proj.bias is not None:
154 self.linear_fc2.bias.requires_grad_(down_proj.bias.requires_grad)
156 def _initialize_activation(self, module: nn.Module, config: Any) -> None:
157 """Select the source-compatible gated activation implementation."""
158 hidden_act = getattr(config, "hidden_act", None)
159 if hidden_act is None:
160 hidden_act = getattr(config, "hidden_activation", None)
161 if hidden_act is None:
162 hidden_act = getattr(config, "mlp_hidden_act", None)
163 activation_func = getattr(module, "act_fn", None)
164 if activation_func is None:
165 if hidden_act is None:
166 hidden_act = "silu"
167 activation_func = ACT2FN[hidden_act]
168 if hidden_act == "silu" and bool(getattr(config, "use_fused_swiglu", True)):
169 self.activation_func = partial(swiglu, dim=-1)
170 return
172 def glu(x: torch.Tensor) -> torch.Tensor:
173 """Apply the configured gated linear unit."""
174 gate, value = torch.chunk(x, 2, dim=-1)
175 return activation_func(gate) * value
177 self.activation_func = glu
179 def __init__(
180 self,
181 *,
182 module: nn.Module,
183 module_fqn: str = "",
184 context: Mapping[str, Any] | None = None,
185 ) -> None:
186 """Build the high-performance module from a Transformers shared expert."""
187 super().__init__()
188 del module_fqn, context
189 config = getattr(module, "config", None)
190 self.config = config
191 self._source_is_fused = all(
192 hasattr(module, name) for name in ("linear_fc1", "linear_fc2")
193 )
194 self._source_is_separate = all(
195 hasattr(module, name) for name in ("gate_proj", "up_proj", "down_proj")
196 )
197 if not self._source_is_fused and not self._source_is_separate:
198 raise TypeError(
199 "SharedExpert requires linear_fc1/linear_fc2 or gate_proj/up_proj/down_proj"
200 )
202 if self._source_is_fused:
203 self._initialize_fused_source(module)
204 return
206 gate_proj, up_proj, down_proj = self._separate_projections(module)
207 self._initialize_separate_linears(gate_proj, up_proj, down_proj)
208 self._initialize_activation(module, config)
209 self._return_bias_tuple = False
210 self.train(module.training)
212 def make_transforms(self) -> list[WeightRenaming | WeightConverter]:
213 """Describe reversible source-checkpoint to high-performance conversion."""
214 if self._source_is_fused:
215 return []
216 transforms: list[WeightRenaming | WeightConverter] = [
217 WeightConverter(
218 source_patterns=["gate_proj.weight", "up_proj.weight"],
219 target_patterns="linear_fc1.weight",
220 operations=[
221 ConcatenateWithSections(
222 (self._gate_size, self._up_size),
223 dim=0,
224 )
225 ],
226 ),
227 WeightRenaming(
228 source_patterns="down_proj.weight",
229 target_patterns="linear_fc2.weight",
230 ),
231 ]
232 if self.linear_fc1.bias is not None:
233 transforms.insert(
234 1,
235 WeightConverter(
236 source_patterns=["gate_proj.bias", "up_proj.bias"],
237 target_patterns="linear_fc1.bias",
238 operations=[
239 ConcatenateWithSections(
240 (self._gate_size, self._up_size),
241 dim=0,
242 )
243 ],
244 ),
245 )
246 if self.linear_fc2.bias is not None:
247 transforms.append(
248 WeightRenaming(
249 source_patterns="down_proj.bias",
250 target_patterns="linear_fc2.bias",
251 )
252 )
253 return transforms
255 def forward(
256 self,
257 hidden_states: torch.Tensor,
258 ) -> torch.Tensor | tuple[torch.Tensor, Optional[torch.Tensor]]:
259 """Apply the shared-expert MLP while preserving the source return contract."""
260 intermediate, bias = self.linear_fc1(hidden_states)
261 if bias is not None:
262 intermediate = intermediate + bias
263 intermediate = self.activation_func(intermediate)
264 output, output_bias = self.linear_fc2(intermediate)
265 if not self._return_bias_tuple:
266 if output_bias is not None:
267 output = output + output_bias
268 return output
269 return output, output_bias