B300 NVFP4 port: advance layers/quantization/utils.py to HEAD (piecemeal-gap fix)
Ported flashinfer_trtllm.py (Stage 1) calls prepare_static_weights_for_trtllm_fp4_moe(is_gated=...), but utils.py (which DEFINES it) was not ported → old signature → TypeError at process_weights. utils.py has 0 local edits; wholesale-replace to HEAD. New fn is gated-aware (gemm1_rows), passes is_gated_act_gemm to flashinfer _maybe_get_cached_w3_w1_permute_indices (verified present on 0.6.12). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/quantization/utils/quant_utils.py
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -43,6 +45,28 @@ def get_scalar_types():
|
||||
ScalarType, scalar_types = get_scalar_types()
|
||||
|
||||
|
||||
def _module_path_match(ignored: str, prefix: str) -> bool:
|
||||
# Match on dotted module-path boundaries so that `mlp.gate` does NOT
|
||||
# match `mlp.gate_up_proj`. Needed for quant configs (e.g. Qwen3.6-FP8)
|
||||
# whose `modules_to_not_convert` lists MoE-template names like `mlp.gate`
|
||||
# that collide with fused dense MLP names by plain substring.
|
||||
ignored = ignored.rstrip(".")
|
||||
prefix = prefix.rstrip(".")
|
||||
if ignored == prefix:
|
||||
return True
|
||||
if prefix.startswith(ignored + "."):
|
||||
return True
|
||||
return ("." + ignored + ".") in ("." + prefix + ".")
|
||||
|
||||
|
||||
# Known fused-linear -> shard names. Used as a fallback when the quant
|
||||
# config doesn't ship packed_modules_mapping (typical for HF FP8 configs).
|
||||
_FALLBACK_FUSED_SHARDS: Mapping[str, List[str]] = {
|
||||
"qkv_proj": ["q_proj", "k_proj", "v_proj"],
|
||||
"gate_up_proj": ["gate_proj", "up_proj"],
|
||||
}
|
||||
|
||||
|
||||
def is_layer_skipped(
|
||||
prefix: str,
|
||||
ignored_layers: List[str],
|
||||
@@ -56,16 +80,19 @@ def is_layer_skipped(
|
||||
# in the safetensors checkpoint. So, we convert the name
|
||||
# from the fused version to unfused + check to make sure that
|
||||
# each shard of the fused layer has the same scheme.
|
||||
if proj_name in fused_mapping:
|
||||
effective_fused = (
|
||||
fused_mapping if proj_name in fused_mapping else _FALLBACK_FUSED_SHARDS
|
||||
)
|
||||
if proj_name in effective_fused:
|
||||
shard_prefixes = [
|
||||
prefix.replace(proj_name, shard_proj_name)
|
||||
for shard_proj_name in fused_mapping[proj_name]
|
||||
for shard_proj_name in effective_fused[proj_name]
|
||||
]
|
||||
|
||||
is_skipped = None
|
||||
for shard_prefix in shard_prefixes:
|
||||
is_shard_skipped = any(
|
||||
ignored in shard_prefix for ignored in ignored_layers
|
||||
_module_path_match(ignored, shard_prefix) for ignored in ignored_layers
|
||||
)
|
||||
|
||||
if is_skipped is None:
|
||||
@@ -77,7 +104,9 @@ def is_layer_skipped(
|
||||
"to have the same precision."
|
||||
)
|
||||
else:
|
||||
is_skipped = any(ignored in prefix for ignored in ignored_layers)
|
||||
is_skipped = any(
|
||||
_module_path_match(ignored, prefix) for ignored in ignored_layers
|
||||
)
|
||||
if "gate_up_proj" in prefix:
|
||||
prefix_gate = prefix.replace("gate_up_proj", "gate_proj")
|
||||
prefix_up = prefix.replace("gate_up_proj", "up_proj")
|
||||
@@ -625,6 +654,7 @@ def prepare_static_weights_for_trtllm_fp4_moe(
|
||||
hidden_size,
|
||||
intermediate_size,
|
||||
num_experts,
|
||||
is_gated: bool = True,
|
||||
):
|
||||
from flashinfer import nvfp4_block_scale_interleave
|
||||
from flashinfer.fused_moe.core import (
|
||||
@@ -636,14 +666,16 @@ def prepare_static_weights_for_trtllm_fp4_moe(
|
||||
_cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
|
||||
epilogue_tile_m = 128 # FIXME: this depends on the kernel internals
|
||||
|
||||
gemm1_rows = (2 if is_gated else 1) * intermediate_size
|
||||
|
||||
# Convert quantized weights to proper formats
|
||||
gemm1_weights_fp4 = gemm1_weights.view(torch.float8_e4m3fn).reshape(
|
||||
num_experts, 2 * intermediate_size, hidden_size // 2
|
||||
num_experts, gemm1_rows, hidden_size // 2
|
||||
) # packed fp4
|
||||
gemm1_scales_linear_fp4 = gemm1_scales_linear_fp4_bytes.view(
|
||||
torch.float8_e4m3fn
|
||||
).reshape(
|
||||
num_experts, 2 * intermediate_size, hidden_size // 16
|
||||
num_experts, gemm1_rows, hidden_size // 16
|
||||
) # fp8 scaling factors
|
||||
|
||||
gemm2_weights_fp4 = gemm2_weights.view(torch.float8_e4m3fn).reshape(
|
||||
@@ -655,52 +687,66 @@ def prepare_static_weights_for_trtllm_fp4_moe(
|
||||
num_experts, hidden_size, intermediate_size // 16
|
||||
) # fp8 scaling factors
|
||||
|
||||
gemm1_weights_fp4_shuffled = []
|
||||
gemm1_scales_fp4_shuffled = []
|
||||
gemm2_weights_fp4_shuffled = []
|
||||
gemm2_scales_fp4_shuffled = []
|
||||
# Pre-allocate output tensors so per-expert shuffles write directly into
|
||||
# contiguous slices instead of building lists + torch.stack(). This avoids
|
||||
# O(num_experts) transient GPU allocations whose freed blocks fragment the
|
||||
# CUDA address space
|
||||
gemm1_weights_fp4_shuffled = torch.empty_like(gemm1_weights_fp4.view(torch.uint8))
|
||||
gemm2_weights_fp4_shuffled = torch.empty_like(gemm2_weights_fp4.view(torch.uint8))
|
||||
|
||||
# Pre-allocate scale output tensors and a reusable scratch buffer for
|
||||
# the permuted input to nvfp4_block_scale_interleave.
|
||||
# nvfp4_block_scale_interleave flattens its input to 1-D, so the
|
||||
# per-expert output size equals the per-expert input numel.
|
||||
def _alloc_scale_buffers(scales):
|
||||
per_expert_shape = scales[0].view(torch.uint8).shape
|
||||
per_expert_numel = scales[0].numel()
|
||||
output = scales.new_empty((num_experts, per_expert_numel), dtype=torch.uint8)
|
||||
scratch = torch.empty(per_expert_shape, dtype=torch.uint8, device=scales.device)
|
||||
return output, scratch
|
||||
|
||||
gemm1_scales_fp4_shuffled, g1s_scratch = _alloc_scale_buffers(
|
||||
gemm1_scales_linear_fp4
|
||||
)
|
||||
gemm2_scales_fp4_shuffled, g2s_scratch = _alloc_scale_buffers(
|
||||
gemm2_scales_linear_fp4
|
||||
)
|
||||
|
||||
for i in range(num_experts):
|
||||
# Calculate the permute indices for the following:
|
||||
# 1. Reorder rows of W1 and scales for fused gated activation
|
||||
# 2. Shuffle weights and scaling factors for transposed mma output
|
||||
# for both w3_w1 and w2 weights and scale factors
|
||||
permute_indices = _maybe_get_cached_w3_w1_permute_indices(
|
||||
_cache_permute_indices,
|
||||
gemm1_weights_fp4[i].view(torch.uint8),
|
||||
epilogue_tile_m,
|
||||
is_gated_act_gemm=is_gated,
|
||||
)
|
||||
gemm1_weights_fp4_shuffled.append(
|
||||
gemm1_weights_fp4[i]
|
||||
.view(torch.uint8)[permute_indices.to(gemm1_weights_fp4.device)]
|
||||
.contiguous()
|
||||
)
|
||||
gemm1_weights_fp4_shuffled[i] = gemm1_weights_fp4[i].view(torch.uint8)[
|
||||
permute_indices.to(gemm1_weights_fp4.device)
|
||||
]
|
||||
|
||||
permute_sf_indices = _maybe_get_cached_w3_w1_permute_indices(
|
||||
_cache_permute_indices,
|
||||
gemm1_scales_linear_fp4[i].view(torch.uint8),
|
||||
epilogue_tile_m,
|
||||
num_elts_per_sf=16,
|
||||
is_gated_act_gemm=is_gated,
|
||||
)
|
||||
gemm1_scales_fp4_shuffled.append(
|
||||
nvfp4_block_scale_interleave(
|
||||
gemm1_scales_linear_fp4[i]
|
||||
.view(torch.uint8)[
|
||||
permute_sf_indices.to(gemm1_scales_linear_fp4.device)
|
||||
]
|
||||
.contiguous()
|
||||
)
|
||||
# Reuse scratch buffer for the permuted scale input
|
||||
torch.index_select(
|
||||
gemm1_scales_linear_fp4[i].view(torch.uint8),
|
||||
0,
|
||||
permute_sf_indices.to(gemm1_scales_linear_fp4.device),
|
||||
out=g1s_scratch,
|
||||
)
|
||||
gemm1_scales_fp4_shuffled[i] = nvfp4_block_scale_interleave(g1s_scratch)
|
||||
|
||||
permute_indices = get_w2_permute_indices_with_cache(
|
||||
_cache_permute_indices,
|
||||
gemm2_weights_fp4[i].view(torch.uint8),
|
||||
epilogue_tile_m,
|
||||
)
|
||||
gemm2_weights_fp4_shuffled.append(
|
||||
gemm2_weights_fp4[i]
|
||||
.view(torch.uint8)[permute_indices.to(gemm2_weights_fp4.device)]
|
||||
.contiguous()
|
||||
)
|
||||
gemm2_weights_fp4_shuffled[i] = gemm2_weights_fp4[i].view(torch.uint8)[
|
||||
permute_indices.to(gemm2_weights_fp4.device)
|
||||
]
|
||||
|
||||
permute_sf_indices = get_w2_permute_indices_with_cache(
|
||||
_cache_permute_indices,
|
||||
@@ -708,30 +754,24 @@ def prepare_static_weights_for_trtllm_fp4_moe(
|
||||
epilogue_tile_m,
|
||||
num_elts_per_sf=16,
|
||||
)
|
||||
gemm2_scales_fp4_shuffled.append(
|
||||
nvfp4_block_scale_interleave(
|
||||
gemm2_scales_linear_fp4[i]
|
||||
.view(torch.uint8)[
|
||||
permute_sf_indices.to(gemm2_scales_linear_fp4.device)
|
||||
]
|
||||
.contiguous()
|
||||
)
|
||||
torch.index_select(
|
||||
gemm2_scales_linear_fp4[i].view(torch.uint8),
|
||||
0,
|
||||
permute_sf_indices.to(gemm2_scales_linear_fp4.device),
|
||||
out=g2s_scratch,
|
||||
)
|
||||
gemm2_scales_fp4_shuffled[i] = nvfp4_block_scale_interleave(g2s_scratch)
|
||||
|
||||
# Stack weights for all experts
|
||||
gemm1_weights_fp4_shuffled = torch.stack(gemm1_weights_fp4_shuffled)
|
||||
gemm1_scales_fp4_shuffled = (
|
||||
torch.stack(gemm1_scales_fp4_shuffled)
|
||||
.view(torch.float8_e4m3fn)
|
||||
.reshape(num_experts, 2 * intermediate_size, hidden_size // 16)
|
||||
)
|
||||
del g1s_scratch, g2s_scratch
|
||||
|
||||
gemm2_weights_fp4_shuffled = torch.stack(gemm2_weights_fp4_shuffled)
|
||||
gemm2_scales_fp4_shuffled = (
|
||||
torch.stack(gemm2_scales_fp4_shuffled)
|
||||
.view(torch.float8_e4m3fn)
|
||||
.reshape(num_experts, hidden_size, intermediate_size // 16)
|
||||
)
|
||||
# Weight outputs stay as uint8 (FP4 packed) — the TRTLLM kernel expects this.
|
||||
gemm1_scales_fp4_shuffled = gemm1_scales_fp4_shuffled.view(
|
||||
torch.float8_e4m3fn
|
||||
).reshape(num_experts, gemm1_rows, hidden_size // 16)
|
||||
|
||||
gemm2_scales_fp4_shuffled = gemm2_scales_fp4_shuffled.view(
|
||||
torch.float8_e4m3fn
|
||||
).reshape(num_experts, hidden_size, intermediate_size // 16)
|
||||
return (
|
||||
gemm1_weights_fp4_shuffled,
|
||||
gemm1_scales_fp4_shuffled,
|
||||
|
||||
Reference in New Issue
Block a user