[Model] Add native IFM K2 Horizon serving support (#37654)

Co-authored-by: Xiaoyu Zhang <1182563586@qq.com>
Co-authored-by: Xinyuan Tong <xinyuantong.cs@gmail.com>
This commit is contained in:
Yash Akhauri
2026-09-03 16:39:43 +08:00
committed by GitHub
co-authored by Xiaoyu Zhang Xinyuan Tong
parent 02d9b3060a
commit 3bac084d4e
34 changed files with 4487 additions and 16 deletions
@@ -0,0 +1,116 @@
import unittest
from sglang.srt.entrypoints.openai.protocol import (
ChatCompletionRequest,
ResponsesRequest,
)
from sglang.srt.parser.reasoning_parser import K2V3Detector, ReasoningParser
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=3, suite="base-a-test-cpu")
class TestK2V3ReasoningParser(CustomTestCase):
def test_reasoning_effort_selects_ifm_pair(self):
expected = {
"high": ("<ifm|think>", "</ifm|think>"),
"medium": ("<ifm|think_fast>", "</ifm|think_fast>"),
"low": ("<ifm|think_faster>", "</ifm|think_faster>"),
}
for effort, tokens in expected.items():
with self.subTest(effort=effort):
detector = K2V3Detector(reasoning_effort=effort)
self.assertEqual(
(detector.think_start_token, detector.think_end_token), tokens
)
self.assertEqual(
set(detector.request_selectable_think_end_tokens),
{
"</ifm|think>",
"</ifm|think_fast>",
"</ifm|think_faster>",
},
)
def test_release_template_fallback_uses_medium_pair(self):
# K2-Horizon-0.9B maps unsupported levels to <ifm|think_fast>.
detector = K2V3Detector(reasoning_effort="none")
self.assertEqual(detector.think_start_token, "<ifm|think_fast>")
def test_end_only_output_preserves_newlines(self):
result = K2V3Detector().detect_and_parse("\nreasoning\n</ifm|think>\nanswer")
self.assertEqual(result.reasoning_text, "\nreasoning\n")
self.assertEqual(result.normal_text, "\nanswer")
def test_tool_group_implicitly_ends_malformed_reasoning(self):
result = K2V3Detector().detect_and_parse(
"reasoning\n<ifm|tool_calls><ifm|tool_call>x</ifm|tool_call>"
)
self.assertEqual(result.reasoning_text, "reasoning\n")
self.assertTrue(result.normal_text.startswith("<ifm|tool_calls>"))
def test_streaming_partial_tags(self):
detector = K2V3Detector(reasoning_effort="medium")
reasoning = ""
normal = ""
wire = "work</ifm|think_fast>\nanswer"
for char in wire:
result = detector.parse_streaming_increment(char)
reasoning += result.reasoning_text
normal += result.normal_text
end = detector.finish()
reasoning += end.reasoning_text
normal += end.normal_text
self.assertEqual(reasoning, "work")
self.assertEqual(normal, "\nanswer")
def test_reasoning_parser_reads_request_effort(self):
request = ChatCompletionRequest(
model="IFM/K2-Horizon-7B",
messages=[{"role": "user", "content": "hi"}],
reasoning_effort="low",
)
parser = ReasoningParser("k2_horizon", request=request)
self.assertIsInstance(parser.detector, K2V3Detector)
self.assertEqual(parser.detector.think_end_token, "</ifm|think_faster>")
def test_template_kwarg_effort_has_rendering_precedence(self):
request = ChatCompletionRequest(
model="IFM/K2-Horizon-7B",
messages=[{"role": "user", "content": "hi"}],
reasoning_effort="high",
chat_template_kwargs={"reasoning_effort": "low"},
)
parser = ReasoningParser("k2_horizon", request=request)
self.assertEqual(parser.detector.think_end_token, "</ifm|think_faster>")
def test_responses_request_effort_selects_non_stream_delimiter(self):
end_tokens = {
"high": "</ifm|think>",
"medium": "</ifm|think_fast>",
"low": "</ifm|think_faster>",
}
for effort, end_token in end_tokens.items():
with self.subTest(effort=effort):
request = ResponsesRequest(
model="IFM/K2-Horizon-7B",
input="hi",
reasoning={"effort": effort},
store=False,
)
parser = ReasoningParser(
"k2_horizon", stream_reasoning=False, request=request
)
self.assertEqual(
parser.parse_non_stream(f"work{end_token}\nanswer"),
("work", "\nanswer"),
)
def test_force_reasoning_cannot_be_disabled(self):
with self.assertRaisesRegex(ValueError, "requires force_reasoning=True"):
ReasoningParser("k2_horizon", force_reasoning=False)
if __name__ == "__main__":
unittest.main()
@@ -483,6 +483,28 @@ class TestTemplateDetectionRuleMatrix(unittest.TestCase):
self.assertEqual(config.toggle_param, "enable_thinking")
self.assertTrue(config.default_enabled)
def test_k2_horizon_detects_always_on_reasoning_and_tool_parsers(self):
template = """
{% set tool_call_fmt = tool_call_format | default('xml') %}
{% set effort = reasoning_effort | default('high') %}
<ifm|think><ifm|think_fast><ifm|think_faster>
<ifm|tool_calls><ifm|tool_call>
"""
force, config = detect_reasoning_pattern(template)
tokenizer = _DummyTokenizer([])
self.assertTrue(force)
self.assertIsNotNone(config)
self.assertEqual(config.special_case, "always")
self.assertEqual(
detect_reasoning_parser(template, tokenizer, config, force),
"k2_horizon",
)
self.assertEqual(
detect_tool_call_parser(template, tokenizer, config, force),
"k2_horizon",
)
class TestToolCallParserDetection(unittest.TestCase):
"""Tests for detect_tool_call_parser() using real model tokenizers."""