项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

311 行
12 KiB
Python

import asyncio
import unittest
from unittest.mock import Mock
from sglang.srt.entrypoints.openai.protocol import V1RerankReqInput
from sglang.srt.managers.tokenizer_manager_score_mixin import ScoreResult
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci
# Keep consistent with other openai_server/basic unit tests.
register_cuda_ci(est_time=9, stage="base-b", runner_config="1-gpu-large")
register_amd_ci(est_time=10, suite="stage-b-test-1-gpu-small-amd")
try:
from sglang.srt.entrypoints.openai.serving_rerank import (
OpenAIServingRerank,
_is_qwen3_reranker_template,
_qwen3_rerank_score,
_render_jinja_chat_template,
)
except ModuleNotFoundError as e:
# Some minimal environments used for unit tests may not have FastAPI/torch installed.
# Skip this test in that case.
if e.name in ("fastapi", "torch"):
OpenAIServingRerank = None # type: ignore[assignment]
else:
raise
class _DummyModelConfig:
# Keep consistent with TokenizerManager.model_config usage
is_generation = False
class _DummyTokenizer:
chat_template = ""
class _DummyTokenizerManager:
# Minimal surface required by OpenAIServingBase/OpenAIServingRerank
server_args = object()
model_config = _DummyModelConfig()
tokenizer = _DummyTokenizer()
async def generate_request(self, *_args, **_kwargs):
raise AssertionError("generate_request should not be called in this unit test")
@unittest.skipIf(OpenAIServingRerank is None, "fastapi/torch is not installed")
class TestOpenAIServingRerankUnit(unittest.TestCase):
def setUp(self):
self.handler = OpenAIServingRerank(_DummyTokenizerManager())
def test_convert_to_internal_request_cross_encoder_pairs(self):
req = V1RerankReqInput(
query="q",
documents=["doc-a", "doc-b"],
instruct="Retrieve semantically similar text.",
)
adapted, processed = self.handler._convert_to_internal_request(req)
# Avoid importing EmbeddingReqInput (requires torch). Use duck-typing checks instead.
self.assertTrue(hasattr(adapted, "is_cross_encoder_request"))
self.assertTrue(adapted.is_cross_encoder_request)
self.assertEqual(getattr(adapted, "text"), [["q", "doc-a"], ["q", "doc-b"]])
self.assertEqual(processed, req)
def test_convert_to_internal_request_qwen3_template_returns_request(self):
tm = _DummyTokenizerManager()
tm.tokenizer.chat_template = (
'... Note that the answer can only be "yes" or "no". ...'
)
handler = OpenAIServingRerank(tm)
req = V1RerankReqInput(query="q", documents=["d1"])
adapted, processed = handler._convert_to_internal_request(req)
self.assertIs(adapted, req)
self.assertIs(processed, req)
def test_build_rerank_response_embedding_list_uses_first_scalar(self):
req = V1RerankReqInput(
query="q",
documents=["doc-a", "doc-b"],
return_documents=True,
)
# Two results with embedding as list, should coerce embedding[0] to float.
# Also verifies sorting (doc-b > doc-a).
ret = [
{"embedding": [0.1, 0.2], "meta_info": {"id": "a"}},
{"embedding": [0.9, -1.0], "meta_info": {"id": "b"}},
]
res = self.handler._build_rerank_response(ret, req)
self.assertEqual(len(res), 2)
# Sorted descending by score, so doc-b first.
self.assertEqual(res[0].document, "doc-b")
self.assertEqual(res[0].index, 1)
self.assertAlmostEqual(res[0].score, 0.9)
self.assertEqual(res[0].meta_info, {"id": "b"})
self.assertEqual(res[1].document, "doc-a")
self.assertEqual(res[1].index, 0)
self.assertAlmostEqual(res[1].score, 0.1)
self.assertEqual(res[1].meta_info, {"id": "a"})
def test_build_rerank_response_float_list(self):
req = V1RerankReqInput(
query="q", documents=["a", "b", "c"], return_documents=True
)
scores = [0.2, 0.9, 0.1]
res = self.handler._build_rerank_response(scores, req)
self.assertEqual([r.document for r in res], ["b", "a", "c"])
self.assertEqual([r.index for r in res], [1, 0, 2])
self.assertAlmostEqual(res[0].score, 0.9)
self.assertAlmostEqual(res[1].score, 0.2)
self.assertAlmostEqual(res[2].score, 0.1)
def test_helper_is_qwen3_reranker_template(self):
self.assertTrue(
_is_qwen3_reranker_template(
'Note that the answer can only be "yes" or "no".'
)
)
self.assertFalse(_is_qwen3_reranker_template("plain template"))
def test_helper_qwen3_rerank_score(self):
self.assertAlmostEqual(_qwen3_rerank_score(0.9, 0.1), 0.9)
self.assertAlmostEqual(_qwen3_rerank_score(0.0, 0.0), 0.0)
def test_helper_render_jinja_chat_template(self):
# Skip if jinja2 isn't installed in this environment.
try:
import jinja2 # noqa: F401
except ModuleNotFoundError:
self.skipTest("jinja2 is not installed")
tpl = "{{ instruct | default('DEF') }}|{{ messages[0]['content'] }}|{{ messages[1]['content'] }}"
self.assertEqual(
_render_jinja_chat_template(tpl, query="Q", document="D", instruct=None),
"DEF|Q|D",
)
self.assertEqual(
_render_jinja_chat_template(tpl, query="Q", document="D", instruct="I"),
"I|Q|D",
)
def test_handle_non_streaming_request_qwen3_path_uses_score_prompts(self):
class _TM(_DummyTokenizerManager):
def __init__(self):
self.server_args = object()
self.model_config = Mock()
self.model_config.is_generation = True
self.model_config.model_path = "qwen/qwen3"
self.tokenizer = Mock()
self.tokenizer.chat_template = (
'Note that the answer can only be "yes" or "no". '
"{{ messages[0]['content'] }} {{ messages[1]['content'] }}"
)
async def score_prompts(
self, prompts, label_token_ids, apply_softmax, request
):
# Return [p_yes, p_no] for each prompt
assert len(prompts) == 2
assert label_token_ids and len(label_token_ids) == 2
return ScoreResult(scores=[[0.9, 0.1], [0.2, 0.8]], prompt_tokens=42)
handler = OpenAIServingRerank(_TM())
req = V1RerankReqInput(query="q", documents=["d1", "d2"], return_documents=True)
adapted, _ = handler._convert_to_internal_request(req)
raw_request = Mock()
res = asyncio.run(
handler._handle_non_streaming_request(adapted, req, raw_request)
)
self.assertEqual([r.document for r in res], ["d1", "d2"])
self.assertAlmostEqual(res[0].score, 0.9 / (0.9 + 0.1))
self.assertAlmostEqual(res[1].score, 0.2 / (0.2 + 0.8))
def test_build_rerank_response_return_documents_false(self):
"""Test that document field is None when return_documents=False"""
req = V1RerankReqInput(
query="q", documents=["a", "b", "c"], return_documents=False
)
scores = [0.2, 0.9, 0.1]
res = self.handler._build_rerank_response(scores, req)
# All documents should be None
self.assertEqual([r.document for r in res], [None, None, None])
# But scores and indices should still be correct
self.assertEqual([r.index for r in res], [1, 0, 2])
self.assertAlmostEqual(res[0].score, 0.9)
def test_build_rerank_response_top_n(self):
"""Test that top_n limits the number of returned results"""
req = V1RerankReqInput(
query="q", documents=["a", "b", "c"], return_documents=True, top_n=2
)
scores = [0.2, 0.9, 0.1]
res = self.handler._build_rerank_response(scores, req)
# Should only return top 2 results
self.assertEqual(len(res), 2)
self.assertEqual([r.document for r in res], ["b", "a"])
self.assertEqual([r.index for r in res], [1, 0])
self.assertAlmostEqual(res[0].score, 0.9)
self.assertAlmostEqual(res[1].score, 0.2)
def test_build_rerank_response_top_n_greater_than_total(self):
"""Test that top_n greater than total documents returns all documents"""
req = V1RerankReqInput(
query="q", documents=["a", "b"], return_documents=True, top_n=10
)
scores = [0.2, 0.9]
res = self.handler._build_rerank_response(scores, req)
# Should return all 2 documents even though top_n=10
self.assertEqual(len(res), 2)
self.assertEqual([r.document for r in res], ["b", "a"])
def test_build_rerank_response_top_n_with_return_documents_false(self):
"""Test top_n works correctly with return_documents=False"""
req = V1RerankReqInput(
query="q", documents=["a", "b", "c"], return_documents=False, top_n=1
)
scores = [0.2, 0.9, 0.1]
res = self.handler._build_rerank_response(scores, req)
# Should only return top 1 result, and document should be None
self.assertEqual(len(res), 1)
self.assertIsNone(res[0].document)
self.assertEqual(res[0].index, 1)
self.assertAlmostEqual(res[0].score, 0.9)
def test_handle_vl_reranker_request(self):
"""Test the Qwen3-VL reranker path with mocked logprobs."""
import math
# Mock tokenizer manager that supports generate_request
class _AsyncGen:
def __init__(self, val):
self.val = val
def __aiter__(self):
return self
async def __anext__(self):
return self.val
class _TM(_DummyTokenizerManager):
def __init__(self):
self.server_args = object()
self.model_config = Mock()
self.model_config.is_generation = True
self.model_config.model_path = "qwen/qwen3-vl"
self.tokenizer = Mock()
# Mock VL template detection
self.tokenizer.chat_template = (
"{% for x in query %}{{ x.text }}{% endfor %}"
"{% for x in document %}{{ x.text }}{% endfor %}"
'answer can only be "yes" or "no" <|vision_start|>'
)
async def generate_request(self, req, _raw):
# Return logprobs for yes/no
# Mock logprobs: P(yes) > P(no) for first doc, P(no) > P(yes) for second
if not hasattr(self, "call_count"):
self.call_count = 0
if self.call_count == 0:
# First doc: yes is likely
yes_logprob = math.log(0.8)
no_logprob = math.log(0.2)
else:
# Second doc: no is likely
yes_logprob = math.log(0.3)
no_logprob = math.log(0.7)
self.call_count += 1
# Qwen3 token IDs: YES=9693, NO=2152
top_logprobs = [
(yes_logprob, 9693, "yes"),
(no_logprob, 2152, "no"),
]
# The rerank handler checks output_top_logprobs[0] for the first generated token
meta_info = {"output_top_logprobs": [top_logprobs]}
yield {"meta_info": meta_info, "embedding": None}
handler = OpenAIServingRerank(_TM())
req = V1RerankReqInput(
query="query", documents=["doc1", "doc2"], return_documents=True
)
# Force VL path is handled by detection logic inside handler
# We mocked chat_template to satisfy _is_qwen3_vl_reranker_template
raw_request = Mock()
res = asyncio.run(handler._handle_non_streaming_request(req, req, raw_request))
self.assertEqual(len(res), 2)
# First doc should have higher score
self.assertEqual(res[0].document, "doc1")
self.assertAlmostEqual(res[0].score, 0.8) # 0.8 / (0.8+0.2) = 0.8
self.assertEqual(res[1].document, "doc2")
self.assertAlmostEqual(res[1].score, 0.3) # 0.3 / (0.3+0.7) = 0.3
if __name__ == "__main__":
unittest.main(verbosity=2)