chore: vendor sglang v0.5.10 snapshot
This commit is contained in:
92
third_party/sglang/benchmark/kernels/quantization/README.md
vendored
Normal file
92
third_party/sglang/benchmark/kernels/quantization/README.md
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
# W8A8 Block-wise Quantization Kernel Tuning
|
||||
|
||||
Auto-tune Triton FP8/INT8 block-wise quantization kernels for optimal performance.
|
||||
|
||||
## When to Use Triton FP8 Block-wise Quantization Kernel vs DeepGEMM
|
||||
|
||||
**Use Triton FP8 Block-wise Quantization Kernel when:**
|
||||
- Output dtype is NOT `bfloat16` (e.g., `float16`, `float32`)
|
||||
- DeepGEMM is disabled (environment variable `SGLANG_ENABLE_JIT_DEEPGEMM=0`)
|
||||
- Running on GPUs with compute capability < SM90 (DeepGEMM requires SM90+)
|
||||
- You need cross-platform compatibility (Triton works on both NVIDIA and AMD GPUs)
|
||||
|
||||
**Use DeepGEMM when:**
|
||||
- Output dtype is `bfloat16` AND DeepGEMM is enabled
|
||||
- Running on NVIDIA GPUs with compute capability >= SM90 (e.g., H100, H200)
|
||||
- Need maximum performance for production workloads (DeepGEMM is highly optimized for Hopper architecture)
|
||||
|
||||
**Note:** DeepGEMM requires CUDA compute capability >= 9.0 (SM90+). It is specifically optimized for NVIDIA Hopper GPUs (H100/H200).
|
||||
|
||||
The kernel selection logic in SGLang automatically chooses DeepGEMM when conditions are met (see `w8a8_block_fp8_matmul` function in `fp8_kernel.py`), otherwise falls back to Triton implementation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Default (DeepSeek-V3):**
|
||||
```bash
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --tp-size 8
|
||||
```
|
||||
|
||||
**Custom Model (specify N and K):**
|
||||
```bash
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 5120 --K 25600
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `--N`, `--K`: Weight matrix dimensions (N=output_dim, K=input_dim). If not specified, uses `--tp-size` for DeepSeek-V3
|
||||
- `--tp-size`: Tensor parallelism size for DeepSeek-V3 (default: 8)
|
||||
- `--input-type`: `fp8` or `int8` (default: fp8)
|
||||
- `--block-n`, `--block-k`: Block quantization granularity (default: 128)
|
||||
- `--batch-size`: Test single batch size (optional)
|
||||
|
||||
## How to Calculate N and K
|
||||
|
||||
For a linear layer `y = xW^T` where `x` is (M, K) and `W` is (N, K):
|
||||
- **N**: Output features (weight matrix output dimension)
|
||||
- **K**: Input features (weight matrix input dimension)
|
||||
|
||||
**Example: Qwen3-VL-32B** (hidden_size=5120, intermediate_size=25600, num_heads=64, num_kv_heads=8, head_dim=128) and TP=1
|
||||
```bash
|
||||
# QKV projection: Q(8192) + K(1024) + V(1024) = 10240
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 10240 --K 5120
|
||||
|
||||
# MLP gate+up (SwiGLU): 2 * intermediate_size = 51200
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 51200 --K 5120
|
||||
|
||||
# MLP down projection
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 5120 --K 25600
|
||||
|
||||
# O projection (if separate from QKV)
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 5120 --K 8192
|
||||
```
|
||||
|
||||
If TP=8:
|
||||
|
||||
```bash
|
||||
# QKV projection: Q(8192) + K(1024) + V(1024) = 10240 / TP=8
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 1280 --K 5120
|
||||
|
||||
# MLP gate+up (SwiGLU): 2 * intermediate_size = 51200 / TP=8
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 6400 --K 5120
|
||||
|
||||
# MLP down projection
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 5120 --K 3200
|
||||
|
||||
# O projection (if separate from QKV)
|
||||
python benchmark/kernels/quantization/tuning_block_wise_kernel.py --N 5120 --K 1024
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Generates JSON config files saved to `python/sglang/srt/layers/quantization/configs/`:
|
||||
```
|
||||
N={N},K={K},device_name={DEVICE},dtype=fp8_w8a8,block_shape=[128,128].json
|
||||
```
|
||||
|
||||
Config maps batch size to optimal kernel parameters:
|
||||
```json
|
||||
{
|
||||
"1": {"BLOCK_SIZE_M": 16, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 128, ...},
|
||||
"2048": {"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 128, ...}
|
||||
}
|
||||
```
|
||||
137
third_party/sglang/benchmark/kernels/quantization/bench_fp4_quant.py
vendored
Normal file
137
third_party/sglang/benchmark/kernels/quantization/bench_fp4_quant.py
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
import argparse
|
||||
import itertools
|
||||
|
||||
import torch
|
||||
import triton
|
||||
from flashinfer import (
|
||||
scaled_fp4_grouped_quantize,
|
||||
silu_and_mul_scaled_nvfp4_experts_quantize,
|
||||
)
|
||||
from sgl_kernel.elementwise import silu_and_mul
|
||||
|
||||
from sglang.benchmark.bench_utils import run_bench
|
||||
from sglang.srt.layers import deep_gemm_wrapper
|
||||
from sglang.srt.layers.moe.ep_moe.kernels import silu_and_mul_masked_post_quant_fwd
|
||||
|
||||
|
||||
def _test_accuracy_once(E, M, K, input_dtype, device):
|
||||
x = torch.randn(E, M, K, device=device, dtype=input_dtype)
|
||||
glb_scales = torch.ones((E,), dtype=torch.float32, device=device)
|
||||
masks = torch.full((E,), M, dtype=torch.int32, device=device)
|
||||
out, blk_scales = silu_and_mul_scaled_nvfp4_experts_quantize(x, masks, glb_scales)
|
||||
out1, blk_scales1 = scaled_fp4_grouped_quantize(
|
||||
silu_and_mul(x),
|
||||
masks,
|
||||
glb_scales,
|
||||
)
|
||||
|
||||
torch.testing.assert_close(out, out1)
|
||||
torch.testing.assert_close(blk_scales, blk_scales1)
|
||||
print(f"E: {E}, M: {M}, K: {K}, type: {input_dtype} OK")
|
||||
|
||||
|
||||
NUM_RANKS = 48
|
||||
M_PER_RANKs = [128, 256, 512, 1024]
|
||||
Ms = [M_PER_RANK * NUM_RANKS for M_PER_RANK in M_PER_RANKs]
|
||||
Ks = [2048, 4096, 7168]
|
||||
|
||||
|
||||
@triton.testing.perf_report(
|
||||
triton.testing.Benchmark(
|
||||
x_names=["M", "K"],
|
||||
x_vals=list(itertools.product(Ms, Ks)),
|
||||
x_log=False,
|
||||
line_arg="provider",
|
||||
line_vals=["triton_fp8", "cuda_unfused_fp4", "cuda_fused_fp4"],
|
||||
line_names=["triton_fp8", "cuda_unfused_fp4", "cuda_fused_fp4"],
|
||||
styles=[("blue", "-"), ("orange", "-"), ("green", "-")],
|
||||
ylabel="ms",
|
||||
plot_name="fp4 quant",
|
||||
args={},
|
||||
)
|
||||
)
|
||||
def benchmark(M, K, provider):
|
||||
E = 6
|
||||
device = "cuda"
|
||||
x = torch.randn(E, M, K, device=device, dtype=torch.bfloat16)
|
||||
glb_scales = torch.ones((E,), dtype=torch.float32, device=device)
|
||||
masks = torch.randint(1, 4096, (E,), dtype=torch.int32, device=device)
|
||||
fp8_out = torch.empty(
|
||||
(
|
||||
x.shape[0],
|
||||
x.shape[1],
|
||||
x.shape[2] // 2,
|
||||
),
|
||||
device=x.device,
|
||||
dtype=torch.float8_e4m3fn,
|
||||
)
|
||||
scale_block_size = 128
|
||||
fp8_scales = torch.empty(
|
||||
(
|
||||
x.shape[0],
|
||||
x.shape[1],
|
||||
x.shape[2] // 2 // scale_block_size,
|
||||
),
|
||||
device=x.device,
|
||||
dtype=torch.float32,
|
||||
)
|
||||
|
||||
quantiles = (0.5, 0.2, 0.8)
|
||||
if provider == "triton_fp8":
|
||||
ms, min_ms, max_ms = run_bench(
|
||||
lambda: silu_and_mul_masked_post_quant_fwd(
|
||||
x,
|
||||
fp8_out,
|
||||
fp8_scales,
|
||||
scale_block_size,
|
||||
masks,
|
||||
scale_ue8m0=deep_gemm_wrapper.DEEPGEMM_SCALE_UE8M0,
|
||||
),
|
||||
quantiles=quantiles,
|
||||
)
|
||||
if provider == "cuda_unfused_fp4":
|
||||
ms, min_ms, max_ms = run_bench(
|
||||
lambda: scaled_fp4_grouped_quantize(
|
||||
silu_and_mul(x),
|
||||
masks,
|
||||
glb_scales,
|
||||
),
|
||||
quantiles=quantiles,
|
||||
)
|
||||
if provider == "cuda_fused_fp4":
|
||||
ms, min_ms, max_ms = run_bench(
|
||||
lambda: silu_and_mul_scaled_nvfp4_experts_quantize(
|
||||
x,
|
||||
masks,
|
||||
glb_scales,
|
||||
),
|
||||
quantiles=quantiles,
|
||||
)
|
||||
|
||||
return ms, min_ms, max_ms
|
||||
|
||||
|
||||
def test_accuracy():
|
||||
E = 6
|
||||
N_RANKS = 48
|
||||
Ms = [128, 256, 512, 1024]
|
||||
Ks = [2048, 4096, 7168]
|
||||
input_dtype = torch.bfloat16
|
||||
for M in Ms:
|
||||
for K in Ks:
|
||||
_test_accuracy_once(E, N_RANKS * M, K, input_dtype, "cuda")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--save_path",
|
||||
type=str,
|
||||
default="./bench_fp4_quant_res",
|
||||
help="Path to save fp4 quant benchmark results",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
test_accuracy()
|
||||
|
||||
benchmark.run(print_data=True, show_plots=True, save_path=args.save_path)
|
||||
95
third_party/sglang/benchmark/kernels/quantization/bench_int8_quant.py
vendored
Normal file
95
third_party/sglang/benchmark/kernels/quantization/bench_int8_quant.py
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import argparse
|
||||
|
||||
import torch
|
||||
import triton
|
||||
from vllm._custom_ops import scaled_int8_quant as vllm_scaled_int8_quant
|
||||
|
||||
from sglang.benchmark.bench_utils import run_bench
|
||||
from sglang.srt.layers.quantization.int8_kernel import per_token_quant_int8
|
||||
|
||||
|
||||
@torch.compile(backend="inductor")
|
||||
def torch_int8_quant(x):
|
||||
int8_max = torch.iinfo(torch.int8).max
|
||||
|
||||
abs_max = x.abs().max(dim=-1, keepdim=True).values
|
||||
scales = abs_max.to(torch.float32) / float(int8_max)
|
||||
|
||||
q_x = (x / scales).round().to(torch.int8)
|
||||
|
||||
return q_x, scales
|
||||
|
||||
|
||||
def _test_accuracy_once(M, K, input_dtype, device):
|
||||
x = torch.randn(M, K, dtype=input_dtype, device=device) * 5000
|
||||
out, scales, _ = vllm_scaled_int8_quant(x, symmetric=True)
|
||||
out1, scales1 = per_token_quant_int8(x)
|
||||
out2, scales2 = torch_int8_quant(x)
|
||||
torch.testing.assert_close(out, out2, atol=1, rtol=0)
|
||||
torch.testing.assert_close(out, out1, atol=1, rtol=0)
|
||||
torch.testing.assert_close(scales, scales2)
|
||||
torch.testing.assert_close(scales1, scales2)
|
||||
print(f"M: {M}, K: {K}, type: {input_dtype} OK")
|
||||
|
||||
|
||||
def test_accuracy():
|
||||
Ms = [1, 13, 128, 1024, 2048, 4096]
|
||||
Ks = [512, 1024, 2048, 8192]
|
||||
input_dtypes = [torch.float16, torch.bfloat16]
|
||||
for M in Ms:
|
||||
for K in Ks:
|
||||
for input_dtype in input_dtypes:
|
||||
_test_accuracy_once(M, K, input_dtype, "cuda")
|
||||
|
||||
|
||||
@triton.testing.perf_report(
|
||||
triton.testing.Benchmark(
|
||||
x_names=["batch_size"],
|
||||
x_vals=[1, 16, 32, 64, 128, 256, 512, 1024, 2048],
|
||||
x_log=False,
|
||||
line_arg="provider",
|
||||
line_vals=["vllm op", "triton", "torch.compile"],
|
||||
line_names=["vllm op", "triton", "torch.compile"],
|
||||
styles=[("blue", "-"), ("orange", "-"), ("red", "-")],
|
||||
ylabel="ms",
|
||||
plot_name="int8 per token quant",
|
||||
args={},
|
||||
)
|
||||
)
|
||||
def benchmark(batch_size, provider):
|
||||
M, K = batch_size, 16384
|
||||
x = torch.randn(M, K, dtype=torch.float16, device="cuda") * 1000
|
||||
|
||||
quantiles = (0.5, 0.2, 0.8)
|
||||
if provider == "vllm op":
|
||||
ms, min_ms, max_ms = run_bench(
|
||||
lambda: vllm_scaled_int8_quant(x, symmetric=True),
|
||||
quantiles=quantiles,
|
||||
)
|
||||
if provider == "triton":
|
||||
ms, min_ms, max_ms = run_bench(
|
||||
lambda: per_token_quant_int8(x),
|
||||
quantiles=quantiles,
|
||||
)
|
||||
if provider == "torch.compile":
|
||||
ms, min_ms, max_ms = run_bench(
|
||||
lambda: torch_int8_quant(x),
|
||||
quantiles=quantiles,
|
||||
)
|
||||
|
||||
return ms, min_ms, max_ms
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--save_path",
|
||||
type=str,
|
||||
default="./bench_int8_quant_res",
|
||||
help="Path to save int8 quant benchmark results",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
test_accuracy()
|
||||
|
||||
benchmark.run(print_data=True, show_plots=True, save_path=args.save_path)
|
||||
527
third_party/sglang/benchmark/kernels/quantization/tuning_block_wise_kernel.py
vendored
Normal file
527
third_party/sglang/benchmark/kernels/quantization/tuning_block_wise_kernel.py
vendored
Normal file
@@ -0,0 +1,527 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import multiprocessing as mp
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import torch
|
||||
import triton
|
||||
from tqdm import tqdm
|
||||
|
||||
mp.set_start_method("spawn", force=True)
|
||||
|
||||
from sglang.srt.layers.quantization.fp8_kernel import (
|
||||
_w8a8_block_fp8_matmul,
|
||||
_w8a8_block_fp8_matmul_unrolledx4,
|
||||
)
|
||||
from sglang.srt.layers.quantization.int8_kernel import _w8a8_block_int8_matmul
|
||||
from sglang.srt.utils import (
|
||||
get_device,
|
||||
get_device_core_count,
|
||||
get_device_count,
|
||||
get_device_name,
|
||||
is_hip,
|
||||
)
|
||||
|
||||
_is_hip = is_hip()
|
||||
|
||||
DTYPE_MAP = {
|
||||
"float32": torch.float32,
|
||||
"float16": torch.float16,
|
||||
"half": torch.half,
|
||||
"bfloat16": torch.bfloat16,
|
||||
}
|
||||
|
||||
|
||||
def w8a8_block_matmul(
|
||||
A: torch.Tensor,
|
||||
B: torch.Tensor,
|
||||
As: torch.Tensor,
|
||||
Bs: torch.Tensor,
|
||||
block_size: List[int],
|
||||
config: Dict[str, Any],
|
||||
output_dtype: torch.dtype = torch.float16,
|
||||
) -> torch.Tensor:
|
||||
"""This function performs matrix multiplication with block-wise quantization.
|
||||
|
||||
It takes two input tensors `A` and `B` with scales `As` and `Bs`.
|
||||
The output is returned in the specified `output_dtype`.
|
||||
|
||||
Args:
|
||||
A: The input tensor, e.g., activation.
|
||||
B: The input tensor, e.g., weight.
|
||||
As: The per-token-group quantization scale for `A`.
|
||||
Bs: The per-block quantization scale for `B`.
|
||||
block_size: The block size for per-block quantization. It should be 2-dim, e.g., [128, 128].
|
||||
output_dytpe: The dtype of the returned tensor.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The result of matmul.
|
||||
"""
|
||||
assert len(block_size) == 2
|
||||
block_n, block_k = block_size[0], block_size[1]
|
||||
|
||||
assert A.shape[-1] == B.shape[-1]
|
||||
assert A.shape[:-1] == As.shape[:-1] and A.is_contiguous()
|
||||
assert triton.cdiv(A.shape[-1], block_k) == As.shape[-1]
|
||||
M = A.numel() // A.shape[-1]
|
||||
|
||||
assert B.ndim == 2 and B.is_contiguous() and Bs.ndim == 2
|
||||
N, K = B.shape
|
||||
assert triton.cdiv(N, block_n) == Bs.shape[0]
|
||||
assert triton.cdiv(K, block_k) == Bs.shape[1]
|
||||
|
||||
C_shape = A.shape[:-1] + (N,)
|
||||
C = A.new_empty(C_shape, dtype=output_dtype)
|
||||
|
||||
needs_masking = bool(K % config["BLOCK_SIZE_K"] != 0)
|
||||
|
||||
def grid(META):
|
||||
return (
|
||||
triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),
|
||||
)
|
||||
|
||||
# Use manually unrolledx4 kernel on AMD GPU when the grid size is small.
|
||||
# Empirical testing shows the sweet spot lies when it's less than the # of
|
||||
# compute units available on the device.
|
||||
num_workgroups = triton.cdiv(M, config["BLOCK_SIZE_M"]) * triton.cdiv(
|
||||
N, config["BLOCK_SIZE_N"]
|
||||
)
|
||||
|
||||
if A.dtype == torch.float8_e4m3fnuz or A.dtype == torch.float8_e4m3fn:
|
||||
kernel = (
|
||||
_w8a8_block_fp8_matmul_unrolledx4
|
||||
if (_is_hip == True and num_workgroups <= get_device_core_count())
|
||||
else _w8a8_block_fp8_matmul
|
||||
)
|
||||
else:
|
||||
kernel = _w8a8_block_int8_matmul
|
||||
|
||||
kernel[grid](
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
As,
|
||||
Bs,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
block_n,
|
||||
block_k,
|
||||
A.stride(-2),
|
||||
A.stride(-1),
|
||||
B.stride(1),
|
||||
B.stride(0),
|
||||
C.stride(-2),
|
||||
C.stride(-1),
|
||||
As.stride(-2),
|
||||
As.stride(-1),
|
||||
Bs.stride(1),
|
||||
Bs.stride(0),
|
||||
**config,
|
||||
needs_masking=needs_masking,
|
||||
)
|
||||
|
||||
return C
|
||||
|
||||
|
||||
def get_rocm_configs_compute_bound():
|
||||
configs = []
|
||||
waves_per_eu_range = 0
|
||||
for num_stages in [2]:
|
||||
for block_m in [32, 64, 128, 256]:
|
||||
for block_k in [32, 64, 128, 256]:
|
||||
for block_n in [16, 32, 64, 128, 256]:
|
||||
for num_warps in [4, 8]:
|
||||
for group_size in [1, 4, 8, 16, 32]:
|
||||
configs.append(
|
||||
{
|
||||
"BLOCK_SIZE_M": block_m,
|
||||
"BLOCK_SIZE_N": block_n,
|
||||
"BLOCK_SIZE_K": block_k,
|
||||
"GROUP_SIZE_M": group_size,
|
||||
"num_warps": num_warps,
|
||||
"num_stages": num_stages,
|
||||
"waves_per_eu": waves_per_eu_range,
|
||||
}
|
||||
)
|
||||
return configs
|
||||
|
||||
|
||||
def get_configs_compute_bound():
|
||||
configs = []
|
||||
if _is_hip:
|
||||
configs = get_rocm_configs_compute_bound()
|
||||
else:
|
||||
for num_stages in [2, 3, 4, 5]:
|
||||
for block_m in [16, 32, 64, 128, 256]:
|
||||
for block_k in [64, 128]:
|
||||
for block_n in [32, 64, 128, 256]:
|
||||
for num_warps in [4, 8]:
|
||||
for group_size in [1, 16, 32, 64]:
|
||||
configs.append(
|
||||
{
|
||||
"BLOCK_SIZE_M": block_m,
|
||||
"BLOCK_SIZE_N": block_n,
|
||||
"BLOCK_SIZE_K": block_k,
|
||||
"GROUP_SIZE_M": group_size,
|
||||
"num_warps": num_warps,
|
||||
"num_stages": num_stages,
|
||||
}
|
||||
)
|
||||
return configs
|
||||
|
||||
|
||||
def get_weight_shapes(tp_size):
|
||||
# NOTE(HandH1998): The weight shapes only works for DeepSeek-V3. Modify them, if you tune for another different model.
|
||||
# cannot TP
|
||||
total = [
|
||||
(512 + 64, 7168),
|
||||
((128 + 64) * 128, 7168),
|
||||
(128 * (128 + 128), 512),
|
||||
(7168, 16384),
|
||||
(7168, 18432),
|
||||
]
|
||||
# N can TP
|
||||
n_tp = [
|
||||
(18432 * 2, 7168),
|
||||
((128 + 64) * 128, 7168),
|
||||
(128 * (128 + 128), 512),
|
||||
(24576, 1536),
|
||||
(4096, 7168),
|
||||
]
|
||||
# K can TP
|
||||
k_tp = [(7168, 18432), (7168, 16384), (7168, 2048)]
|
||||
|
||||
weight_shapes = []
|
||||
for t in total:
|
||||
weight_shapes.append(t)
|
||||
for n_t in n_tp:
|
||||
new_t = (n_t[0] // tp_size, n_t[1])
|
||||
weight_shapes.append(new_t)
|
||||
for k_t in k_tp:
|
||||
new_t = (k_t[0], k_t[1] // tp_size)
|
||||
weight_shapes.append(new_t)
|
||||
return weight_shapes
|
||||
|
||||
|
||||
def benchmark_config(
|
||||
A, B, As, Bs, block_size, config, out_dtype=torch.float16, num_iters=10
|
||||
):
|
||||
def run():
|
||||
w8a8_block_matmul(A, B, As, Bs, block_size, config, out_dtype)
|
||||
|
||||
torch.get_device_module().synchronize()
|
||||
# JIT complication & warmup
|
||||
for _ in range(5):
|
||||
run()
|
||||
torch.get_device_module().synchronize()
|
||||
|
||||
start_event = torch.get_device_module().Event(enable_timing=True)
|
||||
end_event = torch.get_device_module().Event(enable_timing=True)
|
||||
|
||||
latencies: List[float] = []
|
||||
for i in range(num_iters):
|
||||
torch.get_device_module().synchronize()
|
||||
start_event.record()
|
||||
run()
|
||||
end_event.record()
|
||||
end_event.synchronize()
|
||||
latencies.append(start_event.elapsed_time(end_event))
|
||||
avg = sum(latencies) / (num_iters * 10) * 1000 # us
|
||||
return avg
|
||||
|
||||
|
||||
def tune(M, N, K, block_size, out_dtype, search_space, input_type):
|
||||
factor_for_scale = 1e-2
|
||||
device = get_device()
|
||||
|
||||
if input_type == "fp8":
|
||||
fp8_info = torch.finfo(
|
||||
torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
||||
)
|
||||
fp8_max, fp8_min = fp8_info.max, fp8_info.min
|
||||
|
||||
A_fp32 = (
|
||||
(torch.rand(M, K, dtype=torch.float32, device=device) - 0.5) * 2 * fp8_max
|
||||
)
|
||||
A = A_fp32.clamp(min=fp8_min, max=fp8_max).to(
|
||||
torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
||||
)
|
||||
|
||||
B_fp32 = (
|
||||
(torch.rand(N, K, dtype=torch.float32, device=device) - 0.5) * 2 * fp8_max
|
||||
)
|
||||
B = B_fp32.clamp(min=fp8_min, max=fp8_max).to(
|
||||
torch.float8_e4m3fnuz if _is_hip else torch.float8_e4m3fn
|
||||
)
|
||||
else:
|
||||
int8_info = torch.iinfo(torch.int8)
|
||||
int8_max, int8_min = int8_info.max, int8_info.min
|
||||
|
||||
A_fp32 = (
|
||||
(torch.rand(M, K, dtype=torch.float32, device=device) - 0.5) * 2 * int8_max
|
||||
)
|
||||
A = A_fp32.clamp(min=int8_min, max=int8_max).to(torch.int8)
|
||||
|
||||
B_fp32 = (
|
||||
(torch.rand(N, K, dtype=torch.float32, device=device) - 0.5) * 2 * int8_max
|
||||
)
|
||||
B = B_fp32.clamp(min=int8_min, max=int8_max).to(torch.int8)
|
||||
|
||||
block_n, block_k = block_size[0], block_size[1]
|
||||
n_tiles = (N + block_n - 1) // block_n
|
||||
k_tiles = (K + block_k - 1) // block_k
|
||||
|
||||
As = torch.rand(M, k_tiles, dtype=torch.float32, device=device) * factor_for_scale
|
||||
Bs = (
|
||||
torch.rand(n_tiles, k_tiles, dtype=torch.float32, device=device)
|
||||
* factor_for_scale
|
||||
)
|
||||
|
||||
best_config = None
|
||||
best_time = float("inf")
|
||||
for config in tqdm(search_space):
|
||||
try:
|
||||
kernel_time = benchmark_config(
|
||||
A,
|
||||
B,
|
||||
As,
|
||||
Bs,
|
||||
block_size,
|
||||
config,
|
||||
out_dtype,
|
||||
num_iters=10,
|
||||
)
|
||||
except triton.runtime.autotuner.OutOfResources:
|
||||
# Some configurations may be invalid and fail to compile.
|
||||
continue
|
||||
|
||||
if kernel_time < best_time:
|
||||
best_time = kernel_time
|
||||
best_config = config
|
||||
now = datetime.now()
|
||||
print(f"{now.ctime()}] Completed tuning for batch_size={M}")
|
||||
assert best_config is not None
|
||||
return best_config
|
||||
|
||||
|
||||
def save_configs(
|
||||
N,
|
||||
K,
|
||||
block_n,
|
||||
block_k,
|
||||
configs,
|
||||
save_path,
|
||||
input_type="fp8",
|
||||
lock=None,
|
||||
) -> None:
|
||||
os.makedirs(save_path, exist_ok=True)
|
||||
device_name = get_device_name().replace(" ", "_")
|
||||
json_file_name = f"N={N},K={K},device_name={device_name},dtype={input_type}_w8a8,block_shape=[{block_n}, {block_k}].json"
|
||||
|
||||
config_file_path = os.path.join(save_path, json_file_name)
|
||||
print(f"Writing best config to {config_file_path}...")
|
||||
|
||||
if lock is not None:
|
||||
lock.acquire()
|
||||
try:
|
||||
existing_configs = {}
|
||||
if os.path.exists(config_file_path):
|
||||
with open(config_file_path, "r") as f:
|
||||
existing_configs = json.load(f)
|
||||
existing_configs = {int(k): v for k, v in existing_configs.items()}
|
||||
|
||||
existing_configs.update(configs)
|
||||
|
||||
with open(config_file_path, "w") as f:
|
||||
json.dump(existing_configs, f, indent=4)
|
||||
f.write("\n")
|
||||
finally:
|
||||
if lock is not None:
|
||||
lock.release()
|
||||
|
||||
|
||||
def tune_on_gpu(args_dict):
|
||||
"""Run tuning on a specific GPU."""
|
||||
gpu_id = args_dict["gpu_id"]
|
||||
batch_sizes = args_dict["batch_sizes"]
|
||||
weight_shapes = args_dict["weight_shapes"]
|
||||
args = args_dict["args"]
|
||||
lock = args_dict["lock"]
|
||||
|
||||
torch.get_device_module().set_device(gpu_id)
|
||||
print(f"Starting tuning on GPU {gpu_id} with batch sizes {batch_sizes}")
|
||||
|
||||
block_n = args.block_n
|
||||
block_k = args.block_k
|
||||
out_dtype = DTYPE_MAP[args.out_dtype]
|
||||
save_path = args.save_path
|
||||
input_type = args.input_type
|
||||
|
||||
search_space = get_configs_compute_bound()
|
||||
search_space = [
|
||||
config for config in search_space if block_k % config["BLOCK_SIZE_K"] == 0
|
||||
]
|
||||
|
||||
start = time.perf_counter()
|
||||
results = {}
|
||||
for shape in tqdm(weight_shapes, desc=f"GPU {gpu_id} - Shapes"):
|
||||
N, K = shape[0], shape[1]
|
||||
print(f"[GPU {gpu_id}] Tune for weight shape of `N: {N}, K: {K}`")
|
||||
benchmark_results = [
|
||||
tune(
|
||||
batch_size,
|
||||
N,
|
||||
K,
|
||||
[block_n, block_k],
|
||||
out_dtype,
|
||||
search_space,
|
||||
input_type,
|
||||
)
|
||||
for batch_size in tqdm(batch_sizes, desc=f"GPU {gpu_id} - Batch sizes")
|
||||
]
|
||||
best_configs = {M: config for M, config in zip(batch_sizes, benchmark_results)}
|
||||
save_configs(N, K, block_n, block_k, best_configs, save_path, input_type, lock)
|
||||
|
||||
end = time.perf_counter()
|
||||
print(f"Tuning on GPU {gpu_id} took {end - start:.2f} seconds")
|
||||
|
||||
|
||||
def distribute_batch_sizes(batch_sizes, num_gpus):
|
||||
"""Distribute batch sizes across available GPUs."""
|
||||
batches_per_gpu = []
|
||||
for i in range(num_gpus):
|
||||
start_idx = i * len(batch_sizes) // num_gpus
|
||||
end_idx = (i + 1) * len(batch_sizes) // num_gpus
|
||||
batches_per_gpu.append(batch_sizes[start_idx:end_idx])
|
||||
return batches_per_gpu
|
||||
|
||||
|
||||
def main(args):
|
||||
print(args)
|
||||
|
||||
num_gpus = get_device_count()
|
||||
if num_gpus == 0:
|
||||
raise RuntimeError("No GPU available for tuning")
|
||||
print(f"Found {num_gpus} GPUs for parallel tuning")
|
||||
|
||||
torch.get_device_module().init()
|
||||
|
||||
if args.batch_size is None:
|
||||
batch_sizes = [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
16,
|
||||
24,
|
||||
32,
|
||||
48,
|
||||
64,
|
||||
96,
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
1536,
|
||||
2048,
|
||||
3072,
|
||||
4096,
|
||||
]
|
||||
else:
|
||||
batch_sizes = [args.batch_size]
|
||||
num_gpus = 1 # If only one batch size, use only one GPU
|
||||
|
||||
# Support manual N and K specification
|
||||
if args.N is not None and args.K is not None:
|
||||
weight_shapes = [(args.N, args.K)]
|
||||
print(f"Using manually specified weight shape: N={args.N}, K={args.K}")
|
||||
else:
|
||||
weight_shapes = get_weight_shapes(args.tp_size)
|
||||
print(f"Using predefined weight shapes for TP size {args.tp_size}")
|
||||
|
||||
batches_per_gpu = distribute_batch_sizes(batch_sizes, num_gpus)
|
||||
|
||||
ctx = mp.get_context("spawn")
|
||||
manager = ctx.Manager()
|
||||
lock = manager.Lock()
|
||||
|
||||
process_args = []
|
||||
for gpu_id in range(num_gpus):
|
||||
process_args.append(
|
||||
{
|
||||
"gpu_id": gpu_id,
|
||||
"batch_sizes": batches_per_gpu[gpu_id],
|
||||
"weight_shapes": weight_shapes, # Each GPU processes all weight shapes
|
||||
"args": args,
|
||||
"lock": lock,
|
||||
}
|
||||
)
|
||||
|
||||
with ctx.Pool(num_gpus) as pool:
|
||||
pool.map(tune_on_gpu, process_args)
|
||||
|
||||
print("Multi-GPU tuning completed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
"--tp-size",
|
||||
"-tp",
|
||||
type=int,
|
||||
default=8,
|
||||
help="Tensor parallelism size (ignored if --N and --K are specified)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--N",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Output dimension of weight matrix (number of columns)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--K",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Input dimension of weight matrix (number of rows)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input-type", type=str, choices=["fp8", "int8"], default="fp8"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dtype",
|
||||
type=str,
|
||||
choices=["float32", "float16", "bfloat16", "half"],
|
||||
default="float16",
|
||||
)
|
||||
parser.add_argument("--block-n", type=int, default=128)
|
||||
parser.add_argument("--block-k", type=int, default=128)
|
||||
parser.add_argument("--batch-size", type=int, required=False)
|
||||
parser.add_argument(
|
||||
"--save-path", type=str, default="python/sglang/srt/layers/quantization/configs"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Validate arguments
|
||||
if (args.N is None) != (args.K is None):
|
||||
parser.error("--N and --K must be specified together or not at all")
|
||||
|
||||
main(args)
|
||||
Reference in New Issue
Block a user