Files
2026-10-02 09:35:09 +00:00

257 lines
9.0 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from __future__ import annotations
import json
import os
from collections.abc import Sequence
from typing import TYPE_CHECKING, Literal
import torch
import torch.nn as nn
from vllm.model_executor.layers.linear import (
ColumnParallelLinear,
LinearBase,
ReplicatedLinear,
RowParallelLinear,
)
from vllm.model_executor.models.transformers.utils import init_on_device_without_buffers
from vllm.model_executor.models.utils import maybe_prefix
from vllm_omni.diffusion.data import OmniDiffusionConfig
from vllm_omni.quantization import build_quant_config
if TYPE_CHECKING:
from transformers import PretrainedConfig, PreTrainedModel
from transformers.models.auto.auto_factory import _BaseAutoModelClass
from vllm.model_executor.layers.quantization.base_config import (
QuantizationConfig,
)
Style = Literal["colwise", "colwise_rep", "rowwise", "rowwise_rep", "replicate"]
def make_attention_mask(
hidden_states: torch.Tensor,
seq_lengths: list[int],
) -> torch.Tensor | None:
"""Build a padding mask only when the batch contains padding.
``seq_lengths`` must be non-empty and contain valid sequence lengths.
Returns a boolean mask of shape ``(len(seq_lengths), max(seq_lengths))``
on the device of ``hidden_states``, with True marking valid tokens,
or None when all sequence lengths are equal.
Passing an all-true CUDA mask makes attention backends inspect a device
scalar to select the dense path, which introduces a host-device sync.
"""
max_seq_len = max(seq_lengths)
if all(seq_len == max_seq_len for seq_len in seq_lengths):
return None
attention_mask = hidden_states.new_zeros(
len(seq_lengths),
max_seq_len,
dtype=torch.bool,
)
for i, seq_len in enumerate(seq_lengths):
attention_mask[i, :seq_len] = True
return attention_mask
def replace_linear_class(
linear: nn.Linear,
style: Style = "replicate",
quant_config: QuantizationConfig | None = None,
*,
prefix: str = "",
) -> ColumnParallelLinear | RowParallelLinear | ReplicatedLinear:
"""
Replace nn.Linear with one of vLLM's tensor parallel linear classes.
Args:
linear: `nn.Linear` to be replaced.
style: Tensor parallel style of the new linear, e.g. "colwise".
quant_config: Quantization config for the new linear.
Returns:
The new linear.
"""
if not isinstance(style, str):
raise ValueError(f"Unsupported parallel style type {type(style)}, expected str")
vllm_linear_maps = {
"colwise": (ColumnParallelLinear, {}),
"colwise_rep": (ColumnParallelLinear, {"gather_output": True}),
"rowwise": (RowParallelLinear, {}),
"rowwise_rep": (RowParallelLinear, {"input_is_parallel": False}),
"replicate": (ReplicatedLinear, {}),
}
vllm_linear_cls, vllm_linear_kwargs = vllm_linear_maps[style]
return vllm_linear_cls(
input_size=linear.in_features,
output_size=linear.out_features,
bias=linear.bias is not None,
quant_config=quant_config,
prefix=prefix,
return_bias=False,
**vllm_linear_kwargs,
)
def recursive_replace_linear(model: nn.Module, od_config: OmniDiffusionConfig):
"""Recursively replace modules in the model as needed.
Currently, this replaces:
- `nn.Linear` with vLLM's tensor parallel linear classes
"""
# Prefix the patterns because we always start from `self.model`
quant_config = build_quant_config(od_config.quantization_config)
def _recursive_replace(module: nn.Module, prefix: str):
for child_name, child_module in module.named_children():
new_module = child_module
qual_name = maybe_prefix(prefix, child_name)
# Replace modules as needed
if isinstance(child_module, nn.Linear):
style = "replicate"
new_module = replace_linear_class(child_module, style, quant_config, prefix=qual_name)
else:
_recursive_replace(child_module, prefix=qual_name)
if new_module is not child_module:
setattr(module, child_name, new_module)
_recursive_replace(model, prefix="")
def recursive_replace_linear_with_quantization_config(
model: nn.Module,
quant_config: QuantizationConfig | None,
prefix: str = "",
skip_modules: Sequence[str] = (),
) -> None:
for child_name, child_module in model.named_children():
qual_name = maybe_prefix(prefix, child_name)
if any(qual_name == path or qual_name.startswith(path + ".") for path in skip_modules):
continue
if isinstance(child_module, nn.Linear):
setattr(
model,
child_name,
replace_linear_class(child_module, quant_config=quant_config, prefix=qual_name),
)
else:
recursive_replace_linear_with_quantization_config(
child_module, quant_config, prefix=qual_name, skip_modules=skip_modules
)
def init_parameters(
module: nn.Module,
dtype: torch.dtype | None,
device: torch.device | None = None,
):
for name, param in module.named_parameters(recurse=False):
if param.device == torch.device("meta"):
new_param = nn.Parameter(
torch.empty_like(
param.data,
dtype=dtype,
device=device,
),
requires_grad=param.requires_grad,
)
setattr(module, name, new_param)
for child in module.children():
init_parameters(child, dtype, device)
def release_module_parameters_to_meta(module: nn.Module) -> None:
"""Release a fully constructed module's parameter storage to meta.
The module must finish construction first so initialization code and vLLM
weight-loader metadata follow the ordinary CPU path. ``swap_tensors`` keeps
each registered Parameter object's identity while dropping only its storage.
Persistent buffers intentionally remain materialized.
"""
for param in module.parameters():
if param.device.type == "meta":
continue
meta_tensor = torch.empty_like(param, device="meta")
if type(param) is nn.Parameter:
meta_param = nn.Parameter(meta_tensor, requires_grad=param.requires_grad)
else:
meta_param = param.__class__.__new__(param.__class__, meta_tensor)
meta_param.requires_grad_(param.requires_grad)
meta_param.__dict__.update(getattr(param, "__dict__", {}))
torch.utils.swap_tensors(param, meta_param)
def init_parameters_preserving_vllm_linear(
module: nn.Module,
dtype: torch.dtype | None,
device: torch.device | None = None,
) -> None:
if isinstance(module, LinearBase):
return
for name, param in module.named_parameters(recurse=False):
if param.is_meta:
setattr(
module,
name,
nn.Parameter(torch.empty_like(param, dtype=dtype, device=device), requires_grad=param.requires_grad),
)
for child in module.children():
init_parameters_preserving_vllm_linear(child, dtype, device)
def create_transformers_model(
auto_cls: _BaseAutoModelClass,
od_config: OmniDiffusionConfig,
hf_config: PretrainedConfig,
dtype: torch.dtype | None = None,
device: torch.device | None = None,
) -> PreTrainedModel:
"""Create a HuggingFace model using the given auto class and model name."""
dtype = dtype or od_config.dtype
device = device or torch.get_default_device()
with init_on_device_without_buffers("meta"):
model = auto_cls.from_config(hf_config)
recursive_replace_linear(model, od_config)
init_parameters(model, dtype=dtype, device=device)
return model
def create_transformers_model_with_vllm_linears(
auto_cls: _BaseAutoModelClass,
hf_config: PretrainedConfig,
quant_config: QuantizationConfig | None,
dtype: torch.dtype,
device: torch.device,
prefix: str = "",
skip_modules: Sequence[str] = (),
) -> PreTrainedModel:
"""Build a Transformers model with vLLM linears."""
with init_on_device_without_buffers("meta"):
model = auto_cls.from_config(hf_config, dtype=dtype)
recursive_replace_linear_with_quantization_config(model, quant_config, prefix=prefix, skip_modules=skip_modules)
init_parameters_preserving_vllm_linear(model, dtype=dtype, device=device)
return model
def _load_json(model_path: str, filename: str, local_files_only: bool = True) -> dict:
"""Load a JSON config file from a local path or HuggingFace Hub repo."""
if local_files_only:
path = os.path.join(model_path, *filename.split("/"))
with open(path) as f:
return json.load(f)
else:
from vllm_omni.transformers_utils.repo_utils import hf_api
cached = hf_api().hf_hub_download(repo_id=model_path, filename=filename)
with open(cached) as f:
return json.load(f)