main
py 205 lines 7.25 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """HyperConnection (Gated Residual) utilities for the AMD model variant.
4
5 Implements the HyperConnection residual scheme proposed in
6 "HyperConnections" (https://arxiv.org/abs/2409.19606). This AMD variant
7 delays each HC combine to the following HC mix boundary. HC glue kernels,
8 including fused combine+RMSNorm, live in ``ops/hc.py``; projections remain
9 standard vLLM Linear modules.
10
11 Hidden states between layers have shape ``[..., HC*HS]`` with HS inner
12 (HC outer, HS inner — checkpoint-native layout).
13
14 Typical usage inside a transformer decoder layer::
15
16 self.attn_hc = GatedResidual(hc_config)
17
18 hidden_states, block_input, injection = self.attn_hc.mix(hidden_states)
19 attention_output = attention(block_input)
20 hidden_states, block_input, injection = self.mlp_hc.combine_and_mix(
21 hidden_states, attention_output, injection
22 )
23 """
24
25 import torch
26 from torch import nn
27
28 from vllm.model_executor.layers.linear import (
29 MergedColumnParallelLinear,
30 ReplicatedLinear,
31 )
32 from vllm.model_executor.models.utils import maybe_prefix
33
34 from ..common.hyperconnection import (
35 GroupedGemmaRMSNorm,
36 HyperConnectionConfig,
37 )
38 from .ops.hc import (
39 grouped_gemma_rmsnorm,
40 hc_combine,
41 hc_combine_norm,
42 hc_gate_mix,
43 hc_silu,
44 )
45
46
47 # ---------------------------------------------------------------------------
48 # Gated-residual variant
49 # ---------------------------------------------------------------------------
50 class GatedResidual(nn.Module):
51 """Gated HyperConnection with learnable low-rank mixing and injection.
52
53 ``combine_and_mix()`` runs the pre pipeline (grouped GemmaRMSNorm -> merged
54 low-rank down+inject GEMM -> silu -> up GEMM -> sigmoid -> gated mean
55 over the HC streams). When passed a pending block output and an injection,
56 it fuses their residual combine with the RMSNorm. Final mixers use
57 ``use_combine=False`` and do not produce a new injection.
58
59 Weights: the norm owns the grouped GemmaRMSNorm affine; the projections
60 are vLLM Linear modules (merged replicated linear for down+inject), so
61 GEMM dispatch (e.g. the low-latency skinny GEMM) applies through the
62 standard quant_method mechanism.
63 """
64
65 def __init__(
66 self,
67 config: HyperConnectionConfig,
68 use_combine: bool = True,
69 prefix: str = "",
70 ) -> None:
71 super().__init__()
72 self.config = config
73 self.lora_rank = config.hc_lowrank
74 self.hc_count = config.hc_count
75 self.hidden_size = config.hidden_size
76 self.use_combine = use_combine
77
78 norm_size = (
79 self.hyper_hidden_size if config.hc_per_branch_norm else config.hidden_size
80 )
81 group_size = config.hidden_size if config.hc_per_branch_norm else None
82 # Normalize each H-sized HC stream independently while retaining a
83 # separate affine weight for every element of the HC*H layout.
84 self.hc_norm = GroupedGemmaRMSNorm(
85 norm_size,
86 eps=config.rms_norm_eps,
87 group_size=group_size,
88 dtype=config.params_dtype,
89 )
90
91 # -- vLLM Linear weights --------------------------------------------
92 # The merged skinny-GEMM shape is physically padded to 16 rows for
93 # alignment and efficient backend dispatch.
94 self.pad_size = (-(self.lora_rank + self.hc_count)) % 16 if use_combine else 0
95 if use_combine:
96 self.input_mix_weight_down_block_inject = MergedColumnParallelLinear(
97 self.hyper_hidden_size,
98 [self.lora_rank, self.hc_count]
99 + ([self.pad_size] if self.pad_size else []),
100 bias=False,
101 params_dtype=config.params_dtype,
102 quant_config=None,
103 prefix=maybe_prefix(prefix, "input_mix_weight_down_block_inject"),
104 return_bias=False,
105 disable_tp=True,
106 )
107 else:
108 self.input_mix_weight_down = ReplicatedLinear(
109 self.hyper_hidden_size,
110 self.lora_rank,
111 bias=False,
112 params_dtype=config.params_dtype,
113 quant_config=None,
114 prefix=maybe_prefix(prefix, "input_mix_weight_down"),
115 return_bias=False,
116 )
117 self.input_mix_weight_up = ReplicatedLinear(
118 self.lora_rank,
119 self.hyper_hidden_size,
120 bias=False,
121 params_dtype=config.params_dtype,
122 quant_config=None,
123 prefix=maybe_prefix(prefix, "input_mix_weight_up"),
124 return_bias=False,
125 )
126
127 def mix(
128 self, hidden_states: torch.Tensor
129 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
130 xn = grouped_gemma_rmsnorm(
131 hidden_states,
132 self.hc_norm.weight,
133 self.config.rms_norm_eps,
134 self.hc_count,
135 )
136
137 if self.use_combine:
138 # produce injection logits for combine
139 split_sizes = [self.lora_rank, self.hc_count, self.pad_size]
140 down_and_injection = self.input_mix_weight_down_block_inject(xn)
141 lora, injection, _ = down_and_injection.split(split_sizes, dim=-1)
142 else:
143 lora = self.input_mix_weight_down(xn)
144 injection = None
145
146 lora = hc_silu(lora, self.hc_count)
147 gate = self.input_mix_weight_up(lora) # [M, D]
148 block_input = hc_gate_mix(xn, gate, self.hc_count)
149
150 return hidden_states, block_input, injection
151
152 def combine_and_mix(
153 self,
154 hidden_states: torch.Tensor,
155 prev_block_output: torch.Tensor,
156 prev_injection: torch.Tensor,
157 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
158 """Consume a pending combine, then prepare the next block input.
159
160 ``hidden_states`` is the multi-stream state from before the pending
161 block's mix. Its combine with ``block_output`` is fused with this
162 module's input RMSNorm.
163 """
164 hidden_states, xn = hc_combine_norm(
165 hidden_states,
166 prev_block_output,
167 prev_injection,
168 self.hc_norm.weight,
169 self.config.rms_norm_eps,
170 self.hc_count,
171 )
172
173 if self.use_combine:
174 # produce injection logits for combine
175 split_sizes = [self.lora_rank, self.hc_count, self.pad_size]
176 down_and_injection = self.input_mix_weight_down_block_inject(xn)
177 lora, injection, _ = down_and_injection.split(split_sizes, dim=-1)
178 else:
179 lora = self.input_mix_weight_down(xn)
180 injection = None
181
182 lora = hc_silu(lora, self.hc_count)
183 gate = self.input_mix_weight_up(lora) # [M, D]
184 block_input = hc_gate_mix(xn, gate, self.hc_count)
185
186 return hidden_states, block_input, injection
187
188 def combine(
189 self,
190 hidden_states: torch.Tensor,
191 block_output: torch.Tensor,
192 injection: torch.Tensor,
193 ) -> torch.Tensor:
194 return hc_combine(hidden_states, block_output, injection, self.hc_count)
195
196 @property
197 def hyper_hidden_size(self) -> int:
198 return self.hc_count * self.hidden_size
199
200
201 __all__ = [
202 "GatedResidual",
203 "GroupedGemmaRMSNorm",
204 "HyperConnectionConfig",
205 ]