Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / modules / mtp.py: 100%
71 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +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"""DeepSeek-V3-style MTP layers and teacher-forced training orchestration.
17Fusion uses the released-weight convention [normalized embedding, normalized
18hidden], as in Transformers MtpLayer. The training loop uses ground-truth future
19tokens, not the candidate-token generation loop in Transformers MtpModel.
20Model adapters own decoder construction, attention/position metadata, distributed
21layouts and checkpoint key mapping. No model-specific routing policy lives here.
22"""
24from __future__ import annotations
26import math
27from collections.abc import Callable, Mapping, Sequence
28from dataclasses import dataclass
29from functools import partial
30from typing import Any
32import torch
33from torch import nn
35__all__ = ["MTPLayer", "MultiTokenPrediction", "MTPOutput", "build_mtp"]
38class _MTPRMSNorm(nn.Module):
39 """Match the HF DeepSeek normalization cast order, including FP32 weights."""
41 def __init__(self, hidden_size: int, eps: float) -> None:
42 """Create a direct scale with the requested normalization epsilon."""
43 super().__init__()
44 self.weight = nn.Parameter(torch.ones(hidden_size))
45 self.eps = eps
47 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
48 """Normalize in FP32 and preserve the reference multiplication order."""
49 input_dtype = hidden_states.dtype
50 states = hidden_states.float()
51 states = states * torch.rsqrt(states.square().mean(-1, keepdim=True) + self.eps)
52 return self.weight * states.to(input_dtype)
55class MTPLayer(nn.Module):
56 """Fuse a future embedding with the previous depth's hidden state.
58 Args:
59 hidden_size: Width of both inputs and the decoder output.
60 decoder: Model-owned causal Transformer block returning a Tensor.
61 rms_norm_eps: Epsilon for the default DeepSeek-style RMSNorms.
62 norm_factory: Optional factory accepting hidden_size. It must return a
63 fresh module for each of enorm, hnorm and post_norm.
65 Note:
66 All three norms have independent parameters. The returned post-normalized
67 state is used by both the shared head and the next MTP depth. A model with
68 different fusion/normalization semantics should supply its own layer to
69 MultiTokenPrediction, rather than changing this released-weight convention.
70 """
72 def __init__(self, hidden_size: int, decoder: nn.Module, *, rms_norm_eps: float = 1e-6,
73 norm_factory: Callable[[int], nn.Module] | None = None) -> None:
74 """Build independent fusion and output norms around the supplied decoder."""
75 super().__init__()
76 if isinstance(hidden_size, bool) or not isinstance(hidden_size, int) or hidden_size <= 0:
77 raise ValueError("hidden_size must be a positive integer")
78 if not math.isfinite(rms_norm_eps) or rms_norm_eps <= 0:
79 raise ValueError("rms_norm_eps must be positive and finite")
80 factory = norm_factory or partial(_MTPRMSNorm, eps=rms_norm_eps)
81 self.enorm = factory(hidden_size)
82 self.hnorm = factory(hidden_size)
83 self.eh_proj = nn.Linear(2 * hidden_size, hidden_size, bias=False)
84 self.mtp_block = decoder
85 self.post_norm = factory(hidden_size)
87 def forward(self, hidden_states: torch.Tensor, inputs_embeds: torch.Tensor,
88 **decoder_kwargs: Any) -> torch.Tensor:
89 """Return [B, S, H] states; kwargs must describe this depth's sequence."""
90 if hidden_states.shape != inputs_embeds.shape:
91 raise ValueError("MTP hidden_states and inputs_embeds must have identical shapes")
92 projection_input = torch.cat((self.enorm(inputs_embeds), self.hnorm(hidden_states)), dim=-1)
93 return self.post_norm(self.mtp_block(self.eh_proj(projection_input), **decoder_kwargs))
96@dataclass
97class MTPOutput:
98 """Depth k (one-based) has S-k positions; logits are empty if head is None."""
100 hidden_states: tuple[torch.Tensor, ...]
101 logits: tuple[torch.Tensor, ...]
104class MultiTokenPrediction(nn.Module):
105 """Run independent MTP depths sequentially with ground-truth future tokens.
107 Args:
108 layers: Independent layers with forward(hidden_states, inputs_embeds,
109 **decoder_kwargs) -> Tensor. Normally MTPLayer instances;
110 adapters may supply another layer with the same contract.
112 Note:
113 This loop consumes complete, unsharded rows. It does not implement CP halo
114 exchange, packed-document boundaries, generation/KV caching or automatic
115 Trainer integration. Those belong to the model adapter. A distributed
116 adapter may also call individual layers with already-aligned inputs.
117 """
119 def __init__(self, layers: Sequence[nn.Module]) -> None:
120 """Register the prediction layers without copying external shared weights."""
121 super().__init__()
122 if not layers:
123 raise ValueError("MTP requires at least one prediction layer")
124 self.layers = nn.ModuleList(layers)
126 def forward(self, hidden_states: torch.Tensor, input_ids: torch.Tensor, *,
127 embedding: nn.Module, head: nn.Module | None = None,
128 decoder_kwargs: Sequence[Mapping[str, Any]] | None = None) -> MTPOutput:
129 """Predict future tokens without detaching the main-model computation.
131 Args:
132 hidden_states: Main trunk output BEFORE its final norm, [B, S, H].
133 input_ids: Ground-truth tokens [B, S], not next-token labels.
134 embedding: Main model's embedding, reused without registering a copy.
135 head: Main model's output projection, or None for external chunk loss.
136 decoder_kwargs: One mapping per depth. For depth k the caller must
137 provide attention masks/positions for S-k query positions. None
138 is suitable only for decoders that construct causal metadata.
140 Returns:
141 Per-depth states and optional logits. At depth k, position i fuses
142 the prior state at i with token i+k, and predicts token i+k+1.
143 """
144 if hidden_states.ndim != 3 or input_ids.ndim != 2 or hidden_states.shape[:2] != input_ids.shape:
145 raise ValueError("MTP expects aligned hidden_states [B, S, H] and input_ids [B, S]")
146 if input_ids.shape[0] == 0 or input_ids.shape[1] <= len(self.layers):
147 raise ValueError("MTP needs a nonempty batch and sequence length greater than its depth")
148 if decoder_kwargs is not None and len(decoder_kwargs) != len(self.layers):
149 raise ValueError("decoder_kwargs must contain one mapping per MTP depth")
150 states, logits = [], []
151 for index, layer in enumerate(self.layers):
152 kwargs = {} if decoder_kwargs is None else decoder_kwargs[index]
153 future_embeddings = embedding(input_ids[:, index + 1:])
154 hidden_states = layer(hidden_states[:, :-1], future_embeddings, **kwargs)
155 states.append(hidden_states)
156 if head is not None:
157 logits.append(head(hidden_states))
158 return MTPOutput(tuple(states), tuple(logits))
161def build_mtp(
162 mtp_num_layers: int | None = None, *, layer_factory: Callable[[int], nn.Module],
163) -> MultiTokenPrediction | None:
164 """Build the configured MTP depths, or return None when disabled.
166 Args:
167 mtp_num_layers: Megatron-style depth count. None or zero disables MTP;
168 a positive integer creates that many independent prediction layers.
169 layer_factory: Model-owned factory called with each zero-based depth.
170 It must return a fresh MTP layer, including its causal decoder.
172 Returns:
173 Registered MTP stack, or None without invoking the factory when disabled.
175 Note:
176 Model builders pass their YAML mtp_num_layers value here, attach the
177 returned module, and include its output in forward/loss when enabled.
178 This factory does not attach MTP to arbitrary models automatically.
179 """
180 if mtp_num_layers is None:
181 return None
182 if isinstance(mtp_num_layers, bool) or not isinstance(mtp_num_layers, int) or mtp_num_layers < 0:
183 raise ValueError("mtp_num_layers must be None or a nonnegative integer")
184 if mtp_num_layers == 0:
185 return None
186 return MultiTokenPrediction([layer_factory(depth) for depth in range(mtp_num_layers)])