Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d452b2d
Online quantization support for experts_int8
Josephasafg Mar 28, 2026
3fb8121
Added online moe
Josephasafg Mar 29, 2026
497f425
Added device and type annotation
Josephasafg Mar 29, 2026
0b9b111
Semantics
Josephasafg Mar 29, 2026
6c9f74d
Added device to int8
Josephasafg Mar 29, 2026
a9bff7a
Fixed pre commit
Josephasafg Mar 29, 2026
0dde06b
Merge remote-tracking branch 'upstream/main' into experts_int8_consol…
Josephasafg Mar 30, 2026
559535e
Merge branch 'main' into experts_int8_consolidation
Josephasafg Mar 30, 2026
6eccc74
Merge remote-tracking branch 'upstream/main' into experts_int8_consol…
Josephasafg Apr 2, 2026
e4d3fb5
Merge branch 'main' of https://github.com/vllm-project/vllm into expe…
Josephasafg Apr 5, 2026
990afbc
extract more logic
Josephasafg Apr 5, 2026
187135f
revert bad merge
Josephasafg Apr 5, 2026
cfe08e1
Semantics
Josephasafg Apr 5, 2026
43c5912
Use oracle for int8
Josephasafg Apr 5, 2026
cebf88b
More consolidation
Josephasafg Apr 5, 2026
35fb080
Remove comments
Josephasafg Apr 5, 2026
832d36e
Used get_fused_moe_quant_config
Josephasafg Apr 5, 2026
3f9e18c
Removed unused code
Josephasafg Apr 5, 2026
93617bc
Re-added maybe_make_prepare_finalize
Josephasafg Apr 5, 2026
893d9e9
Added assert
Josephasafg Apr 5, 2026
c81631f
Merge branch 'main' of https://github.com/vllm-project/vllm into expe…
Josephasafg Apr 9, 2026
6cea5aa
Add new online frontend support for int8
Josephasafg Apr 13, 2026
958b090
Moved moe base class under online/common
Josephasafg Apr 13, 2026
6e6b56e
Semantics
Josephasafg Apr 13, 2026
535a39c
Rename var
Josephasafg Apr 14, 2026
d029510
Remove common folder
Josephasafg Apr 16, 2026
42426af
Merge branch 'main' into experts_int8_consolidation
Josephasafg Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion tests/quantization/test_experts_int8.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,5 @@ def test_model_experts_int8_startup(
dtype=dtype,
enforce_eager=True,
quantization="experts_int8",
allow_deprecated_quantization=True,
) as vllm_model:
vllm_model.generate_greedy(example_prompts, max_tokens)
1 change: 0 additions & 1 deletion vllm/model_executor/layers/quantization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"tpu_int8",
"fbgemm_fp8",
"fp_quant",
"experts_int8",
"petit_nvfp4",
]

Expand Down
159 changes: 54 additions & 105 deletions vllm/model_executor/layers/quantization/experts_int8.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
from typing import Any

import torch
from torch.nn import Module

from vllm.distributed import get_tensor_model_parallel_rank, get_tp_group
from vllm.model_executor.layers.fused_moe import (
FusedMoE,
FusedMoEConfig,
FusedMoEMethodBase,
)
from vllm.model_executor.layers.fused_moe.config import (
FusedMoEQuantConfig,
Expand All @@ -21,11 +20,17 @@
QuantizationConfig,
QuantizeMethodBase,
)
from vllm.model_executor.utils import set_weight_attrs
from vllm.model_executor.layers.quantization.online_moe import (
OnlineMoEMethodBase,
)


class ExpertsInt8Config(QuantizationConfig):
"""Config class for Int8 experts quantization."""
"""Config for online int8 MoE expert quantization.

MoE expert weights are loaded in full precision and quantized to int8 with
per-row scales during model loading. Linear layers are left unquantized.
"""

def __init__(self) -> None:
super().__init__()
Expand Down Expand Up @@ -60,78 +65,67 @@ def get_quant_method(
return None


class ExpertsInt8MoEMethod(FusedMoEMethodBase):
class ExpertsInt8MoEMethod(OnlineMoEMethodBase):
"""Online int8 MoE quantization method.

Inherits meta-device weight creation and the
``process_weights_after_loading`` guard from :class:`OnlineMoEMethodBase`.
Implements ``_quantize_weights`` for per-row int8 quantization.
"""

def __init__(
self,
quant_config: ExpertsInt8Config,
quant_config: QuantizationConfig,
moe: FusedMoEConfig,
):
super().__init__(moe)
self.quant_config = quant_config

def create_weights(
self,
layer: torch.nn.Module,
num_experts: int,
hidden_size: int,
intermediate_size_per_partition: int,
params_dtype: torch.dtype,
**extra_weight_attrs,
):
int8_dtype = torch.int8
def _quantize_weights(self, layer: Module) -> None:
vmax = torch.iinfo(torch.int8).max

assert "weight_loader" in extra_weight_attrs
weight_loader = extra_weight_attrs["weight_loader"]
wrapped_weight_loader = ExpertsInt8MoEMethod.quantizing_weight_loader(
layer, weight_loader
)
extra_weight_attrs["weight_loader"] = wrapped_weight_loader

# Fused gate_up_proj (column parallel)
w13_weight = torch.nn.Parameter(
torch.empty(
num_experts,
2 * intermediate_size_per_partition,
hidden_size,
dtype=int8_dtype,
),
requires_grad=False,
)
layer.register_parameter("w13_weight", w13_weight)
set_weight_attrs(w13_weight, extra_weight_attrs)

# down_proj (row parallel)
w2_weight = torch.nn.Parameter(
torch.empty(
num_experts,
hidden_size,
intermediate_size_per_partition,
dtype=int8_dtype,
),
requires_grad=False,
w13 = torch.empty_like(layer.w13_weight, dtype=torch.int8)
w2 = torch.empty_like(layer.w2_weight, dtype=torch.int8)
w13_scale = torch.zeros(
layer.num_experts,
layer.w13_weight.shape[1],
dtype=torch.float32,
)
layer.register_parameter("w2_weight", w2_weight)
set_weight_attrs(w2_weight, extra_weight_attrs)

w13_scale = torch.nn.Parameter(
torch.zeros(
num_experts, 2 * intermediate_size_per_partition, dtype=torch.float32
),
requires_grad=False,
w2_scale = torch.zeros(
layer.num_experts,
layer.w2_weight.shape[1],
dtype=torch.float32,
)
Comment thread
Josephasafg marked this conversation as resolved.
Outdated
layer.register_parameter("w13_scale", w13_scale)

w2_scale = torch.nn.Parameter(
torch.zeros(num_experts, hidden_size, dtype=torch.float32),
requires_grad=False,
)
layer.register_parameter("w2_scale", w2_scale)
for expert in range(layer.local_num_experts):
# w13: per-row quantization over hidden_size dim
w = layer.w13_weight[expert, :, :]
scales = w.abs().amax(dim=1) / vmax
q = w.div(scales.unsqueeze(1)).round().clamp(-vmax, vmax)
Comment thread
Josephasafg marked this conversation as resolved.
Outdated
w13[expert, :, :] = q.to(torch.int8)
w13_scale[expert, :] = scales

# w2: per-row quantization over intermediate_size dim
w = layer.w2_weight[expert, :, :]
scales = w.abs().amax(dim=1) / vmax
q = w.div(scales.unsqueeze(1)).round().clamp(-vmax, vmax)
Comment thread
Josephasafg marked this conversation as resolved.
Outdated
w2[expert, :, :] = q.to(torch.int8)
w2_scale[expert, :] = scales

# Replace full-precision weights with quantized versions
layer.w13_weight = torch.nn.Parameter(w13, requires_grad=False)
layer.w2_weight = torch.nn.Parameter(w2, requires_grad=False)
layer.w13_scale = torch.nn.Parameter(w13_scale, requires_grad=False)
layer.w2_scale = torch.nn.Parameter(w2_scale, requires_grad=False)

def get_fused_moe_quant_config(
self, layer: torch.nn.Module
) -> FusedMoEQuantConfig | None:
return int8_w8a16_moe_quant_config(
w1_scale=layer.w13_scale, w2_scale=layer.w2_scale, w1_zp=None, w2_zp=None
w1_scale=layer.w13_scale,
w2_scale=layer.w2_scale,
w1_zp=None,
w2_zp=None,
)

def apply(
Expand All @@ -157,48 +151,3 @@ def apply(
expert_map=layer.expert_map,
quant_config=self.moe_quant_config,
)

@staticmethod
def quantizing_weight_loader(layer, weight_loader):
def quantize_and_call_weight_loader(
param: torch.nn.Parameter,
loaded_weight: torch.Tensor,
weight_name: str,
shard_id: int,
expert_id: int,
):
tp_rank = get_tensor_model_parallel_rank()
shard_size = layer.intermediate_size_per_partition
shard = slice(tp_rank * shard_size, (tp_rank + 1) * shard_size)
device = get_tp_group().device
loaded_weight = loaded_weight.to(device)
# w1, gate_proj case: Load into first shard of w13.
if shard_id == "w1":
scales = quantize_in_place_and_get_scales(loaded_weight[shard, :])
layer.w13_scale.data[expert_id, 0:shard_size].copy_(scales[:, 0])
# w3, up_proj case: Load into second shard of w13.
elif shard_id == "w3":
scales = quantize_in_place_and_get_scales(loaded_weight[shard, :])
layer.w13_scale.data[expert_id, shard_size : 2 * shard_size].copy_(
scales[:, 0]
)
# w2, down_proj case: Load into only shard of w2.
elif shard_id == "w2":
scales = quantize_in_place_and_get_scales(loaded_weight[:, shard])
layer.w2_scale.data[expert_id, :].copy_(scales[:, 0])
else:
raise ValueError(f"Shard id must be in [0,1,2] but got {shard_id}")
weight_loader(param, loaded_weight, weight_name, shard_id, expert_id)

return quantize_and_call_weight_loader


def quantize_in_place_and_get_scales(weight: torch.Tensor) -> torch.Tensor:
vmax = torch.iinfo(torch.int8).max
scales = torch.max(torch.abs(weight), dim=1, keepdim=True)[0] / vmax

weight.div_(scales)
weight.round_()
weight.clamp_(-vmax, vmax)

return scales
Loading
Loading