Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/sglang/srt/__pycache__/conversation.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/__pycache__/function_call_parser.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/__pycache__/mm_utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/__pycache__/warmup.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/base_attn_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/cutlass_mla_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/double_sparsity_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashattention_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashinfer_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashinfer_mla_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashmla_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/torch_native_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/triton_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/vision.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/decode_attention.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/double_sparsity_attention.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/extend_attention.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/prefill_attention.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/rocm_mla_decode_rope.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/__init__.py +334 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/awq.py +204 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/base_config.py +140 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/blockwise_int8.py +412 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__init__.py +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/compressed_tensors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/compressed_tensors_moe.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/compressed_tensors.py +664 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/compressed_tensors_moe.py +677 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__init__.py +11 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__pycache__/compressed_tensors_w8a16_fp8.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__pycache__/compressed_tensors_w8a8_fp8.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_scheme.py +56 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a16_fp8.py +153 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a8_fp8.py +159 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/utils.py +218 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/deep_gemm.py +385 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8.py +997 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8_kernel.py +1068 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8_utils.py +514 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/gptq.py +439 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/int8_kernel.py +412 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/int8_utils.py +73 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/kv_cache.py +89 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/modelopt_quant.py +463 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/moe_wna16.py +505 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/utils.py +147 -0
- lib/python3.12/site-packages/sglang/srt/layers/quantization/w8a8_fp8.py +331 -0
lib/python3.12/site-packages/sglang/srt/__pycache__/conversation.cpython-312.pyc
ADDED
|
Binary file (26.5 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/__pycache__/function_call_parser.cpython-312.pyc
ADDED
|
Binary file (32.9 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/__pycache__/mm_utils.cpython-312.pyc
ADDED
|
Binary file (12.7 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/__pycache__/warmup.cpython-312.pyc
ADDED
|
Binary file (2.33 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/base_attn_backend.cpython-312.pyc
ADDED
|
Binary file (4.18 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/cutlass_mla_backend.cpython-312.pyc
ADDED
|
Binary file (11 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/double_sparsity_backend.cpython-312.pyc
ADDED
|
Binary file (10.5 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashattention_backend.cpython-312.pyc
ADDED
|
Binary file (66.2 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashinfer_backend.cpython-312.pyc
ADDED
|
Binary file (44.7 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashinfer_mla_backend.cpython-312.pyc
ADDED
|
Binary file (31.6 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashmla_backend.cpython-312.pyc
ADDED
|
Binary file (11.6 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/torch_native_backend.cpython-312.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/triton_backend.cpython-312.pyc
ADDED
|
Binary file (30.1 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (3.51 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/vision.cpython-312.pyc
ADDED
|
Binary file (13.3 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/decode_attention.cpython-312.pyc
ADDED
|
Binary file (19.1 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/double_sparsity_attention.cpython-312.pyc
ADDED
|
Binary file (33.9 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/extend_attention.cpython-312.pyc
ADDED
|
Binary file (14.9 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/prefill_attention.cpython-312.pyc
ADDED
|
Binary file (7.13 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/rocm_mla_decode_rope.cpython-312.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/__init__.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://raw.githubusercontent.com/vllm-project/vllm/v0.5.5/vllm/model_executor/layers/quantization/__init__.py
|
| 2 |
+
import builtins
|
| 3 |
+
import inspect
|
| 4 |
+
import re
|
| 5 |
+
from copy import deepcopy
|
| 6 |
+
from typing import Callable, Dict, Optional, Type, Union
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
from vllm.model_executor.layers.quantization.aqlm import AQLMConfig
|
| 12 |
+
from vllm.model_executor.layers.quantization.awq_marlin import (
|
| 13 |
+
AWQMarlinConfig,
|
| 14 |
+
AWQMoEMethod,
|
| 15 |
+
)
|
| 16 |
+
from vllm.model_executor.layers.quantization.bitsandbytes import BitsAndBytesConfig
|
| 17 |
+
from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors_moe import (
|
| 18 |
+
CompressedTensorsW8A8Fp8MoEMethod,
|
| 19 |
+
CompressedTensorsWNA16MoEMethod,
|
| 20 |
+
)
|
| 21 |
+
from vllm.model_executor.layers.quantization.deepspeedfp import DeepSpeedFPConfig
|
| 22 |
+
from vllm.model_executor.layers.quantization.experts_int8 import ExpertsInt8Config
|
| 23 |
+
from vllm.model_executor.layers.quantization.fbgemm_fp8 import FBGEMMFp8Config
|
| 24 |
+
from vllm.model_executor.layers.quantization.gguf import GGUFConfig
|
| 25 |
+
from vllm.model_executor.layers.quantization.gptq import GPTQLinearMethod
|
| 26 |
+
from vllm.model_executor.layers.quantization.gptq_marlin import (
|
| 27 |
+
GPTQMarlinLinearMethod,
|
| 28 |
+
GPTQMarlinMoEMethod,
|
| 29 |
+
)
|
| 30 |
+
from vllm.model_executor.layers.quantization.gptq_marlin_24 import (
|
| 31 |
+
GPTQMarlin24Config,
|
| 32 |
+
)
|
| 33 |
+
from vllm.model_executor.layers.quantization.marlin import MarlinConfig
|
| 34 |
+
from vllm.model_executor.layers.quantization.qqq import QQQConfig
|
| 35 |
+
from vllm.model_executor.layers.quantization.tpu_int8 import Int8TpuConfig
|
| 36 |
+
|
| 37 |
+
VLLM_AVAILABLE = True
|
| 38 |
+
except ImportError:
|
| 39 |
+
VLLM_AVAILABLE = False
|
| 40 |
+
|
| 41 |
+
# Define empty classes as placeholders when vllm is not available
|
| 42 |
+
class DummyConfig:
|
| 43 |
+
def override_quantization_method(self, *args, **kwargs):
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
AQLMConfig = AWQMarlinConfig = BitsAndBytesConfig = CompressedTensorsConfig = (
|
| 47 |
+
DeepSpeedFPConfig
|
| 48 |
+
) = ExpertsInt8Config = FBGEMMFp8Config = GGUFConfig = GPTQMarlin24Config = (
|
| 49 |
+
MarlinConfig
|
| 50 |
+
) = QQQConfig = Int8TpuConfig = DummyConfig
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
from sglang.srt.layers.linear import LinearBase, UnquantizedLinearMethod
|
| 54 |
+
from sglang.srt.layers.quantization.awq import AWQConfig
|
| 55 |
+
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
| 56 |
+
from sglang.srt.layers.quantization.blockwise_int8 import BlockInt8Config
|
| 57 |
+
from sglang.srt.layers.quantization.compressed_tensors.compressed_tensors import (
|
| 58 |
+
CompressedTensorsConfig,
|
| 59 |
+
)
|
| 60 |
+
from sglang.srt.layers.quantization.fp8 import Fp8Config
|
| 61 |
+
from sglang.srt.layers.quantization.gptq import GPTQConfig, GPTQMarlinConfig
|
| 62 |
+
from sglang.srt.layers.quantization.modelopt_quant import (
|
| 63 |
+
ModelOptFp4Config,
|
| 64 |
+
ModelOptFp8Config,
|
| 65 |
+
)
|
| 66 |
+
from sglang.srt.layers.quantization.moe_wna16 import MoeWNA16Config
|
| 67 |
+
from sglang.srt.layers.quantization.w8a8_fp8 import W8A8Fp8Config
|
| 68 |
+
from sglang.srt.layers.quantization.w8a8_int8 import W8A8Int8Config
|
| 69 |
+
|
| 70 |
+
# Base quantization methods that don't depend on vllm
|
| 71 |
+
BASE_QUANTIZATION_METHODS: Dict[str, Type[QuantizationConfig]] = {
|
| 72 |
+
"fp8": Fp8Config,
|
| 73 |
+
"blockwise_int8": BlockInt8Config,
|
| 74 |
+
"modelopt": ModelOptFp8Config,
|
| 75 |
+
"modelopt_fp4": ModelOptFp4Config,
|
| 76 |
+
"w8a8_int8": W8A8Int8Config,
|
| 77 |
+
"w8a8_fp8": W8A8Fp8Config,
|
| 78 |
+
"moe_wna16": MoeWNA16Config,
|
| 79 |
+
"compressed-tensors": CompressedTensorsConfig,
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
# VLLM-dependent quantization methods
|
| 83 |
+
VLLM_QUANTIZATION_METHODS = {
|
| 84 |
+
"aqlm": AQLMConfig,
|
| 85 |
+
"awq": AWQConfig,
|
| 86 |
+
"deepspeedfp": DeepSpeedFPConfig,
|
| 87 |
+
"tpu_int8": Int8TpuConfig,
|
| 88 |
+
"fbgemm_fp8": FBGEMMFp8Config,
|
| 89 |
+
"marlin": MarlinConfig,
|
| 90 |
+
"gguf": GGUFConfig,
|
| 91 |
+
"gptq_marlin_24": GPTQMarlin24Config,
|
| 92 |
+
"awq_marlin": AWQMarlinConfig,
|
| 93 |
+
"bitsandbytes": BitsAndBytesConfig,
|
| 94 |
+
"qqq": QQQConfig,
|
| 95 |
+
"experts_int8": ExpertsInt8Config,
|
| 96 |
+
"gptq_marlin": GPTQMarlinConfig,
|
| 97 |
+
"gptq": GPTQConfig,
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
QUANTIZATION_METHODS = {**BASE_QUANTIZATION_METHODS, **VLLM_QUANTIZATION_METHODS}
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_quantization_config(quantization: str) -> Type[QuantizationConfig]:
|
| 104 |
+
if quantization not in QUANTIZATION_METHODS:
|
| 105 |
+
raise ValueError(
|
| 106 |
+
f"Invalid quantization method: {quantization}. "
|
| 107 |
+
f"Available methods: {list(QUANTIZATION_METHODS.keys())}"
|
| 108 |
+
)
|
| 109 |
+
if quantization in VLLM_QUANTIZATION_METHODS and not VLLM_AVAILABLE:
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"{quantization} quantization requires some operators from vllm. "
|
| 112 |
+
"Pleaes install vllm by `pip install vllm==0.7.2`"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
return QUANTIZATION_METHODS[quantization]
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# Match dynamic rules with module name (prefix) and override quantize
|
| 119 |
+
# config if module (prefix) matches a rule
|
| 120 |
+
def override_config(config: QuantizationConfig, prefix: str):
|
| 121 |
+
weight_bits = get_dynamic_override(config, prefix, "bits", config.weight_bits)
|
| 122 |
+
if isinstance(weight_bits, int):
|
| 123 |
+
config.weight_bits = weight_bits
|
| 124 |
+
group_size = get_dynamic_override(config, prefix, "group_size", config.group_size)
|
| 125 |
+
if isinstance(group_size, int):
|
| 126 |
+
config.group_size = group_size
|
| 127 |
+
desc_act = get_dynamic_override(config, prefix, "desc_act", config.desc_act)
|
| 128 |
+
if isinstance(desc_act, bool):
|
| 129 |
+
config.desc_act = desc_act
|
| 130 |
+
|
| 131 |
+
config.pack_factor = 32 // config.weight_bits # packed into int32
|
| 132 |
+
if config.get_name() == "gptq_marlin":
|
| 133 |
+
is_sym = get_dynamic_override(config, prefix, "sym", config.is_sym)
|
| 134 |
+
if isinstance(is_sym, bool):
|
| 135 |
+
config.is_sym = is_sym
|
| 136 |
+
|
| 137 |
+
if (config.weight_bits, config.is_sym) not in config.TYPE_MAP:
|
| 138 |
+
raise ValueError(
|
| 139 |
+
"Unsupported quantization config: "
|
| 140 |
+
f"bits={config.weight_bits}, sym={config.is_sym}"
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
config.quant_type = config.TYPE_MAP[(config.weight_bits, config.is_sym)]
|
| 144 |
+
elif config.get_name() == "gptq":
|
| 145 |
+
if config.weight_bits not in [2, 3, 4, 8]:
|
| 146 |
+
raise ValueError(
|
| 147 |
+
"Currently, only 2/3/4/8-bit weight quantization is "
|
| 148 |
+
f"supported for GPTQ, but got {config.weight_bits} bits."
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def get_dynamic_override(
|
| 153 |
+
config: QuantizationConfig,
|
| 154 |
+
layer_name: str,
|
| 155 |
+
key: Optional[str] = None,
|
| 156 |
+
default_value: Union[int, bool, None] = None,
|
| 157 |
+
) -> Union[Dict, int, bool, None]:
|
| 158 |
+
for pattern, pattern_dict in config.dynamic.items():
|
| 159 |
+
# Negative match: matched modules are excluded from quantized init
|
| 160 |
+
if pattern.startswith("-:"):
|
| 161 |
+
if re.match(pattern.removeprefix("-:"), layer_name):
|
| 162 |
+
return False
|
| 163 |
+
# Positive match: matched modules have quant properties overrides
|
| 164 |
+
# base quant config
|
| 165 |
+
elif re.match(pattern.removeprefix("+:"), layer_name):
|
| 166 |
+
if key is None:
|
| 167 |
+
return pattern_dict
|
| 168 |
+
else:
|
| 169 |
+
return pattern_dict.get(key, default_value)
|
| 170 |
+
return default_value
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def get_linear_quant_method(
|
| 174 |
+
config: QuantizationConfig,
|
| 175 |
+
layer: torch.nn.Module,
|
| 176 |
+
prefix: str,
|
| 177 |
+
linear_method_cls: type,
|
| 178 |
+
):
|
| 179 |
+
# Move import here to avoid circular import. This is only used in monkey patching
|
| 180 |
+
# of vllm's QuantizationConfig.
|
| 181 |
+
from sglang.srt.layers.vocab_parallel_embedding import (
|
| 182 |
+
ParallelLMHead,
|
| 183 |
+
UnquantizedEmbeddingMethod,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
cloned_config = deepcopy(config)
|
| 187 |
+
parallel_lm_head_quantized = (
|
| 188 |
+
isinstance(layer, ParallelLMHead) and cloned_config.lm_head_quantized
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
if isinstance(layer, LinearBase) or parallel_lm_head_quantized:
|
| 192 |
+
# False = skip module, None = no override, else = Positive match
|
| 193 |
+
if (
|
| 194 |
+
get_dynamic_override( # noqa: E712
|
| 195 |
+
cloned_config, layer_name=prefix # noqa: E712
|
| 196 |
+
)
|
| 197 |
+
== False
|
| 198 |
+
): # noqa: E712
|
| 199 |
+
if parallel_lm_head_quantized:
|
| 200 |
+
return UnquantizedEmbeddingMethod()
|
| 201 |
+
return UnquantizedLinearMethod()
|
| 202 |
+
|
| 203 |
+
if prefix:
|
| 204 |
+
# Dynamic per module/layer rules may override base config
|
| 205 |
+
override_config(cloned_config, prefix=prefix)
|
| 206 |
+
|
| 207 |
+
return linear_method_cls(cloned_config)
|
| 208 |
+
return None
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def gptq_get_quant_method(self, layer, prefix):
|
| 212 |
+
from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE
|
| 213 |
+
|
| 214 |
+
if isinstance(layer, FusedMoE):
|
| 215 |
+
return GPTQMarlinMoEMethod(self)
|
| 216 |
+
|
| 217 |
+
if isinstance(self, GPTQConfig):
|
| 218 |
+
return get_linear_quant_method(
|
| 219 |
+
self, layer, prefix=prefix, linear_method_cls=GPTQLinearMethod
|
| 220 |
+
)
|
| 221 |
+
elif isinstance(self, GPTQMarlinConfig):
|
| 222 |
+
return get_linear_quant_method(
|
| 223 |
+
self, layer, prefix=prefix, linear_method_cls=GPTQMarlinLinearMethod
|
| 224 |
+
)
|
| 225 |
+
return None
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
original_isinstance = builtins.isinstance
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def monkey_patch_isinstance_for_vllm_base_layer(reverse: bool = False):
|
| 232 |
+
"""
|
| 233 |
+
Patch isinstance so that the `get_quant_method` in vllm's QuantizationConfig
|
| 234 |
+
can recognize sglang layers
|
| 235 |
+
"""
|
| 236 |
+
if not VLLM_AVAILABLE:
|
| 237 |
+
return
|
| 238 |
+
|
| 239 |
+
if reverse:
|
| 240 |
+
builtins.isinstance = original_isinstance
|
| 241 |
+
return
|
| 242 |
+
|
| 243 |
+
from vllm.model_executor.layers.fused_moe import FusedMoE
|
| 244 |
+
from vllm.model_executor.layers.linear import LinearBase
|
| 245 |
+
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
| 246 |
+
VocabParallelEmbedding,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
from sglang.srt.layers.linear import LinearBase as PatchedLinearBase
|
| 250 |
+
from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE as PatchedFusedMoE
|
| 251 |
+
from sglang.srt.layers.vocab_parallel_embedding import (
|
| 252 |
+
VocabParallelEmbedding as PatchedVocabParallelEmbedding,
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
def patched_isinstance(obj, classinfo):
|
| 256 |
+
if classinfo is LinearBase:
|
| 257 |
+
return original_isinstance(obj, PatchedLinearBase)
|
| 258 |
+
if classinfo is FusedMoE:
|
| 259 |
+
return original_isinstance(obj, PatchedFusedMoE)
|
| 260 |
+
if classinfo is VocabParallelEmbedding:
|
| 261 |
+
return original_isinstance(obj, PatchedVocabParallelEmbedding)
|
| 262 |
+
return original_isinstance(obj, classinfo)
|
| 263 |
+
|
| 264 |
+
builtins.isinstance = patched_isinstance
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def monkey_patch_moe_apply(class_obj: "FusedMoEMethodBase"):
|
| 268 |
+
"""
|
| 269 |
+
Monkey patch the apply function of vllm's FusedMoEMethodBase.
|
| 270 |
+
Convert sglang arguments to vllm arguments.
|
| 271 |
+
"""
|
| 272 |
+
original_apply = class_obj.apply
|
| 273 |
+
sig = inspect.signature(original_apply)
|
| 274 |
+
param_names = list(sig.parameters.keys())
|
| 275 |
+
has_correction_bias = "e_score_correction_bias" in param_names
|
| 276 |
+
|
| 277 |
+
def new_apply(
|
| 278 |
+
self,
|
| 279 |
+
layer: torch.nn.Module,
|
| 280 |
+
x: torch.Tensor,
|
| 281 |
+
router_logits: torch.Tensor,
|
| 282 |
+
top_k: int,
|
| 283 |
+
renormalize: bool,
|
| 284 |
+
use_grouped_topk: bool,
|
| 285 |
+
topk_group: Optional[int] = None,
|
| 286 |
+
num_expert_group: Optional[int] = None,
|
| 287 |
+
custom_routing_function: Optional[Callable] = None,
|
| 288 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 289 |
+
activation: str = "silu",
|
| 290 |
+
apply_router_weight_on_input: bool = False,
|
| 291 |
+
inplace: bool = True,
|
| 292 |
+
no_combine: bool = False,
|
| 293 |
+
routed_scaling_factor: Optional[float] = None,
|
| 294 |
+
):
|
| 295 |
+
assert activation == "silu"
|
| 296 |
+
assert inplace and not no_combine
|
| 297 |
+
|
| 298 |
+
kwargs = {
|
| 299 |
+
"self": self,
|
| 300 |
+
"layer": layer,
|
| 301 |
+
"x": x,
|
| 302 |
+
"router_logits": router_logits,
|
| 303 |
+
"top_k": top_k,
|
| 304 |
+
"renormalize": renormalize,
|
| 305 |
+
"use_grouped_topk": use_grouped_topk,
|
| 306 |
+
"topk_group": topk_group,
|
| 307 |
+
"num_expert_group": num_expert_group,
|
| 308 |
+
"custom_routing_function": custom_routing_function,
|
| 309 |
+
}
|
| 310 |
+
if correction_bias is not None:
|
| 311 |
+
if not has_correction_bias:
|
| 312 |
+
raise ValueError(
|
| 313 |
+
"Please increase the version of your vllm. Try `pip install vllm==0.7.2`"
|
| 314 |
+
)
|
| 315 |
+
kwargs["e_score_correction_bias"] = correction_bias
|
| 316 |
+
return original_apply(**kwargs)
|
| 317 |
+
|
| 318 |
+
setattr(class_obj, "apply", new_apply)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def monkey_patch_quant_configs():
|
| 322 |
+
"""Apply all monkey patches in one place."""
|
| 323 |
+
setattr(GPTQMarlinConfig, "get_quant_method", gptq_get_quant_method)
|
| 324 |
+
setattr(GPTQConfig, "get_quant_method", gptq_get_quant_method)
|
| 325 |
+
|
| 326 |
+
monkey_patch_moe_apply(AWQMoEMethod)
|
| 327 |
+
monkey_patch_moe_apply(GPTQMarlinMoEMethod)
|
| 328 |
+
monkey_patch_moe_apply(CompressedTensorsW8A8Fp8MoEMethod)
|
| 329 |
+
monkey_patch_moe_apply(CompressedTensorsWNA16MoEMethod)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# Only apply monkey patches if vllm is available
|
| 333 |
+
if VLLM_AVAILABLE:
|
| 334 |
+
monkey_patch_quant_configs()
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/awq.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
import logging
|
| 3 |
+
from typing import Any, Dict, List, Optional
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from sglang.srt.layers.linear import (
|
| 8 |
+
LinearBase,
|
| 9 |
+
LinearMethodBase,
|
| 10 |
+
UnquantizedLinearMethod,
|
| 11 |
+
)
|
| 12 |
+
from sglang.srt.layers.parameter import GroupQuantScaleParameter, PackedvLLMParameter
|
| 13 |
+
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
| 14 |
+
from sglang.srt.utils import is_cuda
|
| 15 |
+
|
| 16 |
+
_is_cuda = is_cuda()
|
| 17 |
+
if _is_cuda:
|
| 18 |
+
from sgl_kernel import awq_dequantize
|
| 19 |
+
|
| 20 |
+
logger = logging.getLogger(__name__)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def is_layer_skipped_awq(prefix: str, modules_to_not_convert: List[str]):
|
| 24 |
+
return any(module_name in prefix for module_name in modules_to_not_convert)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class AWQConfig(QuantizationConfig):
|
| 28 |
+
"""Config class for AWQ.
|
| 29 |
+
|
| 30 |
+
Reference: https://arxiv.org/abs/2306.00978
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
def __init__(
|
| 34 |
+
self,
|
| 35 |
+
weight_bits: int,
|
| 36 |
+
group_size: int,
|
| 37 |
+
zero_point: bool,
|
| 38 |
+
modules_to_not_convert: Optional[List[str]] = None,
|
| 39 |
+
) -> None:
|
| 40 |
+
super().__init__()
|
| 41 |
+
self.weight_bits = weight_bits
|
| 42 |
+
self.group_size = group_size
|
| 43 |
+
self.zero_point = zero_point
|
| 44 |
+
self.modules_to_not_convert = modules_to_not_convert or []
|
| 45 |
+
|
| 46 |
+
if self.weight_bits != 4:
|
| 47 |
+
raise ValueError(
|
| 48 |
+
"Currently, only 4-bit weight quantization is supported for "
|
| 49 |
+
f"AWQ, but got {self.weight_bits} bits."
|
| 50 |
+
)
|
| 51 |
+
self.pack_factor = 32 // self.weight_bits
|
| 52 |
+
|
| 53 |
+
def __repr__(self) -> str:
|
| 54 |
+
return (
|
| 55 |
+
f"AWQConfig(weight_bits={self.weight_bits}, "
|
| 56 |
+
f"group_size={self.group_size}, "
|
| 57 |
+
f"zero_point={self.zero_point}, "
|
| 58 |
+
f"modules_to_not_convert={self.modules_to_not_convert})"
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 62 |
+
return []
|
| 63 |
+
|
| 64 |
+
def get_name(self) -> str:
|
| 65 |
+
return "awq"
|
| 66 |
+
|
| 67 |
+
def get_supported_act_dtypes(self) -> List[torch.dtype]:
|
| 68 |
+
return [torch.half]
|
| 69 |
+
|
| 70 |
+
@classmethod
|
| 71 |
+
def get_min_capability(cls) -> int:
|
| 72 |
+
# The AWQ kernel only supports Turing or newer GPUs.
|
| 73 |
+
return 75
|
| 74 |
+
|
| 75 |
+
@staticmethod
|
| 76 |
+
def get_config_filenames() -> List[str]:
|
| 77 |
+
return [
|
| 78 |
+
"quant_config.json", # E.g., casperhansen/vicuna-7b-v1.5-awq
|
| 79 |
+
# E.g., abhinavkulkarni/mosaicml-mpt-7b-instruct-w4-g128-awq
|
| 80 |
+
"quantize_config.json",
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
@classmethod
|
| 84 |
+
def from_config(cls, config: Dict[str, Any]) -> "AWQConfig":
|
| 85 |
+
weight_bits = cls.get_from_keys(config, ["w_bit", "bits"])
|
| 86 |
+
group_size = cls.get_from_keys(config, ["q_group_size", "group_size"])
|
| 87 |
+
zero_point = cls.get_from_keys(config, ["zero_point"])
|
| 88 |
+
modules_to_not_convert = cls.get_from_keys_or(
|
| 89 |
+
config, ["modules_to_not_convert"], None
|
| 90 |
+
)
|
| 91 |
+
return cls(weight_bits, group_size, zero_point, modules_to_not_convert)
|
| 92 |
+
|
| 93 |
+
def get_quant_method(
|
| 94 |
+
self, layer: torch.nn.Module, prefix: str
|
| 95 |
+
) -> Optional["LinearMethodBase"]:
|
| 96 |
+
|
| 97 |
+
if isinstance(layer, LinearBase):
|
| 98 |
+
if is_layer_skipped_awq(prefix, self.modules_to_not_convert):
|
| 99 |
+
return UnquantizedLinearMethod()
|
| 100 |
+
return AWQLinearMethod(self)
|
| 101 |
+
return None
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class AWQLinearMethod(LinearMethodBase):
|
| 105 |
+
"""Linear method for AWQ.
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
quant_config: The AWQ quantization config.
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
def __init__(self, quant_config: AWQConfig):
|
| 112 |
+
self.quant_config = quant_config
|
| 113 |
+
|
| 114 |
+
def create_weights(
|
| 115 |
+
self,
|
| 116 |
+
layer: torch.nn.Module,
|
| 117 |
+
input_size_per_partition: int,
|
| 118 |
+
output_partition_sizes: List[int],
|
| 119 |
+
input_size: int,
|
| 120 |
+
output_size: int,
|
| 121 |
+
params_dtype: torch.dtype,
|
| 122 |
+
**extra_weight_attrs,
|
| 123 |
+
):
|
| 124 |
+
if input_size_per_partition % self.quant_config.group_size != 0:
|
| 125 |
+
raise ValueError(
|
| 126 |
+
"The input size is not aligned with the quantized "
|
| 127 |
+
"weight shape. This can be caused by too large "
|
| 128 |
+
"tensor parallel size."
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 132 |
+
if output_size_per_partition % self.quant_config.pack_factor != 0:
|
| 133 |
+
raise ValueError(
|
| 134 |
+
"The output size is not aligned with the quantized "
|
| 135 |
+
"weight shape. This can be caused by too large "
|
| 136 |
+
"tensor parallel size."
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 140 |
+
qweight = PackedvLLMParameter(
|
| 141 |
+
data=torch.empty(
|
| 142 |
+
input_size_per_partition,
|
| 143 |
+
output_size_per_partition // self.quant_config.pack_factor,
|
| 144 |
+
dtype=torch.int32,
|
| 145 |
+
),
|
| 146 |
+
input_dim=0,
|
| 147 |
+
output_dim=1,
|
| 148 |
+
packed_dim=1,
|
| 149 |
+
packed_factor=self.quant_config.pack_factor,
|
| 150 |
+
weight_loader=weight_loader,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
qzeros = PackedvLLMParameter(
|
| 154 |
+
data=torch.empty(
|
| 155 |
+
input_size_per_partition // self.quant_config.group_size,
|
| 156 |
+
output_size_per_partition // self.quant_config.pack_factor,
|
| 157 |
+
dtype=torch.int32,
|
| 158 |
+
),
|
| 159 |
+
input_dim=0,
|
| 160 |
+
output_dim=1,
|
| 161 |
+
packed_dim=1,
|
| 162 |
+
packed_factor=self.quant_config.pack_factor,
|
| 163 |
+
weight_loader=weight_loader,
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
scales = GroupQuantScaleParameter(
|
| 167 |
+
data=torch.empty(
|
| 168 |
+
input_size_per_partition // self.quant_config.group_size,
|
| 169 |
+
output_size_per_partition,
|
| 170 |
+
dtype=params_dtype,
|
| 171 |
+
),
|
| 172 |
+
input_dim=0,
|
| 173 |
+
output_dim=1,
|
| 174 |
+
weight_loader=weight_loader,
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
layer.register_parameter("qweight", qweight)
|
| 178 |
+
layer.register_parameter("qzeros", qzeros)
|
| 179 |
+
layer.register_parameter("scales", scales)
|
| 180 |
+
|
| 181 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 182 |
+
layer.qweight = torch.nn.Parameter(layer.qweight.data, requires_grad=False)
|
| 183 |
+
layer.qzeros = torch.nn.Parameter(layer.qzeros.data, requires_grad=False)
|
| 184 |
+
layer.scales = torch.nn.Parameter(layer.scales.data, requires_grad=False)
|
| 185 |
+
|
| 186 |
+
def apply(
|
| 187 |
+
self,
|
| 188 |
+
layer: torch.nn.Module,
|
| 189 |
+
x: torch.Tensor,
|
| 190 |
+
bias: Optional[torch.Tensor] = None,
|
| 191 |
+
) -> torch.Tensor:
|
| 192 |
+
qweight = layer.qweight
|
| 193 |
+
scales = layer.scales
|
| 194 |
+
qzeros = layer.qzeros
|
| 195 |
+
pack_factor = self.quant_config.pack_factor
|
| 196 |
+
out_shape = x.shape[:-1] + (qweight.shape[-1] * pack_factor,)
|
| 197 |
+
reshaped_x = x.reshape(-1, x.shape[-1])
|
| 198 |
+
|
| 199 |
+
out = awq_dequantize(qweight, scales, qzeros)
|
| 200 |
+
out = torch.matmul(reshaped_x, out)
|
| 201 |
+
|
| 202 |
+
if bias is not None:
|
| 203 |
+
out.add_(bias)
|
| 204 |
+
return out.reshape(out_shape)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/base_config.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://raw.githubusercontent.com/vllm-project/vllm/v0.5.5/vllm/model_executor/layers/quantization/base_config.py
|
| 2 |
+
|
| 3 |
+
import inspect
|
| 4 |
+
from abc import ABC, abstractmethod
|
| 5 |
+
from typing import Any, Dict, List, Optional, Type
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from torch import nn
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class QuantizeMethodBase(ABC):
|
| 12 |
+
"""Base class for different quantized methods."""
|
| 13 |
+
|
| 14 |
+
@abstractmethod
|
| 15 |
+
def create_weights(
|
| 16 |
+
self, layer: torch.nn.Module, *weight_args, **extra_weight_attrs
|
| 17 |
+
):
|
| 18 |
+
"""Create weights for a layer.
|
| 19 |
+
|
| 20 |
+
The weights will be set as attributes of the layer."""
|
| 21 |
+
raise NotImplementedError
|
| 22 |
+
|
| 23 |
+
@abstractmethod
|
| 24 |
+
def apply(self, layer: torch.nn.Module, *args, **kwargs) -> torch.Tensor:
|
| 25 |
+
"""Apply the weights in layer to the input tensor.
|
| 26 |
+
|
| 27 |
+
Expects create_weights to have been called before on the layer."""
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
def process_weights_after_loading(self, layer: nn.Module) -> None:
|
| 31 |
+
"""Process the weight after loading.
|
| 32 |
+
|
| 33 |
+
This can be used for example, to transpose weights for computation.
|
| 34 |
+
"""
|
| 35 |
+
return
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class QuantizationConfig(ABC):
|
| 39 |
+
"""Base class for quantization configs."""
|
| 40 |
+
|
| 41 |
+
def __init__(self):
|
| 42 |
+
super().__init__()
|
| 43 |
+
# mapping is updated by models as they initialize
|
| 44 |
+
self.packed_modules_mapping: Dict[str, List[str]] = dict()
|
| 45 |
+
|
| 46 |
+
@abstractmethod
|
| 47 |
+
def get_name(self) -> str:
|
| 48 |
+
"""Name of the quantization method."""
|
| 49 |
+
raise NotImplementedError
|
| 50 |
+
|
| 51 |
+
@abstractmethod
|
| 52 |
+
def get_supported_act_dtypes(self) -> List[torch.dtype]:
|
| 53 |
+
"""List of supported activation dtypes."""
|
| 54 |
+
raise NotImplementedError
|
| 55 |
+
|
| 56 |
+
@classmethod
|
| 57 |
+
@abstractmethod
|
| 58 |
+
def get_min_capability(cls) -> int:
|
| 59 |
+
"""Minimum GPU capability to support the quantization method.
|
| 60 |
+
|
| 61 |
+
E.g., 70 for Volta, 75 for Turing, 80 for Ampere.
|
| 62 |
+
This requirement is due to the custom CUDA kernels used by the
|
| 63 |
+
quantization method.
|
| 64 |
+
"""
|
| 65 |
+
raise NotImplementedError
|
| 66 |
+
|
| 67 |
+
@staticmethod
|
| 68 |
+
@abstractmethod
|
| 69 |
+
def get_config_filenames() -> List[str]:
|
| 70 |
+
"""List of filenames to search for in the model directory."""
|
| 71 |
+
raise NotImplementedError
|
| 72 |
+
|
| 73 |
+
@classmethod
|
| 74 |
+
@abstractmethod
|
| 75 |
+
def from_config(cls, config: Dict[str, Any]) -> "QuantizationConfig":
|
| 76 |
+
"""Create a config class from the model's quantization config."""
|
| 77 |
+
raise NotImplementedError
|
| 78 |
+
|
| 79 |
+
@classmethod
|
| 80 |
+
def override_quantization_method(cls, hf_quant_cfg, user_quant) -> Optional[str]:
|
| 81 |
+
"""
|
| 82 |
+
Detects if this quantization method can support a given checkpoint
|
| 83 |
+
format by overriding the user specified quantization method --
|
| 84 |
+
this method should only be overwritten by subclasses in exceptional
|
| 85 |
+
circumstances
|
| 86 |
+
"""
|
| 87 |
+
return None
|
| 88 |
+
|
| 89 |
+
@staticmethod
|
| 90 |
+
def get_from_keys(config: Dict[str, Any], keys: List[str]) -> Any:
|
| 91 |
+
"""Get a value from the model's quantization config."""
|
| 92 |
+
for key in keys:
|
| 93 |
+
if key in config:
|
| 94 |
+
return config[key]
|
| 95 |
+
raise ValueError(
|
| 96 |
+
f"Cannot find any of {keys} in the model's " "quantization config."
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
@staticmethod
|
| 100 |
+
def get_from_keys_or(config: Dict[str, Any], keys: List[str], default: Any) -> Any:
|
| 101 |
+
"""Get a optional value from the model's quantization config."""
|
| 102 |
+
try:
|
| 103 |
+
return QuantizationConfig.get_from_keys(config, keys)
|
| 104 |
+
except ValueError:
|
| 105 |
+
return default
|
| 106 |
+
|
| 107 |
+
@abstractmethod
|
| 108 |
+
def get_quant_method(
|
| 109 |
+
self, layer: torch.nn.Module, prefix: str
|
| 110 |
+
) -> Optional[QuantizeMethodBase]:
|
| 111 |
+
"""Get the quantize method to use for the quantized layer.
|
| 112 |
+
|
| 113 |
+
Args:
|
| 114 |
+
layer: The layer for the quant method.
|
| 115 |
+
prefix: The full name of the layer in the state dict
|
| 116 |
+
Returns:
|
| 117 |
+
The quantize method. None if the given layer doesn't support quant
|
| 118 |
+
method.
|
| 119 |
+
"""
|
| 120 |
+
raise NotImplementedError
|
| 121 |
+
|
| 122 |
+
@abstractmethod
|
| 123 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 124 |
+
"""Returns the activation function names that should be post-scaled.
|
| 125 |
+
|
| 126 |
+
For now, this is only used by AWQ.
|
| 127 |
+
"""
|
| 128 |
+
raise NotImplementedError
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def method_has_implemented_embedding(method_class: Type[QuantizeMethodBase]) -> bool:
|
| 132 |
+
"""
|
| 133 |
+
Not all quant methods have embedding implemented, so we need to check that
|
| 134 |
+
it exists for our given method. We check this by making sure the function
|
| 135 |
+
has been changed from the base implementation.
|
| 136 |
+
"""
|
| 137 |
+
base_embedding = inspect.getattr_static(QuantizeMethodBase, "embedding", None)
|
| 138 |
+
class_embedding = inspect.getattr_static(method_class, "embedding", None)
|
| 139 |
+
|
| 140 |
+
return class_embedding is not None and class_embedding is not base_embedding
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/blockwise_int8.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/v0.6.4.post1/vllm/model_executor/layers/quantization/fp8.py
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Any, Callable, Dict, List, Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch.nn import Module
|
| 8 |
+
|
| 9 |
+
from sglang.srt.distributed import get_tensor_model_parallel_world_size
|
| 10 |
+
from sglang.srt.layers.linear import (
|
| 11 |
+
LinearBase,
|
| 12 |
+
LinearMethodBase,
|
| 13 |
+
UnquantizedLinearMethod,
|
| 14 |
+
)
|
| 15 |
+
from sglang.srt.layers.parameter import BlockQuantScaleParameter, ModelWeightParameter
|
| 16 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 17 |
+
QuantizationConfig,
|
| 18 |
+
QuantizeMethodBase,
|
| 19 |
+
)
|
| 20 |
+
from sglang.srt.layers.quantization.int8_utils import apply_w8a8_block_int8_linear
|
| 21 |
+
from sglang.srt.layers.quantization.utils import is_layer_skipped
|
| 22 |
+
from sglang.srt.utils import set_weight_attrs
|
| 23 |
+
|
| 24 |
+
ACTIVATION_SCHEMES = ["static", "dynamic"]
|
| 25 |
+
|
| 26 |
+
logger = logging.getLogger(__name__)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class BlockInt8Config(QuantizationConfig):
|
| 30 |
+
"""Config class for INT8."""
|
| 31 |
+
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
is_checkpoint_int8_serialized: bool = False,
|
| 35 |
+
activation_scheme: str = "dynamic",
|
| 36 |
+
ignored_layers: Optional[List[str]] = None,
|
| 37 |
+
weight_block_size: List[int] = None,
|
| 38 |
+
) -> None:
|
| 39 |
+
self.is_checkpoint_int8_serialized = is_checkpoint_int8_serialized
|
| 40 |
+
if is_checkpoint_int8_serialized:
|
| 41 |
+
logger.warning(
|
| 42 |
+
"Detected int8 checkpoint. Please note that the "
|
| 43 |
+
"format is experimental and subject to change."
|
| 44 |
+
)
|
| 45 |
+
if activation_scheme not in ACTIVATION_SCHEMES:
|
| 46 |
+
raise ValueError(f"Unsupported activation scheme {activation_scheme}")
|
| 47 |
+
self.activation_scheme = activation_scheme
|
| 48 |
+
self.ignored_layers = ignored_layers or []
|
| 49 |
+
if weight_block_size is not None:
|
| 50 |
+
if not is_checkpoint_int8_serialized:
|
| 51 |
+
raise ValueError(
|
| 52 |
+
f"The block-wise quantization only supports int8-serialized checkpoint for now."
|
| 53 |
+
)
|
| 54 |
+
if len(weight_block_size) != 2:
|
| 55 |
+
raise ValueError(
|
| 56 |
+
f"The quantization block size of weight must have 2 dimensions, but got {len(weight_block_size)} dimensions."
|
| 57 |
+
)
|
| 58 |
+
if activation_scheme != "dynamic":
|
| 59 |
+
raise ValueError(
|
| 60 |
+
f"The block-wise quantization only supports dynamic activation scheme for now, but got {activation_scheme} activation scheme."
|
| 61 |
+
)
|
| 62 |
+
self.weight_block_size = weight_block_size
|
| 63 |
+
|
| 64 |
+
@classmethod
|
| 65 |
+
def get_name(cls) -> str:
|
| 66 |
+
return "blockwise_int8"
|
| 67 |
+
|
| 68 |
+
@classmethod
|
| 69 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 70 |
+
return [torch.bfloat16, torch.half]
|
| 71 |
+
|
| 72 |
+
@classmethod
|
| 73 |
+
def get_min_capability(cls) -> int:
|
| 74 |
+
return 80
|
| 75 |
+
|
| 76 |
+
@classmethod
|
| 77 |
+
def get_config_filenames(cls) -> List[str]:
|
| 78 |
+
return []
|
| 79 |
+
|
| 80 |
+
@classmethod
|
| 81 |
+
def from_config(cls, config: Dict[str, Any]) -> "BlockInt8Config":
|
| 82 |
+
quant_method = cls.get_from_keys(config, ["quant_method"])
|
| 83 |
+
is_checkpoint_int8_serialized = "int8" in quant_method
|
| 84 |
+
activation_scheme = cls.get_from_keys(config, ["activation_scheme"])
|
| 85 |
+
ignored_layers = cls.get_from_keys_or(config, ["ignored_layers"], None)
|
| 86 |
+
weight_block_size = cls.get_from_keys_or(config, ["weight_block_size"], None)
|
| 87 |
+
return cls(
|
| 88 |
+
is_checkpoint_int8_serialized=is_checkpoint_int8_serialized,
|
| 89 |
+
activation_scheme=activation_scheme,
|
| 90 |
+
ignored_layers=ignored_layers,
|
| 91 |
+
weight_block_size=weight_block_size,
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
def get_quant_method(
|
| 95 |
+
self, layer: torch.nn.Module, prefix: str
|
| 96 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 97 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
|
| 98 |
+
|
| 99 |
+
if isinstance(layer, LinearBase):
|
| 100 |
+
if is_layer_skipped(prefix, self.ignored_layers):
|
| 101 |
+
return UnquantizedLinearMethod()
|
| 102 |
+
return BlockInt8LinearMethod(self)
|
| 103 |
+
elif isinstance(layer, FusedMoE):
|
| 104 |
+
return BlockInt8MoEMethod(self)
|
| 105 |
+
return None
|
| 106 |
+
|
| 107 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 108 |
+
return []
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class BlockInt8LinearMethod(LinearMethodBase):
|
| 112 |
+
"""Linear method for INT8.
|
| 113 |
+
Supports loading INT8 checkpoints with static weight scale and
|
| 114 |
+
dynamic activation scale.
|
| 115 |
+
|
| 116 |
+
Limitations:
|
| 117 |
+
Only support block-wise int8 quantization and int8 checkpoint
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
quant_config: The quantization config.
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
def __init__(self, quant_config: BlockInt8Config):
|
| 124 |
+
self.quant_config = quant_config
|
| 125 |
+
assert self.quant_config.weight_block_size is not None
|
| 126 |
+
assert self.quant_config.is_checkpoint_int8_serialized
|
| 127 |
+
|
| 128 |
+
def create_weights(
|
| 129 |
+
self,
|
| 130 |
+
layer: torch.nn.Module,
|
| 131 |
+
input_size_per_partition: int,
|
| 132 |
+
output_partition_sizes: List[int],
|
| 133 |
+
input_size: int,
|
| 134 |
+
output_size: int,
|
| 135 |
+
params_dtype: torch.dtype,
|
| 136 |
+
**extra_weight_attrs,
|
| 137 |
+
):
|
| 138 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 139 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 140 |
+
|
| 141 |
+
tp_size = get_tensor_model_parallel_world_size()
|
| 142 |
+
|
| 143 |
+
block_n, block_k = (
|
| 144 |
+
self.quant_config.weight_block_size[0],
|
| 145 |
+
self.quant_config.weight_block_size[1],
|
| 146 |
+
)
|
| 147 |
+
# Required by row parallel
|
| 148 |
+
if tp_size > 1 and input_size // input_size_per_partition == tp_size:
|
| 149 |
+
if input_size_per_partition % block_k != 0:
|
| 150 |
+
raise ValueError(
|
| 151 |
+
f"Weight input_size_per_partition = "
|
| 152 |
+
f"{input_size_per_partition} is not divisible by "
|
| 153 |
+
f"weight quantization block_k = {block_k}."
|
| 154 |
+
)
|
| 155 |
+
# Required by collum parallel or enabling merged weights
|
| 156 |
+
if (tp_size > 1 and output_size // output_size_per_partition == tp_size) or len(
|
| 157 |
+
output_partition_sizes
|
| 158 |
+
) > 1:
|
| 159 |
+
for output_partition_size in output_partition_sizes:
|
| 160 |
+
if output_partition_size % block_n != 0:
|
| 161 |
+
raise ValueError(
|
| 162 |
+
f"Weight output_partition_size = "
|
| 163 |
+
f"{output_partition_size} is not divisible by "
|
| 164 |
+
f"weight quantization block_n = {block_n}."
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
layer.logical_widths = output_partition_sizes
|
| 168 |
+
|
| 169 |
+
layer.input_size_per_partition = input_size_per_partition
|
| 170 |
+
layer.output_size_per_partition = output_size_per_partition
|
| 171 |
+
layer.orig_dtype = params_dtype
|
| 172 |
+
|
| 173 |
+
# WEIGHT
|
| 174 |
+
weight_dtype = (
|
| 175 |
+
torch.int8
|
| 176 |
+
if self.quant_config.is_checkpoint_int8_serialized
|
| 177 |
+
else params_dtype
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
weight = ModelWeightParameter(
|
| 181 |
+
data=torch.empty(
|
| 182 |
+
output_size_per_partition, input_size_per_partition, dtype=weight_dtype
|
| 183 |
+
),
|
| 184 |
+
input_dim=1,
|
| 185 |
+
output_dim=0,
|
| 186 |
+
weight_loader=weight_loader,
|
| 187 |
+
)
|
| 188 |
+
layer.register_parameter("weight", weight)
|
| 189 |
+
|
| 190 |
+
# WEIGHT SCALE
|
| 191 |
+
|
| 192 |
+
scale = BlockQuantScaleParameter(
|
| 193 |
+
data=torch.empty(
|
| 194 |
+
(output_size_per_partition + block_n - 1) // block_n,
|
| 195 |
+
(input_size_per_partition + block_k - 1) // block_k,
|
| 196 |
+
dtype=torch.float32,
|
| 197 |
+
),
|
| 198 |
+
input_dim=1,
|
| 199 |
+
output_dim=0,
|
| 200 |
+
weight_loader=weight_loader,
|
| 201 |
+
)
|
| 202 |
+
scale[:] = torch.finfo(torch.float32).min
|
| 203 |
+
layer.register_parameter("weight_scale_inv", scale)
|
| 204 |
+
|
| 205 |
+
# INPUT ACTIVATION SCALE
|
| 206 |
+
assert self.quant_config.activation_scheme == "dynamic"
|
| 207 |
+
layer.register_parameter("input_scale", None)
|
| 208 |
+
|
| 209 |
+
def process_weights_after_loading(self, layer: Module) -> None:
|
| 210 |
+
# Block quant doesn't need to process weights after loading
|
| 211 |
+
# Use torch Parameter to avoid cuda graph capturing issue
|
| 212 |
+
layer.weight = torch.nn.Parameter(layer.weight.data, requires_grad=False)
|
| 213 |
+
layer.weight_scale_inv = torch.nn.Parameter(
|
| 214 |
+
layer.weight_scale_inv.data, requires_grad=False
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
def apply(
|
| 218 |
+
self,
|
| 219 |
+
layer: torch.nn.Module,
|
| 220 |
+
x: torch.Tensor,
|
| 221 |
+
bias: Optional[torch.Tensor] = None,
|
| 222 |
+
) -> torch.Tensor:
|
| 223 |
+
return apply_w8a8_block_int8_linear(
|
| 224 |
+
input=x,
|
| 225 |
+
weight=layer.weight,
|
| 226 |
+
block_size=self.quant_config.weight_block_size,
|
| 227 |
+
weight_scale=layer.weight_scale_inv,
|
| 228 |
+
input_scale=None,
|
| 229 |
+
bias=bias,
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class BlockInt8MoEMethod:
|
| 234 |
+
"""MoE method for INT8.
|
| 235 |
+
Supports loading INT8 checkpoints with static weight scale and
|
| 236 |
+
dynamic activation scale.
|
| 237 |
+
|
| 238 |
+
Limitations:
|
| 239 |
+
Only support block-wise int8 quantization and int8 checkpoint
|
| 240 |
+
|
| 241 |
+
Args:
|
| 242 |
+
quant_config: The quantization config.
|
| 243 |
+
"""
|
| 244 |
+
|
| 245 |
+
def __new__(cls, *args, **kwargs):
|
| 246 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoEMethodBase
|
| 247 |
+
|
| 248 |
+
if not hasattr(cls, "_initialized"):
|
| 249 |
+
original_init = cls.__init__
|
| 250 |
+
new_cls = type(
|
| 251 |
+
cls.__name__,
|
| 252 |
+
(FusedMoEMethodBase,),
|
| 253 |
+
{
|
| 254 |
+
"__init__": original_init,
|
| 255 |
+
**{k: v for k, v in cls.__dict__.items() if k != "__dict__"},
|
| 256 |
+
},
|
| 257 |
+
)
|
| 258 |
+
obj = super(new_cls, new_cls).__new__(new_cls)
|
| 259 |
+
obj.__init__(*args, **kwargs)
|
| 260 |
+
return obj
|
| 261 |
+
return super().__new__(cls)
|
| 262 |
+
|
| 263 |
+
def __init__(self, quant_config):
|
| 264 |
+
self.quant_config = quant_config
|
| 265 |
+
assert self.quant_config.weight_block_size is not None
|
| 266 |
+
assert self.quant_config.is_checkpoint_int8_serialized
|
| 267 |
+
|
| 268 |
+
def create_weights(
|
| 269 |
+
self,
|
| 270 |
+
layer: Module,
|
| 271 |
+
num_experts: int,
|
| 272 |
+
hidden_size: int,
|
| 273 |
+
intermediate_size: int,
|
| 274 |
+
params_dtype: torch.dtype,
|
| 275 |
+
**extra_weight_attrs,
|
| 276 |
+
):
|
| 277 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoeWeightScaleSupported
|
| 278 |
+
|
| 279 |
+
if self.quant_config.is_checkpoint_int8_serialized:
|
| 280 |
+
params_dtype = torch.int8
|
| 281 |
+
tp_size = get_tensor_model_parallel_world_size()
|
| 282 |
+
|
| 283 |
+
block_n, block_k = (
|
| 284 |
+
self.quant_config.weight_block_size[0],
|
| 285 |
+
self.quant_config.weight_block_size[1],
|
| 286 |
+
)
|
| 287 |
+
# NOTE(HandH1998): To ensure proper alignment of the block-wise quantization scales, the output_size of the weights for both the gate and up layers must be divisible by block_n.
|
| 288 |
+
# Required by collum parallel or enabling merged weights
|
| 289 |
+
if intermediate_size % block_n != 0:
|
| 290 |
+
raise ValueError(
|
| 291 |
+
f"The output_size of gate's and up's weight = "
|
| 292 |
+
f"{intermediate_size} is not divisible by "
|
| 293 |
+
f"weight quantization block_n = {block_n}."
|
| 294 |
+
)
|
| 295 |
+
if tp_size > 1:
|
| 296 |
+
# Required by row parallel
|
| 297 |
+
if intermediate_size % block_k != 0:
|
| 298 |
+
raise ValueError(
|
| 299 |
+
f"The input_size of down's weight = "
|
| 300 |
+
f"{intermediate_size} is not divisible by "
|
| 301 |
+
f"weight quantization block_k = {block_k}."
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
# WEIGHTS
|
| 305 |
+
w13_weight = torch.nn.Parameter(
|
| 306 |
+
torch.empty(
|
| 307 |
+
num_experts, 2 * intermediate_size, hidden_size, dtype=params_dtype
|
| 308 |
+
),
|
| 309 |
+
requires_grad=False,
|
| 310 |
+
)
|
| 311 |
+
layer.register_parameter("w13_weight", w13_weight)
|
| 312 |
+
set_weight_attrs(w13_weight, extra_weight_attrs)
|
| 313 |
+
|
| 314 |
+
w2_weight = torch.nn.Parameter(
|
| 315 |
+
torch.empty(
|
| 316 |
+
num_experts, hidden_size, intermediate_size, dtype=params_dtype
|
| 317 |
+
),
|
| 318 |
+
requires_grad=False,
|
| 319 |
+
)
|
| 320 |
+
layer.register_parameter("w2_weight", w2_weight)
|
| 321 |
+
set_weight_attrs(w2_weight, extra_weight_attrs)
|
| 322 |
+
|
| 323 |
+
# WEIGHT_SCALES
|
| 324 |
+
w13_weight_scale = torch.nn.Parameter(
|
| 325 |
+
torch.ones(
|
| 326 |
+
num_experts,
|
| 327 |
+
2 * ((intermediate_size + block_n - 1) // block_n),
|
| 328 |
+
(hidden_size + block_k - 1) // block_k,
|
| 329 |
+
dtype=torch.float32,
|
| 330 |
+
),
|
| 331 |
+
requires_grad=False,
|
| 332 |
+
)
|
| 333 |
+
w2_weight_scale = torch.nn.Parameter(
|
| 334 |
+
torch.ones(
|
| 335 |
+
num_experts,
|
| 336 |
+
(hidden_size + block_n - 1) // block_n,
|
| 337 |
+
(intermediate_size + block_k - 1) // block_k,
|
| 338 |
+
dtype=torch.float32,
|
| 339 |
+
),
|
| 340 |
+
requires_grad=False,
|
| 341 |
+
)
|
| 342 |
+
layer.register_parameter("w13_weight_scale_inv", w13_weight_scale)
|
| 343 |
+
layer.register_parameter("w2_weight_scale_inv", w2_weight_scale)
|
| 344 |
+
|
| 345 |
+
extra_weight_attrs.update(
|
| 346 |
+
{"quant_method": FusedMoeWeightScaleSupported.BLOCK.value}
|
| 347 |
+
)
|
| 348 |
+
set_weight_attrs(w13_weight_scale, extra_weight_attrs)
|
| 349 |
+
set_weight_attrs(w2_weight_scale, extra_weight_attrs)
|
| 350 |
+
|
| 351 |
+
# INPUT_SCALES
|
| 352 |
+
assert self.quant_config.activation_scheme == "dynamic"
|
| 353 |
+
layer.w13_input_scale = None
|
| 354 |
+
layer.w2_input_scale = None
|
| 355 |
+
|
| 356 |
+
def process_weights_after_loading(self, layer: Module) -> None:
|
| 357 |
+
# Block quant doesn't need to process weights after loading
|
| 358 |
+
return
|
| 359 |
+
|
| 360 |
+
def apply(
|
| 361 |
+
self,
|
| 362 |
+
layer: torch.nn.Module,
|
| 363 |
+
x: torch.Tensor,
|
| 364 |
+
router_logits: torch.Tensor,
|
| 365 |
+
top_k: int,
|
| 366 |
+
renormalize: bool,
|
| 367 |
+
use_grouped_topk: bool,
|
| 368 |
+
topk_group: Optional[int] = None,
|
| 369 |
+
num_expert_group: Optional[int] = None,
|
| 370 |
+
custom_routing_function: Optional[Callable] = None,
|
| 371 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 372 |
+
activation: str = "silu",
|
| 373 |
+
apply_router_weight_on_input: bool = False,
|
| 374 |
+
inplace: bool = True,
|
| 375 |
+
no_combine: bool = False,
|
| 376 |
+
routed_scaling_factor: Optional[float] = None,
|
| 377 |
+
) -> torch.Tensor:
|
| 378 |
+
from sglang.srt.layers.moe.fused_moe_triton.fused_moe import fused_experts
|
| 379 |
+
from sglang.srt.layers.moe.topk import select_experts
|
| 380 |
+
|
| 381 |
+
# Expert selection
|
| 382 |
+
topk_weights, topk_ids = select_experts(
|
| 383 |
+
hidden_states=x,
|
| 384 |
+
router_logits=router_logits,
|
| 385 |
+
use_grouped_topk=use_grouped_topk,
|
| 386 |
+
top_k=top_k,
|
| 387 |
+
renormalize=renormalize,
|
| 388 |
+
topk_group=topk_group,
|
| 389 |
+
num_expert_group=num_expert_group,
|
| 390 |
+
custom_routing_function=custom_routing_function,
|
| 391 |
+
correction_bias=correction_bias,
|
| 392 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# Expert fusion with INT8 quantization
|
| 396 |
+
return fused_experts(
|
| 397 |
+
x,
|
| 398 |
+
layer.w13_weight,
|
| 399 |
+
layer.w2_weight,
|
| 400 |
+
topk_weights=topk_weights,
|
| 401 |
+
topk_ids=topk_ids,
|
| 402 |
+
inplace=inplace,
|
| 403 |
+
activation=activation,
|
| 404 |
+
apply_router_weight_on_input=apply_router_weight_on_input,
|
| 405 |
+
use_int8_w8a8=True,
|
| 406 |
+
w1_scale=(layer.w13_weight_scale_inv),
|
| 407 |
+
w2_scale=(layer.w2_weight_scale_inv),
|
| 408 |
+
a1_scale=layer.w13_input_scale,
|
| 409 |
+
a2_scale=layer.w2_input_scale,
|
| 410 |
+
block_shape=self.quant_config.weight_block_size,
|
| 411 |
+
no_combine=no_combine,
|
| 412 |
+
)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (228 Bytes). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/compressed_tensors.cpython-312.pyc
ADDED
|
Binary file (25.3 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/compressed_tensors_moe.cpython-312.pyc
ADDED
|
Binary file (27.6 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (7.5 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/compressed_tensors.py
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/tree/v0.8.2/vllm/model_executor/layers/quantization/compressed_tensors
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
from contextlib import suppress
|
| 6 |
+
from typing import Any, Dict, List, Literal, NamedTuple, Optional, Tuple, cast
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from compressed_tensors.config import (
|
| 10 |
+
CompressionFormat,
|
| 11 |
+
SparsityCompressionConfig,
|
| 12 |
+
SparsityStructure,
|
| 13 |
+
)
|
| 14 |
+
from compressed_tensors.quantization import (
|
| 15 |
+
QuantizationArgs,
|
| 16 |
+
QuantizationStrategy,
|
| 17 |
+
QuantizationType,
|
| 18 |
+
)
|
| 19 |
+
from pydantic import BaseModel
|
| 20 |
+
|
| 21 |
+
from sglang.srt.layers.linear import (
|
| 22 |
+
LinearBase,
|
| 23 |
+
LinearMethodBase,
|
| 24 |
+
UnquantizedLinearMethod,
|
| 25 |
+
)
|
| 26 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 27 |
+
QuantizationConfig,
|
| 28 |
+
QuantizeMethodBase,
|
| 29 |
+
)
|
| 30 |
+
from sglang.srt.layers.quantization.compressed_tensors.compressed_tensors_moe import ( # noqa: E501
|
| 31 |
+
CompressedTensorsMoEMethod,
|
| 32 |
+
)
|
| 33 |
+
from sglang.srt.layers.quantization.compressed_tensors.schemes import (
|
| 34 |
+
CompressedTensorsScheme,
|
| 35 |
+
CompressedTensorsW8A8Fp8,
|
| 36 |
+
CompressedTensorsW8A16Fp8,
|
| 37 |
+
)
|
| 38 |
+
from sglang.srt.layers.quantization.compressed_tensors.utils import (
|
| 39 |
+
find_matched_target,
|
| 40 |
+
is_activation_quantization_format,
|
| 41 |
+
should_ignore_layer,
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
try:
|
| 45 |
+
import vllm
|
| 46 |
+
|
| 47 |
+
VLLM_AVAILABLE = True
|
| 48 |
+
except ImportError:
|
| 49 |
+
VLLM_AVAILABLE = False
|
| 50 |
+
|
| 51 |
+
logger = logging.getLogger(__name__)
|
| 52 |
+
|
| 53 |
+
__all__ = ["CompressedTensorsLinearMethod"]
|
| 54 |
+
|
| 55 |
+
SPARSITY_CONFIG_NAME: Literal["sparsity_config"] = "sparsity_config"
|
| 56 |
+
QUANTIZATION_SCHEME_MAP_TYPE = Dict[str, Optional[Dict[str, QuantizationArgs]]]
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class DeviceCapability(NamedTuple):
|
| 60 |
+
major: int
|
| 61 |
+
minor: int
|
| 62 |
+
|
| 63 |
+
def as_version_str(self) -> str:
|
| 64 |
+
return f"{self.major}.{self.minor}"
|
| 65 |
+
|
| 66 |
+
def to_int(self) -> int:
|
| 67 |
+
"""
|
| 68 |
+
Express device capability as an integer ``<major><minor>``.
|
| 69 |
+
|
| 70 |
+
It is assumed that the minor version is always a single digit.
|
| 71 |
+
"""
|
| 72 |
+
assert 0 <= self.minor < 10
|
| 73 |
+
return self.major * 10 + self.minor
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class CompressedTensorsConfig(QuantizationConfig):
|
| 77 |
+
|
| 78 |
+
def __init__(
|
| 79 |
+
self,
|
| 80 |
+
target_scheme_map: Dict[str, Any],
|
| 81 |
+
ignore: List[str],
|
| 82 |
+
quant_format: str,
|
| 83 |
+
sparsity_scheme_map: Dict[str, SparsityCompressionConfig],
|
| 84 |
+
sparsity_ignore_list: List[str],
|
| 85 |
+
kv_cache_scheme: Optional[Dict[str, Any]] = None,
|
| 86 |
+
config: Optional[Dict[str, Any]] = None,
|
| 87 |
+
packed_modules_mapping: Dict[str, List[str]] = {},
|
| 88 |
+
):
|
| 89 |
+
super().__init__()
|
| 90 |
+
self.ignore = ignore
|
| 91 |
+
self.quant_format = quant_format
|
| 92 |
+
# Map from [target -> scheme]
|
| 93 |
+
self.target_scheme_map = target_scheme_map
|
| 94 |
+
self.kv_cache_scheme = kv_cache_scheme
|
| 95 |
+
self.sparsity_scheme_map = sparsity_scheme_map
|
| 96 |
+
self.sparsity_ignore_list = sparsity_ignore_list
|
| 97 |
+
self.config = config
|
| 98 |
+
self.packed_modules_mapping = packed_modules_mapping
|
| 99 |
+
|
| 100 |
+
def get_linear_method(self) -> "CompressedTensorsLinearMethod":
|
| 101 |
+
return CompressedTensorsLinearMethod(self)
|
| 102 |
+
|
| 103 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 104 |
+
return [torch.float16, torch.bfloat16]
|
| 105 |
+
|
| 106 |
+
@classmethod
|
| 107 |
+
def get_min_capability(cls) -> int:
|
| 108 |
+
return 70
|
| 109 |
+
|
| 110 |
+
def get_name(self) -> str:
|
| 111 |
+
return "compressed_tensors"
|
| 112 |
+
|
| 113 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 114 |
+
return []
|
| 115 |
+
|
| 116 |
+
def get_quant_method(
|
| 117 |
+
self,
|
| 118 |
+
layer: torch.nn.Module,
|
| 119 |
+
prefix: str,
|
| 120 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 121 |
+
|
| 122 |
+
# Check if the layer is skipped for quantization.
|
| 123 |
+
# TODO (@robertgshaw2): support module names
|
| 124 |
+
if should_ignore_layer(
|
| 125 |
+
prefix, ignore=self.ignore, fused_mapping=self.packed_modules_mapping
|
| 126 |
+
):
|
| 127 |
+
return UnquantizedLinearMethod()
|
| 128 |
+
if isinstance(layer, LinearBase):
|
| 129 |
+
scheme = self.get_scheme(layer=layer, layer_name=prefix)
|
| 130 |
+
if scheme is None:
|
| 131 |
+
return UnquantizedLinearMethod()
|
| 132 |
+
layer.scheme = scheme
|
| 133 |
+
return CompressedTensorsLinearMethod(self)
|
| 134 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
|
| 135 |
+
|
| 136 |
+
if isinstance(layer, FusedMoE):
|
| 137 |
+
return CompressedTensorsMoEMethod.get_moe_method(self)
|
| 138 |
+
return None
|
| 139 |
+
|
| 140 |
+
@classmethod
|
| 141 |
+
def from_config(cls, config: Dict[str, Any]) -> "CompressedTensorsConfig":
|
| 142 |
+
ignore: List[str] = cast(List[str], config.get("ignore", []))
|
| 143 |
+
quant_format = cast(str, config.get("format"))
|
| 144 |
+
target_scheme_map = cls._quantization_scheme_map_from_config(config=config)
|
| 145 |
+
sparsity_scheme_map, sparsity_ignore_list = cls._parse_sparsity_config(
|
| 146 |
+
config=config
|
| 147 |
+
)
|
| 148 |
+
packed_modules_mapping = config.get("packed_modules_mapping", {})
|
| 149 |
+
|
| 150 |
+
return cls(
|
| 151 |
+
target_scheme_map=target_scheme_map,
|
| 152 |
+
ignore=ignore,
|
| 153 |
+
quant_format=quant_format,
|
| 154 |
+
sparsity_scheme_map=sparsity_scheme_map,
|
| 155 |
+
sparsity_ignore_list=sparsity_ignore_list,
|
| 156 |
+
config=config,
|
| 157 |
+
packed_modules_mapping=packed_modules_mapping,
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
@classmethod
|
| 161 |
+
def _parse_sparsity_config(
|
| 162 |
+
cls, config: Dict[str, Any]
|
| 163 |
+
) -> Tuple[Dict[str, SparsityCompressionConfig], List[str]]:
|
| 164 |
+
"""
|
| 165 |
+
:param config: The `quantization_config` dictionary from config.json
|
| 166 |
+
:return: A tuple with two elements
|
| 167 |
+
1. A dictionary mapping target layer names to their corresponding
|
| 168 |
+
sparsity_config
|
| 169 |
+
2. A list of layer names to ignore for sparsity
|
| 170 |
+
"""
|
| 171 |
+
if not (sparsity_config := config.get(SPARSITY_CONFIG_NAME)):
|
| 172 |
+
return dict(), []
|
| 173 |
+
|
| 174 |
+
sparsity_config = SparsityCompressionConfig.model_validate(sparsity_config)
|
| 175 |
+
sparse_scheme_map: Dict[str, SparsityCompressionConfig] = {
|
| 176 |
+
target: sparsity_config for target in sparsity_config.targets or list()
|
| 177 |
+
}
|
| 178 |
+
sparsity_ignore_list = sparsity_config.ignore or list()
|
| 179 |
+
return sparse_scheme_map, sparsity_ignore_list
|
| 180 |
+
|
| 181 |
+
@classmethod
|
| 182 |
+
def _quantization_scheme_map_from_config(
|
| 183 |
+
cls, config: Dict[str, Any]
|
| 184 |
+
) -> QUANTIZATION_SCHEME_MAP_TYPE:
|
| 185 |
+
"""
|
| 186 |
+
:param config: The `quantization_config` dictionary from config.json
|
| 187 |
+
:return: A dictionary mapping target layer names to their corresponding
|
| 188 |
+
quantization_args for weights and input activations
|
| 189 |
+
"""
|
| 190 |
+
target_scheme_map: Dict[str, Any] = dict()
|
| 191 |
+
quant_format = cast(str, config.get("format"))
|
| 192 |
+
|
| 193 |
+
# The quant_config has multiple config_groups, each containing
|
| 194 |
+
# an input_activations key with details about how the activations are
|
| 195 |
+
# quantized, a weights key indicating how the weights are quantized,
|
| 196 |
+
# and a list of targets under the `targets` key, dictating which
|
| 197 |
+
# layers are impacted by the quantization details. The quantization
|
| 198 |
+
# details follow the structure defined by the QuantizationArgs
|
| 199 |
+
# pydantic model, which is used to verify the structure of the
|
| 200 |
+
# quant_config and also store the details for later use.
|
| 201 |
+
|
| 202 |
+
config_groups = config.get("config_groups", dict())
|
| 203 |
+
for _, quant_config in config_groups.items():
|
| 204 |
+
targets = quant_config.get("targets")
|
| 205 |
+
for target in targets:
|
| 206 |
+
target_scheme_map[target] = {}
|
| 207 |
+
target_scheme_map[target]["weights"] = QuantizationArgs.model_validate(
|
| 208 |
+
quant_config.get("weights")
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
target_scheme_map[target]["input_activations"] = None
|
| 212 |
+
if is_activation_quantization_format(quant_format):
|
| 213 |
+
input_activations = quant_config.get("input_activations")
|
| 214 |
+
# The only case where we have activation quant supported
|
| 215 |
+
# but no input_activations provided in the config
|
| 216 |
+
# should be w8a16fp8 w8a16fp8 can also run for cases where
|
| 217 |
+
# there is an input_quant but it is ignored
|
| 218 |
+
if not input_activations:
|
| 219 |
+
assert (
|
| 220 |
+
target_scheme_map[target]["weights"].type
|
| 221 |
+
== QuantizationType.FLOAT
|
| 222 |
+
)
|
| 223 |
+
else:
|
| 224 |
+
target_scheme_map[target]["input_activations"] = (
|
| 225 |
+
QuantizationArgs.model_validate( # noqa: E501
|
| 226 |
+
quant_config.get("input_activations")
|
| 227 |
+
)
|
| 228 |
+
)
|
| 229 |
+
return target_scheme_map
|
| 230 |
+
|
| 231 |
+
@classmethod
|
| 232 |
+
def get_config_filenames(cls) -> List[str]:
|
| 233 |
+
return []
|
| 234 |
+
|
| 235 |
+
def _check_scheme_supported(self, min_capability: int, error: bool = True) -> bool:
|
| 236 |
+
capability_tuple = DeviceCapability(*torch.cuda.get_device_capability())
|
| 237 |
+
|
| 238 |
+
if capability_tuple is not None:
|
| 239 |
+
capability = capability_tuple.to_int()
|
| 240 |
+
supported = capability >= min_capability
|
| 241 |
+
if error and not supported:
|
| 242 |
+
raise RuntimeError(
|
| 243 |
+
"Quantization scheme is not supported for ",
|
| 244 |
+
f"the current GPU. Min capability: {min_capability}. ",
|
| 245 |
+
f"Current capability: {capability}.",
|
| 246 |
+
)
|
| 247 |
+
return supported
|
| 248 |
+
else:
|
| 249 |
+
return False
|
| 250 |
+
|
| 251 |
+
def _is_static_tensor_w8a8(
|
| 252 |
+
self, weight_quant: BaseModel, input_quant: BaseModel
|
| 253 |
+
) -> bool:
|
| 254 |
+
is_8_bits = weight_quant.num_bits == input_quant.num_bits == 8
|
| 255 |
+
weight_strategy = (
|
| 256 |
+
weight_quant.strategy == QuantizationStrategy.TENSOR.value
|
| 257 |
+
or weight_quant.strategy == QuantizationStrategy.CHANNEL.value
|
| 258 |
+
)
|
| 259 |
+
is_tensor = (
|
| 260 |
+
weight_strategy
|
| 261 |
+
and input_quant.strategy == QuantizationStrategy.TENSOR.value
|
| 262 |
+
)
|
| 263 |
+
is_static = not weight_quant.dynamic and not input_quant.dynamic
|
| 264 |
+
|
| 265 |
+
# Both symmetric and asymmetric input quantization supported.
|
| 266 |
+
# Only symmetric weight quantization supported.
|
| 267 |
+
return is_8_bits and is_tensor and weight_quant.symmetric and is_static
|
| 268 |
+
|
| 269 |
+
def _is_dynamic_token_w8a8(
|
| 270 |
+
self, weight_quant: BaseModel, input_quant: BaseModel
|
| 271 |
+
) -> bool:
|
| 272 |
+
is_8_bits = weight_quant.num_bits == input_quant.num_bits == 8
|
| 273 |
+
weight_strategy = (
|
| 274 |
+
weight_quant.strategy == QuantizationStrategy.TENSOR.value
|
| 275 |
+
or weight_quant.strategy == QuantizationStrategy.CHANNEL.value
|
| 276 |
+
)
|
| 277 |
+
is_token = (
|
| 278 |
+
weight_strategy and input_quant.strategy == QuantizationStrategy.TOKEN.value
|
| 279 |
+
)
|
| 280 |
+
is_dynamic = not weight_quant.dynamic and input_quant.dynamic
|
| 281 |
+
|
| 282 |
+
# Both symmetric and asymmetric input quantization supported.
|
| 283 |
+
# Only symmetric weight quantization supported.
|
| 284 |
+
return is_8_bits and is_token and weight_quant.symmetric and is_dynamic
|
| 285 |
+
|
| 286 |
+
def _is_fp8_w8a8(self, weight_quant: BaseModel, input_quant: BaseModel) -> bool:
|
| 287 |
+
# Confirm weights and activations quantized.
|
| 288 |
+
if weight_quant is None or input_quant is None:
|
| 289 |
+
return False
|
| 290 |
+
|
| 291 |
+
# Confirm weight scheme is supported.
|
| 292 |
+
is_floating_point = (
|
| 293 |
+
weight_quant.type == QuantizationType.FLOAT
|
| 294 |
+
and input_quant.type == QuantizationType.FLOAT
|
| 295 |
+
)
|
| 296 |
+
is_symmetric_weight = weight_quant.symmetric
|
| 297 |
+
is_static_weight = not weight_quant.dynamic
|
| 298 |
+
is_per_tensor_or_channel_weight = weight_quant.strategy in [
|
| 299 |
+
QuantizationStrategy.TENSOR,
|
| 300 |
+
QuantizationStrategy.CHANNEL,
|
| 301 |
+
]
|
| 302 |
+
if not (
|
| 303 |
+
is_floating_point
|
| 304 |
+
and is_symmetric_weight
|
| 305 |
+
and is_static_weight
|
| 306 |
+
and is_per_tensor_or_channel_weight
|
| 307 |
+
):
|
| 308 |
+
return False
|
| 309 |
+
|
| 310 |
+
# Dynamic quantization is always supported if weights supported.
|
| 311 |
+
if input_quant.dynamic:
|
| 312 |
+
return True
|
| 313 |
+
|
| 314 |
+
# Confirm activation scheme is supported.
|
| 315 |
+
is_symmetric_activation = input_quant.symmetric
|
| 316 |
+
is_per_tensor_activation = input_quant.strategy == QuantizationStrategy.TENSOR
|
| 317 |
+
return is_symmetric_activation and is_per_tensor_activation
|
| 318 |
+
|
| 319 |
+
def _is_fp8_w8a16(self, weight_quant: BaseModel, input_quant: BaseModel) -> bool:
|
| 320 |
+
# Confirm weights quantized.
|
| 321 |
+
if weight_quant is None:
|
| 322 |
+
return False
|
| 323 |
+
|
| 324 |
+
# Confirm we have floating points.
|
| 325 |
+
if weight_quant.type != QuantizationType.FLOAT:
|
| 326 |
+
return False
|
| 327 |
+
|
| 328 |
+
# Confirm weight scheme is supported.
|
| 329 |
+
is_symmetric_weight = weight_quant.symmetric
|
| 330 |
+
is_static_weight = not weight_quant.dynamic
|
| 331 |
+
is_per_tensor_or_channel_weight = weight_quant.strategy in [
|
| 332 |
+
QuantizationStrategy.TENSOR,
|
| 333 |
+
QuantizationStrategy.CHANNEL,
|
| 334 |
+
]
|
| 335 |
+
if not (
|
| 336 |
+
is_symmetric_weight
|
| 337 |
+
and is_static_weight # noqa: SIM103
|
| 338 |
+
and is_per_tensor_or_channel_weight
|
| 339 |
+
):
|
| 340 |
+
return False
|
| 341 |
+
|
| 342 |
+
# All conditions satisfied.
|
| 343 |
+
return True
|
| 344 |
+
|
| 345 |
+
def _is_wNa16_group_channel(
|
| 346 |
+
self, weight_quant: BaseModel, input_quant: BaseModel
|
| 347 |
+
) -> bool:
|
| 348 |
+
input_quant_none = input_quant is None
|
| 349 |
+
is_symmetric = weight_quant.symmetric
|
| 350 |
+
is_channel_group = (
|
| 351 |
+
weight_quant.strategy == QuantizationStrategy.CHANNEL.value
|
| 352 |
+
or weight_quant.strategy == QuantizationStrategy.GROUP.value
|
| 353 |
+
)
|
| 354 |
+
is_static = not weight_quant.dynamic
|
| 355 |
+
|
| 356 |
+
return is_channel_group and input_quant_none and is_symmetric and is_static
|
| 357 |
+
|
| 358 |
+
def _get_scheme_from_parts(
|
| 359 |
+
self, weight_quant: BaseModel, input_quant: BaseModel
|
| 360 |
+
) -> "CompressedTensorsScheme":
|
| 361 |
+
|
| 362 |
+
# Detect If Mixed Precision
|
| 363 |
+
if self._is_wNa16_group_channel(weight_quant, input_quant):
|
| 364 |
+
if not VLLM_AVAILABLE:
|
| 365 |
+
raise ImportError(
|
| 366 |
+
"vllm is not installed, to use CompressedTensorsW4A16Sparse24 and CompressedTensorsWNA16, please install vllm"
|
| 367 |
+
)
|
| 368 |
+
if (
|
| 369 |
+
self.quant_format == CompressionFormat.marlin_24.value
|
| 370 |
+
and weight_quant.num_bits in W4A16SPARSE24_SUPPORTED_BITS
|
| 371 |
+
):
|
| 372 |
+
return CompressedTensorsW4A16Sparse24(
|
| 373 |
+
strategy=weight_quant.strategy,
|
| 374 |
+
num_bits=weight_quant.num_bits,
|
| 375 |
+
group_size=weight_quant.group_size,
|
| 376 |
+
)
|
| 377 |
+
if (
|
| 378 |
+
self.quant_format == CompressionFormat.pack_quantized.value
|
| 379 |
+
and weight_quant.num_bits in WNA16_SUPPORTED_BITS
|
| 380 |
+
):
|
| 381 |
+
return CompressedTensorsWNA16(
|
| 382 |
+
num_bits=weight_quant.num_bits,
|
| 383 |
+
strategy=weight_quant.strategy,
|
| 384 |
+
group_size=weight_quant.group_size,
|
| 385 |
+
actorder=weight_quant.actorder,
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
if is_activation_quantization_format(self.quant_format):
|
| 389 |
+
if self._is_fp8_w8a8(weight_quant, input_quant):
|
| 390 |
+
is_fp8_w8a8_supported = self._check_scheme_supported(
|
| 391 |
+
CompressedTensorsW8A8Fp8.get_min_capability(), error=False
|
| 392 |
+
)
|
| 393 |
+
if is_fp8_w8a8_supported:
|
| 394 |
+
return CompressedTensorsW8A8Fp8(
|
| 395 |
+
strategy=weight_quant.strategy,
|
| 396 |
+
is_static_input_scheme=(
|
| 397 |
+
input_quant and not input_quant.dynamic
|
| 398 |
+
),
|
| 399 |
+
)
|
| 400 |
+
else:
|
| 401 |
+
# note: input_quant will be present for converted models;
|
| 402 |
+
# will be ignored during inference post loading
|
| 403 |
+
return CompressedTensorsW8A16Fp8(
|
| 404 |
+
strategy=weight_quant.strategy,
|
| 405 |
+
is_static_input_scheme=not input_quant.dynamic,
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
# note: input_quant can be None
|
| 409 |
+
if self._is_fp8_w8a16(weight_quant, input_quant):
|
| 410 |
+
if not VLLM_AVAILABLE:
|
| 411 |
+
raise ImportError(
|
| 412 |
+
"vllm is not installed, to use CompressedTensorsW8A16Fp8, please install vllm"
|
| 413 |
+
)
|
| 414 |
+
is_static_input_scheme = input_quant and not input_quant.dynamic
|
| 415 |
+
return CompressedTensorsW8A16Fp8(
|
| 416 |
+
strategy=weight_quant.strategy,
|
| 417 |
+
is_static_input_scheme=is_static_input_scheme,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
if self._is_static_tensor_w8a8(weight_quant, input_quant):
|
| 421 |
+
return CompressedTensorsW8A8Int8(
|
| 422 |
+
strategy=weight_quant.strategy,
|
| 423 |
+
is_static_input_scheme=True,
|
| 424 |
+
input_symmetric=input_quant.symmetric,
|
| 425 |
+
)
|
| 426 |
+
|
| 427 |
+
if self._is_dynamic_token_w8a8(weight_quant, input_quant):
|
| 428 |
+
return CompressedTensorsW8A8Int8(
|
| 429 |
+
strategy=weight_quant.strategy,
|
| 430 |
+
is_static_input_scheme=False,
|
| 431 |
+
input_symmetric=input_quant.symmetric,
|
| 432 |
+
)
|
| 433 |
+
|
| 434 |
+
raise NotImplementedError("No compressed-tensors compatible scheme was found.")
|
| 435 |
+
|
| 436 |
+
def get_scheme(
|
| 437 |
+
self, layer: torch.nn.Module, layer_name: Optional[str] = None
|
| 438 |
+
) -> Optional["CompressedTensorsScheme"]:
|
| 439 |
+
"""
|
| 440 |
+
compressed-tensors supports non uniform in the following way:
|
| 441 |
+
|
| 442 |
+
targets of config_groups: There can be N config_groups which each
|
| 443 |
+
have a quantization scheme. Each config_group has a list of targets
|
| 444 |
+
which can be a full layer_name, a regex for a layer_name, or
|
| 445 |
+
an nn.Module name.
|
| 446 |
+
|
| 447 |
+
Detect whether a layer_name is found in any target and
|
| 448 |
+
use the quantization scheme corresponding to the matched target
|
| 449 |
+
to select the CompressedTensorsScheme used for infernece.
|
| 450 |
+
"""
|
| 451 |
+
|
| 452 |
+
# Find the "target" in the compressed-tensors config
|
| 453 |
+
# that our layer conforms to.
|
| 454 |
+
# TODO (@robertgshaw): add compressed-tensors as dep
|
| 455 |
+
# so we do not have to re-write these functions
|
| 456 |
+
# need to make accelerate optional in ct to do this
|
| 457 |
+
|
| 458 |
+
# Will be empty for models with only sparsity
|
| 459 |
+
weight_quant = input_quant = None
|
| 460 |
+
if self.target_scheme_map:
|
| 461 |
+
matched_target = find_matched_target(
|
| 462 |
+
layer_name=layer_name,
|
| 463 |
+
module=layer,
|
| 464 |
+
targets=self.target_scheme_map.keys(),
|
| 465 |
+
fused_mapping=self.packed_modules_mapping,
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
scheme_dict = self.target_scheme_map[matched_target]
|
| 469 |
+
weight_quant = scheme_dict.get("weights")
|
| 470 |
+
input_quant = scheme_dict.get("input_activations")
|
| 471 |
+
|
| 472 |
+
# Find the sparsity scheme of the layer
|
| 473 |
+
# assume that fused layers inerhit first component's sparsity scheme
|
| 474 |
+
sparsity_targets = self.sparsity_scheme_map.keys() - set(
|
| 475 |
+
self.sparsity_ignore_list
|
| 476 |
+
)
|
| 477 |
+
sparsity_scheme: Optional[SparsityCompressionConfig] = None
|
| 478 |
+
with suppress(ValueError):
|
| 479 |
+
matched_target = find_matched_target(
|
| 480 |
+
layer_name=layer_name,
|
| 481 |
+
module=layer,
|
| 482 |
+
targets=sparsity_targets,
|
| 483 |
+
fused_mapping=self.packed_modules_mapping,
|
| 484 |
+
)
|
| 485 |
+
sparsity_scheme = self.sparsity_scheme_map[matched_target]
|
| 486 |
+
|
| 487 |
+
if self.supports_cutlass_24(
|
| 488 |
+
weight_quant=weight_quant,
|
| 489 |
+
input_quant=input_quant,
|
| 490 |
+
sparsity_scheme=sparsity_scheme,
|
| 491 |
+
):
|
| 492 |
+
if not VLLM_AVAILABLE:
|
| 493 |
+
raise ImportError(
|
| 494 |
+
"vllm is not installed, to use CompressedTensors24, please install vllm"
|
| 495 |
+
)
|
| 496 |
+
# Have a valid sparsity scheme
|
| 497 |
+
# Validate layer is supported by Cutlass 2:4 Kernel
|
| 498 |
+
model_compression_config = (
|
| 499 |
+
None
|
| 500 |
+
if sparsity_scheme is None or sparsity_scheme.format == "dense"
|
| 501 |
+
else self.config
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
scheme = CompressedTensors24(
|
| 505 |
+
quantized=weight_quant is not None or input_quant is not None,
|
| 506 |
+
weight_quant=weight_quant,
|
| 507 |
+
input_quant=input_quant,
|
| 508 |
+
model_compression_config=model_compression_config,
|
| 509 |
+
)
|
| 510 |
+
elif weight_quant is None:
|
| 511 |
+
logger.warning_once(
|
| 512 |
+
"Acceleration for non-quantized schemes is "
|
| 513 |
+
"not supported by Compressed Tensors. "
|
| 514 |
+
"Falling back to UnquantizedLinearMethod"
|
| 515 |
+
)
|
| 516 |
+
return None
|
| 517 |
+
|
| 518 |
+
else:
|
| 519 |
+
# Find the quant_scheme
|
| 520 |
+
scheme = self._get_scheme_from_parts( # type: ignore
|
| 521 |
+
weight_quant=weight_quant,
|
| 522 |
+
input_quant=input_quant,
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
# Raise error if device does not support the scheme
|
| 526 |
+
# (e.g. fp8 needs ada lovelace)
|
| 527 |
+
self._check_scheme_supported(scheme.get_min_capability())
|
| 528 |
+
logger.debug("Using scheme: %s for %s", scheme.__class__.__name__, layer_name)
|
| 529 |
+
return scheme
|
| 530 |
+
|
| 531 |
+
def get_cache_scale(self, name: str) -> Optional[str]:
|
| 532 |
+
"""
|
| 533 |
+
Check whether the param name matches the format for k/v cache scales
|
| 534 |
+
in compressed-tensors. If this is the case, return its equivalent
|
| 535 |
+
param name expected by vLLM
|
| 536 |
+
|
| 537 |
+
:param name: param name
|
| 538 |
+
:return: matching param name for KV cache scale in vLLM
|
| 539 |
+
"""
|
| 540 |
+
if name.endswith(".output_scale") and ".k_proj" in name:
|
| 541 |
+
return name.replace(".k_proj.output_scale", ".attn.k_scale")
|
| 542 |
+
if name.endswith(".output_scale") and ".v_proj" in name:
|
| 543 |
+
return name.replace(".v_proj.output_scale", ".attn.v_scale")
|
| 544 |
+
# If no matches, return None
|
| 545 |
+
return None
|
| 546 |
+
|
| 547 |
+
@staticmethod
|
| 548 |
+
def supports_cutlass_24(
|
| 549 |
+
weight_quant: Optional[QuantizationArgs],
|
| 550 |
+
input_quant: Optional[QuantizationArgs],
|
| 551 |
+
sparsity_scheme: Optional[SparsityCompressionConfig] = None,
|
| 552 |
+
) -> bool:
|
| 553 |
+
"""
|
| 554 |
+
Check if the layer is supported by the Cutlass 2:4 Kernel
|
| 555 |
+
Conditions:
|
| 556 |
+
- Overarching condition: Sparsity Structure is 2:4
|
| 557 |
+
- Unquantized cases are supported
|
| 558 |
+
- Weight only quantization is not-supported
|
| 559 |
+
- Supported weight quantization strategies are TENSOR and CHANNEL
|
| 560 |
+
- Supported input quantization strategies are TENSOR and TOKEN
|
| 561 |
+
- Only 8 bit quantization is supported
|
| 562 |
+
|
| 563 |
+
:return: True if the layer is supported by the Cutlass 2:4 Kernel
|
| 564 |
+
False otherwise
|
| 565 |
+
"""
|
| 566 |
+
if sparsity_scheme is None:
|
| 567 |
+
return False
|
| 568 |
+
|
| 569 |
+
is_valid_sparsity_structure: bool = (
|
| 570 |
+
sparsity_scheme.sparsity_structure == SparsityStructure.TWO_FOUR.value
|
| 571 |
+
)
|
| 572 |
+
|
| 573 |
+
valid_compressors = {
|
| 574 |
+
CompressionFormat.dense.value,
|
| 575 |
+
CompressionFormat.sparse_24_bitmask.value,
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
is_valid_sparsity = (
|
| 579 |
+
is_valid_sparsity_structure and sparsity_scheme.format in valid_compressors
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
if not is_valid_sparsity:
|
| 583 |
+
return False
|
| 584 |
+
|
| 585 |
+
# Unquantized cases are supported
|
| 586 |
+
if weight_quant is None and input_quant is None:
|
| 587 |
+
return True
|
| 588 |
+
|
| 589 |
+
# Weight only quantization is not-supported
|
| 590 |
+
if weight_quant is not None and input_quant is None:
|
| 591 |
+
return False
|
| 592 |
+
|
| 593 |
+
supported_weight_quant_strategies = [
|
| 594 |
+
QuantizationStrategy.TENSOR.value,
|
| 595 |
+
QuantizationStrategy.CHANNEL.value,
|
| 596 |
+
]
|
| 597 |
+
|
| 598 |
+
assert weight_quant is not None
|
| 599 |
+
assert input_quant is not None
|
| 600 |
+
if weight_quant.strategy not in supported_weight_quant_strategies:
|
| 601 |
+
return False
|
| 602 |
+
|
| 603 |
+
supported_input_quant_strategies = [
|
| 604 |
+
QuantizationStrategy.TENSOR.value,
|
| 605 |
+
QuantizationStrategy.TOKEN.value,
|
| 606 |
+
]
|
| 607 |
+
|
| 608 |
+
if input_quant.strategy not in supported_input_quant_strategies:
|
| 609 |
+
return False
|
| 610 |
+
|
| 611 |
+
return weight_quant.num_bits == input_quant.num_bits == 8
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
class CompressedTensorsLinearMethod(LinearMethodBase):
|
| 615 |
+
|
| 616 |
+
def __init__(self, quantization_config: CompressedTensorsConfig):
|
| 617 |
+
self.quantization_config = quantization_config
|
| 618 |
+
|
| 619 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 620 |
+
layer.scheme.process_weights_after_loading(layer)
|
| 621 |
+
|
| 622 |
+
def create_weights(
|
| 623 |
+
self,
|
| 624 |
+
layer: torch.nn.Module,
|
| 625 |
+
input_size_per_partition: int,
|
| 626 |
+
output_partition_sizes: List[int],
|
| 627 |
+
input_size: int,
|
| 628 |
+
output_size: int,
|
| 629 |
+
params_dtype: torch.dtype,
|
| 630 |
+
**extra_weight_attrs,
|
| 631 |
+
):
|
| 632 |
+
"""
|
| 633 |
+
Use the CompressedTensorsScheme associated with each layer to create
|
| 634 |
+
the necessary parameters for the layer. See LinearMethodBase for param
|
| 635 |
+
details
|
| 636 |
+
"""
|
| 637 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 638 |
+
layer.scheme.create_weights(
|
| 639 |
+
layer=layer,
|
| 640 |
+
input_size=input_size,
|
| 641 |
+
input_size_per_partition=input_size_per_partition,
|
| 642 |
+
output_partition_sizes=output_partition_sizes,
|
| 643 |
+
output_size=output_size,
|
| 644 |
+
params_dtype=params_dtype,
|
| 645 |
+
weight_loader=weight_loader,
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
def apply(
|
| 649 |
+
self,
|
| 650 |
+
layer: torch.nn.Module,
|
| 651 |
+
x: torch.Tensor,
|
| 652 |
+
bias: Optional[torch.Tensor] = None,
|
| 653 |
+
):
|
| 654 |
+
"""
|
| 655 |
+
Use the output of create_weights and the CompressedTensorsScheme
|
| 656 |
+
associated with the layer to apply the forward pass with the
|
| 657 |
+
layer input. See LinearMethodBase for param details
|
| 658 |
+
|
| 659 |
+
"""
|
| 660 |
+
|
| 661 |
+
scheme = layer.scheme
|
| 662 |
+
if scheme is None:
|
| 663 |
+
raise ValueError("A scheme must be defined for each layer")
|
| 664 |
+
return scheme.apply_weights(layer, x, bias=bias)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/compressed_tensors_moe.py
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/tree/v0.8.2/vllm/model_executor/layers/quantization/compressed_tensors
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
import enum
|
| 5 |
+
import logging
|
| 6 |
+
from enum import Enum
|
| 7 |
+
from typing import Callable, List, Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from compressed_tensors import CompressionFormat
|
| 11 |
+
from compressed_tensors.quantization import QuantizationStrategy
|
| 12 |
+
|
| 13 |
+
from sglang.srt.layers.quantization.fp8_kernel import scaled_fp8_quant
|
| 14 |
+
from sglang.srt.layers.quantization.fp8_utils import normalize_e4m3fn_to_e4m3fnuz
|
| 15 |
+
from sglang.srt.layers.quantization.utils import (
|
| 16 |
+
all_close_1d,
|
| 17 |
+
is_cuda,
|
| 18 |
+
is_fp8_fnuz,
|
| 19 |
+
per_tensor_dequantize,
|
| 20 |
+
replace_parameter,
|
| 21 |
+
)
|
| 22 |
+
from sglang.srt.utils import set_weight_attrs
|
| 23 |
+
|
| 24 |
+
_is_cuda = is_cuda()
|
| 25 |
+
|
| 26 |
+
if not _is_cuda:
|
| 27 |
+
from vllm import _custom_ops as vllm_ops
|
| 28 |
+
from vllm._custom_ops import scaled_fp8_quant
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
import vllm
|
| 32 |
+
|
| 33 |
+
VLLM_AVAILABLE = True
|
| 34 |
+
except ImportError:
|
| 35 |
+
VLLM_AVAILABLE = False
|
| 36 |
+
|
| 37 |
+
logger = logging.getLogger(__name__)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class GPTQMarlinState(Enum):
|
| 41 |
+
REPACK = enum.auto()
|
| 42 |
+
READY = enum.auto()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
__all__ = [
|
| 46 |
+
"CompressedTensorsMoEMethod",
|
| 47 |
+
"CompressedTensorsW8A8Fp8MoEMethod",
|
| 48 |
+
"CompressedTensorsWNA16MoEMethod",
|
| 49 |
+
]
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class CompressedTensorsMoEMethod:
|
| 53 |
+
def __new__(cls, *args, **kwargs):
|
| 54 |
+
if cls is CompressedTensorsMoEMethod:
|
| 55 |
+
return super().__new__(cls)
|
| 56 |
+
return super().__new__(cls)
|
| 57 |
+
|
| 58 |
+
@staticmethod
|
| 59 |
+
def get_moe_method(
|
| 60 |
+
quant_config: "CompressedTensorsConfig", # type: ignore # noqa E501
|
| 61 |
+
) -> "CompressedTensorsMoEMethod":
|
| 62 |
+
# TODO: @dsikka: refactor this to use schemes as other kernels
|
| 63 |
+
# are supported + check if the layer is being ignored.
|
| 64 |
+
weight_quant = quant_config.target_scheme_map["Linear"].get("weights")
|
| 65 |
+
input_quant = quant_config.target_scheme_map["Linear"].get("input_activations")
|
| 66 |
+
|
| 67 |
+
if quant_config._is_wNa16_group_channel(weight_quant, input_quant):
|
| 68 |
+
if not VLLM_AVAILABLE:
|
| 69 |
+
raise ImportError(
|
| 70 |
+
"vllm is not installed, to use CompressedTensorsWNA16MoEMethod, please install vllm."
|
| 71 |
+
)
|
| 72 |
+
return CompressedTensorsWNA16MoEMethod(quant_config)
|
| 73 |
+
elif quant_config._is_fp8_w8a8(weight_quant, input_quant):
|
| 74 |
+
return CompressedTensorsW8A8Fp8MoEMethod(quant_config)
|
| 75 |
+
else:
|
| 76 |
+
raise RuntimeError(
|
| 77 |
+
f"Unsupported FusedMoe scheme: {weight_quant}, {input_quant}"
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
|
| 82 |
+
|
| 83 |
+
def __init__(
|
| 84 |
+
self, quant_config: "CompressedTensorsConfig" # type: ignore # noqa E501
|
| 85 |
+
):
|
| 86 |
+
self.quant_config = quant_config
|
| 87 |
+
self.weight_quant = self.quant_config.target_scheme_map["Linear"].get("weights")
|
| 88 |
+
self.input_quant = self.quant_config.target_scheme_map["Linear"].get(
|
| 89 |
+
"input_activations"
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
self.static_input_scales = not self.input_quant.dynamic
|
| 93 |
+
|
| 94 |
+
def create_weights(
|
| 95 |
+
self,
|
| 96 |
+
layer: torch.nn.Module,
|
| 97 |
+
num_experts: int,
|
| 98 |
+
hidden_size: int,
|
| 99 |
+
intermediate_size_per_partition: int,
|
| 100 |
+
params_dtype: torch.dtype,
|
| 101 |
+
**extra_weight_attrs,
|
| 102 |
+
):
|
| 103 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoeWeightScaleSupported
|
| 104 |
+
|
| 105 |
+
params_dtype = torch.float8_e4m3fn
|
| 106 |
+
|
| 107 |
+
# WEIGHTS
|
| 108 |
+
w13_weight = torch.nn.Parameter(
|
| 109 |
+
torch.empty(
|
| 110 |
+
num_experts,
|
| 111 |
+
2 * intermediate_size_per_partition,
|
| 112 |
+
hidden_size,
|
| 113 |
+
dtype=params_dtype,
|
| 114 |
+
),
|
| 115 |
+
requires_grad=False,
|
| 116 |
+
)
|
| 117 |
+
layer.register_parameter("w13_weight", w13_weight)
|
| 118 |
+
set_weight_attrs(w13_weight, extra_weight_attrs)
|
| 119 |
+
|
| 120 |
+
w2_weight = torch.nn.Parameter(
|
| 121 |
+
torch.empty(
|
| 122 |
+
num_experts,
|
| 123 |
+
hidden_size,
|
| 124 |
+
intermediate_size_per_partition,
|
| 125 |
+
dtype=params_dtype,
|
| 126 |
+
),
|
| 127 |
+
requires_grad=False,
|
| 128 |
+
)
|
| 129 |
+
layer.register_parameter("w2_weight", w2_weight)
|
| 130 |
+
set_weight_attrs(w2_weight, extra_weight_attrs)
|
| 131 |
+
|
| 132 |
+
# WEIGHT_SCALES
|
| 133 |
+
# per-tensor quantization
|
| 134 |
+
if self.weight_quant.strategy == QuantizationStrategy.TENSOR:
|
| 135 |
+
# Allocate 2 scales for w1 and w3 respectively.
|
| 136 |
+
# They will be combined to a single scale after weight loading.
|
| 137 |
+
w13_weight_scale = torch.nn.Parameter(
|
| 138 |
+
torch.ones(num_experts, 2, dtype=torch.float32), requires_grad=False
|
| 139 |
+
)
|
| 140 |
+
w2_weight_scale = torch.nn.Parameter(
|
| 141 |
+
torch.ones(num_experts, dtype=torch.float32), requires_grad=False
|
| 142 |
+
)
|
| 143 |
+
weight_quant_method = FusedMoeWeightScaleSupported.TENSOR.value
|
| 144 |
+
elif self.weight_quant.strategy == QuantizationStrategy.CHANNEL:
|
| 145 |
+
w13_weight_scale = torch.nn.Parameter(
|
| 146 |
+
torch.ones(
|
| 147 |
+
num_experts,
|
| 148 |
+
2 * intermediate_size_per_partition,
|
| 149 |
+
1,
|
| 150 |
+
dtype=torch.float32,
|
| 151 |
+
),
|
| 152 |
+
requires_grad=False,
|
| 153 |
+
)
|
| 154 |
+
w2_weight_scale = torch.nn.Parameter(
|
| 155 |
+
torch.ones(num_experts, hidden_size, 1, dtype=torch.float32),
|
| 156 |
+
requires_grad=False,
|
| 157 |
+
)
|
| 158 |
+
weight_quant_method = FusedMoeWeightScaleSupported.CHANNEL.value
|
| 159 |
+
else:
|
| 160 |
+
raise ValueError(
|
| 161 |
+
f"Unsupported weight quantization strategy: {self.weight_quant.strategy}"
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
layer.register_parameter("w13_weight_scale", w13_weight_scale)
|
| 165 |
+
layer.register_parameter("w2_weight_scale", w2_weight_scale)
|
| 166 |
+
# Add the quantization method used (per tensor/grouped/channel)
|
| 167 |
+
# to ensure the weight scales are loaded in properly
|
| 168 |
+
extra_weight_attrs.update({"quant_method": weight_quant_method})
|
| 169 |
+
set_weight_attrs(w13_weight_scale, extra_weight_attrs)
|
| 170 |
+
set_weight_attrs(w2_weight_scale, extra_weight_attrs)
|
| 171 |
+
|
| 172 |
+
# INPUT_SCALES
|
| 173 |
+
if self.static_input_scales:
|
| 174 |
+
assert (
|
| 175 |
+
self.input_quant.strategy == QuantizationStrategy.TENSOR
|
| 176 |
+
), "Only per-tensor quantization is supported for static input scales"
|
| 177 |
+
w13_input_scale = torch.nn.Parameter(
|
| 178 |
+
torch.ones(num_experts, dtype=torch.float32), requires_grad=False
|
| 179 |
+
)
|
| 180 |
+
layer.register_parameter("w13_input_scale", w13_input_scale)
|
| 181 |
+
set_weight_attrs(w13_input_scale, extra_weight_attrs)
|
| 182 |
+
|
| 183 |
+
w2_input_scale = torch.nn.Parameter(
|
| 184 |
+
torch.ones(num_experts, dtype=torch.float32), requires_grad=False
|
| 185 |
+
)
|
| 186 |
+
layer.register_parameter("w2_input_scale", w2_input_scale)
|
| 187 |
+
set_weight_attrs(w2_input_scale, extra_weight_attrs)
|
| 188 |
+
else:
|
| 189 |
+
layer.w13_input_scale = None
|
| 190 |
+
layer.w2_input_scale = None
|
| 191 |
+
|
| 192 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 193 |
+
# Fp8 moe kernels require a single activation scale.
|
| 194 |
+
# We take the max of all the scales in case they differ.
|
| 195 |
+
if self.static_input_scales:
|
| 196 |
+
if layer.w13_input_scale is None or layer.w2_input_scale is None:
|
| 197 |
+
raise ValueError(
|
| 198 |
+
"QuantConfig has static quantization, but found "
|
| 199 |
+
"activation scales are None."
|
| 200 |
+
)
|
| 201 |
+
if not all_close_1d(layer.w13_input_scale) or not all_close_1d(
|
| 202 |
+
layer.w2_input_scale
|
| 203 |
+
):
|
| 204 |
+
logger.warning(
|
| 205 |
+
"Found input_scales that are not equal for "
|
| 206 |
+
"fp8 MoE layer. Using the maximum across experts "
|
| 207 |
+
"for each layer."
|
| 208 |
+
)
|
| 209 |
+
layer.w13_input_scale = torch.nn.Parameter(
|
| 210 |
+
layer.w13_input_scale.max(), requires_grad=False
|
| 211 |
+
)
|
| 212 |
+
layer.w2_input_scale = torch.nn.Parameter(
|
| 213 |
+
layer.w2_input_scale.max(), requires_grad=False
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
if is_fp8_fnuz():
|
| 217 |
+
# Normalize the weights and scales
|
| 218 |
+
w13_weight, w13_weight_scale, w13_input_scale = (
|
| 219 |
+
normalize_e4m3fn_to_e4m3fnuz(
|
| 220 |
+
layer.w13_weight, layer.w13_weight_scale, layer.w13_input_scale
|
| 221 |
+
)
|
| 222 |
+
)
|
| 223 |
+
w2_weight, w2_weight_scale, w2_input_scale = normalize_e4m3fn_to_e4m3fnuz(
|
| 224 |
+
layer.w2_weight, layer.w2_weight_scale, layer.w2_input_scale
|
| 225 |
+
)
|
| 226 |
+
# Reset the parameter
|
| 227 |
+
layer.w13_weight = torch.nn.Parameter(w13_weight, requires_grad=False)
|
| 228 |
+
layer.w13_weight_scale = torch.nn.Parameter(
|
| 229 |
+
w13_weight_scale, requires_grad=False
|
| 230 |
+
)
|
| 231 |
+
if w13_input_scale is not None:
|
| 232 |
+
layer.w13_input_scale = torch.nn.Parameter(
|
| 233 |
+
w13_input_scale, requires_grad=False
|
| 234 |
+
)
|
| 235 |
+
layer.w2_weight = torch.nn.Parameter(w2_weight, requires_grad=False)
|
| 236 |
+
layer.w2_weight_scale = torch.nn.Parameter(
|
| 237 |
+
w2_weight_scale, requires_grad=False
|
| 238 |
+
)
|
| 239 |
+
if w2_input_scale is not None:
|
| 240 |
+
layer.w2_input_scale = torch.nn.Parameter(
|
| 241 |
+
w2_input_scale, requires_grad=False
|
| 242 |
+
)
|
| 243 |
+
if self.weight_quant.strategy == QuantizationStrategy.TENSOR:
|
| 244 |
+
# Fp8 moe kernel needs single weight scale for w13 per expert.
|
| 245 |
+
# We take the max then dequant and requant each expert.
|
| 246 |
+
assert layer.w13_weight_scale is not None
|
| 247 |
+
shard_size = layer.intermediate_size_per_partition
|
| 248 |
+
max_w13_scales = layer.w13_weight_scale.max(dim=1).values
|
| 249 |
+
for expert_id in range(layer.local_num_experts):
|
| 250 |
+
start = 0
|
| 251 |
+
for shard_id in range(2):
|
| 252 |
+
dq_weight = per_tensor_dequantize(
|
| 253 |
+
layer.w13_weight[expert_id][start : start + shard_size, :],
|
| 254 |
+
layer.w13_weight_scale[expert_id][shard_id],
|
| 255 |
+
)
|
| 256 |
+
(
|
| 257 |
+
layer.w13_weight[expert_id][start : start + shard_size, :],
|
| 258 |
+
_,
|
| 259 |
+
) = scaled_fp8_quant(dq_weight, max_w13_scales[expert_id])
|
| 260 |
+
|
| 261 |
+
start += shard_size
|
| 262 |
+
|
| 263 |
+
layer.w13_weight_scale = torch.nn.Parameter(
|
| 264 |
+
max_w13_scales, requires_grad=False
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
def apply(
|
| 268 |
+
self,
|
| 269 |
+
layer: torch.nn.Module,
|
| 270 |
+
x: torch.Tensor,
|
| 271 |
+
router_logits: torch.Tensor,
|
| 272 |
+
top_k: int,
|
| 273 |
+
renormalize: bool,
|
| 274 |
+
use_grouped_topk: bool = False,
|
| 275 |
+
topk_group: Optional[int] = None,
|
| 276 |
+
num_expert_group: Optional[int] = None,
|
| 277 |
+
global_num_experts: int = -1,
|
| 278 |
+
expert_map: Optional[torch.Tensor] = None,
|
| 279 |
+
custom_routing_function: Optional[Callable] = None,
|
| 280 |
+
scoring_func: str = "softmax",
|
| 281 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 282 |
+
activation: str = "silu",
|
| 283 |
+
inplace: bool = True,
|
| 284 |
+
no_combine: bool = False,
|
| 285 |
+
apply_router_weight_on_input: bool = False,
|
| 286 |
+
routed_scaling_factor: Optional[float] = None,
|
| 287 |
+
) -> torch.Tensor:
|
| 288 |
+
from sglang.srt.layers.moe.fused_moe_triton import fused_experts
|
| 289 |
+
from sglang.srt.layers.moe.topk import select_experts
|
| 290 |
+
|
| 291 |
+
topk_weights, topk_ids = select_experts(
|
| 292 |
+
hidden_states=x,
|
| 293 |
+
router_logits=router_logits,
|
| 294 |
+
use_grouped_topk=use_grouped_topk,
|
| 295 |
+
top_k=top_k,
|
| 296 |
+
renormalize=renormalize,
|
| 297 |
+
topk_group=topk_group,
|
| 298 |
+
num_expert_group=num_expert_group,
|
| 299 |
+
custom_routing_function=custom_routing_function,
|
| 300 |
+
correction_bias=correction_bias,
|
| 301 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
return fused_experts(
|
| 305 |
+
x,
|
| 306 |
+
layer.w13_weight,
|
| 307 |
+
layer.w2_weight,
|
| 308 |
+
topk_weights=topk_weights,
|
| 309 |
+
topk_ids=topk_ids,
|
| 310 |
+
inplace=inplace,
|
| 311 |
+
activation=activation,
|
| 312 |
+
use_fp8_w8a8=True,
|
| 313 |
+
per_channel_quant=self.weight_quant.strategy
|
| 314 |
+
== QuantizationStrategy.CHANNEL,
|
| 315 |
+
w1_scale=layer.w13_weight_scale,
|
| 316 |
+
w2_scale=layer.w2_weight_scale,
|
| 317 |
+
a1_scale=layer.w13_input_scale,
|
| 318 |
+
a2_scale=layer.w2_input_scale,
|
| 319 |
+
apply_router_weight_on_input=apply_router_weight_on_input,
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
|
| 324 |
+
|
| 325 |
+
def __init__(
|
| 326 |
+
self, quant_config: "CompressedTensorsConfig" # type: ignore # noqa E501
|
| 327 |
+
):
|
| 328 |
+
self.quant_config = quant_config
|
| 329 |
+
# TODO: @dsikka: refactor this to use schemes as other kernels
|
| 330 |
+
# are supported + check if the layer is being ignored.
|
| 331 |
+
config = self.quant_config.target_scheme_map["Linear"].get("weights")
|
| 332 |
+
self.num_bits = config.num_bits
|
| 333 |
+
self.packed_factor = 32 // config.num_bits
|
| 334 |
+
self.strategy = config.strategy
|
| 335 |
+
self.group_size = config.group_size
|
| 336 |
+
self.actorder = config.actorder
|
| 337 |
+
assert config.symmetric, "Only symmetric quantization is supported for MoE"
|
| 338 |
+
|
| 339 |
+
if not (
|
| 340 |
+
self.quant_config.quant_format == CompressionFormat.pack_quantized.value
|
| 341 |
+
and self.num_bits in WNA16_SUPPORTED_BITS
|
| 342 |
+
):
|
| 343 |
+
raise ValueError(
|
| 344 |
+
"For Fused MoE layers, only ",
|
| 345 |
+
f"{CompressionFormat.pack_quantized.value} ",
|
| 346 |
+
"is supported for the following bits: ",
|
| 347 |
+
f"{WNA16_SUPPORTED_BITS}",
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
def create_weights(
|
| 351 |
+
self,
|
| 352 |
+
layer: torch.nn.Module,
|
| 353 |
+
num_experts: int,
|
| 354 |
+
hidden_size: int,
|
| 355 |
+
intermediate_size_per_partition: int,
|
| 356 |
+
params_dtype: torch.dtype,
|
| 357 |
+
**extra_weight_attrs,
|
| 358 |
+
):
|
| 359 |
+
|
| 360 |
+
assert (
|
| 361 |
+
params_dtype == torch.float16
|
| 362 |
+
), "float16 is required for MoE compressed models. Set dtype=torch.float16" # noqa: E501
|
| 363 |
+
|
| 364 |
+
intermediate_size_full = extra_weight_attrs.pop("intermediate_size_full")
|
| 365 |
+
|
| 366 |
+
# Will transpose the loaded weight along the
|
| 367 |
+
# intermediate and hidden dim sizes. Will
|
| 368 |
+
# shard for TP along the transposed dims
|
| 369 |
+
extra_weight_attrs.update(
|
| 370 |
+
{"is_transposed": True, "quant_method": self.strategy}
|
| 371 |
+
)
|
| 372 |
+
w13_weight = torch.nn.Parameter(
|
| 373 |
+
torch.empty(
|
| 374 |
+
num_experts,
|
| 375 |
+
hidden_size // self.packed_factor,
|
| 376 |
+
2 * intermediate_size_per_partition,
|
| 377 |
+
dtype=torch.int32,
|
| 378 |
+
),
|
| 379 |
+
requires_grad=False,
|
| 380 |
+
)
|
| 381 |
+
layer.register_parameter("w13_weight_packed", w13_weight)
|
| 382 |
+
set_weight_attrs(w13_weight, extra_weight_attrs)
|
| 383 |
+
|
| 384 |
+
w2_weight = torch.nn.Parameter(
|
| 385 |
+
torch.empty(
|
| 386 |
+
num_experts,
|
| 387 |
+
intermediate_size_per_partition // self.packed_factor,
|
| 388 |
+
hidden_size,
|
| 389 |
+
dtype=torch.int32,
|
| 390 |
+
),
|
| 391 |
+
requires_grad=False,
|
| 392 |
+
)
|
| 393 |
+
layer.register_parameter("w2_weight_packed", w2_weight)
|
| 394 |
+
set_weight_attrs(w2_weight, extra_weight_attrs)
|
| 395 |
+
|
| 396 |
+
# In the case where we have actorder/g_idx,
|
| 397 |
+
# we do not partition the w2 scales
|
| 398 |
+
load_full_w2 = self.actorder and self.group_size != -1
|
| 399 |
+
w2_scales_size = (
|
| 400 |
+
intermediate_size_full if load_full_w2 else intermediate_size_per_partition
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
self.is_k_full = (not self.actorder) or (
|
| 404 |
+
intermediate_size_per_partition == intermediate_size_full
|
| 405 |
+
)
|
| 406 |
+
|
| 407 |
+
if self.strategy == "channel":
|
| 408 |
+
num_groups_w2 = num_groups_w13 = 1
|
| 409 |
+
self.group_size = -1
|
| 410 |
+
else:
|
| 411 |
+
num_groups_w2 = w2_scales_size // self.group_size
|
| 412 |
+
num_groups_w13 = hidden_size // self.group_size
|
| 413 |
+
|
| 414 |
+
w13_scale = torch.nn.Parameter(
|
| 415 |
+
torch.ones(
|
| 416 |
+
num_experts,
|
| 417 |
+
num_groups_w13,
|
| 418 |
+
2 * intermediate_size_per_partition,
|
| 419 |
+
dtype=params_dtype,
|
| 420 |
+
),
|
| 421 |
+
requires_grad=False,
|
| 422 |
+
)
|
| 423 |
+
layer.register_parameter("w13_weight_scale", w13_scale)
|
| 424 |
+
set_weight_attrs(w13_scale, extra_weight_attrs)
|
| 425 |
+
|
| 426 |
+
w2_scale = torch.nn.Parameter(
|
| 427 |
+
torch.ones(num_experts, num_groups_w2, hidden_size, dtype=params_dtype),
|
| 428 |
+
requires_grad=False,
|
| 429 |
+
)
|
| 430 |
+
layer.register_parameter("w2_weight_scale", w2_scale)
|
| 431 |
+
set_weight_attrs(w2_scale, extra_weight_attrs)
|
| 432 |
+
set_weight_attrs(w2_scale, {"load_full_w2": load_full_w2})
|
| 433 |
+
|
| 434 |
+
w2_weight_shape = torch.nn.Parameter(
|
| 435 |
+
torch.empty(num_experts, 2), requires_grad=False
|
| 436 |
+
)
|
| 437 |
+
layer.register_parameter("w2_weight_shape", w2_weight_shape)
|
| 438 |
+
set_weight_attrs(w2_weight_shape, extra_weight_attrs)
|
| 439 |
+
w13_weight_shape = torch.nn.Parameter(
|
| 440 |
+
torch.empty(num_experts, 2), requires_grad=False
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
layer.register_parameter("w13_weight_shape", w13_weight_shape)
|
| 444 |
+
set_weight_attrs(w13_weight_shape, extra_weight_attrs)
|
| 445 |
+
|
| 446 |
+
w13_g_idx = torch.nn.Parameter(
|
| 447 |
+
torch.empty(
|
| 448 |
+
num_experts,
|
| 449 |
+
hidden_size,
|
| 450 |
+
dtype=torch.int32,
|
| 451 |
+
),
|
| 452 |
+
requires_grad=False,
|
| 453 |
+
)
|
| 454 |
+
layer.register_parameter("w13_weight_g_idx", w13_g_idx)
|
| 455 |
+
set_weight_attrs(w13_g_idx, extra_weight_attrs)
|
| 456 |
+
|
| 457 |
+
w2_g_idx = torch.nn.Parameter(
|
| 458 |
+
torch.empty(
|
| 459 |
+
num_experts,
|
| 460 |
+
intermediate_size_per_partition,
|
| 461 |
+
dtype=torch.int32,
|
| 462 |
+
),
|
| 463 |
+
requires_grad=False,
|
| 464 |
+
)
|
| 465 |
+
layer.register_parameter("w2_weight_g_idx", w2_g_idx)
|
| 466 |
+
set_weight_attrs(w2_g_idx, extra_weight_attrs)
|
| 467 |
+
|
| 468 |
+
w13_g_idx_sort_indices = torch.nn.Parameter(
|
| 469 |
+
torch.empty(
|
| 470 |
+
num_experts,
|
| 471 |
+
hidden_size,
|
| 472 |
+
dtype=torch.int32,
|
| 473 |
+
),
|
| 474 |
+
requires_grad=False,
|
| 475 |
+
)
|
| 476 |
+
layer.register_parameter("w13_g_idx_sort_indices", w13_g_idx_sort_indices)
|
| 477 |
+
set_weight_attrs(w13_g_idx_sort_indices, extra_weight_attrs)
|
| 478 |
+
|
| 479 |
+
w2_g_idx_sort_indices = torch.nn.Parameter(
|
| 480 |
+
torch.empty(
|
| 481 |
+
num_experts,
|
| 482 |
+
intermediate_size_per_partition,
|
| 483 |
+
dtype=torch.int32,
|
| 484 |
+
),
|
| 485 |
+
requires_grad=False,
|
| 486 |
+
)
|
| 487 |
+
layer.register_parameter("w2_g_idx_sort_indices", w2_g_idx_sort_indices)
|
| 488 |
+
set_weight_attrs(w2_g_idx_sort_indices, extra_weight_attrs)
|
| 489 |
+
|
| 490 |
+
layer.a13_scale = None
|
| 491 |
+
layer.a2_scale = None
|
| 492 |
+
layer.marlin_state = GPTQMarlinState.REPACK
|
| 493 |
+
|
| 494 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 495 |
+
|
| 496 |
+
def replace_tensor(name, new_t):
|
| 497 |
+
# It is important to use resize_() here since it ensures
|
| 498 |
+
# the same buffer is reused
|
| 499 |
+
getattr(layer, name).resize_(new_t.shape)
|
| 500 |
+
getattr(layer, name).copy_(new_t)
|
| 501 |
+
del new_t
|
| 502 |
+
|
| 503 |
+
def get_scale_perms(num_bits: int):
|
| 504 |
+
scale_perm: List[int] = []
|
| 505 |
+
for i in range(8):
|
| 506 |
+
scale_perm.extend([i + 8 * j for j in range(8)])
|
| 507 |
+
scale_perm_single: List[int] = []
|
| 508 |
+
for i in range(4):
|
| 509 |
+
scale_perm_single.extend(
|
| 510 |
+
[2 * i + j for j in [0, 1, 8, 9, 16, 17, 24, 25]]
|
| 511 |
+
)
|
| 512 |
+
return scale_perm, scale_perm_single
|
| 513 |
+
|
| 514 |
+
def marlin_permute_scales(
|
| 515 |
+
s: torch.Tensor, size_k: int, size_n: int, group_size: int, num_bits: int
|
| 516 |
+
):
|
| 517 |
+
scale_perm, scale_perm_single = get_scale_perms(num_bits)
|
| 518 |
+
if group_size < size_k and group_size != -1:
|
| 519 |
+
s = s.reshape((-1, len(scale_perm)))[:, scale_perm]
|
| 520 |
+
else:
|
| 521 |
+
s = s.reshape((-1, len(scale_perm_single)))[:, scale_perm_single]
|
| 522 |
+
s = s.reshape((-1, size_n)).contiguous()
|
| 523 |
+
return s
|
| 524 |
+
|
| 525 |
+
def marlin_moe_permute_scales(
|
| 526 |
+
s: torch.Tensor, size_k: int, size_n: int, group_size: int, num_bits: int
|
| 527 |
+
):
|
| 528 |
+
num_experts = s.shape[0]
|
| 529 |
+
output = torch.empty(
|
| 530 |
+
(num_experts, s.shape[1], s.shape[2]), device=s.device, dtype=s.dtype
|
| 531 |
+
)
|
| 532 |
+
for e in range(num_experts):
|
| 533 |
+
output[e] = marlin_permute_scales(
|
| 534 |
+
s[e], size_k, size_n, group_size, num_bits
|
| 535 |
+
)
|
| 536 |
+
return output
|
| 537 |
+
|
| 538 |
+
size_k2 = layer.w2_weight_packed.shape[2]
|
| 539 |
+
size_k13 = layer.w13_weight_packed.shape[2]
|
| 540 |
+
|
| 541 |
+
num_experts = layer.w13_weight_g_idx.shape[0]
|
| 542 |
+
device = layer.w13_weight_g_idx.device
|
| 543 |
+
|
| 544 |
+
# when running models with grouped act order,
|
| 545 |
+
# resort to g_idx values provided in checkpoint
|
| 546 |
+
if self.actorder == "group":
|
| 547 |
+
w13_g_idx_sort_indices = torch.empty_like(layer.w13_weight_g_idx)
|
| 548 |
+
w2_g_idx_sort_indices = torch.empty_like(layer.w2_weight_g_idx)
|
| 549 |
+
w13_sorted_g_idx = torch.empty_like(layer.w13_weight_g_idx)
|
| 550 |
+
w2_sorted_g_idx = torch.empty_like(layer.w2_weight_g_idx)
|
| 551 |
+
|
| 552 |
+
for e in range(num_experts):
|
| 553 |
+
w13_g_idx_sort_indices[e] = torch.argsort(layer.w13_weight_g_idx[e]).to(
|
| 554 |
+
torch.int32
|
| 555 |
+
)
|
| 556 |
+
w2_g_idx_sort_indices[e] = torch.argsort(layer.w2_weight_g_idx[e]).to(
|
| 557 |
+
torch.int32
|
| 558 |
+
)
|
| 559 |
+
w13_sorted_g_idx[e] = layer.w13_weight_g_idx[e][
|
| 560 |
+
w13_g_idx_sort_indices[e]
|
| 561 |
+
]
|
| 562 |
+
w2_sorted_g_idx[e] = layer.w2_weight_g_idx[e][w2_g_idx_sort_indices[e]]
|
| 563 |
+
|
| 564 |
+
replace_parameter(layer, "w13_weight_g_idx", w13_sorted_g_idx)
|
| 565 |
+
replace_parameter(layer, "w2_weight_g_idx", w2_sorted_g_idx)
|
| 566 |
+
replace_parameter(layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices)
|
| 567 |
+
replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices)
|
| 568 |
+
|
| 569 |
+
else:
|
| 570 |
+
layer.w13_weight_g_idx = torch.nn.Parameter(
|
| 571 |
+
torch.empty((num_experts, 0), dtype=torch.int32, device=device),
|
| 572 |
+
requires_grad=False,
|
| 573 |
+
)
|
| 574 |
+
layer.w2_weight_g_idx = torch.nn.Parameter(
|
| 575 |
+
torch.empty((num_experts, 0), dtype=torch.int32, device=device),
|
| 576 |
+
requires_grad=False,
|
| 577 |
+
)
|
| 578 |
+
layer.w13_g_idx_sort_indices = torch.nn.Parameter(
|
| 579 |
+
torch.empty((num_experts, 0), dtype=torch.int32, device=device),
|
| 580 |
+
requires_grad=False,
|
| 581 |
+
)
|
| 582 |
+
layer.w2_g_idx_sort_indices = torch.nn.Parameter(
|
| 583 |
+
torch.empty((num_experts, 0), dtype=torch.int32, device=device),
|
| 584 |
+
requires_grad=False,
|
| 585 |
+
)
|
| 586 |
+
|
| 587 |
+
marlin_w13_qweight = vllm_ops.gptq_marlin_moe_repack(
|
| 588 |
+
layer.w13_weight_packed,
|
| 589 |
+
layer.w13_g_idx_sort_indices,
|
| 590 |
+
layer.w13_weight_packed.shape[1] * self.packed_factor,
|
| 591 |
+
layer.w13_weight_packed.shape[2],
|
| 592 |
+
self.num_bits,
|
| 593 |
+
)
|
| 594 |
+
replace_tensor("w13_weight_packed", marlin_w13_qweight)
|
| 595 |
+
marlin_w2_qweight = vllm_ops.gptq_marlin_moe_repack(
|
| 596 |
+
layer.w2_weight_packed,
|
| 597 |
+
layer.w2_g_idx_sort_indices,
|
| 598 |
+
layer.w2_weight_packed.shape[1] * self.packed_factor,
|
| 599 |
+
layer.w2_weight_packed.shape[2],
|
| 600 |
+
self.num_bits,
|
| 601 |
+
)
|
| 602 |
+
replace_tensor("w2_weight_packed", marlin_w2_qweight)
|
| 603 |
+
# Repack scales
|
| 604 |
+
marlin_w13_scales = marlin_moe_permute_scales(
|
| 605 |
+
layer.w13_weight_scale,
|
| 606 |
+
size_k13,
|
| 607 |
+
layer.w13_weight_scale.shape[2],
|
| 608 |
+
self.group_size,
|
| 609 |
+
self.num_bits,
|
| 610 |
+
)
|
| 611 |
+
replace_tensor("w13_weight_scale", marlin_w13_scales)
|
| 612 |
+
marlin_w2_scales = marlin_moe_permute_scales(
|
| 613 |
+
layer.w2_weight_scale,
|
| 614 |
+
layer.w2_weight_scale.shape[1]
|
| 615 |
+
* (self.group_size if self.group_size != -1 else self.packed_factor),
|
| 616 |
+
size_k2,
|
| 617 |
+
self.group_size,
|
| 618 |
+
self.num_bits,
|
| 619 |
+
)
|
| 620 |
+
replace_tensor("w2_weight_scale", marlin_w2_scales)
|
| 621 |
+
|
| 622 |
+
def apply(
|
| 623 |
+
self,
|
| 624 |
+
layer: torch.nn.Module,
|
| 625 |
+
x: torch.Tensor,
|
| 626 |
+
router_logits: torch.Tensor,
|
| 627 |
+
top_k: int,
|
| 628 |
+
renormalize: bool,
|
| 629 |
+
use_grouped_topk: bool = False,
|
| 630 |
+
topk_group: Optional[int] = None,
|
| 631 |
+
num_expert_group: Optional[int] = None,
|
| 632 |
+
global_num_experts: int = -1,
|
| 633 |
+
expert_map: Optional[torch.Tensor] = None,
|
| 634 |
+
custom_routing_function: Optional[Callable] = None,
|
| 635 |
+
scoring_func: str = "softmax",
|
| 636 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 637 |
+
activation: str = "silu",
|
| 638 |
+
routed_scaling_factor: Optional[float] = None,
|
| 639 |
+
) -> torch.Tensor:
|
| 640 |
+
from sglang.srt.layers.moe.topk import select_experts
|
| 641 |
+
|
| 642 |
+
assert activation == "silu", "Only SiLU activation is supported."
|
| 643 |
+
if expert_map is not None:
|
| 644 |
+
raise NotImplementedError(
|
| 645 |
+
"Expert Parallelism is not supported for " "fused Marlin MoE method."
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
topk_weights, topk_ids = select_experts(
|
| 649 |
+
hidden_states=x,
|
| 650 |
+
router_logits=router_logits,
|
| 651 |
+
use_grouped_topk=use_grouped_topk,
|
| 652 |
+
top_k=top_k,
|
| 653 |
+
renormalize=renormalize,
|
| 654 |
+
topk_group=topk_group,
|
| 655 |
+
num_expert_group=num_expert_group,
|
| 656 |
+
custom_routing_function=custom_routing_function,
|
| 657 |
+
scoring_func=scoring_func,
|
| 658 |
+
correction_bias=correction_bias,
|
| 659 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 660 |
+
)
|
| 661 |
+
|
| 662 |
+
return torch.ops.vllm.fused_marlin_moe(
|
| 663 |
+
x,
|
| 664 |
+
layer.w13_weight_packed,
|
| 665 |
+
layer.w2_weight_packed,
|
| 666 |
+
layer.w13_weight_scale,
|
| 667 |
+
layer.w2_weight_scale,
|
| 668 |
+
router_logits,
|
| 669 |
+
topk_weights,
|
| 670 |
+
topk_ids,
|
| 671 |
+
g_idx1=layer.w13_weight_g_idx,
|
| 672 |
+
g_idx2=layer.w2_weight_g_idx,
|
| 673 |
+
sort_indices1=layer.w13_g_idx_sort_indices,
|
| 674 |
+
sort_indices2=layer.w2_g_idx_sort_indices,
|
| 675 |
+
num_bits=self.num_bits,
|
| 676 |
+
is_k_full=self.is_k_full,
|
| 677 |
+
)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
|
| 3 |
+
from .compressed_tensors_scheme import CompressedTensorsScheme
|
| 4 |
+
from .compressed_tensors_w8a8_fp8 import CompressedTensorsW8A8Fp8
|
| 5 |
+
from .compressed_tensors_w8a16_fp8 import CompressedTensorsW8A16Fp8
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"CompressedTensorsScheme",
|
| 9 |
+
"CompressedTensorsW8A8Fp8",
|
| 10 |
+
"CompressedTensorsW8A16Fp8",
|
| 11 |
+
]
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__pycache__/compressed_tensors_w8a16_fp8.cpython-312.pyc
ADDED
|
Binary file (6.33 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__pycache__/compressed_tensors_w8a8_fp8.cpython-312.pyc
ADDED
|
Binary file (6.13 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_scheme.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/tree/main/vllm/model_executor/layers/quantization/compressed_tensors
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
from abc import ABC, abstractmethod
|
| 5 |
+
from typing import Optional
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
__all__ = ["CompressedTensorsScheme"]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class CompressedTensorsScheme(ABC):
|
| 13 |
+
"""
|
| 14 |
+
Abstract class used to describe the weight creation and forward pass
|
| 15 |
+
of different quantization schemes supported by CompressedTensors.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
@classmethod
|
| 19 |
+
@abstractmethod
|
| 20 |
+
def get_min_capability(cls) -> int:
|
| 21 |
+
"""
|
| 22 |
+
Get minimum device capability.
|
| 23 |
+
"""
|
| 24 |
+
raise NotImplementedError
|
| 25 |
+
|
| 26 |
+
@abstractmethod
|
| 27 |
+
def create_weights(self, *args, **kwargs):
|
| 28 |
+
"""
|
| 29 |
+
Weight creation for the particular scheme. Inputs to this function
|
| 30 |
+
|
| 31 |
+
"""
|
| 32 |
+
raise NotImplementedError
|
| 33 |
+
|
| 34 |
+
@abstractmethod
|
| 35 |
+
def apply_weights(
|
| 36 |
+
self, layer: torch.nn.Module, x: torch.Tensor, bias: Optional[torch.Tensor]
|
| 37 |
+
):
|
| 38 |
+
"""
|
| 39 |
+
Run the forward pass for the particular scheme. This is where
|
| 40 |
+
scheme-specific dequant/quant steps/kernels should be applied.
|
| 41 |
+
|
| 42 |
+
:param layer: torch.nn.Module with the registered weights and
|
| 43 |
+
other parameters relevant to the particular scheme.
|
| 44 |
+
:param x: input to the layer
|
| 45 |
+
:param bias: bias parameter
|
| 46 |
+
|
| 47 |
+
"""
|
| 48 |
+
raise NotImplementedError
|
| 49 |
+
|
| 50 |
+
@abstractmethod
|
| 51 |
+
def process_weights_after_loading(self, layer: torch.nn.Module):
|
| 52 |
+
"""
|
| 53 |
+
Called after weight loading is complete for any cleanup that
|
| 54 |
+
needs to occur.
|
| 55 |
+
"""
|
| 56 |
+
raise NotImplementedError
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a16_fp8.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/tree/main/vllm/model_executor/layers/quantization/compressed_tensors
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
from typing import Callable, List, Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from compressed_tensors.quantization import QuantizationStrategy
|
| 8 |
+
|
| 9 |
+
from sglang.srt.layers.parameter import (
|
| 10 |
+
ChannelQuantScaleParameter,
|
| 11 |
+
ModelWeightParameter,
|
| 12 |
+
PerTensorScaleParameter,
|
| 13 |
+
)
|
| 14 |
+
from sglang.srt.layers.quantization.compressed_tensors.schemes import (
|
| 15 |
+
CompressedTensorsScheme,
|
| 16 |
+
)
|
| 17 |
+
from sglang.srt.layers.quantization.utils import convert_to_channelwise
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
from vllm.model_executor.layers.quantization.utils.marlin_utils_fp8 import (
|
| 21 |
+
apply_fp8_marlin_linear,
|
| 22 |
+
prepare_fp8_layer_for_marlin,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
MARLIN_FP8_AVAILABLE = True
|
| 26 |
+
except ImportError:
|
| 27 |
+
MARLIN_FP8_AVAILABLE = False
|
| 28 |
+
|
| 29 |
+
def apply_fp8_marlin_linear(*args, **kwargs):
|
| 30 |
+
raise ImportError("vllm is not installed")
|
| 31 |
+
|
| 32 |
+
def prepare_fp8_layer_for_marlin(*args, **kwargs):
|
| 33 |
+
raise ImportError("vllm is not installed")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
__all__ = ["CompressedTensorsW8A16Fp8"]
|
| 37 |
+
|
| 38 |
+
SUPPORTED_STRATEGIES = [QuantizationStrategy.CHANNEL, QuantizationStrategy.TENSOR]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class CompressedTensorsW8A16Fp8(CompressedTensorsScheme):
|
| 42 |
+
def __init__(self, strategy: str, is_static_input_scheme: bool):
|
| 43 |
+
self.strategy = strategy
|
| 44 |
+
self.is_static_input_scheme = is_static_input_scheme
|
| 45 |
+
|
| 46 |
+
if not MARLIN_FP8_AVAILABLE:
|
| 47 |
+
raise ImportError(
|
| 48 |
+
"vllm is not installed. To use CompressedTensorsW8A16Fp8, please install vllm"
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
@classmethod
|
| 52 |
+
def get_min_capability(cls) -> int:
|
| 53 |
+
# ampere and up
|
| 54 |
+
return 80
|
| 55 |
+
|
| 56 |
+
# W8A8-Fp8 kernels support only per-tensor and per-channel cases.
|
| 57 |
+
# So if we have a fused module (QKV, MLP) with per tensor scales,
|
| 58 |
+
# we expand each scale to its shard's channels.
|
| 59 |
+
def process_weights_after_loading(self, layer) -> None:
|
| 60 |
+
if self.strategy == QuantizationStrategy.TENSOR:
|
| 61 |
+
ws_channelwise = convert_to_channelwise(
|
| 62 |
+
layer.weight_scale, layer.logical_widths
|
| 63 |
+
)
|
| 64 |
+
layer.weight_scale = torch.nn.Parameter(ws_channelwise, requires_grad=False)
|
| 65 |
+
else:
|
| 66 |
+
# required by torch.compile to be torch.nn.Parameter
|
| 67 |
+
layer.weight_scale = torch.nn.Parameter(
|
| 68 |
+
layer.weight_scale.data, requires_grad=False
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Weights must be transposed for marlin
|
| 72 |
+
layer.weight = torch.nn.Parameter(layer.weight.t(), requires_grad=False)
|
| 73 |
+
|
| 74 |
+
if self.is_static_input_scheme:
|
| 75 |
+
# required by torch.compile to be torch.nn.Parameter
|
| 76 |
+
layer.input_scale = torch.nn.Parameter(
|
| 77 |
+
layer.input_scale.data, requires_grad=False
|
| 78 |
+
)
|
| 79 |
+
prepare_fp8_layer_for_marlin(layer, strategy="channel")
|
| 80 |
+
|
| 81 |
+
def create_weights(
|
| 82 |
+
self,
|
| 83 |
+
layer: torch.nn.Module,
|
| 84 |
+
input_size: int,
|
| 85 |
+
output_partition_sizes: List[int],
|
| 86 |
+
input_size_per_partition: int,
|
| 87 |
+
params_dtype: torch.dtype,
|
| 88 |
+
weight_loader: Callable,
|
| 89 |
+
**kwargs,
|
| 90 |
+
):
|
| 91 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 92 |
+
layer.logical_widths = output_partition_sizes
|
| 93 |
+
layer.input_size_per_partition = input_size_per_partition
|
| 94 |
+
layer.output_size_per_partition = output_size_per_partition
|
| 95 |
+
layer.orig_dtype = params_dtype
|
| 96 |
+
|
| 97 |
+
# WEIGHT
|
| 98 |
+
weight = ModelWeightParameter(
|
| 99 |
+
data=torch.empty(
|
| 100 |
+
output_size_per_partition,
|
| 101 |
+
input_size_per_partition,
|
| 102 |
+
dtype=torch.float8_e4m3fn,
|
| 103 |
+
),
|
| 104 |
+
input_dim=1,
|
| 105 |
+
output_dim=0,
|
| 106 |
+
weight_loader=weight_loader,
|
| 107 |
+
)
|
| 108 |
+
layer.register_parameter("weight", weight)
|
| 109 |
+
|
| 110 |
+
# WEIGHT SCALE
|
| 111 |
+
if self.strategy == QuantizationStrategy.CHANNEL:
|
| 112 |
+
weight_scale = ChannelQuantScaleParameter(
|
| 113 |
+
data=torch.empty((sum(output_partition_sizes), 1), dtype=torch.float32),
|
| 114 |
+
output_dim=0,
|
| 115 |
+
weight_loader=weight_loader,
|
| 116 |
+
)
|
| 117 |
+
elif self.strategy == QuantizationStrategy.TENSOR:
|
| 118 |
+
weight_scale = PerTensorScaleParameter(
|
| 119 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 120 |
+
weight_loader=weight_loader,
|
| 121 |
+
)
|
| 122 |
+
else:
|
| 123 |
+
raise ValueError(
|
| 124 |
+
f"Unsupported weight strategy={self.strategy}, "
|
| 125 |
+
f"supported strategies are {SUPPORTED_STRATEGIES}"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
weight_scale[:] = torch.finfo(torch.float32).min
|
| 129 |
+
layer.register_parameter("weight_scale", weight_scale)
|
| 130 |
+
|
| 131 |
+
# INPUT SCALE (to deal with converted checkpoints)
|
| 132 |
+
if self.is_static_input_scheme:
|
| 133 |
+
input_scale = PerTensorScaleParameter(
|
| 134 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 135 |
+
weight_loader=weight_loader,
|
| 136 |
+
)
|
| 137 |
+
layer.register_parameter("input_scale", input_scale)
|
| 138 |
+
|
| 139 |
+
def apply_weights(
|
| 140 |
+
self,
|
| 141 |
+
layer: torch.nn.Module,
|
| 142 |
+
x: torch.Tensor,
|
| 143 |
+
bias: Optional[torch.Tensor] = None,
|
| 144 |
+
) -> torch.Tensor:
|
| 145 |
+
return apply_fp8_marlin_linear(
|
| 146 |
+
input=x,
|
| 147 |
+
weight=layer.weight,
|
| 148 |
+
weight_scale=layer.weight_scale,
|
| 149 |
+
workspace=layer.workspace,
|
| 150 |
+
size_n=layer.output_size_per_partition,
|
| 151 |
+
size_k=layer.input_size_per_partition,
|
| 152 |
+
bias=bias,
|
| 153 |
+
)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a8_fp8.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/tree/main/vllm/model_executor/layers/quantization/compressed_tensors
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
from typing import Callable, List, Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from compressed_tensors.quantization import QuantizationStrategy
|
| 8 |
+
from torch.nn import Parameter
|
| 9 |
+
|
| 10 |
+
from sglang.srt.layers.parameter import (
|
| 11 |
+
ChannelQuantScaleParameter,
|
| 12 |
+
ModelWeightParameter,
|
| 13 |
+
PerTensorScaleParameter,
|
| 14 |
+
)
|
| 15 |
+
from sglang.srt.layers.quantization.compressed_tensors.schemes import (
|
| 16 |
+
CompressedTensorsScheme,
|
| 17 |
+
)
|
| 18 |
+
from sglang.srt.layers.quantization.fp8_utils import (
|
| 19 |
+
apply_fp8_linear,
|
| 20 |
+
normalize_e4m3fn_to_e4m3fnuz,
|
| 21 |
+
)
|
| 22 |
+
from sglang.srt.layers.quantization.utils import is_fp8_fnuz, requantize_with_max_scale
|
| 23 |
+
|
| 24 |
+
__all__ = ["CompressedTensorsW8A8Fp8"]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class CompressedTensorsW8A8Fp8(CompressedTensorsScheme):
|
| 28 |
+
|
| 29 |
+
def __init__(self, strategy: str, is_static_input_scheme: bool):
|
| 30 |
+
self.strategy = strategy
|
| 31 |
+
self.is_static_input_scheme = is_static_input_scheme
|
| 32 |
+
|
| 33 |
+
@classmethod
|
| 34 |
+
def get_min_capability(cls) -> int:
|
| 35 |
+
# lovelace and up
|
| 36 |
+
return 89
|
| 37 |
+
|
| 38 |
+
def process_weights_after_loading(self, layer) -> None:
|
| 39 |
+
# If per tensor, when we have a fused module (e.g. QKV) with per
|
| 40 |
+
# tensor scales (thus N scales being passed to the kernel),
|
| 41 |
+
# requantize so we can always run per tensor
|
| 42 |
+
if self.strategy == QuantizationStrategy.TENSOR:
|
| 43 |
+
max_w_scale, weight = requantize_with_max_scale(
|
| 44 |
+
weight=layer.weight,
|
| 45 |
+
weight_scale=layer.weight_scale,
|
| 46 |
+
logical_widths=layer.logical_widths,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
if is_fp8_fnuz():
|
| 50 |
+
input_scale = getattr(layer, "input_scale", None)
|
| 51 |
+
|
| 52 |
+
weight, max_w_scale, input_scale = normalize_e4m3fn_to_e4m3fnuz(
|
| 53 |
+
weight=weight, weight_scale=max_w_scale, input_scale=input_scale
|
| 54 |
+
)
|
| 55 |
+
if input_scale is not None:
|
| 56 |
+
layer.input_scale = Parameter(input_scale, requires_grad=False)
|
| 57 |
+
|
| 58 |
+
layer.weight = Parameter(weight.t(), requires_grad=False)
|
| 59 |
+
layer.weight_scale = Parameter(max_w_scale, requires_grad=False)
|
| 60 |
+
|
| 61 |
+
# If channelwise, scales are already lined up, so just transpose.
|
| 62 |
+
elif self.strategy == QuantizationStrategy.CHANNEL:
|
| 63 |
+
weight = layer.weight
|
| 64 |
+
|
| 65 |
+
if is_fp8_fnuz():
|
| 66 |
+
input_scale = getattr(layer, "input_scale", None)
|
| 67 |
+
|
| 68 |
+
weight, weight_scale, input_scale = normalize_e4m3fn_to_e4m3fnuz(
|
| 69 |
+
weight=weight,
|
| 70 |
+
weight_scale=layer.weight_scale,
|
| 71 |
+
input_scale=input_scale,
|
| 72 |
+
)
|
| 73 |
+
if input_scale is not None:
|
| 74 |
+
layer.input_scale = Parameter(input_scale, requires_grad=False)
|
| 75 |
+
else:
|
| 76 |
+
weight_scale = layer.weight_scale.data
|
| 77 |
+
|
| 78 |
+
layer.weight = Parameter(weight.t(), requires_grad=False)
|
| 79 |
+
# required by torch.compile to be torch.nn.Parameter
|
| 80 |
+
layer.weight_scale = Parameter(weight_scale, requires_grad=False)
|
| 81 |
+
|
| 82 |
+
else:
|
| 83 |
+
raise ValueError(f"Unknown quantization strategy {self.strategy}")
|
| 84 |
+
|
| 85 |
+
# INPUT SCALE
|
| 86 |
+
if self.is_static_input_scheme and hasattr(layer, "input_scale"):
|
| 87 |
+
layer.input_scale = Parameter(layer.input_scale.max(), requires_grad=False)
|
| 88 |
+
else:
|
| 89 |
+
layer.input_scale = None
|
| 90 |
+
|
| 91 |
+
def create_weights(
|
| 92 |
+
self,
|
| 93 |
+
layer: torch.nn.Module,
|
| 94 |
+
output_partition_sizes: List[int],
|
| 95 |
+
input_size_per_partition: int,
|
| 96 |
+
params_dtype: torch.dtype,
|
| 97 |
+
weight_loader: Callable,
|
| 98 |
+
**kwargs,
|
| 99 |
+
):
|
| 100 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 101 |
+
layer.logical_widths = output_partition_sizes
|
| 102 |
+
|
| 103 |
+
# WEIGHT
|
| 104 |
+
weight = ModelWeightParameter(
|
| 105 |
+
data=torch.empty(
|
| 106 |
+
output_size_per_partition,
|
| 107 |
+
input_size_per_partition,
|
| 108 |
+
dtype=torch.float8_e4m3fn,
|
| 109 |
+
),
|
| 110 |
+
input_dim=1,
|
| 111 |
+
output_dim=0,
|
| 112 |
+
weight_loader=weight_loader,
|
| 113 |
+
)
|
| 114 |
+
layer.register_parameter("weight", weight)
|
| 115 |
+
|
| 116 |
+
# WEIGHT SCALE
|
| 117 |
+
# TODO: update create_xxx_parameter functions to return
|
| 118 |
+
# the newly added parameters
|
| 119 |
+
if self.strategy == QuantizationStrategy.CHANNEL:
|
| 120 |
+
weight_scale = ChannelQuantScaleParameter(
|
| 121 |
+
data=torch.empty((sum(output_partition_sizes), 1), dtype=torch.float32),
|
| 122 |
+
output_dim=0,
|
| 123 |
+
weight_loader=weight_loader,
|
| 124 |
+
)
|
| 125 |
+
else:
|
| 126 |
+
assert self.strategy == QuantizationStrategy.TENSOR
|
| 127 |
+
weight_scale = PerTensorScaleParameter(
|
| 128 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 129 |
+
weight_loader=weight_loader,
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
# min requirement for fp8 kernels
|
| 133 |
+
weight_scale[:] = torch.finfo(torch.float32).min
|
| 134 |
+
layer.register_parameter("weight_scale", weight_scale)
|
| 135 |
+
|
| 136 |
+
# INPUT SCALE
|
| 137 |
+
if self.is_static_input_scheme:
|
| 138 |
+
input_scale = PerTensorScaleParameter(
|
| 139 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 140 |
+
weight_loader=weight_loader,
|
| 141 |
+
)
|
| 142 |
+
input_scale[:] = torch.finfo(torch.float32).min
|
| 143 |
+
layer.register_parameter("input_scale", input_scale)
|
| 144 |
+
|
| 145 |
+
def apply_weights(
|
| 146 |
+
self,
|
| 147 |
+
layer: torch.nn.Module,
|
| 148 |
+
x: torch.Tensor,
|
| 149 |
+
bias: Optional[torch.Tensor] = None,
|
| 150 |
+
) -> torch.Tensor:
|
| 151 |
+
return apply_fp8_linear(
|
| 152 |
+
input=x,
|
| 153 |
+
weight=layer.weight,
|
| 154 |
+
weight_scale=layer.weight_scale,
|
| 155 |
+
input_scale=layer.input_scale,
|
| 156 |
+
bias=bias,
|
| 157 |
+
use_per_token_if_dynamic=True,
|
| 158 |
+
compressed_tensor_quant=True,
|
| 159 |
+
)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/utils.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/tree/main/vllm/model_executor/layers/quantization/compressed_tensors
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
import re
|
| 5 |
+
from types import MappingProxyType
|
| 6 |
+
from typing import Iterable, List, Mapping, Optional
|
| 7 |
+
|
| 8 |
+
from compressed_tensors import CompressionFormat
|
| 9 |
+
from torch.nn import Module
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def is_activation_quantization_format(format: str) -> bool:
|
| 13 |
+
_ACTIVATION_QUANTIZATION_FORMATS = [
|
| 14 |
+
CompressionFormat.naive_quantized.value,
|
| 15 |
+
CompressionFormat.int_quantized.value,
|
| 16 |
+
CompressionFormat.float_quantized.value,
|
| 17 |
+
]
|
| 18 |
+
return format in _ACTIVATION_QUANTIZATION_FORMATS
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def should_ignore_layer(
|
| 22 |
+
layer_name: Optional[str],
|
| 23 |
+
ignore: Iterable[str] = tuple(),
|
| 24 |
+
fused_mapping: Mapping[str, List[str]] = MappingProxyType({}),
|
| 25 |
+
) -> bool:
|
| 26 |
+
if layer_name is None:
|
| 27 |
+
return False
|
| 28 |
+
|
| 29 |
+
# layer_name = model.layers.0.self_attn.qkv_proj
|
| 30 |
+
# proj_name = qkv_proj
|
| 31 |
+
proj_name = layer_name.split(".")[-1]
|
| 32 |
+
|
| 33 |
+
# Fused layers like gate_up_proj or qkv_proj will not be fused
|
| 34 |
+
# in the safetensors checkpoint. So, we convert the name
|
| 35 |
+
# from the fused version to unfused + check to make sure that
|
| 36 |
+
# each shard of the fused layer has the same scheme.
|
| 37 |
+
if proj_name in fused_mapping and layer_name not in ignore:
|
| 38 |
+
shard_proj_names = fused_mapping[proj_name]
|
| 39 |
+
|
| 40 |
+
# Convert fused_name --> [shard_names]
|
| 41 |
+
shard_names = [
|
| 42 |
+
layer_name.replace(proj_name, shard_proj_name)
|
| 43 |
+
for shard_proj_name in shard_proj_names
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
# Layer should be ignored if shards are ignored.
|
| 47 |
+
should_ignore_layer = None
|
| 48 |
+
for shard_name in shard_names:
|
| 49 |
+
should_ignore_shard = check_equal_or_regex_match(
|
| 50 |
+
layer_name=shard_name, targets=ignore
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# If shard_idx=0, set layer ignore to match shard.
|
| 54 |
+
if should_ignore_layer is None:
|
| 55 |
+
should_ignore_layer = should_ignore_shard
|
| 56 |
+
|
| 57 |
+
# If shard_idx=1+ confirm scheme matches prior shards.
|
| 58 |
+
elif should_ignore_shard != should_ignore_layer:
|
| 59 |
+
raise ValueError(
|
| 60 |
+
f"Found a different quantization schemes for "
|
| 61 |
+
f"{shard_proj_names} in {layer_name}. vLLM "
|
| 62 |
+
"requires all to use the same scheme."
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
# Unfused layers like down_proj and o_proj will match
|
| 66 |
+
# the safetensors checkpoint already.
|
| 67 |
+
else:
|
| 68 |
+
should_ignore_layer = check_equal_or_regex_match(
|
| 69 |
+
layer_name=layer_name, targets=ignore
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
assert should_ignore_layer is not None
|
| 73 |
+
return should_ignore_layer
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def check_equal_or_regex_match(layer_name: str, targets: Iterable[str]) -> bool:
|
| 77 |
+
"""
|
| 78 |
+
Checks whether a layer_name is exactly equal or a regex match for
|
| 79 |
+
if target starts with 're:' to any target in list.
|
| 80 |
+
"""
|
| 81 |
+
for target in targets:
|
| 82 |
+
if _is_equal_or_regex_match(layer_name, target):
|
| 83 |
+
return True
|
| 84 |
+
return False
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def find_matched_target(
|
| 88 |
+
layer_name: Optional[str],
|
| 89 |
+
module: Module,
|
| 90 |
+
targets: Iterable[str],
|
| 91 |
+
fused_mapping: Mapping[str, List[str]] = MappingProxyType({}),
|
| 92 |
+
) -> str:
|
| 93 |
+
"""
|
| 94 |
+
Helper function to look up which "target" in the compressed-tensors
|
| 95 |
+
config that a layer corresponds to.
|
| 96 |
+
|
| 97 |
+
Recall that a compressed-tensors configs has a concept of
|
| 98 |
+
config_groups, where each layer can be quantized with with a different
|
| 99 |
+
scheme.
|
| 100 |
+
|
| 101 |
+
targets in each config_group will be a list of either layer names
|
| 102 |
+
(or regexes corresponding to layer names) or names of torch Modules.
|
| 103 |
+
|
| 104 |
+
First, we try to match the layer_name with a target
|
| 105 |
+
Second, we try to match the module's name with a target
|
| 106 |
+
Third, we try to map the layer_name to a list of fused module names.
|
| 107 |
+
*All* component module names must match in order for a match to be
|
| 108 |
+
successful. A successful match returns the first component target
|
| 109 |
+
|
| 110 |
+
:param layer_name: layer name
|
| 111 |
+
:param module: torch.nn.Module
|
| 112 |
+
:param targets: list of targets to match the layer against
|
| 113 |
+
:param fused_mapping: map from fused layer names to its components
|
| 114 |
+
:param fused_strategy: either "all" or "any". If using "all", fused
|
| 115 |
+
layers match if "all" of its components match
|
| 116 |
+
"""
|
| 117 |
+
|
| 118 |
+
if layer_name is None:
|
| 119 |
+
layer_name = ""
|
| 120 |
+
|
| 121 |
+
matched_target = (
|
| 122 |
+
_find_first_match(layer_name, targets)
|
| 123 |
+
or _find_first_match(module.__class__.__name__, targets, True)
|
| 124 |
+
or _match_fused_layer(layer_name, targets, fused_mapping)
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if matched_target is None:
|
| 128 |
+
raise ValueError(
|
| 129 |
+
f"Unable to find matching target for {layer_name} in the "
|
| 130 |
+
"compressed-tensors config."
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
return matched_target
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _find_first_match(
|
| 137 |
+
value: str, targets: Iterable[str], check_contains: bool = False
|
| 138 |
+
) -> Optional[str]:
|
| 139 |
+
"""
|
| 140 |
+
Returns first element of target that matches value either
|
| 141 |
+
exactly or as a regex after 're:'. If check_contains is set to True,
|
| 142 |
+
additionally checks if the target string is contained within the value.
|
| 143 |
+
|
| 144 |
+
:param value: string to compare the list of targets against
|
| 145 |
+
:param targets: list of targets to match the layer against
|
| 146 |
+
:param check_contains: whether or not to do a substring match
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
for target in targets:
|
| 150 |
+
if _is_equal_or_regex_match(value, target, check_contains=check_contains):
|
| 151 |
+
return target
|
| 152 |
+
return None
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _is_equal_or_regex_match(
|
| 156 |
+
value: str, target: str, check_contains: bool = False
|
| 157 |
+
) -> bool:
|
| 158 |
+
"""
|
| 159 |
+
Checks whether a value is exactly equal or a regex match for target
|
| 160 |
+
if target starts with 're:'. If check_contains is set to True,
|
| 161 |
+
additionally checks if the target string is contained within the value.
|
| 162 |
+
"""
|
| 163 |
+
|
| 164 |
+
if target.startswith("re:"):
|
| 165 |
+
pattern = target[3:]
|
| 166 |
+
if re.match(pattern, value):
|
| 167 |
+
return True
|
| 168 |
+
elif check_contains:
|
| 169 |
+
if target.lower() in value.lower():
|
| 170 |
+
return True
|
| 171 |
+
elif target == value:
|
| 172 |
+
return True
|
| 173 |
+
return False
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _match_fused_layer(
|
| 177 |
+
layer_name: str,
|
| 178 |
+
target_layers: Iterable[str],
|
| 179 |
+
fused_mapping: Mapping[str, List[str]],
|
| 180 |
+
) -> Optional[str]:
|
| 181 |
+
"""
|
| 182 |
+
Match a fused layer name to its corresponding individual layer in
|
| 183 |
+
target_layers. Returns first value in fused_mapping which matches targets
|
| 184 |
+
|
| 185 |
+
Implements an "all" matching strategy where a fused layer matches iff
|
| 186 |
+
"all" of its components match
|
| 187 |
+
|
| 188 |
+
:param layer_name: layer name
|
| 189 |
+
:param target_layers: list of targets to match the layer against
|
| 190 |
+
:param fused_mapping: map from fused layer names to its components
|
| 191 |
+
|
| 192 |
+
Examples:
|
| 193 |
+
layer_name = "model.layers.0.self_attn.qkv_proj"
|
| 194 |
+
target_layers = ["model.layers.0.self_attn.q_proj",
|
| 195 |
+
"model.layers.0.self_attn.k_proj",
|
| 196 |
+
"model.layers.0.self_attn.v_proj"]
|
| 197 |
+
"""
|
| 198 |
+
# find layer_name in mapping
|
| 199 |
+
fused = next((key for key in fused_mapping if layer_name.endswith(key)), None)
|
| 200 |
+
if fused is None:
|
| 201 |
+
return None
|
| 202 |
+
|
| 203 |
+
# expand path of unfused components
|
| 204 |
+
unfused_paths = [
|
| 205 |
+
layer_name.replace(fused, unfused) for unfused in fused_mapping[fused]
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
# for each unfused component, find a match in targets
|
| 209 |
+
unfused_matches: List[Optional[str]] = []
|
| 210 |
+
for unfused in unfused_paths:
|
| 211 |
+
for target in target_layers:
|
| 212 |
+
if _is_equal_or_regex_match(unfused, target):
|
| 213 |
+
unfused_matches.append(target)
|
| 214 |
+
break
|
| 215 |
+
else:
|
| 216 |
+
unfused_matches.append(None)
|
| 217 |
+
|
| 218 |
+
return unfused_matches[0] if all(unfused_matches) else None
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/deep_gemm.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
from contextlib import contextmanager
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from enum import IntEnum, auto
|
| 6 |
+
from typing import Callable, Dict, List, Optional, Tuple
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from tqdm.contrib.concurrent import thread_map
|
| 10 |
+
|
| 11 |
+
from sglang.srt.server_args import ServerArgs
|
| 12 |
+
from sglang.srt.utils import get_bool_env_var, get_device_sm, get_int_env_var, is_cuda
|
| 13 |
+
|
| 14 |
+
_ENABLE_JIT_DEEPGEMM = False
|
| 15 |
+
if is_cuda():
|
| 16 |
+
import deep_gemm
|
| 17 |
+
from deep_gemm import get_num_sms
|
| 18 |
+
from deep_gemm.jit_kernels.gemm import get_best_configs
|
| 19 |
+
from deep_gemm.jit_kernels.gemm import includes as deep_gemm_includes
|
| 20 |
+
from deep_gemm.jit_kernels.gemm import template as deep_gemm_gemm_template
|
| 21 |
+
from deep_gemm.jit_kernels.m_grouped_gemm import (
|
| 22 |
+
template as deep_gemm_grouped_gemm_template,
|
| 23 |
+
)
|
| 24 |
+
from deep_gemm.jit_kernels.tuner import jit_tuner
|
| 25 |
+
|
| 26 |
+
sm_version = get_device_sm()
|
| 27 |
+
if sm_version == 90:
|
| 28 |
+
if get_bool_env_var("SGL_ENABLE_JIT_DEEPGEMM", default="true"):
|
| 29 |
+
_ENABLE_JIT_DEEPGEMM = True
|
| 30 |
+
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
_BUILTIN_M_LIST = list(range(1, 1024 * 16 + 1))
|
| 34 |
+
_ENABLE_JIT_DEEPGEMM_PRECOMPILE = get_bool_env_var(
|
| 35 |
+
"SGL_JIT_DEEPGEMM_PRECOMPILE", "true"
|
| 36 |
+
)
|
| 37 |
+
_DO_COMPILE_ALL = True
|
| 38 |
+
_IS_FIRST_RANK_ON_NODE = get_bool_env_var("SGL_IS_FIRST_RANK_ON_NODE", "true")
|
| 39 |
+
_COMPILE_WORKERS = get_int_env_var("SGL_JIT_DEEPGEMM_COMPILE_WORKERS", 4)
|
| 40 |
+
_IN_PRECOMPILE_STAGE = get_bool_env_var("SGL_IN_DEEPGEMM_PRECOMPILE_STAGE", "false")
|
| 41 |
+
|
| 42 |
+
# Force redirect deep_gemm cache_dir
|
| 43 |
+
os.environ["DG_CACHE_DIR"] = os.getenv(
|
| 44 |
+
"SGL_DG_CACHE_DIR", os.path.expanduser("~") + "/.cache/deep_gemm"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def update_deep_gemm_config(gpu_id: int, server_args: ServerArgs):
|
| 49 |
+
global _BUILTIN_M_LIST
|
| 50 |
+
global _DO_COMPILE_ALL
|
| 51 |
+
global _IS_FIRST_RANK_ON_NODE
|
| 52 |
+
|
| 53 |
+
# Generate m_max
|
| 54 |
+
m_max = 1024 * 16
|
| 55 |
+
if server_args.chunked_prefill_size < 1:
|
| 56 |
+
m_max = 1024 * 64
|
| 57 |
+
elif server_args.chunked_prefill_size > 8192:
|
| 58 |
+
m_max = server_args.chunked_prefill_size * 2
|
| 59 |
+
m_max = min(1024 * 128, m_max)
|
| 60 |
+
_BUILTIN_M_LIST = list(range(1, m_max + 1))
|
| 61 |
+
|
| 62 |
+
_IS_FIRST_RANK_ON_NODE = ServerArgs.base_gpu_id == gpu_id
|
| 63 |
+
|
| 64 |
+
# Check if is the first rank on node.
|
| 65 |
+
# Default each rank will try compile all Ms to
|
| 66 |
+
# load all symbols at the launch stages.
|
| 67 |
+
# Avoid loading symbols at the serving stages.
|
| 68 |
+
_DO_COMPILE_ALL = _IS_FIRST_RANK_ON_NODE or not _IN_PRECOMPILE_STAGE
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class DeepGemmKernelType(IntEnum):
|
| 72 |
+
GROUPED_GEMM_NT_F8F8BF16_MASKED = auto()
|
| 73 |
+
GROUPED_GEMM_NT_F8F8BF16_CONTIG = auto()
|
| 74 |
+
GEMM_NT_F8F8BF16 = auto()
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@dataclass
|
| 78 |
+
class DeepGemmKernelHelper:
|
| 79 |
+
name: str
|
| 80 |
+
compile_func: Callable[
|
| 81 |
+
[
|
| 82 |
+
int,
|
| 83 |
+
int,
|
| 84 |
+
int,
|
| 85 |
+
Tuple[int, int, int, int, Tuple[int, bool], Tuple[int, int, int]],
|
| 86 |
+
],
|
| 87 |
+
None,
|
| 88 |
+
]
|
| 89 |
+
configure_func: Callable[
|
| 90 |
+
[int, int, int, int, int],
|
| 91 |
+
Tuple[int, int, int, int, Tuple[int, bool], Tuple[int, int, int]],
|
| 92 |
+
]
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
_INITIALIZATION_DICT: Dict[Tuple[DeepGemmKernelType, int, int, int], bool] = dict()
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _compile_warning_1():
|
| 99 |
+
if not _IN_PRECOMPILE_STAGE and _IS_FIRST_RANK_ON_NODE:
|
| 100 |
+
logger.warning(
|
| 101 |
+
"Entering DeepGEMM JIT Pre-Complie session. "
|
| 102 |
+
"And it may takes a long time(Typically 10-20 mins) "
|
| 103 |
+
"if you have not run `sglang.compile_deep_gemm`. "
|
| 104 |
+
"Recommand to run `sglang.compile_deep_gemm` with same args as `sglang.launch_server`"
|
| 105 |
+
" for pre-compilation to reduce the overhead if you have not run it before. "
|
| 106 |
+
"For example: "
|
| 107 |
+
"`python3 -m sglang.compile_deep_gemm --model deepseek-ai/DeepSeek-V3 --tp 8 --trust-remote-code`"
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _compile_warning_2():
|
| 112 |
+
logger.warning(
|
| 113 |
+
"Entering DeepGEMM JIT Single Kernel Complie session. "
|
| 114 |
+
"And it will makes inference throughput becomes flaky. "
|
| 115 |
+
"Please run `sglang.compile_deep_gemm` with same args as `sglang.launch_server`"
|
| 116 |
+
" for pre-compilation to solve this issue. "
|
| 117 |
+
"For example: "
|
| 118 |
+
"`python3 -m sglang.compile_deep_gemm --model deepseek-ai/DeepSeek-V3 --tp 8 --trust-remote-code`"
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _compile_grouped_gemm_nt_f8f8bf16_masked_one(
|
| 123 |
+
n: int,
|
| 124 |
+
k: int,
|
| 125 |
+
num_groups: int,
|
| 126 |
+
config: Tuple[int, int, int, int, Tuple[int, bool], Tuple[int, int, int]],
|
| 127 |
+
) -> None:
|
| 128 |
+
# Auto-tuning with compilation
|
| 129 |
+
global deep_gemm_includes, deep_gemm_grouped_gemm_template
|
| 130 |
+
_, block_m, block_n, num_stages, tma_multicast_config, smem_config = config
|
| 131 |
+
_ = jit_tuner.compile_and_tune(
|
| 132 |
+
name="m_grouped_gemm_fp8_fp8_bf16_nt",
|
| 133 |
+
keys={
|
| 134 |
+
"N": n,
|
| 135 |
+
"K": k,
|
| 136 |
+
"BLOCK_M": block_m,
|
| 137 |
+
"BLOCK_N": block_n,
|
| 138 |
+
"SWIZZLE_D_MODE": smem_config[1],
|
| 139 |
+
"BLOCK_N_PADDING": smem_config[2],
|
| 140 |
+
"NUM_GROUPS": num_groups,
|
| 141 |
+
"NUM_STAGES": num_stages,
|
| 142 |
+
"NUM_TMA_MULTICAST": tma_multicast_config[0],
|
| 143 |
+
"IS_TMA_MULTICAST_ON_A": tma_multicast_config[1],
|
| 144 |
+
"GEMM_TYPE": "GroupedMasked",
|
| 145 |
+
},
|
| 146 |
+
space=(),
|
| 147 |
+
includes=deep_gemm_includes,
|
| 148 |
+
arg_defs=(
|
| 149 |
+
("lhs", torch.float8_e4m3fn),
|
| 150 |
+
("lhs_scales", torch.float),
|
| 151 |
+
("rhs", torch.float8_e4m3fn),
|
| 152 |
+
("rhs_scales", torch.float),
|
| 153 |
+
("out", torch.bfloat16),
|
| 154 |
+
("grouped_layout", torch.int32),
|
| 155 |
+
("m", int),
|
| 156 |
+
("stream", torch.cuda.Stream),
|
| 157 |
+
("num_sms", int),
|
| 158 |
+
("smem_size", int),
|
| 159 |
+
),
|
| 160 |
+
template=deep_gemm_grouped_gemm_template,
|
| 161 |
+
args=[],
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def _compile_grouped_gemm_nt_f8f8bf16_contig_one(
|
| 166 |
+
n: int,
|
| 167 |
+
k: int,
|
| 168 |
+
num_groups: int,
|
| 169 |
+
config: Tuple[int, int, int, int, Tuple[int, bool], Tuple[int, int, int]],
|
| 170 |
+
) -> None:
|
| 171 |
+
global deep_gemm_includes, deep_gemm_grouped_gemm_template
|
| 172 |
+
_, block_m, block_n, num_stages, tma_multicast_config, smem_config = config
|
| 173 |
+
_ = jit_tuner.compile_and_tune(
|
| 174 |
+
name="m_grouped_gemm_fp8_fp8_bf16_nt",
|
| 175 |
+
keys={
|
| 176 |
+
"N": n,
|
| 177 |
+
"K": k,
|
| 178 |
+
"BLOCK_M": block_m,
|
| 179 |
+
"BLOCK_N": block_n,
|
| 180 |
+
"SWIZZLE_D_MODE": smem_config[1],
|
| 181 |
+
"BLOCK_N_PADDING": smem_config[2],
|
| 182 |
+
"NUM_GROUPS": num_groups,
|
| 183 |
+
"NUM_STAGES": num_stages,
|
| 184 |
+
"NUM_TMA_MULTICAST": tma_multicast_config[0],
|
| 185 |
+
"IS_TMA_MULTICAST_ON_A": tma_multicast_config[1],
|
| 186 |
+
"GEMM_TYPE": "GroupedContiguous",
|
| 187 |
+
},
|
| 188 |
+
space=(),
|
| 189 |
+
includes=deep_gemm_includes,
|
| 190 |
+
arg_defs=(
|
| 191 |
+
("lhs", torch.float8_e4m3fn),
|
| 192 |
+
("lhs_scales", torch.float),
|
| 193 |
+
("rhs", torch.float8_e4m3fn),
|
| 194 |
+
("rhs_scales", torch.float),
|
| 195 |
+
("out", torch.bfloat16),
|
| 196 |
+
("grouped_layout", torch.int32),
|
| 197 |
+
("m", int),
|
| 198 |
+
("num_groups", int),
|
| 199 |
+
("stream", torch.cuda.Stream),
|
| 200 |
+
("num_sms", int),
|
| 201 |
+
("smem_size", int),
|
| 202 |
+
),
|
| 203 |
+
template=deep_gemm_grouped_gemm_template,
|
| 204 |
+
args=[],
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _compile_gemm_nt_f8f8bf16_one(
|
| 209 |
+
n: int,
|
| 210 |
+
k: int,
|
| 211 |
+
_: int, # _ is a dummy parameter to align with other interfaces
|
| 212 |
+
config: Tuple[int, int, int, int, Tuple[int, bool], Tuple[int, int, int]],
|
| 213 |
+
) -> None:
|
| 214 |
+
global deep_gemm_includes, deep_gemm_gemm_template
|
| 215 |
+
_, block_m, block_n, num_stages, tma_multicast_config, smem_config = config
|
| 216 |
+
_ = jit_tuner.compile_and_tune(
|
| 217 |
+
name="gemm_fp8_fp8_bf16_nt",
|
| 218 |
+
keys={
|
| 219 |
+
"N": n,
|
| 220 |
+
"K": k,
|
| 221 |
+
"BLOCK_M": block_m,
|
| 222 |
+
"BLOCK_N": block_n,
|
| 223 |
+
"SWIZZLE_D_MODE": smem_config[1],
|
| 224 |
+
"BLOCK_N_PADDING": smem_config[2],
|
| 225 |
+
"NUM_STAGES": num_stages,
|
| 226 |
+
"NUM_TMA_MULTICAST": tma_multicast_config[0],
|
| 227 |
+
"IS_TMA_MULTICAST_ON_A": tma_multicast_config[1],
|
| 228 |
+
},
|
| 229 |
+
space=(),
|
| 230 |
+
includes=deep_gemm_includes,
|
| 231 |
+
arg_defs=(
|
| 232 |
+
("lhs", torch.float8_e4m3fn),
|
| 233 |
+
("lhs_scales", torch.float),
|
| 234 |
+
("rhs", torch.float8_e4m3fn),
|
| 235 |
+
("rhs_scales", torch.float),
|
| 236 |
+
("out", torch.bfloat16),
|
| 237 |
+
("m", int),
|
| 238 |
+
("stream", torch.cuda.Stream),
|
| 239 |
+
("num_sms", int),
|
| 240 |
+
("smem_size", int),
|
| 241 |
+
),
|
| 242 |
+
template=deep_gemm_gemm_template,
|
| 243 |
+
args=[],
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
_KERNEL_HELPER_DICT: Dict[DeepGemmKernelType, DeepGemmKernelHelper] = {
|
| 248 |
+
DeepGemmKernelType.GROUPED_GEMM_NT_F8F8BF16_MASKED: DeepGemmKernelHelper(
|
| 249 |
+
name="m_grouped_gemm_fp8_fp8_bf16_nt_masked",
|
| 250 |
+
compile_func=_compile_grouped_gemm_nt_f8f8bf16_masked_one,
|
| 251 |
+
configure_func=lambda m, n, k, num_groups, num_sms: get_best_configs(
|
| 252 |
+
m, n, k, num_groups, num_sms, is_grouped_masked=True
|
| 253 |
+
),
|
| 254 |
+
),
|
| 255 |
+
DeepGemmKernelType.GROUPED_GEMM_NT_F8F8BF16_CONTIG: DeepGemmKernelHelper(
|
| 256 |
+
name="m_grouped_gemm_fp8_fp8_bf16_nt_contiguous",
|
| 257 |
+
compile_func=_compile_grouped_gemm_nt_f8f8bf16_contig_one,
|
| 258 |
+
configure_func=lambda m, n, k, _, num_sms: get_best_configs(
|
| 259 |
+
m, n, k, 1, num_sms, is_grouped_contiguous=True
|
| 260 |
+
),
|
| 261 |
+
),
|
| 262 |
+
DeepGemmKernelType.GEMM_NT_F8F8BF16: DeepGemmKernelHelper(
|
| 263 |
+
name="gemm_fp8_fp8_bf16_nt",
|
| 264 |
+
compile_func=_compile_gemm_nt_f8f8bf16_one,
|
| 265 |
+
configure_func=lambda m, n, k, _, num_sms: get_best_configs(
|
| 266 |
+
m, n, k, 1, num_sms
|
| 267 |
+
),
|
| 268 |
+
),
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def _maybe_compile_deep_gemm_one_type_all(
|
| 273 |
+
kernel_type: DeepGemmKernelType,
|
| 274 |
+
n: int,
|
| 275 |
+
k: int,
|
| 276 |
+
num_groups: int,
|
| 277 |
+
m_list: Optional[List[int]] = None,
|
| 278 |
+
) -> None:
|
| 279 |
+
|
| 280 |
+
global _INITIALIZATION_DICT
|
| 281 |
+
global _BUILTIN_M_LIST
|
| 282 |
+
|
| 283 |
+
query_key = (kernel_type, n, k, num_groups)
|
| 284 |
+
if (
|
| 285 |
+
_ENABLE_JIT_DEEPGEMM_PRECOMPILE
|
| 286 |
+
and _DO_COMPILE_ALL
|
| 287 |
+
and _INITIALIZATION_DICT.get(query_key) is None
|
| 288 |
+
):
|
| 289 |
+
_INITIALIZATION_DICT[query_key] = True
|
| 290 |
+
|
| 291 |
+
kernel_helper = _KERNEL_HELPER_DICT[kernel_type]
|
| 292 |
+
_compile_warning_1()
|
| 293 |
+
logger.info(
|
| 294 |
+
f"Try DeepGEMM JIT Compiling for "
|
| 295 |
+
f"<{kernel_helper.name}> N={n}, K={k}, num_groups={num_groups} with all Ms."
|
| 296 |
+
f"{' It only takes a litte time(Typically 1 sec) if you have run `sglang.compile_deep_gemm`. ' if not _IN_PRECOMPILE_STAGE else ''}"
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
# NOTE(alcanderian): get_num_sms should be change when 2-batch-overlap is introduced
|
| 300 |
+
num_sms = get_num_sms()
|
| 301 |
+
collected_configs = set()
|
| 302 |
+
for m in m_list if m_list is not None else _BUILTIN_M_LIST:
|
| 303 |
+
# Put config into set to get unique configs and reduce cases to be compiled
|
| 304 |
+
collected_configs.add(
|
| 305 |
+
kernel_helper.configure_func(m, n, k, num_groups, num_sms)
|
| 306 |
+
)
|
| 307 |
+
compile_func = lambda config: kernel_helper.compile_func(
|
| 308 |
+
n, k, num_groups, config
|
| 309 |
+
)
|
| 310 |
+
thread_map(compile_func, collected_configs, max_workers=_COMPILE_WORKERS)
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def grouped_gemm_nt_f8f8bf16_masked(
|
| 314 |
+
lhs: Tuple[torch.Tensor, torch.Tensor],
|
| 315 |
+
rhs: Tuple[torch.Tensor, torch.Tensor],
|
| 316 |
+
out: torch.Tensor,
|
| 317 |
+
masked_m: torch.Tensor,
|
| 318 |
+
expected_m: int,
|
| 319 |
+
):
|
| 320 |
+
num_groups, _, k = lhs[0].shape
|
| 321 |
+
_, n, _ = rhs[0].shape
|
| 322 |
+
|
| 323 |
+
kernel_type = DeepGemmKernelType.GROUPED_GEMM_NT_F8F8BF16_MASKED
|
| 324 |
+
_maybe_compile_deep_gemm_one_type_all(kernel_type, n, k, num_groups)
|
| 325 |
+
|
| 326 |
+
with _log_jit_build(expected_m, n, k, kernel_type):
|
| 327 |
+
deep_gemm.m_grouped_gemm_fp8_fp8_bf16_nt_masked(
|
| 328 |
+
lhs, rhs, out, masked_m, expected_m
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def grouped_gemm_nt_f8f8bf16_contig(
|
| 333 |
+
lhs: Tuple[torch.Tensor, torch.Tensor],
|
| 334 |
+
rhs: Tuple[torch.Tensor, torch.Tensor],
|
| 335 |
+
out: torch.Tensor,
|
| 336 |
+
m_indices: torch.Tensor,
|
| 337 |
+
):
|
| 338 |
+
m, k = lhs[0].shape
|
| 339 |
+
num_groups, n, _ = rhs[0].shape
|
| 340 |
+
|
| 341 |
+
kernel_type = DeepGemmKernelType.GROUPED_GEMM_NT_F8F8BF16_CONTIG
|
| 342 |
+
_maybe_compile_deep_gemm_one_type_all(kernel_type, n, k, num_groups)
|
| 343 |
+
|
| 344 |
+
with _log_jit_build(m, n, k, kernel_type):
|
| 345 |
+
deep_gemm.m_grouped_gemm_fp8_fp8_bf16_nt_contiguous(lhs, rhs, out, m_indices)
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def gemm_nt_f8f8bf16(
|
| 349 |
+
lhs: Tuple[torch.Tensor, torch.Tensor],
|
| 350 |
+
rhs: Tuple[torch.Tensor, torch.Tensor],
|
| 351 |
+
out: torch.Tensor,
|
| 352 |
+
):
|
| 353 |
+
m, k = lhs[0].shape
|
| 354 |
+
n, _ = rhs[0].shape
|
| 355 |
+
|
| 356 |
+
kernel_type = DeepGemmKernelType.GEMM_NT_F8F8BF16
|
| 357 |
+
_maybe_compile_deep_gemm_one_type_all(kernel_type, n, k, 1)
|
| 358 |
+
|
| 359 |
+
with _log_jit_build(m, n, k, kernel_type):
|
| 360 |
+
deep_gemm.gemm_fp8_fp8_bf16_nt(lhs, rhs, out)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
@contextmanager
|
| 364 |
+
def _log_jit_build(M: int, N: int, K: int, kernel_type: DeepGemmKernelType):
|
| 365 |
+
if _IN_PRECOMPILE_STAGE:
|
| 366 |
+
yield
|
| 367 |
+
return
|
| 368 |
+
|
| 369 |
+
from deep_gemm.jit.runtime import RuntimeCache
|
| 370 |
+
|
| 371 |
+
origin_func = RuntimeCache.__getitem__
|
| 372 |
+
|
| 373 |
+
def __patched_func(self, *args, **kwargs):
|
| 374 |
+
ret = origin_func(self, *args, **kwargs)
|
| 375 |
+
if ret is None:
|
| 376 |
+
kernel_helper = _KERNEL_HELPER_DICT[kernel_type]
|
| 377 |
+
_compile_warning_2()
|
| 378 |
+
logger.warning(
|
| 379 |
+
f"DeepGEMM JIT Compiling for <{kernel_helper.name}> M={M}, N={N}, K={K}. Please wait."
|
| 380 |
+
)
|
| 381 |
+
return ret
|
| 382 |
+
|
| 383 |
+
RuntimeCache.__getitem__ = __patched_func
|
| 384 |
+
yield
|
| 385 |
+
RuntimeCache.__getitem__ = origin_func
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8.py
ADDED
|
@@ -0,0 +1,997 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/v0.6.4.post1/vllm/model_executor/layers/quantization/fp8.py
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Any, Callable, Dict, List, Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from torch.nn import Module
|
| 9 |
+
from torch.nn.parameter import Parameter
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
from vllm.model_executor.layers.quantization.utils.marlin_utils_fp8 import (
|
| 13 |
+
apply_fp8_marlin_linear,
|
| 14 |
+
prepare_fp8_layer_for_marlin,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
MARLIN_FP8_AVAILABLE = True
|
| 18 |
+
except ImportError:
|
| 19 |
+
MARLIN_FP8_AVAILABLE = False
|
| 20 |
+
|
| 21 |
+
def dummy_func(*args, **kwargs):
|
| 22 |
+
raise ImportError(
|
| 23 |
+
"marlin FP8 requires some operators from vllm. Please install vllm."
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
apply_fp8_marlin_linear = prepare_fp8_layer_for_marlin = dummy_func
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
from sglang.srt.distributed import get_tensor_model_parallel_world_size
|
| 30 |
+
from sglang.srt.layers.linear import (
|
| 31 |
+
LinearBase,
|
| 32 |
+
LinearMethodBase,
|
| 33 |
+
UnquantizedLinearMethod,
|
| 34 |
+
)
|
| 35 |
+
from sglang.srt.layers.parameter import (
|
| 36 |
+
BlockQuantScaleParameter,
|
| 37 |
+
ModelWeightParameter,
|
| 38 |
+
PerTensorScaleParameter,
|
| 39 |
+
)
|
| 40 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 41 |
+
QuantizationConfig,
|
| 42 |
+
QuantizeMethodBase,
|
| 43 |
+
)
|
| 44 |
+
from sglang.srt.layers.quantization.fp8_kernel import (
|
| 45 |
+
per_token_group_quant_fp8,
|
| 46 |
+
scaled_fp8_quant,
|
| 47 |
+
)
|
| 48 |
+
from sglang.srt.layers.quantization.fp8_utils import (
|
| 49 |
+
apply_fp8_linear,
|
| 50 |
+
apply_w8a8_block_fp8_linear,
|
| 51 |
+
cutlass_fp8_supported,
|
| 52 |
+
input_to_float8,
|
| 53 |
+
normalize_e4m3fn_to_e4m3fnuz,
|
| 54 |
+
)
|
| 55 |
+
from sglang.srt.layers.quantization.kv_cache import BaseKVCacheMethod
|
| 56 |
+
from sglang.srt.layers.quantization.utils import (
|
| 57 |
+
all_close_1d,
|
| 58 |
+
convert_to_channelwise,
|
| 59 |
+
is_layer_skipped,
|
| 60 |
+
per_tensor_dequantize,
|
| 61 |
+
requantize_with_max_scale,
|
| 62 |
+
)
|
| 63 |
+
from sglang.srt.utils import (
|
| 64 |
+
get_bool_env_var,
|
| 65 |
+
is_cuda,
|
| 66 |
+
is_hip,
|
| 67 |
+
print_warning_once,
|
| 68 |
+
set_weight_attrs,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
_is_hip = is_hip()
|
| 72 |
+
_is_cuda = is_cuda()
|
| 73 |
+
|
| 74 |
+
if _is_hip:
|
| 75 |
+
from aiter import ActivationType, QuantType
|
| 76 |
+
from aiter.fused_moe_bf16_asm import asm_moe, ck_moe_2stages
|
| 77 |
+
from aiter.ops.shuffle import shuffle_weight
|
| 78 |
+
|
| 79 |
+
if not _is_cuda:
|
| 80 |
+
from vllm._custom_ops import scaled_fp8_quant
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
ACTIVATION_SCHEMES = ["static", "dynamic"]
|
| 84 |
+
|
| 85 |
+
logger = logging.getLogger(__name__)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class Fp8Config(QuantizationConfig):
|
| 89 |
+
"""Config class for FP8."""
|
| 90 |
+
|
| 91 |
+
def __init__(
|
| 92 |
+
self,
|
| 93 |
+
is_checkpoint_fp8_serialized: bool = False,
|
| 94 |
+
activation_scheme: str = "dynamic",
|
| 95 |
+
ignored_layers: Optional[List[str]] = None,
|
| 96 |
+
weight_block_size: List[int] = None,
|
| 97 |
+
) -> None:
|
| 98 |
+
self.is_checkpoint_fp8_serialized = is_checkpoint_fp8_serialized
|
| 99 |
+
if is_checkpoint_fp8_serialized:
|
| 100 |
+
logger.warning(
|
| 101 |
+
"Detected fp8 checkpoint. Please note that the "
|
| 102 |
+
"format is experimental and subject to change."
|
| 103 |
+
)
|
| 104 |
+
if activation_scheme not in ACTIVATION_SCHEMES:
|
| 105 |
+
raise ValueError(f"Unsupported activation scheme {activation_scheme}")
|
| 106 |
+
self.activation_scheme = activation_scheme
|
| 107 |
+
self.ignored_layers = ignored_layers or []
|
| 108 |
+
if weight_block_size is not None:
|
| 109 |
+
if not is_checkpoint_fp8_serialized:
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"The block-wise quantization only supports fp8-serialized checkpoint for now."
|
| 112 |
+
)
|
| 113 |
+
if len(weight_block_size) != 2:
|
| 114 |
+
raise ValueError(
|
| 115 |
+
f"The quantization block size of weight must have 2 dimensions, but got {len(weight_block_size)} dimensions."
|
| 116 |
+
)
|
| 117 |
+
if activation_scheme != "dynamic":
|
| 118 |
+
raise ValueError(
|
| 119 |
+
f"The block-wise quantization only supports dynamic activation scheme for now, but got {activation_scheme} activation scheme."
|
| 120 |
+
)
|
| 121 |
+
self.weight_block_size = weight_block_size
|
| 122 |
+
|
| 123 |
+
@classmethod
|
| 124 |
+
def get_name(cls) -> str:
|
| 125 |
+
return "fp8"
|
| 126 |
+
|
| 127 |
+
@classmethod
|
| 128 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 129 |
+
return [torch.bfloat16, torch.half]
|
| 130 |
+
|
| 131 |
+
@classmethod
|
| 132 |
+
def get_min_capability(cls) -> int:
|
| 133 |
+
return 80
|
| 134 |
+
|
| 135 |
+
@classmethod
|
| 136 |
+
def get_config_filenames(cls) -> List[str]:
|
| 137 |
+
return []
|
| 138 |
+
|
| 139 |
+
@classmethod
|
| 140 |
+
def from_config(cls, config: Dict[str, Any]) -> "Fp8Config":
|
| 141 |
+
quant_method = cls.get_from_keys(config, ["quant_method"])
|
| 142 |
+
is_checkpoint_fp8_serialized = "fp8" in quant_method
|
| 143 |
+
activation_scheme = cls.get_from_keys(config, ["activation_scheme"])
|
| 144 |
+
ignored_layers = cls.get_from_keys_or(config, ["ignored_layers"], None)
|
| 145 |
+
weight_block_size = cls.get_from_keys_or(config, ["weight_block_size"], None)
|
| 146 |
+
return cls(
|
| 147 |
+
is_checkpoint_fp8_serialized=is_checkpoint_fp8_serialized,
|
| 148 |
+
activation_scheme=activation_scheme,
|
| 149 |
+
ignored_layers=ignored_layers,
|
| 150 |
+
weight_block_size=weight_block_size,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
def get_quant_method(
|
| 154 |
+
self, layer: torch.nn.Module, prefix: str
|
| 155 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 156 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
|
| 157 |
+
|
| 158 |
+
if isinstance(layer, LinearBase):
|
| 159 |
+
if is_layer_skipped(prefix, self.ignored_layers):
|
| 160 |
+
return UnquantizedLinearMethod()
|
| 161 |
+
return Fp8LinearMethod(self)
|
| 162 |
+
elif isinstance(layer, FusedMoE):
|
| 163 |
+
return Fp8MoEMethod(self)
|
| 164 |
+
return None
|
| 165 |
+
|
| 166 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 167 |
+
return []
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class Fp8LinearMethod(LinearMethodBase):
|
| 171 |
+
"""Linear method for FP8.
|
| 172 |
+
Supports loading FP8 checkpoints with static weight scale and
|
| 173 |
+
dynamic/static activation scale.
|
| 174 |
+
|
| 175 |
+
Also supports loading quantized FP16/BF16 model checkpoints with dynamic
|
| 176 |
+
activation scaling. The weight scaling factor will be initialized after
|
| 177 |
+
the model weights are loaded.
|
| 178 |
+
|
| 179 |
+
Limitations:
|
| 180 |
+
1. Only support per-tensor quantization due to torch._scaled_mm support.
|
| 181 |
+
2. Only support float8_e4m3fn data type due to the limitation of
|
| 182 |
+
torch._scaled_mm (https://github.com/pytorch/pytorch/blob/2e48b39603411a41c5025efbe52f89560b827825/aten/src/ATen/native/cuda/Blas.cpp#L854-L856)
|
| 183 |
+
|
| 184 |
+
Args:
|
| 185 |
+
quant_config: The quantization config.
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
def __init__(self, quant_config: Fp8Config):
|
| 189 |
+
self.quant_config = quant_config
|
| 190 |
+
self.cutlass_fp8_supported = cutlass_fp8_supported()
|
| 191 |
+
|
| 192 |
+
# For GPUs that lack FP8 hardware support, we can leverage the Marlin
|
| 193 |
+
# kernel for fast weight-only FP8 quantization
|
| 194 |
+
self.use_marlin = (
|
| 195 |
+
get_bool_env_var("SGLANG_FORCE_FP8_MARLIN") and MARLIN_FP8_AVAILABLE
|
| 196 |
+
)
|
| 197 |
+
# Disable marlin for ROCm
|
| 198 |
+
if _is_hip:
|
| 199 |
+
self.use_marlin = False
|
| 200 |
+
|
| 201 |
+
self.block_quant = self.quant_config.weight_block_size is not None
|
| 202 |
+
if self.block_quant:
|
| 203 |
+
# Marlin doesn't support block-wise fp8
|
| 204 |
+
self.use_marlin = False
|
| 205 |
+
|
| 206 |
+
def create_weights(
|
| 207 |
+
self,
|
| 208 |
+
layer: torch.nn.Module,
|
| 209 |
+
input_size_per_partition: int,
|
| 210 |
+
output_partition_sizes: List[int],
|
| 211 |
+
input_size: int,
|
| 212 |
+
output_size: int,
|
| 213 |
+
params_dtype: torch.dtype,
|
| 214 |
+
**extra_weight_attrs,
|
| 215 |
+
):
|
| 216 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 217 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 218 |
+
|
| 219 |
+
tp_size = get_tensor_model_parallel_world_size()
|
| 220 |
+
if self.block_quant:
|
| 221 |
+
block_n, block_k = (
|
| 222 |
+
self.quant_config.weight_block_size[0],
|
| 223 |
+
self.quant_config.weight_block_size[1],
|
| 224 |
+
)
|
| 225 |
+
# Required by row parallel
|
| 226 |
+
if tp_size > 1 and input_size // input_size_per_partition == tp_size:
|
| 227 |
+
if input_size_per_partition % block_k != 0:
|
| 228 |
+
raise ValueError(
|
| 229 |
+
f"Weight input_size_per_partition = "
|
| 230 |
+
f"{input_size_per_partition} is not divisible by "
|
| 231 |
+
f"weight quantization block_k = {block_k}."
|
| 232 |
+
)
|
| 233 |
+
# Required by collum parallel or enabling merged weights
|
| 234 |
+
if (
|
| 235 |
+
tp_size > 1 and output_size // output_size_per_partition == tp_size
|
| 236 |
+
) or len(output_partition_sizes) > 1:
|
| 237 |
+
for output_partition_size in output_partition_sizes:
|
| 238 |
+
if output_partition_size % block_n != 0:
|
| 239 |
+
raise ValueError(
|
| 240 |
+
f"Weight output_partition_size = "
|
| 241 |
+
f"{output_partition_size} is not divisible by "
|
| 242 |
+
f"weight quantization block_n = {block_n}."
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
layer.logical_widths = output_partition_sizes
|
| 246 |
+
layer.input_size_per_partition = input_size_per_partition
|
| 247 |
+
layer.output_size_per_partition = output_size_per_partition
|
| 248 |
+
layer.orig_dtype = params_dtype
|
| 249 |
+
|
| 250 |
+
# WEIGHT
|
| 251 |
+
weight_dtype = (
|
| 252 |
+
torch.float8_e4m3fn
|
| 253 |
+
if self.quant_config.is_checkpoint_fp8_serialized
|
| 254 |
+
else params_dtype
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
weight = ModelWeightParameter(
|
| 258 |
+
data=torch.empty(
|
| 259 |
+
output_size_per_partition, input_size_per_partition, dtype=weight_dtype
|
| 260 |
+
),
|
| 261 |
+
input_dim=1,
|
| 262 |
+
output_dim=0,
|
| 263 |
+
weight_loader=weight_loader,
|
| 264 |
+
)
|
| 265 |
+
layer.register_parameter("weight", weight)
|
| 266 |
+
|
| 267 |
+
# If checkpoint is serialized fp8, load them.
|
| 268 |
+
# Otherwise, wait until process_weights_after_loading.
|
| 269 |
+
if self.quant_config.is_checkpoint_fp8_serialized:
|
| 270 |
+
# WEIGHT SCALE
|
| 271 |
+
if self.block_quant:
|
| 272 |
+
assert self.quant_config.activation_scheme == "dynamic"
|
| 273 |
+
scale = BlockQuantScaleParameter(
|
| 274 |
+
data=torch.empty(
|
| 275 |
+
(output_size_per_partition + block_n - 1) // block_n,
|
| 276 |
+
(input_size_per_partition + block_k - 1) // block_k,
|
| 277 |
+
dtype=torch.float32,
|
| 278 |
+
),
|
| 279 |
+
input_dim=1,
|
| 280 |
+
output_dim=0,
|
| 281 |
+
weight_loader=weight_loader,
|
| 282 |
+
)
|
| 283 |
+
scale[:] = torch.finfo(torch.float32).min
|
| 284 |
+
layer.register_parameter("weight_scale_inv", scale)
|
| 285 |
+
else:
|
| 286 |
+
scale = PerTensorScaleParameter(
|
| 287 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 288 |
+
weight_loader=weight_loader,
|
| 289 |
+
)
|
| 290 |
+
scale[:] = torch.finfo(torch.float32).min
|
| 291 |
+
layer.register_parameter("weight_scale", scale)
|
| 292 |
+
|
| 293 |
+
# INPUT ACTIVATION SCALE
|
| 294 |
+
if self.quant_config.activation_scheme == "static":
|
| 295 |
+
scale = PerTensorScaleParameter(
|
| 296 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 297 |
+
weight_loader=weight_loader,
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
scale[:] = torch.finfo(torch.float32).min
|
| 301 |
+
layer.register_parameter("input_scale", scale)
|
| 302 |
+
else:
|
| 303 |
+
layer.register_parameter("input_scale", None)
|
| 304 |
+
|
| 305 |
+
def process_weights_after_loading(self, layer: Module) -> None:
|
| 306 |
+
# Block quant doesn't need to process weights after loading
|
| 307 |
+
if self.block_quant:
|
| 308 |
+
# If ROCm, normalize the weights and scales to e4m3fnuz
|
| 309 |
+
if _is_hip:
|
| 310 |
+
# activation_scheme: dynamic
|
| 311 |
+
weight, weight_scale, _ = normalize_e4m3fn_to_e4m3fnuz(
|
| 312 |
+
weight=layer.weight,
|
| 313 |
+
weight_scale=layer.weight_scale_inv,
|
| 314 |
+
input_scale=None,
|
| 315 |
+
)
|
| 316 |
+
layer.weight = torch.nn.Parameter(weight, requires_grad=False)
|
| 317 |
+
layer.weight_scale_inv = torch.nn.Parameter(
|
| 318 |
+
weight_scale, requires_grad=False
|
| 319 |
+
)
|
| 320 |
+
layer.input_scale = None
|
| 321 |
+
else:
|
| 322 |
+
layer.weight = torch.nn.Parameter(
|
| 323 |
+
layer.weight.data, requires_grad=False
|
| 324 |
+
)
|
| 325 |
+
layer.weight_scale_inv = torch.nn.Parameter(
|
| 326 |
+
layer.weight_scale_inv.data, requires_grad=False
|
| 327 |
+
)
|
| 328 |
+
return
|
| 329 |
+
|
| 330 |
+
layer.weight = torch.nn.Parameter(layer.weight.data, requires_grad=False)
|
| 331 |
+
|
| 332 |
+
# If checkpoint not serialized fp8, quantize the weights.
|
| 333 |
+
if not self.quant_config.is_checkpoint_fp8_serialized:
|
| 334 |
+
if self.cutlass_fp8_supported or self.use_marlin:
|
| 335 |
+
# apply per-channel quantization default, as cutlass sgl-kernel and marlin only support per-channel scale
|
| 336 |
+
qweight, weight_scale = per_token_group_quant_fp8(
|
| 337 |
+
layer.weight, layer.weight.shape[-1]
|
| 338 |
+
)
|
| 339 |
+
weight_scale = weight_scale.t().contiguous()
|
| 340 |
+
else:
|
| 341 |
+
# per-tensor quantization
|
| 342 |
+
qweight, weight_scale = input_to_float8(layer.weight)
|
| 343 |
+
|
| 344 |
+
# Update the layer with the new values.
|
| 345 |
+
layer.weight = Parameter(qweight.t(), requires_grad=False)
|
| 346 |
+
layer.weight_scale = Parameter(weight_scale, requires_grad=False)
|
| 347 |
+
layer.input_scale = None
|
| 348 |
+
|
| 349 |
+
# If checkpoint is fp8, handle that there are N scales for N
|
| 350 |
+
# shards in a fused module
|
| 351 |
+
else:
|
| 352 |
+
layer.weight_scale = torch.nn.Parameter(
|
| 353 |
+
layer.weight_scale.data, requires_grad=False
|
| 354 |
+
)
|
| 355 |
+
if self.quant_config.activation_scheme == "static":
|
| 356 |
+
layer.input_scale = torch.nn.Parameter(
|
| 357 |
+
layer.input_scale.data, requires_grad=False
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
# cutlass sgl-kernel and marlin only support per-channel scale
|
| 361 |
+
if self.cutlass_fp8_supported or self.use_marlin:
|
| 362 |
+
weight = layer.weight
|
| 363 |
+
weight_scale = convert_to_channelwise(
|
| 364 |
+
layer.weight_scale, layer.logical_widths
|
| 365 |
+
)
|
| 366 |
+
else:
|
| 367 |
+
# Dequant -> Quant with max scale so we can run per tensor.
|
| 368 |
+
weight = layer.weight
|
| 369 |
+
weight_scale = layer.weight_scale
|
| 370 |
+
# If ROCm, normalize the weights and scales to e4m3fnuz
|
| 371 |
+
if _is_hip:
|
| 372 |
+
weight, weight_scale, input_scale = normalize_e4m3fn_to_e4m3fnuz(
|
| 373 |
+
weight=weight,
|
| 374 |
+
weight_scale=weight_scale,
|
| 375 |
+
input_scale=layer.input_scale,
|
| 376 |
+
)
|
| 377 |
+
if input_scale is not None:
|
| 378 |
+
layer.input_scale = Parameter(input_scale, requires_grad=False)
|
| 379 |
+
|
| 380 |
+
weight_scale, weight = requantize_with_max_scale(
|
| 381 |
+
weight=weight,
|
| 382 |
+
weight_scale=weight_scale,
|
| 383 |
+
logical_widths=layer.logical_widths,
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
# Update layer with new values.
|
| 387 |
+
layer.weight = Parameter(weight.t(), requires_grad=False)
|
| 388 |
+
layer.weight_scale = Parameter(weight_scale, requires_grad=False)
|
| 389 |
+
if self.quant_config.activation_scheme == "static":
|
| 390 |
+
layer.input_scale = Parameter(
|
| 391 |
+
layer.input_scale.max(), requires_grad=False
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
if self.use_marlin:
|
| 395 |
+
prepare_fp8_layer_for_marlin(layer)
|
| 396 |
+
# Activations not quantized for marlin.
|
| 397 |
+
del layer.input_scale
|
| 398 |
+
|
| 399 |
+
def apply(
|
| 400 |
+
self,
|
| 401 |
+
layer: torch.nn.Module,
|
| 402 |
+
x: torch.Tensor,
|
| 403 |
+
bias: Optional[torch.Tensor] = None,
|
| 404 |
+
) -> torch.Tensor:
|
| 405 |
+
|
| 406 |
+
if self.use_marlin:
|
| 407 |
+
return apply_fp8_marlin_linear(
|
| 408 |
+
input=x,
|
| 409 |
+
weight=layer.weight,
|
| 410 |
+
weight_scale=layer.weight_scale,
|
| 411 |
+
workspace=layer.workspace,
|
| 412 |
+
size_n=layer.output_size_per_partition,
|
| 413 |
+
size_k=layer.input_size_per_partition,
|
| 414 |
+
bias=bias,
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
if self.block_quant:
|
| 418 |
+
return apply_w8a8_block_fp8_linear(
|
| 419 |
+
input=x,
|
| 420 |
+
weight=layer.weight,
|
| 421 |
+
block_size=self.quant_config.weight_block_size,
|
| 422 |
+
weight_scale=layer.weight_scale_inv,
|
| 423 |
+
input_scale=None,
|
| 424 |
+
bias=bias,
|
| 425 |
+
)
|
| 426 |
+
|
| 427 |
+
return apply_fp8_linear(
|
| 428 |
+
input=x,
|
| 429 |
+
weight=layer.weight,
|
| 430 |
+
weight_scale=layer.weight_scale,
|
| 431 |
+
input_scale=layer.input_scale,
|
| 432 |
+
bias=bias,
|
| 433 |
+
cutlass_fp8_supported=self.cutlass_fp8_supported,
|
| 434 |
+
use_per_token_if_dynamic=False,
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
class Fp8MoEMethod:
|
| 439 |
+
"""MoE method for FP8.
|
| 440 |
+
Supports loading FP8 checkpoints with static weight scale and
|
| 441 |
+
dynamic/static activation scale.
|
| 442 |
+
|
| 443 |
+
Also supports loading quantized FP16/BF16 model checkpoints with dynamic
|
| 444 |
+
activation scaling. The weight scaling factor will be initialized after
|
| 445 |
+
the model weights are loaded.
|
| 446 |
+
|
| 447 |
+
Args:
|
| 448 |
+
quant_config: The quantization config.
|
| 449 |
+
"""
|
| 450 |
+
|
| 451 |
+
def __new__(cls, *args, **kwargs):
|
| 452 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoEMethodBase
|
| 453 |
+
|
| 454 |
+
if not hasattr(cls, "_initialized"):
|
| 455 |
+
original_init = cls.__init__
|
| 456 |
+
new_cls = type(
|
| 457 |
+
cls.__name__,
|
| 458 |
+
(FusedMoEMethodBase,),
|
| 459 |
+
{
|
| 460 |
+
"__init__": original_init,
|
| 461 |
+
**{k: v for k, v in cls.__dict__.items() if k != "__dict__"},
|
| 462 |
+
},
|
| 463 |
+
)
|
| 464 |
+
obj = super(new_cls, new_cls).__new__(new_cls)
|
| 465 |
+
obj.__init__(*args, **kwargs)
|
| 466 |
+
return obj
|
| 467 |
+
return super().__new__(cls)
|
| 468 |
+
|
| 469 |
+
def __init__(self, quant_config):
|
| 470 |
+
self.quant_config = quant_config
|
| 471 |
+
self.block_quant = self.quant_config.weight_block_size is not None
|
| 472 |
+
|
| 473 |
+
def create_weights(
|
| 474 |
+
self,
|
| 475 |
+
layer: Module,
|
| 476 |
+
num_experts: int,
|
| 477 |
+
hidden_size: int,
|
| 478 |
+
intermediate_size: int,
|
| 479 |
+
params_dtype: torch.dtype,
|
| 480 |
+
**extra_weight_attrs,
|
| 481 |
+
):
|
| 482 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoeWeightScaleSupported
|
| 483 |
+
|
| 484 |
+
if self.quant_config.is_checkpoint_fp8_serialized:
|
| 485 |
+
params_dtype = (
|
| 486 |
+
torch.uint32
|
| 487 |
+
if get_bool_env_var("SGLANG_INT4_WEIGHT")
|
| 488 |
+
else torch.float8_e4m3fn
|
| 489 |
+
)
|
| 490 |
+
tp_size = get_tensor_model_parallel_world_size()
|
| 491 |
+
if self.block_quant:
|
| 492 |
+
block_n, block_k = (
|
| 493 |
+
self.quant_config.weight_block_size[0],
|
| 494 |
+
self.quant_config.weight_block_size[1],
|
| 495 |
+
)
|
| 496 |
+
# NOTE(HandH1998): To ensure proper alignment of the block-wise quantization scales, the output_size of the weights for both the gate and up layers must be divisible by block_n.
|
| 497 |
+
# Required by collum parallel or enabling merged weights
|
| 498 |
+
if intermediate_size % block_n != 0:
|
| 499 |
+
raise ValueError(
|
| 500 |
+
f"The output_size of gate's and up's weight = "
|
| 501 |
+
f"{intermediate_size} is not divisible by "
|
| 502 |
+
f"weight quantization block_n = {block_n}."
|
| 503 |
+
)
|
| 504 |
+
if tp_size > 1:
|
| 505 |
+
# Required by row parallel
|
| 506 |
+
if intermediate_size % block_k != 0:
|
| 507 |
+
raise ValueError(
|
| 508 |
+
f"The input_size of down's weight = "
|
| 509 |
+
f"{intermediate_size} is not divisible by "
|
| 510 |
+
f"weight quantization block_k = {block_k}."
|
| 511 |
+
)
|
| 512 |
+
|
| 513 |
+
# WEIGHTS
|
| 514 |
+
if _is_hip and get_bool_env_var("SGLANG_INT4_WEIGHT"):
|
| 515 |
+
# INT4 MoE weight - INT32 packed
|
| 516 |
+
w13_weight = torch.nn.Parameter(
|
| 517 |
+
torch.empty(
|
| 518 |
+
num_experts,
|
| 519 |
+
2 * intermediate_size,
|
| 520 |
+
hidden_size // 8,
|
| 521 |
+
dtype=params_dtype,
|
| 522 |
+
),
|
| 523 |
+
requires_grad=False,
|
| 524 |
+
)
|
| 525 |
+
w2_weight = torch.nn.Parameter(
|
| 526 |
+
torch.empty(
|
| 527 |
+
num_experts, hidden_size, intermediate_size // 8, dtype=params_dtype
|
| 528 |
+
),
|
| 529 |
+
requires_grad=False,
|
| 530 |
+
)
|
| 531 |
+
else:
|
| 532 |
+
w13_weight = torch.nn.Parameter(
|
| 533 |
+
torch.empty(
|
| 534 |
+
num_experts, 2 * intermediate_size, hidden_size, dtype=params_dtype
|
| 535 |
+
),
|
| 536 |
+
requires_grad=False,
|
| 537 |
+
)
|
| 538 |
+
w2_weight = torch.nn.Parameter(
|
| 539 |
+
torch.empty(
|
| 540 |
+
num_experts, hidden_size, intermediate_size, dtype=params_dtype
|
| 541 |
+
),
|
| 542 |
+
requires_grad=False,
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
layer.register_parameter("w13_weight", w13_weight)
|
| 546 |
+
set_weight_attrs(w13_weight, extra_weight_attrs)
|
| 547 |
+
|
| 548 |
+
layer.register_parameter("w2_weight", w2_weight)
|
| 549 |
+
set_weight_attrs(w2_weight, extra_weight_attrs)
|
| 550 |
+
|
| 551 |
+
# WEIGHT_SCALES
|
| 552 |
+
if self.block_quant:
|
| 553 |
+
w13_weight_scale = torch.nn.Parameter(
|
| 554 |
+
torch.ones(
|
| 555 |
+
num_experts,
|
| 556 |
+
2 * ((intermediate_size + block_n - 1) // block_n),
|
| 557 |
+
(hidden_size + block_k - 1) // block_k,
|
| 558 |
+
dtype=torch.float32,
|
| 559 |
+
),
|
| 560 |
+
requires_grad=False,
|
| 561 |
+
)
|
| 562 |
+
w2_weight_scale = torch.nn.Parameter(
|
| 563 |
+
torch.ones(
|
| 564 |
+
num_experts,
|
| 565 |
+
(hidden_size + block_n - 1) // block_n,
|
| 566 |
+
(intermediate_size + block_k - 1) // block_k,
|
| 567 |
+
dtype=torch.float32,
|
| 568 |
+
),
|
| 569 |
+
requires_grad=False,
|
| 570 |
+
)
|
| 571 |
+
layer.register_parameter("w13_weight_scale_inv", w13_weight_scale)
|
| 572 |
+
layer.register_parameter("w2_weight_scale_inv", w2_weight_scale)
|
| 573 |
+
assert self.quant_config.activation_scheme == "dynamic"
|
| 574 |
+
else:
|
| 575 |
+
# Allocate 2 scales for w1 and w3 respectively.
|
| 576 |
+
# They will be combined to a single scale after weight loading.
|
| 577 |
+
w13_weight_scale = torch.nn.Parameter(
|
| 578 |
+
torch.ones(num_experts, 2, dtype=torch.float32), requires_grad=False
|
| 579 |
+
)
|
| 580 |
+
w2_weight_scale = torch.nn.Parameter(
|
| 581 |
+
torch.ones(num_experts, dtype=torch.float32), requires_grad=False
|
| 582 |
+
)
|
| 583 |
+
layer.register_parameter("w13_weight_scale", w13_weight_scale)
|
| 584 |
+
layer.register_parameter("w2_weight_scale", w2_weight_scale)
|
| 585 |
+
|
| 586 |
+
if (
|
| 587 |
+
_is_hip
|
| 588 |
+
): # and get_bool_env_var("SGLANG_AITER_MOE"): TODO: add check back after triton kernel
|
| 589 |
+
# ROCm - using column scaling, duplicate scaling numbers in case per tensor scaling
|
| 590 |
+
w13_weight_scale1 = torch.nn.Parameter(
|
| 591 |
+
torch.ones(num_experts, 2 * intermediate_size, dtype=torch.float32),
|
| 592 |
+
requires_grad=False,
|
| 593 |
+
)
|
| 594 |
+
w2_weight_scale1 = torch.nn.Parameter(
|
| 595 |
+
torch.ones(num_experts, hidden_size, dtype=torch.float32),
|
| 596 |
+
requires_grad=False,
|
| 597 |
+
)
|
| 598 |
+
layer.register_parameter("w13_weight_scale1", w13_weight_scale1)
|
| 599 |
+
layer.register_parameter("w2_weight_scale1", w2_weight_scale1)
|
| 600 |
+
|
| 601 |
+
# Add the quantization method used (per tensor/grouped/channel)
|
| 602 |
+
# to ensure the weight scales are loaded in properly
|
| 603 |
+
extra_weight_attrs.update(
|
| 604 |
+
{"quant_method": FusedMoeWeightScaleSupported.BLOCK.value}
|
| 605 |
+
if self.block_quant
|
| 606 |
+
else {"quant_method": FusedMoeWeightScaleSupported.TENSOR.value}
|
| 607 |
+
)
|
| 608 |
+
# If loading fp8 checkpoint, pass the weight loaders.
|
| 609 |
+
# If loading an fp16 checkpoint, do not (we will quantize in
|
| 610 |
+
# process_weights_after_loading()
|
| 611 |
+
if self.quant_config.is_checkpoint_fp8_serialized:
|
| 612 |
+
set_weight_attrs(w13_weight_scale, extra_weight_attrs)
|
| 613 |
+
set_weight_attrs(w2_weight_scale, extra_weight_attrs)
|
| 614 |
+
|
| 615 |
+
if _is_hip and get_bool_env_var("SGLANG_INT4_WEIGHT"):
|
| 616 |
+
extra_weight_attrs.update(
|
| 617 |
+
{"quant_method": FusedMoeWeightScaleSupported.CHANNEL.value}
|
| 618 |
+
)
|
| 619 |
+
set_weight_attrs(w13_weight_scale1, extra_weight_attrs)
|
| 620 |
+
set_weight_attrs(w2_weight_scale1, extra_weight_attrs)
|
| 621 |
+
|
| 622 |
+
# INPUT_SCALES
|
| 623 |
+
if self.quant_config.activation_scheme == "static":
|
| 624 |
+
if not self.quant_config.is_checkpoint_fp8_serialized:
|
| 625 |
+
raise ValueError(
|
| 626 |
+
"Found static activation scheme for checkpoint that "
|
| 627 |
+
"was not serialized fp8."
|
| 628 |
+
)
|
| 629 |
+
|
| 630 |
+
w13_input_scale = torch.nn.Parameter(
|
| 631 |
+
torch.ones(num_experts, dtype=torch.float32), requires_grad=False
|
| 632 |
+
)
|
| 633 |
+
layer.register_parameter("w13_input_scale", w13_input_scale)
|
| 634 |
+
set_weight_attrs(w13_input_scale, extra_weight_attrs)
|
| 635 |
+
|
| 636 |
+
w2_input_scale = torch.nn.Parameter(
|
| 637 |
+
torch.ones(num_experts, dtype=torch.float32), requires_grad=False
|
| 638 |
+
)
|
| 639 |
+
layer.register_parameter("w2_input_scale", w2_input_scale)
|
| 640 |
+
set_weight_attrs(w2_input_scale, extra_weight_attrs)
|
| 641 |
+
|
| 642 |
+
else:
|
| 643 |
+
layer.w13_input_scale = None
|
| 644 |
+
layer.w2_input_scale = None
|
| 645 |
+
|
| 646 |
+
def process_weights_after_loading(self, layer: Module) -> None:
|
| 647 |
+
if _is_hip and get_bool_env_var("SGLANG_INT4_WEIGHT"):
|
| 648 |
+
self.process_weights_hip_int4(layer)
|
| 649 |
+
return
|
| 650 |
+
|
| 651 |
+
# Block quant doesn't need to process weights after loading
|
| 652 |
+
if self.block_quant:
|
| 653 |
+
# If ROCm, normalize the weights and scales to e4m3fnuz
|
| 654 |
+
if _is_hip:
|
| 655 |
+
# activation_scheme: dynamic
|
| 656 |
+
w13_weight, w13_weight_scale, _ = normalize_e4m3fn_to_e4m3fnuz(
|
| 657 |
+
weight=layer.w13_weight,
|
| 658 |
+
weight_scale=layer.w13_weight_scale_inv,
|
| 659 |
+
input_scale=None,
|
| 660 |
+
)
|
| 661 |
+
w2_weight, w2_weight_scale, _ = normalize_e4m3fn_to_e4m3fnuz(
|
| 662 |
+
weight=layer.w2_weight,
|
| 663 |
+
weight_scale=layer.w2_weight_scale_inv,
|
| 664 |
+
input_scale=None,
|
| 665 |
+
)
|
| 666 |
+
# Reset the parameter
|
| 667 |
+
layer.w13_weight = torch.nn.Parameter(w13_weight, requires_grad=False)
|
| 668 |
+
layer.w13_weight_scale_inv = torch.nn.Parameter(
|
| 669 |
+
w13_weight_scale, requires_grad=False
|
| 670 |
+
)
|
| 671 |
+
layer.w13_input_scale = None
|
| 672 |
+
layer.w2_weight = torch.nn.Parameter(w2_weight, requires_grad=False)
|
| 673 |
+
layer.w2_weight_scale_inv = torch.nn.Parameter(
|
| 674 |
+
w2_weight_scale, requires_grad=False
|
| 675 |
+
)
|
| 676 |
+
layer.w2_input_scale = None
|
| 677 |
+
|
| 678 |
+
if get_bool_env_var("SGLANG_AITER_MOE"):
|
| 679 |
+
# Pre-shuffle weights
|
| 680 |
+
layer.w13_weight.data = shuffle_weight(
|
| 681 |
+
layer.w13_weight.contiguous(), (16, 16)
|
| 682 |
+
)
|
| 683 |
+
layer.w2_weight.data = shuffle_weight(
|
| 684 |
+
layer.w2_weight.contiguous(), (16, 16)
|
| 685 |
+
)
|
| 686 |
+
return
|
| 687 |
+
|
| 688 |
+
# If checkpoint is fp16 or bfloat16, quantize in place.
|
| 689 |
+
if not self.quant_config.is_checkpoint_fp8_serialized:
|
| 690 |
+
# If ROCm, use float8_e4m3fnuz instead (MI300x HW)
|
| 691 |
+
fp8_dtype = torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
| 692 |
+
w13_weight = torch.empty_like(layer.w13_weight.data, dtype=fp8_dtype)
|
| 693 |
+
w2_weight = torch.empty_like(layer.w2_weight.data, dtype=fp8_dtype)
|
| 694 |
+
|
| 695 |
+
# Re-initialize w13_scale because we directly quantize
|
| 696 |
+
# merged w13 weights and generate a single scaling factor.
|
| 697 |
+
layer.w13_weight_scale = torch.nn.Parameter(
|
| 698 |
+
torch.ones(
|
| 699 |
+
layer.num_experts, dtype=torch.float32, device=w13_weight.device
|
| 700 |
+
),
|
| 701 |
+
requires_grad=False,
|
| 702 |
+
)
|
| 703 |
+
for expert in range(layer.num_experts):
|
| 704 |
+
w13_weight[expert, :, :], layer.w13_weight_scale[expert] = (
|
| 705 |
+
scaled_fp8_quant(layer.w13_weight.data[expert, :, :])
|
| 706 |
+
)
|
| 707 |
+
w2_weight[expert, :, :], layer.w2_weight_scale[expert] = (
|
| 708 |
+
scaled_fp8_quant(layer.w2_weight.data[expert, :, :])
|
| 709 |
+
)
|
| 710 |
+
layer.w13_weight = torch.nn.Parameter(w13_weight, requires_grad=False)
|
| 711 |
+
layer.w2_weight = torch.nn.Parameter(w2_weight, requires_grad=False)
|
| 712 |
+
|
| 713 |
+
if _is_hip:
|
| 714 |
+
self.process_weights_hip_scale_padding(layer)
|
| 715 |
+
return
|
| 716 |
+
|
| 717 |
+
# If checkpoint is fp8, we need to handle that the
|
| 718 |
+
# MoE kernels require single activation scale and single weight
|
| 719 |
+
# scale for w13 per expert.
|
| 720 |
+
else:
|
| 721 |
+
# Fp8 moe kernels require a single activation scale.
|
| 722 |
+
# We take the max of all the scales in case they differ.
|
| 723 |
+
if self.quant_config.activation_scheme == "static":
|
| 724 |
+
if layer.w13_input_scale is None or layer.w2_input_scale is None:
|
| 725 |
+
raise ValueError(
|
| 726 |
+
"QuantConfig has static quantization, but found "
|
| 727 |
+
"activation scales are None."
|
| 728 |
+
)
|
| 729 |
+
if not all_close_1d(layer.w13_input_scale) or not all_close_1d(
|
| 730 |
+
layer.w2_input_scale
|
| 731 |
+
):
|
| 732 |
+
print_warning_once(
|
| 733 |
+
"Found input_scales that are not equal for "
|
| 734 |
+
"fp8 MoE layer. Using the maximum across experts "
|
| 735 |
+
"for each layer. "
|
| 736 |
+
)
|
| 737 |
+
layer.w13_input_scale = torch.nn.Parameter(
|
| 738 |
+
layer.w13_input_scale.max(), requires_grad=False
|
| 739 |
+
)
|
| 740 |
+
layer.w2_input_scale = torch.nn.Parameter(
|
| 741 |
+
layer.w2_input_scale.max(), requires_grad=False
|
| 742 |
+
)
|
| 743 |
+
|
| 744 |
+
# If ROCm, normalize the weights and scales to e4m3fnuz
|
| 745 |
+
if _is_hip:
|
| 746 |
+
# Normalize the weights and scales
|
| 747 |
+
w13_weight, w13_weight_scale, w13_input_scale = (
|
| 748 |
+
normalize_e4m3fn_to_e4m3fnuz(
|
| 749 |
+
layer.w13_weight, layer.w13_weight_scale, layer.w13_input_scale
|
| 750 |
+
)
|
| 751 |
+
)
|
| 752 |
+
w2_weight, w2_weight_scale, w2_input_scale = (
|
| 753 |
+
normalize_e4m3fn_to_e4m3fnuz(
|
| 754 |
+
layer.w2_weight, layer.w2_weight_scale, layer.w2_input_scale
|
| 755 |
+
)
|
| 756 |
+
)
|
| 757 |
+
# Reset the parameter
|
| 758 |
+
layer.w13_weight = torch.nn.Parameter(w13_weight, requires_grad=False)
|
| 759 |
+
layer.w13_weight_scale = torch.nn.Parameter(
|
| 760 |
+
w13_weight_scale, requires_grad=False
|
| 761 |
+
)
|
| 762 |
+
if w13_input_scale is not None:
|
| 763 |
+
layer.w13_input_scale = torch.nn.Parameter(
|
| 764 |
+
w13_input_scale, requires_grad=False
|
| 765 |
+
)
|
| 766 |
+
layer.w2_weight = torch.nn.Parameter(w2_weight, requires_grad=False)
|
| 767 |
+
layer.w2_weight_scale = torch.nn.Parameter(
|
| 768 |
+
w2_weight_scale, requires_grad=False
|
| 769 |
+
)
|
| 770 |
+
if w2_input_scale is not None:
|
| 771 |
+
layer.w2_input_scale = torch.nn.Parameter(
|
| 772 |
+
w2_input_scale, requires_grad=False
|
| 773 |
+
)
|
| 774 |
+
# Fp8 moe kernel needs single weight scale for w13 per expert.
|
| 775 |
+
# We take the max then dequant and requant each expert.
|
| 776 |
+
assert layer.w13_weight_scale is not None
|
| 777 |
+
shard_size = layer.intermediate_size_per_partition
|
| 778 |
+
max_w13_scales = layer.w13_weight_scale.max(dim=1).values
|
| 779 |
+
for expert_id in range(layer.num_experts):
|
| 780 |
+
start = 0
|
| 781 |
+
for shard_id in range(2):
|
| 782 |
+
dq_weight = per_tensor_dequantize(
|
| 783 |
+
layer.w13_weight[expert_id][start : start + shard_size, :],
|
| 784 |
+
layer.w13_weight_scale[expert_id][shard_id],
|
| 785 |
+
)
|
| 786 |
+
(
|
| 787 |
+
layer.w13_weight[expert_id][start : start + shard_size, :],
|
| 788 |
+
_,
|
| 789 |
+
) = scaled_fp8_quant(dq_weight, max_w13_scales[expert_id])
|
| 790 |
+
start += shard_size
|
| 791 |
+
|
| 792 |
+
layer.w13_weight_scale = torch.nn.Parameter(
|
| 793 |
+
max_w13_scales, requires_grad=False
|
| 794 |
+
)
|
| 795 |
+
|
| 796 |
+
if _is_hip:
|
| 797 |
+
self.process_weights_hip_scale_padding(layer)
|
| 798 |
+
return
|
| 799 |
+
|
| 800 |
+
def process_weights_hip_int4(self, layer: Module):
|
| 801 |
+
# TODO: and get_bool_env_var("SGLANG_AITER_MOE"): add after triton kernel added
|
| 802 |
+
# INT4-FP8 (INT4 MoE Weight, FP8 Compute)
|
| 803 |
+
# Weight Permutation
|
| 804 |
+
layer.w13_weight = torch.nn.Parameter(
|
| 805 |
+
shuffle_weight(layer.w13_weight.data, (16, 16)),
|
| 806 |
+
requires_grad=False,
|
| 807 |
+
)
|
| 808 |
+
torch.cuda.empty_cache()
|
| 809 |
+
layer.w2_weight = torch.nn.Parameter(
|
| 810 |
+
shuffle_weight(layer.w2_weight.data, (16, 16)),
|
| 811 |
+
requires_grad=False,
|
| 812 |
+
)
|
| 813 |
+
torch.cuda.empty_cache()
|
| 814 |
+
|
| 815 |
+
# INT4-FP8 : offset INT4 w13_weight_scale1 to single w13_weight_scale
|
| 816 |
+
# Fp8 moe kernel needs single fp8 w13_weight_scale for w13 per expert.
|
| 817 |
+
# We won't do requant each expert's fp8 weight (not direct available),
|
| 818 |
+
# instead we adjust half of INT4 w13_weight_scale1 numbers
|
| 819 |
+
assert layer.w13_weight_scale is not None
|
| 820 |
+
shard_size = layer.intermediate_size_per_partition
|
| 821 |
+
max_w13_scales = layer.w13_weight_scale.max(dim=1).values
|
| 822 |
+
for expert_id in range(layer.num_experts):
|
| 823 |
+
start = 0
|
| 824 |
+
max_w13_scale_fp8 = max_w13_scales[expert_id]
|
| 825 |
+
for shard_id in range(2):
|
| 826 |
+
if layer.w13_weight_scale[expert_id][shard_id] != max_w13_scale_fp8:
|
| 827 |
+
int4_rescale = (
|
| 828 |
+
layer.w13_weight_scale[expert_id][shard_id] / max_w13_scale_fp8
|
| 829 |
+
)
|
| 830 |
+
layer.w13_weight_scale1[expert_id][
|
| 831 |
+
start : start + shard_size
|
| 832 |
+
] *= int4_rescale
|
| 833 |
+
start += shard_size
|
| 834 |
+
|
| 835 |
+
layer.w13_weight_scale = torch.nn.Parameter(max_w13_scales, requires_grad=False)
|
| 836 |
+
|
| 837 |
+
# special hack to asm_moe, which takes (weight_scale1 * weight_scale) as post GEMM scaling
|
| 838 |
+
# optimal design - shall apply per-column weight_scale1 before GEMM, and weight_scale post
|
| 839 |
+
for expert_id in range(layer.num_experts):
|
| 840 |
+
layer.w13_weight_scale1[expert_id] *= max_w13_scales[expert_id]
|
| 841 |
+
layer.w2_weight_scale1[expert_id] *= layer.w2_weight_scale[expert_id]
|
| 842 |
+
|
| 843 |
+
def process_weights_hip_scale_padding(self, layer: Module):
|
| 844 |
+
from sglang.srt.layers.moe.fused_moe_triton.fused_moe import (
|
| 845 |
+
padding_size, # Avoid circular import
|
| 846 |
+
)
|
| 847 |
+
|
| 848 |
+
if get_bool_env_var("SGLANG_AITER_MOE"):
|
| 849 |
+
layer.w13_weight = torch.nn.Parameter(
|
| 850 |
+
shuffle_weight(layer.w13_weight.data, (16, 16)),
|
| 851 |
+
requires_grad=False,
|
| 852 |
+
)
|
| 853 |
+
torch.cuda.empty_cache()
|
| 854 |
+
layer.w2_weight = torch.nn.Parameter(
|
| 855 |
+
shuffle_weight(layer.w2_weight.data, (16, 16)),
|
| 856 |
+
requires_grad=False,
|
| 857 |
+
)
|
| 858 |
+
torch.cuda.empty_cache()
|
| 859 |
+
# ROCm (SGLANG_AITER_MOE): using column-wise scaling
|
| 860 |
+
layer.w13_weight_scale1 *= layer.w13_weight_scale.unsqueeze(-1)
|
| 861 |
+
layer.w2_weight_scale1 *= layer.w2_weight_scale.unsqueeze(-1)
|
| 862 |
+
elif get_bool_env_var("SGLANG_MOE_PADDING"):
|
| 863 |
+
# If ROCm, apply weight padding (min. Mem channel contention) only if set
|
| 864 |
+
layer.w13_weight = torch.nn.Parameter(
|
| 865 |
+
F.pad(layer.w13_weight.data, (0, padding_size), "constant", 0),
|
| 866 |
+
requires_grad=False,
|
| 867 |
+
)
|
| 868 |
+
torch.cuda.empty_cache()
|
| 869 |
+
layer.w2_weight = torch.nn.Parameter(
|
| 870 |
+
F.pad(layer.w2_weight.data, (0, padding_size), "constant", 0),
|
| 871 |
+
requires_grad=False,
|
| 872 |
+
)
|
| 873 |
+
torch.cuda.empty_cache()
|
| 874 |
+
|
| 875 |
+
def apply(
|
| 876 |
+
self,
|
| 877 |
+
layer: torch.nn.Module,
|
| 878 |
+
x: torch.Tensor,
|
| 879 |
+
router_logits: torch.Tensor,
|
| 880 |
+
top_k: int,
|
| 881 |
+
renormalize: bool,
|
| 882 |
+
use_grouped_topk: bool,
|
| 883 |
+
topk_group: Optional[int] = None,
|
| 884 |
+
num_expert_group: Optional[int] = None,
|
| 885 |
+
custom_routing_function: Optional[Callable] = None,
|
| 886 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 887 |
+
activation: str = "silu",
|
| 888 |
+
apply_router_weight_on_input: bool = False,
|
| 889 |
+
inplace: bool = True,
|
| 890 |
+
no_combine: bool = False,
|
| 891 |
+
routed_scaling_factor: Optional[float] = None,
|
| 892 |
+
) -> torch.Tensor:
|
| 893 |
+
from sglang.srt.layers.moe.fused_moe_triton.fused_moe import fused_experts
|
| 894 |
+
from sglang.srt.layers.moe.topk import select_experts
|
| 895 |
+
|
| 896 |
+
# Expert selection
|
| 897 |
+
topk_weights, topk_ids = select_experts(
|
| 898 |
+
hidden_states=x,
|
| 899 |
+
router_logits=router_logits,
|
| 900 |
+
use_grouped_topk=use_grouped_topk,
|
| 901 |
+
top_k=top_k,
|
| 902 |
+
renormalize=renormalize,
|
| 903 |
+
topk_group=topk_group,
|
| 904 |
+
num_expert_group=num_expert_group,
|
| 905 |
+
custom_routing_function=custom_routing_function,
|
| 906 |
+
correction_bias=correction_bias,
|
| 907 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
if _is_hip:
|
| 911 |
+
if get_bool_env_var("SGLANG_INT4_WEIGHT"):
|
| 912 |
+
# TODO: add triton kernel and add check get_bool_env_var("SGLANG_AITER_MOE")
|
| 913 |
+
assert not no_combine, f"{no_combine=} is not supported."
|
| 914 |
+
return ck_moe_2stages(
|
| 915 |
+
x,
|
| 916 |
+
layer.w13_weight,
|
| 917 |
+
layer.w2_weight,
|
| 918 |
+
topk_weights,
|
| 919 |
+
topk_ids,
|
| 920 |
+
QuantType.per_Token,
|
| 921 |
+
layer.w13_weight_scale1,
|
| 922 |
+
layer.w2_weight_scale1,
|
| 923 |
+
activation=(
|
| 924 |
+
ActivationType.Silu
|
| 925 |
+
if activation == "silu"
|
| 926 |
+
else ActivationType.Gelu
|
| 927 |
+
),
|
| 928 |
+
)
|
| 929 |
+
|
| 930 |
+
if get_bool_env_var("SGLANG_AITER_MOE"):
|
| 931 |
+
assert not no_combine, f"{no_combine=} is not supported."
|
| 932 |
+
if self.block_quant:
|
| 933 |
+
# TODO(SGLANG_AITER_MOE): FP8 block_quant only supports 'silu' for the time-being.
|
| 934 |
+
assert (
|
| 935 |
+
activation == "silu"
|
| 936 |
+
), f"SGLANG_AITER_MOE: FP8 bloack_quant {activation=} will be supported later, unset SGLANG_AITER_MOE"
|
| 937 |
+
return asm_moe(
|
| 938 |
+
x,
|
| 939 |
+
layer.w13_weight,
|
| 940 |
+
layer.w2_weight,
|
| 941 |
+
topk_weights,
|
| 942 |
+
topk_ids,
|
| 943 |
+
layer.w13_weight_scale_inv,
|
| 944 |
+
layer.w2_weight_scale_inv,
|
| 945 |
+
block_shape=tuple(self.quant_config.weight_block_size),
|
| 946 |
+
expert_mask=None,
|
| 947 |
+
)
|
| 948 |
+
else:
|
| 949 |
+
return ck_moe_2stages(
|
| 950 |
+
x,
|
| 951 |
+
layer.w13_weight,
|
| 952 |
+
layer.w2_weight,
|
| 953 |
+
topk_weights,
|
| 954 |
+
topk_ids,
|
| 955 |
+
QuantType.per_Token,
|
| 956 |
+
layer.w13_weight_scale1,
|
| 957 |
+
layer.w2_weight_scale1,
|
| 958 |
+
activation=(
|
| 959 |
+
ActivationType.Silu
|
| 960 |
+
if activation == "silu"
|
| 961 |
+
else ActivationType.Gelu
|
| 962 |
+
),
|
| 963 |
+
)
|
| 964 |
+
|
| 965 |
+
# Expert fusion with FP8 quantization
|
| 966 |
+
return fused_experts(
|
| 967 |
+
x,
|
| 968 |
+
layer.w13_weight,
|
| 969 |
+
layer.w2_weight,
|
| 970 |
+
topk_weights=topk_weights,
|
| 971 |
+
topk_ids=topk_ids,
|
| 972 |
+
inplace=inplace and not no_combine,
|
| 973 |
+
activation=activation,
|
| 974 |
+
apply_router_weight_on_input=apply_router_weight_on_input,
|
| 975 |
+
use_fp8_w8a8=True,
|
| 976 |
+
w1_scale=(
|
| 977 |
+
layer.w13_weight_scale_inv
|
| 978 |
+
if self.block_quant
|
| 979 |
+
else layer.w13_weight_scale
|
| 980 |
+
),
|
| 981 |
+
w2_scale=(
|
| 982 |
+
layer.w2_weight_scale_inv if self.block_quant else layer.w2_weight_scale
|
| 983 |
+
),
|
| 984 |
+
a1_scale=layer.w13_input_scale,
|
| 985 |
+
a2_scale=layer.w2_input_scale,
|
| 986 |
+
block_shape=self.quant_config.weight_block_size,
|
| 987 |
+
no_combine=no_combine,
|
| 988 |
+
)
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
class Fp8KVCacheMethod(BaseKVCacheMethod):
|
| 992 |
+
"""
|
| 993 |
+
Supports loading kv-cache scaling factors from FP8 checkpoints.
|
| 994 |
+
"""
|
| 995 |
+
|
| 996 |
+
def __init__(self, quant_config: Fp8Config):
|
| 997 |
+
super().__init__(quant_config)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8_kernel.py
ADDED
|
@@ -0,0 +1,1068 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
|
| 15 |
+
import functools
|
| 16 |
+
import json
|
| 17 |
+
import logging
|
| 18 |
+
import os
|
| 19 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
import triton
|
| 23 |
+
import triton.language as tl
|
| 24 |
+
|
| 25 |
+
from sglang.srt.layers.quantization.deep_gemm import _ENABLE_JIT_DEEPGEMM
|
| 26 |
+
from sglang.srt.utils import (
|
| 27 |
+
direct_register_custom_op,
|
| 28 |
+
get_device_core_count,
|
| 29 |
+
get_device_name,
|
| 30 |
+
is_cuda,
|
| 31 |
+
is_hip,
|
| 32 |
+
supports_custom_op,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
_is_hip = is_hip()
|
| 36 |
+
_is_cuda = is_cuda()
|
| 37 |
+
_fp8_type = torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
| 38 |
+
if _is_hip:
|
| 39 |
+
fp8_max = 224.0
|
| 40 |
+
else:
|
| 41 |
+
fp8_max = torch.finfo(_fp8_type).max
|
| 42 |
+
fp8_min = -fp8_max
|
| 43 |
+
|
| 44 |
+
if _is_cuda:
|
| 45 |
+
from sgl_kernel import (
|
| 46 |
+
sgl_per_tensor_quant_fp8,
|
| 47 |
+
sgl_per_token_group_quant_fp8,
|
| 48 |
+
sgl_per_token_quant_fp8,
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
from sglang.srt.layers.quantization.deep_gemm import (
|
| 52 |
+
gemm_nt_f8f8bf16 as deep_gemm_gemm_nt_f8f8bf16,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
logger = logging.getLogger(__name__)
|
| 56 |
+
|
| 57 |
+
if supports_custom_op():
|
| 58 |
+
|
| 59 |
+
def deep_gemm_fp8_fp8_bf16_nt(
|
| 60 |
+
A: torch.Tensor,
|
| 61 |
+
As: torch.Tensor,
|
| 62 |
+
B: torch.Tensor,
|
| 63 |
+
Bs: torch.Tensor,
|
| 64 |
+
C: torch.Tensor,
|
| 65 |
+
) -> None:
|
| 66 |
+
deep_gemm_gemm_nt_f8f8bf16((A, As), (B, Bs), C)
|
| 67 |
+
|
| 68 |
+
def deep_gemm_fp8_fp8_bf16_nt_fake(
|
| 69 |
+
A: torch.Tensor,
|
| 70 |
+
As: torch.Tensor,
|
| 71 |
+
B: torch.Tensor,
|
| 72 |
+
Bs: torch.Tensor,
|
| 73 |
+
C: torch.Tensor,
|
| 74 |
+
) -> None:
|
| 75 |
+
return
|
| 76 |
+
|
| 77 |
+
direct_register_custom_op(
|
| 78 |
+
op_name="deep_gemm_fp8_fp8_bf16_nt",
|
| 79 |
+
op_func=deep_gemm_fp8_fp8_bf16_nt,
|
| 80 |
+
mutates_args=["C"],
|
| 81 |
+
fake_impl=deep_gemm_fp8_fp8_bf16_nt_fake,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
@triton.jit
|
| 86 |
+
def _per_token_group_quant_fp8(
|
| 87 |
+
# Pointers to inputs and output
|
| 88 |
+
y_ptr,
|
| 89 |
+
y_q_ptr,
|
| 90 |
+
y_s_ptr,
|
| 91 |
+
# Stride of input
|
| 92 |
+
y_stride,
|
| 93 |
+
# Collums of input
|
| 94 |
+
N,
|
| 95 |
+
# Avoid to divide zero
|
| 96 |
+
eps,
|
| 97 |
+
# Information for float8
|
| 98 |
+
fp8_min,
|
| 99 |
+
fp8_max,
|
| 100 |
+
# Meta-parameters
|
| 101 |
+
BLOCK: tl.constexpr,
|
| 102 |
+
):
|
| 103 |
+
"""A Triton-accelerated function to perform per-token-group quantization on a
|
| 104 |
+
tensor.
|
| 105 |
+
|
| 106 |
+
This function converts the tensor values into float8 values.
|
| 107 |
+
"""
|
| 108 |
+
# Map the program id to the row of X and Y it should compute.
|
| 109 |
+
g_id = tl.program_id(0)
|
| 110 |
+
y_ptr += g_id * y_stride
|
| 111 |
+
y_q_ptr += g_id * y_stride
|
| 112 |
+
y_s_ptr += g_id
|
| 113 |
+
|
| 114 |
+
cols = tl.arange(0, BLOCK) # N <= BLOCK
|
| 115 |
+
mask = cols < N
|
| 116 |
+
|
| 117 |
+
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
|
| 118 |
+
# Quant
|
| 119 |
+
_absmax = tl.maximum(tl.max(tl.abs(y)), eps)
|
| 120 |
+
y_s = _absmax / fp8_max
|
| 121 |
+
y_s_inv = 1.0 / y_s
|
| 122 |
+
y_q = tl.clamp(y * y_s_inv, fp8_min, fp8_max).to(y_q_ptr.dtype.element_ty)
|
| 123 |
+
|
| 124 |
+
tl.store(y_q_ptr + cols, y_q, mask=mask)
|
| 125 |
+
tl.store(y_s_ptr, y_s)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@triton.jit
|
| 129 |
+
def _per_token_group_quant_fp8_colmajor(
|
| 130 |
+
# Pointers to inputs and output
|
| 131 |
+
y_ptr,
|
| 132 |
+
y_q_ptr,
|
| 133 |
+
y_s_ptr,
|
| 134 |
+
group_size,
|
| 135 |
+
# Num columns of y
|
| 136 |
+
y_num_columns,
|
| 137 |
+
# Stride from one column to the next of y_s
|
| 138 |
+
y_s_col_stride,
|
| 139 |
+
# Avoid to divide zero
|
| 140 |
+
eps,
|
| 141 |
+
# Information for float8
|
| 142 |
+
fp8_min,
|
| 143 |
+
fp8_max,
|
| 144 |
+
# Meta-parameters
|
| 145 |
+
BLOCK: tl.constexpr,
|
| 146 |
+
):
|
| 147 |
+
"""A Triton-accelerated function to perform per-token-group
|
| 148 |
+
quantization on a tensor.
|
| 149 |
+
This function converts the tensor values into float8 values.
|
| 150 |
+
"""
|
| 151 |
+
# Map the program id to the row of X and Y it should compute.
|
| 152 |
+
g_id = tl.program_id(0)
|
| 153 |
+
y_ptr += g_id * group_size
|
| 154 |
+
y_q_ptr += g_id * group_size
|
| 155 |
+
|
| 156 |
+
# Convert g_id the flattened block coordinate to 2D so we can index
|
| 157 |
+
# into the output y_scales matrix
|
| 158 |
+
blocks_per_row = y_num_columns // group_size
|
| 159 |
+
scale_col = g_id % blocks_per_row
|
| 160 |
+
scale_row = g_id // blocks_per_row
|
| 161 |
+
y_s_ptr += scale_col * y_s_col_stride + scale_row
|
| 162 |
+
|
| 163 |
+
cols = tl.arange(0, BLOCK) # group_size <= BLOCK
|
| 164 |
+
mask = cols < group_size
|
| 165 |
+
|
| 166 |
+
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
|
| 167 |
+
# Quant
|
| 168 |
+
_absmax = tl.maximum(tl.max(tl.abs(y)), eps)
|
| 169 |
+
y_s = _absmax / fp8_max
|
| 170 |
+
y_q = tl.clamp(y / y_s, fp8_min, fp8_max).to(y_q_ptr.dtype.element_ty)
|
| 171 |
+
|
| 172 |
+
tl.store(y_q_ptr + cols, y_q, mask=mask)
|
| 173 |
+
tl.store(y_s_ptr, y_s)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def per_token_group_quant_fp8(
|
| 177 |
+
x: torch.Tensor,
|
| 178 |
+
group_size: int,
|
| 179 |
+
eps: float = 1e-10,
|
| 180 |
+
column_major_scales: bool = False,
|
| 181 |
+
scale_tma_aligned: bool = False,
|
| 182 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 183 |
+
"""Function to perform per-token-group quantization on an input tensor `x`.
|
| 184 |
+
|
| 185 |
+
It converts the tensor values into signed float8 values and returns the
|
| 186 |
+
quantized tensor along with the scaling factor used for quantization.
|
| 187 |
+
|
| 188 |
+
Args:
|
| 189 |
+
x: The input tenosr with ndim >= 2.
|
| 190 |
+
group_size: The group size used for quantization.
|
| 191 |
+
eps: The minimum to avoid dividing zero.
|
| 192 |
+
|
| 193 |
+
Returns:
|
| 194 |
+
Tuple[torch.Tensor, torch.Tensor]: The quantized tensor and the scaling factor for quantization.
|
| 195 |
+
"""
|
| 196 |
+
assert (
|
| 197 |
+
x.shape[-1] % group_size == 0
|
| 198 |
+
), "the last dimension of `x` cannot be divisible by `group_size`"
|
| 199 |
+
assert x.is_contiguous(), "`x` is not contiguous"
|
| 200 |
+
|
| 201 |
+
x_q = torch.empty_like(x, device=x.device, dtype=_fp8_type)
|
| 202 |
+
M = x.numel() // group_size
|
| 203 |
+
N = group_size
|
| 204 |
+
if column_major_scales:
|
| 205 |
+
if scale_tma_aligned:
|
| 206 |
+
# aligned to 4 * sizeof(float)
|
| 207 |
+
aligned_size = (x.shape[-2] + 3) // 4 * 4
|
| 208 |
+
x_s = torch.empty(
|
| 209 |
+
x.shape[:-2] + (x.shape[-1] // group_size, aligned_size),
|
| 210 |
+
device=x.device,
|
| 211 |
+
dtype=torch.float32,
|
| 212 |
+
).permute(-1, -2)[: x.shape[-2], :]
|
| 213 |
+
else:
|
| 214 |
+
x_s = torch.empty(
|
| 215 |
+
(x.shape[-1] // group_size,) + x.shape[:-1],
|
| 216 |
+
device=x.device,
|
| 217 |
+
dtype=torch.float32,
|
| 218 |
+
).permute(-1, -2)
|
| 219 |
+
else:
|
| 220 |
+
x_s = torch.empty(
|
| 221 |
+
x.shape[:-1] + (x.shape[-1] // group_size,),
|
| 222 |
+
device=x.device,
|
| 223 |
+
dtype=torch.float32,
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
BLOCK = triton.next_power_of_2(N)
|
| 227 |
+
# heuristics for number of warps
|
| 228 |
+
num_warps = min(max(BLOCK // 256, 1), 8)
|
| 229 |
+
num_stages = 1
|
| 230 |
+
if column_major_scales:
|
| 231 |
+
_per_token_group_quant_fp8_colmajor[(M,)](
|
| 232 |
+
x,
|
| 233 |
+
x_q,
|
| 234 |
+
x_s,
|
| 235 |
+
group_size,
|
| 236 |
+
x.shape[1],
|
| 237 |
+
x_s.stride(1),
|
| 238 |
+
eps,
|
| 239 |
+
fp8_min=fp8_min,
|
| 240 |
+
fp8_max=fp8_max,
|
| 241 |
+
BLOCK=BLOCK,
|
| 242 |
+
num_warps=num_warps,
|
| 243 |
+
num_stages=num_stages,
|
| 244 |
+
)
|
| 245 |
+
else:
|
| 246 |
+
_per_token_group_quant_fp8[(M,)](
|
| 247 |
+
x,
|
| 248 |
+
x_q,
|
| 249 |
+
x_s,
|
| 250 |
+
group_size,
|
| 251 |
+
N,
|
| 252 |
+
eps,
|
| 253 |
+
fp8_min=fp8_min,
|
| 254 |
+
fp8_max=fp8_max,
|
| 255 |
+
BLOCK=BLOCK,
|
| 256 |
+
num_warps=num_warps,
|
| 257 |
+
num_stages=num_stages,
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
return x_q, x_s
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def sglang_per_token_group_quant_fp8(
|
| 264 |
+
x: torch.Tensor,
|
| 265 |
+
group_size: int,
|
| 266 |
+
eps: float = 1e-10,
|
| 267 |
+
column_major_scales: bool = False,
|
| 268 |
+
scale_tma_aligned: bool = False,
|
| 269 |
+
):
|
| 270 |
+
assert (
|
| 271 |
+
x.shape[-1] % group_size == 0
|
| 272 |
+
), "the last dimension of `x` cannot be divisible by `group_size`"
|
| 273 |
+
assert x.is_contiguous(), "`x` is not contiguous"
|
| 274 |
+
|
| 275 |
+
x_q = torch.empty_like(x, device=x.device, dtype=_fp8_type)
|
| 276 |
+
if column_major_scales:
|
| 277 |
+
if scale_tma_aligned:
|
| 278 |
+
# aligned to 4 * sizeof(float)
|
| 279 |
+
aligned_size = (x.shape[-2] + 3) // 4 * 4
|
| 280 |
+
x_s = torch.empty(
|
| 281 |
+
x.shape[:-2] + (x.shape[-1] // group_size, aligned_size),
|
| 282 |
+
device=x.device,
|
| 283 |
+
dtype=torch.float32,
|
| 284 |
+
).permute(-1, -2)[: x.shape[-2], :]
|
| 285 |
+
else:
|
| 286 |
+
x_s = torch.empty(
|
| 287 |
+
(x.shape[-1] // group_size,) + x.shape[:-1],
|
| 288 |
+
device=x.device,
|
| 289 |
+
dtype=torch.float32,
|
| 290 |
+
).permute(-1, -2)
|
| 291 |
+
else:
|
| 292 |
+
x_s = torch.empty(
|
| 293 |
+
x.shape[:-1] + (x.shape[-1] // group_size,),
|
| 294 |
+
device=x.device,
|
| 295 |
+
dtype=torch.float32,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
sgl_per_token_group_quant_fp8(x, x_q, x_s, group_size, eps, fp8_min, fp8_max)
|
| 299 |
+
|
| 300 |
+
return x_q, x_s
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def sglang_per_token_quant_fp8(
|
| 304 |
+
x: torch.Tensor,
|
| 305 |
+
dtype: torch.dtype = _fp8_type,
|
| 306 |
+
):
|
| 307 |
+
assert x.is_contiguous(), "`x` is not contiguous"
|
| 308 |
+
|
| 309 |
+
x_q = torch.empty_like(x, device=x.device, dtype=dtype)
|
| 310 |
+
x_s = torch.empty(
|
| 311 |
+
x.shape[0],
|
| 312 |
+
1,
|
| 313 |
+
device=x.device,
|
| 314 |
+
dtype=torch.float32,
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
sgl_per_token_quant_fp8(x, x_q, x_s)
|
| 318 |
+
|
| 319 |
+
return x_q, x_s
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
@triton.jit
|
| 323 |
+
def _static_quant_fp8(
|
| 324 |
+
# Pointers to inputs and output
|
| 325 |
+
y_ptr,
|
| 326 |
+
y_q_ptr,
|
| 327 |
+
y_s_ptr,
|
| 328 |
+
y_s_repeat_ptr,
|
| 329 |
+
# Stride of input
|
| 330 |
+
y_stride,
|
| 331 |
+
# Collums of input
|
| 332 |
+
N,
|
| 333 |
+
# Information for float8
|
| 334 |
+
fp8_min,
|
| 335 |
+
fp8_max,
|
| 336 |
+
# Meta-parameters
|
| 337 |
+
BLOCK: tl.constexpr,
|
| 338 |
+
REPEAT_SCALE: tl.constexpr,
|
| 339 |
+
):
|
| 340 |
+
"""A Triton-accelerated function to perform quantization using the given scale on a
|
| 341 |
+
tensor
|
| 342 |
+
|
| 343 |
+
This function converts the tensor values into float8 values.
|
| 344 |
+
"""
|
| 345 |
+
# Map the program id to the row of X and Y it should compute.
|
| 346 |
+
g_id = tl.program_id(0)
|
| 347 |
+
y_ptr += g_id * y_stride
|
| 348 |
+
y_q_ptr += g_id * y_stride
|
| 349 |
+
if REPEAT_SCALE:
|
| 350 |
+
y_s_repeat_ptr += g_id
|
| 351 |
+
|
| 352 |
+
cols = tl.arange(0, BLOCK) # N <= BLOCK
|
| 353 |
+
mask = cols < N
|
| 354 |
+
|
| 355 |
+
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
|
| 356 |
+
y_s = tl.load(y_s_ptr).to(tl.float32)
|
| 357 |
+
y_s_inv = 1.0 / y_s
|
| 358 |
+
y_q = tl.clamp(y * y_s_inv, fp8_min, fp8_max).to(y_q_ptr.dtype.element_ty)
|
| 359 |
+
|
| 360 |
+
tl.store(y_q_ptr + cols, y_q, mask=mask)
|
| 361 |
+
if REPEAT_SCALE:
|
| 362 |
+
tl.store(y_s_repeat_ptr, y_s)
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def static_quant_fp8(
|
| 366 |
+
x: torch.Tensor,
|
| 367 |
+
x_s: torch.Tensor,
|
| 368 |
+
repeat_scale: bool = False,
|
| 369 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 370 |
+
"""Function to perform static quantization using the given scale on an input tensor `x`.
|
| 371 |
+
|
| 372 |
+
It converts the tensor values into signed float8 values and returns the
|
| 373 |
+
quantized tensor along with the scaling factor used for quantization.
|
| 374 |
+
|
| 375 |
+
Args:
|
| 376 |
+
x: The input tenosr with ndim >= 2.
|
| 377 |
+
x_s: The quantization scale.
|
| 378 |
+
repeat_scale: Whether to broadcast per-tensor scale to per-channel scale.
|
| 379 |
+
dtype: The dype of output tensor.
|
| 380 |
+
|
| 381 |
+
Returns:
|
| 382 |
+
Tuple[torch.Tensor, torch.Tensor]: The quantized tensor and the scaling factor for quantization.
|
| 383 |
+
"""
|
| 384 |
+
assert x.is_contiguous(), "`x` is not contiguous"
|
| 385 |
+
assert x_s.numel() == 1, "only supports per-tensor scale"
|
| 386 |
+
|
| 387 |
+
x_q = torch.empty_like(x, device=x.device, dtype=_fp8_type)
|
| 388 |
+
M = x.numel() // x.shape[-1]
|
| 389 |
+
N = x.shape[-1]
|
| 390 |
+
if repeat_scale:
|
| 391 |
+
x_s_repeat = torch.empty(
|
| 392 |
+
(M, 1),
|
| 393 |
+
device=x.device,
|
| 394 |
+
dtype=torch.float32,
|
| 395 |
+
)
|
| 396 |
+
else:
|
| 397 |
+
x_s_repeat = None
|
| 398 |
+
|
| 399 |
+
BLOCK = triton.next_power_of_2(N)
|
| 400 |
+
# heuristics for number of warps
|
| 401 |
+
num_warps = min(max(BLOCK // 256, 1), 8)
|
| 402 |
+
num_stages = 1
|
| 403 |
+
_static_quant_fp8[(M,)](
|
| 404 |
+
x,
|
| 405 |
+
x_q,
|
| 406 |
+
x_s,
|
| 407 |
+
x_s_repeat,
|
| 408 |
+
N,
|
| 409 |
+
N,
|
| 410 |
+
fp8_min=fp8_min,
|
| 411 |
+
fp8_max=fp8_max,
|
| 412 |
+
BLOCK=BLOCK,
|
| 413 |
+
REPEAT_SCALE=repeat_scale,
|
| 414 |
+
num_warps=num_warps,
|
| 415 |
+
num_stages=num_stages,
|
| 416 |
+
)
|
| 417 |
+
x_s = x_s_repeat if repeat_scale else x_s
|
| 418 |
+
return x_q, x_s
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
@triton.jit
|
| 422 |
+
def _w8a8_block_fp8_matmul(
|
| 423 |
+
# Pointers to inputs and output
|
| 424 |
+
A,
|
| 425 |
+
B,
|
| 426 |
+
C,
|
| 427 |
+
As,
|
| 428 |
+
Bs,
|
| 429 |
+
# Shape for matmul
|
| 430 |
+
M,
|
| 431 |
+
N,
|
| 432 |
+
K,
|
| 433 |
+
# Block size for block-wise quantization
|
| 434 |
+
group_n,
|
| 435 |
+
group_k,
|
| 436 |
+
# Stride for inputs and output
|
| 437 |
+
stride_am,
|
| 438 |
+
stride_ak,
|
| 439 |
+
stride_bk,
|
| 440 |
+
stride_bn,
|
| 441 |
+
stride_cm,
|
| 442 |
+
stride_cn,
|
| 443 |
+
stride_As_m,
|
| 444 |
+
stride_As_k,
|
| 445 |
+
stride_Bs_k,
|
| 446 |
+
stride_Bs_n,
|
| 447 |
+
# Meta-parameters
|
| 448 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 449 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 450 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 451 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 452 |
+
):
|
| 453 |
+
"""Triton-accelerated function used to perform linear operations (dot
|
| 454 |
+
product) on input tensors `A` and `B` with block-wise quantization, and store the result in output
|
| 455 |
+
tensor `C`.
|
| 456 |
+
"""
|
| 457 |
+
|
| 458 |
+
pid = tl.program_id(axis=0)
|
| 459 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 460 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 461 |
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
| 462 |
+
group_id = pid // num_pid_in_group
|
| 463 |
+
first_pid_m = group_id * GROUP_SIZE_M
|
| 464 |
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
| 465 |
+
pid_m = first_pid_m + (pid % group_size_m)
|
| 466 |
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
| 467 |
+
|
| 468 |
+
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 469 |
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 470 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 471 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 472 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 473 |
+
|
| 474 |
+
As_ptrs = As + offs_am * stride_As_m
|
| 475 |
+
offs_bsn = offs_bn // group_n
|
| 476 |
+
Bs_ptrs = Bs + offs_bsn * stride_Bs_n
|
| 477 |
+
|
| 478 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 479 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 480 |
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
|
| 481 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
|
| 482 |
+
|
| 483 |
+
k_start = k * BLOCK_SIZE_K
|
| 484 |
+
offs_ks = k_start // group_k
|
| 485 |
+
a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
|
| 486 |
+
b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
|
| 487 |
+
|
| 488 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 489 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 490 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 491 |
+
|
| 492 |
+
if C.dtype.element_ty == tl.bfloat16:
|
| 493 |
+
c = accumulator.to(tl.bfloat16)
|
| 494 |
+
elif C.dtype.element_ty == tl.float16:
|
| 495 |
+
c = accumulator.to(tl.float16)
|
| 496 |
+
else:
|
| 497 |
+
c = accumulator.to(tl.float32)
|
| 498 |
+
|
| 499 |
+
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 500 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 501 |
+
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 502 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 503 |
+
tl.store(c_ptrs, c, mask=c_mask)
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
@triton.jit
|
| 507 |
+
def _w8a8_block_fp8_matmul_unrolledx4(
|
| 508 |
+
# Pointers to inputs and output
|
| 509 |
+
A,
|
| 510 |
+
B,
|
| 511 |
+
C,
|
| 512 |
+
As,
|
| 513 |
+
Bs,
|
| 514 |
+
# Shape for matmul
|
| 515 |
+
M,
|
| 516 |
+
N,
|
| 517 |
+
K,
|
| 518 |
+
# Block size for block-wise quantization
|
| 519 |
+
group_n,
|
| 520 |
+
group_k,
|
| 521 |
+
# Stride for inputs and output
|
| 522 |
+
stride_am,
|
| 523 |
+
stride_ak,
|
| 524 |
+
stride_bk,
|
| 525 |
+
stride_bn,
|
| 526 |
+
stride_cm,
|
| 527 |
+
stride_cn,
|
| 528 |
+
stride_As_m,
|
| 529 |
+
stride_As_k,
|
| 530 |
+
stride_Bs_k,
|
| 531 |
+
stride_Bs_n,
|
| 532 |
+
# Meta-parameters
|
| 533 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 534 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 535 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 536 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 537 |
+
):
|
| 538 |
+
"""Triton-accelerated function used to perform linear operations (dot
|
| 539 |
+
product) on input tensors `A` and `B` with block-wise quantization, and store the result in output
|
| 540 |
+
tensor `C`.
|
| 541 |
+
"""
|
| 542 |
+
|
| 543 |
+
pid = tl.program_id(axis=0)
|
| 544 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 545 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 546 |
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
| 547 |
+
group_id = pid // num_pid_in_group
|
| 548 |
+
first_pid_m = group_id * GROUP_SIZE_M
|
| 549 |
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
| 550 |
+
pid_m = first_pid_m + (pid % group_size_m)
|
| 551 |
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
| 552 |
+
|
| 553 |
+
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 554 |
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 555 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 556 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 557 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 558 |
+
|
| 559 |
+
As_ptrs = As + offs_am * stride_As_m
|
| 560 |
+
offs_bsn = offs_bn // group_n
|
| 561 |
+
Bs_ptrs = Bs + offs_bsn * stride_Bs_n
|
| 562 |
+
|
| 563 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 564 |
+
# manually unroll to 4 iterations
|
| 565 |
+
UNROLL_FACTOR = 4
|
| 566 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K * UNROLL_FACTOR)):
|
| 567 |
+
# 1st iteration
|
| 568 |
+
a = tl.load(
|
| 569 |
+
a_ptrs,
|
| 570 |
+
mask=offs_k[None, :] < K - (k * UNROLL_FACTOR) * BLOCK_SIZE_K,
|
| 571 |
+
other=0.0,
|
| 572 |
+
)
|
| 573 |
+
b = tl.load(
|
| 574 |
+
b_ptrs,
|
| 575 |
+
mask=offs_k[:, None] < K - (k * UNROLL_FACTOR) * BLOCK_SIZE_K,
|
| 576 |
+
other=0.0,
|
| 577 |
+
)
|
| 578 |
+
|
| 579 |
+
k_start = (k * UNROLL_FACTOR) * BLOCK_SIZE_K
|
| 580 |
+
offs_ks = k_start // group_k
|
| 581 |
+
a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
|
| 582 |
+
b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
|
| 583 |
+
|
| 584 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 585 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 586 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 587 |
+
|
| 588 |
+
# 2nd iteration
|
| 589 |
+
a = tl.load(
|
| 590 |
+
a_ptrs,
|
| 591 |
+
mask=offs_k[None, :] < K - (k * UNROLL_FACTOR + 1) * BLOCK_SIZE_K,
|
| 592 |
+
other=0.0,
|
| 593 |
+
)
|
| 594 |
+
b = tl.load(
|
| 595 |
+
b_ptrs,
|
| 596 |
+
mask=offs_k[:, None] < K - (k * UNROLL_FACTOR + 1) * BLOCK_SIZE_K,
|
| 597 |
+
other=0.0,
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
k_start = k_start + BLOCK_SIZE_K
|
| 601 |
+
offs_ks = k_start // group_k
|
| 602 |
+
a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
|
| 603 |
+
b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
|
| 604 |
+
|
| 605 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 606 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 607 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 608 |
+
|
| 609 |
+
# 3rd iteration
|
| 610 |
+
a = tl.load(
|
| 611 |
+
a_ptrs,
|
| 612 |
+
mask=offs_k[None, :] < K - (k * UNROLL_FACTOR + 2) * BLOCK_SIZE_K,
|
| 613 |
+
other=0.0,
|
| 614 |
+
)
|
| 615 |
+
b = tl.load(
|
| 616 |
+
b_ptrs,
|
| 617 |
+
mask=offs_k[:, None] < K - (k * UNROLL_FACTOR + 2) * BLOCK_SIZE_K,
|
| 618 |
+
other=0.0,
|
| 619 |
+
)
|
| 620 |
+
|
| 621 |
+
k_start = k_start + BLOCK_SIZE_K
|
| 622 |
+
offs_ks = k_start // group_k
|
| 623 |
+
a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
|
| 624 |
+
b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
|
| 625 |
+
|
| 626 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 627 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 628 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 629 |
+
|
| 630 |
+
# 4th iteration
|
| 631 |
+
a = tl.load(
|
| 632 |
+
a_ptrs,
|
| 633 |
+
mask=offs_k[None, :] < K - (k * UNROLL_FACTOR + 3) * BLOCK_SIZE_K,
|
| 634 |
+
other=0.0,
|
| 635 |
+
)
|
| 636 |
+
b = tl.load(
|
| 637 |
+
b_ptrs,
|
| 638 |
+
mask=offs_k[:, None] < K - (k * UNROLL_FACTOR + 3) * BLOCK_SIZE_K,
|
| 639 |
+
other=0.0,
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
k_start = k_start + BLOCK_SIZE_K
|
| 643 |
+
offs_ks = k_start // group_k
|
| 644 |
+
a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
|
| 645 |
+
b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
|
| 646 |
+
|
| 647 |
+
accumulator += tl.dot(a, b) * a_s[:, None] * b_s[None, :]
|
| 648 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 649 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 650 |
+
|
| 651 |
+
if C.dtype.element_ty == tl.bfloat16:
|
| 652 |
+
c = accumulator.to(tl.bfloat16)
|
| 653 |
+
elif C.dtype.element_ty == tl.float16:
|
| 654 |
+
c = accumulator.to(tl.float16)
|
| 655 |
+
else:
|
| 656 |
+
c = accumulator.to(tl.float32)
|
| 657 |
+
|
| 658 |
+
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 659 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 660 |
+
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 661 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 662 |
+
tl.store(c_ptrs, c, mask=c_mask)
|
| 663 |
+
|
| 664 |
+
|
| 665 |
+
@functools.lru_cache
|
| 666 |
+
def get_w8a8_block_fp8_configs(
|
| 667 |
+
N: int, K: int, block_n: int, block_k: int
|
| 668 |
+
) -> Optional[Dict[int, Any]]:
|
| 669 |
+
"""
|
| 670 |
+
Return optimized configurations for the w8a8 block fp8 kernel.
|
| 671 |
+
|
| 672 |
+
The return value will be a dictionary that maps an irregular grid of
|
| 673 |
+
batch sizes to configurations of the w8a8 block fp8 kernel. To evaluate the
|
| 674 |
+
kernel on a given batch size bs, the closest batch size in the grid should
|
| 675 |
+
be picked and the associated configuration chosen to invoke the kernel.
|
| 676 |
+
"""
|
| 677 |
+
|
| 678 |
+
# First look up if an optimized configuration is available in the configs
|
| 679 |
+
# directory
|
| 680 |
+
device_name = get_device_name().replace(" ", "_")
|
| 681 |
+
json_file_name = f"N={N},K={K},device_name={device_name},dtype=fp8_w8a8,block_shape=[{block_n}, {block_k}].json"
|
| 682 |
+
|
| 683 |
+
config_file_path = os.path.join(
|
| 684 |
+
os.path.dirname(os.path.realpath(__file__)), "configs", json_file_name
|
| 685 |
+
)
|
| 686 |
+
if os.path.exists(config_file_path):
|
| 687 |
+
with open(config_file_path) as f:
|
| 688 |
+
logger.info(
|
| 689 |
+
"Using configuration from %s for W8A8 Block FP8 kernel.",
|
| 690 |
+
config_file_path,
|
| 691 |
+
)
|
| 692 |
+
# If a configuration has been found, return it
|
| 693 |
+
return {int(key): val for key, val in json.load(f).items()}
|
| 694 |
+
|
| 695 |
+
# If no optimized configuration is available, we will use the default
|
| 696 |
+
# configuration
|
| 697 |
+
logger.warning(
|
| 698 |
+
(
|
| 699 |
+
"Using default W8A8 Block FP8 kernel config. Performance might be sub-optimal! "
|
| 700 |
+
"Config file not found at %s"
|
| 701 |
+
),
|
| 702 |
+
config_file_path,
|
| 703 |
+
)
|
| 704 |
+
return None
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
def w8a8_block_fp8_matmul(
|
| 708 |
+
A: torch.Tensor,
|
| 709 |
+
B: torch.Tensor,
|
| 710 |
+
As: torch.Tensor,
|
| 711 |
+
Bs: torch.Tensor,
|
| 712 |
+
block_size: List[int],
|
| 713 |
+
output_dtype: torch.dtype = torch.float16,
|
| 714 |
+
) -> torch.Tensor:
|
| 715 |
+
"""This function performs matrix multiplication with block-wise quantization.
|
| 716 |
+
|
| 717 |
+
It takes two input tensors `A` and `B` with scales `As` and `Bs`.
|
| 718 |
+
The output is returned in the specified `output_dtype`.
|
| 719 |
+
|
| 720 |
+
Args:
|
| 721 |
+
A: The input tensor, e.g., activation.
|
| 722 |
+
B: The input tensor, e.g., weight.
|
| 723 |
+
As: The per-token-group quantization scale for `A`.
|
| 724 |
+
Bs: The per-block quantization scale for `B`.
|
| 725 |
+
block_size: The block size for per-block quantization. It should be 2-dim, e.g., [128, 128].
|
| 726 |
+
output_dytpe: The dtype of the returned tensor.
|
| 727 |
+
|
| 728 |
+
Returns:
|
| 729 |
+
torch.Tensor: The result of matmul.
|
| 730 |
+
"""
|
| 731 |
+
assert len(block_size) == 2
|
| 732 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 733 |
+
|
| 734 |
+
assert A.shape[-1] == B.shape[-1]
|
| 735 |
+
assert A.shape[:-1] == As.shape[:-1] and A.is_contiguous()
|
| 736 |
+
assert triton.cdiv(A.shape[-1], block_k) == As.shape[-1]
|
| 737 |
+
M = A.numel() // A.shape[-1]
|
| 738 |
+
|
| 739 |
+
assert B.ndim == 2 and B.is_contiguous() and Bs.ndim == 2
|
| 740 |
+
N, K = B.shape
|
| 741 |
+
assert triton.cdiv(N, block_n) == Bs.shape[0]
|
| 742 |
+
assert triton.cdiv(K, block_k) == Bs.shape[1]
|
| 743 |
+
|
| 744 |
+
C_shape = A.shape[:-1] + (N,)
|
| 745 |
+
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 746 |
+
|
| 747 |
+
configs = get_w8a8_block_fp8_configs(N, K, block_size[0], block_size[1])
|
| 748 |
+
if configs:
|
| 749 |
+
# If an optimal configuration map has been found, look up the
|
| 750 |
+
# optimal config
|
| 751 |
+
config = configs[min(configs.keys(), key=lambda x: abs(x - M))]
|
| 752 |
+
else:
|
| 753 |
+
# Default config
|
| 754 |
+
# Block-wise quant: BLOCK_SIZE_K must be divisable by block_size[1]
|
| 755 |
+
config = {
|
| 756 |
+
"BLOCK_SIZE_M": 64,
|
| 757 |
+
"BLOCK_SIZE_N": block_size[0],
|
| 758 |
+
"BLOCK_SIZE_K": block_size[1],
|
| 759 |
+
"GROUP_SIZE_M": 32,
|
| 760 |
+
"num_warps": 4,
|
| 761 |
+
"num_stages": 3,
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
def grid(META):
|
| 765 |
+
return (
|
| 766 |
+
triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),
|
| 767 |
+
)
|
| 768 |
+
|
| 769 |
+
# Use manually unrolledx4 kernel on AMD GPU when the grid size is small.
|
| 770 |
+
# Empirical testing shows the sweet spot lies when it's less than the # of
|
| 771 |
+
# compute units available on the device.
|
| 772 |
+
num_workgroups = triton.cdiv(M, config["BLOCK_SIZE_M"]) * triton.cdiv(
|
| 773 |
+
N, config["BLOCK_SIZE_N"]
|
| 774 |
+
)
|
| 775 |
+
|
| 776 |
+
# deepgemm only support bf16
|
| 777 |
+
if C.dtype == torch.bfloat16 and _ENABLE_JIT_DEEPGEMM:
|
| 778 |
+
if supports_custom_op():
|
| 779 |
+
torch.ops.sglang.deep_gemm_fp8_fp8_bf16_nt(A, As, B, Bs, C)
|
| 780 |
+
else:
|
| 781 |
+
deep_gemm_gemm_nt_f8f8bf16((A, As), (B, Bs), C)
|
| 782 |
+
else:
|
| 783 |
+
kernel = (
|
| 784 |
+
_w8a8_block_fp8_matmul_unrolledx4
|
| 785 |
+
if (_is_hip == True and num_workgroups <= get_device_core_count())
|
| 786 |
+
else _w8a8_block_fp8_matmul
|
| 787 |
+
)
|
| 788 |
+
|
| 789 |
+
kernel[grid](
|
| 790 |
+
A,
|
| 791 |
+
B,
|
| 792 |
+
C,
|
| 793 |
+
As,
|
| 794 |
+
Bs,
|
| 795 |
+
M,
|
| 796 |
+
N,
|
| 797 |
+
K,
|
| 798 |
+
block_n,
|
| 799 |
+
block_k,
|
| 800 |
+
A.stride(-2),
|
| 801 |
+
A.stride(-1),
|
| 802 |
+
B.stride(1),
|
| 803 |
+
B.stride(0),
|
| 804 |
+
C.stride(-2),
|
| 805 |
+
C.stride(-1),
|
| 806 |
+
As.stride(-2),
|
| 807 |
+
As.stride(-1),
|
| 808 |
+
Bs.stride(1),
|
| 809 |
+
Bs.stride(0),
|
| 810 |
+
**config,
|
| 811 |
+
)
|
| 812 |
+
|
| 813 |
+
return C
|
| 814 |
+
|
| 815 |
+
|
| 816 |
+
@triton.jit
|
| 817 |
+
def _per_tensor_quant_mla_fp8_stage1(
|
| 818 |
+
x_ptr,
|
| 819 |
+
x_s_ptr,
|
| 820 |
+
head_size,
|
| 821 |
+
x_stride_h,
|
| 822 |
+
x_stride_s,
|
| 823 |
+
eps,
|
| 824 |
+
fp8_max,
|
| 825 |
+
BLOCK_SIZE: tl.constexpr,
|
| 826 |
+
):
|
| 827 |
+
seq_id = tl.program_id(0)
|
| 828 |
+
head_id = tl.program_id(1)
|
| 829 |
+
offset = tl.arange(0, BLOCK_SIZE)
|
| 830 |
+
mask = offset < head_size
|
| 831 |
+
|
| 832 |
+
x_ptr += head_id * x_stride_h + seq_id * x_stride_s
|
| 833 |
+
x = tl.load(x_ptr + offset, mask=mask, other=0.0).to(tl.float32)
|
| 834 |
+
_absmax = tl.maximum(tl.max(tl.abs(x)), eps)
|
| 835 |
+
|
| 836 |
+
tl.atomic_max(x_s_ptr, _absmax / fp8_max)
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
@triton.jit
|
| 840 |
+
def _per_tensor_quant_mla_fp8_stage2(
|
| 841 |
+
x_ptr,
|
| 842 |
+
x_s_ptr,
|
| 843 |
+
x_q_ptr,
|
| 844 |
+
num_seq,
|
| 845 |
+
head_size,
|
| 846 |
+
x_stride_h,
|
| 847 |
+
x_stride_s,
|
| 848 |
+
fp8_min,
|
| 849 |
+
fp8_max,
|
| 850 |
+
BLOCK_SIZE: tl.constexpr,
|
| 851 |
+
):
|
| 852 |
+
seq_id = tl.program_id(0)
|
| 853 |
+
head_id = tl.program_id(1)
|
| 854 |
+
offset = tl.arange(0, BLOCK_SIZE)
|
| 855 |
+
mask = offset < head_size
|
| 856 |
+
|
| 857 |
+
x_s = tl.load(x_s_ptr)
|
| 858 |
+
x_s_inv = 1.0 / x_s
|
| 859 |
+
|
| 860 |
+
x_ptr += head_id * x_stride_h + seq_id * x_stride_s
|
| 861 |
+
x_q_ptr += head_id * num_seq * head_size + seq_id * head_size
|
| 862 |
+
|
| 863 |
+
x = tl.load(x_ptr + offset, mask=mask, other=0.0).to(tl.float32)
|
| 864 |
+
x_q = tl.clamp(x * x_s_inv, fp8_min, fp8_max).to(x_q_ptr.dtype.element_ty)
|
| 865 |
+
tl.store(x_q_ptr + offset, x_q, mask=mask)
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def per_tensor_quant_mla_fp8(
|
| 869 |
+
x: torch.Tensor, x_s_out: torch.Tensor, eps: float = 1e-12
|
| 870 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 871 |
+
"""
|
| 872 |
+
This function quantizes input values to float8 values with tensor-wise quantization
|
| 873 |
+
and specialized for mla absorbed case.
|
| 874 |
+
"""
|
| 875 |
+
assert x.dim() == 3, "`x` is not a 3d-tensor"
|
| 876 |
+
assert (
|
| 877 |
+
x_s_out.shape == (1,)
|
| 878 |
+
and x_s_out.dtype == torch.float32
|
| 879 |
+
and x_s_out.device == x.device
|
| 880 |
+
)
|
| 881 |
+
|
| 882 |
+
x_q = x.new_empty(x.size(), dtype=_fp8_type)
|
| 883 |
+
|
| 884 |
+
num_head, num_seq, head_size = x.shape
|
| 885 |
+
BLOCK_SIZE = triton.next_power_of_2(head_size)
|
| 886 |
+
grid = (num_seq, num_head)
|
| 887 |
+
|
| 888 |
+
_per_tensor_quant_mla_fp8_stage1[grid](
|
| 889 |
+
x,
|
| 890 |
+
x_s_out,
|
| 891 |
+
head_size,
|
| 892 |
+
x.stride(0),
|
| 893 |
+
x.stride(1),
|
| 894 |
+
eps,
|
| 895 |
+
fp8_max,
|
| 896 |
+
BLOCK_SIZE,
|
| 897 |
+
)
|
| 898 |
+
_per_tensor_quant_mla_fp8_stage2[grid](
|
| 899 |
+
x,
|
| 900 |
+
x_s_out,
|
| 901 |
+
x_q,
|
| 902 |
+
num_seq,
|
| 903 |
+
head_size,
|
| 904 |
+
x.stride(0),
|
| 905 |
+
x.stride(1),
|
| 906 |
+
fp8_min,
|
| 907 |
+
fp8_max,
|
| 908 |
+
BLOCK_SIZE,
|
| 909 |
+
)
|
| 910 |
+
|
| 911 |
+
return x_q, x_s_out
|
| 912 |
+
|
| 913 |
+
|
| 914 |
+
@triton.jit
|
| 915 |
+
def _per_token_group_quant_mla_deep_gemm_masked_fp8(
|
| 916 |
+
y_ptr,
|
| 917 |
+
y_q_ptr,
|
| 918 |
+
y_s_ptr,
|
| 919 |
+
masked_m_ptr,
|
| 920 |
+
group_size,
|
| 921 |
+
y_stride_b,
|
| 922 |
+
y_stride_t,
|
| 923 |
+
y_q_stride_b,
|
| 924 |
+
y_q_stride_t,
|
| 925 |
+
y_s_stride_b,
|
| 926 |
+
y_s_stride_g,
|
| 927 |
+
eps,
|
| 928 |
+
fp8_min,
|
| 929 |
+
fp8_max,
|
| 930 |
+
NUM_GROUP: tl.constexpr,
|
| 931 |
+
BLOCK: tl.constexpr,
|
| 932 |
+
):
|
| 933 |
+
"""A Triton-accelerated function to perform per-token-group
|
| 934 |
+
quantization on a tensor for deep_gemm grouped_gemm_masked.
|
| 935 |
+
This function converts the tensor values into float8 values.
|
| 936 |
+
y and y_q: (b, t, k)
|
| 937 |
+
y_s: (b, k//group_size, t)
|
| 938 |
+
"""
|
| 939 |
+
t_id = tl.program_id(0)
|
| 940 |
+
b_id = tl.program_id(1)
|
| 941 |
+
|
| 942 |
+
y_ptr += b_id * y_stride_b + t_id * y_stride_t
|
| 943 |
+
y_q_ptr += b_id * y_q_stride_b + t_id * y_q_stride_t
|
| 944 |
+
y_s_ptr += b_id * y_s_stride_b + t_id
|
| 945 |
+
|
| 946 |
+
if t_id == 0:
|
| 947 |
+
tl.store(masked_m_ptr + b_id, tl.num_programs(0))
|
| 948 |
+
|
| 949 |
+
cols = tl.arange(0, BLOCK) # group_size <= BLOCK
|
| 950 |
+
mask = cols < group_size
|
| 951 |
+
|
| 952 |
+
for gid in range(NUM_GROUP):
|
| 953 |
+
y = tl.load(y_ptr + gid * group_size + cols, mask=mask, other=0.0).to(
|
| 954 |
+
tl.float32
|
| 955 |
+
)
|
| 956 |
+
_absmax = tl.maximum(tl.max(tl.abs(y)), eps)
|
| 957 |
+
y_s = _absmax / fp8_max
|
| 958 |
+
y_q = tl.clamp(y / y_s, fp8_min, fp8_max).to(y_q_ptr.dtype.element_ty)
|
| 959 |
+
|
| 960 |
+
tl.store(y_q_ptr + gid * group_size + cols, y_q, mask=mask)
|
| 961 |
+
tl.store(y_s_ptr + gid * y_s_stride_g, y_s)
|
| 962 |
+
|
| 963 |
+
|
| 964 |
+
def per_tensor_quant_mla_deep_gemm_masked_fp8(
|
| 965 |
+
x: torch.Tensor,
|
| 966 |
+
group_size: int = 128,
|
| 967 |
+
eps: float = 1e-12,
|
| 968 |
+
dtype: torch.dtype = torch.float8_e4m3fn,
|
| 969 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 970 |
+
"""
|
| 971 |
+
This function quantizes input values to float8 values with per-token-group-quantization
|
| 972 |
+
for deep_gemm grouped_gemm_masked and specialized for mla absorbed case.
|
| 973 |
+
"""
|
| 974 |
+
assert x.dim() == 3, "`x` is not a 3d-tensor"
|
| 975 |
+
|
| 976 |
+
finfo = torch.finfo(dtype)
|
| 977 |
+
fp8_max = finfo.max
|
| 978 |
+
if _is_hip:
|
| 979 |
+
dtype = torch.float8_e4m3fnuz
|
| 980 |
+
fp8_max = 224.0
|
| 981 |
+
|
| 982 |
+
b, m, k = x.shape
|
| 983 |
+
aligned_m = (m + 255) // 256 * 256 # 256 is the max block_m of the gemm kernel
|
| 984 |
+
num_tiles_k = k // group_size
|
| 985 |
+
assert num_tiles_k * group_size == k, f"k % {group_size} must be zero"
|
| 986 |
+
|
| 987 |
+
x_q = x.new_empty((b, aligned_m, k), dtype=dtype)
|
| 988 |
+
x_s = x.new_empty((b, num_tiles_k, aligned_m), dtype=torch.float32)
|
| 989 |
+
masked_m = x.new_empty((b,), dtype=torch.int32)
|
| 990 |
+
|
| 991 |
+
BLOCK_SIZE = triton.next_power_of_2(group_size)
|
| 992 |
+
grid = (m, b)
|
| 993 |
+
|
| 994 |
+
_per_token_group_quant_mla_deep_gemm_masked_fp8[grid](
|
| 995 |
+
x,
|
| 996 |
+
x_q,
|
| 997 |
+
x_s,
|
| 998 |
+
masked_m,
|
| 999 |
+
group_size,
|
| 1000 |
+
x.stride(0),
|
| 1001 |
+
x.stride(1),
|
| 1002 |
+
x_q.stride(0),
|
| 1003 |
+
x_q.stride(1),
|
| 1004 |
+
x_s.stride(0),
|
| 1005 |
+
x_s.stride(1),
|
| 1006 |
+
eps,
|
| 1007 |
+
-fp8_max,
|
| 1008 |
+
fp8_max,
|
| 1009 |
+
num_tiles_k,
|
| 1010 |
+
BLOCK_SIZE,
|
| 1011 |
+
)
|
| 1012 |
+
|
| 1013 |
+
return x_q, x_s.transpose(1, 2), masked_m, m, aligned_m
|
| 1014 |
+
|
| 1015 |
+
|
| 1016 |
+
def scaled_fp8_quant(
|
| 1017 |
+
input: torch.Tensor,
|
| 1018 |
+
scale: Optional[torch.Tensor] = None,
|
| 1019 |
+
num_token_padding: Optional[int] = None,
|
| 1020 |
+
use_per_token_if_dynamic: bool = False,
|
| 1021 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 1022 |
+
"""
|
| 1023 |
+
Quantize input tensor to FP8 (8-bit floating point) format.
|
| 1024 |
+
|
| 1025 |
+
Args:
|
| 1026 |
+
input (torch.Tensor): Input tensor to be quantized
|
| 1027 |
+
scale (Optional[torch.Tensor]): Pre-computed scaling factor for static quantization.
|
| 1028 |
+
If None, scales will be computed dynamically.
|
| 1029 |
+
num_token_padding (Optional[int]): If specified, pad the first dimension
|
| 1030 |
+
of the output to at least this value.
|
| 1031 |
+
use_per_token_if_dynamic (bool): When using dynamic scaling (scale=None),
|
| 1032 |
+
determines the quantization granularity:
|
| 1033 |
+
- True: compute scale per token
|
| 1034 |
+
- False: compute single scale per tensor
|
| 1035 |
+
|
| 1036 |
+
Returns:
|
| 1037 |
+
Tuple[torch.Tensor, torch.Tensor]: A tuple containing:
|
| 1038 |
+
- quantized_tensor: The FP8 quantized version of input
|
| 1039 |
+
- scale_tensor: The scaling factors used for quantization
|
| 1040 |
+
|
| 1041 |
+
Raises:
|
| 1042 |
+
AssertionError: If input is not 2D or if static scale's numel != 1
|
| 1043 |
+
"""
|
| 1044 |
+
assert input.ndim == 2, f"Expected 2D input tensor, got {input.ndim}D"
|
| 1045 |
+
shape = input.shape
|
| 1046 |
+
out_dtype = torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
| 1047 |
+
if num_token_padding:
|
| 1048 |
+
shape = (max(num_token_padding, input.shape[0]), shape[1])
|
| 1049 |
+
output = torch.empty(shape, device=input.device, dtype=out_dtype)
|
| 1050 |
+
|
| 1051 |
+
if scale is None:
|
| 1052 |
+
# Dynamic scaling
|
| 1053 |
+
if use_per_token_if_dynamic:
|
| 1054 |
+
scale = torch.empty((shape[0], 1), device=input.device, dtype=torch.float32)
|
| 1055 |
+
sgl_per_token_quant_fp8(input, output, scale)
|
| 1056 |
+
else:
|
| 1057 |
+
scale = torch.zeros(1, device=input.device, dtype=torch.float32)
|
| 1058 |
+
sgl_per_tensor_quant_fp8(
|
| 1059 |
+
input, output, scale, is_static=False
|
| 1060 |
+
) # False for dynamic
|
| 1061 |
+
else:
|
| 1062 |
+
# Static scaling
|
| 1063 |
+
assert scale.numel() == 1, f"Expected scalar scale, got numel={scale.numel()}"
|
| 1064 |
+
sgl_per_tensor_quant_fp8(
|
| 1065 |
+
input, output, scale, is_static=True
|
| 1066 |
+
) # True for static
|
| 1067 |
+
|
| 1068 |
+
return output, scale
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8_utils.py
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import List, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from sglang.srt.layers.quantization.fp8_kernel import sglang_per_token_group_quant_fp8
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from vllm import _custom_ops as ops
|
| 10 |
+
|
| 11 |
+
VLLM_AVAILABLE = True
|
| 12 |
+
except ImportError:
|
| 13 |
+
VLLM_AVAILABLE = False
|
| 14 |
+
|
| 15 |
+
from sglang.srt.layers.quantization.deep_gemm import _ENABLE_JIT_DEEPGEMM
|
| 16 |
+
from sglang.srt.layers.quantization.fp8_kernel import (
|
| 17 |
+
per_token_group_quant_fp8,
|
| 18 |
+
scaled_fp8_quant,
|
| 19 |
+
sglang_per_token_quant_fp8,
|
| 20 |
+
static_quant_fp8,
|
| 21 |
+
w8a8_block_fp8_matmul,
|
| 22 |
+
)
|
| 23 |
+
from sglang.srt.utils import (
|
| 24 |
+
get_bool_env_var,
|
| 25 |
+
get_cuda_version,
|
| 26 |
+
get_device_capability,
|
| 27 |
+
is_cuda,
|
| 28 |
+
is_hip,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
_is_hip = is_hip()
|
| 32 |
+
_is_cuda = is_cuda()
|
| 33 |
+
|
| 34 |
+
if _is_hip and get_bool_env_var("SGLANG_AITER_MOE"):
|
| 35 |
+
from aiter import gemm_a8w8_blockscale
|
| 36 |
+
|
| 37 |
+
if _is_cuda:
|
| 38 |
+
from sgl_kernel import fp8_blockwise_scaled_mm, fp8_scaled_mm
|
| 39 |
+
|
| 40 |
+
use_vllm_cutlass_w8a8_fp8_kernel = get_bool_env_var("USE_VLLM_CUTLASS_W8A8_FP8_KERNEL")
|
| 41 |
+
|
| 42 |
+
# Input scaling factors are no longer optional in _scaled_mm starting
|
| 43 |
+
# from pytorch 2.5. Allocating a dummy tensor to pass as input_scale
|
| 44 |
+
TORCH_DEVICE_IDENTITY = None
|
| 45 |
+
|
| 46 |
+
_TORCH_VERSION = torch.__version__.split("+")[0]
|
| 47 |
+
try:
|
| 48 |
+
_TORCH_VERSION_TUPLE = tuple(map(int, _TORCH_VERSION.split(".")[:3]))
|
| 49 |
+
except ValueError:
|
| 50 |
+
_TORCH_VERSION_TUPLE = (0, 0, 0)
|
| 51 |
+
|
| 52 |
+
# The condition to determine if it is on a platform that supports
|
| 53 |
+
# torch._scaled_mm rowwise feature.
|
| 54 |
+
# The condition is determined once as the operations
|
| 55 |
+
# are time consuming.
|
| 56 |
+
USE_ROWWISE_TORCH_SCALED_MM = (
|
| 57 |
+
_is_hip and get_device_capability() >= (9, 4) and _TORCH_VERSION_TUPLE >= (2, 7, 0)
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def cutlass_fp8_supported():
|
| 62 |
+
if not _is_cuda:
|
| 63 |
+
return False
|
| 64 |
+
major, minor = get_device_capability()
|
| 65 |
+
cuda_version = get_cuda_version()
|
| 66 |
+
if major >= 9:
|
| 67 |
+
return cuda_version >= (12, 0)
|
| 68 |
+
elif major == 8 and minor == 9:
|
| 69 |
+
return cuda_version >= (12, 4)
|
| 70 |
+
return False
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def normalize_e4m3fn_to_e4m3fnuz(
|
| 74 |
+
weight: torch.Tensor,
|
| 75 |
+
weight_scale: torch.Tensor,
|
| 76 |
+
input_scale: Optional[torch.Tensor] = None,
|
| 77 |
+
) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
|
| 78 |
+
assert weight.dtype == torch.float8_e4m3fn
|
| 79 |
+
# The bits pattern 10000000(-128) represents zero in e4m3fn
|
| 80 |
+
# but NaN in e4m3fnuz. So here we set it to 0.
|
| 81 |
+
# https://onnx.ai/onnx/technical/float8.html
|
| 82 |
+
weight_as_int8 = weight.view(torch.int8)
|
| 83 |
+
ROCM_FP8_NAN_AS_INT = -128
|
| 84 |
+
weight_as_int8[weight_as_int8 == ROCM_FP8_NAN_AS_INT] = 0
|
| 85 |
+
weight = weight_as_int8.view(torch.float8_e4m3fnuz)
|
| 86 |
+
|
| 87 |
+
# For the same bits representation, e4m3fnuz value is half of
|
| 88 |
+
# the e4m3fn value, so we should double the scaling factor to
|
| 89 |
+
# get the same dequantized value.
|
| 90 |
+
# https://onnx.ai/onnx/technical/float8.html
|
| 91 |
+
weight_scale = weight_scale * 2.0
|
| 92 |
+
if input_scale is not None:
|
| 93 |
+
input_scale = input_scale * 2.0
|
| 94 |
+
return weight, weight_scale, input_scale
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def cutlass_block_fp8_supported() -> bool:
|
| 98 |
+
if not get_bool_env_var("SUPPORT_CUTLASS_BLOCK_FP8"):
|
| 99 |
+
return False
|
| 100 |
+
if _is_cuda:
|
| 101 |
+
major, minor = torch.cuda.get_device_capability()
|
| 102 |
+
sm_version = major * 10 + minor
|
| 103 |
+
cuda_version = tuple(map(int, torch.version.cuda.split(".")))
|
| 104 |
+
if cuda_version >= (12, 0) and sm_version >= 90:
|
| 105 |
+
return True
|
| 106 |
+
return False
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
CUTLASS_BLOCK_FP8_SUPPORTED = cutlass_block_fp8_supported()
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def apply_w8a8_block_fp8_linear(
|
| 113 |
+
input: torch.Tensor,
|
| 114 |
+
weight: torch.Tensor,
|
| 115 |
+
block_size: List[int],
|
| 116 |
+
weight_scale: torch.Tensor,
|
| 117 |
+
input_scale: Optional[torch.Tensor] = None,
|
| 118 |
+
bias: Optional[torch.Tensor] = None,
|
| 119 |
+
) -> torch.Tensor:
|
| 120 |
+
assert input_scale is None
|
| 121 |
+
# View input as 2D matrix for fp8 methods
|
| 122 |
+
input_2d = input.view(-1, input.shape[-1])
|
| 123 |
+
output_shape = [*input.shape[:-1], weight.shape[0]]
|
| 124 |
+
# TODO: add more robust shape check here
|
| 125 |
+
shape_supported_by_cutlass = (
|
| 126 |
+
weight.shape[0] % 128 == 0 and weight.shape[1] % 128 == 0
|
| 127 |
+
)
|
| 128 |
+
if CUTLASS_BLOCK_FP8_SUPPORTED and shape_supported_by_cutlass:
|
| 129 |
+
q_input, x_scale = per_token_group_quant_fp8(
|
| 130 |
+
input_2d, block_size[1], column_major_scales=True
|
| 131 |
+
)
|
| 132 |
+
output = fp8_blockwise_scaled_mm(
|
| 133 |
+
q_input, weight.T, x_scale, weight_scale.T, out_dtype=input.dtype
|
| 134 |
+
)
|
| 135 |
+
elif _is_hip and get_bool_env_var("SGLANG_AITER_MOE"):
|
| 136 |
+
q_input, x_scale = per_token_group_quant_fp8(
|
| 137 |
+
input_2d, block_size[1], column_major_scales=False
|
| 138 |
+
)
|
| 139 |
+
output = torch.zeros(
|
| 140 |
+
[q_input.shape[0], weight.shape[0]],
|
| 141 |
+
dtype=input.dtype,
|
| 142 |
+
device=q_input.device,
|
| 143 |
+
)
|
| 144 |
+
gemm_a8w8_blockscale(q_input, weight, x_scale, weight_scale, output)
|
| 145 |
+
else:
|
| 146 |
+
if _ENABLE_JIT_DEEPGEMM:
|
| 147 |
+
q_input, x_scale = sglang_per_token_group_quant_fp8(
|
| 148 |
+
input_2d,
|
| 149 |
+
block_size[1],
|
| 150 |
+
column_major_scales=True,
|
| 151 |
+
scale_tma_aligned=True,
|
| 152 |
+
)
|
| 153 |
+
else:
|
| 154 |
+
q_input, x_scale = per_token_group_quant_fp8(
|
| 155 |
+
input_2d, block_size[1], column_major_scales=False
|
| 156 |
+
)
|
| 157 |
+
output = w8a8_block_fp8_matmul(
|
| 158 |
+
q_input, weight, x_scale, weight_scale, block_size, output_dtype=input.dtype
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
if bias is not None:
|
| 162 |
+
output = output + bias
|
| 163 |
+
return output.to(dtype=input.dtype).view(*output_shape)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def input_to_float8(
|
| 167 |
+
x: torch.Tensor, dtype: torch.dtype = torch.float8_e4m3fn
|
| 168 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 169 |
+
"""This function quantizes input values to float8 values with tensor-wise quantization."""
|
| 170 |
+
finfo = torch.finfo(dtype)
|
| 171 |
+
min_val, max_val = x.aminmax()
|
| 172 |
+
amax = torch.maximum(min_val.abs(), max_val.abs()).float().clamp(min=1e-12)
|
| 173 |
+
fp8_max = finfo.max
|
| 174 |
+
if _is_hip:
|
| 175 |
+
dtype = torch.float8_e4m3fnuz
|
| 176 |
+
fp8_max = 224.0
|
| 177 |
+
scale = fp8_max / amax
|
| 178 |
+
x_scl_sat = (x.float() * scale).clamp(min=-fp8_max, max=fp8_max)
|
| 179 |
+
return x_scl_sat.to(dtype).contiguous(), scale.float().reciprocal()
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def block_quant_to_tensor_quant(
|
| 183 |
+
x_q_block: torch.Tensor,
|
| 184 |
+
x_s: torch.Tensor,
|
| 185 |
+
block_size: List[int],
|
| 186 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 187 |
+
"""This function converts block-wise quantization to tensor-wise quantization.
|
| 188 |
+
The inputs are block-wise quantization tensor `x_q_block`, block-wise quantization scale
|
| 189 |
+
and the block size.
|
| 190 |
+
The outputs are tensor-wise quantization tensor and tensor-wise quantization scale.
|
| 191 |
+
Note only float8 is supported for now.
|
| 192 |
+
"""
|
| 193 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 194 |
+
n, k = x_q_block.shape
|
| 195 |
+
n_tiles = (n + block_n - 1) // block_n
|
| 196 |
+
k_tiles = (k + block_k - 1) // block_k
|
| 197 |
+
assert n_tiles == x_s.shape[0]
|
| 198 |
+
assert k_tiles == x_s.shape[1]
|
| 199 |
+
|
| 200 |
+
x_dq_block = x_q_block.to(torch.float32)
|
| 201 |
+
|
| 202 |
+
x_dq_block_tiles = [
|
| 203 |
+
[
|
| 204 |
+
x_dq_block[
|
| 205 |
+
j * block_n : min((j + 1) * block_n, n),
|
| 206 |
+
i * block_k : min((i + 1) * block_k, k),
|
| 207 |
+
]
|
| 208 |
+
for i in range(k_tiles)
|
| 209 |
+
]
|
| 210 |
+
for j in range(n_tiles)
|
| 211 |
+
]
|
| 212 |
+
|
| 213 |
+
for i in range(k_tiles):
|
| 214 |
+
for j in range(n_tiles):
|
| 215 |
+
x_dq_block_tiles[j][i][:, :] = x_dq_block_tiles[j][i] * x_s[j][i]
|
| 216 |
+
|
| 217 |
+
x_q_tensor, scale = (
|
| 218 |
+
scaled_fp8_quant(x_dq_block)
|
| 219 |
+
if _is_cuda
|
| 220 |
+
else input_to_float8(x_dq_block, dtype=x_q_block.dtype)
|
| 221 |
+
)
|
| 222 |
+
return x_q_tensor, scale
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def channel_quant_to_tensor_quant(
|
| 226 |
+
x_q_channel: torch.Tensor,
|
| 227 |
+
x_s: torch.Tensor,
|
| 228 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 229 |
+
x_dq_channel = x_q_channel.to(torch.float32) * x_s
|
| 230 |
+
x_q_tensor, scale = (
|
| 231 |
+
scaled_fp8_quant(x_dq_channel)
|
| 232 |
+
if _is_cuda
|
| 233 |
+
else input_to_float8(x_dq_channel, dtype=x_q_channel.dtype)
|
| 234 |
+
)
|
| 235 |
+
return x_q_tensor, scale
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _process_scaled_mm_output(output, input_2d_shape, output_shape):
|
| 239 |
+
if type(output) is tuple and len(output) == 2:
|
| 240 |
+
output = output[0]
|
| 241 |
+
return torch.narrow(output, 0, 0, input_2d_shape[0]).view(*output_shape)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _apply_fallback_scaled_mm(
|
| 245 |
+
qinput,
|
| 246 |
+
weight,
|
| 247 |
+
x_scale,
|
| 248 |
+
weight_scale,
|
| 249 |
+
input_2d_shape,
|
| 250 |
+
output_shape,
|
| 251 |
+
bias,
|
| 252 |
+
input_dtype,
|
| 253 |
+
):
|
| 254 |
+
global TORCH_DEVICE_IDENTITY
|
| 255 |
+
if TORCH_DEVICE_IDENTITY is None:
|
| 256 |
+
TORCH_DEVICE_IDENTITY = torch.ones(1, dtype=torch.float32, device=weight.device)
|
| 257 |
+
|
| 258 |
+
output = torch._scaled_mm(
|
| 259 |
+
qinput,
|
| 260 |
+
weight,
|
| 261 |
+
scale_a=TORCH_DEVICE_IDENTITY,
|
| 262 |
+
scale_b=TORCH_DEVICE_IDENTITY,
|
| 263 |
+
out_dtype=torch.float32,
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
output = _process_scaled_mm_output(output, input_2d_shape, output_shape)
|
| 267 |
+
x_scale = torch.narrow(x_scale, 0, 0, input_2d_shape[0])
|
| 268 |
+
|
| 269 |
+
output = output * x_scale * weight_scale.t()
|
| 270 |
+
if bias is not None:
|
| 271 |
+
output = output + bias
|
| 272 |
+
return output.to(dtype=input_dtype)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def apply_fp8_linear(
|
| 276 |
+
input: torch.Tensor,
|
| 277 |
+
weight: torch.Tensor,
|
| 278 |
+
weight_scale: torch.Tensor,
|
| 279 |
+
input_scale: Optional[torch.Tensor] = None,
|
| 280 |
+
input_scale_ub: Optional[torch.Tensor] = None,
|
| 281 |
+
bias: Optional[torch.Tensor] = None,
|
| 282 |
+
cutlass_fp8_supported: bool = cutlass_fp8_supported(),
|
| 283 |
+
use_per_token_if_dynamic: bool = False,
|
| 284 |
+
pad_output: Optional[bool] = None,
|
| 285 |
+
compressed_tensor_quant: bool = False,
|
| 286 |
+
) -> torch.Tensor:
|
| 287 |
+
# Note: we pad the input because torch._scaled_mm is more performant
|
| 288 |
+
# for matrices with batch dimension > 16.
|
| 289 |
+
# This could change in the future.
|
| 290 |
+
# We also don't pad when using torch.compile,
|
| 291 |
+
# as it breaks with dynamic shapes.
|
| 292 |
+
if pad_output is None:
|
| 293 |
+
pad_output = not get_bool_env_var("SGLANG_ENABLE_TORCH_COMPILE")
|
| 294 |
+
output_padding = 17 if pad_output else None
|
| 295 |
+
|
| 296 |
+
# View input as 2D matrix for fp8 methods
|
| 297 |
+
input_2d = input.view(-1, input.shape[-1])
|
| 298 |
+
output_shape = [*input.shape[:-1], weight.shape[1]]
|
| 299 |
+
|
| 300 |
+
if compressed_tensor_quant:
|
| 301 |
+
# cutlass_scaled_mm supports per tensor/channel W and per tensor/token A
|
| 302 |
+
# for sgl-kernel fp8_scaled_mm, it support per channel W now
|
| 303 |
+
if cutlass_fp8_supported and weight_scale.numel() == weight.shape[1]:
|
| 304 |
+
qinput, x_scale = scaled_fp8_quant(
|
| 305 |
+
input_2d,
|
| 306 |
+
input_scale,
|
| 307 |
+
use_per_token_if_dynamic=use_per_token_if_dynamic,
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
# Fused GEMM_DQ
|
| 311 |
+
if VLLM_AVAILABLE and use_vllm_cutlass_w8a8_fp8_kernel:
|
| 312 |
+
# Fall back to vllm cutlass w8a8 fp8 kernel
|
| 313 |
+
output = ops.cutlass_scaled_mm(
|
| 314 |
+
qinput,
|
| 315 |
+
weight,
|
| 316 |
+
out_dtype=input.dtype,
|
| 317 |
+
scale_a=x_scale,
|
| 318 |
+
scale_b=weight_scale,
|
| 319 |
+
bias=bias,
|
| 320 |
+
)
|
| 321 |
+
else:
|
| 322 |
+
assert (
|
| 323 |
+
weight_scale.numel() == weight.shape[1]
|
| 324 |
+
), "cutlass w8a8 fp8 sgl-kernel only supports per-channel scale"
|
| 325 |
+
output = fp8_scaled_mm(
|
| 326 |
+
qinput,
|
| 327 |
+
weight,
|
| 328 |
+
x_scale,
|
| 329 |
+
weight_scale,
|
| 330 |
+
out_dtype=input.dtype,
|
| 331 |
+
bias=bias,
|
| 332 |
+
)
|
| 333 |
+
return output.view(*output_shape)
|
| 334 |
+
|
| 335 |
+
# torch.scaled_mm supports per tensor weights + activations only
|
| 336 |
+
# so fallback to naive if per channel or per token
|
| 337 |
+
else:
|
| 338 |
+
# Maybe apply padding to output, see comment in __init__
|
| 339 |
+
qinput, x_scale = (
|
| 340 |
+
scaled_fp8_quant(
|
| 341 |
+
input_2d,
|
| 342 |
+
input_scale,
|
| 343 |
+
num_token_padding=output_padding,
|
| 344 |
+
use_per_token_if_dynamic=use_per_token_if_dynamic,
|
| 345 |
+
)
|
| 346 |
+
if _is_cuda
|
| 347 |
+
else ops.scaled_fp8_quant(
|
| 348 |
+
input_2d,
|
| 349 |
+
input_scale,
|
| 350 |
+
num_token_padding=output_padding,
|
| 351 |
+
use_per_token_if_dynamic=use_per_token_if_dynamic,
|
| 352 |
+
)
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
per_tensor_weights = weight_scale.numel() == 1
|
| 356 |
+
per_tensor_activations = x_scale.numel() == 1
|
| 357 |
+
|
| 358 |
+
if per_tensor_weights and per_tensor_activations:
|
| 359 |
+
# Fused GEMM_DQ
|
| 360 |
+
output = torch._scaled_mm(
|
| 361 |
+
qinput,
|
| 362 |
+
weight,
|
| 363 |
+
out_dtype=input.dtype,
|
| 364 |
+
scale_a=x_scale,
|
| 365 |
+
scale_b=weight_scale,
|
| 366 |
+
bias=bias,
|
| 367 |
+
)
|
| 368 |
+
return _process_scaled_mm_output(output, input_2d.shape, output_shape)
|
| 369 |
+
|
| 370 |
+
elif (
|
| 371 |
+
use_per_token_if_dynamic
|
| 372 |
+
and not per_tensor_weights
|
| 373 |
+
and not per_tensor_activations
|
| 374 |
+
and USE_ROWWISE_TORCH_SCALED_MM
|
| 375 |
+
):
|
| 376 |
+
# For now validated on ROCm platform
|
| 377 |
+
# fp8 rowwise scaling in torch._scaled_mm is introduced in
|
| 378 |
+
# https://github.com/pytorch/pytorch/pull/144432 using hipBLASLt
|
| 379 |
+
# and ROCm 6.3, which only exists in torch 2.7 and above.
|
| 380 |
+
# For CUDA platform please validate if the
|
| 381 |
+
# torch._scaled_mm support rowwise scaled GEMM
|
| 382 |
+
# Fused GEMM_DQ Rowwise GEMM
|
| 383 |
+
output = torch._scaled_mm(
|
| 384 |
+
qinput,
|
| 385 |
+
weight,
|
| 386 |
+
out_dtype=input.dtype,
|
| 387 |
+
scale_a=x_scale,
|
| 388 |
+
scale_b=weight_scale.t(),
|
| 389 |
+
bias=bias,
|
| 390 |
+
)
|
| 391 |
+
return _process_scaled_mm_output(output, input_2d.shape, output_shape)
|
| 392 |
+
|
| 393 |
+
else:
|
| 394 |
+
# Fallback for channelwise case, where we use unfused DQ
|
| 395 |
+
# due to limitations with scaled_mm
|
| 396 |
+
|
| 397 |
+
# Symmetric quantized GEMM by definition computes the following:
|
| 398 |
+
# C = (s_x * X) (s_w * W) + bias
|
| 399 |
+
# This is equivalent to dequantizing the weights and activations
|
| 400 |
+
# before applying a GEMM.
|
| 401 |
+
#
|
| 402 |
+
# In order to compute quantized operands, a quantized kernel
|
| 403 |
+
# will rewrite the above like so:
|
| 404 |
+
# C = s_w * s_x * (X * W) + bias
|
| 405 |
+
#
|
| 406 |
+
# For the scaled_mm fallback case, we break this down, since it
|
| 407 |
+
# does not support s_w being a vector.
|
| 408 |
+
return _apply_fallback_scaled_mm(
|
| 409 |
+
qinput,
|
| 410 |
+
weight,
|
| 411 |
+
x_scale,
|
| 412 |
+
weight_scale,
|
| 413 |
+
input_2d.shape,
|
| 414 |
+
output_shape,
|
| 415 |
+
bias,
|
| 416 |
+
input.dtype,
|
| 417 |
+
)
|
| 418 |
+
else:
|
| 419 |
+
# cutlass w8a8 fp8 sgl-kernel only supports per-token scale
|
| 420 |
+
if input_scale is not None:
|
| 421 |
+
assert input_scale.numel() == 1
|
| 422 |
+
# broadcast per-tensor scale to per-token scale when supporting cutlass
|
| 423 |
+
qinput, x_scale = static_quant_fp8(
|
| 424 |
+
input_2d, input_scale, repeat_scale=cutlass_fp8_supported
|
| 425 |
+
)
|
| 426 |
+
else:
|
| 427 |
+
# default use per-token quantization if dynamic
|
| 428 |
+
if _is_cuda:
|
| 429 |
+
qinput, x_scale = sglang_per_token_quant_fp8(input_2d)
|
| 430 |
+
else:
|
| 431 |
+
# TODO(kkhuang): temporarily enforce per-tensor activation scaling if weight is per-tensor scaling
|
| 432 |
+
# final solution should be: 1. add support to per-tensor activation scaling.
|
| 433 |
+
# 2. solve the torch.compile error from weight_scale.numel() == 1 and x_scale.numel() > 1 (below line#308)
|
| 434 |
+
if _is_hip and weight_scale.numel() == 1:
|
| 435 |
+
qinput, x_scale = ops.scaled_fp8_quant(
|
| 436 |
+
input_2d,
|
| 437 |
+
input_scale,
|
| 438 |
+
use_per_token_if_dynamic=use_per_token_if_dynamic,
|
| 439 |
+
)
|
| 440 |
+
else:
|
| 441 |
+
qinput, x_scale = per_token_group_quant_fp8(
|
| 442 |
+
input_2d, group_size=input_2d.shape[1]
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
if cutlass_fp8_supported:
|
| 446 |
+
try:
|
| 447 |
+
if VLLM_AVAILABLE and use_vllm_cutlass_w8a8_fp8_kernel:
|
| 448 |
+
# Fall back to vllm cutlass w8a8 fp8 kernel
|
| 449 |
+
output = ops.cutlass_scaled_mm(
|
| 450 |
+
qinput,
|
| 451 |
+
weight,
|
| 452 |
+
out_dtype=input.dtype,
|
| 453 |
+
scale_a=x_scale,
|
| 454 |
+
scale_b=weight_scale,
|
| 455 |
+
bias=bias,
|
| 456 |
+
)
|
| 457 |
+
else:
|
| 458 |
+
assert (
|
| 459 |
+
weight_scale.numel() == weight.shape[1]
|
| 460 |
+
), "cutlass w8a8 fp8 sgl-kernel only supports per-channel scale"
|
| 461 |
+
output = fp8_scaled_mm(
|
| 462 |
+
qinput,
|
| 463 |
+
weight,
|
| 464 |
+
x_scale,
|
| 465 |
+
weight_scale,
|
| 466 |
+
out_dtype=input.dtype,
|
| 467 |
+
bias=bias,
|
| 468 |
+
)
|
| 469 |
+
return output.view(*output_shape)
|
| 470 |
+
except (ImportError, NameError, AttributeError):
|
| 471 |
+
pass
|
| 472 |
+
|
| 473 |
+
# torch.scaled_mm supports per tensor weights + activations only
|
| 474 |
+
# so fallback to naive if per channel or per token
|
| 475 |
+
per_tensor_weights = weight_scale.numel() == 1
|
| 476 |
+
per_tensor_activations = x_scale.numel() == 1
|
| 477 |
+
|
| 478 |
+
if per_tensor_weights and per_tensor_activations:
|
| 479 |
+
# Fused GEMM_DQ
|
| 480 |
+
output = torch._scaled_mm(
|
| 481 |
+
qinput,
|
| 482 |
+
weight,
|
| 483 |
+
out_dtype=input.dtype,
|
| 484 |
+
scale_a=x_scale,
|
| 485 |
+
scale_b=weight_scale,
|
| 486 |
+
bias=bias,
|
| 487 |
+
)
|
| 488 |
+
return _process_scaled_mm_output(output, input_2d.shape, output_shape)
|
| 489 |
+
|
| 490 |
+
else:
|
| 491 |
+
# Fallback for channelwise case, where we use unfused DQ
|
| 492 |
+
# due to limitations with scaled_mm
|
| 493 |
+
|
| 494 |
+
# Symmetric quantized GEMM by definition computes the following:
|
| 495 |
+
# C = (s_x * X) (s_w * W) + bias
|
| 496 |
+
# This is equivalent to dequantizing the weights and activations
|
| 497 |
+
# before applying a GEMM.
|
| 498 |
+
#
|
| 499 |
+
# In order to compute quantized operands, a quantized kernel
|
| 500 |
+
# will rewrite the above like so:
|
| 501 |
+
# C = s_w * s_x * (X * W) + bias
|
| 502 |
+
#
|
| 503 |
+
# For the scaled_mm fallback case, we break this down, since it
|
| 504 |
+
# does not support s_w being a vector.
|
| 505 |
+
return _apply_fallback_scaled_mm(
|
| 506 |
+
qinput,
|
| 507 |
+
weight,
|
| 508 |
+
x_scale,
|
| 509 |
+
weight_scale,
|
| 510 |
+
input_2d.shape,
|
| 511 |
+
output_shape,
|
| 512 |
+
bias,
|
| 513 |
+
input.dtype,
|
| 514 |
+
)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/gptq.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from fractions import Fraction
|
| 3 |
+
from typing import Any, Dict, List, Optional, Union
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from sglang.srt.layers.linear import LinearBase
|
| 8 |
+
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
| 9 |
+
from sglang.srt.utils import is_cuda
|
| 10 |
+
|
| 11 |
+
_is_cuda = is_cuda()
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
from vllm.model_executor.layers.quantization.base_config import QuantizeMethodBase
|
| 15 |
+
from vllm.model_executor.layers.quantization.gptq import GPTQLinearMethod
|
| 16 |
+
from vllm.model_executor.layers.quantization.gptq_marlin import (
|
| 17 |
+
GPTQMarlinLinearMethod,
|
| 18 |
+
GPTQMarlinMoEMethod,
|
| 19 |
+
)
|
| 20 |
+
from vllm.model_executor.layers.quantization.marlin import MarlinLinearMethod
|
| 21 |
+
from vllm.model_executor.layers.quantization.utils.marlin_utils import (
|
| 22 |
+
check_marlin_supported,
|
| 23 |
+
)
|
| 24 |
+
from vllm.scalar_type import scalar_types
|
| 25 |
+
|
| 26 |
+
VLLM_AVAILABLE = True
|
| 27 |
+
except ImportError:
|
| 28 |
+
VLLM_AVAILABLE = False
|
| 29 |
+
|
| 30 |
+
GPTQLinearMethod = MarlinLinearMethod = QuantizeMethodBase = Any
|
| 31 |
+
|
| 32 |
+
class scalar_types:
|
| 33 |
+
uint4b8 = "uint4b8"
|
| 34 |
+
uint8b128 = "uint8b128"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
logger = logging.getLogger(__name__)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def check_marlin_format(hf_quant_cfg: Dict[str, Any]) -> bool:
|
| 41 |
+
# compat: gptqmodel and autogptq (eol) main use checkpoint_format: str
|
| 42 |
+
# compat: autogptq <=0.7.1 is_marlin_format: bool
|
| 43 |
+
return hf_quant_cfg.get("checkpoint_format") == "marlin" or hf_quant_cfg.get(
|
| 44 |
+
"is_marlin_format", False
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class GPTQConfig(QuantizationConfig):
|
| 49 |
+
"""Config class for GPTQ.
|
| 50 |
+
|
| 51 |
+
Reference: https://arxiv.org/abs/2210.17323
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
def __init__(
|
| 55 |
+
self,
|
| 56 |
+
weight_bits: int,
|
| 57 |
+
group_size: int,
|
| 58 |
+
desc_act: bool,
|
| 59 |
+
lm_head_quantized: bool,
|
| 60 |
+
dynamic: Dict[str, Dict[str, Union[int, bool]]],
|
| 61 |
+
) -> None:
|
| 62 |
+
# GPTQModel use `dynamic` config property to allow per module
|
| 63 |
+
# quantization config so each module can be individually optimized.
|
| 64 |
+
# Format is Dict[str, Dict] where key is a regex string that can
|
| 65 |
+
# perform both positive ("+:" prefixed) or negative ("-:" prefixed)
|
| 66 |
+
# matching of a module.
|
| 67 |
+
# Default to positive match, override base quant config mode, if no
|
| 68 |
+
# prefix is used. Value is in dict format of field key and override
|
| 69 |
+
# value.
|
| 70 |
+
# Negative matching will skip quantization init for this module
|
| 71 |
+
# entirely:
|
| 72 |
+
# non-quantized inference. More details and quantization examples can be
|
| 73 |
+
# found at: https://github.com/ModelCloud/GPTQModel
|
| 74 |
+
# Example:
|
| 75 |
+
# # last 1/2 of the layers 10-21 has 8bit vs 4bit for 0-9
|
| 76 |
+
# # last 1/4 of the layers 16-21 has 8bit and group_size 64
|
| 77 |
+
# dynamic = {
|
| 78 |
+
# #`.*\.` matches the layers_node prefix
|
| 79 |
+
# # positive match layer 10-15
|
| 80 |
+
# r"+:.*\.(?:1[0-5])\..*": {"bits": 8,},
|
| 81 |
+
# # positive match layer 16-21
|
| 82 |
+
# r"+:.*\.(?:1[6-9]|20|21)\..*": {"bits": 8, "group_size": 64,},
|
| 83 |
+
# r"-:.*\.moe\..*": {}, # negative match (skip) all `moe` layers
|
| 84 |
+
# }
|
| 85 |
+
super().__init__()
|
| 86 |
+
self.dynamic = dynamic
|
| 87 |
+
|
| 88 |
+
self.weight_bits = weight_bits
|
| 89 |
+
self.group_size = group_size
|
| 90 |
+
self.desc_act = desc_act
|
| 91 |
+
self.lm_head_quantized = lm_head_quantized
|
| 92 |
+
self.pack_factor = Fraction(32, self.weight_bits)
|
| 93 |
+
if self.weight_bits not in [2, 3, 4, 8]:
|
| 94 |
+
raise ValueError(
|
| 95 |
+
"Currently, only 2/3/4/8-bit weight quantization is "
|
| 96 |
+
f"supported for GPTQ, but got {self.weight_bits} bits."
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
def __repr__(self) -> str:
|
| 100 |
+
return (
|
| 101 |
+
f"GPTQConfig(weight_bits={self.weight_bits}, "
|
| 102 |
+
f"group_size={self.group_size}, "
|
| 103 |
+
f"desc_act={self.desc_act}),"
|
| 104 |
+
f"lm_head_quantized={self.lm_head_quantized}), "
|
| 105 |
+
f"dynamic={self.dynamic}"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 109 |
+
"""Returns the activation function names that should be post-scaled.
|
| 110 |
+
|
| 111 |
+
For now, this is only used by AWQ.
|
| 112 |
+
"""
|
| 113 |
+
raise NotImplementedError
|
| 114 |
+
|
| 115 |
+
@classmethod
|
| 116 |
+
def get_name(cls) -> str:
|
| 117 |
+
return "gptq"
|
| 118 |
+
|
| 119 |
+
@classmethod
|
| 120 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 121 |
+
return [torch.half]
|
| 122 |
+
|
| 123 |
+
@classmethod
|
| 124 |
+
# Need to figure it out
|
| 125 |
+
def get_min_capability(cls) -> int:
|
| 126 |
+
return 60
|
| 127 |
+
|
| 128 |
+
@classmethod
|
| 129 |
+
def get_config_filenames(cls) -> List[str]:
|
| 130 |
+
return ["quantize_config.json"]
|
| 131 |
+
|
| 132 |
+
@classmethod
|
| 133 |
+
def from_config(cls, config: Dict[str, Any]) -> "GPTQConfig":
|
| 134 |
+
dynamic = cls.get_from_keys_or(config, ["dynamic"], default={})
|
| 135 |
+
dynamic = {} if dynamic is None else dynamic
|
| 136 |
+
|
| 137 |
+
weight_bits = cls.get_from_keys(config, ["bits"])
|
| 138 |
+
group_size = cls.get_from_keys(config, ["group_size"])
|
| 139 |
+
desc_act = cls.get_from_keys(config, ["desc_act"])
|
| 140 |
+
lm_head_quantized = cls.get_from_keys_or(config, ["lm_head"], default=False)
|
| 141 |
+
return cls(weight_bits, group_size, desc_act, lm_head_quantized, dynamic)
|
| 142 |
+
|
| 143 |
+
def get_quant_method(
|
| 144 |
+
self, layer: torch.nn.Module, prefix: str
|
| 145 |
+
) -> Optional[GPTQLinearMethod]:
|
| 146 |
+
# Delay the import to avoid circular dependency
|
| 147 |
+
from sglang.srt.layers.quantization import get_linear_quant_method
|
| 148 |
+
|
| 149 |
+
return get_linear_quant_method(self, layer, prefix, GPTQLinearMethod)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class GPTQMarlinConfig(QuantizationConfig):
|
| 153 |
+
"""Config class for GPTQ Marlin"""
|
| 154 |
+
|
| 155 |
+
# (num_bits, is_sym) -> quant_type
|
| 156 |
+
TYPE_MAP = {
|
| 157 |
+
(4, True): scalar_types.uint4b8,
|
| 158 |
+
(8, True): scalar_types.uint8b128,
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
def __init__(
|
| 162 |
+
self,
|
| 163 |
+
weight_bits: int,
|
| 164 |
+
group_size: int,
|
| 165 |
+
desc_act: bool,
|
| 166 |
+
is_sym: bool,
|
| 167 |
+
lm_head_quantized: bool,
|
| 168 |
+
dynamic: Dict[str, Dict[str, Union[int, bool]]],
|
| 169 |
+
full_config: Dict[str, Any],
|
| 170 |
+
) -> None:
|
| 171 |
+
super().__init__()
|
| 172 |
+
if desc_act and group_size == -1:
|
| 173 |
+
# In this case, act_order == True is the same as act_order == False
|
| 174 |
+
# (since we have only one group per output channel)
|
| 175 |
+
desc_act = False
|
| 176 |
+
|
| 177 |
+
# GPTQModel use `dynamic` config property to allow per module
|
| 178 |
+
# quantization config so each module can be individually optimized.
|
| 179 |
+
# Format is Dict[str, Dict] where key is a regex string that can
|
| 180 |
+
# perform both positive ("+:" prefixed) or negative ("-:" prefixed)
|
| 181 |
+
# matching of a module.
|
| 182 |
+
# Default to positive match, override base quant config mode, if no
|
| 183 |
+
# prefix is used. Value is in dict format of field key and override
|
| 184 |
+
# value.
|
| 185 |
+
# Negative matching will skip quantization init for this module
|
| 186 |
+
# entirely:
|
| 187 |
+
# non-quantized inference. More details and quantization examples can be
|
| 188 |
+
# found at: https://github.com/ModelCloud/GPTQModel
|
| 189 |
+
# Example:
|
| 190 |
+
# # last 1/2 of the layers 10-21 has 8bit vs 4bit for 0-9
|
| 191 |
+
# # last 1/4 of the layers 16-21 has 8bit and group_size 64
|
| 192 |
+
# dynamic = {
|
| 193 |
+
# #`.*\.` matches the layers_node prefix
|
| 194 |
+
# # positive match layer 10-15
|
| 195 |
+
# r"+:.*\.(?:1[0-5])\..*": {"bits": 8,},
|
| 196 |
+
# # positive match layer 16-21
|
| 197 |
+
# r"+:.*\.(?:1[6-9]|20|21)\..*": {"bits": 8, "group_size": 64,},
|
| 198 |
+
# r"-:.*\.moe\..*": {}, # negative match (skip) all `moe` layers
|
| 199 |
+
# }
|
| 200 |
+
self.dynamic = dynamic
|
| 201 |
+
|
| 202 |
+
self.weight_bits = weight_bits
|
| 203 |
+
self.is_sym = is_sym
|
| 204 |
+
|
| 205 |
+
self.pack_factor = 32 // weight_bits # packed into int32
|
| 206 |
+
self.group_size = group_size
|
| 207 |
+
self.desc_act = desc_act
|
| 208 |
+
self.lm_head_quantized = lm_head_quantized
|
| 209 |
+
self.full_config = full_config
|
| 210 |
+
|
| 211 |
+
if (weight_bits, is_sym) not in self.TYPE_MAP:
|
| 212 |
+
raise ValueError(
|
| 213 |
+
"Unsupported quantization config: " f"bits={weight_bits}, sym={is_sym}"
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
# (num_bits, is_sym) -> quant_type
|
| 217 |
+
self.quant_type = self.TYPE_MAP[(weight_bits, is_sym)]
|
| 218 |
+
|
| 219 |
+
def __repr__(self) -> str:
|
| 220 |
+
return (
|
| 221 |
+
f"GPTQMarlinConfig(quant_type={self.quant_type}, "
|
| 222 |
+
f"group_size={self.group_size}, "
|
| 223 |
+
f"desc_act={self.desc_act}, "
|
| 224 |
+
f"lm_head_quantized={self.lm_head_quantized}), "
|
| 225 |
+
f"dynamic={self.dynamic}"
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 229 |
+
"""Returns the activation function names that should be post-scaled.
|
| 230 |
+
|
| 231 |
+
For now, this is only used by AWQ.
|
| 232 |
+
"""
|
| 233 |
+
raise NotImplementedError
|
| 234 |
+
|
| 235 |
+
@classmethod
|
| 236 |
+
def get_name(cls) -> str:
|
| 237 |
+
return "gptq_marlin"
|
| 238 |
+
|
| 239 |
+
@classmethod
|
| 240 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 241 |
+
return [torch.half, torch.bfloat16]
|
| 242 |
+
|
| 243 |
+
@classmethod
|
| 244 |
+
def get_min_capability(cls) -> int:
|
| 245 |
+
return 80
|
| 246 |
+
|
| 247 |
+
@classmethod
|
| 248 |
+
def get_config_filenames(cls) -> List[str]:
|
| 249 |
+
return ["quantize_config.json"]
|
| 250 |
+
|
| 251 |
+
@classmethod
|
| 252 |
+
def from_config(cls, config: Dict[str, Any]) -> "GPTQMarlinConfig":
|
| 253 |
+
dynamic = cls.get_from_keys_or(config, ["dynamic"], default={})
|
| 254 |
+
dynamic = {} if dynamic is None else dynamic
|
| 255 |
+
|
| 256 |
+
weight_bits = cls.get_from_keys(config, ["bits"])
|
| 257 |
+
group_size = cls.get_from_keys(config, ["group_size"])
|
| 258 |
+
desc_act = cls.get_from_keys(config, ["desc_act"])
|
| 259 |
+
is_sym = cls.get_from_keys(config, ["sym"])
|
| 260 |
+
lm_head_quantized = cls.get_from_keys_or(config, ["lm_head"], default=False)
|
| 261 |
+
return cls(
|
| 262 |
+
weight_bits,
|
| 263 |
+
group_size,
|
| 264 |
+
desc_act,
|
| 265 |
+
is_sym,
|
| 266 |
+
lm_head_quantized,
|
| 267 |
+
dynamic,
|
| 268 |
+
config,
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
@classmethod
|
| 272 |
+
def override_quantization_method(cls, hf_quant_cfg, user_quant) -> Optional[str]:
|
| 273 |
+
is_marlin_format = check_marlin_format(hf_quant_cfg)
|
| 274 |
+
|
| 275 |
+
can_convert = cls.is_gptq_marlin_compatible(hf_quant_cfg)
|
| 276 |
+
|
| 277 |
+
is_valid_user_quant = (
|
| 278 |
+
user_quant is None or user_quant == "marlin" or user_quant == "gptq_marlin"
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
if not is_marlin_format and can_convert and is_valid_user_quant:
|
| 282 |
+
msg = (
|
| 283 |
+
"The model is convertible to {} during runtime."
|
| 284 |
+
" Using {} kernel.".format(cls.get_name(), cls.get_name())
|
| 285 |
+
)
|
| 286 |
+
logger.info(msg)
|
| 287 |
+
return cls.get_name()
|
| 288 |
+
|
| 289 |
+
if not is_marlin_format and can_convert and user_quant == "gptq":
|
| 290 |
+
logger.info(
|
| 291 |
+
"Detected that the model can run with gptq_marlin"
|
| 292 |
+
", however you specified quantization=gptq explicitly,"
|
| 293 |
+
" so forcing gptq. Use quantization=gptq_marlin for"
|
| 294 |
+
" faster inference"
|
| 295 |
+
)
|
| 296 |
+
return None
|
| 297 |
+
|
| 298 |
+
def get_quant_method(
|
| 299 |
+
self, layer: torch.nn.Module, prefix: str
|
| 300 |
+
) -> Optional[QuantizeMethodBase]:
|
| 301 |
+
# Delay the import to avoid circular dependency
|
| 302 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
|
| 303 |
+
from sglang.srt.layers.quantization import get_linear_quant_method
|
| 304 |
+
|
| 305 |
+
if isinstance(layer, FusedMoE):
|
| 306 |
+
return GPTQMarlinMoEMethod(self)
|
| 307 |
+
# TODO: re-enable after SGLang syncs with vllm >= 0.7.3
|
| 308 |
+
# if layer.num_experts > 32:
|
| 309 |
+
# # For MoEs with many experts the moe_wna16 kernel is faster
|
| 310 |
+
# return MoeWNA16Config.from_config(self.full_config).get_quant_method(
|
| 311 |
+
# layer, prefix
|
| 312 |
+
# )
|
| 313 |
+
# else:
|
| 314 |
+
# return GPTQMarlinMoEMethod(self)
|
| 315 |
+
return get_linear_quant_method(self, layer, prefix, GPTQMarlinLinearMethod)
|
| 316 |
+
|
| 317 |
+
@classmethod
|
| 318 |
+
def is_gptq_marlin_compatible(cls, quant_config: Dict[str, Any]):
|
| 319 |
+
quant_method = quant_config.get("quant_method", "").lower()
|
| 320 |
+
num_bits = quant_config.get("bits")
|
| 321 |
+
group_size = quant_config.get("group_size")
|
| 322 |
+
sym = quant_config.get("sym")
|
| 323 |
+
desc_act = quant_config.get("desc_act")
|
| 324 |
+
|
| 325 |
+
if not _is_cuda:
|
| 326 |
+
return False
|
| 327 |
+
|
| 328 |
+
if quant_method != "gptq":
|
| 329 |
+
return False
|
| 330 |
+
|
| 331 |
+
# Marlin conversion is only valid if required properties are found
|
| 332 |
+
if num_bits is None or group_size is None or sym is None or desc_act is None:
|
| 333 |
+
return False
|
| 334 |
+
|
| 335 |
+
if (num_bits, sym) not in cls.TYPE_MAP:
|
| 336 |
+
return False
|
| 337 |
+
|
| 338 |
+
return check_marlin_supported(
|
| 339 |
+
quant_type=cls.TYPE_MAP[(num_bits, sym)], group_size=group_size
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
class MarlinConfig(QuantizationConfig):
|
| 344 |
+
"""Config class for Marlin.
|
| 345 |
+
|
| 346 |
+
Reference: https://github.com/IST-DASLab/marlin/tree/master
|
| 347 |
+
"""
|
| 348 |
+
|
| 349 |
+
def __init__(
|
| 350 |
+
self,
|
| 351 |
+
group_size: int,
|
| 352 |
+
lm_head_quantized: bool,
|
| 353 |
+
) -> None:
|
| 354 |
+
# Group size for the quantization.
|
| 355 |
+
self.group_size = group_size
|
| 356 |
+
self.lm_head_quantized = lm_head_quantized
|
| 357 |
+
if self.group_size != 128 and self.group_size != -1:
|
| 358 |
+
raise ValueError(
|
| 359 |
+
"Currently, only group size 128 and -1 (channelwise) "
|
| 360 |
+
"is supported for Marlin, but got group_size of "
|
| 361 |
+
f"{self.group_size}"
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
# 4 Bits packed into 32 bit datatype.
|
| 365 |
+
self.pack_factor = 32 // 4
|
| 366 |
+
|
| 367 |
+
# Tile size used by marlin kernels.
|
| 368 |
+
self.tile_size = 16
|
| 369 |
+
|
| 370 |
+
# Min out_features dim
|
| 371 |
+
self.min_n_threads = 64
|
| 372 |
+
|
| 373 |
+
# Min in_features dim
|
| 374 |
+
self.min_k_threads = 128
|
| 375 |
+
|
| 376 |
+
# Max parallel problems to solve at once (improves large
|
| 377 |
+
# batch performance)
|
| 378 |
+
self.max_parallel = 16
|
| 379 |
+
|
| 380 |
+
# Permutation length used by the marlin kernels.
|
| 381 |
+
self.perm_len = 1024
|
| 382 |
+
|
| 383 |
+
def __repr__(self) -> str:
|
| 384 |
+
return (
|
| 385 |
+
f"MarlinConfig(group_size={self.group_size}, "
|
| 386 |
+
f"lm_head_quantized={self.lm_head_quantized})"
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
@classmethod
|
| 390 |
+
def get_name(cls) -> str:
|
| 391 |
+
return "marlin"
|
| 392 |
+
|
| 393 |
+
@classmethod
|
| 394 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 395 |
+
return [torch.half]
|
| 396 |
+
|
| 397 |
+
@classmethod
|
| 398 |
+
# Need to figure it out
|
| 399 |
+
def get_min_capability(cls) -> int:
|
| 400 |
+
return 80
|
| 401 |
+
|
| 402 |
+
@classmethod
|
| 403 |
+
def get_config_filenames(cls) -> List[str]:
|
| 404 |
+
return ["quantize_config.json"]
|
| 405 |
+
|
| 406 |
+
@classmethod
|
| 407 |
+
def from_config(cls, config: Dict[str, Any]) -> "MarlinConfig":
|
| 408 |
+
group_size = cls.get_from_keys(config, ["group_size"])
|
| 409 |
+
lm_head_quantized = cls.get_from_keys_or(config, ["lm_head"], default=False)
|
| 410 |
+
return cls(group_size, lm_head_quantized)
|
| 411 |
+
|
| 412 |
+
@classmethod
|
| 413 |
+
def override_quantization_method(cls, hf_quant_cfg, user_quant) -> Optional[str]:
|
| 414 |
+
is_marlin_format = check_marlin_format(hf_quant_cfg)
|
| 415 |
+
|
| 416 |
+
is_valid_user_quant = (
|
| 417 |
+
user_quant is None or user_quant == "gptq" or user_quant == "marlin"
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
if is_marlin_format and is_valid_user_quant:
|
| 421 |
+
msg = "The model is serialized in {} format. Using {} kernel.".format(
|
| 422 |
+
cls.get_name(), cls.get_name()
|
| 423 |
+
)
|
| 424 |
+
logger.info(msg)
|
| 425 |
+
return cls.get_name()
|
| 426 |
+
|
| 427 |
+
return None
|
| 428 |
+
|
| 429 |
+
def get_quant_method(
|
| 430 |
+
self, layer: torch.nn.Module, prefix: str
|
| 431 |
+
) -> Optional[MarlinLinearMethod]:
|
| 432 |
+
# Delay the import to avoid circular dependency
|
| 433 |
+
from sglang.srt.layers.vocab_parallel_embedding import ParallelLMHead
|
| 434 |
+
|
| 435 |
+
if isinstance(layer, LinearBase) or (
|
| 436 |
+
isinstance(layer, ParallelLMHead) and self.lm_head_quantized
|
| 437 |
+
):
|
| 438 |
+
return MarlinLinearMethod(self)
|
| 439 |
+
return None
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/int8_kernel.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import functools
|
| 2 |
+
import json
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import triton
|
| 9 |
+
import triton.language as tl
|
| 10 |
+
|
| 11 |
+
from sglang.srt.utils import get_device_name, is_cuda
|
| 12 |
+
|
| 13 |
+
_is_cuda = is_cuda()
|
| 14 |
+
if _is_cuda:
|
| 15 |
+
from sgl_kernel import sgl_per_token_group_quant_int8
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@triton.jit
|
| 21 |
+
def _per_token_quant_int8(
|
| 22 |
+
x_ptr,
|
| 23 |
+
xq_ptr,
|
| 24 |
+
scale_ptr,
|
| 25 |
+
stride_x,
|
| 26 |
+
stride_xq,
|
| 27 |
+
N,
|
| 28 |
+
BLOCK: tl.constexpr,
|
| 29 |
+
):
|
| 30 |
+
# Adapted from https://github.com/InternLM/lmdeploy/blob/086481ed84b59bee3b8e4274e5fc69620040c048/lmdeploy/pytorch/kernels/cuda/w8a8_triton_kernels.py#L282
|
| 31 |
+
row_id = tl.program_id(0)
|
| 32 |
+
|
| 33 |
+
cols = tl.arange(0, BLOCK)
|
| 34 |
+
mask = cols < N
|
| 35 |
+
|
| 36 |
+
x = tl.load(x_ptr + row_id * stride_x + cols, mask=mask, other=0.0).to(tl.float32)
|
| 37 |
+
absmax = tl.maximum(tl.max(tl.abs(x)), 1e-10)
|
| 38 |
+
scale_x = absmax / 127
|
| 39 |
+
x_q = x * (127 / absmax)
|
| 40 |
+
x_q = tl.extra.cuda.libdevice.round(x_q).to(tl.int8)
|
| 41 |
+
|
| 42 |
+
tl.store(xq_ptr + row_id * stride_xq + cols, x_q, mask=mask)
|
| 43 |
+
tl.store(scale_ptr + row_id, scale_x)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def per_token_quant_int8(x):
|
| 47 |
+
M = x.numel() // x.shape[-1]
|
| 48 |
+
N = x.shape[-1]
|
| 49 |
+
x_q = torch.empty_like(x, device=x.device, dtype=torch.int8)
|
| 50 |
+
scales = torch.empty(x.shape[:-1] + (1,), device=x.device, dtype=torch.float32)
|
| 51 |
+
BLOCK = triton.next_power_of_2(N)
|
| 52 |
+
# heuristics for number of warps
|
| 53 |
+
num_warps = min(max(BLOCK // 256, 1), 8)
|
| 54 |
+
|
| 55 |
+
assert x.is_contiguous()
|
| 56 |
+
_per_token_quant_int8[(M,)](
|
| 57 |
+
x,
|
| 58 |
+
x_q,
|
| 59 |
+
scales,
|
| 60 |
+
stride_x=x.stride(-2),
|
| 61 |
+
stride_xq=x_q.stride(-2),
|
| 62 |
+
N=N,
|
| 63 |
+
BLOCK=BLOCK,
|
| 64 |
+
num_warps=num_warps,
|
| 65 |
+
num_stages=1,
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
return x_q, scales
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@triton.jit
|
| 72 |
+
def _per_token_group_quant_int8(
|
| 73 |
+
# Pointers to inputs and output
|
| 74 |
+
y_ptr,
|
| 75 |
+
y_q_ptr,
|
| 76 |
+
y_s_ptr,
|
| 77 |
+
# Stride of input
|
| 78 |
+
y_stride,
|
| 79 |
+
# Collums of input
|
| 80 |
+
N,
|
| 81 |
+
# Avoid to divide zero
|
| 82 |
+
eps,
|
| 83 |
+
# Information for int8
|
| 84 |
+
int8_min,
|
| 85 |
+
int8_max,
|
| 86 |
+
# Meta-parameters
|
| 87 |
+
BLOCK: tl.constexpr,
|
| 88 |
+
):
|
| 89 |
+
"""A Triton-accelerated function to perform per-token-group quantization on a
|
| 90 |
+
tensor.
|
| 91 |
+
|
| 92 |
+
This function converts the tensor values into int8 values.
|
| 93 |
+
"""
|
| 94 |
+
# Map the program id to the row of X and Y it should compute.
|
| 95 |
+
g_id = tl.program_id(0)
|
| 96 |
+
y_ptr += g_id * y_stride
|
| 97 |
+
y_q_ptr += g_id * y_stride
|
| 98 |
+
y_s_ptr += g_id
|
| 99 |
+
|
| 100 |
+
cols = tl.arange(0, BLOCK) # N <= BLOCK
|
| 101 |
+
mask = cols < N
|
| 102 |
+
|
| 103 |
+
y = tl.load(y_ptr + cols, mask=mask, other=0.0).to(tl.float32)
|
| 104 |
+
# Quant
|
| 105 |
+
_absmax = tl.maximum(tl.max(tl.abs(y)), eps)
|
| 106 |
+
y_s = _absmax / int8_max
|
| 107 |
+
y_q = tl.clamp(y / y_s, int8_min, int8_max).to(y_q_ptr.dtype.element_ty)
|
| 108 |
+
|
| 109 |
+
tl.store(y_q_ptr + cols, y_q, mask=mask)
|
| 110 |
+
tl.store(y_s_ptr, y_s)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def per_token_group_quant_int8(
|
| 114 |
+
x: torch.Tensor,
|
| 115 |
+
group_size: int,
|
| 116 |
+
eps: float = 1e-10,
|
| 117 |
+
dtype: torch.dtype = torch.int8,
|
| 118 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 119 |
+
"""Function to perform per-token-group quantization on an input tensor `x`.
|
| 120 |
+
|
| 121 |
+
It converts the tensor values into signed int8 values and returns the
|
| 122 |
+
quantized tensor along with the scaling factor used for quantization.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
x: The input tenosr with ndim >= 2.
|
| 126 |
+
group_size: The group size used for quantization.
|
| 127 |
+
eps: The minimum to avoid dividing zero.
|
| 128 |
+
dtype: The dype of output tensor. Note that only `torch.int8` is supported for now.
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
Tuple[torch.Tensor, torch.Tensor]: The quantized tensor and the scaling factor for quantization.
|
| 132 |
+
"""
|
| 133 |
+
assert (
|
| 134 |
+
x.shape[-1] % group_size == 0
|
| 135 |
+
), "the last dimension of `x` cannot be divisible by `group_size`"
|
| 136 |
+
assert x.is_contiguous(), "`x` is not contiguous"
|
| 137 |
+
|
| 138 |
+
iinfo = torch.iinfo(dtype)
|
| 139 |
+
int8_max = iinfo.max
|
| 140 |
+
int8_min = iinfo.min
|
| 141 |
+
|
| 142 |
+
x_q = torch.empty_like(x, device=x.device, dtype=dtype)
|
| 143 |
+
M = x.numel() // group_size
|
| 144 |
+
N = group_size
|
| 145 |
+
x_s = torch.empty(
|
| 146 |
+
x.shape[:-1] + (x.shape[-1] // group_size,),
|
| 147 |
+
device=x.device,
|
| 148 |
+
dtype=torch.float32,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
BLOCK = triton.next_power_of_2(N)
|
| 152 |
+
# heuristics for number of warps
|
| 153 |
+
num_warps = min(max(BLOCK // 256, 1), 8)
|
| 154 |
+
num_stages = 1
|
| 155 |
+
_per_token_group_quant_int8[(M,)](
|
| 156 |
+
x,
|
| 157 |
+
x_q,
|
| 158 |
+
x_s,
|
| 159 |
+
group_size,
|
| 160 |
+
N,
|
| 161 |
+
eps,
|
| 162 |
+
int8_min=int8_min,
|
| 163 |
+
int8_max=int8_max,
|
| 164 |
+
BLOCK=BLOCK,
|
| 165 |
+
num_warps=num_warps,
|
| 166 |
+
num_stages=num_stages,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
return x_q, x_s
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def sglang_per_token_group_quant_int8(
|
| 173 |
+
x: torch.Tensor,
|
| 174 |
+
group_size: int,
|
| 175 |
+
eps: float = 1e-10,
|
| 176 |
+
dtype: torch.dtype = torch.int8,
|
| 177 |
+
):
|
| 178 |
+
assert (
|
| 179 |
+
x.shape[-1] % group_size == 0
|
| 180 |
+
), "the last dimension of `x` cannot be divisible by `group_size`"
|
| 181 |
+
assert x.is_contiguous(), "`x` is not contiguous"
|
| 182 |
+
|
| 183 |
+
iinfo = torch.iinfo(dtype)
|
| 184 |
+
int8_max = iinfo.max
|
| 185 |
+
int8_min = iinfo.min
|
| 186 |
+
|
| 187 |
+
x_q = torch.empty_like(x, device=x.device, dtype=dtype)
|
| 188 |
+
x_s = torch.empty(
|
| 189 |
+
x.shape[:-1] + (x.shape[-1] // group_size,),
|
| 190 |
+
device=x.device,
|
| 191 |
+
dtype=torch.float32,
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
sgl_per_token_group_quant_int8(x, x_q, x_s, group_size, eps, int8_min, int8_max)
|
| 195 |
+
|
| 196 |
+
return x_q, x_s
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
@triton.jit
|
| 200 |
+
def _w8a8_block_int8_matmul(
|
| 201 |
+
# Pointers to inputs and output
|
| 202 |
+
A,
|
| 203 |
+
B,
|
| 204 |
+
C,
|
| 205 |
+
As,
|
| 206 |
+
Bs,
|
| 207 |
+
# Shape for matmul
|
| 208 |
+
M,
|
| 209 |
+
N,
|
| 210 |
+
K,
|
| 211 |
+
# Block size for block-wise quantization
|
| 212 |
+
group_n,
|
| 213 |
+
group_k,
|
| 214 |
+
# Stride for inputs and output
|
| 215 |
+
stride_am,
|
| 216 |
+
stride_ak,
|
| 217 |
+
stride_bk,
|
| 218 |
+
stride_bn,
|
| 219 |
+
stride_cm,
|
| 220 |
+
stride_cn,
|
| 221 |
+
stride_As_m,
|
| 222 |
+
stride_As_k,
|
| 223 |
+
stride_Bs_k,
|
| 224 |
+
stride_Bs_n,
|
| 225 |
+
# Meta-parameters
|
| 226 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 227 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 228 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 229 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 230 |
+
):
|
| 231 |
+
"""Triton-accelerated function used to perform linear operations (dot
|
| 232 |
+
product) on input tensors `A` and `B` with block-wise quantization, and store the result in output
|
| 233 |
+
tensor `C`.
|
| 234 |
+
"""
|
| 235 |
+
|
| 236 |
+
pid = tl.program_id(axis=0)
|
| 237 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 238 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 239 |
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
| 240 |
+
group_id = pid // num_pid_in_group
|
| 241 |
+
first_pid_m = group_id * GROUP_SIZE_M
|
| 242 |
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
| 243 |
+
pid_m = first_pid_m + (pid % group_size_m)
|
| 244 |
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
| 245 |
+
|
| 246 |
+
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 247 |
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 248 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 249 |
+
a_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
|
| 250 |
+
b_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 251 |
+
|
| 252 |
+
As_ptrs = As + offs_am * stride_As_m
|
| 253 |
+
offs_bsn = offs_bn // group_n
|
| 254 |
+
Bs_ptrs = Bs + offs_bsn * stride_Bs_n
|
| 255 |
+
|
| 256 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 257 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 258 |
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)
|
| 259 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
|
| 260 |
+
|
| 261 |
+
k_start = k * BLOCK_SIZE_K
|
| 262 |
+
offs_ks = k_start // group_k
|
| 263 |
+
a_s = tl.load(As_ptrs + offs_ks * stride_As_k)
|
| 264 |
+
b_s = tl.load(Bs_ptrs + offs_ks * stride_Bs_k)
|
| 265 |
+
|
| 266 |
+
accumulator += tl.dot(a, b).to(tl.float32) * a_s[:, None] * b_s[None, :]
|
| 267 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 268 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 269 |
+
|
| 270 |
+
if C.dtype.element_ty == tl.bfloat16:
|
| 271 |
+
c = accumulator.to(tl.bfloat16)
|
| 272 |
+
elif C.dtype.element_ty == tl.float16:
|
| 273 |
+
c = accumulator.to(tl.float16)
|
| 274 |
+
else:
|
| 275 |
+
c = accumulator.to(tl.float32)
|
| 276 |
+
|
| 277 |
+
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 278 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 279 |
+
c_ptrs = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
| 280 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
| 281 |
+
tl.store(c_ptrs, c, mask=c_mask)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
@functools.lru_cache
|
| 285 |
+
def get_w8a8_block_int8_configs(
|
| 286 |
+
N: int, K: int, block_n: int, block_k: int
|
| 287 |
+
) -> Optional[Dict[int, Any]]:
|
| 288 |
+
"""
|
| 289 |
+
Return optimized configurations for the w8a8 block fp8 kernel.
|
| 290 |
+
|
| 291 |
+
The return value will be a dictionary that maps an irregular grid of
|
| 292 |
+
batch sizes to configurations of the w8a8 block fp8 kernel. To evaluate the
|
| 293 |
+
kernel on a given batch size bs, the closest batch size in the grid should
|
| 294 |
+
be picked and the associated configuration chosen to invoke the kernel.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
# First look up if an optimized configuration is available in the configs
|
| 298 |
+
# directory
|
| 299 |
+
device_name = get_device_name().replace(" ", "_")
|
| 300 |
+
json_file_name = f"N={N},K={K},device_name={device_name},dtype=int8_w8a8,block_shape=[{block_n}, {block_k}].json"
|
| 301 |
+
|
| 302 |
+
config_file_path = os.path.join(
|
| 303 |
+
os.path.dirname(os.path.realpath(__file__)), "configs", json_file_name
|
| 304 |
+
)
|
| 305 |
+
if os.path.exists(config_file_path):
|
| 306 |
+
with open(config_file_path) as f:
|
| 307 |
+
logger.info(
|
| 308 |
+
"Using configuration from %s for W8A8 Block INT8 kernel.",
|
| 309 |
+
config_file_path,
|
| 310 |
+
)
|
| 311 |
+
# If a configuration has been found, return it
|
| 312 |
+
return {int(key): val for key, val in json.load(f).items()}
|
| 313 |
+
|
| 314 |
+
# If no optimized configuration is available, we will use the default
|
| 315 |
+
# configuration
|
| 316 |
+
logger.warning(
|
| 317 |
+
(
|
| 318 |
+
"Using default W8A8 Block INT8 kernel config. Performance might be sub-optimal! "
|
| 319 |
+
"Config file not found at %s"
|
| 320 |
+
),
|
| 321 |
+
config_file_path,
|
| 322 |
+
)
|
| 323 |
+
return None
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def w8a8_block_int8_matmul(
|
| 327 |
+
A: torch.Tensor,
|
| 328 |
+
B: torch.Tensor,
|
| 329 |
+
As: torch.Tensor,
|
| 330 |
+
Bs: torch.Tensor,
|
| 331 |
+
block_size: List[int],
|
| 332 |
+
output_dtype: torch.dtype = torch.float16,
|
| 333 |
+
) -> torch.Tensor:
|
| 334 |
+
"""This function performs matrix multiplication with block-wise quantization.
|
| 335 |
+
|
| 336 |
+
It takes two input tensors `A` and `B` with scales `As` and `Bs`.
|
| 337 |
+
The output is returned in the specified `output_dtype`.
|
| 338 |
+
|
| 339 |
+
Args:
|
| 340 |
+
A: The input tensor, e.g., activation.
|
| 341 |
+
B: The input tensor, e.g., weight.
|
| 342 |
+
As: The per-token-group quantization scale for `A`.
|
| 343 |
+
Bs: The per-block quantization scale for `B`.
|
| 344 |
+
block_size: The block size for per-block quantization. It should be 2-dim, e.g., [128, 128].
|
| 345 |
+
output_dytpe: The dtype of the returned tensor.
|
| 346 |
+
|
| 347 |
+
Returns:
|
| 348 |
+
torch.Tensor: The result of matmul.
|
| 349 |
+
"""
|
| 350 |
+
assert len(block_size) == 2
|
| 351 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 352 |
+
|
| 353 |
+
assert A.shape[-1] == B.shape[-1]
|
| 354 |
+
assert A.shape[:-1] == As.shape[:-1] and A.is_contiguous()
|
| 355 |
+
assert triton.cdiv(A.shape[-1], block_k) == As.shape[-1]
|
| 356 |
+
M = A.numel() // A.shape[-1]
|
| 357 |
+
|
| 358 |
+
assert B.ndim == 2 and B.is_contiguous() and Bs.ndim == 2
|
| 359 |
+
N, K = B.shape
|
| 360 |
+
assert triton.cdiv(N, block_n) == Bs.shape[0]
|
| 361 |
+
assert triton.cdiv(K, block_k) == Bs.shape[1]
|
| 362 |
+
|
| 363 |
+
C_shape = A.shape[:-1] + (N,)
|
| 364 |
+
C = A.new_empty(C_shape, dtype=output_dtype)
|
| 365 |
+
|
| 366 |
+
configs = get_w8a8_block_int8_configs(N, K, block_size[0], block_size[1])
|
| 367 |
+
if configs:
|
| 368 |
+
# If an optimal configuration map has been found, look up the
|
| 369 |
+
# optimal config
|
| 370 |
+
config = configs[min(configs.keys(), key=lambda x: abs(x - M))]
|
| 371 |
+
else:
|
| 372 |
+
# Default config
|
| 373 |
+
# Block-wise quant: BLOCK_SIZE_K must be divisable by block_size[1]
|
| 374 |
+
config = {
|
| 375 |
+
"BLOCK_SIZE_M": 64,
|
| 376 |
+
"BLOCK_SIZE_N": block_size[0],
|
| 377 |
+
"BLOCK_SIZE_K": block_size[1],
|
| 378 |
+
"GROUP_SIZE_M": 32,
|
| 379 |
+
"num_warps": 4,
|
| 380 |
+
"num_stages": 3,
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
def grid(META):
|
| 384 |
+
return (
|
| 385 |
+
triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
_w8a8_block_int8_matmul[grid](
|
| 389 |
+
A,
|
| 390 |
+
B,
|
| 391 |
+
C,
|
| 392 |
+
As,
|
| 393 |
+
Bs,
|
| 394 |
+
M,
|
| 395 |
+
N,
|
| 396 |
+
K,
|
| 397 |
+
block_n,
|
| 398 |
+
block_k,
|
| 399 |
+
A.stride(-2),
|
| 400 |
+
A.stride(-1),
|
| 401 |
+
B.stride(1),
|
| 402 |
+
B.stride(0),
|
| 403 |
+
C.stride(-2),
|
| 404 |
+
C.stride(-1),
|
| 405 |
+
As.stride(-2),
|
| 406 |
+
As.stride(-1),
|
| 407 |
+
Bs.stride(1),
|
| 408 |
+
Bs.stride(0),
|
| 409 |
+
**config,
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
return C
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/int8_utils.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Optional, Tuple
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from sglang.srt.layers.quantization.int8_kernel import (
|
| 6 |
+
per_token_group_quant_int8,
|
| 7 |
+
w8a8_block_int8_matmul,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def apply_w8a8_block_int8_linear(
|
| 12 |
+
input: torch.Tensor,
|
| 13 |
+
weight: torch.Tensor,
|
| 14 |
+
block_size: List[int],
|
| 15 |
+
weight_scale: torch.Tensor,
|
| 16 |
+
input_scale: Optional[torch.Tensor] = None,
|
| 17 |
+
bias: Optional[torch.Tensor] = None,
|
| 18 |
+
) -> torch.Tensor:
|
| 19 |
+
assert input_scale is None
|
| 20 |
+
# View input as 2D matrix for fp8 methods
|
| 21 |
+
input_2d = input.view(-1, input.shape[-1])
|
| 22 |
+
output_shape = [*input.shape[:-1], weight.shape[0]]
|
| 23 |
+
|
| 24 |
+
q_input, x_scale = per_token_group_quant_int8(input_2d, block_size[1])
|
| 25 |
+
output = w8a8_block_int8_matmul(
|
| 26 |
+
q_input, weight, x_scale, weight_scale, block_size, output_dtype=input.dtype
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
if bias is not None:
|
| 30 |
+
output = output + bias
|
| 31 |
+
return output.to(dtype=input.dtype).view(*output_shape)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def input_to_int8(
|
| 35 |
+
x: torch.Tensor, dtype: torch.dtype = torch.int8
|
| 36 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 37 |
+
"""This function quantizes input values to int8 values with tensor-wise quantization."""
|
| 38 |
+
iinfo = torch.iinfo(dtype)
|
| 39 |
+
min_val, max_val = x.aminmax()
|
| 40 |
+
amax = torch.maximum(min_val.abs(), max_val.abs()).clamp(min=1e-12)
|
| 41 |
+
int8_min, int8_max = iinfo.min, iinfo.max
|
| 42 |
+
scale = int8_max / amax
|
| 43 |
+
x_scl_sat = (x * scale).clamp(min=int8_min, max=int8_max)
|
| 44 |
+
return x_scl_sat.to(dtype).contiguous(), scale.float().reciprocal()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def block_dequant(
|
| 48 |
+
x_q_block: torch.Tensor,
|
| 49 |
+
x_s: torch.Tensor,
|
| 50 |
+
block_size: List[int],
|
| 51 |
+
) -> torch.Tensor:
|
| 52 |
+
"""This function conducts block-wise dequantization.
|
| 53 |
+
The inputs are block-wise quantization tensor `x_q_block`, block-wise quantization scale
|
| 54 |
+
and the block size.
|
| 55 |
+
The outputs are dequantized tensor.
|
| 56 |
+
"""
|
| 57 |
+
block_n, block_k = block_size[0], block_size[1]
|
| 58 |
+
n, k = x_q_block.shape
|
| 59 |
+
n_tiles = (n + block_n - 1) // block_n
|
| 60 |
+
k_tiles = (k + block_k - 1) // block_k
|
| 61 |
+
assert n_tiles == x_s.shape[0]
|
| 62 |
+
assert k_tiles == x_s.shape[1]
|
| 63 |
+
|
| 64 |
+
x_dq_block = x_q_block.to(torch.float32)
|
| 65 |
+
|
| 66 |
+
for i in range(k_tiles):
|
| 67 |
+
for j in range(n_tiles):
|
| 68 |
+
x_dq_block[
|
| 69 |
+
j * block_n : min((j + 1) * block_n, n),
|
| 70 |
+
i * block_k : min((i + 1) * block_k, k),
|
| 71 |
+
] *= x_s[j][i]
|
| 72 |
+
|
| 73 |
+
return x_dq_block
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/kv_cache.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/quantization/kv_cache.py
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 8 |
+
QuantizationConfig,
|
| 9 |
+
QuantizeMethodBase,
|
| 10 |
+
)
|
| 11 |
+
from sglang.srt.layers.radix_attention import RadixAttention
|
| 12 |
+
from sglang.srt.utils import is_hip
|
| 13 |
+
|
| 14 |
+
_is_hip = is_hip()
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class BaseKVCacheMethod(QuantizeMethodBase):
|
| 20 |
+
"""
|
| 21 |
+
Quant method that adds `k_scale` and `v_scale` attributes to the
|
| 22 |
+
Attention layer to support loading those scaling factors from checkpoints.
|
| 23 |
+
The k/v_scale will be used to:
|
| 24 |
+
- quantize k/v_cache entries before saving them to the cache
|
| 25 |
+
- dequantize k/v_cache entries before fetching them from the cache
|
| 26 |
+
|
| 27 |
+
:param quant_config: the appropriate QuantizationConfig
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, quant_config: QuantizationConfig):
|
| 31 |
+
self.quant_config = quant_config
|
| 32 |
+
|
| 33 |
+
def create_weights(self, layer: torch.nn.Module):
|
| 34 |
+
"""
|
| 35 |
+
Create "weight" (aka k_scale and v_scale) for an attention layer.
|
| 36 |
+
"""
|
| 37 |
+
# Initialize the KV cache scales to -1.0, which is an invalid value.
|
| 38 |
+
# If the k/v_scale appears in the checkpoint, it will be
|
| 39 |
+
# overwritten when loading weights.
|
| 40 |
+
layer.k_scale = torch.nn.Parameter(
|
| 41 |
+
torch.tensor(-1.0, dtype=torch.float32), requires_grad=False
|
| 42 |
+
)
|
| 43 |
+
layer.v_scale = torch.nn.Parameter(
|
| 44 |
+
torch.tensor(-1.0, dtype=torch.float32), requires_grad=False
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
@classmethod
|
| 48 |
+
def is_fp8_fnuz(cls) -> bool:
|
| 49 |
+
# only device 0 is checked, this assumes MI300 platforms are homogeneous
|
| 50 |
+
return "gfx94" in torch.cuda.get_device_properties(0).gcnArchName
|
| 51 |
+
|
| 52 |
+
def apply(self, layer: torch.nn.Module) -> torch.Tensor:
|
| 53 |
+
raise RuntimeError(f"{self.__class__.__name__}.apply should not be called.")
|
| 54 |
+
|
| 55 |
+
def process_weights_after_loading(self, layer: RadixAttention) -> None:
|
| 56 |
+
if layer.k_scale > 0.0 and layer.v_scale > 0.0:
|
| 57 |
+
# We prefer to use separate k_scale and v_scale if present
|
| 58 |
+
k_scale = layer.k_scale.to("cpu").tolist()
|
| 59 |
+
v_scale = layer.v_scale.to("cpu").tolist()
|
| 60 |
+
if _is_hip and self.is_fp8_fnuz():
|
| 61 |
+
k_scale *= 2
|
| 62 |
+
v_scale *= 2
|
| 63 |
+
elif layer.k_scale < 0.0 and layer.v_scale < 0.0:
|
| 64 |
+
# If no scales were loaded (both scales are invalid negative
|
| 65 |
+
# values), use the default value of 1.0
|
| 66 |
+
k_scale = 1.0
|
| 67 |
+
v_scale = 1.0
|
| 68 |
+
else:
|
| 69 |
+
# If we find a single kv_scale in the checkpoint, we remap
|
| 70 |
+
# kv_scale to k_scale during weight loading, and duplicate
|
| 71 |
+
# k_scale to v_scale here
|
| 72 |
+
assert layer.k_scale > 0.0
|
| 73 |
+
scale_to_duplicate = max(layer.k_scale, layer.v_scale)
|
| 74 |
+
k_scale = scale_to_duplicate.to("cpu").tolist()
|
| 75 |
+
v_scale = scale_to_duplicate.to("cpu").tolist()
|
| 76 |
+
if _is_hip and self.is_fp8_fnuz():
|
| 77 |
+
k_scale *= 2
|
| 78 |
+
v_scale *= 2
|
| 79 |
+
|
| 80 |
+
if not isinstance(k_scale, float) or not isinstance(v_scale, float):
|
| 81 |
+
raise ValueError(
|
| 82 |
+
"Only support per-tensor scaling factor " "for fp8 KV cache"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
# These are used in the final Attention.forward()
|
| 86 |
+
layer.k_scale.copy_(k_scale)
|
| 87 |
+
layer.v_scale.copy_(v_scale)
|
| 88 |
+
layer.k_scale_float = k_scale
|
| 89 |
+
layer.v_scale_float = v_scale
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/modelopt_quant.py
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/quantization/modelopt.py
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Any, Dict, List, Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch.nn.parameter import Parameter
|
| 8 |
+
|
| 9 |
+
from sglang.srt.layers.linear import LinearBase, LinearMethodBase
|
| 10 |
+
from sglang.srt.layers.parameter import ModelWeightParameter, PerTensorScaleParameter
|
| 11 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 12 |
+
QuantizationConfig,
|
| 13 |
+
QuantizeMethodBase,
|
| 14 |
+
)
|
| 15 |
+
from sglang.srt.layers.quantization.fp8_utils import (
|
| 16 |
+
apply_fp8_linear,
|
| 17 |
+
cutlass_fp8_supported,
|
| 18 |
+
)
|
| 19 |
+
from sglang.srt.layers.quantization.kv_cache import BaseKVCacheMethod
|
| 20 |
+
from sglang.srt.layers.quantization.utils import (
|
| 21 |
+
convert_to_channelwise,
|
| 22 |
+
requantize_with_max_scale,
|
| 23 |
+
)
|
| 24 |
+
from sglang.srt.layers.radix_attention import RadixAttention
|
| 25 |
+
from sglang.srt.utils import is_cuda
|
| 26 |
+
|
| 27 |
+
if is_cuda():
|
| 28 |
+
from sgl_kernel import cutlass_scaled_fp4_mm, scaled_fp4_quant
|
| 29 |
+
|
| 30 |
+
# Initialize logger for the module
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
# Supported activation schemes for the current configuration
|
| 34 |
+
ACTIVATION_SCHEMES = ["static"]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class ModelOptFp8Config(QuantizationConfig):
|
| 38 |
+
"""Configuration for ModelOpt FP8 quantization, including serialization and compatibility checks."""
|
| 39 |
+
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
is_checkpoint_fp8_serialized: bool = False,
|
| 43 |
+
kv_cache_quant_method: Optional[str] = None,
|
| 44 |
+
exclude_modules: Optional[List[str]] = None,
|
| 45 |
+
) -> None:
|
| 46 |
+
"""
|
| 47 |
+
Args:
|
| 48 |
+
is_checkpoint_fp8_serialized (bool): Indicates if the checkpoint uses serialized FP8 format.
|
| 49 |
+
"""
|
| 50 |
+
self.is_checkpoint_fp8_serialized = is_checkpoint_fp8_serialized
|
| 51 |
+
self.kv_cache_quant_method = kv_cache_quant_method
|
| 52 |
+
self.exclude_modules = exclude_modules
|
| 53 |
+
if is_checkpoint_fp8_serialized:
|
| 54 |
+
logger.warning(
|
| 55 |
+
"Detected ModelOpt FP8 checkpoint. The format is experimental and subject to change."
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
@classmethod
|
| 59 |
+
def get_name(cls) -> str:
|
| 60 |
+
return "modelopt"
|
| 61 |
+
|
| 62 |
+
@classmethod
|
| 63 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 64 |
+
return [torch.bfloat16, torch.half]
|
| 65 |
+
|
| 66 |
+
@classmethod
|
| 67 |
+
def get_min_capability(cls) -> int:
|
| 68 |
+
return 89 # Minimum hardware capability (e.g., Hopper GPUs).
|
| 69 |
+
|
| 70 |
+
@classmethod
|
| 71 |
+
def get_config_filenames(cls) -> List[str]:
|
| 72 |
+
return ["hf_quant_config.json"]
|
| 73 |
+
|
| 74 |
+
@classmethod
|
| 75 |
+
def from_config(cls, config: Dict[str, Any]) -> "ModelOptFp8Config":
|
| 76 |
+
quant_method = cls.get_from_keys(config, ["quantization"]).get("quant_algo")
|
| 77 |
+
kv_cache_quant_method = cls.get_from_keys(config, ["quantization"]).get(
|
| 78 |
+
"kv_cache_quant_algo"
|
| 79 |
+
)
|
| 80 |
+
exclude_modules = cls.get_from_keys(config, ["quantization"]).get(
|
| 81 |
+
"exclude_modules"
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
if "FP8" not in quant_method:
|
| 85 |
+
raise ValueError(
|
| 86 |
+
"ModelOpt only supports static FP8 quantization in SGLang. "
|
| 87 |
+
"Check the `hf_quant_config.json` file for your model's configuration."
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
return cls(
|
| 91 |
+
is_checkpoint_fp8_serialized=True,
|
| 92 |
+
kv_cache_quant_method=kv_cache_quant_method,
|
| 93 |
+
exclude_modules=exclude_modules,
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
def get_quant_method(
|
| 97 |
+
self, layer: torch.nn.Module, prefix: str
|
| 98 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 99 |
+
if self.exclude_modules and any(
|
| 100 |
+
module in prefix for module in self.exclude_modules
|
| 101 |
+
):
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
if isinstance(layer, LinearBase):
|
| 105 |
+
return ModelOptFp8LinearMethod(self)
|
| 106 |
+
if self.kv_cache_quant_method and isinstance(layer, RadixAttention):
|
| 107 |
+
return ModelOptFp8KVCacheMethod(self)
|
| 108 |
+
|
| 109 |
+
return None
|
| 110 |
+
|
| 111 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 112 |
+
return []
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class ModelOptFp8LinearMethod(LinearMethodBase):
|
| 116 |
+
"""Linear method for ModelOpt static FP8 quantization.
|
| 117 |
+
|
| 118 |
+
Supports loading FP8 checkpoints with static weight and activation scales.
|
| 119 |
+
Future support may include dynamic scales.
|
| 120 |
+
|
| 121 |
+
**Limitations**:
|
| 122 |
+
1. Only supports per-tensor quantization due to `torch._scaled_mm` limitations.
|
| 123 |
+
2. Only supports the `float8_e4m3fn` data type.
|
| 124 |
+
|
| 125 |
+
Args:
|
| 126 |
+
quant_config (ModelOptFp8Config): The ModelOpt quantization configuration.
|
| 127 |
+
"""
|
| 128 |
+
|
| 129 |
+
def __init__(self, quant_config: ModelOptFp8Config):
|
| 130 |
+
super().__init__()
|
| 131 |
+
self.quant_config = quant_config
|
| 132 |
+
self.cutlass_fp8_supported = cutlass_fp8_supported()
|
| 133 |
+
|
| 134 |
+
def create_weights(
|
| 135 |
+
self,
|
| 136 |
+
layer: torch.nn.Module,
|
| 137 |
+
input_size_per_partition: int,
|
| 138 |
+
output_partition_sizes: List[int],
|
| 139 |
+
params_dtype: torch.dtype,
|
| 140 |
+
**extra_weight_attrs,
|
| 141 |
+
) -> None:
|
| 142 |
+
"""Creates and registers weights, weight scales, and input scales for FP8 quantization."""
|
| 143 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 144 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 145 |
+
weight_dtype = (
|
| 146 |
+
torch.float8_e4m3fn
|
| 147 |
+
if self.quant_config.is_checkpoint_fp8_serialized
|
| 148 |
+
else params_dtype
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Set layer attributes
|
| 152 |
+
layer.logical_widths = output_partition_sizes
|
| 153 |
+
layer.input_size_per_partition = input_size_per_partition
|
| 154 |
+
layer.output_size_per_partition = output_size_per_partition
|
| 155 |
+
|
| 156 |
+
# Register weight
|
| 157 |
+
layer.register_parameter(
|
| 158 |
+
"weight",
|
| 159 |
+
ModelWeightParameter(
|
| 160 |
+
data=torch.empty(
|
| 161 |
+
output_size_per_partition,
|
| 162 |
+
input_size_per_partition,
|
| 163 |
+
dtype=weight_dtype,
|
| 164 |
+
),
|
| 165 |
+
input_dim=1,
|
| 166 |
+
output_dim=0,
|
| 167 |
+
weight_loader=weight_loader,
|
| 168 |
+
),
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
if self.quant_config.is_checkpoint_fp8_serialized:
|
| 172 |
+
# Register weight and input scales
|
| 173 |
+
for scale_name in ["weight_scale", "input_scale"]:
|
| 174 |
+
layer.register_parameter(
|
| 175 |
+
scale_name,
|
| 176 |
+
PerTensorScaleParameter(
|
| 177 |
+
data=torch.full(
|
| 178 |
+
(len(output_partition_sizes),),
|
| 179 |
+
torch.finfo(torch.float32).min,
|
| 180 |
+
dtype=torch.float32,
|
| 181 |
+
),
|
| 182 |
+
weight_loader=weight_loader,
|
| 183 |
+
),
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 187 |
+
"""Requantizes weights after loading using the maximum scale."""
|
| 188 |
+
max_w_scale, quantized_weight = requantize_with_max_scale(
|
| 189 |
+
layer.weight, layer.weight_scale, layer.logical_widths
|
| 190 |
+
)
|
| 191 |
+
layer.weight = Parameter(quantized_weight.t(), requires_grad=False)
|
| 192 |
+
# cutlass sgl-kernel only supports per-channel scale
|
| 193 |
+
if self.cutlass_fp8_supported:
|
| 194 |
+
max_w_scale = convert_to_channelwise(max_w_scale, layer.logical_widths)
|
| 195 |
+
layer.weight_scale = Parameter(max_w_scale, requires_grad=False)
|
| 196 |
+
layer.input_scale = Parameter(layer.input_scale.max(), requires_grad=False)
|
| 197 |
+
|
| 198 |
+
def apply(
|
| 199 |
+
self,
|
| 200 |
+
layer: torch.nn.Module,
|
| 201 |
+
x: torch.Tensor,
|
| 202 |
+
bias: Optional[torch.Tensor] = None,
|
| 203 |
+
) -> torch.Tensor:
|
| 204 |
+
"""Applies FP8 linear transformation."""
|
| 205 |
+
return apply_fp8_linear(
|
| 206 |
+
input=x,
|
| 207 |
+
weight=layer.weight,
|
| 208 |
+
weight_scale=layer.weight_scale,
|
| 209 |
+
input_scale=layer.input_scale,
|
| 210 |
+
bias=bias,
|
| 211 |
+
cutlass_fp8_supported=self.cutlass_fp8_supported,
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class ModelOptFp8KVCacheMethod(BaseKVCacheMethod):
|
| 216 |
+
"""
|
| 217 |
+
Handles loading FP8 kv-cache scaling factors from modelopt quantized checkpoints.
|
| 218 |
+
"""
|
| 219 |
+
|
| 220 |
+
def __init__(self, quant_config: ModelOptFp8Config):
|
| 221 |
+
super().__init__(quant_config)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class ModelOptFp4Config(QuantizationConfig):
|
| 225 |
+
"""Config class for FP4."""
|
| 226 |
+
|
| 227 |
+
def __init__(
|
| 228 |
+
self,
|
| 229 |
+
is_checkpoint_nvfp4_serialized: bool = False,
|
| 230 |
+
kv_cache_quant_algo: str = None,
|
| 231 |
+
group_size: int = None,
|
| 232 |
+
exclude_modules: List[str] = None,
|
| 233 |
+
) -> None:
|
| 234 |
+
self.is_checkpoint_nvfp4_serialized = is_checkpoint_nvfp4_serialized
|
| 235 |
+
if is_checkpoint_nvfp4_serialized:
|
| 236 |
+
logger.warning(
|
| 237 |
+
"Detected nvfp4 checkpoint. Please note that the "
|
| 238 |
+
"format is experimental and subject to change."
|
| 239 |
+
)
|
| 240 |
+
self.group_size = group_size
|
| 241 |
+
self.kv_cache_quant_algo = kv_cache_quant_algo
|
| 242 |
+
self.exclude_modules = exclude_modules
|
| 243 |
+
|
| 244 |
+
@classmethod
|
| 245 |
+
def get_name(cls) -> str:
|
| 246 |
+
return "modelopt_fp4"
|
| 247 |
+
|
| 248 |
+
@classmethod
|
| 249 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 250 |
+
return [torch.bfloat16, torch.half, torch.float8_e4m3fn]
|
| 251 |
+
|
| 252 |
+
@classmethod
|
| 253 |
+
def get_min_capability(cls) -> int:
|
| 254 |
+
return 100
|
| 255 |
+
|
| 256 |
+
@classmethod
|
| 257 |
+
def get_config_filenames(cls) -> List[str]:
|
| 258 |
+
return ["hf_quant_config.json"]
|
| 259 |
+
|
| 260 |
+
@classmethod
|
| 261 |
+
def from_config(cls, config: Dict[str, Any]) -> "ModelOptFp4Config":
|
| 262 |
+
quant_config = cls.get_from_keys(config, ["quantization"])
|
| 263 |
+
quant_method = quant_config["quant_algo"]
|
| 264 |
+
if not quant_method in ["FP8", "NVFP4"]:
|
| 265 |
+
raise ValueError(
|
| 266 |
+
f"ModelOpt currently only supports: FP8, NVFP4"
|
| 267 |
+
" quantizations in sglang. Please check the "
|
| 268 |
+
"`hf_quant_config.json` file for your model's "
|
| 269 |
+
"quant configuration."
|
| 270 |
+
)
|
| 271 |
+
is_checkpoint_nvfp4_serialized = "NVFP4" in quant_method
|
| 272 |
+
kv_cache_quant_algo = quant_config["kv_cache_quant_algo"]
|
| 273 |
+
group_size = quant_config["group_size"]
|
| 274 |
+
exclude_modules = quant_config["exclude_modules"]
|
| 275 |
+
if not (group_size and kv_cache_quant_algo and exclude_modules):
|
| 276 |
+
raise ValueError(
|
| 277 |
+
"NVFP4 quantization requires group size and "
|
| 278 |
+
"kv_cache_quant_algo specified in "
|
| 279 |
+
"hf_quant_config.json"
|
| 280 |
+
)
|
| 281 |
+
return cls(
|
| 282 |
+
is_checkpoint_nvfp4_serialized,
|
| 283 |
+
kv_cache_quant_algo,
|
| 284 |
+
group_size,
|
| 285 |
+
exclude_modules,
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
def get_quant_method(
|
| 289 |
+
self, layer: torch.nn.Module, prefix: str
|
| 290 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 291 |
+
if self.exclude_modules and any(
|
| 292 |
+
module in prefix for module in self.exclude_modules
|
| 293 |
+
):
|
| 294 |
+
return None
|
| 295 |
+
|
| 296 |
+
if isinstance(layer, LinearBase):
|
| 297 |
+
return ModelOptFp4LinearMethod(self)
|
| 298 |
+
if self.kv_cache_quant_algo and isinstance(layer, RadixAttention):
|
| 299 |
+
return ModelOptFp8KVCacheMethod(self)
|
| 300 |
+
|
| 301 |
+
return None
|
| 302 |
+
|
| 303 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 304 |
+
return []
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
class ModelOptFp4LinearMethod(LinearMethodBase):
|
| 308 |
+
"""Linear method for NVFP4.
|
| 309 |
+
Supports loading NVFP4 checkpoints with the following structure:
|
| 310 |
+
|
| 311 |
+
|Tensor Name | datatype | shape |
|
| 312 |
+
|----------------------------------------------------|
|
| 313 |
+
|input_scale | torch.float32 | scalar |
|
| 314 |
+
|weight | NVFP4(SE2M1) | [1, X, y/2] |
|
| 315 |
+
|weight_scale | FP8-E4M3 | [X, Y] |
|
| 316 |
+
|weight_scale_2 | torch.float32 | scalar |
|
| 317 |
+
|
| 318 |
+
The weights are quantized per block of 16 elements.
|
| 319 |
+
Args: quant_config: The ModelOpt quantization config.
|
| 320 |
+
"""
|
| 321 |
+
|
| 322 |
+
def __init__(self, quant_config: ModelOptFp4Config):
|
| 323 |
+
self.quant_config = quant_config
|
| 324 |
+
|
| 325 |
+
def create_weights(
|
| 326 |
+
self,
|
| 327 |
+
layer: torch.nn.Module,
|
| 328 |
+
input_size_per_partition: int,
|
| 329 |
+
output_partition_sizes: List[int],
|
| 330 |
+
input_size: int,
|
| 331 |
+
output_size: int,
|
| 332 |
+
params_dtype: torch.dtype,
|
| 333 |
+
**extra_weight_attrs,
|
| 334 |
+
):
|
| 335 |
+
del input_size, output_size
|
| 336 |
+
if not self.quant_config.is_checkpoint_nvfp4_serialized:
|
| 337 |
+
raise ValueError(
|
| 338 |
+
"NVFP4 quantization was selected, "
|
| 339 |
+
" dynamic quantization is not supported."
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
output_size_per_partition = sum(output_partition_sizes)
|
| 343 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 344 |
+
|
| 345 |
+
layer.logical_widths = output_partition_sizes
|
| 346 |
+
|
| 347 |
+
layer.input_size_per_partition = input_size_per_partition
|
| 348 |
+
layer.output_size_per_partition = output_size_per_partition
|
| 349 |
+
if input_size_per_partition % 16 != 0:
|
| 350 |
+
raise ValueError(
|
| 351 |
+
"Unsupported model when in features size is " "not multiple of 16"
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
weight_dtype = (
|
| 355 |
+
torch.float8_e4m3fn
|
| 356 |
+
if self.quant_config.is_checkpoint_nvfp4_serialized
|
| 357 |
+
else params_dtype
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
weight = ModelWeightParameter(
|
| 361 |
+
data=torch.empty(
|
| 362 |
+
# 2 fp4 data is packed in one uint8 in the input dimension
|
| 363 |
+
output_size_per_partition,
|
| 364 |
+
input_size_per_partition // 2,
|
| 365 |
+
dtype=torch.uint8,
|
| 366 |
+
),
|
| 367 |
+
input_dim=1,
|
| 368 |
+
output_dim=0,
|
| 369 |
+
weight_loader=weight_loader,
|
| 370 |
+
)
|
| 371 |
+
layer.register_parameter("weight", weight)
|
| 372 |
+
|
| 373 |
+
input_scale = PerTensorScaleParameter(
|
| 374 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 375 |
+
weight_loader=weight_loader,
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
layer.register_parameter("input_scale", input_scale)
|
| 379 |
+
|
| 380 |
+
weight_scale_2 = PerTensorScaleParameter(
|
| 381 |
+
data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
|
| 382 |
+
weight_loader=weight_loader,
|
| 383 |
+
)
|
| 384 |
+
layer.register_parameter("weight_scale_2", weight_scale_2)
|
| 385 |
+
|
| 386 |
+
weight_scale = ModelWeightParameter(
|
| 387 |
+
data=torch.empty(
|
| 388 |
+
output_size_per_partition,
|
| 389 |
+
input_size_per_partition // self.quant_config.group_size,
|
| 390 |
+
dtype=weight_dtype,
|
| 391 |
+
),
|
| 392 |
+
input_dim=1,
|
| 393 |
+
output_dim=0,
|
| 394 |
+
weight_loader=weight_loader,
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
layer.register_parameter("weight_scale", weight_scale)
|
| 398 |
+
|
| 399 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 400 |
+
input_scale_2 = layer.input_scale.max().to(torch.float32)
|
| 401 |
+
weight_scale_2 = layer.weight_scale_2.max().to(torch.float32)
|
| 402 |
+
layer.input_scale = Parameter(input_scale_2, requires_grad=False)
|
| 403 |
+
layer.weight_scale_2 = Parameter(weight_scale_2, requires_grad=False)
|
| 404 |
+
layer.alpha = Parameter(
|
| 405 |
+
layer.input_scale * layer.weight_scale_2, requires_grad=False
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
# Pad and blockwise interleave weight_scale
|
| 409 |
+
scales = layer.weight_scale
|
| 410 |
+
scale_ndim = scales.ndim
|
| 411 |
+
if scale_ndim == 2:
|
| 412 |
+
scales = scales.unsqueeze(0)
|
| 413 |
+
assert scales.ndim == 3
|
| 414 |
+
B, M, K = scales.shape
|
| 415 |
+
round_up_multiple = lambda x, m: (x + m - 1) // m * m
|
| 416 |
+
M_padded = round_up_multiple(M, 128)
|
| 417 |
+
K_padded = round_up_multiple(K, 4)
|
| 418 |
+
padded_scales = torch.zeros((B, M_padded, K_padded), dtype=scales.dtype)
|
| 419 |
+
padded_scales[:B, :M, :K] = scales
|
| 420 |
+
batches, rows, cols = padded_scales.shape
|
| 421 |
+
assert rows % 128 == 0
|
| 422 |
+
assert cols % 4 == 0
|
| 423 |
+
padded_scales = padded_scales.reshape(batches, rows // 128, 4, 32, cols // 4, 4)
|
| 424 |
+
padded_scales = padded_scales.permute((0, 1, 4, 3, 2, 5))
|
| 425 |
+
padded_scales = padded_scales.contiguous().cuda()
|
| 426 |
+
padded_scales = (
|
| 427 |
+
padded_scales.reshape(M, K)
|
| 428 |
+
if scale_ndim == 2
|
| 429 |
+
else padded_scales.reshape(B, M, K)
|
| 430 |
+
)
|
| 431 |
+
layer.weight_scale_interleaved = Parameter(padded_scales, requires_grad=False)
|
| 432 |
+
|
| 433 |
+
def apply(
|
| 434 |
+
self,
|
| 435 |
+
layer: torch.nn.Module,
|
| 436 |
+
x: torch.Tensor,
|
| 437 |
+
bias: Optional[torch.Tensor] = None,
|
| 438 |
+
) -> torch.Tensor:
|
| 439 |
+
output_dtype = x.dtype
|
| 440 |
+
x_m, _ = x.shape
|
| 441 |
+
w_n, _ = layer.weight.shape
|
| 442 |
+
output_shape = [x_m, w_n]
|
| 443 |
+
|
| 444 |
+
# Quantize BF16 or FP16 to (FP4 and interleaved block scale)
|
| 445 |
+
x_fp4, x_scale_interleaved = scaled_fp4_quant(x, 1 / layer.input_scale)
|
| 446 |
+
|
| 447 |
+
assert x_fp4.dtype == torch.uint8
|
| 448 |
+
assert x_scale_interleaved.dtype == torch.float8_e4m3fn
|
| 449 |
+
assert layer.weight.dtype == torch.uint8
|
| 450 |
+
assert layer.weight_scale_interleaved.dtype == torch.float8_e4m3fn
|
| 451 |
+
assert layer.alpha.dtype == torch.float32
|
| 452 |
+
|
| 453 |
+
out = cutlass_scaled_fp4_mm(
|
| 454 |
+
x_fp4,
|
| 455 |
+
layer.weight,
|
| 456 |
+
x_scale_interleaved,
|
| 457 |
+
layer.weight_scale_interleaved,
|
| 458 |
+
layer.alpha,
|
| 459 |
+
output_dtype,
|
| 460 |
+
)
|
| 461 |
+
if bias is not None:
|
| 462 |
+
out = out + bias
|
| 463 |
+
return out.view(*output_shape)
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/moe_wna16.py
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/quantization/moe_wna16.py
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from typing import Any, Callable, Dict, List, Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from sglang.srt.distributed import get_tensor_model_parallel_rank
|
| 9 |
+
from sglang.srt.distributed.parallel_state import get_tp_group
|
| 10 |
+
from sglang.srt.layers.linear import LinearBase, UnquantizedLinearMethod
|
| 11 |
+
from sglang.srt.layers.quantization.awq import AWQConfig
|
| 12 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 13 |
+
QuantizationConfig,
|
| 14 |
+
QuantizeMethodBase,
|
| 15 |
+
)
|
| 16 |
+
from sglang.srt.layers.quantization.gptq import GPTQConfig, GPTQMarlinConfig
|
| 17 |
+
from sglang.srt.utils import get_device_capability, set_weight_attrs
|
| 18 |
+
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class MoeWNA16Config(QuantizationConfig):
|
| 23 |
+
"""Config class for MOE WNA16 (W8A16/W4A16) quantization."""
|
| 24 |
+
|
| 25 |
+
def __init__(
|
| 26 |
+
self,
|
| 27 |
+
linear_quant_method: str,
|
| 28 |
+
weight_bits: int,
|
| 29 |
+
group_size: int,
|
| 30 |
+
has_zp: bool,
|
| 31 |
+
lm_head_quantized: bool,
|
| 32 |
+
modules_to_not_convert: Optional[List[str]],
|
| 33 |
+
full_config: Dict[str, Any],
|
| 34 |
+
) -> None:
|
| 35 |
+
super().__init__()
|
| 36 |
+
self.weight_bits = weight_bits
|
| 37 |
+
self.group_size = group_size
|
| 38 |
+
self.has_zp = has_zp
|
| 39 |
+
self.bit8_pack_factor = 8 // self.weight_bits
|
| 40 |
+
self.lm_head_quantized = lm_head_quantized
|
| 41 |
+
self.linear_quant_method = linear_quant_method
|
| 42 |
+
self.full_config = full_config
|
| 43 |
+
self.use_marlin = False
|
| 44 |
+
# Avoid circular import
|
| 45 |
+
|
| 46 |
+
if self.linear_quant_method == "gptq":
|
| 47 |
+
self.use_marlin = GPTQMarlinConfig.is_gptq_marlin_compatible(full_config)
|
| 48 |
+
elif self.linear_quant_method == "awq":
|
| 49 |
+
capability_tuple = get_device_capability()
|
| 50 |
+
device_capability = (
|
| 51 |
+
-1
|
| 52 |
+
if capability_tuple is None
|
| 53 |
+
else capability_tuple[0] * 10 + capability_tuple[1]
|
| 54 |
+
)
|
| 55 |
+
awq_min_capability = AWQConfig.get_min_capability()
|
| 56 |
+
if device_capability < awq_min_capability:
|
| 57 |
+
raise ValueError(
|
| 58 |
+
"The quantization method moe_wna16 + awq is not supported "
|
| 59 |
+
"for the current GPU. "
|
| 60 |
+
f"Minimum capability: {awq_min_capability}. "
|
| 61 |
+
f"Current capability: {device_capability}."
|
| 62 |
+
)
|
| 63 |
+
else:
|
| 64 |
+
raise ValueError("moe_wna16 only support gptq and awq.")
|
| 65 |
+
|
| 66 |
+
if modules_to_not_convert is None:
|
| 67 |
+
self.modules_to_not_convert = []
|
| 68 |
+
else:
|
| 69 |
+
self.modules_to_not_convert = modules_to_not_convert
|
| 70 |
+
|
| 71 |
+
@classmethod
|
| 72 |
+
def get_name(cls) -> str:
|
| 73 |
+
return "moe_wna16"
|
| 74 |
+
|
| 75 |
+
@classmethod
|
| 76 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 77 |
+
return [torch.bfloat16, torch.half]
|
| 78 |
+
|
| 79 |
+
@classmethod
|
| 80 |
+
def get_min_capability(cls) -> int:
|
| 81 |
+
return 70
|
| 82 |
+
|
| 83 |
+
@classmethod
|
| 84 |
+
def get_config_filenames(cls) -> List[str]:
|
| 85 |
+
return ["quantize_config.json"]
|
| 86 |
+
|
| 87 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 88 |
+
raise NotImplementedError
|
| 89 |
+
|
| 90 |
+
@classmethod
|
| 91 |
+
def from_config(cls, config: Dict[str, Any]) -> "MoeWNA16Config":
|
| 92 |
+
quant_method = cls.get_from_keys(config, ["quant_method"])
|
| 93 |
+
weight_bits = cls.get_from_keys(config, ["bits"])
|
| 94 |
+
group_size = cls.get_from_keys(config, ["group_size"])
|
| 95 |
+
lm_head_quantized = cls.get_from_keys_or(config, ["lm_head"], default=False)
|
| 96 |
+
if quant_method == "gptq":
|
| 97 |
+
has_zp = not cls.get_from_keys(config, ["sym"])
|
| 98 |
+
modules_to_not_convert = []
|
| 99 |
+
elif quant_method == "awq":
|
| 100 |
+
has_zp = cls.get_from_keys(config, ["zero_point"])
|
| 101 |
+
modules_to_not_convert = cls.get_from_keys_or(
|
| 102 |
+
config, ["modules_to_not_convert"], None
|
| 103 |
+
)
|
| 104 |
+
else:
|
| 105 |
+
raise ValueError("moe_wna16 only support gptq and awq.")
|
| 106 |
+
|
| 107 |
+
return cls(
|
| 108 |
+
quant_method,
|
| 109 |
+
weight_bits,
|
| 110 |
+
group_size,
|
| 111 |
+
has_zp,
|
| 112 |
+
lm_head_quantized,
|
| 113 |
+
modules_to_not_convert,
|
| 114 |
+
config,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
@classmethod
|
| 118 |
+
def override_quantization_method(cls, hf_quant_cfg, user_quant) -> Optional[str]:
|
| 119 |
+
can_convert = cls.is_moe_wna16_compatible(hf_quant_cfg)
|
| 120 |
+
if can_convert and user_quant == "moe_wna16":
|
| 121 |
+
return cls.get_name()
|
| 122 |
+
return None
|
| 123 |
+
|
| 124 |
+
@classmethod
|
| 125 |
+
def is_moe_wna16_compatible(cls, quant_config: Dict[str, Any]):
|
| 126 |
+
# Extract data from quant config.
|
| 127 |
+
quant_method = quant_config.get("quant_method", "").lower()
|
| 128 |
+
num_bits = quant_config.get("bits")
|
| 129 |
+
desc_act = quant_config.get("desc_act")
|
| 130 |
+
|
| 131 |
+
capability_tuple = get_device_capability()
|
| 132 |
+
device_capability = (
|
| 133 |
+
-1
|
| 134 |
+
if capability_tuple is None
|
| 135 |
+
else capability_tuple[0] * 10 + capability_tuple[1]
|
| 136 |
+
)
|
| 137 |
+
# Avoid circular import
|
| 138 |
+
awq_min_capability = AWQConfig.get_min_capability()
|
| 139 |
+
|
| 140 |
+
gptq_compatible = quant_method == "gptq" and not desc_act and num_bits in [4, 8]
|
| 141 |
+
awq_compatible = (
|
| 142 |
+
quant_method == "awq"
|
| 143 |
+
and num_bits == 4
|
| 144 |
+
and device_capability >= awq_min_capability
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
return gptq_compatible or awq_compatible
|
| 148 |
+
|
| 149 |
+
def get_quant_method(
|
| 150 |
+
self, layer: torch.nn.Module, prefix: str
|
| 151 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 152 |
+
# avoid circular import
|
| 153 |
+
from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE
|
| 154 |
+
|
| 155 |
+
if is_layer_skipped_quant(prefix, self.modules_to_not_convert):
|
| 156 |
+
return UnquantizedLinearMethod()
|
| 157 |
+
elif isinstance(layer, LinearBase):
|
| 158 |
+
|
| 159 |
+
if self.linear_quant_method == "gptq":
|
| 160 |
+
if self.use_marlin:
|
| 161 |
+
return GPTQMarlinConfig.from_config(
|
| 162 |
+
self.full_config
|
| 163 |
+
).get_quant_method(layer, prefix)
|
| 164 |
+
else:
|
| 165 |
+
return GPTQConfig.from_config(self.full_config).get_quant_method(
|
| 166 |
+
layer, prefix
|
| 167 |
+
)
|
| 168 |
+
elif self.linear_quant_method == "awq":
|
| 169 |
+
return AWQConfig.from_config(self.full_config).get_quant_method(
|
| 170 |
+
layer, prefix
|
| 171 |
+
)
|
| 172 |
+
else:
|
| 173 |
+
raise ValueError("moe_wna16 only support gptq and awq.")
|
| 174 |
+
elif isinstance(layer, FusedMoE):
|
| 175 |
+
return MoeWNA16Method(self)
|
| 176 |
+
return None
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def is_layer_skipped_quant(prefix: str, modules_to_not_convert: List[str]):
|
| 180 |
+
return any(module_name in prefix for module_name in modules_to_not_convert)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class MoeWNA16Method:
|
| 184 |
+
"""Linear method for MOE WNA16 (W8A16/W4A16) quantization.
|
| 185 |
+
|
| 186 |
+
Args:
|
| 187 |
+
quant_config: The MOE WNA16 (W8A16/W4A16) quantization config.
|
| 188 |
+
"""
|
| 189 |
+
|
| 190 |
+
def __new__(cls, *args, **kwargs):
|
| 191 |
+
# avoid circular import
|
| 192 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoEMethodBase
|
| 193 |
+
|
| 194 |
+
if not hasattr(cls, "_initialized"):
|
| 195 |
+
original_init = cls.__init__
|
| 196 |
+
new_cls = type(
|
| 197 |
+
cls.__name__,
|
| 198 |
+
(FusedMoEMethodBase,),
|
| 199 |
+
{
|
| 200 |
+
"__init__": original_init,
|
| 201 |
+
**{k: v for k, v in cls.__dict__.items() if k != "__dict__"},
|
| 202 |
+
},
|
| 203 |
+
)
|
| 204 |
+
obj = super(new_cls, new_cls).__new__(new_cls)
|
| 205 |
+
obj.__init__(*args, **kwargs)
|
| 206 |
+
return obj
|
| 207 |
+
return super().__new__(cls)
|
| 208 |
+
|
| 209 |
+
def __init__(self, quant_config: MoeWNA16Config):
|
| 210 |
+
self.quant_config = quant_config
|
| 211 |
+
|
| 212 |
+
def create_weights(
|
| 213 |
+
self,
|
| 214 |
+
layer: torch.nn.Module,
|
| 215 |
+
num_experts: int,
|
| 216 |
+
hidden_size: int,
|
| 217 |
+
intermediate_size_per_partition: int,
|
| 218 |
+
params_dtype: torch.dtype,
|
| 219 |
+
**extra_weight_attrs,
|
| 220 |
+
):
|
| 221 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoeWeightScaleSupported
|
| 222 |
+
|
| 223 |
+
layer.quant_config = self.quant_config
|
| 224 |
+
bit8_pack_factor = self.quant_config.bit8_pack_factor
|
| 225 |
+
group_size = self.quant_config.group_size
|
| 226 |
+
group_size_div_factor = 1
|
| 227 |
+
|
| 228 |
+
# make intermediate_size and hidden_size diviable by group_size
|
| 229 |
+
# we reduce the group size to ensure that
|
| 230 |
+
# and we would repeat the loaded_weight later
|
| 231 |
+
while intermediate_size_per_partition % group_size or hidden_size % group_size:
|
| 232 |
+
group_size = group_size // 2
|
| 233 |
+
group_size_div_factor *= 2
|
| 234 |
+
assert group_size >= 32
|
| 235 |
+
layer.group_size = group_size
|
| 236 |
+
layer.group_size_div_factor = group_size_div_factor
|
| 237 |
+
|
| 238 |
+
strategy = FusedMoeWeightScaleSupported.GROUP.value
|
| 239 |
+
extra_weight_attrs.update({"quant_method": strategy, "is_transposed": False})
|
| 240 |
+
|
| 241 |
+
assert "weight_loader" in extra_weight_attrs
|
| 242 |
+
weight_loader = extra_weight_attrs["weight_loader"]
|
| 243 |
+
wrapped_weight_loader = MoeWNA16Method.get_weight_loader(layer, weight_loader)
|
| 244 |
+
extra_weight_attrs["weight_loader"] = wrapped_weight_loader
|
| 245 |
+
|
| 246 |
+
# Fused gate_up_proj (column parallel)
|
| 247 |
+
w13_qweight = torch.nn.Parameter(
|
| 248 |
+
torch.empty(
|
| 249 |
+
num_experts,
|
| 250 |
+
2 * intermediate_size_per_partition,
|
| 251 |
+
hidden_size // bit8_pack_factor,
|
| 252 |
+
dtype=torch.uint8,
|
| 253 |
+
),
|
| 254 |
+
requires_grad=False,
|
| 255 |
+
)
|
| 256 |
+
layer.register_parameter("w13_qweight", w13_qweight)
|
| 257 |
+
set_weight_attrs(w13_qweight, extra_weight_attrs)
|
| 258 |
+
|
| 259 |
+
# down_proj (row parallel)
|
| 260 |
+
w2_qweight = torch.nn.Parameter(
|
| 261 |
+
torch.empty(
|
| 262 |
+
num_experts,
|
| 263 |
+
hidden_size,
|
| 264 |
+
intermediate_size_per_partition // bit8_pack_factor,
|
| 265 |
+
dtype=torch.uint8,
|
| 266 |
+
),
|
| 267 |
+
requires_grad=False,
|
| 268 |
+
)
|
| 269 |
+
layer.register_parameter("w2_qweight", w2_qweight)
|
| 270 |
+
set_weight_attrs(w2_qweight, extra_weight_attrs)
|
| 271 |
+
|
| 272 |
+
w13_scales = torch.nn.Parameter(
|
| 273 |
+
torch.zeros(
|
| 274 |
+
num_experts,
|
| 275 |
+
2 * intermediate_size_per_partition,
|
| 276 |
+
hidden_size // group_size,
|
| 277 |
+
dtype=params_dtype,
|
| 278 |
+
),
|
| 279 |
+
requires_grad=False,
|
| 280 |
+
)
|
| 281 |
+
layer.register_parameter("w13_scales", w13_scales)
|
| 282 |
+
set_weight_attrs(w13_scales, extra_weight_attrs)
|
| 283 |
+
|
| 284 |
+
w2_scales = torch.nn.Parameter(
|
| 285 |
+
torch.zeros(
|
| 286 |
+
num_experts,
|
| 287 |
+
hidden_size,
|
| 288 |
+
intermediate_size_per_partition // group_size,
|
| 289 |
+
dtype=params_dtype,
|
| 290 |
+
),
|
| 291 |
+
requires_grad=False,
|
| 292 |
+
)
|
| 293 |
+
layer.register_parameter("w2_scales", w2_scales)
|
| 294 |
+
set_weight_attrs(w2_scales, extra_weight_attrs)
|
| 295 |
+
|
| 296 |
+
if self.quant_config.has_zp:
|
| 297 |
+
w13_qzeros = torch.nn.Parameter(
|
| 298 |
+
torch.zeros(
|
| 299 |
+
num_experts,
|
| 300 |
+
2 * intermediate_size_per_partition // bit8_pack_factor,
|
| 301 |
+
hidden_size // group_size,
|
| 302 |
+
dtype=torch.uint8,
|
| 303 |
+
),
|
| 304 |
+
requires_grad=False,
|
| 305 |
+
)
|
| 306 |
+
layer.register_parameter("w13_qzeros", w13_qzeros)
|
| 307 |
+
set_weight_attrs(w13_qzeros, extra_weight_attrs)
|
| 308 |
+
|
| 309 |
+
w2_qzeros = torch.nn.Parameter(
|
| 310 |
+
torch.zeros(
|
| 311 |
+
num_experts,
|
| 312 |
+
hidden_size // bit8_pack_factor,
|
| 313 |
+
intermediate_size_per_partition // group_size,
|
| 314 |
+
dtype=torch.uint8,
|
| 315 |
+
),
|
| 316 |
+
requires_grad=False,
|
| 317 |
+
)
|
| 318 |
+
layer.register_parameter("w2_qzeros", w2_qzeros)
|
| 319 |
+
set_weight_attrs(w2_qzeros, extra_weight_attrs)
|
| 320 |
+
|
| 321 |
+
if self.quant_config.linear_quant_method == "gptq":
|
| 322 |
+
# some param are unused, but we need to init them in order to
|
| 323 |
+
# load weights
|
| 324 |
+
invalid_param_keys = ["w13_g_idx", "w2_g_idx"]
|
| 325 |
+
if not self.quant_config.has_zp:
|
| 326 |
+
invalid_param_keys += ["w13_qzeros", "w2_qzeros"]
|
| 327 |
+
for key in invalid_param_keys:
|
| 328 |
+
param = torch.nn.Parameter(
|
| 329 |
+
torch.empty((0,), dtype=torch.int32), requires_grad=False
|
| 330 |
+
)
|
| 331 |
+
layer.register_parameter(key, param)
|
| 332 |
+
set_weight_attrs(param, extra_weight_attrs)
|
| 333 |
+
|
| 334 |
+
def apply(
|
| 335 |
+
self,
|
| 336 |
+
layer: torch.nn.Module,
|
| 337 |
+
x: torch.Tensor,
|
| 338 |
+
router_logits: torch.Tensor,
|
| 339 |
+
top_k: int,
|
| 340 |
+
renormalize: bool,
|
| 341 |
+
use_grouped_topk: bool = False,
|
| 342 |
+
topk_group: Optional[int] = None,
|
| 343 |
+
num_expert_group: Optional[int] = None,
|
| 344 |
+
custom_routing_function: Optional[Callable] = None,
|
| 345 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 346 |
+
activation: str = "silu",
|
| 347 |
+
apply_router_weight_on_input: bool = False,
|
| 348 |
+
inplace: bool = True,
|
| 349 |
+
no_combine: bool = False,
|
| 350 |
+
routed_scaling_factor: Optional[float] = None,
|
| 351 |
+
) -> torch.Tensor:
|
| 352 |
+
# avoid circular import
|
| 353 |
+
from sglang.srt.layers.moe.fused_moe_triton.fused_moe import fused_experts
|
| 354 |
+
from sglang.srt.layers.moe.topk import select_experts
|
| 355 |
+
|
| 356 |
+
assert activation == "silu", "Only SiLU activation is supported."
|
| 357 |
+
topk_weights, topk_ids = select_experts(
|
| 358 |
+
hidden_states=x,
|
| 359 |
+
router_logits=router_logits,
|
| 360 |
+
top_k=top_k,
|
| 361 |
+
use_grouped_topk=use_grouped_topk,
|
| 362 |
+
renormalize=renormalize,
|
| 363 |
+
topk_group=topk_group,
|
| 364 |
+
num_expert_group=num_expert_group,
|
| 365 |
+
custom_routing_function=custom_routing_function,
|
| 366 |
+
correction_bias=correction_bias,
|
| 367 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
weight_bits = self.quant_config.weight_bits
|
| 371 |
+
has_zp = self.quant_config.has_zp
|
| 372 |
+
|
| 373 |
+
return fused_experts(
|
| 374 |
+
x,
|
| 375 |
+
layer.w13_qweight,
|
| 376 |
+
layer.w2_qweight,
|
| 377 |
+
topk_weights=topk_weights,
|
| 378 |
+
topk_ids=topk_ids,
|
| 379 |
+
inplace=inplace,
|
| 380 |
+
apply_router_weight_on_input=apply_router_weight_on_input,
|
| 381 |
+
use_int4_w4a16=weight_bits == 4,
|
| 382 |
+
use_int8_w8a16=weight_bits == 8,
|
| 383 |
+
w1_scale=layer.w13_scales,
|
| 384 |
+
w2_scale=layer.w2_scales,
|
| 385 |
+
w1_zp=layer.w13_qzeros if has_zp else None,
|
| 386 |
+
w2_zp=layer.w2_qzeros if has_zp else None,
|
| 387 |
+
block_shape=[0, layer.group_size],
|
| 388 |
+
no_combine=no_combine,
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
@staticmethod
|
| 392 |
+
def get_weight_loader(layer, weight_loader):
|
| 393 |
+
|
| 394 |
+
def convert_awq_tensor(tensor, tensor_type):
|
| 395 |
+
# convert awq qweight/qzeros to a standard format (assume int4)
|
| 396 |
+
# qweight: (k, n // pack_factor_bit32) -> (n, k // pack_factor_bit8)
|
| 397 |
+
# qzeros: (k // group_size, n // pack_factor_bit32) ->
|
| 398 |
+
# (n // pack_factor_bit8, k // group_size)
|
| 399 |
+
# pack_factor_bit32 = 32 // weight_bits
|
| 400 |
+
# pack_factor_bit8 = 8 // weight_bits
|
| 401 |
+
|
| 402 |
+
# 0. suppose origin shape (a, b), dtype int32
|
| 403 |
+
# 1. convert to uint8, shape (a, b) -> (a, 4 * b)
|
| 404 |
+
size0 = tensor.size(0)
|
| 405 |
+
tensor = tensor.view(torch.uint8)
|
| 406 |
+
|
| 407 |
+
# 2. unpack to uint4 (only when weight_bits == 4)
|
| 408 |
+
# shape (a, 4 * b) -> (a, 4 * b, 2)
|
| 409 |
+
shifter = torch.tensor([0, 4], dtype=torch.uint8, device=tensor.device)
|
| 410 |
+
tensor = (tensor[:, :, None] >> shifter) & 0xF
|
| 411 |
+
|
| 412 |
+
# 3. change order, see
|
| 413 |
+
# https://github.com/casper-hansen/AutoAWQ/blob/v0.2.8/awq/utils/quant_utils.py
|
| 414 |
+
# shape -> (a, 4 * b * pack_factor_bit8)
|
| 415 |
+
reverse_awq_pack_order = [0, 4, 1, 5, 2, 6, 3, 7]
|
| 416 |
+
tensor = tensor.view(-1, 8)[:, reverse_awq_pack_order]
|
| 417 |
+
tensor = tensor.view(size0, -1)
|
| 418 |
+
|
| 419 |
+
# 4. transpose, shape -> (4 * b * pack_factor_bit8, a)
|
| 420 |
+
tensor = tensor.T.contiguous()
|
| 421 |
+
|
| 422 |
+
# 5. repack (only when weight_bits == 4)
|
| 423 |
+
# qweight shape -> (4 * b * pack_factor_bit8, a // pack_factor_bit8)
|
| 424 |
+
# qzeros shape -> (4 * b, a)
|
| 425 |
+
|
| 426 |
+
if tensor_type == "qweight":
|
| 427 |
+
tensor = tensor[:, 1::2] * 16 + tensor[:, ::2]
|
| 428 |
+
elif tensor_type == "qzeros":
|
| 429 |
+
tensor = tensor[1::2, :] * 16 + tensor[::2, :]
|
| 430 |
+
return tensor
|
| 431 |
+
|
| 432 |
+
def convert_gptq_int4_qzeros(tensor):
|
| 433 |
+
tensor = tensor.view(torch.uint8)
|
| 434 |
+
shifter = torch.tensor([0, 4], dtype=torch.uint8, device=tensor.device)
|
| 435 |
+
tensor = (tensor[:, :, None] >> shifter) & 0xF
|
| 436 |
+
tensor = tensor + 1
|
| 437 |
+
tensor = tensor[:, :, 0] + tensor[:, :, 1] * 16
|
| 438 |
+
return tensor
|
| 439 |
+
|
| 440 |
+
def moe_wna16_weight_loader(
|
| 441 |
+
param: torch.nn.Parameter,
|
| 442 |
+
loaded_weight: torch.Tensor,
|
| 443 |
+
weight_name: str,
|
| 444 |
+
shard_id: str,
|
| 445 |
+
expert_id: int,
|
| 446 |
+
):
|
| 447 |
+
if "g_idx" in weight_name:
|
| 448 |
+
return
|
| 449 |
+
if not layer.quant_config.has_zp and "qzeros" in weight_name:
|
| 450 |
+
return
|
| 451 |
+
|
| 452 |
+
device = get_tp_group().device
|
| 453 |
+
tp_rank = get_tensor_model_parallel_rank()
|
| 454 |
+
loaded_weight = loaded_weight.to(device)
|
| 455 |
+
shard_size = layer.intermediate_size_per_partition
|
| 456 |
+
|
| 457 |
+
# convert gptq and awq weight to a standard format
|
| 458 |
+
if layer.quant_config.linear_quant_method == "awq":
|
| 459 |
+
assert layer.quant_config.weight_bits == 4
|
| 460 |
+
if "weight" in weight_name:
|
| 461 |
+
loaded_weight = convert_awq_tensor(loaded_weight, "qweight")
|
| 462 |
+
elif "zeros" in weight_name:
|
| 463 |
+
loaded_weight = convert_awq_tensor(loaded_weight, "qzeros")
|
| 464 |
+
else:
|
| 465 |
+
loaded_weight = loaded_weight.T
|
| 466 |
+
elif layer.quant_config.linear_quant_method == "gptq":
|
| 467 |
+
assert layer.quant_config.weight_bits in [4, 8]
|
| 468 |
+
if "weight" in weight_name:
|
| 469 |
+
loaded_weight = loaded_weight.T.contiguous().view(torch.uint8)
|
| 470 |
+
elif "zeros" in weight_name:
|
| 471 |
+
# add 1 to gptq qzeros to align with awq
|
| 472 |
+
loaded_weight = loaded_weight.view(torch.uint8)
|
| 473 |
+
if layer.quant_config.weight_bits == 4:
|
| 474 |
+
loaded_weight = convert_gptq_int4_qzeros(loaded_weight).T
|
| 475 |
+
else:
|
| 476 |
+
loaded_weight = loaded_weight.T + 1
|
| 477 |
+
else:
|
| 478 |
+
loaded_weight = loaded_weight.T
|
| 479 |
+
|
| 480 |
+
# repeat the qzeros/scales to fit new group size
|
| 481 |
+
if (
|
| 482 |
+
layer.group_size_div_factor > 1
|
| 483 |
+
and "qzeros" in weight_name
|
| 484 |
+
or "scales" in weight_name
|
| 485 |
+
):
|
| 486 |
+
loaded_weight = loaded_weight.repeat_interleave(
|
| 487 |
+
layer.group_size_div_factor, 1
|
| 488 |
+
)
|
| 489 |
+
|
| 490 |
+
if "w13_qzeros" in weight_name:
|
| 491 |
+
tensor = loaded_weight.view(layer.tp_size, -1, loaded_weight.size(1))[
|
| 492 |
+
tp_rank
|
| 493 |
+
]
|
| 494 |
+
if shard_id == "w1":
|
| 495 |
+
param.data[expert_id, : shard_size // 2] = tensor
|
| 496 |
+
else:
|
| 497 |
+
param.data[expert_id, shard_size // 2 :] = tensor
|
| 498 |
+
elif "w2_qzeros" in weight_name:
|
| 499 |
+
param.data[expert_id] = loaded_weight.view(
|
| 500 |
+
loaded_weight.size(0), layer.tp_size, -1
|
| 501 |
+
)[:, tp_rank]
|
| 502 |
+
else:
|
| 503 |
+
weight_loader(param, loaded_weight, weight_name, shard_id, expert_id)
|
| 504 |
+
|
| 505 |
+
return moe_wna16_weight_loader
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/utils.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/quantization/utils/quant_utils.py
|
| 2 |
+
|
| 3 |
+
from types import MappingProxyType
|
| 4 |
+
from typing import List, Mapping, Tuple, Union
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from sglang.srt.layers.quantization.fp8_kernel import scaled_fp8_quant
|
| 9 |
+
from sglang.srt.utils import is_cuda
|
| 10 |
+
|
| 11 |
+
_is_cuda = is_cuda()
|
| 12 |
+
|
| 13 |
+
if not _is_cuda:
|
| 14 |
+
from vllm._custom_ops import scaled_fp8_quant
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def is_fp8_fnuz() -> bool:
|
| 18 |
+
# only device 0 is checked, this assumes MI300 platforms are homogeneous
|
| 19 |
+
return "gfx94" in torch.cuda.get_device_properties(0).gcnArchName
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def is_layer_skipped(
|
| 23 |
+
prefix: str,
|
| 24 |
+
ignored_layers: List[str],
|
| 25 |
+
fused_mapping: Mapping[str, List[str]] = MappingProxyType({}),
|
| 26 |
+
) -> bool:
|
| 27 |
+
# prefix: model.layers.0.self_attn.q_proj
|
| 28 |
+
# proj_name: q_proj
|
| 29 |
+
proj_name = prefix.split(".")[-1]
|
| 30 |
+
|
| 31 |
+
# Fused layers like gate_up_proj or qkv_proj will not be fused
|
| 32 |
+
# in the safetensors checkpoint. So, we convert the name
|
| 33 |
+
# from the fused version to unfused + check to make sure that
|
| 34 |
+
# each shard of the fused layer has the same scheme.
|
| 35 |
+
if proj_name in fused_mapping:
|
| 36 |
+
shard_prefixes = [
|
| 37 |
+
prefix.replace(proj_name, shard_proj_name)
|
| 38 |
+
for shard_proj_name in fused_mapping[proj_name]
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
is_skipped = None
|
| 42 |
+
for shard_prefix in shard_prefixes:
|
| 43 |
+
is_shard_skipped = shard_prefix in ignored_layers
|
| 44 |
+
|
| 45 |
+
if is_skipped is None:
|
| 46 |
+
is_skipped = is_shard_skipped
|
| 47 |
+
elif is_shard_skipped != is_skipped:
|
| 48 |
+
raise ValueError(
|
| 49 |
+
f"Detected some but not all shards of {prefix} "
|
| 50 |
+
"are quantized. All shards of fused layers "
|
| 51 |
+
"to have the same precision."
|
| 52 |
+
)
|
| 53 |
+
else:
|
| 54 |
+
is_skipped = prefix in ignored_layers
|
| 55 |
+
|
| 56 |
+
assert is_skipped is not None
|
| 57 |
+
return is_skipped
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def per_tensor_dequantize(
|
| 61 |
+
tensor: torch.Tensor, inv_scale: Union[float, torch.Tensor]
|
| 62 |
+
) -> torch.Tensor:
|
| 63 |
+
fake_qweight = tensor.to(torch.float16)
|
| 64 |
+
dq_weight = fake_qweight * inv_scale
|
| 65 |
+
return dq_weight
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def all_close_1d(x: torch.Tensor) -> bool:
|
| 69 |
+
assert len(x.shape) == 1
|
| 70 |
+
return all(torch.allclose(x[0], x[i]) for i in range(x.shape[0]))
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def convert_to_channelwise(
|
| 74 |
+
weight_scale: torch.Tensor, logical_widths: List[int]
|
| 75 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 76 |
+
# Create channelwise buffer
|
| 77 |
+
weight_scale_channel = torch.empty(
|
| 78 |
+
(sum(logical_widths), 1), dtype=torch.float32, device=weight_scale.device
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
# Handle scalar tensor case: broadcast same scale to all channels
|
| 82 |
+
if weight_scale.dim() == 0:
|
| 83 |
+
weight_scale_channel.fill_(weight_scale.item())
|
| 84 |
+
return weight_scale_channel
|
| 85 |
+
|
| 86 |
+
# Expand each scale to match the size of each logical matrix.
|
| 87 |
+
start = 0
|
| 88 |
+
for idx, logical_width in enumerate(logical_widths):
|
| 89 |
+
end = start + logical_width
|
| 90 |
+
weight_scale_channel[start:end, :] = weight_scale[idx]
|
| 91 |
+
start = end
|
| 92 |
+
|
| 93 |
+
return weight_scale_channel
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def requantize_with_max_scale(
|
| 97 |
+
weight: torch.Tensor, weight_scale: torch.Tensor, logical_widths: List[int]
|
| 98 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 99 |
+
# Max scale to be used for requanitzation.
|
| 100 |
+
max_w_scale = weight_scale.max()
|
| 101 |
+
|
| 102 |
+
# QKV / MLP is fused in the on disk checkpoint if any of the
|
| 103 |
+
# weight scales are still set to the default since we initialize
|
| 104 |
+
# N weight scales for N shards but we only load 1 weight scale
|
| 105 |
+
# from disk in this case. Skip requantization in this case (since)
|
| 106 |
+
# we already are quantized with the single scale.
|
| 107 |
+
# * Sample Model: nm-testing/Phi-3-mini-128k-instruct-FP8
|
| 108 |
+
unfused_module_in_checkpoint = (
|
| 109 |
+
weight_scale[-1] > torch.finfo(torch.float8_e4m3fn).min
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
# If unfused checkpoint, need requanize with the single scale.
|
| 113 |
+
if unfused_module_in_checkpoint:
|
| 114 |
+
start = 0
|
| 115 |
+
for idx, logical_width in enumerate(logical_widths):
|
| 116 |
+
end = start + logical_width
|
| 117 |
+
weight_dq = per_tensor_dequantize(weight[start:end, :], weight_scale[idx])
|
| 118 |
+
weight[start:end, :], _ = scaled_fp8_quant(weight_dq, max_w_scale)
|
| 119 |
+
start = end
|
| 120 |
+
|
| 121 |
+
return max_w_scale, weight
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/quantization/utils/layer_utils.py
|
| 125 |
+
# Newly generated tensors need to replace existing tensors that are
|
| 126 |
+
# already registered as parameters by vLLM (and won't be freed)
|
| 127 |
+
def replace_parameter(
|
| 128 |
+
mod: torch.nn.Module, name: str, new: Union[torch.Tensor, torch.nn.Parameter]
|
| 129 |
+
) -> None:
|
| 130 |
+
|
| 131 |
+
old = getattr(mod, name)
|
| 132 |
+
if (
|
| 133 |
+
type(old) is type(new)
|
| 134 |
+
and old.dtype == new.dtype
|
| 135 |
+
and old.untyped_storage().nbytes() == new.untyped_storage().nbytes()
|
| 136 |
+
):
|
| 137 |
+
# If we can just update in-place to avoid re-registering
|
| 138 |
+
# can be faster if the underlying storage is the same
|
| 139 |
+
update_tensor_inplace(old, new)
|
| 140 |
+
else:
|
| 141 |
+
# Fallback re-register parameter, convert to Parameter if necessary
|
| 142 |
+
# this not only ensures we don't register a tensor as a parameter, but
|
| 143 |
+
# also ensures that all parameter subclasses get re-registered as
|
| 144 |
+
# parameters for `torch.compile` compatibility
|
| 145 |
+
if not isinstance(new, torch.nn.Parameter):
|
| 146 |
+
new = torch.nn.Parameter(new, requires_grad=False)
|
| 147 |
+
mod.register_parameter(name, torch.nn.Parameter(new, requires_grad=False))
|
lib/python3.12/site-packages/sglang/srt/layers/quantization/w8a8_fp8.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Callable, Dict, List, Optional
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch.nn.parameter import Parameter
|
| 5 |
+
|
| 6 |
+
from sglang.srt.layers.linear import LinearMethodBase
|
| 7 |
+
from sglang.srt.layers.parameter import ChannelQuantScaleParameter, ModelWeightParameter
|
| 8 |
+
from sglang.srt.layers.quantization.base_config import (
|
| 9 |
+
QuantizationConfig,
|
| 10 |
+
QuantizeMethodBase,
|
| 11 |
+
)
|
| 12 |
+
from sglang.srt.layers.quantization.fp8_kernel import per_token_group_quant_fp8
|
| 13 |
+
from sglang.srt.layers.quantization.fp8_utils import (
|
| 14 |
+
apply_fp8_linear,
|
| 15 |
+
cutlass_fp8_supported,
|
| 16 |
+
input_to_float8,
|
| 17 |
+
normalize_e4m3fn_to_e4m3fnuz,
|
| 18 |
+
)
|
| 19 |
+
from sglang.srt.utils import is_hip, set_weight_attrs
|
| 20 |
+
|
| 21 |
+
_is_hip = is_hip()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class W8A8Fp8Config(QuantizationConfig):
|
| 25 |
+
"""Config class for W8A8 FP8 Quantization.
|
| 26 |
+
|
| 27 |
+
Weight Quantization:
|
| 28 |
+
- Method: Static quantization
|
| 29 |
+
- Granularity: Per-channel
|
| 30 |
+
- Type: Symmetric
|
| 31 |
+
|
| 32 |
+
Activation Quantization:
|
| 33 |
+
- Method: Dynamic quantization
|
| 34 |
+
- Granularity: Per-token
|
| 35 |
+
- Type: Symmetric
|
| 36 |
+
|
| 37 |
+
Note:
|
| 38 |
+
- For models without offline quantization, weights will be quantized during model loading
|
| 39 |
+
- If CUTLASS is supported: Per-channel weight quantization is used
|
| 40 |
+
- If CUTLASS is not supported: Falls back to per-tensor weight quantization
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __init__(self, is_checkpoint_fp8_serialized: bool = False):
|
| 44 |
+
self.is_checkpoint_fp8_serialized = is_checkpoint_fp8_serialized
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def get_supported_act_dtypes(cls) -> List[torch.dtype]:
|
| 48 |
+
return [torch.float16, torch.bfloat16]
|
| 49 |
+
|
| 50 |
+
@classmethod
|
| 51 |
+
def get_min_capability(cls) -> int:
|
| 52 |
+
return 89
|
| 53 |
+
|
| 54 |
+
@classmethod
|
| 55 |
+
def get_name(self) -> str:
|
| 56 |
+
return "w8a8_fp8"
|
| 57 |
+
|
| 58 |
+
@classmethod
|
| 59 |
+
def get_config_filenames(cls) -> List[str]:
|
| 60 |
+
return []
|
| 61 |
+
|
| 62 |
+
@classmethod
|
| 63 |
+
def from_config(cls, config: Dict[str, Any]) -> "W8A8Fp8Config":
|
| 64 |
+
quant_method = cls.get_from_keys(config, ["quant_method"])
|
| 65 |
+
is_checkpoint_fp8_serialized = (
|
| 66 |
+
"compressed-tensors" in quant_method or "w8a8_fp8" in quant_method
|
| 67 |
+
)
|
| 68 |
+
return cls(is_checkpoint_fp8_serialized=is_checkpoint_fp8_serialized)
|
| 69 |
+
|
| 70 |
+
def get_quant_method(
|
| 71 |
+
self,
|
| 72 |
+
layer: torch.nn.Module,
|
| 73 |
+
prefix: str,
|
| 74 |
+
) -> Optional["QuantizeMethodBase"]:
|
| 75 |
+
from sglang.srt.layers.linear import LinearBase
|
| 76 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
|
| 77 |
+
|
| 78 |
+
if isinstance(layer, LinearBase):
|
| 79 |
+
return W8A8Fp8LinearMethod(self)
|
| 80 |
+
elif isinstance(layer, FusedMoE):
|
| 81 |
+
return W8A8FP8MoEMethod(self)
|
| 82 |
+
return None
|
| 83 |
+
|
| 84 |
+
def get_scaled_act_names(self) -> List[str]:
|
| 85 |
+
return []
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class W8A8Fp8LinearMethod(LinearMethodBase):
|
| 89 |
+
|
| 90 |
+
def __init__(self, quantization_config: W8A8Fp8Config):
|
| 91 |
+
self.cutlass_fp8_supported = cutlass_fp8_supported()
|
| 92 |
+
self.quantization_config = quantization_config
|
| 93 |
+
|
| 94 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 95 |
+
weight = layer.weight
|
| 96 |
+
|
| 97 |
+
if self.quantization_config.is_checkpoint_fp8_serialized:
|
| 98 |
+
weight_scale = layer.weight_scale.detach()
|
| 99 |
+
# If checkpoint offline quantized with w8a8_fp8, load the weight and weight_scale directly.
|
| 100 |
+
if _is_hip:
|
| 101 |
+
weight, weight_scale, _ = normalize_e4m3fn_to_e4m3fnuz(
|
| 102 |
+
weight=weight, weight_scale=weight_scale
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
layer.weight = Parameter(weight.t(), requires_grad=False)
|
| 106 |
+
layer.weight_scale = Parameter(weight_scale, requires_grad=False)
|
| 107 |
+
else:
|
| 108 |
+
# If checkpoint not offline quantized, quantize the weights with per-channel quantization.
|
| 109 |
+
if self.cutlass_fp8_supported:
|
| 110 |
+
# if cutlass supported, we use cutlass_scaled_mm
|
| 111 |
+
# which requires per-channel quantization on weight
|
| 112 |
+
qweight, weight_scale = per_token_group_quant_fp8(
|
| 113 |
+
layer.weight, layer.weight.shape[-1]
|
| 114 |
+
)
|
| 115 |
+
weight_scale = weight_scale.t().contiguous()
|
| 116 |
+
if _is_hip:
|
| 117 |
+
weight, weight_scale, _ = normalize_e4m3fn_to_e4m3fnuz(
|
| 118 |
+
weight=weight, weight_scale=weight_scale
|
| 119 |
+
)
|
| 120 |
+
else:
|
| 121 |
+
# if cutlass not supported, we fall back to use torch._scaled_mm
|
| 122 |
+
# which requires per tensor quantization on weight
|
| 123 |
+
fp8_dtype = torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
| 124 |
+
qweight, weight_scale = input_to_float8(layer.weight, dtype=fp8_dtype)
|
| 125 |
+
|
| 126 |
+
# Update the layer with the new values.
|
| 127 |
+
layer.weight = Parameter(qweight.t(), requires_grad=False)
|
| 128 |
+
layer.weight_scale = Parameter(weight_scale, requires_grad=False)
|
| 129 |
+
layer.input_scale = None
|
| 130 |
+
|
| 131 |
+
def create_weights(
|
| 132 |
+
self,
|
| 133 |
+
layer: torch.nn.Module,
|
| 134 |
+
input_size_per_partition: int,
|
| 135 |
+
output_partition_sizes: List[int],
|
| 136 |
+
input_size: int,
|
| 137 |
+
output_size: int,
|
| 138 |
+
params_dtype: torch.dtype,
|
| 139 |
+
**extra_weight_attrs,
|
| 140 |
+
):
|
| 141 |
+
weight_dtype = (
|
| 142 |
+
torch.float8_e4m3fn
|
| 143 |
+
if self.quantization_config.is_checkpoint_fp8_serialized
|
| 144 |
+
else params_dtype
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
weight_loader = extra_weight_attrs.get("weight_loader")
|
| 148 |
+
self.logical_widths = output_partition_sizes
|
| 149 |
+
|
| 150 |
+
weight = ModelWeightParameter(
|
| 151 |
+
data=torch.empty(
|
| 152 |
+
sum(output_partition_sizes),
|
| 153 |
+
input_size_per_partition,
|
| 154 |
+
dtype=weight_dtype,
|
| 155 |
+
),
|
| 156 |
+
input_dim=1,
|
| 157 |
+
output_dim=0,
|
| 158 |
+
weight_loader=weight_loader,
|
| 159 |
+
)
|
| 160 |
+
layer.register_parameter("weight", weight)
|
| 161 |
+
|
| 162 |
+
if self.quantization_config.is_checkpoint_fp8_serialized:
|
| 163 |
+
weight_scale = ChannelQuantScaleParameter(
|
| 164 |
+
data=torch.empty((sum(output_partition_sizes), 1), dtype=torch.float32),
|
| 165 |
+
output_dim=0,
|
| 166 |
+
weight_loader=weight_loader,
|
| 167 |
+
)
|
| 168 |
+
layer.register_parameter("weight_scale", weight_scale)
|
| 169 |
+
else:
|
| 170 |
+
layer.weight_scale = None
|
| 171 |
+
|
| 172 |
+
def apply(
|
| 173 |
+
self,
|
| 174 |
+
layer: torch.nn.Module,
|
| 175 |
+
x: torch.Tensor,
|
| 176 |
+
bias: Optional[torch.Tensor] = None,
|
| 177 |
+
):
|
| 178 |
+
return apply_fp8_linear(
|
| 179 |
+
x,
|
| 180 |
+
layer.weight,
|
| 181 |
+
layer.weight_scale,
|
| 182 |
+
bias=bias,
|
| 183 |
+
cutlass_fp8_supported=self.cutlass_fp8_supported,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class W8A8FP8MoEMethod:
|
| 188 |
+
"""MoE method for FP8.
|
| 189 |
+
Supports loading FP8 checkpoints with static weight scale and
|
| 190 |
+
dynamic/static activation scale.
|
| 191 |
+
Also supports loading quantized FP16/BF16 model checkpoints with dynamic
|
| 192 |
+
activation scaling. The weight scaling factor will be initialized after
|
| 193 |
+
the model weights are loaded.
|
| 194 |
+
Args:
|
| 195 |
+
quant_config: The quantization config.
|
| 196 |
+
"""
|
| 197 |
+
|
| 198 |
+
def __new__(cls, *args, **kwargs):
|
| 199 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoEMethodBase
|
| 200 |
+
|
| 201 |
+
if not hasattr(cls, "_initialized"):
|
| 202 |
+
original_init = cls.__init__
|
| 203 |
+
new_cls = type(
|
| 204 |
+
cls.__name__,
|
| 205 |
+
(FusedMoEMethodBase,),
|
| 206 |
+
{
|
| 207 |
+
"__init__": original_init,
|
| 208 |
+
**{k: v for k, v in cls.__dict__.items() if k != "__dict__"},
|
| 209 |
+
},
|
| 210 |
+
)
|
| 211 |
+
obj = super(new_cls, new_cls).__new__(new_cls)
|
| 212 |
+
obj.__init__(*args, **kwargs)
|
| 213 |
+
return obj
|
| 214 |
+
return super().__new__(cls)
|
| 215 |
+
|
| 216 |
+
def __init__(self, quant_config):
|
| 217 |
+
self.quant_config = quant_config
|
| 218 |
+
|
| 219 |
+
def create_weights(
|
| 220 |
+
self,
|
| 221 |
+
layer: torch.nn.Module,
|
| 222 |
+
num_experts: int,
|
| 223 |
+
hidden_size: int,
|
| 224 |
+
intermediate_size: int,
|
| 225 |
+
params_dtype: torch.dtype,
|
| 226 |
+
**extra_weight_attrs,
|
| 227 |
+
):
|
| 228 |
+
from sglang.srt.layers.moe.fused_moe_triton import FusedMoeWeightScaleSupported
|
| 229 |
+
|
| 230 |
+
fp8_dtype = torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
| 231 |
+
# WEIGHTS
|
| 232 |
+
w13_weight = torch.nn.Parameter(
|
| 233 |
+
torch.empty(
|
| 234 |
+
num_experts, 2 * intermediate_size, hidden_size, dtype=fp8_dtype
|
| 235 |
+
),
|
| 236 |
+
requires_grad=False,
|
| 237 |
+
)
|
| 238 |
+
layer.register_parameter("w13_weight", w13_weight)
|
| 239 |
+
set_weight_attrs(w13_weight, extra_weight_attrs)
|
| 240 |
+
|
| 241 |
+
w2_weight = torch.nn.Parameter(
|
| 242 |
+
torch.empty(num_experts, hidden_size, intermediate_size, dtype=fp8_dtype),
|
| 243 |
+
requires_grad=False,
|
| 244 |
+
)
|
| 245 |
+
layer.register_parameter("w2_weight", w2_weight)
|
| 246 |
+
set_weight_attrs(w2_weight, extra_weight_attrs)
|
| 247 |
+
|
| 248 |
+
w13_weight_scale = torch.nn.Parameter(
|
| 249 |
+
torch.ones(num_experts, 2 * intermediate_size, 1, dtype=torch.float32),
|
| 250 |
+
requires_grad=False,
|
| 251 |
+
)
|
| 252 |
+
w2_weight_scale = torch.nn.Parameter(
|
| 253 |
+
torch.ones(num_experts, hidden_size, 1, dtype=torch.float32),
|
| 254 |
+
requires_grad=False,
|
| 255 |
+
)
|
| 256 |
+
layer.register_parameter("w13_weight_scale", w13_weight_scale)
|
| 257 |
+
layer.register_parameter("w2_weight_scale", w2_weight_scale)
|
| 258 |
+
|
| 259 |
+
extra_weight_attrs.update(
|
| 260 |
+
{"quant_method": FusedMoeWeightScaleSupported.CHANNEL.value}
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
set_weight_attrs(w13_weight_scale, extra_weight_attrs)
|
| 264 |
+
set_weight_attrs(w2_weight_scale, extra_weight_attrs)
|
| 265 |
+
|
| 266 |
+
w13_input_scale = None
|
| 267 |
+
layer.register_parameter("w13_input_scale", w13_input_scale)
|
| 268 |
+
|
| 269 |
+
w2_input_scale = None
|
| 270 |
+
layer.register_parameter("w2_input_scale", w2_input_scale)
|
| 271 |
+
|
| 272 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 273 |
+
layer.w13_weight = Parameter(layer.w13_weight, requires_grad=False)
|
| 274 |
+
layer.w2_weight = Parameter(layer.w2_weight, requires_grad=False)
|
| 275 |
+
layer.w13_weight_scale = Parameter(
|
| 276 |
+
layer.w13_weight_scale.data, requires_grad=False
|
| 277 |
+
)
|
| 278 |
+
layer.w2_weight_scale = Parameter(
|
| 279 |
+
layer.w2_weight_scale.data, requires_grad=False
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
def apply(
|
| 283 |
+
self,
|
| 284 |
+
layer: torch.nn.Module,
|
| 285 |
+
x: torch.Tensor,
|
| 286 |
+
router_logits: torch.Tensor,
|
| 287 |
+
top_k: int,
|
| 288 |
+
renormalize: bool,
|
| 289 |
+
use_grouped_topk: bool,
|
| 290 |
+
topk_group: Optional[int] = None,
|
| 291 |
+
num_expert_group: Optional[int] = None,
|
| 292 |
+
custom_routing_function: Optional[Callable] = None,
|
| 293 |
+
correction_bias: Optional[torch.Tensor] = None,
|
| 294 |
+
activation: str = "silu",
|
| 295 |
+
inplace: bool = True,
|
| 296 |
+
no_combine: bool = False,
|
| 297 |
+
routed_scaling_factor: Optional[float] = None,
|
| 298 |
+
) -> torch.Tensor:
|
| 299 |
+
from sglang.srt.layers.moe.fused_moe_triton.fused_moe import fused_experts
|
| 300 |
+
from sglang.srt.layers.moe.topk import select_experts
|
| 301 |
+
|
| 302 |
+
# Expert selection
|
| 303 |
+
topk_weights, topk_ids = select_experts(
|
| 304 |
+
hidden_states=x,
|
| 305 |
+
router_logits=router_logits,
|
| 306 |
+
use_grouped_topk=use_grouped_topk,
|
| 307 |
+
top_k=top_k,
|
| 308 |
+
renormalize=renormalize,
|
| 309 |
+
topk_group=topk_group,
|
| 310 |
+
num_expert_group=num_expert_group,
|
| 311 |
+
custom_routing_function=custom_routing_function,
|
| 312 |
+
correction_bias=correction_bias,
|
| 313 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
return fused_experts(
|
| 317 |
+
x,
|
| 318 |
+
layer.w13_weight,
|
| 319 |
+
layer.w2_weight,
|
| 320 |
+
topk_weights=topk_weights,
|
| 321 |
+
topk_ids=topk_ids,
|
| 322 |
+
inplace=inplace,
|
| 323 |
+
activation=activation,
|
| 324 |
+
use_fp8_w8a8=True,
|
| 325 |
+
per_channel_quant=True,
|
| 326 |
+
w1_scale=(layer.w13_weight_scale),
|
| 327 |
+
w2_scale=(layer.w2_weight_scale),
|
| 328 |
+
a1_scale=layer.w13_input_scale,
|
| 329 |
+
a2_scale=layer.w2_input_scale,
|
| 330 |
+
no_combine=no_combine,
|
| 331 |
+
)
|