Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions python/sglang/srt/model_loader/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,62 @@ def load_weights_only(model, weights, target_device):
for name, loaded_weight in weights
)

try:
from sglang.srt.runtime_context import get_model

model_bag = get_model()
runtime_kv_dtype = getattr(model_bag, "kv_cache_dtype", None)

kv_scheme = None
if quant_config is not None and hasattr(quant_config, "kv_cache_scheme"):
kv_scheme = quant_config.kv_cache_scheme
elif hasattr(model, "config") and hasattr(model.config, "hf_config"):
quant_cfg = getattr(model.config.hf_config, "quantization_config", None)
if isinstance(quant_cfg, dict):
kv_scheme = quant_cfg.get("kv_cache_scheme")
elif hasattr(quant_cfg, "kv_cache_scheme"):
kv_scheme = getattr(quant_cfg, "kv_cache_scheme", None)

if isinstance(kv_scheme, dict):
checkpoint_bits = kv_scheme.get("num_bits")
expected_bits = None
if runtime_kv_dtype:

@waynehacking8 waynehacking8 Oct 21, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the default --kv-cache-dtype auto this drops the scales: expected_bits stays None for "auto", but configure_kv_cache_dtype later resolves auto to fp8_e4m3 for compressed-tensors checkpoints with a supported kv_cache_scheme, so the FP8 pool runs with scale 1.0. A probe of the filter shows the scales kept on base eb4b167d27, dropped on this head, and kept again with the change below (bfloat16 still drops them): https://gist.github.com/waynehacking8/0685c4403346f321f0896860aa882f42. A (8, "auto", False) case with kv_cache_quant_algo="FP8" in the new test would cover it.

Suggested change
if runtime_kv_dtype:
if runtime_kv_dtype == "auto":
# configure_kv_cache_dtype resolves auto to an FP8 pool here.
algo = getattr(quant_config, "kv_cache_quant_algo", None)
if isinstance(algo, str) and algo.upper() == "FP8":
expected_bits = 8
elif runtime_kv_dtype:

@devtyagi3909 devtyagi3909 Oct 21, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see the catch on the auto resolution for compressed-tensors. applied the suggestion and added the (8, "auto", False) test case with kv_cache_quant_algo="FP8". pushed.

if "fp8" in runtime_kv_dtype or "int8" in runtime_kv_dtype:
expected_bits = 8
elif "fp4" in runtime_kv_dtype:
expected_bits = 4
elif (
"bf16" in runtime_kv_dtype
or "bfloat16" in runtime_kv_dtype
or "fp16" in runtime_kv_dtype
or "float16" in runtime_kv_dtype
):
expected_bits = 16

if checkpoint_bits and (
expected_bits is None or checkpoint_bits != expected_bits
):
logger.info(
"ignoring kv cache scales from checkpoint (checkpoint_bits=%s, runtime_dtype=%s). "
"mismatch prevents scale corruption; falling back to dynamic scales.",
checkpoint_bits,
runtime_kv_dtype,
)

def _filter_kv_scales(ws):
for n, w in ws:
if (
n.endswith("k_scale")
or n.endswith("v_scale")
or n.endswith("kv_scale")
):
continue
yield n, w

weights = _filter_kv_scales(weights)
except (ValueError, AttributeError):
pass

weights = monokernel_weights_iterator(model=model, weights=weights)
model.load_weights(weights)
if (is_nvfp4_online or is_modelopt_fp4_online) and target_device.type == "cuda":
Expand Down
58 changes: 58 additions & 0 deletions test/registered/unit/model_loader/test_default_model_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,64 @@ def load_weights_and_postprocess(self, model, weights, target_device):
startup_prefetch_active=True,
)

def test_kv_cache_scale_dropped_on_mismatch(self):
cases = [
# (checkpoint_bits, runtime_kv_dtype, expect_scales_dropped)
(8, "nvfp4", True),
(8, "bfloat16", True),
(8, "fp8", False),
(4, "nvfp4", False),
(4, "fp8", True),
]
for checkpoint_bits, runtime_dtype, expect_dropped in cases:
with self.subTest(ckpt=checkpoint_bits, runtime=runtime_dtype):
model = Mock()
model.quant_config = SimpleNamespace(
kv_cache_scheme={"num_bits": checkpoint_bits},
is_nvfp4_online=False,
get_name=lambda: "dummy",
)
loaded = []
model.load_weights.side_effect = lambda ws: loaded.extend(list(ws))

dummy_weights = [
("model.layers.0.self_attn.q_proj.weight", torch.zeros(1)),
("model.layers.0.self_attn.k_scale", torch.ones(1)),
("model.layers.0.self_attn.v_scale", torch.ones(1)),
]

model_bag = SimpleNamespace(kv_cache_dtype=runtime_dtype)
with (
patch.object(loader_mod, "is_cuda_alike", return_value=False),
patch(
"sglang.srt.runtime_context.get_model",
return_value=model_bag,
),
patch.object(
loader_mod,
"monokernel_weights_iterator",
side_effect=lambda model, weights: weights,
),
):
DefaultModelLoader.load_weights_only(
model, iter(dummy_weights), torch.device("cpu")
)

loaded_names = [name for name, _ in loaded]
if expect_dropped:
self.assertEqual(
loaded_names, ["model.layers.0.self_attn.q_proj.weight"]
)
else:
self.assertEqual(
loaded_names,
[
"model.layers.0.self_attn.q_proj.weight",
"model.layers.0.self_attn.k_scale",
"model.layers.0.self_attn.v_scale",
],
)


if __name__ == "__main__":
unittest.main()
Loading