main
py 137 lines 5.48 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Inkling logits processor (muP + LoRA aware).
4
5 Inkling divides the final logits by a muP width multiplier
6 (``logits_mup_width_multiplier``). This applies it two ways, depending on
7 whether an lm_head LoRA is attached:
8
9 * No LoRA: fold ``1/mup`` into the lm_head GEMM alpha (fp32 epilogue) -- no
10 separate elementwise kernel, no extra rounding, no weight mutation.
11 * LoRA attached: the LoRA manager wraps this layer in
12 ``LogitsProcessorWithLoRA``, whose ``forward`` calls
13 ``type(base_layer).forward(self=wrapper)`` -- so this ``forward`` runs with
14 ``self`` bound to the wrapper. We detect that via ``base_layer`` and take the
15 LoRA path: run the wrapper's ``_get_logits`` (base logits + the lm_head LoRA
16 delta), then divide the full logits by the multiplier so the delta is scaled
17 too. muP thus composes with the LoRA delta, with the dispatch as the only
18 model-side branching.
19 """
20
21 from __future__ import annotations
22
23 import torch
24
25 from vllm.model_executor.layers.logits_processor import LogitsProcessor
26 from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
27
28
29 class InklingLogitsProcessor(LogitsProcessor):
30 """``LogitsProcessor`` that applies Inkling's muP logits width multiplier.
31
32 Args:
33 vocab_size: Padded vocabulary size.
34 org_vocab_size: Unpadded vocabulary size (defaults to ``vocab_size``).
35 scale: Base logits scale (kept ``1.0`` for the served checkpoint).
36 logits_as_input: Whether the input is already logits.
37 soft_cap: Optional logit soft cap (``None`` for the served checkpoint).
38 logits_mup_width_multiplier: muP width divisor for the final logits;
39 ``None`` or ``0`` disables it.
40
41 """
42
43 def __init__(
44 self,
45 vocab_size: int,
46 org_vocab_size: int | None = None,
47 scale: float = 1.0,
48 logits_as_input: bool = False,
49 soft_cap: float | None = None,
50 logits_mup_width_multiplier: float | None = None,
51 ) -> None:
52 super().__init__(
53 vocab_size=vocab_size,
54 org_vocab_size=org_vocab_size,
55 scale=scale,
56 logits_as_input=logits_as_input,
57 soft_cap=soft_cap,
58 )
59 self.logits_mup_width_multiplier = logits_mup_width_multiplier
60 self._logits_zero: torch.Tensor | None = None
61
62 def forward(
63 self,
64 lm_head: VocabParallelEmbedding,
65 hidden_states: torch.Tensor,
66 embedding_bias: torch.Tensor | None = None,
67 skip_gather: bool = False,
68 ) -> torch.Tensor | None:
69 # ``base_layer`` exists only on the LogitsProcessorWithLoRA wrapper,
70 # which calls this forward with ``self`` bound to the wrapper. The
71 # wrapper is not an ``InklingLogitsProcessor`` instance, so dispatch
72 # ``_lora_forward`` explicitly through the base_layer's class (it
73 # provides ``_get_logits``/``logits_as_input``; only ``_lora_forward``
74 # lives on this class).
75 if hasattr(self, "base_layer"):
76 return type(self.base_layer)._lora_forward(
77 self, lm_head, hidden_states, embedding_bias, skip_gather
78 )
79 return self._base_forward(lm_head, hidden_states, embedding_bias, skip_gather)
80
81 def _lora_forward(
82 self,
83 lm_head: VocabParallelEmbedding,
84 hidden_states: torch.Tensor,
85 embedding_bias: torch.Tensor | None = None,
86 skip_gather: bool = False,
87 ) -> torch.Tensor | None:
88 # ``self`` is the LogitsProcessorWithLoRA wrapper here: ``_get_logits``
89 # returns the base logits plus the lm_head LoRA delta. Apply the muP
90 # divisor on the full logits so the LoRA delta is scaled too.
91 mup_multiplier = self.base_layer.logits_mup_width_multiplier
92 mup = 1.0 / mup_multiplier if mup_multiplier else None
93 if self.logits_as_input:
94 logits = hidden_states
95 else:
96 logits = self._get_logits(
97 hidden_states, lm_head, embedding_bias, skip_gather
98 )
99 # TODO: fuse this multiplication
100 if logits is not None and mup:
101 assert self.base_layer.soft_cap is None
102 assert self.base_layer.scale == 1.0
103 logits = logits * mup
104 return logits
105
106 def _base_forward(
107 self,
108 lm_head: VocabParallelEmbedding,
109 hidden_states: torch.Tensor,
110 embedding_bias: torch.Tensor | None = None,
111 skip_gather: bool = False,
112 ) -> torch.Tensor | None:
113 mup = self.logits_mup_width_multiplier
114 if not mup:
115 return super().forward(lm_head, hidden_states, embedding_bias, skip_gather)
116 # Fold the muP width divisor into the lm_head GEMM alpha (fp32 epilogue):
117 # no separate elementwise kernel, no bf16 rounding of scaled logits, and
118 # no weight mutation. Overfit to the served checkpoint: bf16 lm_head, no
119 # soft cap, unit logits scale.
120 assert self.soft_cap is None
121 assert self.scale == 1.0
122 w = lm_head.weight
123 if self._logits_zero is None:
124 self._logits_zero = w.new_zeros(1)
125 logits = torch.addmm(
126 self._logits_zero,
127 hidden_states,
128 w.t(),
129 beta=0.0,
130 alpha=1.0 / mup,
131 )
132 if skip_gather:
133 return logits
134 logits = self._gather_logits(logits)
135 if logits is not None:
136 logits = logits[..., : self.org_vocab_size]
137 return logits