fix: warm up Kimi VLM vision encoder at startup (#31298)

This commit is contained in:
Mick
2026-07-20 08:50:57 +08:00
committed by GitHub
parent b3570a4531
commit 6a25dd7b5f
2 changed files with 101 additions and 1 deletions
+42 -1
View File
@@ -2023,6 +2023,46 @@ def _admin_api_key_missing_response(
# Minimal 32x32 black PNG (base64, GLM4v requires at least 32x32 sized image) # Minimal 32x32 black PNG (base64, GLM4v requires at least 32x32 sized image)
MINIMUM_PNG_PICTURE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAbUlEQVRYhe3VsQ2AMAxE0Y/lIgNQULD/OqyCMgCihCKSG4yRuKuiNH6JLsoEbMACOGBcua9HOR7Y6w6swBwMy0qLTpkeI77qdEBpBFAHBBDAGH8WrwJKI4AAegUCfAKgEgpQDvh3CR3oQCuav58qlAw73kKCSgAAAABJRU5ErkJggg==" MINIMUM_PNG_PICTURE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAbUlEQVRYhe3VsQ2AMAxE0Y/lIgNQULD/OqyCMgCihCKSG4yRuKuiNH6JLsoEbMACOGBcua9HOR7Y6w6swBwMy0qLTpkeI77qdEBpBFAHBBDAGH8WrwJKI4AAegUCfAKgEgpQDvh3CR3oQCuav58qlAw73kKCSgAAAABJRU5ErkJggg=="
# Kimi K2.5/K2.7 runs its MoonViT position interpolation through torch.compile.
# The minimal image above does not exercise a representative image shape and
# leaves the first client request paying the compilation cost. Keep this
# narrowly scoped: a larger default warmup image would unnecessarily lengthen
# startup for VLMs whose encoders do not have this behavior.
KIMI_VLM_WARMUP_PNG_PICTURE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAADEUlEQVR42u3BgQAAAADDoPlTX+EAVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBvArQAAf/YBFAAAAAASUVORK5CYII="
# Kimi K3's native vision preprocessing uses a 14x14 spatial patch size. A
# 448x448 image exercises a representative 32x32 patch grid without padding.
KIMI_K3_VLM_WARMUP_PNG_PICTURE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAcAAAAHACAIAAAC6Ry8kAAACX0lEQVR42u3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAtwEyRwAB32QPDQAAAABJRU5ErkJggg=="
def _get_vlm_warmup_image_base64(model_info: dict) -> str:
"""Choose the VLM image used by the startup warmup request.
A 512x512 image triggers Kimi K2.5/K2.7's representative compiled
position-interpolation path during startup. Kimi K3 uses a 448x448 image
matching its native vision patch grid. This keeps one-time vision setup
work out of the first external image request.
Other VLMs retain the minimal image to avoid changing their startup cost.
"""
architectures = model_info.get("architectures") or []
if (
"KimiK3ForConditionalGeneration" in architectures
or model_info.get("model_type") == "kimi_k3"
):
logger.info(
"Using a 448x448 image for Kimi K3 VLM startup warmup to exercise "
"its native 32x32 vision patch grid."
)
return KIMI_K3_VLM_WARMUP_PNG_PICTURE_BASE64
if "KimiK25ForConditionalGeneration" in architectures:
logger.info(
"Using a 512x512 image for Kimi VLM startup warmup to compile "
"MoonViT position interpolation."
)
return KIMI_VLM_WARMUP_PNG_PICTURE_BASE64
return MINIMUM_PNG_PICTURE_BASE64
async def _send_disaggregation_warmup_requests( async def _send_disaggregation_warmup_requests(
server_args: ServerArgs, server_args: ServerArgs,
@@ -2137,7 +2177,8 @@ def _execute_server_warmup(server_args: ServerArgs):
{ {
"type": "image_url", "type": "image_url",
"image_url": { "image_url": {
"url": f"data:image/png;base64,{MINIMUM_PNG_PICTURE_BASE64}" "url": "data:image/png;base64,"
f"{_get_vlm_warmup_image_base64(model_info)}"
}, },
}, },
{ {
@@ -0,0 +1,59 @@
"""Unit tests for model-specific server warmup inputs."""
import base64
import struct
import unittest
from sglang.srt.entrypoints.http_server import (
KIMI_K3_VLM_WARMUP_PNG_PICTURE_BASE64,
KIMI_VLM_WARMUP_PNG_PICTURE_BASE64,
MINIMUM_PNG_PICTURE_BASE64,
_get_vlm_warmup_image_base64,
)
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
class TestVlmWarmupImage(CustomTestCase):
def test_kimi_k2_uses_representative_vision_image(self):
image_base64 = _get_vlm_warmup_image_base64(
{"architectures": ["KimiK25ForConditionalGeneration"]}
)
self.assertEqual(image_base64, KIMI_VLM_WARMUP_PNG_PICTURE_BASE64)
png = base64.b64decode(KIMI_VLM_WARMUP_PNG_PICTURE_BASE64)
self.assertEqual(png[:8], b"\x89PNG\r\n\x1a\n")
self.assertEqual(struct.unpack(">II", png[16:24]), (512, 512))
def test_kimi_k3_uses_native_patch_grid_image(self):
for model_info in (
{"architectures": ["KimiK3ForConditionalGeneration"]},
{"architectures": None, "model_type": "kimi_k3"},
):
with self.subTest(model_info=model_info):
self.assertEqual(
_get_vlm_warmup_image_base64(model_info),
KIMI_K3_VLM_WARMUP_PNG_PICTURE_BASE64,
)
png = base64.b64decode(KIMI_K3_VLM_WARMUP_PNG_PICTURE_BASE64)
self.assertEqual(png[:8], b"\x89PNG\r\n\x1a\n")
self.assertEqual(struct.unpack(">II", png[16:24]), (448, 448))
def test_other_vlms_keep_minimal_startup_image(self):
self.assertEqual(
_get_vlm_warmup_image_base64(
{"architectures": ["Qwen3VLForConditionalGeneration"]}
),
MINIMUM_PNG_PICTURE_BASE64,
)
self.assertEqual(
_get_vlm_warmup_image_base64({"architectures": None}),
MINIMUM_PNG_PICTURE_BASE64,
)
if __name__ == "__main__":
unittest.main()