ZhengyangZhang commited on
Commit
8086a45
·
verified ·
1 Parent(s): 995408e

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/sglang/srt/__pycache__/conversation.cpython-312.pyc +0 -0
  2. lib/python3.12/site-packages/sglang/srt/__pycache__/function_call_parser.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/sglang/srt/__pycache__/mm_utils.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/sglang/srt/__pycache__/warmup.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/base_attn_backend.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/cutlass_mla_backend.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/double_sparsity_backend.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashattention_backend.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashinfer_backend.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashinfer_mla_backend.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/flashmla_backend.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/torch_native_backend.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/triton_backend.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/utils.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/sglang/srt/layers/attention/__pycache__/vision.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/decode_attention.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/double_sparsity_attention.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/extend_attention.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/prefill_attention.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/sglang/srt/layers/attention/triton_ops/__pycache__/rocm_mla_decode_rope.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/sglang/srt/layers/quantization/__init__.py +334 -0
  22. lib/python3.12/site-packages/sglang/srt/layers/quantization/awq.py +204 -0
  23. lib/python3.12/site-packages/sglang/srt/layers/quantization/base_config.py +140 -0
  24. lib/python3.12/site-packages/sglang/srt/layers/quantization/blockwise_int8.py +412 -0
  25. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__init__.py +0 -0
  26. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/__init__.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/compressed_tensors.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/compressed_tensors_moe.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/__pycache__/utils.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/compressed_tensors.py +664 -0
  31. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/compressed_tensors_moe.py +677 -0
  32. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__init__.py +11 -0
  33. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__pycache__/compressed_tensors_w8a16_fp8.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/__pycache__/compressed_tensors_w8a8_fp8.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_scheme.py +56 -0
  36. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a16_fp8.py +153 -0
  37. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/schemes/compressed_tensors_w8a8_fp8.py +159 -0
  38. lib/python3.12/site-packages/sglang/srt/layers/quantization/compressed_tensors/utils.py +218 -0
  39. lib/python3.12/site-packages/sglang/srt/layers/quantization/deep_gemm.py +385 -0
  40. lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8.py +997 -0
  41. lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8_kernel.py +1068 -0
  42. lib/python3.12/site-packages/sglang/srt/layers/quantization/fp8_utils.py +514 -0
  43. lib/python3.12/site-packages/sglang/srt/layers/quantization/gptq.py +439 -0
  44. lib/python3.12/site-packages/sglang/srt/layers/quantization/int8_kernel.py +412 -0
  45. lib/python3.12/site-packages/sglang/srt/layers/quantization/int8_utils.py +73 -0
  46. lib/python3.12/site-packages/sglang/srt/layers/quantization/kv_cache.py +89 -0
  47. lib/python3.12/site-packages/sglang/srt/layers/quantization/modelopt_quant.py +463 -0
  48. lib/python3.12/site-packages/sglang/srt/layers/quantization/moe_wna16.py +505 -0
  49. lib/python3.12/site-packages/sglang/srt/layers/quantization/utils.py +147 -0
  50. 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
+ )