Files
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

966 lines
33 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import itertools
from collections.abc import Callable, Iterable, Mapping
from contextlib import contextmanager
from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeAlias, overload
import regex as re
import torch
import torch.nn as nn
from torch.nn.modules.module import register_module_module_registration_hook
from vllm.config import VllmConfig
from vllm.distributed import (
get_tensor_model_parallel_rank,
get_tensor_model_parallel_world_size,
)
from vllm.logger import init_logger
from vllm.model_executor.model_loader.reload import (
support_quantized_model_reload_from_hp_weights,
)
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm.model_executor.models.interfaces import supports_any_eagle
from vllm.multimodal import NestedTensors
from vllm.sequence import IntermediateTensors
from vllm.utils.math_utils import cdiv
from vllm.utils.torch_utils import (
async_tensor_h2d,
direct_register_custom_op,
)
if TYPE_CHECKING:
from transformers import PretrainedConfig
from transformers.conversion_mapping import WeightRenaming
from vllm.model_executor.layers.quantization import QuantizationConfig
logger = init_logger(__name__)
ShardId: TypeAlias = str | int | tuple[int, ...]
@dataclass
class WeightsMapper:
"""Maps the name of each weight if they match the following patterns.
If a key maps to a value of `None`, the corresponding weight is ignored."""
orig_to_new_renaming: list["WeightRenaming"] = field(default_factory=list)
orig_to_new_regex: Mapping[re.Pattern, str | None] = field(default_factory=dict)
orig_to_new_substr: Mapping[str, str | None] = field(default_factory=dict)
orig_to_new_stacked: Mapping[str, tuple[str, ShardId]] = field(default_factory=dict)
orig_to_new_prefix: Mapping[str, str | None] = field(default_factory=dict)
orig_to_new_suffix: Mapping[str, str | None] = field(default_factory=dict)
def __or__(self, other: "WeightsMapper") -> "WeightsMapper":
"""Combine two `WeightsMapper`s by merging their mappings."""
return WeightsMapper(
orig_to_new_renaming=[
*self.orig_to_new_renaming,
*other.orig_to_new_renaming,
],
orig_to_new_regex={**self.orig_to_new_regex, **other.orig_to_new_regex},
orig_to_new_substr={**self.orig_to_new_substr, **other.orig_to_new_substr},
orig_to_new_stacked={
**self.orig_to_new_stacked,
**other.orig_to_new_stacked,
},
orig_to_new_prefix={**self.orig_to_new_prefix, **other.orig_to_new_prefix},
orig_to_new_suffix={**self.orig_to_new_suffix, **other.orig_to_new_suffix},
)
def _map_name(self, key: str) -> str | None:
"""Map a weight name (backward-compatible wrapper that discards shard_id)."""
result = self._map_name_with_shard(key)
return result[0] if result is not None else None
def _map_name_with_shard(self, key: str) -> tuple[str, ShardId | None] | None:
"""Map a weight name and extract any shard_id metadata.
Returns:
(mapped_name, shard_id) if the name should be kept.
None if the name should be dropped.
"""
# Deprecation warnings
if key.endswith(".kv_scale"):
logger.warning_once(
"DEPRECATED. Found kv_scale in the checkpoint. "
"This format is deprecated in favor of separate k_scale and "
"v_scale tensors and will be removed in a future release. "
"Functionally, we will remap kv_scale to k_scale and duplicate "
"k_scale to v_scale"
)
for renaming in self.orig_to_new_renaming:
key, _ = renaming.rename_source_key(key)
for pattern, new_key in self.orig_to_new_regex.items():
if pattern.search(key):
if new_key is None:
return None
key = pattern.sub(new_key, key)
for substr, new_key in self.orig_to_new_substr.items():
if substr in key:
if new_key is None:
return None
key = key.replace(substr, new_key, 1)
shard_id: ShardId | None = None
for substr, (new_key, new_shard_id) in self.orig_to_new_stacked.items():
if substr in key:
key = key.replace(substr, new_key, 1)
shard_id = new_shard_id
for prefix, new_key in self.orig_to_new_prefix.items():
if key.startswith(prefix):
if new_key is None:
return None
key = key.replace(prefix, new_key, 1)
for suffix, new_key in self.orig_to_new_suffix.items():
if key.endswith(suffix):
if new_key is None:
return None
key = new_key.join(key.rsplit(suffix, 1))
return key, shard_id
def apply(
self, weights: Iterable[tuple[str, torch.Tensor]]
) -> Iterable[tuple[str, torch.Tensor]]:
for name, data in weights:
result = self._map_name_with_shard(name)
if result is None:
continue
out_name, shard_id = result
if shard_id is not None:
data.shard_id = shard_id
yield out_name, data
def apply_list(self, values: list[str]) -> list[str]:
return [
out_name
for name in values
if (out_name := self._map_name(name)) is not None
]
def apply_dict(self, values: dict[str, Any]) -> dict[str, Any]:
return {
out_name: value
for name, value in values.items()
if (out_name := self._map_name(name)) is not None
}
def get_unstacked_mapper(self) -> "WeightsMapper":
"""Mapper variant that drops stacked maps, keeping all genuine renames/prefixes.
Consumers that reference the checkpoint's *unstacked* module names (LoRA name
parsing and the quantization config's layer lists) need the constituent names
(e.g. `q_proj`) to survive rather than being rewritten to the stacked vLLM name
(`qkv_proj`)."""
return replace(self, orig_to_new_stacked={})
class AutoWeightsLoader:
"""
Helper class to load weights into a [`torch.nn.Module`][]. It is able
to automatically detect child modules and parameters while iterating over
the weights only once.
The weight loading logic for individual modules can be overridden
by defining a `load_weights` method.
Similarly, the weight loading logic for individual parameters can be
overridden by defining a `weight_loader` method.
Detailed weight loading information can be viewed by setting the
environment variable `VLLM_LOGGING_LEVEL=DEBUG`.
"""
# Models trained using early version ColossalAI or quantized by
# GPTQModel may include these tensors in checkpoint. Skip them.
ROTARY_EMBEDS_UNUSED_WEIGHTS = [
"rotary_pos_emb.inv_freq",
"rotary_emb.inv_freq",
"rotary_emb.cos_cached",
"rotary_emb.sin_cached",
]
def __init__(
self,
module: nn.Module,
*,
skip_prefixes: list[str] | None = None,
skip_substrs: list[str] | None = None,
ignore_unexpected_prefixes: list[str] | None = None,
ignore_unexpected_suffixes: list[str] | None = None,
) -> None:
super().__init__()
self.module = module
self.skip_prefixes = skip_prefixes or []
self.skip_substrs = skip_substrs or []
self.ignore_unexpected_prefixes = ignore_unexpected_prefixes or []
self.ignore_unexpected_suffixes = ignore_unexpected_suffixes or []
# update default skip_substrs
self.skip_substrs += self.ROTARY_EMBEDS_UNUSED_WEIGHTS
def _groupby_prefix(
self,
weights: Iterable[tuple[str, torch.Tensor]],
) -> Iterable[tuple[str, Iterable[tuple[str, torch.Tensor]]]]:
weights_by_parts = (
(weight_name.split(".", 1), weight_data)
for weight_name, weight_data in weights
)
for prefix, group in itertools.groupby(weights_by_parts, key=lambda x: x[0][0]):
yield (
prefix,
# Because maxsplit=1 in weight_name.split(...),
# the length of `parts` must either be 1 or 2
(
("" if len(parts) == 1 else parts[1], weights_data)
for parts, weights_data in group
),
)
def _get_qualname(self, prefix: str, rest: str) -> str:
if prefix == "":
return rest
if rest == "":
return prefix
return ".".join((prefix, rest))
def _can_skip(self, qualname: str) -> bool:
return any(qualname.startswith(p) for p in self.skip_prefixes) or any(
substr in qualname for substr in self.skip_substrs
)
def _can_ignore_unexpected(self, qualname: str) -> bool:
iup = (qualname.startswith(p) for p in self.ignore_unexpected_prefixes)
ius = (qualname.endswith(s) for s in self.ignore_unexpected_suffixes)
return any(iup) or any(ius)
def _load_param(
self,
base_prefix: str,
param: nn.Parameter,
weights: Iterable[tuple[str, torch.Tensor]],
) -> Iterable[str]:
for weight_name, weight_data in weights:
weight_qualname = self._get_qualname(base_prefix, weight_name)
if self._can_skip(weight_qualname):
logger.debug("Skipping weight %s", weight_qualname)
continue
if weight_name != "":
if self._can_ignore_unexpected(weight_qualname):
logger.debug("Ignoring weight %s", weight_qualname)
continue
raise ValueError(
f"Attempted to load nested weight {weight_qualname!r} "
f"into a single parameter {base_prefix!r}"
)
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, weight_data)
logger.debug("Loaded weight %s with shape %s", weight_qualname, param.shape)
yield weight_qualname
def _add_loadable_non_param_tensors(
self, module: nn.Module, child_params: dict[str, torch.Tensor]
):
"""
Add tensor names that are not in the model params that may be in the
safetensors, e.g., batch normalization stats and registered buffers.
"""
# Add persistent registered buffers.
# Non-persistent buffers are excluded, matching PyTorch state_dict().
non_persistent = getattr(module, "_non_persistent_buffers_set", set())
for buf_name, buf in module.named_buffers(recurse=False):
if buf_name not in child_params and buf_name not in non_persistent:
child_params[buf_name] = buf
if isinstance(
module,
(
nn.BatchNorm1d,
nn.BatchNorm2d,
nn.BatchNorm3d,
nn.LazyBatchNorm1d,
nn.LazyBatchNorm2d,
nn.LazyBatchNorm3d,
nn.SyncBatchNorm,
),
):
module_state_dict = module.state_dict()
for stat_name in ("running_mean", "running_var", "num_batches_tracked"):
child_params[stat_name] = module_state_dict[stat_name]
def _load_module(
self,
base_prefix: str,
module: nn.Module,
weights: Iterable[tuple[str, torch.Tensor]],
) -> Iterable[str]:
if isinstance(module, (StageMissingLayer, PPMissingLayer)):
return
# Avoid infinite recursion since this function is typically
# called inside load_weights of the module itself
if module != self.module:
module_load_weights = getattr(module, "load_weights", None)
if callable(module_load_weights):
loaded_params = module_load_weights(weights)
if loaded_params is None:
logger.warning(
"Unable to collect loaded parameters for module %s", module
)
else:
yield from map(
lambda x: self._get_qualname(base_prefix, x),
loaded_params,
)
child_modules = dict(module.named_children())
child_params = dict(module.named_parameters(recurse=False))
# Add missing tensors the weight loader needs to be able to load
# that aren't registered as params, e.g., batchnorm statistics.
self._add_loadable_non_param_tensors(module, child_params)
for child_prefix, child_weights in self._groupby_prefix(weights):
prefix = self._get_qualname(base_prefix, child_prefix)
if child_prefix in child_modules:
if self._can_skip(prefix + "."):
logger.debug("Skipping module %s", prefix)
continue
yield from self._load_module(
prefix, child_modules[child_prefix], child_weights
)
elif child_prefix in child_params:
if self._can_skip(prefix):
logger.debug("Skipping param %s", prefix)
continue
yield from self._load_param(
prefix, child_params[child_prefix], child_weights
)
else:
can_skip_module = self._can_skip(prefix + ".")
can_skip_param = self._can_skip(prefix)
if can_skip_module or can_skip_param:
logger.debug("Skipping missing %s", prefix)
continue
can_ignore_module = self._can_ignore_unexpected(prefix + ".")
can_ignore_param = self._can_ignore_unexpected(prefix)
if can_ignore_module or can_ignore_param:
logger.debug("Ignoring missing %s", prefix)
continue
named_parameters = module.named_parameters(recurse=True)
desc_param_keys = {
maybe_prefix(base_prefix, k) for k, _ in named_parameters
}
msg = (
f"There is no module or parameter named {prefix!r} "
f"in {self.module._get_name()}. "
f"The available parameters belonging to {base_prefix} "
f"({module._get_name()}) are: {desc_param_keys}"
)
raise ValueError(msg)
@support_quantized_model_reload_from_hp_weights
def load_weights(
self,
weights: Iterable[tuple[str, torch.Tensor]],
*,
mapper: WeightsMapper | None = None,
) -> set[str]:
# Ignore unexpected biases (typically from GPTQ models)
self.ignore_unexpected_suffixes.append(".bias")
# Many models store quant_config in the base model instead of the causal model.
# We look at the causal model's direct children for this reason.
modules = (self.module, *self.module.children())
iterator = (m.quant_config for m in modules if hasattr(m, "quant_config"))
if quant_config := next(iterator, None):
# Get mappings and ignore prefixes for KV cache quantization scales
mapper = mapper or WeightsMapper()
mapper |= quant_config.get_cache_scale_mapper()
ignore_unexpected_suffixes = quant_config._ignore_unexpected_suffixes
self.ignore_unexpected_suffixes.extend(ignore_unexpected_suffixes)
if mapper is not None:
weights = mapper.apply(weights)
# filter out weights with first-prefix/substr to skip in name
weights = (
(name, weight) for name, weight in weights if not self._can_skip(name)
)
autoloaded_weights = set(self._load_module("", self.module, weights))
return autoloaded_weights
def init_vllm_registered_model(
vllm_config: VllmConfig,
*,
prefix: str = "",
hf_config: "PretrainedConfig | None" = None,
architectures: list[str] | None = None,
) -> nn.Module:
"""
Helper function to initialize an inner model registered to vLLM,
based on the arguments passed to the outer vLLM model.
"""
from vllm.model_executor.model_loader.utils import initialize_model
if hf_config is None and architectures is not None:
# So that the architectures field is overridden
hf_config = vllm_config.model_config.hf_config
if hf_config is not None:
vllm_config = vllm_config.with_hf_config(hf_config, architectures=architectures)
return initialize_model(vllm_config=vllm_config, prefix=prefix)
@overload
def flatten_bn(x: torch.Tensor) -> torch.Tensor: ...
@overload
def flatten_bn(x: list[torch.Tensor]) -> list[torch.Tensor]: ...
@overload
def flatten_bn(
x: list[torch.Tensor] | torch.Tensor,
*,
concat: Literal[True],
) -> torch.Tensor: ...
@overload
def flatten_bn(
x: list[torch.Tensor] | torch.Tensor,
*,
concat: bool = False,
) -> list[torch.Tensor] | torch.Tensor: ...
def flatten_bn(
x: list[torch.Tensor] | torch.Tensor,
*,
concat: bool = False,
) -> list[torch.Tensor] | torch.Tensor:
"""
Flatten the `B` and `N` dimensions of batched multimodal inputs.
The input tensor should have shape `(B, N, ...)`.
"""
if isinstance(x, torch.Tensor):
return x.flatten(0, 1)
if concat:
return torch.cat(x)
return [x_n for x_b in x for x_n in x_b]
def _flatten_embeddings(embeddings: NestedTensors) -> torch.Tensor:
"""
Recursively flattens and concatenates NestedTensors on all but the last
dimension.
"""
if isinstance(embeddings, torch.Tensor):
# Flatten all but the last dimension.
return embeddings.flatten(0, -2)
return torch.cat(tuple(_flatten_embeddings(t) for t in embeddings))
def _embedding_count_expression(embeddings: NestedTensors) -> str:
"""
Constructs a debugging representation of the number of embeddings in the
NestedTensors.
"""
if isinstance(embeddings, torch.Tensor):
return " x ".join([str(dim) for dim in embeddings.shape[:-1]])
return " + ".join(_embedding_count_expression(inner) for inner in embeddings)
def split_list_into_ranges(lst: torch.Tensor, interval: int) -> list[list[int]]:
ranges: list[list[int]] = [[] for _ in range((max(lst) // interval) + 1)]
for num in lst:
index = num // interval
ranges[index].append(num)
return ranges
def _merge_multimodal_embeddings(
inputs_embeds: torch.Tensor,
multimodal_embeddings: NestedTensors,
is_multimodal: torch.Tensor,
) -> torch.Tensor:
"""
Merge `multimodal_embeddings` into `inputs_embeds` by overwriting the
positions in `inputs_embeds` corresponding to placeholder tokens in
`input_ids`.
Note:
This updates `inputs_embeds` in place.
"""
if len(multimodal_embeddings) == 0:
return inputs_embeds
mm_embeds_flat = _flatten_embeddings(multimodal_embeddings)
input_dtype = inputs_embeds.dtype
try:
# If is_multimodal is on CPU this avoids a D2H sync
inputs_embeds[is_multimodal] = mm_embeds_flat.to(dtype=input_dtype)
except RuntimeError as e:
num_actual_tokens = len(mm_embeds_flat)
num_expected_tokens = is_multimodal.sum().item()
if num_actual_tokens != num_expected_tokens:
expr = _embedding_count_expression(multimodal_embeddings)
raise ValueError(
f"Attempted to assign {expr} = {num_actual_tokens} "
f"multimodal tokens to {num_expected_tokens} placeholders"
) from e
raise ValueError("Error during index put operation") from e
return inputs_embeds
def isin_list(
elements: torch.Tensor,
test_elements_list: list[int],
) -> torch.Tensor:
test_elements = async_tensor_h2d(
test_elements_list, dtype=torch.int64, device=elements.device
)
return torch.isin(elements, test_elements)
class StageMissingLayer(nn.Module):
def __init__(self, stage_name: str, module: nn.Module | None = None) -> None:
super().__init__()
self.stage_name = stage_name
# Don't register this as a child module in order to
# avoid missing keys when loading weights
self.__dict__["module"] = module
def __getattr__(self, name: str):
return getattr(self.__dict__["module"], name)
def __call__(self, *args, **kwargs):
raise RuntimeError(f"{self} should not be called")
def extra_repr(self) -> str:
return f"stage_name={self.stage_name!r}"
@contextmanager
def collect_children(
module: nn.Module,
*,
targets: type[nn.Module] | tuple[type[nn.Module], ...] | None = None,
):
"""
Within this context, collect all direct child assignments to `module`,
returning a list of children names that is internally updated until the
context is exited.
If `targets` is set, instead collect descendents of `module`
that are an instance of `targets`, even if they aren't direct children.
"""
children_names = list[str]()
if targets is None:
def hook(module_: nn.Module, name: str, submodule: nn.Module):
if module_ is module:
children_names.append(name)
with register_module_module_registration_hook(hook):
yield children_names
else:
yield children_names
for name, module_ in module.named_modules():
if isinstance(module_, targets):
children_names.append(name)
@contextmanager
def no_init_weights(
module: nn.Module,
placeholder: Callable[[nn.Module], nn.Module],
*,
targets: type[nn.Module] | tuple[type[nn.Module], ...] | None = None,
):
"""
Within this context, prevent weight initialization from using device memory and
replace direct child assignments to `module` with the result of `placeholder()`.
If `targets` is set, instead prevent weight initialization and
replace assignments where the child is an instance of `targets`,
even if they aren't direct children of `module`.
"""
if targets is None:
def hook(module_: nn.Module, name: str, submodule: nn.Module):
if module_ is module:
return placeholder(submodule)
return submodule
with register_module_module_registration_hook(hook), torch.device("meta"):
yield
else:
def hook(module_: nn.Module, name: str, submodule: nn.Module):
if isinstance(module_, targets):
submodule.to("meta") # Free memory
if isinstance(submodule, targets):
submodule.to("meta") # Free memory
return placeholder(submodule)
return submodule
# Not all descendents are targeted, so we can't use a blanket
# `torch.device("meta")` context
with register_module_module_registration_hook(hook):
yield
class LayerFn(Protocol):
def __call__(self, prefix: str) -> torch.nn.Module: ...
class PPMissingLayer(torch.nn.Identity):
"""
A placeholder layer for missing layers in a pipeline parallel model.
"""
def __init__(self, *args, **kwargs):
super().__init__()
def forward(self, *args, **kwargs):
"""Return the first arg from args or the first value from kwargs."""
return args[0] if args else next(iter(kwargs.values()))
def make_layers(
num_hidden_layers: int,
layer_fn: LayerFn,
prefix: str,
) -> tuple[int, int, torch.nn.ModuleList]:
"""Make a list of layers with the given layer function, taking
pipeline parallelism into account.
Args:
num_hidden_layers: Total number of hidden layers in the model.
layer_fn: Function to create a layer given its index.
prefix: Prefix for layer names.
Returns:
Tuple of (start_layer, end_layer, modules).
"""
from vllm.distributed.parallel_state import get_pp_group
from vllm.distributed.utils import get_pp_indices
from vllm.model_executor.offloader import get_offloader
start_layer, end_layer = get_pp_indices(
num_hidden_layers, get_pp_group().rank_in_group, get_pp_group().world_size
)
modules = torch.nn.ModuleList(
[PPMissingLayer() for _ in range(start_layer)]
+ get_offloader().wrap_modules(
layer_fn(prefix=f"{prefix}.{idx}") for idx in range(start_layer, end_layer)
)
+ [PPMissingLayer() for _ in range(end_layer, num_hidden_layers)]
)
return start_layer, end_layer, modules
# NOTE: don't use lru_cache here because it can prevent garbage collection
_model_to_pp_missing_layer_names: dict[int, list[str]] = {}
def get_pp_missing_layer_names(model: torch.nn.Module) -> list[str]:
"""Get the names of the missing layers in a pipeline parallel model."""
model_id = id(model)
if model_id in _model_to_pp_missing_layer_names:
return _model_to_pp_missing_layer_names[model_id]
missing_layer_names = []
for name, module in model.named_modules():
if isinstance(module, (StageMissingLayer, PPMissingLayer)):
# NOTE: the trailing dot is used to match the prefix of the layer.
# without the dot, we could match a layer that is not missing,
# e.g., 'encoder.layer.1' would match 'encoder.layer.11'
missing_layer_names.append(name + ".")
_model_to_pp_missing_layer_names[model_id] = missing_layer_names
return missing_layer_names
def is_pp_missing_parameter(name: str, model: torch.nn.Module) -> bool:
"""Check if a parameter is missing in a pipeline parallel model."""
if isinstance(model, (StageMissingLayer, PPMissingLayer)):
return True
return any(
name.startswith(missing_layer_name)
for missing_layer_name in get_pp_missing_layer_names(model)
)
def make_empty_intermediate_tensors_factory(keys: list[str], hidden_size: int):
def make_empty_intermediate_tensors(
batch_size: int,
dtype: torch.dtype,
device: torch.device,
) -> IntermediateTensors:
return IntermediateTensors(
{
key: torch.zeros((batch_size, hidden_size), dtype=dtype, device=device)
for key in keys
}
)
return make_empty_intermediate_tensors
def maybe_prefix(prefix: str, name: str) -> str:
"""Add a prefix to a name if the prefix is non-empty.
Args:
prefix: The prefix to add. If empty, no prefix will be added.
name: The name to potentially prefix.
Returns:
The string "prefix.name" if prefix was non-empty, otherwise just "name".
"""
return name if not prefix else f"{prefix}.{name}"
def get_draft_quant_config(vllm_config: VllmConfig) -> "QuantizationConfig | None":
"""Get quantization config for Draft models.
Draft models should use their own quantization config instead of the verifier/target
model's config. This helper retrieves the draft model's quantization config.
Args:
vllm_config: The vLLM configuration object.
Returns:
The draft model's config if available, None otherwise.
"""
draft_model_config = vllm_config.speculative_config.draft_model_config
draft_load_config = vllm_config.load_config
return (
VllmConfig.get_quantization_config(draft_model_config, draft_load_config)
if draft_model_config
else None
)
def extract_layer_index(layer_name: str, num_attn_module: int = 1) -> int:
"""
Extract the layer index from the module name.
Examples:
- "encoder.layers.0" -> 0
- "encoder.layers.1.self_attn" -> 1
- "2.self_attn" -> 2
- "model.encoder.layers.0.sub.1" -> ValueError if num_attn_module == 1
"""
subnames = layer_name.split(".")
int_vals: list[int] = []
for subname in subnames:
try:
int_vals.append(int(subname))
except ValueError:
continue
if num_attn_module == 1 or "attn" not in layer_name:
assert len(int_vals) == 1, (
f"layer name {layer_name} should only contain one integer"
)
return int_vals[0]
else:
assert len(int_vals) <= 2, (
f"layer name {layer_name} should contain most two integers"
)
layer_index = (
int_vals[0] * num_attn_module + int_vals[1]
if len(int_vals) == 2
else int_vals[0]
)
return layer_index
def cast_overflow_tensors(tensors: torch.Tensor, offset: float = 1000) -> torch.Tensor:
clamp_value = torch.finfo(tensors.dtype).max - offset
return torch.clamp(tensors, min=-clamp_value, max=clamp_value)
def fast_topk(
values: torch.Tensor, topk: int, dim: int
) -> tuple[torch.Tensor, torch.Tensor]:
"""
Optimized topk implementation that uses torch.max for k=1 case.
This function provides better performance for the common case of k=1
by using torch.max instead of the more general torch.topk.
Args:
values: Input tensor to find top-k values from
topk: Number of top values to return (k). Must be > 0.
dim: Dimension along which to compute topk
Returns:
Tuple of (values, indices) where values are the top-k values
and indices are their corresponding indices in the input tensor
"""
if topk == 1:
# Use max along the specified dimension to get both value and index
return torch.max(values, dim=dim, keepdim=True)
else:
# Use topk for efficiency with larger k values
return torch.topk(values, topk, dim=dim)
# Chunk x along the num_tokens axis for sequence parallelism
# NOTE: This is wrapped in a torch custom op to work around the following issue:
# The output tensor can have a sequence length 0 at small input sequence lengths
# even though we explicitly pad to avoid this.
def sequence_parallel_chunk(x: torch.Tensor) -> torch.Tensor:
return torch.ops.vllm.sequence_parallel_chunk_impl(x)
def sequence_parallel_chunk_impl(x: torch.Tensor) -> torch.Tensor:
tp_size = get_tensor_model_parallel_world_size()
tp_rank = get_tensor_model_parallel_rank()
# all_gather needs the sequence length to be divisible by tp_size
seq_len = x.size(0)
remainder = seq_len % tp_size
if remainder != 0:
pad_len = tp_size - remainder
y = nn.functional.pad(x, (0, 0, 0, pad_len))
else:
y = x
chunk = y.shape[0] // tp_size
start = tp_rank * chunk
out = torch.narrow(y, 0, start, chunk)
# narrow() returns a view; clone when it aliases the input (no-pad case),
# since a functional custom op must not return a view of an input.
return out.clone() if y is x else out
def sequence_parallel_chunk_impl_fake(x: torch.Tensor) -> torch.Tensor:
tp_size = get_tensor_model_parallel_world_size()
seq_len = cdiv(x.size(0), tp_size)
shape = list(x.shape)
shape[0] = seq_len
out = torch.empty(shape, dtype=x.dtype, device=x.device)
return out
direct_register_custom_op(
op_name="sequence_parallel_chunk_impl",
op_func=sequence_parallel_chunk_impl,
fake_impl=sequence_parallel_chunk_impl_fake,
tags=(torch.Tag.needs_fixed_stride_order,),
)
def process_eagle_weight(
model: nn.Module,
name: str,
) -> None:
"""
Update EAGLE model flags based on loaded weight name.
This should be called during weight loading to detect if a model
has its own lm_head or embed_tokens weight.
Args:
model: The model instance (must support EAGLE)
name: The name of the weight to process
"""
if not supports_any_eagle(model):
return
# To prevent overriding with target model's layers
if "lm_head" in name:
model.has_own_lm_head = True
if "embed_tokens" in name:
model.has_own_embed_tokens = True
def get_layer_index(feature_layer_index: int, num_hidden_layers: int) -> int:
"""Given a signed vision feature layer, get the number of hidden layers
needed to leverage it.
Args:
feature_layer_index: Index of a required layer in the visual encoder.
num_hidden_layers: The total number of hidden layers in the visual encoder.
"""
if feature_layer_index < 0:
return num_hidden_layers + feature_layer_index + 1
return feature_layer_index
def scatter_output_slices(
output: torch.Tensor,
indices: list[int],
per_item_out_tokens: list[int],
dest: dict[int, torch.Tensor] | list[torch.Tensor | None],
clone: bool = False,
) -> None:
"""Slice a concatenated output tensor and scatter into dest by index."""
offset = 0
for idx in indices:
n_tok = per_item_out_tokens[idx]
sliced = output[offset : offset + n_tok]
dest[idx] = sliced.clone() if clone else sliced
offset += n_tok