94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
297 lines
9.4 KiB
Python
297 lines
9.4 KiB
Python
import hashlib
|
|
import logging
|
|
import time
|
|
from typing import Dict, Iterable, NamedTuple, Optional, Set
|
|
|
|
import torch
|
|
import torch.distributed as dist
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
from sglang.srt.managers.mm_utils import tensor_hash
|
|
from sglang.srt.utils.weight_checker_comparator import (
|
|
CHUNK_NUMEL,
|
|
ComparableWeight,
|
|
RawComparable,
|
|
compare_weights,
|
|
select_comparable_weight,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class _StrictBaseModel(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
|
class ParallelismInfo(_StrictBaseModel):
|
|
tp_rank: int
|
|
tp_size: int
|
|
dp_rank: int
|
|
dp_size: int
|
|
pp_rank: int
|
|
pp_size: int
|
|
rank: int
|
|
size: int
|
|
|
|
|
|
class ChecksumInfo(_StrictBaseModel):
|
|
checksums: Dict[str, str]
|
|
per_gpu_checksum: str
|
|
parallelism_info: ParallelismInfo
|
|
|
|
|
|
class CheckEntry(NamedTuple):
|
|
name: str
|
|
should_compare: bool
|
|
comparable: ComparableWeight
|
|
|
|
|
|
class QuantizedWeight(NamedTuple):
|
|
comparable_cls: type[ComparableWeight]
|
|
scale_name: str
|
|
|
|
|
|
_NON_PERSISTENT_BUFFER_PATTERNS = (
|
|
"cos_sin_cache",
|
|
"inv_freq",
|
|
"freqs_cis",
|
|
"_weight_fp32",
|
|
)
|
|
|
|
|
|
def _is_non_persistent_buffer_name(name: str) -> bool:
|
|
return any(pat in name for pat in _NON_PERSISTENT_BUFFER_PATTERNS)
|
|
|
|
|
|
class WeightChecker:
|
|
def __init__(self, model_runner):
|
|
self._model_runner = model_runner
|
|
self._snapshot_tensors = None
|
|
|
|
def handle(self, action: str, allow_quant_error: bool = False) -> Optional[Dict]:
|
|
logger.info(
|
|
f"[WeightChecker] handle action={action} allow_quant_error={allow_quant_error}"
|
|
)
|
|
if action == "snapshot":
|
|
return self._snapshot()
|
|
elif action == "reset_tensors":
|
|
return self._reset_tensors()
|
|
elif action == "compare":
|
|
return self._compare(allow_quant_error=allow_quant_error)
|
|
elif action == "checksum":
|
|
return self._compute_checksum()
|
|
else:
|
|
raise Exception(f"Unsupported {action=}")
|
|
|
|
def _snapshot(self):
|
|
named_tensors = [
|
|
(name, param.data.detach().cpu()) for name, param in self._model_state()
|
|
]
|
|
self._snapshot_tensors = dict(named_tensors)
|
|
assert len(self._snapshot_tensors) == len(
|
|
named_tensors
|
|
), f"should not have duplicated tensor name"
|
|
|
|
def _reset_tensors(self):
|
|
for name, param in self._model_state():
|
|
if _is_non_persistent_buffer_name(name):
|
|
continue
|
|
param.copy_(_random_like(param))
|
|
|
|
def _compare(self, allow_quant_error: bool = False):
|
|
assert self._snapshot_tensors is not None
|
|
|
|
quantized_set = _build_quantized_set(self._model_runner.model)
|
|
skip_compare_names = {
|
|
name
|
|
for name, param in self._model_state()
|
|
if getattr(param, "_skip_weight_check", False)
|
|
}
|
|
_check_tensors(
|
|
expect_tensors=_build_check_entries(
|
|
self._snapshot_tensors, skip_compare_names, quantized_set
|
|
),
|
|
actual_tensors=_build_check_entries(
|
|
dict(self._model_state()), skip_compare_names, quantized_set
|
|
),
|
|
allow_quant_error=allow_quant_error,
|
|
)
|
|
|
|
def _compute_checksum(self) -> Dict:
|
|
torch.cuda.synchronize()
|
|
start = time.perf_counter()
|
|
|
|
quantized_set = _build_quantized_set(self._model_runner.model)
|
|
skip_compare_names = {
|
|
name
|
|
for name, param in self._model_state()
|
|
if getattr(param, "_skip_weight_check", False)
|
|
}
|
|
|
|
# Hash the dequantized weight so two (qweight, scale) pairs with the same
|
|
# bf16 hash equal.
|
|
checksums = {}
|
|
for name, should_compare, comparable in _build_check_entries(
|
|
dict(self._model_state()), skip_compare_names, quantized_set
|
|
):
|
|
if should_compare:
|
|
checksums[name] = _hash_tensor(comparable.dequantize().data)
|
|
|
|
h = hashlib.sha256()
|
|
for name in sorted(checksums):
|
|
h.update(name.encode())
|
|
h.update(checksums[name].encode())
|
|
overall = h.hexdigest()
|
|
|
|
torch.cuda.synchronize()
|
|
elapsed = time.perf_counter() - start
|
|
logger.info(
|
|
f"[WeightChecker] checksum computed for {len(checksums)} tensors in {elapsed:.3f}s"
|
|
)
|
|
|
|
info = ChecksumInfo(
|
|
checksums=checksums,
|
|
per_gpu_checksum=overall,
|
|
parallelism_info=self._parallelism_info(),
|
|
)
|
|
return info.model_dump()
|
|
|
|
def _parallelism_info(self) -> ParallelismInfo:
|
|
mr = self._model_runner
|
|
return ParallelismInfo(
|
|
tp_rank=mr.tp_rank,
|
|
tp_size=mr.tp_size,
|
|
dp_rank=mr.dp_rank if mr.dp_rank is not None else 0,
|
|
dp_size=mr.dp_size,
|
|
pp_rank=mr.pp_rank,
|
|
pp_size=mr.pp_size,
|
|
rank=dist.get_rank() if dist.is_initialized() else 0,
|
|
size=dist.get_world_size() if dist.is_initialized() else 1,
|
|
)
|
|
|
|
def _model_state(self):
|
|
yield from self._model_runner.model.named_parameters()
|
|
yield from self._model_runner.model.named_buffers()
|
|
|
|
|
|
def _hash_tensor(t: torch.Tensor) -> str:
|
|
return f"{tensor_hash(t):016x}"
|
|
|
|
|
|
def _check_tensors(
|
|
expect_tensors: Iterable[CheckEntry],
|
|
actual_tensors: Iterable[CheckEntry],
|
|
allow_quant_error: bool = False,
|
|
):
|
|
good_names = []
|
|
error_messages = []
|
|
info_messages = []
|
|
|
|
for (expect_name, should_compare, expect_comparable), (
|
|
actual_name,
|
|
actual_should_compare,
|
|
actual_comparable,
|
|
) in zip(expect_tensors, actual_tensors, strict=True):
|
|
assert expect_name == actual_name, f"{expect_name=} {actual_name=}"
|
|
assert (
|
|
should_compare == actual_should_compare
|
|
), f"{should_compare=} {actual_should_compare=}"
|
|
name = expect_name
|
|
|
|
try:
|
|
equal, max_abs_err, mean_abs_err, num_exceed = compare_weights(
|
|
expect_comparable, actual_comparable
|
|
)
|
|
except Exception as e:
|
|
e.add_note(
|
|
f"when handling {name=} expect={expect_comparable!r} actual={actual_comparable!r}"
|
|
)
|
|
raise
|
|
if equal:
|
|
good_names.append(name)
|
|
continue
|
|
msg = (
|
|
f"name={name} "
|
|
f"max_abs_err={max_abs_err} "
|
|
f"mean_abs_err={mean_abs_err} "
|
|
f"num_exceed={num_exceed} "
|
|
f"expect={expect_comparable!r} actual={actual_comparable!r} "
|
|
)
|
|
if not should_compare:
|
|
info_messages.append(msg)
|
|
elif allow_quant_error and num_exceed == 0:
|
|
info_messages.append(msg + "(within quantization ULP tolerance)")
|
|
else:
|
|
error_messages.append(msg)
|
|
|
|
logger.info(f"[check_tensors] equal tensors: {good_names}")
|
|
if len(info_messages) > 0:
|
|
logger.info(f"[check_tensors] info: {info_messages}")
|
|
if len(error_messages) > 0:
|
|
raise Exception(f"check tensor equality failed:\n" + "\n".join(error_messages))
|
|
|
|
|
|
def _random_like(t: torch.Tensor):
|
|
device = t.device
|
|
shape = t.shape
|
|
dtype = t.dtype
|
|
|
|
if dtype.is_floating_point:
|
|
out = torch.empty(shape, device=device, dtype=dtype)
|
|
for chunk in out.view(-1).split(CHUNK_NUMEL):
|
|
chunk.copy_(
|
|
torch.rand(chunk.shape, device=device, dtype=torch.float32).to(dtype)
|
|
)
|
|
return out
|
|
|
|
if dtype == torch.bool:
|
|
return torch.rand(shape, device=device) > 0.5
|
|
|
|
info = torch.iinfo(dtype)
|
|
return torch.randint(
|
|
low=int(info.min), high=int(info.max), size=shape, device=device, dtype=dtype
|
|
)
|
|
|
|
|
|
def _build_quantized_set(model) -> Dict[str, QuantizedWeight]:
|
|
"""Run the router over the model: {weight_name: QuantizedWeight} for each
|
|
quantized weight; weights absent from the set compare raw."""
|
|
quantized_set = {}
|
|
for module_name, module in model.named_modules():
|
|
comparable_cls = select_comparable_weight(getattr(module, "quant_method", None))
|
|
if comparable_cls is None:
|
|
continue
|
|
prefix = f"{module_name}." if module_name else ""
|
|
own = {name for name, _ in module.named_parameters(recurse=False)}
|
|
for name in own:
|
|
scale = name.replace("weight", "weight_scale_inv")
|
|
if name.endswith("weight") and scale in own:
|
|
quantized_set[prefix + name] = QuantizedWeight(
|
|
comparable_cls, prefix + scale
|
|
)
|
|
return quantized_set
|
|
|
|
|
|
def _build_check_entries(
|
|
raw: Dict[str, torch.Tensor],
|
|
skip_compare_names: Set[str],
|
|
quantized_set: Optional[Dict[str, QuantizedWeight]] = None,
|
|
) -> Iterable[CheckEntry]:
|
|
"""Yields a CheckEntry per weight; quantized weights consume their scale, everything
|
|
else is raw."""
|
|
skip_compare_names = set(skip_compare_names)
|
|
quantized_set = quantized_set or {}
|
|
scale_names = {qw.scale_name for qw in quantized_set.values()}
|
|
|
|
for name, tensor in raw.items():
|
|
if name in scale_names:
|
|
continue # compared via its weight's comparable
|
|
if name in quantized_set:
|
|
qw = quantized_set[name]
|
|
yield CheckEntry(name, True, qw.comparable_cls(tensor, raw[qw.scale_name]))
|
|
else:
|
|
should_compare = name not in skip_compare_names and (
|
|
not _is_non_persistent_buffer_name(name)
|
|
)
|
|
yield CheckEntry(name, should_compare, RawComparable(tensor))
|