Skip to content
Merged
Changes from 7 commits
Commits
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
165 changes: 98 additions & 67 deletions python/sglang/srt/layers/quantization/modelopt_quant.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ class ActivationType(IntEnum):
logger = logging.getLogger(__name__)


def _copy_or_rebind_param(
Comment thread
Fridge003 marked this conversation as resolved.
Outdated
module: torch.nn.Module, name: str, new_value: torch.Tensor
) -> None:
"""Keep parameter identities stable for CUDA graph reuse and hot reload."""
new_value = new_value.detach()
param = getattr(module, name, None)
if isinstance(param, Parameter):
if param.data.shape == new_value.shape and param.data.dtype == new_value.dtype:
param.data.copy_(new_value)
else:
param.data = new_value
param.requires_grad_(False)
else:
setattr(module, name, Parameter(new_value, requires_grad=False))


def _sglang_fp4_gemm_fake(
input: torch.Tensor,
weight: torch.Tensor,
Expand Down Expand Up @@ -1154,13 +1170,13 @@ def create_weights(
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
input_scale_2 = layer.input_scale.max().to(torch.float32)
weight_scale_2 = layer.weight_scale_2.max().to(torch.float32)
layer.input_scale = Parameter(input_scale_2, requires_grad=False)
layer.weight_scale_2 = Parameter(weight_scale_2, requires_grad=False)
layer.alpha = Parameter(
layer.input_scale * layer.weight_scale_2, requires_grad=False

# Keep per-shard scales intact for hot reload; derive scalar params below.
_copy_or_rebind_param(
layer, "alpha", (input_scale_2 * weight_scale_2).to(torch.float32)
)
layer.input_scale_inv = Parameter(
(1 / input_scale_2).to(torch.float32), requires_grad=False
_copy_or_rebind_param(
layer, "input_scale_inv", (1 / input_scale_2).to(torch.float32)
)
if get_fp4_gemm_runner_backend().is_flashinfer_trtllm():
# FlashInfer TRTLLM FP4 GEMM requires a different weight layout.
Expand All @@ -1179,8 +1195,8 @@ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
.view(torch.float8_e4m3fn)
)

layer.weight_scale_interleaved = Parameter(scale, requires_grad=False)
layer.weight = Parameter(weight, requires_grad=False)
_copy_or_rebind_param(layer, "weight_scale_interleaved", scale)
_copy_or_rebind_param(layer, "weight", weight)
return
# Pad and blockwise interleave weight_scale
scales = layer.weight_scale
Expand All @@ -1205,7 +1221,7 @@ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
if scale_ndim == 2
else padded_scales.reshape(B, M_padded, K_padded)
)
layer.weight_scale_interleaved = Parameter(padded_scales, requires_grad=False)
_copy_or_rebind_param(layer, "weight_scale_interleaved", padded_scales)

def apply(
self,
Expand Down Expand Up @@ -1419,19 +1435,22 @@ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:

# GEMM 1 scale processing
if layer.moe_runner_config.is_gated:
if not torch.allclose(
layer.w13_weight_scale_2[:, 0], layer.w13_weight_scale_2[:, 1]
):
logger.warning_once(
"w1_weight_scale_2 must match w3_weight_scale_2. "
"Accuracy may be affected."
)
if layer.w13_weight_scale_2.dim() == 1:
# Some checkpoints store a shared scale for w1/w3.
w13_weight_scale_2 = layer.w13_weight_scale_2
else:
if layer.w13_weight_scale_2.shape[1] >= 2 and not torch.allclose(
layer.w13_weight_scale_2[:, 0],
layer.w13_weight_scale_2[:, 1],
):
logger.warning_once(
"w1_weight_scale_2 must match w3_weight_scale_2. "
"Accuracy may be affected."
)

w13_weight_scale_2 = layer.w13_weight_scale_2[:, 0]
w13_weight_scale_2 = layer.w13_weight_scale_2[:, 0]
else:
w13_weight_scale_2 = layer.w13_weight_scale_2[:]
layer.w13_weight_scale_2 = Parameter(w13_weight_scale_2, requires_grad=False)

# Calculate input scales based on strategy
if self.enable_flashinfer_cutlass_moe or self.enable_flashinfer_trtllm_moe:
w13_input_scale = layer.w13_input_scale.max().to(torch.float32)
Expand Down Expand Up @@ -1472,19 +1491,25 @@ def _slice_scale(w):
w2_input_scale = layer.w2_input_scale

# Create shared parameters
layer.g1_alphas = Parameter(
_copy_or_rebind_param(
layer,
"g1_alphas",
(w13_input_scale * w13_weight_scale_2).to(torch.float32),
requires_grad=False,
)
layer.g2_alphas = Parameter(
_copy_or_rebind_param(
layer,
"g2_alphas",
(w2_input_scale * layer.w2_weight_scale_2).to(torch.float32),
requires_grad=False,
)
layer.w13_input_scale_quant = Parameter(
(1 / w13_input_scale).to(torch.float32), requires_grad=False
_copy_or_rebind_param(
layer,
"w13_input_scale_quant",
(1 / w13_input_scale).to(torch.float32),
)
layer.w2_input_scale_quant = Parameter(
(1 / w2_input_scale).to(torch.float32), requires_grad=False
_copy_or_rebind_param(
layer,
"w2_input_scale_quant",
(1 / w2_input_scale).to(torch.float32),
)

# TODO: for flashinfer always do MOE_NVFP4_DISPATCH
Expand Down Expand Up @@ -1549,40 +1574,36 @@ def _slice_scale(w):
)

# Set flashinfer parameters
layer.gemm1_weights_fp4_shuffled = Parameter(
gemm1_weights_fp4_shuffled, requires_grad=False
_copy_or_rebind_param(
layer, "gemm1_weights_fp4_shuffled", gemm1_weights_fp4_shuffled
)
layer.gemm2_weights_fp4_shuffled = Parameter(
gemm2_weights_fp4_shuffled, requires_grad=False
_copy_or_rebind_param(
layer, "gemm2_weights_fp4_shuffled", gemm2_weights_fp4_shuffled
)
layer.gemm1_scales_fp4_shuffled = Parameter(
gemm1_scales_fp4_shuffled, requires_grad=False
_copy_or_rebind_param(
layer, "gemm1_scales_fp4_shuffled", gemm1_scales_fp4_shuffled
)
layer.gemm2_scales_fp4_shuffled = Parameter(
gemm2_scales_fp4_shuffled, requires_grad=False
_copy_or_rebind_param(
layer, "gemm2_scales_fp4_shuffled", gemm2_scales_fp4_shuffled
)

# Additional parameter needed for TRT-LLM
layer.g1_scale_c = Parameter(
_copy_or_rebind_param(
layer,
"g1_scale_c",
(layer.w2_input_scale_quant * layer.g1_alphas).to(torch.float32),
requires_grad=False,
)

# Clean up weights that won't be used by TRT-LLM
del (
layer.w2_weight,
layer.w2_weight_scale,
layer.w13_weight,
layer.w13_weight_scale,
)
# Keep original weights/scales to support update_weights_from_disk.

else:
# CUTLASS processing - handle w13 and w2 separately

# Process w13 weights
w13_blockscale_swizzled = swizzle_blockscale(layer.w13_weight_scale)
del layer.w13_weight_scale
layer.w13_blockscale_swizzled.data.copy_(w13_blockscale_swizzled)
_copy_or_rebind_param(
layer, "w13_blockscale_swizzled", w13_blockscale_swizzled
)

w13_weight = layer.w13_weight
intermediate_size_pad = w13_blockscale_swizzled.size(1) - w13_weight.size(1)
Expand All @@ -1594,46 +1615,56 @@ def _slice_scale(w):
"but padding is also implemented for gated activations"
)

layer.w13_weight = Parameter(
_copy_or_rebind_param(
layer,
"w13_weight",
torch.nn.functional.pad(
w13_weight, (0, 0, 0, intermediate_size_pad)
),
requires_grad=False,
)
layer.w2_weight = Parameter(
_copy_or_rebind_param(
layer,
"w2_weight",
torch.nn.functional.pad(
layer.w2_weight, (0, intermediate_size_pad // 2, 0, 0)
),
requires_grad=False,
)
layer.w2_weight_scale = Parameter(
_copy_or_rebind_param(
layer,
"w2_weight_scale",
torch.nn.functional.pad(
layer.w2_weight_scale, (0, intermediate_size_pad // 16)
),
requires_grad=False,
)
layer.w2_blockscale_swizzled = Parameter(
swizzle_blockscale(layer.w2_weight_scale), requires_grad=False
)

layer.w13_weight = Parameter(layer.w13_weight.data, requires_grad=False)

# Process w2 weights
w2_blockscale_swizzled = swizzle_blockscale(layer.w2_weight_scale)
del layer.w2_weight_scale
layer.w2_blockscale_swizzled.data.copy_(w2_blockscale_swizzled)
_copy_or_rebind_param(
layer, "w2_blockscale_swizzled", w2_blockscale_swizzled
)

# Both flashinfer cutlass and regular cutlass use same processing for w2

# Set up CUTLASS MoE parameters
# Set up CUTLASS MoE parameters (reuse to keep CUDA graph stable)
Comment thread
Fridge003 marked this conversation as resolved.
device = layer.w13_weight.device
layer.cutlass_moe_params = CutlassMoEParams(
CutlassMoEType.BlockscaledFP4,
device,
num_experts=layer.num_experts, # global num experts
intermediate_size_per_partition=layer.w2_weight.shape[2] * 2, # n
hidden_size=layer.w13_weight.shape[2] * 2,
) # k
inter_size = layer.w2_weight.shape[2] * 2
hidden_size = layer.w13_weight.shape[2] * 2
existing_params = getattr(layer, "cutlass_moe_params", None)
if (
existing_params is None
or existing_params.cutlass_moe_type != CutlassMoEType.BlockscaledFP4
or existing_params.num_experts != layer.num_experts
or existing_params.intermediate_size_per_partition != inter_size
or existing_params.hidden_size != hidden_size
or existing_params.device != device
):
layer.cutlass_moe_params = CutlassMoEParams(
CutlassMoEType.BlockscaledFP4,
device,
num_experts=layer.num_experts, # global num experts
intermediate_size_per_partition=inter_size, # n
hidden_size=hidden_size,
) # k

@property
def load_up_proj_weight_first(self) -> bool:
Expand Down
Loading