main
py 707 lines 27.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Inkling model implementation for NVIDIA GPUs."""
4
5 from __future__ import annotations
6
7 from collections.abc import Iterable
8 from typing import Any, TypeAlias
9
10 import regex as re
11 import torch
12 from torch import nn
13
14 from vllm.config import VllmConfig
15 from vllm.distributed import (
16 get_pp_group,
17 get_tensor_model_parallel_rank,
18 get_tensor_model_parallel_world_size,
19 tensor_model_parallel_all_gather,
20 tensor_model_parallel_reduce_scatter,
21 )
22 from vllm.forward_context import get_forward_context
23 from vllm.model_executor.layers.quantization import QuantizationConfig
24 from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
25 from vllm.model_executor.models.interfaces import (
26 MultiModalEmbeddings,
27 SupportsLoRA,
28 SupportsMultiModal,
29 SupportsPP,
30 )
31 from vllm.model_executor.models.utils import (
32 AutoWeightsLoader,
33 WeightsMapper,
34 make_empty_intermediate_tensors_factory,
35 make_layers,
36 maybe_prefix,
37 )
38 from vllm.models.inkling.common.mm_preprocess import (
39 InklingDummyInputsBuilder,
40 InklingMultiModalProcessor,
41 InklingProcessingInfo,
42 inkling_audio_enabled,
43 inkling_vision_enabled,
44 )
45 from vllm.models.inkling.common.towers import InklingAudio, InklingVision
46 from vllm.multimodal import MULTIMODAL_REGISTRY
47 from vllm.sequence import IntermediateTensors
48
49 from ..configs import InklingMMConfig, InklingModelConfig
50 from .attention import InklingAttention, compute_log_scaling_tau
51 from .layernorm import InklingRMSNorm
52 from .logits_processor import InklingLogitsProcessor
53 from .mlp import InklingDenseMLP
54 from .moe import InklingMoE
55 from .ops.lamport import get_lamport_rs_conv, initialize_lamport_rs_conv
56 from .ops.norm import add_rmsnorm, embed_rmsnorm
57 from .sconv_swa_attn import _ATTN, _MLP, InklingConvState, InklingSconvMetadata
58 from .short_conv import InklingShortConv
59
60 InklingDelta: TypeAlias = torch.Tensor | tuple[torch.Tensor, torch.Tensor]
61
62
63 def _layer_id(name: str) -> int | None:
64 m = re.search(r"\.layers\.(\d+)\.", name)
65 return int(m.group(1)) if m else None
66
67
68 def _sconv_add_norm(
69 delta: InklingDelta,
70 hidden: torch.Tensor,
71 sconv: InklingShortConv,
72 norm: InklingRMSNorm | None,
73 positions: torch.Tensor,
74 ) -> tuple[torch.Tensor | None, torch.Tensor]:
75 """``h = hidden + sconv(TP-sum(delta)); y = rmsnorm(h)``.
76
77 The Lamport path performs reduce-scatter + shard sconv + all-gather +
78 residual add + norm. The NCCL path handles unsupported configurations."""
79 attn_metadata = get_forward_context().attn_metadata
80 m = (
81 attn_metadata.get(sconv.owner.prefix)
82 if isinstance(attn_metadata, dict)
83 else None
84 )
85 cache = sconv.owner.kv_cache
86 off_s, ws = sconv.owner.stream_ranges[sconv.stream_idx]
87 norm_w = norm.weight if norm is not None else None
88 eps = norm.variance_epsilon if norm is not None else 0.0
89 if isinstance(delta, tuple):
90 delta, shared_delta = delta
91 else:
92 shared_delta = None
93
94 mm = get_lamport_rs_conv(hidden.shape[-1], sconv.kernel_size)
95 if mm is not None and mm.usable(delta.shape[0]) and m is not None:
96 assert cache.numel() > 0
97 assert isinstance(m, InklingSconvMetadata)
98 return mm.rs_sconv_ag_add_norm(
99 delta,
100 hidden,
101 sconv.weight.squeeze(1),
102 norm_w,
103 eps,
104 cache,
105 positions,
106 m.block_table,
107 m.seq_idx,
108 m.slot_mapping,
109 off_s,
110 ws,
111 sconv.owner.block_size,
112 shared_tensor=shared_delta,
113 )
114
115 # Fallback: NCCL RS -> shard sconv -> AG -> fused add(+rmsnorm).
116 if shared_delta is not None:
117 delta.add_(shared_delta)
118 shard = tensor_model_parallel_reduce_scatter(delta, dim=-1)
119 shard = sconv(shard.contiguous(), positions)
120 full = tensor_model_parallel_all_gather(shard, dim=-1)
121 if norm is None:
122 return None, hidden + full
123 return add_rmsnorm(hidden, full, norm_w, eps)
124
125
126 class InklingDecoderLayer(nn.Module):
127 def __init__(
128 self,
129 config: InklingModelConfig,
130 layer_id: int,
131 is_local: bool,
132 quant_config: QuantizationConfig | None,
133 prefix: str,
134 force_dense_mlp: bool = False,
135 ) -> None:
136 super().__init__()
137 # Per-layer owner of the conv state as a paged SWA cache. The 4 sconv
138 # streams (K/V/attn/mlp) are packed head-major into one block and share
139 # it. Built first so the attention layer can wire its K/V sconv to it.
140 self.conv_state = InklingConvState(
141 num_kv_heads=(
142 config.swa_num_key_value_heads
143 if is_local
144 else config.num_key_value_heads
145 ),
146 head_dim=config.swa_head_dim if is_local else config.head_dim,
147 hidden_size=config.hidden_size,
148 kernel_size=config.sconv_kernel_size,
149 prefix=f"{prefix}.conv_state",
150 )
151 self.attn_norm = InklingRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
152 self.attn = InklingAttention(
153 config,
154 num_heads=(
155 config.swa_num_attention_heads
156 if is_local
157 else config.num_attention_heads
158 ),
159 num_kv_heads=(
160 config.swa_num_key_value_heads
161 if is_local
162 else config.num_key_value_heads
163 ),
164 head_dim=config.swa_head_dim if is_local else config.head_dim,
165 rel_extent=config.rel_extent,
166 local_extent=config.sliding_window_size,
167 is_local=is_local,
168 prefix=f"{prefix}.attn",
169 quant_config=quant_config,
170 conv_owner=self.conv_state,
171 )
172 self.mlp_norm = InklingRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
173 if force_dense_mlp or layer_id < config.dense_mlp_idx:
174 self.mlp: nn.Module = InklingDenseMLP(
175 hidden_size=config.hidden_size,
176 intermediate_size=config.dense_intermediate_size,
177 use_global_scale=config.use_global_scale,
178 quant_config=quant_config,
179 prefix=f"{prefix}.mlp",
180 )
181 else:
182 self.mlp = InklingMoE(
183 config,
184 prefix=f"{prefix}.mlp",
185 quant_config=quant_config,
186 )
187
188 # Short convolution on the attention-output and MLP-output residual
189 # streams, hidden-sharded: the sublayer outputs are reduce-scattered
190 # to [T, H/tp], the sconv runs on the shard, and an all-gather
191 # restores the full residual — all fused with the residual add + next
192 # rmsnorm via the Lamport P2P kernels for decode-sized batches.
193 tp_size = get_tensor_model_parallel_world_size()
194 sconv_dim = config.hidden_size // tp_size
195 self.attn_sconv = InklingShortConv(
196 sconv_dim, config.sconv_kernel_size, owner=self.conv_state, stream_idx=_ATTN
197 )
198 self.mlp_sconv = InklingShortConv(
199 sconv_dim, config.sconv_kernel_size, owner=self.conv_state, stream_idx=_MLP
200 )
201
202 def forward(
203 self,
204 positions: torch.Tensor,
205 hidden_states: torch.Tensor,
206 pending: tuple[InklingDelta, InklingShortConv] | None = None,
207 defer_mlp_add: bool = False,
208 attn_in: torch.Tensor | None = None,
209 log_scaling: torch.Tensor | None = None,
210 ) -> (
211 torch.Tensor | tuple[torch.Tensor, tuple[torch.Tensor | None, InklingShortConv]]
212 ):
213 # The previous sublayer's (pre-reduce, pre-sconv) delta is folded in
214 # fused with its RS/sconv/AG and this layer's pre-attention rmsnorm.
215 # A None delta means the partials sit in the NVLS symm buffer.
216 if pending is None:
217 if attn_in is None:
218 # First layer; on the text path attn_norm comes fused with
219 # the embedding gather (chain_weight in embed_rmsnorm).
220 attn_in = self.attn_norm(hidden_states)
221 else:
222 attn_in, hidden_states = _sconv_add_norm(
223 pending[0], hidden_states, pending[1], self.attn_norm, positions
224 )
225 attn_output = self.attn(positions, attn_in, log_scaling)
226 mlp_in, hidden_states = _sconv_add_norm(
227 attn_output, hidden_states, self.attn_sconv, self.mlp_norm, positions
228 )
229 mlp_output = (
230 self.mlp.forward_partials(mlp_in)
231 if isinstance(self.mlp, InklingMoE)
232 else self.mlp(mlp_in)
233 )
234 if defer_mlp_add:
235 # Caller folds mlp_output (pre-reduce, pre-sconv) into the next
236 # fused sconv+add+rmsnorm.
237 return hidden_states, (mlp_output, self.mlp_sconv)
238 return _sconv_add_norm(
239 mlp_output, hidden_states, self.mlp_sconv, None, positions
240 )[1]
241
242
243 class InklingReplicatedEmbedding(nn.Module):
244 """Full-vocab embedding table replicated on every TP rank.
245
246 Trades the full table per rank (~2.3 GiB at V=201k / H=6144 bf16, vs a
247 1/tp shard) for no masked lookup or per-lookup TP all-reduce, and keeps the
248 full table on-rank for the fused gather+norm kernel. Bit-exact vs
249 vocab-parallel: the all-reduce there only ever summed one real row against
250 exact zeros. The LM head stays vocab-sharded.
251 """
252
253 def __init__(self, num_embeddings: int, embedding_dim: int) -> None:
254 super().__init__()
255 self.weight = nn.Parameter(
256 torch.empty(num_embeddings, embedding_dim, dtype=torch.get_default_dtype()),
257 requires_grad=False,
258 )
259
260 def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
261 return embed_rmsnorm(input_ids, self.weight, None, 0.0)
262
263
264 class InklingModel(nn.Module):
265 def __init__(
266 self,
267 *,
268 config: InklingModelConfig,
269 quant_config: QuantizationConfig | None,
270 prefix: str,
271 ) -> None:
272 super().__init__()
273 self.config = config
274 self.embed_tokens = InklingReplicatedEmbedding(
275 config.padded_vocab_size, config.hidden_size
276 )
277 self.embed_norm = (
278 InklingRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
279 if config.use_embed_norm
280 else None
281 )
282 local_ids = set(config.local_layer_ids)
283
284 def get_layer(prefix: str) -> InklingDecoderLayer:
285 idx = _layer_id(prefix + ".") or int(prefix.split(".")[-1])
286 return InklingDecoderLayer(
287 config, idx, idx in local_ids, quant_config, prefix
288 )
289
290 self.start_layer, self.end_layer, self.layers = make_layers(
291 config.num_hidden_layers, get_layer, prefix=f"{prefix}.layers"
292 )
293 self.norm = InklingRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
294 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
295 ["hidden_states"], config.hidden_size
296 )
297
298 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
299 # Row gather + embed_norm in one launch.
300 norm = self.embed_norm
301 return embed_rmsnorm(
302 input_ids,
303 self.embed_tokens.weight,
304 norm.weight if norm is not None else None,
305 norm.variance_epsilon if norm is not None else 0.0,
306 )
307
308 def forward(
309 self,
310 input_ids: torch.Tensor | None,
311 positions: torch.Tensor,
312 intermediate_tensors: IntermediateTensors | None,
313 inputs_embeds: torch.Tensor | None = None,
314 ) -> torch.Tensor | IntermediateTensors:
315 attn_in0: torch.Tensor | None = None
316 if get_pp_group().is_first_rank:
317 if inputs_embeds is not None:
318 # embed_norm was already applied when producing inputs_embeds.
319 hidden_states = inputs_embeds
320 else:
321 # Gather + embed_norm + the first layer's attn_norm, one launch.
322 norm = self.embed_norm
323 hidden_states, attn_in0 = embed_rmsnorm(
324 input_ids,
325 self.embed_tokens.weight,
326 norm.weight if norm is not None else None,
327 self.config.rms_norm_eps,
328 chain_weight=self.layers[self.start_layer].attn_norm.weight,
329 )
330 else:
331 assert intermediate_tensors is not None
332 hidden_states = intermediate_tensors["hidden_states"]
333 hidden_states = hidden_states.view(-1, hidden_states.shape[-1])
334 log_scaling = None
335 if self.config.log_scaling_n_floor is not None:
336 log_scaling = compute_log_scaling_tau(
337 positions,
338 self.config.log_scaling_n_floor,
339 self.config.log_scaling_alpha,
340 )
341
342 pending: tuple[InklingDelta, InklingShortConv] | None = None
343 for layer in self.layers[self.start_layer : self.end_layer]:
344 hidden_states, pending = layer(
345 positions,
346 hidden_states,
347 pending=pending,
348 defer_mlp_add=True,
349 attn_in=attn_in0,
350 log_scaling=log_scaling,
351 )
352 attn_in0 = None
353
354 if not get_pp_group().is_last_rank:
355 if pending is not None:
356 hidden_states = _sconv_add_norm(
357 pending[0], hidden_states, pending[1], None, positions
358 )[1]
359 return IntermediateTensors({"hidden_states": hidden_states})
360 if pending is not None:
361 # Final RS/sconv/AG + residual add fused with the final rmsnorm.
362 norm_out = _sconv_add_norm(
363 pending[0], hidden_states, pending[1], self.norm, positions
364 )[0]
365 assert norm_out is not None
366 return norm_out
367 return self.norm(hidden_states)
368
369
370 class _TmlForCausalLMBase(nn.Module, SupportsPP, SupportsLoRA):
371 """Shared text-backbone causal-LM scaffolding for both entry classes."""
372
373 hf_to_vllm_mapper = WeightsMapper(
374 orig_to_new_substr={
375 ".w13_dn": ".gate_up_proj",
376 ".w2_md": ".down_proj",
377 },
378 orig_to_new_stacked={
379 ".attn.wq_du.": (".attn.qkvr.", 0),
380 ".attn.wk_dv.": (".attn.qkvr.", 1),
381 ".attn.wv_dv.": (".attn.qkvr.", 2),
382 ".attn.wr_du.": (".attn.qkvr.", 3),
383 },
384 orig_to_new_prefix={
385 "model.llm.layers.": "model.layers.",
386 "model.llm.embed_norm": "model.embed_norm",
387 "model.llm.embed": "model.embed_tokens",
388 "model.llm.norm": "model.norm",
389 "model.llm.unembed": "lm_head",
390 "language_model.layers.": "model.layers.",
391 "language_model.lm_head.": "lm_head.",
392 },
393 orig_to_new_suffix={
394 # ModelOpt NVFP4 scales
395 ".w13_weight.scale": ".w13_weight_scale",
396 ".w13_weight.scale2": ".w13_weight_scale_2",
397 ".w2_weight.scale": ".w2_weight_scale",
398 ".w2_weight.scale2": ".w2_weight_scale_2",
399 # Compressed tensors NVFP4 parameters
400 ".w13_weight.input_global_scale": ".w13_input_global_scale",
401 ".w13_weight.weight_global_scale": ".w13_weight_global_scale",
402 ".w13_weight.weight_packed": ".w13_weight_packed",
403 ".w13_weight.weight_scale": ".w13_weight_scale",
404 ".w2_weight.input_global_scale": ".w2_input_global_scale",
405 ".w2_weight.weight_global_scale": ".w2_weight_global_scale",
406 ".w2_weight.weight_packed": ".w2_weight_packed",
407 ".w2_weight.weight_scale": ".w2_weight_scale",
408 },
409 )
410 packed_modules_mapping = {
411 "qkvr": ["wq_du", "wk_dv", "wv_dv", "wr_du"],
412 "w13": ["w1", "w3"],
413 }
414 embedding_modules = {
415 "lm_head": "output_embeddings",
416 }
417
418 def _build(
419 self,
420 vllm_config: VllmConfig,
421 text_config: InklingModelConfig,
422 prefix: str,
423 ) -> None:
424 quant_config = vllm_config.quant_config
425 self.config = text_config
426 # Read by the MRV2 runner to publish per-request short-conv metadata.
427 # Short convolution is intrinsic to Inkling, so this is always set.
428 self.uses_sconv = True
429 self.model = InklingModel(
430 config=text_config,
431 quant_config=quant_config,
432 prefix=maybe_prefix(prefix, "model"),
433 )
434 initialize_lamport_rs_conv(
435 text_config.hidden_size,
436 text_config.sconv_kernel_size,
437 vllm_config.scheduler_config.max_num_batched_tokens,
438 )
439 self.lm_head = ParallelLMHead(
440 text_config.padded_vocab_size,
441 text_config.hidden_size,
442 org_num_embeddings=text_config.padded_vocab_size,
443 quant_config=quant_config,
444 prefix=maybe_prefix(prefix, "lm_head"),
445 )
446 self.logits_processor = InklingLogitsProcessor(
447 text_config.padded_vocab_size,
448 org_vocab_size=text_config.vocab_size,
449 soft_cap=text_config.final_logit_softcapping,
450 logits_mup_width_multiplier=text_config.logits_mup_width_multiplier,
451 )
452 self.make_empty_intermediate_tensors = ( # type: ignore[method-assign]
453 self.model.make_empty_intermediate_tensors
454 )
455
456 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
457 return self.model.embed_input_ids(input_ids)
458
459 def forward(
460 self,
461 input_ids: torch.Tensor | None,
462 positions: torch.Tensor,
463 intermediate_tensors: IntermediateTensors | None = None,
464 inputs_embeds: torch.Tensor | None = None,
465 **kwargs: object,
466 ) -> torch.Tensor | IntermediateTensors:
467 return self.model(
468 input_ids,
469 positions,
470 intermediate_tensors,
471 inputs_embeds,
472 )
473
474 def compute_logits(self, hidden_states: torch.Tensor) -> torch.Tensor | None:
475 return self.logits_processor(self.lm_head, hidden_states)
476
477 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
478 return _load_inkling_weights(self, weights, self.config)
479
480
481 class InklingForCausalLM(_TmlForCausalLMBase):
482 """Text-only entry point (``inkling_model`` checkpoints)."""
483
484 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
485 super().__init__()
486 self._build(vllm_config, vllm_config.model_config.hf_config, prefix)
487
488
489 @MULTIMODAL_REGISTRY.register_processor(
490 InklingMultiModalProcessor,
491 info=InklingProcessingInfo,
492 dummy_inputs=InklingDummyInputsBuilder,
493 )
494 class InklingForConditionalGeneration(_TmlForCausalLMBase, SupportsMultiModal):
495 """Top-level (multimodal) entry point.
496
497 Builds the vision + audio towers on top of the shared text backbone. Inkling has
498 NO cross-modal fusion (the vision tower emits one token per patch, the audio
499 tower one token per frame), so generation reuses the inherited backbone
500 ``forward`` / ``compute_logits`` (the latter already applies muP) and this
501 class only adds multimodal embedding + merge.
502 """
503
504 hf_to_vllm_mapper = _TmlForCausalLMBase.hf_to_vllm_mapper | WeightsMapper(
505 orig_to_new_prefix={
506 "model.audio.": "audio.",
507 "model.visual.": "visual.vision_encoder.",
508 },
509 )
510
511 @classmethod
512 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
513 if modality.startswith("image"):
514 return "<|content_image|>"
515 if modality.startswith("audio"):
516 return "<|content_audio_input|>"
517 raise ValueError("Only image or audio modality is supported")
518
519 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
520 super().__init__()
521 config: InklingMMConfig = vllm_config.model_config.hf_config
522
523 self.visual = (
524 InklingVision(config.vision_config, prefix=maybe_prefix(prefix, "visual"))
525 if inkling_vision_enabled(config)
526 else None
527 )
528 self.audio = (
529 InklingAudio(config.audio_config, prefix=maybe_prefix(prefix, "audio"))
530 if inkling_audio_enabled(config)
531 else None
532 )
533
534 self._build(vllm_config, config.text_config, prefix)
535
536 # -- multimodal embedding -------------------------------------------
537
538 def _process_image_input(
539 self, pixel_values: Any, num_patches: Any
540 ) -> tuple[torch.Tensor, ...]:
541 assert self.visual is not None
542 # pixel_values is a list (per item) of [P_i, 2, P, P, 3] tensors,
543 # or a single concatenated tensor. Normalize to a flat batch, run the
544 # tower once, then split back per item.
545 if isinstance(pixel_values, (list, tuple)):
546 if not pixel_values:
547 return ()
548 sizes = [int(p.shape[0]) for p in pixel_values]
549 patches = torch.cat(list(pixel_values), dim=0)
550 else:
551 patches = pixel_values
552 sizes = self._sizes_from(num_patches, patches.shape[0])
553
554 patches = patches.to(device=self.visual.device, dtype=self.visual.dtype)
555 embeds = self.visual(patches) # [total_patches, D]
556 return tuple(embeds.split(sizes))
557
558 def _process_audio_input(
559 self, input_audio_features: Any, num_audio_tokens: Any
560 ) -> tuple[torch.Tensor, ...]:
561 assert self.audio is not None
562 if isinstance(input_audio_features, (list, tuple)):
563 if not input_audio_features:
564 return ()
565 sizes = [int(d.shape[0]) for d in input_audio_features]
566 dmel = torch.cat(list(input_audio_features), dim=0)
567 else:
568 dmel = input_audio_features
569 sizes = self._sizes_from(num_audio_tokens, dmel.shape[0])
570
571 dmel = dmel.to(device=self.audio.device)
572 embeds = self.audio(dmel) # [total_frames, D]
573 return tuple(embeds.split(sizes))
574
575 @staticmethod
576 def _sizes_from(counts: Any, total: int) -> list[int]:
577 if counts is None:
578 return [total]
579 if isinstance(counts, torch.Tensor):
580 return [int(c) for c in counts.flatten().tolist()]
581 if isinstance(counts, (list, tuple)):
582 flat: list[int] = []
583 for c in counts:
584 flat.append(int(c.item()) if isinstance(c, torch.Tensor) else int(c))
585 return flat
586 return [int(counts)]
587
588 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
589 # Iterate modalities in a stable order so the returned per-item tensors
590 # line up with their appearance order; the positional merge in
591 # embed_input_ids handles actual placement.
592 pixel_values = kwargs.get("pixel_values")
593 num_patches = kwargs.get("num_patches")
594 input_audio_features = kwargs.get("input_audio_features")
595 num_audio_tokens = kwargs.get("num_audio_tokens")
596
597 embeddings: tuple[torch.Tensor, ...] = ()
598 if pixel_values is not None and self.visual is not None:
599 embeddings += self._process_image_input(pixel_values, num_patches)
600 if input_audio_features is not None and self.audio is not None:
601 embeddings += self._process_audio_input(
602 input_audio_features, num_audio_tokens
603 )
604 return embeddings
605
606 def embed_input_ids(
607 self,
608 input_ids: torch.Tensor,
609 multimodal_embeddings: MultiModalEmbeddings | None = None,
610 *,
611 is_multimodal: torch.Tensor | None = None,
612 ) -> torch.Tensor:
613 # Override the base's 1-arg embed_input_ids: the runner calls this 3-arg
614 # signature for multimodal models. Text embeddings come from the shared
615 # backbone (which applies embed_norm); MM embeddings are scattered in.
616 from vllm.model_executor.models.utils import _merge_multimodal_embeddings
617
618 # Placeholder ids use unused vocabulary slots and these positions are
619 # overwritten by MM embeds below.
620 inputs_embeds = self.model.embed_input_ids(input_ids)
621 if multimodal_embeddings is None or len(multimodal_embeddings) == 0:
622 return inputs_embeds
623 assert is_multimodal is not None
624 return _merge_multimodal_embeddings(
625 inputs_embeds=inputs_embeds,
626 multimodal_embeddings=multimodal_embeddings,
627 is_multimodal=is_multimodal,
628 )
629
630 def get_language_model(self) -> nn.Module:
631 # This class IS the causal LM (the towers are side branches), so the
632 # language model is self — callers expect a module exposing ``.model``
633 # and ``.lm_head``.
634 return self
635
636
637 # ===========================================================================
638 # Weight loading
639 # ===========================================================================
640
641
642 _MOE_EXPERT_WEIGHT_RE = re.compile(
643 r"^(?P<mlp>.*\.mlp)\.(?P<rest>(?:shared_)?experts\..+)$"
644 )
645
646
647 def _load_inkling_weights(
648 module: nn.Module,
649 weights: Iterable[tuple[str, torch.Tensor]],
650 config: InklingModelConfig,
651 ) -> set[str]:
652 moe_modules = {
653 name: mod for name, mod in module.named_modules() if isinstance(mod, InklingMoE)
654 }
655 loaded: set[str] = set()
656 tp_size = get_tensor_model_parallel_world_size()
657 tp_rank = get_tensor_model_parallel_rank()
658 local_ids = set(config.local_layer_ids)
659
660 def _iter_loadable_weights() -> Iterable[tuple[str, torch.Tensor]]:
661 for name, weight in module.hf_to_vllm_mapper.apply(weights):
662 shard_id = getattr(weight, "shard_id", None)
663 # Replicate K/V conv-free GQA heads when tp_size > num_kv_heads.
664 if (
665 shard_id in (1, 2)
666 and name.endswith(".attn.qkvr.weight")
667 and weight.shape[0] > 0
668 ):
669 lid = _layer_id(name)
670 if lid is not None:
671 is_local = lid in local_ids
672 n_kv = (
673 config.swa_num_key_value_heads
674 if is_local
675 else config.num_key_value_heads
676 )
677 head_dim = config.swa_head_dim if is_local else config.head_dim
678 if tp_size > n_kv and weight.shape[0] == n_kv * head_dim:
679 kv_idx = (tp_rank * n_kv) // tp_size
680 weight = weight.narrow(0, kv_idx * head_dim, head_dim)
681 weight.shard_id = shard_id
682
683 # MoE expert tensors (fused stacked, routed + shared sink): translate
684 # the checkpoint layout to per-expert FusedMoE loads.
685 moe_match = _MOE_EXPERT_WEIGHT_RE.match(name)
686 if moe_match is not None and moe_match.group("mlp") in moe_modules:
687 moe = moe_modules[moe_match.group("mlp")]
688 for rel in moe.load_expert_weight(moe_match.group("rest"), weight):
689 loaded.add(f"{moe_match.group('mlp')}.{rel}")
690 continue
691
692 yield name, weight
693
694 # The release checkpoint also carries auxiliary prediction-head weights;
695 # they are not part of the causal LM served by this implementation.
696 mapper = WeightsMapper(orig_to_new_prefix={"model.mtp.": None})
697 loader = AutoWeightsLoader(module)
698 loaded |= loader.load_weights(_iter_loadable_weights(), mapper=mapper)
699
700 # Post-load MoE fixups (default input scales, zeroed EP-padding experts).
701 for moe_name, moe in moe_modules.items():
702 for rel in moe.finalize_load():
703 loaded.add(f"{moe_name}.{rel}")
704 return loaded
705
706
707 EntryClass = [InklingForCausalLM, InklingForConditionalGeneration]