[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:
co-authored by
Xiaoyu Zhang
Xinyuan Tong
parent
02d9b3060a
commit
3bac084d4e
@@ -43,6 +43,9 @@ from sglang.srt.parser.jinja_template_utils import (
|
||||
jinja_template_may_reorder_tool_results,
|
||||
)
|
||||
from sglang.srt.parser.template_detection import ReasoningToggleConfig
|
||||
from sglang.srt.sampling.sampling_params import (
|
||||
REQUEST_REASONING_END_TOKEN_IDS_KEY,
|
||||
)
|
||||
from sglang.srt.utils import get_or_create_event_loop
|
||||
from sglang.test.ci.ci_register import register_cpu_ci
|
||||
|
||||
@@ -794,6 +797,33 @@ class ServingChatTestCase(unittest.TestCase):
|
||||
|
||||
self.assertEqual(req.reasoning_effort, "high")
|
||||
|
||||
def test_k2_selected_terminator_reaches_sampling_params(self):
|
||||
self.tm._config_overrides["reasoning_parser"] = "k2_horizon"
|
||||
self.chat = OpenAIServingChat(self.tm, self.template_manager)
|
||||
self.template_manager.chat_template_name = None
|
||||
self.template_manager.jinja_template_content_format = "string"
|
||||
self.tm.tokenizer.apply_chat_template.return_value = [1, 2, 3]
|
||||
self.tm.tokenizer.encode.side_effect = lambda text, **_: (
|
||||
[8, 9] if text == "</ifm|think_fast>" else [1, 2, 3]
|
||||
)
|
||||
req = ChatCompletionRequest(
|
||||
model="IFM/K2-Horizon-7B",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
chat_template_kwargs={"reasoning_effort": "medium"},
|
||||
)
|
||||
|
||||
processed = self.chat._process_messages(req, is_multimodal=False)
|
||||
self.assertEqual(processed.reasoning_end_token_ids, [8, 9])
|
||||
|
||||
with patch.object(self.chat, "_process_messages", return_value=processed):
|
||||
adapted, _ = self.chat._convert_to_internal_request(req)
|
||||
self.assertEqual(
|
||||
adapted.sampling_params["custom_params"][
|
||||
REQUEST_REASONING_END_TOKEN_IDS_KEY
|
||||
],
|
||||
[8, 9],
|
||||
)
|
||||
|
||||
def test_kimi_tool_call_keeps_template_default_thinking(self):
|
||||
self.template_manager.chat_template_name = None
|
||||
self.template_manager.jinja_template_content_format = "string"
|
||||
|
||||
@@ -23,6 +23,9 @@ from sglang.srt.entrypoints.openai.serving_responses import (
|
||||
)
|
||||
from sglang.srt.function_call.core_types import ToolCallItem
|
||||
from sglang.srt.parser.template_detection import ReasoningToggleConfig
|
||||
from sglang.srt.sampling.sampling_params import (
|
||||
REQUEST_REASONING_END_TOKEN_IDS_KEY,
|
||||
)
|
||||
from sglang.test.ci.ci_register import register_cpu_ci
|
||||
from sglang.test.test_utils import CustomTestCase
|
||||
|
||||
@@ -246,6 +249,38 @@ class ChatToolForwardingTestCase(CustomTestCase):
|
||||
self.assertEqual(request_prompts, [[4, 5, 6]])
|
||||
self.assertEqual(engine_prompts, [[4, 5, 6]])
|
||||
|
||||
def test_k2_output_parser_reuses_effective_template_default(self):
|
||||
serving = make_serving()
|
||||
serving.reasoning_parser = "k2_horizon"
|
||||
serving.default_chat_template_kwargs = {"reasoning_effort": "low"}
|
||||
serving.template_manager.chat_template_name = None
|
||||
serving.tokenizer_manager.tokenizer.apply_chat_template.return_value = [4, 5, 6]
|
||||
request = ResponsesRequest(
|
||||
model="IFM/K2-Horizon-7B",
|
||||
input="hi",
|
||||
# Template kwargs are the final render inputs, so the server default
|
||||
# below takes precedence over this API convenience field.
|
||||
reasoning={"effort": "medium"},
|
||||
store=False,
|
||||
)
|
||||
|
||||
asyncio.run(
|
||||
serving._make_request(request, None, serving.tokenizer_manager.tokenizer)
|
||||
)
|
||||
|
||||
render_call = serving.tokenizer_manager.tokenizer.apply_chat_template.call_args
|
||||
self.assertEqual(render_call.kwargs["reasoning_effort"], "low")
|
||||
self.assertEqual(request.chat_template_kwargs["reasoning_effort"], "low")
|
||||
|
||||
output_items = serving._make_response_output_items(
|
||||
request,
|
||||
"work</ifm|think_faster>\nanswer",
|
||||
tokenizer=Mock(),
|
||||
require_reasoning=True,
|
||||
)
|
||||
self.assertEqual(output_items[0].content[0].text, "work")
|
||||
self.assertEqual(output_items[1].content[0].text, "\nanswer")
|
||||
|
||||
|
||||
class ReasoningRequestForwardingTestCase(unittest.TestCase):
|
||||
def test_create_responses_uses_processed_reasoning_state(self):
|
||||
@@ -263,6 +298,7 @@ class ReasoningRequestForwardingTestCase(unittest.TestCase):
|
||||
video_data=None,
|
||||
modalities=[],
|
||||
stop=[],
|
||||
reasoning_end_token_ids=[41, 42],
|
||||
)
|
||||
captured = {}
|
||||
|
||||
@@ -308,6 +344,12 @@ class ReasoningRequestForwardingTestCase(unittest.TestCase):
|
||||
|
||||
self.assertEqual(response.status, "completed")
|
||||
self.assertFalse(captured["adapted_request"].require_reasoning)
|
||||
self.assertEqual(
|
||||
captured["adapted_request"].sampling_params["custom_params"][
|
||||
REQUEST_REASONING_END_TOKEN_IDS_KEY
|
||||
],
|
||||
[41, 42],
|
||||
)
|
||||
self.assertFalse(parser_cls.call_args.kwargs["force_reasoning"])
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,37 @@ class NonHarmonyStreamTestCase(CustomTestCase):
|
||||
|
||||
self.assertTrue(parser_cls.call_args.kwargs["force_reasoning"])
|
||||
|
||||
def test_k2_nested_effort_selects_streaming_reasoning_delimiter(self):
|
||||
serving = make_serving()
|
||||
serving.reasoning_parser = "k2_horizon"
|
||||
serving.tool_call_parser = None
|
||||
request = ResponsesRequest(
|
||||
model="IFM/K2-Horizon-7B",
|
||||
input="hi",
|
||||
reasoning={"effort": "medium"},
|
||||
stream=True,
|
||||
store=False,
|
||||
)
|
||||
|
||||
events = StreamFixture(serving, request, require_reasoning=True).run(
|
||||
[engine_chunk("work</ifm|think_fast>\nanswer", 4, finish=True)]
|
||||
)
|
||||
types = event_types(events)
|
||||
payloads = event_payloads(events)
|
||||
reasoning = "".join(
|
||||
payload["delta"]
|
||||
for event_type, payload in zip(types, payloads)
|
||||
if event_type == "response.reasoning_text.delta"
|
||||
)
|
||||
answer = "".join(
|
||||
payload["delta"]
|
||||
for event_type, payload in zip(types, payloads)
|
||||
if event_type == "response.output_text.delta"
|
||||
)
|
||||
|
||||
self.assertEqual(reasoning, "work")
|
||||
self.assertEqual(answer, "\nanswer")
|
||||
|
||||
def test_emits_typed_sse_events_in_order(self):
|
||||
serving = make_serving()
|
||||
serving.reasoning_parser = None
|
||||
|
||||
Reference in New Issue
Block a user