main
py 584 lines 21.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Inference-only LoopCoder model compatible with HuggingFace weights."""
16
17 from __future__ import annotations
18
19 from collections.abc import Callable, Iterable
20 from dataclasses import replace
21 from typing import Any
22
23 import torch
24 from torch import nn
25 from transformers import PreTrainedConfig
26
27 from vllm.compilation.decorators import support_torch_compile
28 from vllm.config import CacheConfig, VllmConfig
29 from vllm.distributed import get_tensor_model_parallel_world_size
30 from vllm.model_executor.layers.attention import Attention
31 from vllm.model_executor.layers.layernorm import RMSNorm
32 from vllm.model_executor.layers.linear import (
33 ColumnParallelLinear,
34 QKVParallelLinear,
35 RowParallelLinear,
36 )
37 from vllm.model_executor.layers.logits_processor import LogitsProcessor
38 from vllm.model_executor.layers.quantization import QuantizationConfig
39 from vllm.model_executor.layers.rotary_embedding import get_rope
40 from vllm.model_executor.layers.vocab_parallel_embedding import (
41 ParallelLMHead,
42 VocabParallelEmbedding,
43 )
44 from vllm.model_executor.model_loader.weight_utils import (
45 default_weight_loader,
46 maybe_remap_kv_scale_name,
47 )
48 from vllm.model_executor.models.llama import LlamaMLP
49 from vllm.sequence import IntermediateTensors
50 from vllm.v1.attention.backend import AttentionType
51
52 from .utils import (
53 AutoWeightsLoader,
54 extract_layer_index,
55 make_layers,
56 maybe_prefix,
57 )
58
59
60 class LoopCoderAttention(nn.Module):
61 def __init__(
62 self,
63 config: PreTrainedConfig,
64 hidden_size: int,
65 num_heads: int,
66 num_kv_heads: int,
67 max_position: int = 4096 * 32,
68 cache_config: CacheConfig | None = None,
69 quant_config: QuantizationConfig | None = None,
70 prefix: str = "",
71 attn_type: str = AttentionType.DECODER,
72 dual_chunk_attention_config: dict[str, Any] | None = None,
73 layer_idx: int = 0,
74 ) -> None:
75 super().__init__()
76 self.layer_idx = layer_idx
77 self.hidden_size = hidden_size
78 tp_size = get_tensor_model_parallel_world_size()
79 self.total_num_heads = num_heads
80 assert self.total_num_heads % tp_size == 0
81 self.num_heads = self.total_num_heads // tp_size
82 self.total_num_kv_heads = num_kv_heads
83 if self.total_num_kv_heads >= tp_size:
84 # Number of KV heads is greater than TP size, so we partition
85 # the KV heads across multiple tensor parallel GPUs.
86 assert self.total_num_kv_heads % tp_size == 0
87 else:
88 # Number of KV heads is less than TP size, so we replicate
89 # the KV heads across multiple tensor parallel GPUs.
90 assert tp_size % self.total_num_kv_heads == 0
91 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
92 self.head_dim = hidden_size // self.total_num_heads
93 self.q_size = self.num_heads * self.head_dim
94 self.kv_size = self.num_kv_heads * self.head_dim
95 self.scaling = self.head_dim**-0.5
96 self.dual_chunk_attention_config = dual_chunk_attention_config
97
98 # Get loop_num from config, default to 2 if not specified
99 self.loop_num = getattr(config, "loop_num", 2)
100
101 self.loop_window_size = getattr(config, "loop_window_size", 64)
102
103 # Use total number of hidden layers instead of hardcoded 24
104 total_layers = config.num_hidden_layers
105
106 self.qkv_proj = QKVParallelLinear(
107 hidden_size,
108 self.head_dim,
109 self.total_num_heads,
110 self.total_num_kv_heads,
111 bias=False,
112 quant_config=quant_config,
113 prefix=f"{prefix}.qkv_proj",
114 )
115 self.o_proj = RowParallelLinear(
116 self.total_num_heads * self.head_dim,
117 hidden_size,
118 bias=False,
119 quant_config=quant_config,
120 prefix=f"{prefix}.o_proj",
121 )
122
123 self.rotary_emb = get_rope(
124 self.head_dim,
125 max_position=max_position,
126 rope_parameters=config.rope_parameters,
127 dual_chunk_attention_config=dual_chunk_attention_config,
128 )
129 self.attn = nn.ModuleList()
130
131 base_cache_config = cache_config
132
133 for loop_idx in range(self.loop_num):
134 base_layer_idx = extract_layer_index(prefix)
135 unique_layer_idx = loop_idx * total_layers + base_layer_idx
136
137 unique_prefix = prefix.replace(
138 f"layers.{base_layer_idx}", f"layers.{unique_layer_idx}"
139 )
140
141 if loop_idx == 0:
142 loop_cache_config = cache_config
143 else:
144 if base_cache_config is not None:
145 loop_cache_config = replace(
146 base_cache_config,
147 sliding_window=self.loop_window_size,
148 )
149 else:
150 loop_cache_config = CacheConfig(
151 sliding_window=self.loop_window_size,
152 cache_dtype="auto",
153 )
154
155 self.attn.append(
156 Attention(
157 self.num_heads,
158 self.head_dim,
159 self.scaling,
160 num_kv_heads=self.num_kv_heads,
161 cache_config=loop_cache_config,
162 quant_config=quant_config,
163 attn_type=attn_type,
164 prefix=f"{unique_prefix}.attn",
165 **{
166 "layer_idx": unique_layer_idx,
167 "dual_chunk_attention_config": dual_chunk_attention_config,
168 }
169 if dual_chunk_attention_config and loop_idx == 0
170 else {},
171 )
172 )
173
174 def forward(
175 self,
176 positions: torch.Tensor,
177 hidden_states: torch.Tensor,
178 loop_idx: int,
179 gate_proj: LoopGateProjection | None = None,
180 ) -> torch.Tensor:
181 if loop_idx == 0:
182 attn = self.attn[0]
183 qkv, _ = self.qkv_proj(hidden_states)
184 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
185 q, k = self.rotary_emb(positions, q, k)
186 attn_output = attn(q, k, v)
187 output, _ = self.o_proj(attn_output)
188 return output
189 else:
190 global_attn = self.attn[0]
191 local_attn = self.attn[loop_idx]
192 qkv, _ = self.qkv_proj(hidden_states)
193 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
194 q, k = self.rotary_emb(positions, q, k)
195 num_tokens, _ = q.shape
196 num_heads = self.num_heads
197 head_dim = self.head_dim
198
199 q_reshaped = q.view(num_tokens, num_heads, head_dim).transpose(0, 1)
200
201 global_attn_output = global_attn(q, None, None)
202 local_attn_output = local_attn(q, k, v)
203 assert gate_proj is not None, "gate_proj must be provided for loop_idx > 0"
204 gate = gate_proj(q_reshaped)
205 output = global_attn_output * gate + local_attn_output * (1 - gate)
206 output, _ = self.o_proj(output)
207 return output
208
209
210 class LoopCoderDecoderLayer(nn.Module):
211 def __init__(
212 self,
213 config: PreTrainedConfig,
214 cache_config: CacheConfig | None = None,
215 quant_config: QuantizationConfig | None = None,
216 prefix: str = "",
217 layer_idx: int = 0,
218 ) -> None:
219 super().__init__()
220 self.hidden_size = config.hidden_size
221 dual_chunk_attention_config = getattr(
222 config, "dual_chunk_attention_config", None
223 )
224 self.layer_idx = layer_idx
225 if getattr(config, "is_causal", True):
226 attn_type = AttentionType.DECODER
227 else:
228 attn_type = AttentionType.ENCODER_ONLY
229
230 self.self_attn = LoopCoderAttention(
231 config=config,
232 hidden_size=self.hidden_size,
233 num_heads=config.num_attention_heads,
234 max_position=config.max_position_embeddings,
235 num_kv_heads=config.num_key_value_heads,
236 cache_config=cache_config,
237 quant_config=quant_config,
238 prefix=f"{prefix}.self_attn",
239 attn_type=attn_type,
240 dual_chunk_attention_config=dual_chunk_attention_config,
241 layer_idx=self.layer_idx,
242 )
243 self.mlp = LlamaMLP(
244 hidden_size=self.hidden_size,
245 intermediate_size=config.intermediate_size,
246 hidden_act=config.hidden_act,
247 quant_config=quant_config,
248 prefix=f"{prefix}.mlp",
249 )
250 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
251 self.post_attention_layernorm = RMSNorm(
252 config.hidden_size, eps=config.rms_norm_eps
253 )
254
255 def forward(
256 self,
257 positions: torch.Tensor,
258 hidden_states: torch.Tensor,
259 loop_idx: int,
260 gate_proj: LoopGateProjection | None = None,
261 ) -> torch.Tensor:
262 residual = hidden_states
263 hidden_states = self.input_layernorm(hidden_states)
264 hidden_states = self.self_attn(
265 positions=positions,
266 hidden_states=hidden_states,
267 loop_idx=loop_idx,
268 gate_proj=gate_proj,
269 )
270 hidden_states = hidden_states + residual
271 residual = hidden_states
272 hidden_states = self.post_attention_layernorm(hidden_states)
273 hidden_states = self.mlp(hidden_states)
274 hidden_states = hidden_states + residual
275
276 return hidden_states
277
278
279 class LoopGateProjection(nn.Module):
280 """Gate projection for mixed attention in Loop 2+.
281
282 Computes: g = sigmoid(linear(Q)) for each head independently.
283 This gate determines how much to use Loop1's KV (global) vs current
284 loop's KV (local).
285
286 Supports tensor parallelism: each GPU handles a subset of heads.
287 The weight matrix has shape [num_heads, head_dim] and is split along
288 the head dimension.
289 """
290
291 def __init__(
292 self,
293 total_num_heads: int,
294 head_dim: int,
295 quant_config: QuantizationConfig | None = None,
296 prefix: str = "",
297 ):
298 super().__init__()
299 self.total_num_heads = total_num_heads
300 self.head_dim = head_dim
301 tp_size = get_tensor_model_parallel_world_size()
302 assert self.total_num_heads % tp_size == 0
303 self.num_heads = self.total_num_heads // tp_size
304
305 self.gate_proj = ColumnParallelLinear(
306 head_dim,
307 self.total_num_heads,
308 bias=True,
309 gather_output=False,
310 quant_config=quant_config,
311 prefix=f"{prefix}.gate_proj",
312 )
313
314 def forward(self, query: torch.Tensor) -> torch.Tensor:
315 """Compute gate values from query tensor.
316
317 Args:
318 query: [num_heads, num_tokens, head_dim] (vLLM flattened format)
319 where num_heads is the number of heads on this TP rank
320 and num_tokens = batch * seq_len
321
322 Returns:
323 gate: [num_tokens, num_heads * head_dim] (flattened format matching q shape)
324
325 """
326 num_heads, num_tokens, head_dim = query.shape
327
328 assert num_heads == self.num_heads, (
329 f"Expected {self.num_heads} heads, got {num_heads}"
330 )
331
332 query_flat = query.reshape(-1, head_dim)
333
334 gate_logits_flat, _ = self.gate_proj(query_flat)
335
336 gate_logits = gate_logits_flat.reshape(
337 num_heads, num_tokens, self.num_heads
338 ) # [num_heads, num_tokens, num_heads]
339
340 # Extract diagonal: each head h's query should use output column h
341 # gate_logits[h, :, h] gives the output for head h at each token
342 gate_logits = torch.diagonal(
343 gate_logits, dim1=0, dim2=2
344 ) # [num_tokens, num_heads]
345 gate_logits = gate_logits.transpose(0, 1) # [num_heads, num_tokens]
346 gate_logits = gate_logits.unsqueeze(-1) # [num_heads, num_tokens, 1]
347
348 # Apply sigmoid
349 gate = torch.sigmoid(gate_logits) # [num_heads, num_tokens, 1]
350
351 # Expand and reshape to match q shape: [num_tokens, num_heads * head_dim]
352 gate = gate.transpose(0, 1) # [num_tokens, num_heads, 1]
353 gate = gate.expand(-1, -1, head_dim) # [num_tokens, num_heads, head_dim]
354 gate = gate.reshape(
355 num_tokens, num_heads * head_dim
356 ) # [num_tokens, num_heads * head_dim]
357
358 return gate
359
360
361 @support_torch_compile(
362 dynamic_arg_dims={
363 "input_ids": 0,
364 "positions": -1,
365 "intermediate_tensors": 0,
366 "inputs_embeds": 0,
367 }
368 )
369 class IQuestLoopCoderModel(nn.Module):
370 def __init__(
371 self,
372 *,
373 vllm_config: VllmConfig,
374 prefix: str = "",
375 decoder_layer_type: type[nn.Module] = LoopCoderDecoderLayer,
376 ):
377 super().__init__()
378
379 config = vllm_config.model_config.hf_config
380 cache_config = vllm_config.cache_config
381 quant_config = vllm_config.quant_config
382
383 # TODO (@robertgshaw2): see if this can be moved out
384 if cache_config.sliding_window is not None and hasattr(
385 config, "max_window_layers"
386 ):
387 assert config.max_window_layers == config.num_hidden_layers, (
388 "Sliding window for some but all layers is not supported. "
389 "This model uses sliding window but `max_window_layers` = {} "
390 "is less than `num_hidden_layers` = {}. Please open an issue "
391 "to discuss this feature.".format(
392 config.max_window_layers,
393 config.num_hidden_layers,
394 )
395 )
396
397 self.config = config
398 self.quant_config = quant_config
399 self.vocab_size = config.vocab_size
400
401 self.embed_tokens = VocabParallelEmbedding(
402 config.vocab_size,
403 config.hidden_size,
404 quant_config=quant_config,
405 prefix=f"{prefix}.embed_tokens",
406 )
407
408 self.loop_num = getattr(self.config, "loop_num", 2)
409 self.window_size = getattr(self.config, "loop_window_size", 64)
410
411 # Gate projections for Loop 2+ (one per layer)
412 head_dim = config.hidden_size // config.num_attention_heads
413 _, _, self.gate_projections = make_layers(
414 config.num_hidden_layers,
415 lambda prefix: LoopGateProjection(
416 total_num_heads=config.num_attention_heads,
417 head_dim=head_dim,
418 quant_config=quant_config,
419 prefix=prefix,
420 ),
421 prefix=f"{prefix}.gate_projections",
422 )
423
424 self.start_layer, self.end_layer, self.layers = make_layers(
425 config.num_hidden_layers,
426 lambda prefix: LoopCoderDecoderLayer(
427 config=config,
428 cache_config=cache_config,
429 quant_config=quant_config,
430 prefix=prefix,
431 layer_idx=extract_layer_index(prefix),
432 ),
433 prefix=f"{prefix}.layers",
434 )
435 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
436
437 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
438 return self.embed_tokens(input_ids)
439
440 def forward(
441 self,
442 input_ids: torch.Tensor | None,
443 positions: torch.Tensor,
444 intermediate_tensors: IntermediateTensors | None = None,
445 inputs_embeds: torch.Tensor | None = None,
446 ) -> torch.Tensor | IntermediateTensors:
447 if inputs_embeds is not None:
448 hidden_states = inputs_embeds
449 else:
450 hidden_states = self.embed_input_ids(input_ids)
451
452 for loop_idx in range(self.loop_num):
453 for layer_idx, layer in enumerate(
454 self.layers[self.start_layer : self.end_layer]
455 ):
456 # Get the actual layer index (accounting for pipeline parallelism)
457 actual_layer_idx = self.start_layer + layer_idx
458 # Get gate_proj for this layer (only for loop_idx > 0)
459 gate_proj = (
460 self.gate_projections[actual_layer_idx] if loop_idx > 0 else None
461 )
462 hidden_states = layer(positions, hidden_states, loop_idx, gate_proj)
463 hidden_states = self.norm(hidden_states)
464 return hidden_states
465
466 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
467 stacked_params_mapping = [
468 # (param_name, shard_name, shard_id)
469 ("qkv_proj", "q_proj", "q"),
470 ("qkv_proj", "k_proj", "k"),
471 ("qkv_proj", "v_proj", "v"),
472 ("gate_up_proj", "gate_proj", 0),
473 ("gate_up_proj", "up_proj", 1),
474 ]
475 params_dict = dict(self.named_parameters(remove_duplicate=False))
476 loaded_params: set[str] = set()
477 for name, loaded_weight in weights:
478 if "rotary_emb.inv_freq" in name:
479 continue
480 for param_name, weight_name, shard_id in stacked_params_mapping:
481 if "gate_projections" in name:
482 continue
483 if weight_name not in name:
484 continue
485 name = name.replace(weight_name, param_name)
486 # Skip loading extra bias for GPTQ models.
487 if name.endswith(".bias") and name not in params_dict:
488 continue
489 if name.endswith("scale"):
490 # Remapping the name of FP8 kv-scale.
491 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
492 if remapped_name is None:
493 continue
494 name = remapped_name
495 param = params_dict[name]
496 weight_loader: Callable[..., None] = getattr(
497 param, "weight_loader", default_weight_loader
498 )
499 if weight_loader == default_weight_loader:
500 weight_loader(param, loaded_weight)
501 else:
502 weight_loader(param, loaded_weight, shard_id)
503 break
504 else:
505 if name.startswith("gate_projections."):
506 if name.endswith(".weight"):
507 vllm_name = name.replace(".weight", ".gate_proj.weight")
508 elif name.endswith(".bias"):
509 vllm_name = name.replace(".bias", ".gate_proj.bias")
510 else:
511 continue
512
513 if vllm_name in params_dict:
514 param = params_dict[vllm_name]
515 weight_loader = getattr(
516 param, "weight_loader", default_weight_loader
517 )
518 weight_loader(param, loaded_weight)
519 loaded_params.add(vllm_name)
520 continue
521 continue
522 if name.endswith(".bias") and name not in params_dict:
523 continue
524 # Remapping the name of FP8 kv-scale.
525 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
526 if remapped_name is None:
527 continue
528 name = remapped_name
529 param = params_dict[name]
530 weight_loader = getattr(param, "weight_loader", default_weight_loader)
531 weight_loader(param, loaded_weight)
532 loaded_params.add(name)
533 return loaded_params
534
535
536 class IQuestLoopCoderForCausalLM(nn.Module):
537 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
538 super().__init__()
539 config = vllm_config.model_config.hf_config
540 quant_config = vllm_config.quant_config
541
542 self.config = config
543
544 self.quant_config = quant_config
545 self.model = IQuestLoopCoderModel(
546 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
547 )
548
549 self.lm_head = ParallelLMHead(
550 config.vocab_size,
551 config.hidden_size,
552 quant_config=quant_config,
553 prefix=maybe_prefix(prefix, "lm_head"),
554 )
555 if config.tie_word_embeddings:
556 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
557
558 self.logits_processor = LogitsProcessor(config.vocab_size)
559
560 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
561 return self.model.embed_input_ids(input_ids)
562
563 def forward(
564 self,
565 input_ids: torch.Tensor | None,
566 positions: torch.Tensor,
567 intermediate_tensors: IntermediateTensors | None = None,
568 inputs_embeds: torch.Tensor | None = None,
569 ) -> torch.Tensor | IntermediateTensors:
570 hidden_states = self.model(
571 input_ids, positions, intermediate_tensors, inputs_embeds
572 )
573 return hidden_states
574
575 def compute_logits(
576 self,
577 hidden_states: torch.Tensor,
578 ) -> torch.Tensor | None:
579 logits = self.logits_processor(self.lm_head, hidden_states)
580 return logits
581
582 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
583 loader = AutoWeightsLoader(self)
584 return loader.load_weights(weights)