chore: import upstream snapshot with attribution
Book-CI / test (macos-latest) (push) Has been cancelled
Book-CI / test (ubuntu-latest) (push) Has been cancelled
Book-CI / test (windows-latest) (push) Has been cancelled
Release Fake Tag / publish (push) Has been cancelled
Deploy / deploy (macos-latest) (push) Has been cancelled
Deploy / deploy (ubuntu-latest) (push) Has been cancelled
Deploy / deploy (windows-latest) (push) Has been cancelled
Release to PyPI / Build & publish sglang-kt (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.11) (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.12) (push) Has been cancelled
Release to PyPI / Publish kt-kernel to PyPI (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:30:03 +08:00
commit ec436095dd
1232 changed files with 404407 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Utilities for kt_kernel package.
"""
from .amx import AMXMoEWrapper, NativeMoEWrapper
from .llamafile import LlamafileMoEWrapper
from .loader import SafeTensorLoader, GGUFLoader, CompressedSafeTensorLoader
__all__ = [
"AMXMoEWrapper",
"NativeMoEWrapper",
"LlamafileMoEWrapper",
"SafeTensorLoader",
"CompressedSafeTensorLoader",
"GGUFLoader",
]
+965
View File
@@ -0,0 +1,965 @@
import gc
import logging
import os
import torch
import ctypes
from typing import List, Optional
logger = logging.getLogger(__name__)
# Use relative imports for package structure
from ..experts_base import BaseMoEWrapper
from .loader import (
SafeTensorLoader,
CompressedSafeTensorLoader,
FP8SafeTensorLoader,
BF16SafeTensorLoader,
GPTQSafeTensorLoader,
MXFP4SafeTensorLoader,
MXFP8SafeTensorLoader,
)
from kt_kernel_ext.moe import MOEConfig
import kt_kernel_ext.moe as _moe_mod
AMXInt4_MOE = getattr(_moe_mod, "AMXInt4_MOE", None)
AMXInt8_MOE = getattr(_moe_mod, "AMXInt8_MOE", None)
AMXInt4_KGroup_MOE = getattr(_moe_mod, "AMXInt4_KGroup_MOE", None)
AMXFP4_KGroup_MOE = getattr(_moe_mod, "AMXFP4_KGroup_MOE", None)
AMXMXFP8_KGroup_MOE = getattr(_moe_mod, "AMXMXFP8_KGroup_MOE", None)
AMXFP8_MOE = getattr(_moe_mod, "AMXFP8_MOE", None)
AMXBF16_MOE = getattr(_moe_mod, "AMXBF16_MOE", None)
AMXFP8PerChannel_MOE = getattr(_moe_mod, "AMXFP8PerChannel_MOE", None)
AVX2BF16_MOE = getattr(_moe_mod, "AVX2BF16_MOE", None)
AVX2FP8_MOE = getattr(_moe_mod, "AVX2FP8_MOE", None)
AVX2GPTQInt4_MOE = getattr(_moe_mod, "AVX2GPTQInt4_MOE", None)
AVX2RawInt4_MOE = getattr(_moe_mod, "AVX2RawInt4_MOE", None)
AVX2MXFP4_MOE = getattr(_moe_mod, "AVX2MXFP4_MOE", None)
AVX2MXFP8_MOE = getattr(_moe_mod, "AVX2MXFP8_MOE", None)
AVXVNNI256GPTQInt4_MOE = getattr(_moe_mod, "AVXVNNI256GPTQInt4_MOE", None)
AVXVNNI256RawInt4_MOE = getattr(_moe_mod, "AVXVNNI256RawInt4_MOE", None)
_HAS_AMXINT4_SUPPORT = AMXInt4_MOE is not None
_HAS_AMXINT8_SUPPORT = AMXInt8_MOE is not None
_HAS_RAWINT4_SUPPORT = AMXInt4_KGroup_MOE is not None
_HAS_MXFP4_SUPPORT = AMXFP4_KGroup_MOE is not None
_HAS_MXFP8_SUPPORT = AMXMXFP8_KGroup_MOE is not None
_HAS_FP8_SUPPORT = AMXFP8_MOE is not None
_HAS_BF16_SUPPORT = AMXBF16_MOE is not None
_HAS_FP8_PERCHANNEL_SUPPORT = AMXFP8PerChannel_MOE is not None
_HAS_AVX2_BF16_SUPPORT = AVX2BF16_MOE is not None
_HAS_AVX2_FP8_SUPPORT = AVX2FP8_MOE is not None
_HAS_AVX2_GPTQ_INT4_SUPPORT = AVX2GPTQInt4_MOE is not None
_HAS_AVX2_RAWINT4_SUPPORT = AVX2RawInt4_MOE is not None
_HAS_AVX2_MXFP4_SUPPORT = AVX2MXFP4_MOE is not None
_HAS_AVX2_MXFP8_SUPPORT = AVX2MXFP8_MOE is not None
_HAS_AVXVNNI256_GPTQ_INT4_SUPPORT = AVXVNNI256GPTQInt4_MOE is not None
_HAS_AVXVNNI256_RAW_INT4_SUPPORT = AVXVNNI256RawInt4_MOE is not None
_AVXVNNI256_GPTQ_INT4_MAX_GROUP_SIZE = 256
_AVXVNNI256_RAW_INT4_MAX_GROUP_SIZE = 256
def _host_has_cpu_flag(*flag_names: str) -> bool:
try:
with open("/proc/cpuinfo", "r") as f:
for line in f:
if line.startswith("flags"):
flags = set(line.split(":", 1)[1].strip().split())
return any(name in flags for name in flag_names)
except OSError:
return False
return False
_HOST_HAS_AVX_VNNI = _host_has_cpu_flag("avx_vnni", "avxvnni")
def _supports_avxvnni256_gptq_int4_group_size(group_size: Optional[int]) -> bool:
if group_size is None:
return True
return group_size > 0 and group_size % 32 == 0 and group_size <= _AVXVNNI256_GPTQ_INT4_MAX_GROUP_SIZE
def _supports_avxvnni256_rawint4_group_size(group_size: Optional[int]) -> bool:
if group_size is None:
return True
return group_size > 0 and group_size % 32 == 0 and group_size <= _AVXVNNI256_RAW_INT4_MAX_GROUP_SIZE
def _select_gptq_int4_backend(group_size: Optional[int] = None):
forced = os.getenv("KT_GPTQ_INT4_BACKEND", "").strip().lower()
avxvnni_group_supported = _supports_avxvnni256_gptq_int4_group_size(group_size)
if forced in {"avxvnni", "avxvnni256"}:
if not _HAS_AVXVNNI256_GPTQ_INT4_SUPPORT:
raise RuntimeError("KT_GPTQ_INT4_BACKEND=avxvnni requested, but AVXVNNI256GPTQInt4_MOE is not compiled in.")
if not _HOST_HAS_AVX_VNNI:
raise RuntimeError("KT_GPTQ_INT4_BACKEND=avxvnni requested, but the current CPU does not support avx_vnni.")
if not avxvnni_group_supported:
raise RuntimeError(
"KT_GPTQ_INT4_BACKEND=avxvnni requested, but "
f"group_size={group_size} is unsupported. AVX-VNNI-256 GPTQ_INT4 only supports "
f"positive multiples of 32 up to {_AVXVNNI256_GPTQ_INT4_MAX_GROUP_SIZE}."
)
return AVXVNNI256GPTQInt4_MOE
if forced == "avx2":
if not _HAS_AVX2_GPTQ_INT4_SUPPORT:
raise RuntimeError("KT_GPTQ_INT4_BACKEND=avx2 requested, but AVX2GPTQInt4_MOE is not compiled in.")
return AVX2GPTQInt4_MOE
if _HAS_AVXVNNI256_GPTQ_INT4_SUPPORT and _HOST_HAS_AVX_VNNI and avxvnni_group_supported:
return AVXVNNI256GPTQInt4_MOE
if _HAS_AVX2_GPTQ_INT4_SUPPORT:
return AVX2GPTQInt4_MOE
return None
def _select_rawint4_backend(group_size: Optional[int] = None):
forced = os.getenv("KT_RAWINT4_BACKEND", "").strip().lower()
avxvnni_group_supported = _supports_avxvnni256_rawint4_group_size(group_size)
if forced == "amx":
if not _HAS_RAWINT4_SUPPORT:
raise RuntimeError("KT_RAWINT4_BACKEND=amx requested, but AMXInt4_KGroup_MOE is not compiled in.")
return AMXInt4_KGroup_MOE
if forced in {"avxvnni", "avxvnni256"}:
if not _HAS_AVXVNNI256_RAW_INT4_SUPPORT:
raise RuntimeError("KT_RAWINT4_BACKEND=avxvnni requested, but AVXVNNI256RawInt4_MOE is not compiled in.")
if not _HOST_HAS_AVX_VNNI:
raise RuntimeError("KT_RAWINT4_BACKEND=avxvnni requested, but the current CPU does not support avx_vnni.")
if not avxvnni_group_supported:
raise RuntimeError(
"KT_RAWINT4_BACKEND=avxvnni requested, but "
f"group_size={group_size} is unsupported. AVX-VNNI-256 RAWINT4 only supports "
f"positive multiples of 32 up to {_AVXVNNI256_RAW_INT4_MAX_GROUP_SIZE}."
)
return AVXVNNI256RawInt4_MOE
if forced == "avx2":
if not _HAS_AVX2_RAWINT4_SUPPORT:
raise RuntimeError("KT_RAWINT4_BACKEND=avx2 requested, but AVX2RawInt4_MOE is not compiled in.")
return AVX2RawInt4_MOE
if _HAS_RAWINT4_SUPPORT:
return AMXInt4_KGroup_MOE
if _HAS_AVXVNNI256_RAW_INT4_SUPPORT and _HOST_HAS_AVX_VNNI and avxvnni_group_supported:
return AVXVNNI256RawInt4_MOE
if _HAS_AVX2_RAWINT4_SUPPORT:
return AVX2RawInt4_MOE
return None
def _select_mxfp4_backend():
"""Select MXFP4 backend: AMX/AVX-512 (preferred) > AVX2 (fallback).
Override with KT_MXFP4_BACKEND=avx2|amx.
Returns None if no MXFP4 backend is available.
"""
forced = os.getenv("KT_MXFP4_BACKEND", "").strip().lower()
if forced == "amx":
if not _HAS_MXFP4_SUPPORT:
raise RuntimeError(
"KT_MXFP4_BACKEND=amx requested, but AMXFP4_KGroup_MOE is not compiled in. "
"Recompile with AVX512F + AVX512BW + AVX512_BF16 enabled."
)
return AMXFP4_KGroup_MOE
if forced == "avx2":
if not _HAS_AVX2_MXFP4_SUPPORT:
raise RuntimeError(
"KT_MXFP4_BACKEND=avx2 requested, but AVX2MXFP4_MOE is not compiled in. "
"Recompile with AVX2 + FMA enabled."
)
return AVX2MXFP4_MOE
if _HAS_MXFP4_SUPPORT:
return AMXFP4_KGroup_MOE
if _HAS_AVX2_MXFP4_SUPPORT:
return AVX2MXFP4_MOE
return None
def _select_mxfp8_backend():
"""Select MXFP8 backend: AMX/AVX-512 (preferred) > AVX2 (fallback).
Override with KT_MXFP8_BACKEND=avx2|amx.
Returns None if no MXFP8 backend is available.
"""
forced = os.getenv("KT_MXFP8_BACKEND", "").strip().lower()
if forced == "amx":
if not _HAS_MXFP8_SUPPORT:
raise RuntimeError(
"KT_MXFP8_BACKEND=amx requested, but AMXMXFP8_KGroup_MOE is not compiled in. "
"Recompile with AVX512F + AVX512BW + AVX512_BF16 + AVX512_VBMI enabled."
)
if not _host_has_cpu_flag("amx_tile", "amx_bf16"):
raise RuntimeError(
"KT_MXFP8_BACKEND=amx requested, but the host CPU lacks AMX (amx_tile / amx_bf16). "
"This would SIGILL at first forward. Unset the env to fall back to AVX2."
)
return AMXMXFP8_KGroup_MOE
if forced == "avx2":
if not _HAS_AVX2_MXFP8_SUPPORT:
raise RuntimeError(
"KT_MXFP8_BACKEND=avx2 requested, but AVX2MXFP8_MOE is not compiled in. "
"Recompile with AVX2 + FMA enabled."
)
return AVX2MXFP8_MOE
# Auto-select: prefer AMX iff the .so was built with it AND the runtime CPU has AMX.
# Compile-time-only check would SIGILL on AVX-512 CPUs lacking AMX (pre-Sapphire Rapids).
if _HAS_MXFP8_SUPPORT and _host_has_cpu_flag("amx_tile", "amx_bf16"):
return AMXMXFP8_KGroup_MOE
if _HAS_AVX2_MXFP8_SUPPORT:
return AVX2MXFP8_MOE
return None
class AMXMoEWrapper(BaseMoEWrapper):
"""
AMX-based MoE wrapper implementation.
Supports AMXINT4 and AMXINT8 quantization methods.
"""
_safetensor_loader_instance = None # Singleton SafeTensorLoader
def __init__(
self,
layer_idx: int,
num_experts: int,
num_experts_per_tok: int,
hidden_size: int,
moe_intermediate_size: int,
gpu_experts_mask: Optional[torch.Tensor],
cpuinfer_threads: int,
threadpool_count: int,
weight_path: str,
chunked_prefill_size: int,
cpu_save: bool = False,
max_deferred_experts_per_token: Optional[int] = None,
method: str = "AMXINT4",
numa_nodes: Optional[List[int]] = None,
):
"""
Initialize AMX MoE Wrapper.
Args:
layer_idx: Layer index
num_experts: Total number of experts
num_experts_per_tok: Number of experts per token (top-k)
hidden_size: Hidden dimension size
moe_intermediate_size: MoE intermediate size
gpu_experts_mask: Boolean mask indicating which experts are on GPU.
Shape: [num_experts], dtype: torch.bool.
mask[i] = True means expert i is on GPU.
If None, all experts are on CPU.
cpuinfer_threads: Number of CPU inference threads
threadpool_count: Number of NUMA subpools
weight_path: Path to AMX weights (SafeTensor format)
chunked_prefill_size: Maximum prefill chunk size
cpu_save: Whether to save weights to CPU memory
max_deferred_experts_per_token: Number of experts per token to defer. Defaults to 0.
method: AMX quantization method ("AMXINT4" or "AMXINT8")
"""
if method == "AMXINT4" and not _HAS_AMXINT4_SUPPORT:
raise RuntimeError(
"AMXINT4 backend not available. Required ISA:\n"
" - AVX512F + AVX512BW (VNNI optional)\n"
"Please recompile kt_kernel_ext with AVX512 enabled."
)
if method == "AMXINT8" and not _HAS_AMXINT8_SUPPORT:
raise RuntimeError(
"AMXINT8 backend not available. Required ISA:\n"
" - AVX512F + AVX512BW (VNNI optional)\n"
"Please recompile kt_kernel_ext with AVX512 enabled."
)
# Initialize base class
super().__init__(
layer_idx=layer_idx,
num_experts=num_experts,
num_experts_per_tok=num_experts_per_tok,
hidden_size=hidden_size,
moe_intermediate_size=moe_intermediate_size,
gpu_experts_mask=gpu_experts_mask,
cpuinfer_threads=cpuinfer_threads,
threadpool_count=threadpool_count,
weight_path=weight_path,
chunked_prefill_size=chunked_prefill_size,
cpu_save=cpu_save,
max_deferred_experts_per_token=max_deferred_experts_per_token,
method=method,
numa_nodes=numa_nodes,
)
# AMX-specific: Check if we should load merged safetensor weights
self.load_merged_weight = False
import glob
if glob.glob(os.path.join(weight_path, "*.safetensors")):
self.load_merged_weight = True
# Initialize SafeTensor loader (singleton)
if self.load_merged_weight:
if AMXMoEWrapper._safetensor_loader_instance is None:
AMXMoEWrapper._safetensor_loader_instance = SafeTensorLoader(weight_path)
self.safetensor_loader = AMXMoEWrapper._safetensor_loader_instance
# AMX-specific weight storage
self.gate_weights = None
self.up_weights = None
self.down_weights = None
self.gate_scales = None
self.up_scales = None
self.down_scales = None
def load_weights_from_tensors(
self,
gate_proj: torch.Tensor,
up_proj: torch.Tensor,
down_proj: torch.Tensor,
physical_to_logical_map_cpu: torch.Tensor,
):
"""
Load and quantize weights from BF16/FP16 tensors (online quantization).
Args:
gate_proj: Gate projection weights [num_experts, intermediate_size, hidden_size]
up_proj: Up projection weights [num_experts, intermediate_size, hidden_size]
down_proj: Down projection weights [num_experts, hidden_size, intermediate_size]
physical_to_logical_map_cpu: Mapping from physical to logical expert IDs
"""
# Store tensors as instance variables to keep them alive
self.gate_proj = gate_proj.contiguous()
self.up_proj = up_proj.contiguous()
self.down_proj = down_proj.contiguous()
# Configure MoE with online quantization (cpu_save mode)
moe_config = MOEConfig(
self.num_experts,
self.num_experts_per_tok,
self.hidden_size,
self.moe_intermediate_size,
self.gpu_experts_mask.data_ptr(),
)
moe_config.layer_idx = self.layer_idx
moe_config.pool = self.cpu_infer.backend_
moe_config.max_len = self.chunked_prefill_size
# Enable save mode for online quantization
moe_config.save = True
moe_config.load = False
# Set weight pointers
moe_config.gate_proj = self.gate_proj.data_ptr()
moe_config.up_proj = self.up_proj.data_ptr()
moe_config.down_proj = self.down_proj.data_ptr()
# Set output path for quantized weights
moe_config.path = self.weight_path
# Create MoE module based on AMX method
if self.method == "AMXINT4":
self.moe = AMXInt4_MOE(moe_config)
elif self.method == "AMXINT8":
self.moe = AMXInt8_MOE(moe_config)
else:
raise NotImplementedError(f"Unsupported AMX method: {self.method}")
# Submit quantization and save task
self.cpu_infer.submit(self.moe.load_weights_task(physical_to_logical_map_cpu.data_ptr()))
self.cpu_infer.sync()
def load_weights(self, physical_to_logical_map_cpu: torch.Tensor):
"""
Load weights for this layer and initialize the MoE module.
Args:
physical_to_logical_map_cpu: Mapping from physical to logical expert IDs
"""
gate_ptr = 0
up_ptr = 0
down_ptr = 0
gate_ptrs = []
up_ptrs = []
down_ptrs = []
gate_scale_ptrs = []
up_scale_ptrs = []
down_scale_ptrs = []
if self.load_merged_weight:
base_key = f"blk.{self.layer_idx}"
w = self.safetensor_loader.load_experts(base_key)
self.gate_weights = w["gate"]
self.up_weights = w["up"]
self.down_weights = w["down"]
self.gate_scales = w["gate_scale"]
self.up_scales = w["up_scale"]
self.down_scales = w["down_scale"]
# Get pointers to weight arrays
gate_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.gate_weights
]
up_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.up_weights
]
down_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.down_weights
]
gate_scale_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.gate_scales
]
up_scale_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.up_scales
]
down_scale_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.down_scales
]
# Configure MoE
moe_config = MOEConfig(
self.num_experts,
self.num_experts_per_tok,
self.hidden_size,
self.moe_intermediate_size,
self.gpu_experts_mask.data_ptr(),
)
moe_config.layer_idx = self.layer_idx
moe_config.pool = self.cpu_infer.backend_
moe_config.max_len = self.chunked_prefill_size
moe_config.gate_proj = gate_ptr
moe_config.up_proj = up_ptr
moe_config.down_proj = down_ptr
moe_config.gate_projs = gate_ptrs
moe_config.up_projs = up_ptrs
moe_config.down_projs = down_ptrs
moe_config.gate_scales = gate_scale_ptrs
moe_config.up_scales = up_scale_ptrs
moe_config.down_scales = down_scale_ptrs
if self.cpu_save:
moe_config.save = True
moe_config.load = False
base_key = f"model.layers.{self.layer_idx}"
try:
w = self.safetensor_loader.load_experts(base_key)
except (ValueError, KeyError):
base_key = f"model.language_model.layers.{self.layer_idx}"
w = self.safetensor_loader.load_experts(base_key)
self.gate_proj = torch.cat(w["gate_weight"], dim=0).contiguous()
self.up_proj = torch.cat(w["up_weight"], dim=0).contiguous()
self.down_proj = torch.cat(w["down_weight"], dim=0).contiguous()
moe_config.gate_proj = self.gate_proj.data_ptr()
moe_config.up_proj = self.up_proj.data_ptr()
moe_config.down_proj = self.down_proj.data_ptr()
else:
moe_config.load = True
if not self.load_merged_weight:
moe_config.path = self.weight_path
# Create MoE module based on AMX method
if self.method == "AMXINT4":
self.moe = AMXInt4_MOE(moe_config)
elif self.method == "AMXINT8":
self.moe = AMXInt8_MOE(moe_config)
else:
raise NotImplementedError(f"Unsupported AMX method: {self.method}")
# Load weights
self.cpu_infer.submit(self.moe.load_weights_task(physical_to_logical_map_cpu.data_ptr()))
self.cpu_infer.sync()
# Clean up temporary weight storage if using merged weights
if self.load_merged_weight:
del self.gate_weights
del self.up_weights
del self.down_weights
del self.gate_scales
del self.up_scales
del self.down_scales
class NativeMoEWrapper(BaseMoEWrapper):
"""Wrapper for RAWINT4/FP8/FP8_PERCHANNEL/BF16 experts stored in compressed SafeTensor format."""
_native_loader_instance = None
def __init__(
self,
layer_idx: int,
num_experts: int,
num_experts_per_tok: int,
hidden_size: int,
moe_intermediate_size: int,
gpu_experts_mask: Optional[torch.Tensor],
cpuinfer_threads: int,
threadpool_count: int,
weight_path: str,
chunked_prefill_size: int,
cpu_save: bool = False,
max_deferred_experts_per_token: Optional[int] = None,
method: str = "RAWINT4",
numa_nodes: Optional[List[int]] = None,
swiglu_limit: float = 0.0,
swiglu_alpha: float = 0.0,
):
self._swiglu_alpha = float(swiglu_alpha)
# Defence in depth: reject swiglu_limit on non-MXFP4/MXFP8 methods even
# if the experts.py guard is bypassed (e.g., by a future caller
# that constructs NativeMoEWrapper directly). Origin: kt-sglang 耦合.
if swiglu_limit != 0.0 and method not in ("MXFP4", "MXFP8"):
raise ValueError(
f"NativeMoEWrapper received swiglu_limit={swiglu_limit} with "
f"method={method!r}; the clamp only applies to MXFP4/MXFP8. "
f"This indicates a missing guard in the caller."
)
if method == "RAWINT4" and not (
_HAS_RAWINT4_SUPPORT or _HAS_AVX2_RAWINT4_SUPPORT or _HAS_AVXVNNI256_RAW_INT4_SUPPORT
):
raise RuntimeError(
"RAWINT4 backend not available. Required ISA:\n"
" - AVX512F + AVX512BW (for AMX backend), or\n"
" - AVX2 + FMA (for AVX2 fallback backend)\n"
"AVX-VNNI-256 will be selected automatically when available on the current CPU.\n"
"Please recompile kt_kernel_ext with AVX512 or AVX2 enabled."
)
if method == "FP8" and not _HAS_FP8_SUPPORT and not _HAS_AVX2_FP8_SUPPORT:
raise RuntimeError(
"FP8 backend not available. Required ISA:\n"
" - AVX512F + AVX512BW + AVX512_BF16 + AVX512_VBMI (for AMX), or\n"
" - AVX2 + FMA (for AVX2 fallback)\n"
"Please recompile kt_kernel_ext with AVX512 + BF16 + VBMI enabled."
)
if method == "FP8_PERCHANNEL" and not _HAS_FP8_PERCHANNEL_SUPPORT:
raise RuntimeError(
"FP8_PERCHANNEL backend not available. Required ISA:\n"
" - AVX512F + AVX512BW + AVX512_BF16 + AVX512_VBMI\n"
"Please recompile kt_kernel_ext with AVX512 + BF16 + VBMI enabled."
)
if method == "BF16" and not _HAS_BF16_SUPPORT and not _HAS_AVX2_BF16_SUPPORT:
raise RuntimeError(
"BF16 backend not available. Required ISA:\n"
" - AVX512F + AVX512BW + AVX512_BF16 (for AMX backend), or\n"
" - AVX2 + FMA (for AVX2 fallback backend)\n"
"Please recompile kt_kernel_ext with AVX512+BF16 or AVX2 enabled."
)
if method == "GPTQ_INT4" and not (_HAS_AVX2_GPTQ_INT4_SUPPORT or _HAS_AVXVNNI256_GPTQ_INT4_SUPPORT):
raise RuntimeError(
"GPTQ_INT4 backend not available.\n"
"Please recompile kt_kernel_ext with GPTQ INT4 support enabled.\n"
"AVX-VNNI-256 will be selected automatically when available on the current CPU."
)
if method == "MXFP4" and not (_HAS_MXFP4_SUPPORT or _HAS_AVX2_MXFP4_SUPPORT):
raise RuntimeError(
"MXFP4 backend not available. Required ISA (any one of):\n"
" - AVX512F + AVX512BW + AVX512_BF16 (for AMX/AVX-512 backend)\n"
" - AVX2 + FMA (for AVX2 fallback backend)\n"
"Please recompile kt_kernel_ext with one of the above enabled."
)
if method == "MXFP8" and not (_HAS_MXFP8_SUPPORT or _HAS_AVX2_MXFP8_SUPPORT):
raise RuntimeError(
"MXFP8 backend not available. Required ISA (any one of):\n"
" - AVX512F + AVX512BW + AVX512_BF16 + AVX512_VBMI (for AMX/AVX-512 backend)\n"
" - AVX2 + FMA (for AVX2 fallback backend)\n"
"Please recompile kt_kernel_ext with one of the above enabled."
)
super().__init__(
layer_idx=layer_idx,
num_experts=num_experts,
num_experts_per_tok=num_experts_per_tok,
hidden_size=hidden_size,
moe_intermediate_size=moe_intermediate_size,
gpu_experts_mask=gpu_experts_mask,
cpuinfer_threads=cpuinfer_threads,
threadpool_count=threadpool_count,
weight_path=weight_path,
chunked_prefill_size=chunked_prefill_size,
cpu_save=cpu_save,
max_deferred_experts_per_token=max_deferred_experts_per_token,
method=method,
numa_nodes=numa_nodes,
swiglu_limit=swiglu_limit,
)
if NativeMoEWrapper._native_loader_instance is None:
NativeMoEWrapper._native_loader_instance = NativeMoEWrapper._create_loader(method, weight_path)
self.loader = NativeMoEWrapper._native_loader_instance
self.gate_weights = None
self.up_weights = None
self.down_weights = None
self.gate_scales = None
self.up_scales = None
self.down_scales = None
@staticmethod
def _create_loader(method: str, weight_path: str):
if method == "RAWINT4":
return CompressedSafeTensorLoader(weight_path)
elif method == "FP8":
return FP8SafeTensorLoader(weight_path)
elif method == "FP8_PERCHANNEL":
return FP8SafeTensorLoader(weight_path, scale_suffix="weight_scale")
elif method == "BF16":
return BF16SafeTensorLoader(weight_path)
elif method == "GPTQ_INT4":
return GPTQSafeTensorLoader(weight_path)
elif method == "MXFP4":
return MXFP4SafeTensorLoader(weight_path)
elif method == "MXFP8":
return MXFP8SafeTensorLoader(weight_path)
else:
raise NotImplementedError(f"Unsupported method for NativeMoEWrapper: {method}")
@staticmethod
def _release_loader(layer_idx: int = -1):
if NativeMoEWrapper._native_loader_instance is not None:
NativeMoEWrapper._native_loader_instance.close_all_handles()
NativeMoEWrapper._native_loader_instance = None
if layer_idx >= 0:
logger.info(
"[KT] Released NativeMoEWrapper loader after layer %d: "
"safetensors mmap handles freed.", layer_idx,
)
else:
logger.info(
"[KT] Released NativeMoEWrapper loader: safetensors mmap handles freed."
)
@staticmethod
def force_release_loader():
NativeMoEWrapper._release_loader()
def load_weights_from_tensors(
self,
gate_proj: torch.Tensor,
up_proj: torch.Tensor,
down_proj: torch.Tensor,
physical_to_logical_map_cpu: torch.Tensor,
):
raise NotImplementedError("RAWINT4 wrapper expects pre-quantized safetensor weights.")
def load_weights(self, physical_to_logical_map_cpu: torch.Tensor):
import time
if NativeMoEWrapper._native_loader_instance is None:
t_recreate_start = time.time()
NativeMoEWrapper._native_loader_instance = NativeMoEWrapper._create_loader(
self.method, self.weight_path
)
self.loader = NativeMoEWrapper._native_loader_instance
t_recreate_elapsed = (time.time() - t_recreate_start) * 1000
logger.info(
"[KT] Recreated NativeMoEWrapper loader for layer %d (took %.1fms)",
self.layer_idx, t_recreate_elapsed,
)
else:
self.loader = NativeMoEWrapper._native_loader_instance
t0 = time.time()
_candidates = [
f"model.layers.{self.layer_idx}",
f"language_model.model.layers.{self.layer_idx}",
f"model.language_model.layers.{self.layer_idx}",
]
weights = None
for base_key in _candidates:
try:
weights = self.loader.load_experts(base_key)
break
except (ValueError, KeyError):
continue
if weights is None:
raise ValueError(
f"No experts found for layer {self.layer_idx} under any prefix: {_candidates}"
)
t1 = time.time()
# Keep individual tensors instead of stacking - avoid expensive memory copy
# weights["gate"], weights["up"], weights["down"] are lists of tensors per expert
self.gate_weights = weights["gate"] # list of tensors
self.up_weights = weights["up"]
self.down_weights = weights["down"]
# BF16 has no scales, others have scales
if self.method == "BF16":
# BF16 doesn't have scales
self.gate_scales = None
self.up_scales = None
self.down_scales = None
else:
# Convert scales to bf16 individually
# self.gate_scales = [t.to(torch.bfloat16).contiguous() for t in weights["gate_scale"]]
# self.up_scales = [t.to(torch.bfloat16).contiguous() for t in weights["up_scale"]]
# self.down_scales = [t.to(torch.bfloat16).contiguous() for t in weights["down_scale"]]
self.gate_scales = weights["gate_scale"]
self.up_scales = weights["up_scale"]
self.down_scales = weights["down_scale"]
if self.method == "RAWINT4":
assert self.gate_scales[0].dtype == torch.bfloat16, "Expected bf16 scales for RAWINT4"
elif self.method == "FP8":
if self.gate_scales[0].dtype != torch.float32:
self.gate_scales = [t.to(torch.float32).contiguous() for t in weights["gate_scale"]]
self.up_scales = [t.to(torch.float32).contiguous() for t in weights["up_scale"]]
self.down_scales = [t.to(torch.float32).contiguous() for t in weights["down_scale"]]
assert self.gate_scales[0].dtype == torch.float32, "Expected float32 scales for FP8"
elif self.method == "FP8_PERCHANNEL":
if self.gate_scales[0].dtype != torch.float32:
self.gate_scales = [t.to(torch.float32).contiguous() for t in weights["gate_scale"]]
self.up_scales = [t.to(torch.float32).contiguous() for t in weights["up_scale"]]
self.down_scales = [t.to(torch.float32).contiguous() for t in weights["down_scale"]]
assert self.gate_scales[0].dtype == torch.float32, "Expected float32 scales for FP8_PERCHANNEL"
elif self.method == "MXFP4":
# ue8m0 is losslessly representable in bf16 (8-bit exponent, 0 mantissa);
# the loader has already done that conversion.
assert self.gate_scales[0].dtype == torch.bfloat16, "Expected bf16 scales for MXFP4"
elif self.method == "MXFP8":
# ue8m0 scales stay as uint8; C++ convert_ue8m0_to_fp32 handles conversion.
assert self.gate_scales[0].dtype == torch.uint8, "Expected uint8 (ue8m0) scales for MXFP8"
t2 = time.time()
# Build pointer lists: [numa_id][expert_id] -> pointer
# Since RAWINT4/FP8/BF16 has no numa sharding, numa dimension is 1
gate_ptrs = [[t.data_ptr() for t in self.gate_weights]]
up_ptrs = [[t.data_ptr() for t in self.up_weights]]
down_ptrs = [[t.data_ptr() for t in self.down_weights]]
# BF16 has no scales, pass empty lists (will use 0/nullptr for consistency)
if self.method == "BF16":
gate_scale_ptrs = [[0 for _ in self.gate_weights]]
up_scale_ptrs = [[0 for _ in self.up_weights]]
down_scale_ptrs = [[0 for _ in self.down_weights]]
else:
gate_scale_ptrs = [[t.data_ptr() for t in self.gate_scales]]
up_scale_ptrs = [[t.data_ptr() for t in self.up_scales]]
down_scale_ptrs = [[t.data_ptr() for t in self.down_scales]]
t3 = time.time()
moe_config = MOEConfig(
self.num_experts,
self.num_experts_per_tok,
self.hidden_size,
self.moe_intermediate_size,
self.gpu_experts_mask.data_ptr(),
)
moe_config.layer_idx = self.layer_idx
moe_config.pool = self.cpu_infer.backend_
moe_config.max_len = self.chunked_prefill_size
# V4-Flash 2604B SwiGLU clamp; 0.0 = disabled (default for non-MXFP4
# paths). Read by `act_fn` in operators/amx/la/amx.hpp via
# `apply_activation` in operators/amx/moe_base.hpp. Re-checked here
# (defence in depth) so a future caller that bypasses both the
# experts.py and the __init__ guards still cannot apply the clamp
# on RAWINT4 / FP8 / BF16 / FP8_PERCHANNEL / GPTQ_INT4 paths.
# Origin: kt-sglang 耦合.
if self.swiglu_limit != 0.0 and self.method not in ("MXFP4", "MXFP8"):
raise ValueError(
f"NativeMoEWrapper.load_weights: swiglu_limit="
f"{self.swiglu_limit} with method={self.method!r}; clamp is "
f"only valid for MXFP4/MXFP8."
)
moe_config.swiglu_limit = self.swiglu_limit
# Use gate_projs instead of gate_proj for per-expert pointers
moe_config.gate_projs = gate_ptrs
moe_config.up_projs = up_ptrs
moe_config.down_projs = down_ptrs
moe_config.gate_scales = gate_scale_ptrs
moe_config.up_scales = up_scale_ptrs
moe_config.down_scales = down_scale_ptrs
# Infer group_size from scale shape (column-major layout)
# For gate/up projection: in_features = hidden_size
# So: group_size = hidden_size / scale.shape[1]
if self.method == "RAWINT4":
group_size = self.hidden_size // self.gate_scales[0].shape[1]
moe_config.quant_config.bits = 4
moe_config.quant_config.group_size = group_size
moe_config.quant_config.zero_point = False
backend_cls = _select_rawint4_backend(group_size)
if backend_cls is None:
raise RuntimeError(
"No RAWINT4 backend is available after runtime selection for "
f"group_size={group_size}. AMX (AMXInt4_KGroup_MOE) is preferred; "
f"AVX-VNNI-256 supports positive multiples of 32 up to "
f"{_AVXVNNI256_RAW_INT4_MAX_GROUP_SIZE}; AVX2 (AVX2RawInt4_MOE) is used as the final fallback."
)
self.moe = backend_cls(moe_config)
elif self.method == "MXFP4":
# MXFP4: E2M1 nibble-packed weights, ue8m0/bf16 per-32 group scale
# (e.g. DeepSeek-V4-Flash routed experts)
group_size = self.hidden_size // self.gate_scales[0].shape[1]
moe_config.quant_config.bits = 4
moe_config.quant_config.group_size = group_size
moe_config.quant_config.zero_point = False
backend_cls = _select_mxfp4_backend()
if backend_cls is None:
raise RuntimeError(
"No MXFP4 backend available after runtime selection. "
"Compile with AVX512_BF16 (AMXFP4_KGroup_MOE) or AVX2 (AVX2MXFP4_MOE)."
)
self.moe = backend_cls(moe_config)
elif self.method == "MXFP8":
# MXFP8: FP8 E4M3fn byte weights, ue8m0/uint8 per-32 group scale
# (e.g. MiniMax-M3-Preview)
group_size = self.hidden_size // self.gate_scales[0].shape[1]
moe_config.quant_config.bits = 8
moe_config.quant_config.group_size = group_size
moe_config.quant_config.zero_point = False
moe_config.swiglu_alpha = getattr(self, "_swiglu_alpha", 0.0)
backend_cls = _select_mxfp8_backend()
if backend_cls is None:
raise RuntimeError(
"No MXFP8 backend available after runtime selection. "
"Compile with AVX512+VBMI (AMXMXFP8_KGroup_MOE) or AVX2 (AVX2MXFP8_MOE)."
)
self.moe = backend_cls(moe_config)
elif self.method == "FP8":
moe_config.quant_config.bits = 8
moe_config.quant_config.group_size = 128
moe_config.quant_config.zero_point = False
if _HAS_FP8_SUPPORT:
self.moe = AMXFP8_MOE(moe_config)
else:
self.moe = AVX2FP8_MOE(moe_config)
elif self.method == "FP8_PERCHANNEL":
moe_config.quant_config.bits = 8
moe_config.quant_config.per_channel = True
moe_config.quant_config.zero_point = False
self.moe = AMXFP8PerChannel_MOE(moe_config)
elif self.method == "GPTQ_INT4":
# GPTQ symmetric INT4: qweight (int32) + scales (fp32)
group_size = self.gate_scales[0].shape[0] # scales shape [K/gs, N], first dim = num_groups
# hidden_size / num_groups = group_size
actual_gs = self.hidden_size // group_size
moe_config.quant_config.bits = 4
moe_config.quant_config.group_size = actual_gs
moe_config.quant_config.zero_point = False
backend_cls = _select_gptq_int4_backend(actual_gs)
if backend_cls is None:
raise RuntimeError(
"No GPTQ_INT4 backend is available after runtime selection for "
f"group_size={actual_gs}. AVX-VNNI-256 supports positive multiples of 32 up to "
f"{_AVXVNNI256_GPTQ_INT4_MAX_GROUP_SIZE}; AVX2 is used as the fallback when available."
)
self.moe = backend_cls(moe_config)
elif self.method == "BF16":
# BF16 has no quantization config needed
# Prefer AMX backend, fall back to AVX2
if _HAS_BF16_SUPPORT:
self.moe = AMXBF16_MOE(moe_config)
else:
self.moe = AVX2BF16_MOE(moe_config)
t4 = time.time()
self.cpu_infer.submit(self.moe.load_weights_task(physical_to_logical_map_cpu.data_ptr()))
self.cpu_infer.sync()
t5 = time.time()
del self.gate_weights
del self.up_weights
del self.down_weights
if self.gate_scales is not None:
del self.gate_scales
del self.up_scales
del self.down_scales
NativeMoEWrapper._release_loader(layer_idx=self.layer_idx)
t6 = time.time()
print(
f"[NativeMoEWrapper Layer {self.layer_idx}] "
f"load_experts: {(t1-t0)*1000:.1f}ms, "
f"prepare_tensors: {(t2-t1)*1000:.1f}ms, "
f"build_ptrs: {(t3-t2)*1000:.1f}ms, "
f"create_moe: {(t4-t3)*1000:.1f}ms, "
f"cpp_load_weights: {(t5-t4)*1000:.1f}ms, "
f"cleanup: {(t6-t5)*1000:.1f}ms, "
f"total: {(t6-t0)*1000:.1f}ms"
)
def submit_write_weight_scale_to_buffer(
self,
gpu_tp_count: int,
expert_id: int,
w13_weight_ptrs,
w13_scale_ptrs,
w2_weight_ptrs,
w2_scale_ptrs,
):
"""
Submit the write_weight_scale_to_buffer task for RAWINT4 KGroup AMX implementation.
This method submits the C++-exposed task `write_weight_scale_to_buffer_task` to the
shared CPUInfer queue. The pointer lists should be plain integer lists (e.g. from
tensor.data_ptr()).
"""
if self.moe is None:
raise RuntimeError("MoE instance not initialized; cannot submit write_weight_scale_to_buffer task.")
if not hasattr(self.moe, "write_weight_scale_to_buffer_task"):
raise NotImplementedError(
"write_weight_scale_to_buffer_task is not available for this backend implementation."
)
self.cpu_infer.submit(
self.moe.write_weight_scale_to_buffer_task(
gpu_tp_count,
expert_id,
w13_weight_ptrs,
w13_scale_ptrs,
w2_weight_ptrs,
w2_scale_ptrs,
)
)
def sync_write_weight_scale_to_buffer(self):
"""
Block until previously submitted write_weight_scale_to_buffer tasks finish.
"""
# The CPUInfer.sync() call blocks until pending tasks complete.
self.cpu_infer.sync()
+227
View File
@@ -0,0 +1,227 @@
import torch
from typing import List, Optional
import os
# Use relative imports for package structure
from ..experts_base import BaseMoEWrapper
from .loader import GGUFLoader
from kt_kernel_ext.moe import MOEConfig
try:
from kt_kernel_ext.moe import MOE
_HAS_LLAMAFILE_SUPPORT = True
except (ImportError, AttributeError):
_HAS_LLAMAFILE_SUPPORT = False
MOE = None
from kt_kernel_ext.kvcache import ggml_type
class LlamafileMoEWrapper(BaseMoEWrapper):
"""
Llamafile-based MoE wrapper implementation.
Supports GGUF quantized weights with llamafile backend.
"""
_gguf_loader_instance = None # Singleton GGUFLoader
def __init__(
self,
layer_idx: int,
num_experts: int,
num_experts_per_tok: int,
hidden_size: int,
moe_intermediate_size: int,
gpu_experts_mask: Optional[torch.Tensor],
cpuinfer_threads: int,
threadpool_count: int,
weight_path: str,
chunked_prefill_size: int,
cpu_save: bool = False,
max_deferred_experts_per_token: Optional[int] = None,
method: str = "LLAMAFILE",
numa_nodes: Optional[List[int]] = None,
):
"""
Initialize Llamafile MoE Wrapper.
Args:
layer_idx: Layer index
num_experts: Total number of experts
num_experts_per_tok: Number of experts per token (top-k)
hidden_size: Hidden dimension size
moe_intermediate_size: MoE intermediate size
gpu_experts_mask: Boolean mask indicating which experts are on GPU.
Shape: [num_experts], dtype: torch.bool.
mask[i] = True means expert i is on GPU.
If None, all experts are on CPU.
cpuinfer_threads: Number of CPU inference threads
threadpool_count: Number of NUMA subpools (TP count)
weight_path: Path to GGUF weights
chunked_prefill_size: Maximum prefill chunk size
cpu_save: Not supported for Llamafile backend
max_deferred_experts_per_token: Number of experts per token to defer. Defaults to 0.
method: Should be "LLAMAFILE"
"""
if not _HAS_LLAMAFILE_SUPPORT:
raise RuntimeError(
"Llamafile backend not available. kt_kernel_ext was not compiled with Llamafile support.\n"
"Please recompile with Llamafile enabled."
)
if not os.path.exists(weight_path):
raise FileNotFoundError(f"GGUF weight path not found: {weight_path}")
# Initialize GGUF loader (singleton)
if LlamafileMoEWrapper._gguf_loader_instance is None:
LlamafileMoEWrapper._gguf_loader_instance = GGUFLoader(weight_path)
self.gguf_loader = LlamafileMoEWrapper._gguf_loader_instance
# Validate TP configuration with QK_K alignment
QK_K = 256
# Check if intermediate_size is divisible by QK_K
if moe_intermediate_size % QK_K != 0:
raise ValueError(
f"intermediate_size ({moe_intermediate_size}) must be divisible by QK_K ({QK_K}) "
f"for Llamafile backend"
)
# Calculate TP splits with QK_K alignment
num_blocks = moe_intermediate_size // QK_K
base_blocks = num_blocks // threadpool_count
extra_blocks = num_blocks % threadpool_count
# Validate that we have enough blocks
if base_blocks == 0:
valid_tp_counts = list(range(1, num_blocks + 1))
raise ValueError(
f"intermediate_size ({moe_intermediate_size}) is too small for threadpool_count ({threadpool_count}).\n"
f"Total blocks: {num_blocks} (intermediate_size / QK_K)\n"
f"Cannot distribute to {threadpool_count} TPs (each TP needs at least 1 block).\n"
f"Valid threadpool_count values: {valid_tp_counts}"
)
# Log TP split information
print(f"[LlamafileMoEWrapper] Layer {layer_idx} TP configuration:")
print(f" intermediate_size: {moe_intermediate_size}")
print(f" threadpool_count: {threadpool_count}")
print(f" QK_K: {QK_K}")
print(f" Total blocks: {num_blocks}")
print(f" Base blocks per TP: {base_blocks}")
print(f" Extra blocks (distributed to first TPs): {extra_blocks}")
current_offset = 0
for tp_id in range(threadpool_count):
tp_blocks = base_blocks + (1 if tp_id < extra_blocks else 0)
tp_size = tp_blocks * QK_K
print(f" TP {tp_id}: size={tp_size}, offset={current_offset}, blocks={tp_blocks}")
current_offset += tp_size
# Initialize base class
super().__init__(
layer_idx=layer_idx,
num_experts=num_experts,
num_experts_per_tok=num_experts_per_tok,
hidden_size=hidden_size,
moe_intermediate_size=moe_intermediate_size,
gpu_experts_mask=gpu_experts_mask,
cpuinfer_threads=cpuinfer_threads,
threadpool_count=threadpool_count,
weight_path=weight_path,
chunked_prefill_size=chunked_prefill_size,
cpu_save=cpu_save,
max_deferred_experts_per_token=max_deferred_experts_per_token,
method=method,
numa_nodes=numa_nodes,
)
self.weights_to_keep = None
def load_weights_from_tensors(
self,
gate_proj: torch.Tensor,
up_proj: torch.Tensor,
down_proj: torch.Tensor,
physical_to_logical_map_cpu: torch.Tensor,
):
"""
Online quantization is not supported for Llamafile backend.
Use pre-quantized GGUF weights instead.
"""
raise NotImplementedError(
"Llamafile backend does not support online quantization (load_weights_from_tensors).\n"
"Please use pre-quantized GGUF weights and call load_weights() instead."
)
def load_weights(self, physical_to_logical_map_cpu: Optional[torch.Tensor] = None):
"""
Load weights for this layer from GGUF files and initialize the MoE module.
Args:
physical_to_logical_map_cpu: Optional mapping from physical to logical expert IDs
Shape: [num_experts], dtype: int32
If None, uses identity mapping [0, 1, 2, ..., num_experts-1]
"""
if not _HAS_LLAMAFILE_SUPPORT:
raise RuntimeError(
"Llamafile backend not available. kt_kernel_ext was not compiled with Llamafile support.\n"
"Please recompile with Llamafile enabled."
)
if physical_to_logical_map_cpu is None:
physical_to_logical_map_cpu = torch.arange(self.num_experts, dtype=torch.int32, device="cpu")
print(f" Using default identity mapping for {self.num_experts} experts")
base_key = f"blk.{self.layer_idx}"
# Load quantized tensors from GGUF
gate_data, gate_type = self.gguf_loader.get_undequanted_tensor_and_ggml_type(f"{base_key}.ffn_gate_exps.weight")
up_data, up_type = self.gguf_loader.get_undequanted_tensor_and_ggml_type(f"{base_key}.ffn_up_exps.weight")
down_data, down_type = self.gguf_loader.get_undequanted_tensor_and_ggml_type(f"{base_key}.ffn_down_exps.weight")
# Keep tensors alive
self.weights_to_keep = (gate_data, up_data, down_data)
hidden_type = ggml_type.BF16
# Configure MoE
moe_config = MOEConfig(
self.num_experts,
self.num_experts_per_tok,
self.hidden_size,
self.moe_intermediate_size,
self.gpu_experts_mask.data_ptr(),
)
moe_config.layer_idx = self.layer_idx
moe_config.pool = self.cpu_infer.backend_
# Llamafile-specific configuration
moe_config.m_block = 32 # Parallel block size
moe_config.group_min_len = 10 # Use forward_one when qlen < 10
moe_config.max_len = self.chunked_prefill_size
moe_config.group_max_len = max(1, int(self.chunked_prefill_size))
# Set weight pointers
moe_config.gate_proj = gate_data.data_ptr()
moe_config.up_proj = up_data.data_ptr()
moe_config.down_proj = down_data.data_ptr()
# Set quantization types
moe_config.gate_type = gate_type
moe_config.up_type = up_type
moe_config.down_type = down_type
moe_config.hidden_type = hidden_type
# Create MoE module
self.moe = MOE(moe_config)
# Load weights
self.cpu_infer.submit(self.moe.load_weights_task(physical_to_logical_map_cpu.data_ptr()))
self.cpu_infer.sync()
# Drop original weights after loading
self.weights_to_keep = None
File diff suppressed because it is too large Load Diff
+321
View File
@@ -0,0 +1,321 @@
import os
import torch
import ctypes
from typing import List, Optional
# Use relative imports for package structure
from ..experts_base import BaseMoEWrapper
from .loader import SafeTensorLoader
from kt_kernel_ext.moe import MOEConfig
try:
from kt_kernel_ext.moe import Int8_KERNEL_MOE
_HAS_INT8_SUPPORT = True
except (ImportError, AttributeError):
Int8_KERNEL_MOE = None
_HAS_INT8_SUPPORT = False
try:
from kt_kernel_ext.moe import Int4_KERNEL_MOE
_HAS_INT4_SUPPORT = True
except (ImportError, AttributeError):
Int4_KERNEL_MOE = None
_HAS_INT4_SUPPORT = False
from typing import Optional
class GeneralMoEWrapper(BaseMoEWrapper):
"""
moe-based MoE wrapper implementation.
Supports MOE_INT4 and MOE_INT8 quantization methods.
"""
_safetensor_loader_instance = None # Singleton SafeTensorLoader
def __init__(
self,
layer_idx: int,
num_experts: int,
num_experts_per_tok: int,
hidden_size: int,
moe_intermediate_size: int,
gpu_experts_mask: Optional[torch.Tensor],
cpuinfer_threads: int,
threadpool_count: int,
weight_path: str,
chunked_prefill_size: int,
cpu_save: bool = False,
max_deferred_experts_per_token: Optional[int] = None,
method: str = "MOE_INT8",
numa_nodes: Optional[List[int]] = None,
):
"""
Initialize general MoE Wrapper.
Args:
layer_idx: Layer index
num_experts: Total number of experts
num_experts_per_tok: Number of experts per token (top-k)
hidden_size: Hidden dimension size
moe_intermediate_size: MoE intermediate size
gpu_experts_mask: Boolean mask indicating which experts are on GPU.
Shape: [num_experts], dtype: torch.bool.
mask[i] = True means expert i is on GPU.
If None, all experts are on CPU.
cpuinfer_threads: Number of CPU inference threads
threadpool_count: Number of NUMA subpools
weight_path: Path to weights (SafeTensor format)
chunked_prefill_size: Maximum prefill chunk size
cpu_save: Whether to save weights to CPU memory
max_deferred_experts_per_token: Number of experts per token to defer. Defaults to 0.
method: general quantization method ("MOE_INT4" or "MOE_INT8")
"""
if not _HAS_INT4_SUPPORT and method == "MOE_INT4":
raise RuntimeError(
"MoE_INT4 backend not available. kt_kernel_ext was not compiled with int4 support.\n"
"Please recompile with int4 enabled."
)
if not _HAS_INT8_SUPPORT and method == "MOE_INT8":
raise RuntimeError(
"MoE_INT8 backend not available. kt_kernel_ext was not compiled with int8 support.\n"
"Please recompile with int8 enabled."
)
# Initialize base class
super().__init__(
layer_idx=layer_idx,
num_experts=num_experts,
num_experts_per_tok=num_experts_per_tok,
hidden_size=hidden_size,
moe_intermediate_size=moe_intermediate_size,
gpu_experts_mask=gpu_experts_mask,
cpuinfer_threads=cpuinfer_threads,
threadpool_count=threadpool_count,
weight_path=weight_path,
chunked_prefill_size=chunked_prefill_size,
cpu_save=cpu_save,
max_deferred_experts_per_token=max_deferred_experts_per_token,
method=method,
numa_nodes=numa_nodes,
)
# moe-specific: Check if we should load merged safetensor weights
self.load_merged_weight = False
import glob
if glob.glob(os.path.join(weight_path, "*.safetensors")):
self.load_merged_weight = True
# Initialize SafeTensor loader (singleton)
if self.load_merged_weight:
if GeneralMoEWrapper._safetensor_loader_instance is None:
GeneralMoEWrapper._safetensor_loader_instance = SafeTensorLoader(weight_path)
self.safetensor_loader = GeneralMoEWrapper._safetensor_loader_instance
# moe-specific weight storage
self.gate_weights = None
self.up_weights = None
self.down_weights = None
self.gate_scales = None
self.up_scales = None
self.down_scales = None
def load_weights_from_tensors(
self,
gate_proj: torch.Tensor,
up_proj: torch.Tensor,
down_proj: torch.Tensor,
physical_to_logical_map_cpu: torch.Tensor,
):
"""
Load and quantize weights from BF16/FP16 tensors (online quantization).
Args:
gate_proj: Gate projection weights [num_experts, intermediate_size, hidden_size]
up_proj: Up projection weights [num_experts, intermediate_size, hidden_size]
down_proj: Down projection weights [num_experts, hidden_size, intermediate_size]
physical_to_logical_map_cpu: Mapping from physical to logical expert IDs
"""
# Store tensors as instance variables to keep them alive
self.gate_proj = gate_proj.contiguous()
self.up_proj = up_proj.contiguous()
self.down_proj = down_proj.contiguous()
# Configure MoE with online quantization (cpu_save mode)
moe_config = MOEConfig(
self.num_experts,
self.num_experts_per_tok,
self.hidden_size,
self.moe_intermediate_size,
self.gpu_experts_mask.data_ptr(),
)
moe_config.layer_idx = self.layer_idx
moe_config.pool = self.cpu_infer.backend_
moe_config.max_len = self.chunked_prefill_size
# Enable save mode for online quantization
moe_config.save = True
moe_config.load = False
# Set weight pointers
moe_config.gate_proj = self.gate_proj.data_ptr()
moe_config.up_proj = self.up_proj.data_ptr()
moe_config.down_proj = self.down_proj.data_ptr()
# Set output path for quantized weights
moe_config.path = self.weight_path
# Create MoE module based on method
if self.method == "MOE_INT4":
self.moe = Int4_KERNEL_MOE(moe_config)
elif self.method == "MOE_INT8":
self.moe = Int8_KERNEL_MOE(moe_config)
else:
raise NotImplementedError(f"Unsupported MoE method: {self.method}")
# Submit quantization and save task
self.cpu_infer.submit(self.moe.load_weights_task(physical_to_logical_map_cpu.data_ptr()))
self.cpu_infer.sync()
def load_weights(self, physical_to_logical_map_cpu: torch.Tensor):
"""
Load weights for this layer and initialize the MoE module.
Args:
physical_to_logical_map_cpu: Mapping from physical to logical expert IDs
"""
gate_ptr = 0
up_ptr = 0
down_ptr = 0
gate_ptrs = []
up_ptrs = []
down_ptrs = []
gate_scale_ptrs = []
up_scale_ptrs = []
down_scale_ptrs = []
if self.load_merged_weight:
base_key = f"blk.{self.layer_idx}"
w = self.safetensor_loader.load_experts(base_key)
self.gate_weights = w["gate"]
self.up_weights = w["up"]
self.down_weights = w["down"]
self.gate_scales = w["gate_scale"]
self.up_scales = w["up_scale"]
self.down_scales = w["down_scale"]
# Get pointers to weight arrays
gate_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.gate_weights
]
up_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.up_weights
]
down_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.down_weights
]
gate_scale_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.gate_scales
]
up_scale_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.up_scales
]
down_scale_ptrs = [
[
ctypes.addressof(ctypes.cast(et.ctypes.data, ctypes.POINTER(ctypes.c_uint64)).contents)
for et in numa_array
]
for numa_array in self.down_scales
]
# Configure MoE
moe_config = MOEConfig(
self.num_experts,
self.num_experts_per_tok,
self.hidden_size,
self.moe_intermediate_size,
self.gpu_experts_mask.data_ptr(),
)
moe_config.layer_idx = self.layer_idx
moe_config.pool = self.cpu_infer.backend_
moe_config.max_len = self.chunked_prefill_size
moe_config.gate_proj = gate_ptr
moe_config.up_proj = up_ptr
moe_config.down_proj = down_ptr
moe_config.gate_projs = gate_ptrs
moe_config.up_projs = up_ptrs
moe_config.down_projs = down_ptrs
moe_config.gate_scales = gate_scale_ptrs
moe_config.up_scales = up_scale_ptrs
moe_config.down_scales = down_scale_ptrs
if self.cpu_save:
moe_config.save = True
moe_config.load = False
base_key = f"model.layers.{self.layer_idx}"
w = self.safetensor_loader.load_experts(base_key)
self.gate_proj = torch.cat(w["gate_weight"], dim=0).contiguous()
self.up_proj = torch.cat(w["up_weight"], dim=0).contiguous()
self.down_proj = torch.cat(w["down_weight"], dim=0).contiguous()
moe_config.gate_proj = self.gate_proj.data_ptr()
moe_config.up_proj = self.up_proj.data_ptr()
moe_config.down_proj = self.down_proj.data_ptr()
else:
moe_config.load = True
if not self.load_merged_weight:
moe_config.path = self.weight_path
# Create MoE module based on moe method
if self.method == "MOE_INT4":
self.moe = Int4_KERNEL_MOE(moe_config)
elif self.method == "MOE_INT8":
self.moe = Int8_KERNEL_MOE(moe_config)
else:
raise NotImplementedError(f"Unsupported MoE method: {self.method}")
# Load weights
self.cpu_infer.submit(self.moe.load_weights_task(physical_to_logical_map_cpu.data_ptr()))
self.cpu_infer.sync()
# Clean up temporary weight storage if using merged weights
if self.load_merged_weight:
del self.gate_weights
del self.up_weights
del self.down_weights
del self.gate_scales
del self.up_scales
del self.down_scales