main
py 1,178 lines 43.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # Copyright 2025 The vLLM team.
4 # Copyright 2025 Google Inc. HuggingFace Inc. team. All rights reserved.
5 #
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 from collections.abc import Iterable
19
20 import torch
21 from torch import nn
22 from transformers.models.gemma3n.configuration_gemma3n import Gemma3nTextConfig
23
24 from vllm.compilation.decorators import support_torch_compile
25 from vllm.config import CacheConfig, VllmConfig
26 from vllm.distributed import get_tensor_model_parallel_world_size
27 from vllm.forward_context import get_forward_context
28 from vllm.logger import init_logger
29 from vllm.model_executor.layers.activation import (
30 _ACTIVATION_REGISTRY,
31 GeluAndMul,
32 GeluAndMulSparse,
33 )
34 from vllm.model_executor.layers.attention import Attention
35 from vllm.model_executor.layers.layernorm import RMSNorm
36 from vllm.model_executor.layers.linear import (
37 ColumnParallelLinear,
38 MergedColumnParallelLinear,
39 QKVParallelLinear,
40 ReplicatedLinear,
41 RowParallelLinear,
42 )
43 from vllm.model_executor.layers.logits_processor import LogitsProcessor
44 from vllm.model_executor.layers.quantization import QuantizationConfig
45 from vllm.model_executor.layers.rotary_embedding import get_rope
46 from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
47 from vllm.sequence import IntermediateTensors
48 from vllm.v1.attention.backends.utils import KVSharingFastPrefillMetadata
49
50 from .interfaces import SupportsQuant
51 from .utils import (
52 AutoWeightsLoader,
53 WeightsMapper,
54 extract_layer_index,
55 make_layers,
56 maybe_prefix,
57 )
58
59 logger = init_logger(__name__)
60
61 EPS = torch.tensor(torch.finfo().min)
62
63
64 class Gemma3nAltUp(nn.Module):
65 """Alternating updates (Altup)
66 The AltUp module wraps transformer layers. The `predict` step modifies the
67 input to the transformer layer, and the `correct` step propagates the output
68 of the transformer layer to the sparsely updated dimensions.
69 See more in the research paper:
70 https://proceedings.neurips.cc/paper_files/paper/2023/file/f2059277ac6ce66e7e5543001afa8bb5-Paper-Conference.pdf
71 """
72
73 def __init__(
74 self,
75 hidden_size: int,
76 rms_norm_eps: float,
77 altup_num_inputs: int,
78 altup_coef_clip: float,
79 altup_active_idx: int,
80 quant_config: QuantizationConfig | None,
81 prefix: str,
82 ):
83 super().__init__()
84
85 self.altup_num_inputs = altup_num_inputs
86 self.altup_active_idx = altup_active_idx
87 self.altup_coef_clip = altup_coef_clip
88
89 self.correction_coefs = ReplicatedLinear(
90 altup_num_inputs,
91 altup_num_inputs,
92 bias=False,
93 quant_config=quant_config,
94 prefix=f"{prefix}.correction_coefs",
95 return_bias=False,
96 )
97 self.prediction_coefs = ReplicatedLinear(
98 altup_num_inputs,
99 altup_num_inputs**2,
100 bias=False,
101 quant_config=quant_config,
102 prefix=f"{prefix}.prediction_coefs",
103 return_bias=False,
104 )
105 self.modality_router = ReplicatedLinear(
106 hidden_size,
107 altup_num_inputs,
108 bias=False,
109 quant_config=quant_config,
110 prefix=f"{prefix}.modality_router",
111 return_bias=False,
112 )
113 self.router_norm = RMSNorm(
114 hidden_size=hidden_size,
115 eps=rms_norm_eps,
116 )
117 self.register_buffer(
118 "router_input_scale",
119 torch.tensor(hidden_size**-1.0, dtype=self.modality_router.weight.dtype),
120 persistent=False,
121 )
122 self.correct_output_scale = nn.Parameter(
123 torch.zeros(hidden_size, dtype=torch.float32)
124 )
125
126 def _compute_router_modalities(self, x: torch.Tensor) -> torch.Tensor:
127 router_inputs = self.router_norm(x) * self.router_input_scale
128 routed = self.modality_router(router_inputs)
129 return torch.tanh(routed.float()).type_as(x)
130
131 def scale_corrected_output(self, corrected: torch.Tensor) -> torch.Tensor:
132 return (
133 corrected.type_as(self.correct_output_scale) * self.correct_output_scale
134 ).type_as(corrected)
135
136 def predict(self, hidden_states: torch.Tensor) -> torch.Tensor:
137 # hidden: [altup_num_inputs, num_tokens, hidden_size]
138 # modalities: [num_tokens, num_altup_inputs]
139 # all_coefs: [num_tokens, num_altup_inputs ** 2]
140 modalities = self._compute_router_modalities(
141 hidden_states[self.altup_active_idx]
142 )
143 all_coefs = self.prediction_coefs(modalities)
144
145 # Reshape and transpose the 2D matrix for the matmul.
146 # all_coefs_T: [num_tokens, num_altup_inputs, num_altup_inputs]
147 all_coefs_T = all_coefs.reshape(
148 -1,
149 self.altup_num_inputs,
150 self.altup_num_inputs,
151 ).permute(0, 2, 1)
152
153 # hidden_states to [num_tokens, hidden_size, altup_num_inputs]
154 predictions = torch.matmul(hidden_states.permute(1, 2, 0), all_coefs_T)
155 # [altup_num_inputs, num_tokens, hidden_size]
156 predictions = predictions.permute(2, 0, 1)
157 predictions += hidden_states
158 return predictions.contiguous()
159
160 def correct(
161 self, predictions: torch.Tensor, activated: torch.Tensor
162 ) -> torch.Tensor:
163 # predictions: [altup_num_inputs, num_tokens, hidden_size]
164 # activated: [num_tokens, hidden_size]
165 # modalities: [num_tokens, altup_num_inputs]
166 modalities = self._compute_router_modalities(activated)
167 # innovation: [num_tokens, altup_num_inputs]
168 innovation = activated - predictions[self.altup_active_idx]
169 # innovation: [altup_num_inputs, num_tokens, hidden_size]
170 innovation = innovation.repeat(self.altup_num_inputs, 1, 1)
171
172 # Permute to [altup_num_inputs, num_tokens] as the last dim
173 # is a scalar applied to each altup input and expand on
174 # num_tokens dim for broadcastability over hidden_size.
175 # all_coefs: [num_tokens, altup_num_inputs]
176 all_coefs = self.correction_coefs(modalities) + 1.0
177 # all_coefs: [altup_num_inputs, num_tokens, 1]
178 all_coefs = all_coefs.T.unsqueeze(-1)
179
180 # Elementwise (broadcast over hidden_size).
181 corrected = torch.mul(innovation, all_coefs)
182 corrected += predictions
183
184 return corrected.contiguous()
185
186
187 class Gemma3nLaurelBlock(nn.Module):
188 """Learned Augmented Residual Layer."""
189
190 def __init__(
191 self,
192 hidden_size: int,
193 laurel_rank: int,
194 rms_norm_eps: float,
195 *,
196 quant_config: QuantizationConfig | None = None,
197 prefix: str,
198 ) -> None:
199 super().__init__()
200
201 self.linear_left = ColumnParallelLinear(
202 hidden_size,
203 laurel_rank,
204 bias=False,
205 quant_config=quant_config,
206 prefix=f"{prefix}.linear_left",
207 return_bias=False,
208 )
209 self.linear_right = RowParallelLinear(
210 laurel_rank,
211 hidden_size,
212 bias=False,
213 quant_config=quant_config,
214 prefix=f"{prefix}.linear_right",
215 return_bias=False,
216 )
217 self.post_laurel_norm = RMSNorm(
218 hidden_size=hidden_size,
219 eps=rms_norm_eps,
220 )
221
222 def forward(self, x: torch.Tensor) -> torch.Tensor:
223 laurel_x = self.linear_left(x)
224 laurel_x = self.linear_right(laurel_x)
225 normed_laurel_x = self.post_laurel_norm(laurel_x)
226 return x + normed_laurel_x
227
228
229 class Gemma3nMLP(nn.Module):
230 def __init__(
231 self,
232 hidden_size: int,
233 intermediate_size: int,
234 hidden_activation: str,
235 activation_sparsity: float = 0.0,
236 quant_config: QuantizationConfig | None = None,
237 prefix: str = "",
238 ) -> None:
239 super().__init__()
240 self.gate_up_proj = MergedColumnParallelLinear(
241 hidden_size,
242 [intermediate_size] * 2,
243 bias=False,
244 quant_config=quant_config,
245 prefix=f"{prefix}.gate_up_proj",
246 )
247 self.down_proj = RowParallelLinear(
248 intermediate_size,
249 hidden_size,
250 bias=False,
251 quant_config=quant_config,
252 prefix=f"{prefix}.down_proj",
253 )
254 if hidden_activation != "gelu_pytorch_tanh":
255 raise ValueError(
256 "Gemma3 uses `gelu_pytorch_tanh` as the hidden activation "
257 "function. Please set `hidden_act` and `hidden_activation` to "
258 "`gelu_pytorch_tanh`."
259 )
260
261 self.act_fn = (
262 GeluAndMulSparse(
263 activation_sparsity=activation_sparsity, approximate="tanh"
264 )
265 if activation_sparsity > 0.0
266 else GeluAndMul(approximate="tanh")
267 )
268
269 def forward(self, x: torch.Tensor) -> torch.Tensor:
270 gate_up, _ = self.gate_up_proj(x)
271 x = self.act_fn(gate_up)
272 x, _ = self.down_proj(x)
273 return x
274
275
276 class Gemma3nAttention(nn.Module):
277 def __init__(
278 self,
279 config: Gemma3nTextConfig,
280 hidden_size: int,
281 num_heads: int,
282 num_kv_heads: int,
283 head_dim: int,
284 max_position_embeddings: int,
285 cache_config: CacheConfig | None = None,
286 quant_config: QuantizationConfig | None = None,
287 prefix: str = "",
288 ) -> None:
289 super().__init__()
290 self.config = config
291 self.hidden_size = hidden_size
292 tp_size = get_tensor_model_parallel_world_size()
293 self.total_num_heads = num_heads
294 assert self.total_num_heads % tp_size == 0
295 self.num_heads = self.total_num_heads // tp_size
296 self.total_num_kv_heads = num_kv_heads
297 if self.total_num_kv_heads >= tp_size:
298 # Number of KV heads is greater than TP size, so we partition
299 # the KV heads across multiple tensor parallel GPUs.
300 assert self.total_num_kv_heads % tp_size == 0
301 else:
302 # Number of KV heads is less than TP size, so we replicate
303 # the KV heads across multiple tensor parallel GPUs.
304 assert tp_size % self.total_num_kv_heads == 0
305 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
306 self.head_dim = head_dim
307 self.q_size = self.num_heads * self.head_dim
308 self.kv_size = self.num_kv_heads * self.head_dim
309
310 layer_idx = extract_layer_index(prefix)
311 first_kv_shared_layer_idx = (
312 config.num_hidden_layers - config.num_kv_shared_layers
313 )
314 self.is_kv_shared_layer = layer_idx >= first_kv_shared_layer_idx
315
316 if self.is_kv_shared_layer:
317 # K/V come from the target layer's cache, so like HF this layer
318 # only has q_proj (and no k_norm/v_norm).
319 self.q_proj = ColumnParallelLinear(
320 hidden_size,
321 self.total_num_heads * self.head_dim,
322 bias=config.attention_bias,
323 quant_config=quant_config,
324 prefix=f"{prefix}.q_proj",
325 )
326 else:
327 self.qkv_proj = QKVParallelLinear(
328 hidden_size,
329 self.head_dim,
330 self.total_num_heads,
331 self.total_num_kv_heads,
332 bias=config.attention_bias,
333 quant_config=quant_config,
334 prefix=f"{prefix}.qkv_proj",
335 )
336 self.k_norm = RMSNorm(hidden_size=self.head_dim, eps=config.rms_norm_eps)
337 self.v_norm = RMSNorm(
338 hidden_size=self.head_dim, eps=config.rms_norm_eps, has_weight=False
339 )
340 self.o_proj = RowParallelLinear(
341 self.total_num_heads * self.head_dim,
342 hidden_size,
343 bias=config.attention_bias,
344 quant_config=quant_config,
345 prefix=f"{prefix}.o_proj",
346 )
347 self.q_norm = RMSNorm(hidden_size=self.head_dim, eps=config.rms_norm_eps)
348
349 layer_type = config.layer_types[layer_idx]
350 is_sliding = layer_type == "sliding_attention"
351 self.sliding_window = config.sliding_window if is_sliding else None
352
353 # Initialize the rotary embedding.
354 if layer_type in config.rope_parameters:
355 # Transformers v5 rope config.
356 rope_parameters = config.rope_parameters[layer_type]
357 else:
358 # Transformers v4 rope config.
359 # Global attention. Use the values in config.json.
360 rope_parameters = config.rope_parameters.copy()
361 # Local attention. Override the values in config.json.
362 if is_sliding:
363 rope_parameters["rope_theta"] = config.rope_local_base_freq
364
365 kv_sharing_target_layer_name = None
366 if self.is_kv_shared_layer:
367 # Last full attention layer is 1 before sharing
368 # Last sliding attention layer is 2 before sharing
369 offset = 2 if self.sliding_window is not None else 1
370 kv_shared_layer_index = first_kv_shared_layer_idx - offset
371 if kv_shared_layer_index >= 0:
372 # Different model wrappers expose layer parameters under
373 # different parent attributes.
374 # For example:
375 # - Gemma3nForCausalLM → parameters live under "model.layers"
376 # - Gemma3nForConditionalGeneration →
377 # under "language_model.model.layers"
378 # This logic extracts the portion of the parameter name
379 # *before* ".layers."
380 # so downstream code can consistently reference the correct
381 # model root regardless of which wrapper class was used.
382 if ".layers." in prefix:
383 param_name_before_layers = prefix.split(".layers.")[0]
384 else:
385 raise ValueError(
386 "Unexpected prefix format for Gemma3nAttention: "
387 f"'{prefix}'. The prefix is expected to contain "
388 "'.layers.' to correctly determine the KV sharing "
389 "target layer."
390 )
391 # Only the greater layer is required to specify sharing.
392 kv_sharing_target_layer_name = f"{param_name_before_layers}.layers.{kv_shared_layer_index}.self_attn.attn" # noqa: E501
393
394 self.rotary_emb = get_rope(
395 self.head_dim,
396 max_position=max_position_embeddings,
397 rope_parameters=rope_parameters,
398 is_neox_style=True,
399 )
400
401 self.attn = Attention(
402 num_heads=self.num_heads,
403 head_size=self.head_dim,
404 scale=1.0,
405 num_kv_heads=self.num_kv_heads,
406 cache_config=cache_config,
407 quant_config=quant_config,
408 per_layer_sliding_window=self.sliding_window,
409 kv_sharing_target_layer_name=kv_sharing_target_layer_name,
410 prefix=f"{prefix}.attn",
411 )
412
413 def forward(
414 self,
415 positions: torch.Tensor,
416 hidden_states: torch.Tensor,
417 **kwargs,
418 ) -> torch.Tensor:
419 if self.is_kv_shared_layer:
420 # Shared KV: only Q is projected; K/V come from the target layer.
421 q, _ = self.q_proj(hidden_states)
422 q = q.unflatten(-1, (self.num_heads, self.head_dim))
423 q = self.q_norm(q)
424 q = q.flatten(-2, -1)
425 q, _ = self.rotary_emb(positions, q, None)
426 attn_output = self.attn(q, None, None)
427 else:
428 qkv, _ = self.qkv_proj(hidden_states)
429 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
430
431 q = q.unflatten(-1, (self.num_heads, self.head_dim))
432 q = self.q_norm(q)
433 q = q.flatten(-2, -1)
434 k = k.unflatten(-1, (self.num_kv_heads, self.head_dim))
435 k = self.k_norm(k)
436 k = k.flatten(-2, -1)
437 v = v.unflatten(-1, (self.num_kv_heads, self.head_dim))
438 v = self.v_norm(v)
439 v = v.flatten(-2, -1)
440
441 q, k = self.rotary_emb(positions, q, k)
442 attn_output = self.attn(q, k, v)
443
444 output, _ = self.o_proj(attn_output)
445 return output
446
447
448 class Gemma3nDecoderLayer(nn.Module):
449 def __init__(
450 self,
451 config: Gemma3nTextConfig,
452 cache_config: CacheConfig | None = None,
453 quant_config: QuantizationConfig | None = None,
454 prefix: str = "",
455 ) -> None:
456 super().__init__()
457 assert isinstance(config, Gemma3nTextConfig)
458 self.altup_active_idx = config.altup_active_idx
459 assert config.altup_correct_scale
460
461 self.altup = Gemma3nAltUp(
462 hidden_size=config.hidden_size,
463 rms_norm_eps=config.rms_norm_eps,
464 altup_num_inputs=config.altup_num_inputs,
465 altup_coef_clip=config.altup_coef_clip,
466 altup_active_idx=config.altup_active_idx,
467 quant_config=quant_config,
468 prefix=f"{prefix}.altup",
469 )
470 self.self_attn = Gemma3nAttention(
471 config=config,
472 hidden_size=config.hidden_size,
473 num_heads=config.num_attention_heads,
474 num_kv_heads=config.num_key_value_heads,
475 head_dim=config.head_dim,
476 max_position_embeddings=config.max_position_embeddings,
477 cache_config=cache_config,
478 quant_config=quant_config,
479 prefix=f"{prefix}.self_attn",
480 )
481 self.mlp = Gemma3nMLP(
482 hidden_size=config.hidden_size,
483 # NOTE: Matformer https://github.com/huggingface/transformers/blob/a52478253bbe522a420e88ea3940d4d98a935300/src/transformers/models/gemma3n/modular_gemma3n.py#L258 # noqa: E501
484 intermediate_size=config.intermediate_size[extract_layer_index(prefix)],
485 hidden_activation=config.hidden_activation,
486 quant_config=quant_config,
487 activation_sparsity=config.activation_sparsity_pattern[
488 extract_layer_index(prefix)
489 ],
490 prefix=f"{prefix}.mlp",
491 )
492 self.laurel = Gemma3nLaurelBlock(
493 hidden_size=config.hidden_size,
494 laurel_rank=config.laurel_rank,
495 rms_norm_eps=config.rms_norm_eps,
496 quant_config=quant_config,
497 prefix=f"{prefix}.laurel",
498 )
499
500 # NOTE(rob): should be ColumnParallelLinear and RowParallelLinear
501 # But, we need to add per_layer_input_gate(x) to per_layer_input.
502 # per_layer_input cannot be sharded, so we replicate for now.
503 self.per_layer_input_gate = ReplicatedLinear(
504 config.hidden_size,
505 config.hidden_size_per_layer_input,
506 bias=False,
507 quant_config=quant_config,
508 prefix=f"{prefix}.per_layer_input_gate",
509 return_bias=False,
510 )
511 self.per_layer_projection = ReplicatedLinear(
512 config.hidden_size_per_layer_input,
513 config.hidden_size,
514 bias=False,
515 quant_config=quant_config,
516 prefix=f"{prefix}.per_layer_projection",
517 return_bias=False,
518 )
519
520 # LayerNorms.
521 self.input_layernorm = RMSNorm(
522 config.hidden_size,
523 eps=config.rms_norm_eps,
524 )
525 self.post_attention_layernorm = RMSNorm(
526 config.hidden_size,
527 eps=config.rms_norm_eps,
528 )
529 self.pre_feedforward_layernorm = RMSNorm(
530 config.hidden_size,
531 eps=config.rms_norm_eps,
532 )
533 self.post_feedforward_layernorm = RMSNorm(
534 config.hidden_size,
535 eps=config.rms_norm_eps,
536 )
537 self.post_per_layer_input_norm = RMSNorm(
538 config.hidden_size,
539 eps=config.rms_norm_eps,
540 )
541
542 self.act_fn = _ACTIVATION_REGISTRY[config.hidden_activation]
543
544 def forward(
545 self,
546 positions: torch.Tensor,
547 hidden_states: torch.Tensor,
548 per_layer_input: torch.Tensor,
549 **kwargs,
550 ) -> tuple[torch.Tensor, torch.Tensor]:
551 # ActUp (predict).
552 predictions = self.altup.predict(hidden_states)
553 active_prediction = predictions[self.altup_active_idx]
554 active_prediction_normed = self.input_layernorm(active_prediction)
555 laurel_output = self.laurel(active_prediction_normed)
556
557 # Attention.
558 attn = self.self_attn(
559 positions=positions,
560 hidden_states=active_prediction_normed,
561 **kwargs,
562 )
563 attn = self.post_attention_layernorm(attn)
564 attn_gated = attn + active_prediction
565 attn_laurel = (attn_gated + laurel_output) / torch.sqrt(torch.tensor(2.0))
566
567 # MLP.
568 attn_norm = self.pre_feedforward_layernorm(attn_laurel)
569 attn_ffw = self.mlp(attn_norm)
570 attn_ffw_norm = self.post_feedforward_layernorm(attn_ffw)
571 attn_ffw_laurel_gated = attn_laurel + attn_ffw_norm
572
573 # ActUp (connect).
574 corrected_predictions = self.altup.correct(predictions, attn_ffw_laurel_gated)
575 first_prediction = corrected_predictions[self.altup_active_idx]
576 first_prediction = self.altup.scale_corrected_output(first_prediction)
577
578 # per_layer_input_gate adapted from jax.numpy.einsum("btd,dp->btp", ...)
579 first_prediction = self.per_layer_input_gate(first_prediction)
580 first_prediction = self.act_fn(first_prediction)
581 first_prediction = torch.mul(first_prediction, per_layer_input)
582
583 # per_layer_projection adapted from jax.numpy.einsum("btp,pd->btd", ...)
584 first_prediction = self.per_layer_projection(first_prediction)
585 first_prediction = self.post_per_layer_input_norm(first_prediction)
586 corrected_predictions[1:] += first_prediction
587
588 return corrected_predictions
589
590
591 # This enables torch.compile if --kv-sharing-fast-prefill passed
592 @support_torch_compile(
593 enable_if=lambda vllm_config: vllm_config.cache_config.kv_sharing_fast_prefill
594 )
595 class Gemma3nSelfDecoder(nn.Module):
596 """Includes altup embedding and self decoder layers."""
597
598 def __init__(
599 self,
600 *,
601 vllm_config: VllmConfig,
602 prefix: str = "",
603 decoder_layers: list[Gemma3nDecoderLayer],
604 layer_idx_start: int,
605 ):
606 super().__init__()
607 self.decoder_layers = decoder_layers
608 self.layer_idx_start = layer_idx_start
609
610 config = vllm_config.model_config.hf_config
611 self.config = config
612 quant_config = vllm_config.quant_config
613
614 self.embed_tokens = VocabParallelEmbedding(
615 config.vocab_size,
616 config.hidden_size,
617 quant_config=quant_config,
618 prefix=f"{prefix}.embed_tokens",
619 )
620 self.register_buffer(
621 "embed_scale",
622 torch.tensor(
623 config.hidden_size**0.5,
624 dtype=self.embed_tokens.weight.dtype,
625 ),
626 persistent=False,
627 )
628 # Additional per-layer embeddings (PLE)
629 self.embed_tokens_per_layer = VocabParallelEmbedding(
630 config.vocab_size_per_layer_input,
631 config.num_hidden_layers * config.hidden_size_per_layer_input,
632 quant_config=quant_config,
633 prefix=f"{prefix}.per_layer_embed_tokens",
634 )
635 self.register_buffer(
636 "embed_scale_per_layer",
637 torch.tensor(
638 config.hidden_size_per_layer_input**0.5,
639 dtype=self.embed_tokens.weight.dtype,
640 ),
641 persistent=False,
642 )
643 self.per_layer_model_projection = ColumnParallelLinear(
644 config.hidden_size,
645 config.num_hidden_layers * config.hidden_size_per_layer_input,
646 bias=False,
647 gather_output=True,
648 return_bias=False,
649 quant_config=quant_config,
650 prefix=f"{prefix}.per_layer_model_projection",
651 )
652 self.per_layer_projection_norm = RMSNorm(
653 hidden_size=config.hidden_size_per_layer_input,
654 eps=config.rms_norm_eps,
655 )
656 self.register_buffer(
657 "per_layer_input_scale",
658 torch.rsqrt(torch.tensor(2.0)).to(self.embed_tokens.weight.dtype),
659 persistent=False,
660 )
661 self.register_buffer(
662 "per_layer_projection_scale",
663 torch.tensor(
664 config.hidden_size**0.5,
665 dtype=self.embed_tokens.weight.dtype,
666 ),
667 persistent=False,
668 )
669 self.altup_projections = nn.ModuleList(
670 [
671 ColumnParallelLinear(
672 config.hidden_size,
673 config.hidden_size,
674 bias=False,
675 gather_output=True,
676 return_bias=False,
677 quant_config=quant_config,
678 prefix=f"{prefix}.altup_projections.{idx - 1}",
679 )
680 for idx in range(1, self.config.altup_num_inputs)
681 ]
682 )
683
684 def get_per_layer_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
685 # Deal with the fact that vocab_size_per_layer_input < vocab_size
686 # which causes us to have some out of vocab tokens by setting
687 # those token ids to 0. This matches the HF implementation.
688 per_layer_inputs_mask = torch.logical_and(
689 input_ids >= 0, input_ids < self.config.vocab_size_per_layer_input
690 )
691 per_layer_inputs_tokens = torch.where(
692 per_layer_inputs_mask, input_ids, torch.zeros_like(input_ids)
693 )
694 return (
695 self.embed_tokens_per_layer(per_layer_inputs_tokens)
696 * self.embed_scale_per_layer
697 )
698
699 def get_per_layer_inputs(
700 self,
701 hidden_states_0: torch.Tensor,
702 per_layer_inputs: torch.Tensor | None,
703 ) -> torch.Tensor:
704 per_layer_projection = self.per_layer_model_projection(hidden_states_0)
705 per_layer_projection = per_layer_projection.reshape(
706 *hidden_states_0.shape[:-1],
707 self.config.num_hidden_layers,
708 self.config.hidden_size_per_layer_input,
709 )
710 per_layer_projection = self.per_layer_projection_norm(per_layer_projection)
711 if per_layer_inputs is not None:
712 # Profiling run does not compute per_layer_inputs
713 per_layer_inputs = per_layer_projection + per_layer_inputs
714 per_layer_inputs *= self.per_layer_input_scale
715 else:
716 per_layer_inputs = per_layer_projection
717 return per_layer_inputs
718
719 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
720 return self.embed_tokens(input_ids) * self.embed_scale
721
722 def altup_embed(self, hidden_states_0: torch.Tensor) -> torch.Tensor:
723 # Altup embed.
724 hidden_states = [hidden_states_0] * self.config.altup_num_inputs
725 target_magnitude = torch.mean(hidden_states_0**2, dim=-1, keepdim=True) ** 0.5
726 for i in range(1, self.config.altup_num_inputs):
727 hidden_states[i] = self.altup_projections[i - 1](hidden_states[i])
728 new_magnitude = (
729 torch.mean(hidden_states[i] ** 2, dim=-1, keepdim=True) ** 0.5
730 )
731 hidden_states[i] *= target_magnitude / torch.maximum(new_magnitude, EPS)
732 hidden_states = torch.stack(hidden_states, dim=-1)
733 return hidden_states
734
735 def forward(
736 self,
737 input_ids: torch.Tensor | None,
738 positions: torch.Tensor,
739 inputs_embeds: torch.Tensor | None = None,
740 per_layer_inputs: torch.Tensor | None = None,
741 **kwargs,
742 ) -> tuple[torch.Tensor, torch.Tensor]:
743 if inputs_embeds is not None:
744 hidden_states_0 = inputs_embeds
745 else:
746 hidden_states_0 = self.embed_input_ids(input_ids)
747
748 adjusted_per_layer_inputs = self.get_per_layer_inputs(
749 hidden_states_0, per_layer_inputs
750 )
751 hidden_states = self.altup_embed(hidden_states_0)
752
753 # [altnum_inputs, num_tokens, hidden_size]
754 hidden_states = hidden_states.permute(2, 0, 1)
755
756 for idx, layer in enumerate(self.decoder_layers):
757 layer_idx = idx + self.layer_idx_start
758 # [altup_num_inputs, num_tokens, hidden_size]
759 hidden_states = layer(
760 positions=positions,
761 hidden_states=hidden_states,
762 per_layer_input=adjusted_per_layer_inputs[:, layer_idx, :],
763 **kwargs,
764 )
765
766 # [num_tokens, hidden_size, altnum_inputs]
767 hidden_states = hidden_states.permute(1, 2, 0)
768
769 return hidden_states, adjusted_per_layer_inputs
770
771
772 # This enables torch.compile if --kv-sharing-fast-prefill passed
773 @support_torch_compile(
774 enable_if=lambda vllm_config: vllm_config.cache_config.kv_sharing_fast_prefill
775 )
776 class Gemma3nCrossDecoder(nn.Module):
777 """Cross-decoder layers."""
778
779 def __init__(
780 self,
781 *,
782 vllm_config: VllmConfig,
783 prefix: str = "",
784 decoder_layers: list[Gemma3nDecoderLayer],
785 layer_idx_start: int,
786 ):
787 super().__init__()
788 self.decoder_layers = decoder_layers
789 self.layer_idx_start = layer_idx_start
790
791 def forward(
792 self,
793 positions: torch.Tensor,
794 hidden_states: torch.Tensor,
795 per_layer_inputs: torch.Tensor,
796 **kwargs,
797 ) -> torch.Tensor:
798 # [altnum_inputs, num_tokens, hidden_size]
799 hidden_states = hidden_states.permute(2, 0, 1)
800 for idx, layer in enumerate(self.decoder_layers):
801 layer_idx = idx + self.layer_idx_start
802 # [altup_num_inputs, num_tokens, hidden_size]
803 hidden_states = layer(
804 positions=positions,
805 hidden_states=hidden_states,
806 per_layer_input=per_layer_inputs[:, layer_idx, :],
807 **kwargs,
808 )
809 # [num_tokens, hidden_size, altnum_inputs]
810 hidden_states = hidden_states.permute(1, 2, 0)
811 return hidden_states
812
813
814 def _kv_sharing_weights_mapper(config: Gemma3nTextConfig) -> WeightsMapper:
815 """KV-shared layers only have q_proj, so qkv_proj packing applies to the
816 other layers. Original checkpoints still ship K/V tensors for the shared
817 layers (fine-tuned ones omit them); those are dropped."""
818 first_kv_shared_layer_idx = config.num_hidden_layers - config.num_kv_shared_layers
819 return WeightsMapper(
820 orig_to_new_substr={
821 f"layers.{i}.self_attn.{name}.": None
822 for i in range(first_kv_shared_layer_idx, config.num_hidden_layers)
823 for name in ("k_proj", "v_proj", "k_norm")
824 },
825 orig_to_new_stacked={
826 f"layers.{i}.self_attn.{shard}_proj.": (
827 f"layers.{i}.self_attn.qkv_proj.",
828 shard,
829 )
830 for i in range(first_kv_shared_layer_idx)
831 for shard in ("q", "k", "v")
832 },
833 )
834
835
836 # This disables torch.compile if --kv-sharing-fast-prefill passed
837 @support_torch_compile(
838 enable_if=lambda vllm_config: not vllm_config.cache_config.kv_sharing_fast_prefill
839 )
840 class Gemma3nTextModel(nn.Module, SupportsQuant):
841 # Decoder layers, altup_unembed_projections and norm live on the text
842 # model; every other submodule lives under self_decoder.
843 hf_to_vllm_mapper = WeightsMapper(
844 orig_to_new_prefix={
845 "embed_tokens.": "self_decoder.embed_tokens.",
846 "embed_tokens_per_layer.": "self_decoder.embed_tokens_per_layer.",
847 "per_layer_model_projection.": "self_decoder.per_layer_model_projection.",
848 "per_layer_projection_norm.": "self_decoder.per_layer_projection_norm.",
849 "altup_projections.": "self_decoder.altup_projections.",
850 },
851 orig_to_new_stacked={
852 ".gate_proj": (".gate_up_proj", 0),
853 ".up_proj": (".gate_up_proj", 1),
854 },
855 )
856
857 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
858 super().__init__()
859 config = vllm_config.model_config.hf_config
860 cache_config = vllm_config.cache_config
861 quant_config = vllm_config.quant_config
862 self.config = config
863 self.quant_config = quant_config
864 self.hf_to_vllm_mapper = self.hf_to_vllm_mapper | _kv_sharing_weights_mapper(
865 config
866 )
867
868 self.altup_unembed_projections = nn.ModuleList(
869 [
870 ColumnParallelLinear(
871 config.hidden_size,
872 config.hidden_size,
873 bias=False,
874 gather_output=True,
875 return_bias=False,
876 quant_config=quant_config,
877 prefix=f"{prefix}.altup_unembed_projections.{idx - 1}",
878 )
879 for idx in range(1, self.config.altup_num_inputs)
880 ]
881 )
882
883 # Allocate config.num_kv_shared_layers layers for self-decoder
884 self.start_layer, self.end_layer, self.layers = make_layers(
885 config.num_hidden_layers,
886 lambda prefix: Gemma3nDecoderLayer(
887 config, cache_config, quant_config, prefix=prefix
888 ),
889 prefix=f"{prefix}.layers",
890 )
891
892 first_kv_shared_layer_idx = (
893 config.num_hidden_layers - config.num_kv_shared_layers
894 )
895
896 # NOTE(sarckk): importing this top level seems to cause issues
897 # during running of tests.
898 from vllm.compilation.backends import set_model_tag
899
900 # Layer idx 0-19 are self-decoder layers in You Only Cache Once (YOCO)
901 with set_model_tag("self_decoder"):
902 self.self_decoder = Gemma3nSelfDecoder(
903 vllm_config=vllm_config,
904 prefix=f"{prefix}.self_decoder",
905 decoder_layers=self.layers[:first_kv_shared_layer_idx],
906 layer_idx_start=0,
907 )
908 # Layer idx 20-30 are cross-decoder layers in YOCO
909 with set_model_tag("cross_decoder"):
910 self.cross_decoder = Gemma3nCrossDecoder(
911 vllm_config=vllm_config,
912 prefix=f"{prefix}.cross_decoder",
913 decoder_layers=self.layers[first_kv_shared_layer_idx:],
914 layer_idx_start=first_kv_shared_layer_idx,
915 )
916
917 self.norm = RMSNorm(
918 config.hidden_size,
919 eps=config.rms_norm_eps,
920 )
921
922 self.fast_prefill_enabled = cache_config.kv_sharing_fast_prefill
923
924 if self.fast_prefill_enabled:
925 # Allocate static buffers for CUDAGraph
926 # TODO(sarckk): Extract this functionality to interface
927 max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens
928 device = next(self.parameters()).device
929 self.positions = torch.zeros(
930 max_num_tokens, dtype=torch.int64, device=device
931 )
932 self.hidden_states = torch.zeros(
933 (max_num_tokens, config.hidden_size, self.config.altup_num_inputs),
934 dtype=self.embed_tokens.weight.dtype,
935 device=device,
936 )
937 self.per_layer_inputs = torch.zeros(
938 (
939 max_num_tokens,
940 self.config.num_hidden_layers,
941 self.config.hidden_size_per_layer_input,
942 ),
943 dtype=self.embed_tokens.weight.dtype,
944 device=device,
945 )
946
947 @property
948 def embed_tokens(self):
949 return self.self_decoder.embed_tokens
950
951 def get_per_layer_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
952 return self.self_decoder.get_per_layer_input_embeddings(input_ids)
953
954 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
955 return self.self_decoder.embed_input_ids(input_ids)
956
957 def fast_prefill_forward(
958 self,
959 input_ids: torch.Tensor | None,
960 positions: torch.Tensor,
961 inputs_embeds: torch.Tensor | None = None,
962 per_layer_inputs: torch.Tensor | None = None,
963 **kwargs,
964 ) -> torch.Tensor:
965 logits_indices_padded, num_logits_indices = None, None
966 attn_metadata = get_forward_context().attn_metadata
967
968 # attn_metadata is None during dummy runs
969 if self.fast_prefill_enabled and attn_metadata is not None:
970 assert isinstance(attn_metadata, dict)
971 # Last layer is a KV sharing layer
972 layer_attn_metadata = attn_metadata[
973 self.layers[-1].self_attn.attn.layer_name
974 ]
975 if isinstance(layer_attn_metadata, KVSharingFastPrefillMetadata):
976 logits_indices_padded = layer_attn_metadata.logits_indices_padded
977 num_logits_indices = layer_attn_metadata.num_logits_indices
978
979 # Copy inputs for cudagraph
980 batch_size = positions.size(0)
981 self.positions[:batch_size].copy_(positions)
982 self_decoder_hidden_states, per_layer_inputs_adjusted = self.self_decoder(
983 input_ids=input_ids,
984 positions=self.positions[:batch_size],
985 inputs_embeds=inputs_embeds,
986 per_layer_inputs=per_layer_inputs,
987 **kwargs,
988 )
989
990 if logits_indices_padded is None:
991 logits_indices_padded = torch.arange(
992 positions.size(0),
993 dtype=positions.dtype,
994 device=positions.device,
995 )
996
997 # NOTE(sarckk): There is currently a bug caused by
998 # vLLM converting output of last piecewise CUDA graph
999 # to weakref, causing memory to be prematurely freed
1000 # when there are multiple compilation units
1001 # Keep .clone() until fix in
1002 # https://github.com/vllm-project/vllm/pull/22282
1003 hidden_states = self_decoder_hidden_states.clone()
1004
1005 # Copy inputs for cudagraph
1006 num_padded_logits_indices = logits_indices_padded.size(0)
1007 self.positions[:num_padded_logits_indices].copy_(
1008 positions[logits_indices_padded]
1009 )
1010 self.hidden_states[:num_padded_logits_indices].copy_(
1011 self_decoder_hidden_states[logits_indices_padded]
1012 )
1013 self.per_layer_inputs[:num_padded_logits_indices].copy_(
1014 per_layer_inputs_adjusted[logits_indices_padded]
1015 )
1016 cross_decoder_hidden_states = self.cross_decoder(
1017 positions=self.positions[:num_padded_logits_indices],
1018 hidden_states=self.hidden_states[:num_padded_logits_indices],
1019 per_layer_inputs=self.per_layer_inputs[:num_padded_logits_indices],
1020 **kwargs,
1021 )
1022
1023 if num_logits_indices is not None:
1024 assert num_logits_indices > 0
1025 # Merge cross-decoder and self-decoder hidden states
1026 hidden_states[logits_indices_padded[:num_logits_indices]] = (
1027 cross_decoder_hidden_states[:num_logits_indices]
1028 )
1029 else:
1030 hidden_states = cross_decoder_hidden_states
1031
1032 return hidden_states
1033
1034 def normal_forward(
1035 self,
1036 input_ids: torch.Tensor | None,
1037 positions: torch.Tensor,
1038 inputs_embeds: torch.Tensor | None = None,
1039 per_layer_inputs: torch.Tensor | None = None,
1040 **kwargs,
1041 ) -> torch.Tensor:
1042 hidden_states, per_layer_inputs = self.self_decoder(
1043 input_ids=input_ids,
1044 positions=positions,
1045 inputs_embeds=inputs_embeds,
1046 per_layer_inputs=per_layer_inputs,
1047 **kwargs,
1048 )
1049 hidden_states = self.cross_decoder(
1050 positions=positions,
1051 hidden_states=hidden_states,
1052 per_layer_inputs=per_layer_inputs,
1053 **kwargs,
1054 )
1055 return hidden_states
1056
1057 def altup_unembed(
1058 self,
1059 hidden_states: torch.Tensor,
1060 ) -> torch.Tensor:
1061 # Altup unembed.
1062 target_magnitude = (
1063 torch.mean(hidden_states[..., 0] ** 2, dim=-1, keepdim=True) ** 0.5
1064 )
1065 for i in range(1, self.config.altup_num_inputs):
1066 hidden_states[..., i] = self.altup_unembed_projections[i - 1](
1067 hidden_states[..., i]
1068 )
1069 new_magnitude = (
1070 torch.mean(hidden_states[..., i] ** 2, dim=-1, keepdim=True) ** 0.5
1071 )
1072 hidden_states[..., i] *= target_magnitude / torch.maximum(
1073 new_magnitude, EPS
1074 )
1075 # [num_tokens,hidden_size, altup_num_inputs] -> [num_tokens,hidden_size]
1076 hidden_states = torch.mean(hidden_states, dim=-1)
1077 return hidden_states
1078
1079 def forward(
1080 self,
1081 input_ids: torch.Tensor | None,
1082 positions: torch.Tensor,
1083 per_layer_inputs: torch.Tensor | None = None,
1084 intermediate_tensors: IntermediateTensors | None = None,
1085 inputs_embeds: torch.Tensor | None = None,
1086 **kwargs,
1087 ) -> torch.Tensor | IntermediateTensors:
1088 if self.fast_prefill_enabled:
1089 hidden_states = self.fast_prefill_forward(
1090 input_ids,
1091 positions,
1092 inputs_embeds,
1093 per_layer_inputs,
1094 **kwargs,
1095 )
1096 else:
1097 hidden_states = self.normal_forward(
1098 input_ids,
1099 positions,
1100 inputs_embeds,
1101 per_layer_inputs,
1102 **kwargs,
1103 )
1104 hidden_states = self.altup_unembed(hidden_states)
1105 return self.norm(hidden_states)
1106
1107 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1108 loader = AutoWeightsLoader(self)
1109 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1110
1111
1112 class Gemma3nForCausalLM(nn.Module):
1113 hf_to_vllm_mapper = WeightsMapper(
1114 orig_to_new_substr={
1115 "embed_audio.": None,
1116 "embed_vision.": None,
1117 "audio_tower.": None,
1118 "vision_tower.": None,
1119 }
1120 )
1121 packed_modules_mapping = {
1122 "qkv_proj": [
1123 "q_proj",
1124 "k_proj",
1125 "v_proj",
1126 ],
1127 "gate_up_proj": [
1128 "gate_proj",
1129 "up_proj",
1130 ],
1131 }
1132
1133 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1134 config = vllm_config.model_config.hf_config
1135
1136 super().__init__()
1137 self.config = config
1138 self.cache_config = vllm_config.cache_config
1139 self.model = Gemma3nTextModel(
1140 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
1141 )
1142 self.logits_processor = LogitsProcessor(
1143 config.vocab_size, soft_cap=config.final_logit_softcapping
1144 )
1145
1146 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1147 return self.model.embed_input_ids(input_ids)
1148
1149 def forward(
1150 self,
1151 input_ids: torch.Tensor | None,
1152 positions: torch.Tensor,
1153 *,
1154 per_layer_inputs: torch.Tensor | None = None,
1155 intermediate_tensors: IntermediateTensors | None = None,
1156 inputs_embeds: torch.Tensor | None = None,
1157 **kwargs,
1158 ) -> torch.Tensor | IntermediateTensors:
1159 hidden_states = self.model(
1160 input_ids,
1161 positions,
1162 per_layer_inputs=per_layer_inputs,
1163 intermediate_tensors=intermediate_tensors,
1164 inputs_embeds=inputs_embeds,
1165 **kwargs,
1166 )
1167 return hidden_states
1168
1169 def compute_logits(
1170 self,
1171 hidden_states: torch.Tensor,
1172 ) -> torch.Tensor | None:
1173 logits = self.logits_processor(self.model.embed_tokens, hidden_states)
1174 return logits
1175
1176 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1177 loader = AutoWeightsLoader(self)
1178 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)