main
py 222 lines 8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """HyperConnection (Gated Residual) utilities — NVIDIA model variant.
4
5 Implements the HyperConnection residual scheme proposed in
6 "HyperConnections" (https://arxiv.org/abs/2409.19606). This NVIDIA 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 import vllm.envs as envs
29 from vllm.model_executor.layers.linear import (
30 MergedColumnParallelLinear,
31 ReplicatedLinear,
32 )
33 from vllm.model_executor.models.utils import maybe_prefix
34 from vllm.platforms import current_platform
35
36 from ..common.hyperconnection import (
37 GroupedGemmaRMSNorm,
38 HyperConnectionConfig,
39 )
40 from .ops.cute_dsl.hc_down_silu import MAX_FUSED_M, hc_down_silu
41 from .ops.hc import (
42 grouped_gemma_rmsnorm,
43 hc_combine,
44 hc_combine_norm,
45 hc_gate_mix,
46 hc_silu,
47 )
48
49
50 # ---------------------------------------------------------------------------
51 # Gated-residual variant
52 # ---------------------------------------------------------------------------
53 class GatedResidual(nn.Module):
54 """Gated HyperConnection with learnable low-rank mixing and injection.
55
56 ``combine_and_mix()`` runs the pre pipeline (grouped GemmaRMSNorm -> merged
57 low-rank down+inject GEMM -> silu -> up GEMM -> sigmoid -> gated mean
58 over the HC streams). When passed a pending block output, it fuses its
59 residual combine with the RMSNorm. A missing injection selects unit-weight
60 combine. Final mixers use ``use_combine=False`` and do not produce a new
61 injection.
62
63 Weights: the norm owns the grouped GemmaRMSNorm affine; the projections
64 are vLLM Linear modules (merged replicated linear for down+inject).
65 Eligible down+inject projections use the fused SiLU GEMM; other projections
66 use their Linear module's GEMM dispatch.
67 """
68
69 def __init__(
70 self,
71 config: HyperConnectionConfig,
72 use_combine: bool = True,
73 prefix: str = "",
74 ) -> None:
75 super().__init__()
76 self.config = config
77 self.lora_rank = config.hc_lowrank
78 self.hc_count = config.hc_count
79 self.hidden_size = config.hidden_size
80 self.use_combine = use_combine
81
82 norm_size = (
83 self.hyper_hidden_size if config.hc_per_branch_norm else config.hidden_size
84 )
85 group_size = config.hidden_size if config.hc_per_branch_norm else None
86 # Normalize each H-sized HC stream independently while retaining a
87 # separate affine weight for every element of the HC*H layout.
88 self.hc_norm = GroupedGemmaRMSNorm(
89 norm_size,
90 eps=config.rms_norm_eps,
91 group_size=group_size,
92 dtype=config.params_dtype,
93 )
94
95 # -- vLLM Linear weights --------------------------------------------
96 # The merged skinny-GEMM shape is physically padded to 16 rows to ensure
97 # good alignment and performant implementation chosen by CuBLAS heuristics.
98 self.pad_size = (-(self.lora_rank + self.hc_count)) % 16 if use_combine else 0
99 if use_combine:
100 self.input_mix_weight_down_block_inject = MergedColumnParallelLinear(
101 self.hyper_hidden_size,
102 [self.lora_rank, self.hc_count]
103 + ([self.pad_size] if self.pad_size else []),
104 bias=False,
105 params_dtype=config.params_dtype,
106 quant_config=None,
107 prefix=maybe_prefix(prefix, "input_mix_weight_down_block_inject"),
108 return_bias=False,
109 disable_tp=True,
110 )
111 weight = self.input_mix_weight_down_block_inject.weight
112 self._use_hc_down_silu = (
113 weight.shape[1] % 8 == 0
114 and weight.dtype == torch.bfloat16
115 and current_platform.has_device_capability(90)
116 )
117 else:
118 self.input_mix_weight_down = ReplicatedLinear(
119 self.hyper_hidden_size,
120 self.lora_rank,
121 bias=False,
122 params_dtype=config.params_dtype,
123 quant_config=None,
124 prefix=maybe_prefix(prefix, "input_mix_weight_down"),
125 return_bias=False,
126 )
127 self.input_mix_weight_up = ReplicatedLinear(
128 self.lora_rank,
129 self.hyper_hidden_size,
130 bias=False,
131 params_dtype=config.params_dtype,
132 quant_config=None,
133 prefix=maybe_prefix(prefix, "input_mix_weight_up"),
134 return_bias=False,
135 )
136
137 def _down_and_inject(
138 self, xn: torch.Tensor
139 ) -> tuple[torch.Tensor, torch.Tensor | None]:
140 """Down projection + SiLU; also returns injection logits if combined."""
141 if not self.use_combine:
142 return hc_silu(self.input_mix_weight_down(xn), self.hc_count), None
143
144 use_fused = (
145 self._use_hc_down_silu
146 and not envs.VLLM_BATCH_INVARIANT
147 and 1 <= xn.shape[0] <= MAX_FUSED_M
148 )
149 if use_fused:
150 return hc_down_silu(
151 xn,
152 self.input_mix_weight_down_block_inject.weight,
153 self.lora_rank,
154 self.hc_count,
155 )
156 down_and_injection = self.input_mix_weight_down_block_inject(xn)
157 split_sizes = [self.lora_rank, self.hc_count, self.pad_size]
158 lora, injection, _ = down_and_injection.split(split_sizes, dim=-1)
159 return hc_silu(lora, self.hc_count), injection
160
161 def mix(
162 self, hidden_states: torch.Tensor
163 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
164 xn = grouped_gemma_rmsnorm(
165 hidden_states,
166 self.hc_norm.weight,
167 self.config.rms_norm_eps,
168 self.hc_count,
169 )
170
171 lora, injection = self._down_and_inject(xn)
172 gate = self.input_mix_weight_up(lora) # [M, D]
173 block_input = hc_gate_mix(xn, gate, self.hc_count)
174
175 return hidden_states, block_input, injection
176
177 def combine_and_mix(
178 self,
179 hidden_states: torch.Tensor,
180 prev_block_output: torch.Tensor,
181 prev_injection: torch.Tensor | None,
182 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
183 """Consume a pending combine, then prepare the next block input.
184
185 ``hidden_states`` is the multi-stream state from before the pending
186 block's mix. Its combine with ``block_output`` is fused with this
187 module's input RMSNorm. A missing injection applies the block output
188 to every stream with unit weight.
189 """
190 hidden_states, xn = hc_combine_norm(
191 hidden_states,
192 prev_block_output,
193 prev_injection,
194 self.hc_norm.weight,
195 self.config.rms_norm_eps,
196 self.hc_count,
197 )
198
199 lora, injection = self._down_and_inject(xn)
200 gate = self.input_mix_weight_up(lora) # [M, D]
201 block_input = hc_gate_mix(xn, gate, self.hc_count)
202
203 return hidden_states, block_input, injection
204
205 def combine(
206 self,
207 hidden_states: torch.Tensor,
208 block_output: torch.Tensor,
209 injection: torch.Tensor | None,
210 ) -> torch.Tensor:
211 return hc_combine(hidden_states, block_output, injection, self.hc_count)
212
213 @property
214 def hyper_hidden_size(self) -> int:
215 return self.hc_count * self.hidden_size
216
217
218 __all__ = [
219 "GatedResidual",
220 "GroupedGemmaRMSNorm",
221 "HyperConnectionConfig",
222 ]