Files
wehub-resource-sync 770d92cb1f
Lint / lint (push) Waiting to run
Windows CI / Windows (push) Waiting to run
Build Docs / Deploy Docs (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:23:58 +08:00

189 lines
6.3 KiB
Python

"""Common utilities for quantization"""
from collections.abc import Sequence
from typing import Callable, List, Optional # noqa: UP035
from tvm import IRModule, relax, te, tirx
from tvm.relax.frontend import nn
from tvm.runtime import DataType, DataTypeCode
from tvm.s_tir import dlight as dl
from tvm.target import Target
from mlc_llm.support import tensor_parallel as tp
def convert_uint_to_float(
weight: te.Tensor,
bits: int,
num_elem_per_storage: int,
storage_dtype: str,
model_dtype: str,
axis: int = -1,
out_shape: Optional[List[tirx.Expr]] = None, # noqa: UP006
ft_reorder: Optional[bool] = False,
) -> te.Tensor:
"""Convert a quantized uint weight to an unquantized float weight."""
tir_bin_mask = tirx.const((1 << bits) - 1, storage_dtype)
if out_shape is None:
out_shape = weight.shape
out_shape[axis] *= num_elem_per_storage
axis = axis if axis >= 0 else len(out_shape) + axis
return te.compute(
shape=out_shape,
fcompute=lambda *idx: tirx.bitwise_and(
tirx.shift_right(
weight(*idx[:axis], idx[axis] // num_elem_per_storage, *idx[axis + 1 :]),
(
(
(idx[axis] % num_elem_per_storage) % 2 * 4
+ (idx[axis] % num_elem_per_storage) // 2
)
* bits
if ft_reorder
else (idx[axis] % num_elem_per_storage) * bits
).astype(storage_dtype),
),
tir_bin_mask,
).astype(model_dtype),
)
def is_final_fc(name: str) -> bool:
"""Determines whether the parameter is the last layer based on its name."""
# TODO: use more specious condition to determine final fc
return name in ["head", "lm_head", "lm_head.linear", "embed_out"]
def is_moe_gate(name: str, node: nn.Linear) -> bool:
"""Check whether the parameter is the MoE gate layer."""
return name.endswith("gate") and isinstance(node.out_features, int) and node.out_features <= 256
def compile_quantize_func(mod: IRModule, device) -> Callable:
"""Compile a quantization function for a given device."""
device_type = device._DEVICE_TYPE_TO_NAME[device.dlpack_device_type()]
if device_type in ["cuda", "rocm", "metal", "vulkan", "opencl"]:
target = Target.current()
if target is None:
target = Target.from_device(device)
with target:
mod = dl.ApplyDefaultSchedule(
dl.gpu.Reduction(),
dl.gpu.GeneralReduction(),
dl.gpu.Fallback(),
)(mod)
elif device_type == "cpu":
target = "llvm"
mod = relax.transform.LegalizeOps()(mod)
else:
raise NotImplementedError(f"Device type {device_type} is not supported")
ex = relax.build(mod, target=target)
vm = relax.VirtualMachine(ex, device)
return vm["main"]
def apply_sharding(shard_strategy, name: str, weight: nn.Parameter):
"""Apply sharding strategy to a weight."""
if isinstance(shard_strategy, tp.ShardSingleDim):
weight.attrs["shard_strategy"] = tp.ShardSingleDim(
name=name,
dim=shard_strategy.dim,
segs=shard_strategy.segs,
)
else:
raise NotImplementedError(f"Unknowing sharding strategy: {shard_strategy}")
def convert_uint_packed_fp8_to_float(
weight: te.Tensor,
num_elem_per_storage: int,
storage_dtype: str,
model_dtype: str,
quant_dtype: str,
axis: int = -1,
out_shape: Optional[Sequence[tirx.Expr]] = None,
) -> te.Tensor:
"""Unpack a fp8 value from the storage dtype and convert to float."""
assert quant_dtype in ["float8_e4m3fn", "float8_e5m2"]
assert DataType(storage_dtype).type_code == DataTypeCode.UINT
bits = DataType(quant_dtype).bits
elem_storage_dtype = DataType(f"uint{bits}")
tir_bin_mask = tirx.const((1 << bits) - 1, "uint8")
if axis < 0:
axis += len(weight.shape)
if out_shape is None:
out_shape = (
*weight.shape[:axis],
weight.shape[axis] * num_elem_per_storage,
*weight.shape[axis + 1 :],
)
axis = axis if axis >= 0 else len(out_shape) + axis
return te.compute(
shape=out_shape,
fcompute=lambda *idx: tirx.reinterpret(
quant_dtype,
tirx.bitwise_and(
tirx.shift_right(
weight(*idx[:axis], idx[axis] // num_elem_per_storage, *idx[axis + 1 :]),
((idx[axis] % num_elem_per_storage) * bits).astype(storage_dtype),
).astype(elem_storage_dtype),
tir_bin_mask,
),
).astype(model_dtype),
)
def pack_weight(
weight: te.Tensor,
axis: int,
num_elem_per_storage: int,
weight_dtype: str,
storage_dtype: str,
out_shape: Optional[Sequence[tirx.Expr]] = None,
):
"""Convert a tensor to a packed format by packing consecutive bits.
This can be useful for sub-byte quantization.
Parameters
----------
weight : te.Tensor
The weight
axis : int
The axis to pack.
num_elem_per_storage : int
The number of elements per storage.
weight_dtype : str
The dtype of the input tensor.
storage_dtype : str
The dtype of the packed tensor.
out_shape : Optional[Sequence[tirx.Expr]]
The output shape of the packed tensor. Zero-padding is added if needed.
"""
assert weight.dtype == storage_dtype
shape = weight.shape
if axis < 0:
axis += len(shape)
k = shape[axis]
axis = axis if axis >= 0 else len(shape) + axis
if out_shape is None:
out_shape = (
*shape[:axis],
tirx.ceildiv(k, num_elem_per_storage),
*shape[axis + 1 :],
)
r = te.reduce_axis((0, num_elem_per_storage), name="r")
packed_weight = te.compute(
shape=out_shape,
fcompute=lambda *idx: tirx.sum(
tirx.if_then_else(
idx[axis] * num_elem_per_storage + r < k,
weight(*idx[:axis], idx[axis] * num_elem_per_storage + r, *idx[axis + 1 :])
<< (r * DataType(weight_dtype).bits),
tirx.const(0, storage_dtype),
),
axis=r,
),
name="packed_weight",
).astype(storage_dtype)
return packed_weight