59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
476 lines
16 KiB
Python
Executable File
476 lines
16 KiB
Python
Executable File
# SPDX-License-Identifier: MIT AND Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright (c) 2026 LightSeek Foundation
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
#
|
|
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
"""Parameter helpers for sharded and packed layer weights."""
|
|
|
|
from collections.abc import Callable
|
|
from fractions import Fraction
|
|
|
|
import torch
|
|
from torch.nn import Parameter
|
|
|
|
from tokenspeed.runtime.utils import get_colorful_logger
|
|
|
|
__all__ = [
|
|
"BaseWeightParameter",
|
|
"PackedWeightParameter",
|
|
"PerTensorScaleParameter",
|
|
"ModelWeightParameter",
|
|
"ChannelQuantScaleParameter",
|
|
"GroupQuantScaleParameter",
|
|
"PackedColumnParameter",
|
|
"RowParallelWeightParameter",
|
|
]
|
|
|
|
logger = get_colorful_logger(__name__)
|
|
|
|
|
|
def _check_shape_match(actual: torch.Size, expected: torch.Size) -> None:
|
|
if actual != expected:
|
|
raise ValueError(f"Shape mismatch: {actual} != {expected}.")
|
|
|
|
|
|
class BaseWeightParameter(Parameter):
|
|
"""Base parameter for TokenSpeed linear layers with custom weight loading."""
|
|
|
|
def __new__(cls, data: torch.Tensor, **kwargs):
|
|
return super().__new__(cls, data=data, requires_grad=False)
|
|
|
|
def __init__(self, data: torch.Tensor, weight_loader: Callable):
|
|
"""Initialize the parameter wrapper with a weight-loader callback."""
|
|
self._weight_loader = weight_loader
|
|
|
|
@property
|
|
def weight_loader(self) -> Callable:
|
|
return self._weight_loader
|
|
|
|
def _assert_and_load(self, loaded_weight: torch.Tensor) -> None:
|
|
_check_shape_match(self.data.shape, loaded_weight.shape)
|
|
self.data.copy_(loaded_weight)
|
|
|
|
def load_column_parallel_weight(self, loaded_weight: torch.Tensor):
|
|
self._assert_and_load(loaded_weight)
|
|
|
|
def load_row_parallel_weight(self, loaded_weight: torch.Tensor):
|
|
self._assert_and_load(loaded_weight)
|
|
|
|
def load_merged_column_weight(self, loaded_weight: torch.Tensor, **kwargs):
|
|
self._assert_and_load(loaded_weight)
|
|
|
|
def load_qkv_weight(self, loaded_weight: torch.Tensor, **kwargs):
|
|
self._assert_and_load(loaded_weight)
|
|
|
|
|
|
class _ColumnParallelWeightParameter(BaseWeightParameter):
|
|
"""Shared column-parallel weight-loading helpers."""
|
|
|
|
def __init__(self, output_dim: int, **kwargs):
|
|
self._output_dim = output_dim
|
|
super().__init__(**kwargs)
|
|
|
|
@property
|
|
def output_dim(self) -> int:
|
|
return self._output_dim
|
|
|
|
def load_column_parallel_weight(
|
|
self,
|
|
loaded_weight: torch.Tensor,
|
|
tp_rank: int,
|
|
use_presharded_weights: bool = False,
|
|
):
|
|
if not use_presharded_weights:
|
|
shard_size = self.data.shape[self.output_dim]
|
|
loaded_weight = loaded_weight.narrow(
|
|
self.output_dim, tp_rank * shard_size, shard_size
|
|
)
|
|
_check_shape_match(self.data.shape, loaded_weight.shape)
|
|
self.data.copy_(loaded_weight)
|
|
|
|
def load_merged_column_weight(
|
|
self, loaded_weight: torch.Tensor, tp_rank: int, **kwargs
|
|
):
|
|
|
|
shard_offset = kwargs.get("shard_offset")
|
|
shard_size = kwargs.get("shard_size")
|
|
use_presharded_weights = kwargs.get("use_presharded_weights")
|
|
if (
|
|
isinstance(self, (PackedColumnParameter, PackedWeightParameter))
|
|
and self.packed_dim == self.output_dim
|
|
):
|
|
shard_size, shard_offset = self.adjust_shard_indexes_for_packing(
|
|
shard_offset=shard_offset, shard_size=shard_size
|
|
)
|
|
|
|
param_data = self.data
|
|
param_data = param_data.narrow(self.output_dim, shard_offset, shard_size)
|
|
if not use_presharded_weights:
|
|
loaded_weight = loaded_weight.narrow(
|
|
self.output_dim, tp_rank * shard_size, shard_size
|
|
)
|
|
_check_shape_match(param_data.shape, loaded_weight.shape)
|
|
param_data.copy_(loaded_weight)
|
|
|
|
def load_qkv_weight(
|
|
self,
|
|
loaded_weight: torch.Tensor,
|
|
tp_rank: int,
|
|
use_presharded_weights: bool = False,
|
|
**kwargs,
|
|
):
|
|
|
|
shard_offset = kwargs.get("shard_offset")
|
|
shard_size = kwargs.get("shard_size")
|
|
shard_id = kwargs.get("shard_id")
|
|
num_heads = kwargs.get("num_heads")
|
|
|
|
if (
|
|
isinstance(self, (PackedColumnParameter, PackedWeightParameter))
|
|
and self.output_dim == self.packed_dim
|
|
):
|
|
shard_size, shard_offset = self.adjust_shard_indexes_for_packing(
|
|
shard_offset=shard_offset, shard_size=shard_size
|
|
)
|
|
|
|
param_data = self.data
|
|
shard_id = tp_rank if shard_id == "q" else tp_rank // num_heads
|
|
param_data = param_data.narrow(self.output_dim, shard_offset, shard_size)
|
|
if not use_presharded_weights:
|
|
loaded_weight = loaded_weight.narrow(
|
|
self.output_dim, shard_id * shard_size, shard_size
|
|
)
|
|
|
|
_check_shape_match(param_data.shape, loaded_weight.shape)
|
|
param_data.copy_(loaded_weight)
|
|
|
|
|
|
class RowParallelWeightParameter(BaseWeightParameter):
|
|
"""Parameter class with row-parallel weight-loading support."""
|
|
|
|
def __init__(self, input_dim: int, **kwargs):
|
|
self._input_dim = input_dim
|
|
super().__init__(**kwargs)
|
|
|
|
@property
|
|
def input_dim(self) -> int:
|
|
return self._input_dim
|
|
|
|
def load_row_parallel_weight(
|
|
self,
|
|
loaded_weight: torch.Tensor,
|
|
tp_rank: int,
|
|
use_presharded_weights: bool = False,
|
|
):
|
|
if not use_presharded_weights:
|
|
shard_size = self.data.shape[self.input_dim]
|
|
loaded_weight = loaded_weight.narrow(
|
|
self.input_dim, tp_rank * shard_size, shard_size
|
|
)
|
|
|
|
if len(loaded_weight.shape) == 0:
|
|
loaded_weight = loaded_weight.reshape(1)
|
|
|
|
_check_shape_match(self.data.shape, loaded_weight.shape)
|
|
self.data.copy_(loaded_weight)
|
|
|
|
|
|
class ModelWeightParameter(_ColumnParallelWeightParameter, RowParallelWeightParameter):
|
|
"""
|
|
Parameter class for linear layer weights. Uses both column and
|
|
row parallelism.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class GroupQuantScaleParameter(
|
|
_ColumnParallelWeightParameter, RowParallelWeightParameter
|
|
):
|
|
"""
|
|
Parameter class for weight scales loaded for weights with
|
|
grouped quantization. Uses both column and row parallelism.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class ChannelQuantScaleParameter(_ColumnParallelWeightParameter):
|
|
"""
|
|
Parameter class for weight scales loaded for weights with
|
|
channel-wise quantization. Equivalent to _ColumnParallelWeightParameter.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class PerTensorScaleParameter(BaseWeightParameter):
|
|
"""
|
|
Parameter class for scales where the number of scales is
|
|
equivalent to the number of logical matrices in fused linear
|
|
layers (e.g. for QKV, there are 3 scales loaded from disk).
|
|
This is relevant to weights with per-tensor quantization.
|
|
Adds functionality to map the scalers to a shard during
|
|
weight loading.
|
|
|
|
Note: additional parameter manipulation may be handled
|
|
for each quantization config specifically, within
|
|
process_weights_after_loading
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
self.qkv_idxs = {"q": 0, "k": 1, "v": 2}
|
|
super().__init__(**kwargs)
|
|
|
|
def _shard_id_as_int(self, shard_id: str | int) -> int:
|
|
if isinstance(shard_id, int):
|
|
return shard_id
|
|
|
|
# if not int, assume shard_id for qkv
|
|
# map to int and return
|
|
if not isinstance(shard_id, str):
|
|
raise TypeError(
|
|
f"shard_id must be a str or int, got {type(shard_id).__name__}."
|
|
)
|
|
if shard_id not in self.qkv_idxs:
|
|
raise ValueError(f"Invalid qkv shard_id: {shard_id}.")
|
|
return self.qkv_idxs[shard_id]
|
|
|
|
# For row parallel layers, no sharding needed
|
|
# load weight into parameter as is
|
|
def load_row_parallel_weight(self, *args, **kwargs):
|
|
kwargs.pop("tp_rank", None)
|
|
kwargs.pop("use_presharded_weights", None)
|
|
super().load_row_parallel_weight(*args, **kwargs)
|
|
|
|
def load_merged_column_weight(self, *args, **kwargs):
|
|
self._load_into_shard_id(*args, **kwargs)
|
|
|
|
def load_qkv_weight(self, *args, **kwargs):
|
|
self._load_into_shard_id(*args, **kwargs)
|
|
|
|
def load_column_parallel_weight(self, *args, **kwargs):
|
|
kwargs.pop("tp_rank", None)
|
|
kwargs.pop("use_presharded_weights", None)
|
|
super().load_row_parallel_weight(*args, **kwargs)
|
|
|
|
def _load_into_shard_id(
|
|
self, loaded_weight: torch.Tensor, shard_id: str | int, **kwargs
|
|
):
|
|
"""
|
|
Slice the parameter data based on the shard id for
|
|
loading.
|
|
"""
|
|
|
|
param_data = self.data
|
|
shard_id = self._shard_id_as_int(shard_id)
|
|
|
|
# AutoFP8 scales do not have a shape
|
|
# compressed-tensors scales do have a shape
|
|
if len(loaded_weight.shape) != 0:
|
|
if loaded_weight.shape[0] != 1:
|
|
raise ValueError(
|
|
f"Expected scale shard with first dimension 1, got {loaded_weight.shape}."
|
|
)
|
|
loaded_weight = loaded_weight[0]
|
|
|
|
param_data = param_data[shard_id]
|
|
_check_shape_match(param_data.shape, loaded_weight.shape)
|
|
param_data.copy_(loaded_weight)
|
|
|
|
|
|
class PackedColumnParameter(_ColumnParallelWeightParameter):
|
|
"""
|
|
Parameter for model parameters which are packed on disk
|
|
and support column parallelism only. See PackedWeightParameter
|
|
for more details on the packed properties.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
packed_factor: int | Fraction,
|
|
packed_dim: int,
|
|
marlin_tile_size: int | None = None,
|
|
**kwargs,
|
|
):
|
|
self._packed_factor = packed_factor
|
|
self._packed_dim = packed_dim
|
|
self._marlin_tile_size = marlin_tile_size
|
|
super().__init__(**kwargs)
|
|
|
|
@property
|
|
def packed_dim(self):
|
|
return self._packed_dim
|
|
|
|
@property
|
|
def packed_factor(self):
|
|
return self._packed_factor
|
|
|
|
@property
|
|
def marlin_tile_size(self):
|
|
return self._marlin_tile_size
|
|
|
|
def adjust_shard_indexes_for_packing(self, shard_size, shard_offset):
|
|
return _adjust_shard_indexes_for_packing(
|
|
shard_size=shard_size,
|
|
shard_offset=shard_offset,
|
|
packed_factor=self.packed_factor,
|
|
marlin_tile_size=self.marlin_tile_size,
|
|
)
|
|
|
|
|
|
class PackedWeightParameter(ModelWeightParameter):
|
|
"""
|
|
Parameter for model weights which are packed on disk.
|
|
Example: GPTQ Marlin weights are int4 or int8, packed into int32.
|
|
Extends the ModelWeightParameter to take in the
|
|
packed factor, the packed dimension, and optionally, marlin
|
|
tile size for marlin kernels. Adjusts the shard_size and
|
|
shard_offset for fused linear layers model weight loading
|
|
by accounting for packing and optionally, marlin tile size.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
packed_factor: int | Fraction,
|
|
packed_dim: int,
|
|
marlin_tile_size: int | None = None,
|
|
**kwargs,
|
|
):
|
|
self._packed_factor = packed_factor
|
|
self._packed_dim = packed_dim
|
|
self._marlin_tile_size = marlin_tile_size
|
|
super().__init__(**kwargs)
|
|
|
|
@property
|
|
def packed_dim(self):
|
|
return self._packed_dim
|
|
|
|
@property
|
|
def packed_factor(self):
|
|
return self._packed_factor
|
|
|
|
@property
|
|
def marlin_tile_size(self):
|
|
return self._marlin_tile_size
|
|
|
|
def adjust_shard_indexes_for_packing(self, shard_size, shard_offset):
|
|
return _adjust_shard_indexes_for_packing(
|
|
shard_size=shard_size,
|
|
shard_offset=shard_offset,
|
|
packed_factor=self.packed_factor,
|
|
marlin_tile_size=self.marlin_tile_size,
|
|
)
|
|
|
|
|
|
def permute_param_layout_(
|
|
param: BaseWeightParameter, input_dim: int, output_dim: int, **kwargs
|
|
) -> BaseWeightParameter:
|
|
"""
|
|
Permute a parameter's layout to the specified input and output dimensions,
|
|
useful for forcing the parameter into a known layout, for example, if I need
|
|
a packed (quantized) weight matrix to be in the layout
|
|
{input_dim = 0, output_dim = 1, packed_dim = 0}
|
|
then I can call:
|
|
permute_param_layout_(x, input_dim=0, output_dim=1, packed_dim=0)
|
|
to ensure x is in the correct layout (permuting it to the correct layout if
|
|
required, asserting if it cannot get it to the correct layout)
|
|
"""
|
|
|
|
curr_input_dim = getattr(param, "input_dim", None)
|
|
curr_output_dim = getattr(param, "output_dim", None)
|
|
|
|
if curr_input_dim is None or curr_output_dim is None:
|
|
if param.data.dim() != 2:
|
|
raise ValueError(
|
|
"permute_param_layout_ only supports 2D parameters when either "
|
|
"input_dim or output_dim is not set"
|
|
)
|
|
|
|
# if one of the dimensions is not set, set it to the opposite of the other
|
|
# we can only do this since we asserted the parameter is 2D above
|
|
if curr_input_dim is None:
|
|
if curr_output_dim is None:
|
|
raise ValueError("either input or output dim must be set")
|
|
curr_input_dim = (curr_output_dim + 1) % 2
|
|
if curr_output_dim is None:
|
|
if curr_input_dim is None:
|
|
raise ValueError("either input or output dim must be set")
|
|
curr_output_dim = (curr_input_dim + 1) % 2
|
|
|
|
# create permutation from the current layout to the layout with
|
|
# self.input_dim at input_dim and self.output_dim at output_dim preserving
|
|
# other dimensions
|
|
perm = [
|
|
i for i in range(param.data.dim()) if i not in [curr_input_dim, curr_output_dim]
|
|
]
|
|
perm.insert(input_dim, curr_input_dim)
|
|
perm.insert(output_dim, curr_output_dim)
|
|
|
|
if "packed_dim" in kwargs:
|
|
if not (
|
|
hasattr(param, "packed_dim")
|
|
and param.packed_dim == perm[kwargs["packed_dim"]]
|
|
):
|
|
raise ValueError(
|
|
"permute_param_layout_ currently doesn't support repacking"
|
|
)
|
|
|
|
param.data = param.data.permute(*perm)
|
|
if hasattr(param, "_input_dim"):
|
|
param._input_dim = input_dim
|
|
if hasattr(param, "_output_dim"):
|
|
param._output_dim = output_dim
|
|
if "packed_dim" in kwargs and hasattr(param, "_packed_dim"):
|
|
param._packed_dim = kwargs["packed_dim"]
|
|
|
|
return param
|
|
|
|
|
|
def _adjust_shard_indexes_for_marlin(shard_size, shard_offset, marlin_tile_size):
|
|
return shard_size * marlin_tile_size, shard_offset * marlin_tile_size
|
|
|
|
|
|
def _adjust_shard_indexes_for_packing(
|
|
shard_size, shard_offset, packed_factor, marlin_tile_size
|
|
):
|
|
shard_size = shard_size // packed_factor
|
|
shard_offset = shard_offset // packed_factor
|
|
if marlin_tile_size is not None:
|
|
return _adjust_shard_indexes_for_marlin(
|
|
shard_size=shard_size,
|
|
shard_offset=shard_offset,
|
|
marlin_tile_size=marlin_tile_size,
|
|
)
|
|
return shard_size, shard_offset
|
|
|
|
|
|
class BlockQuantScaleParameter(
|
|
_ColumnParallelWeightParameter, RowParallelWeightParameter
|
|
):
|
|
"""
|
|
Parameter class for weight scales loaded for weights with
|
|
block-wise quantization. Uses both column and row parallelism.
|
|
"""
|
|
|
|
pass
|