Prhub

#32457 [Model] Serve bare Qwen3Model backbone natively as an embedding model

原始 PR 作者 zhendonghua 合并时间 2026-07-27 15:49 文件变更 4 提交数 2 评论 2 代码增减 +192 / -0

执行摘要

bare Qwen3Model 原生嵌入适配,吞吐提升 32%

Checkpoints 导出为 architectures=['Qwen3Model'] 时,由于没有原生实现,ModelRegistry 会回退到 Transformers 后端,导致吞吐受限(profile 显示 GPU 时间被 copy 操作主导,GEMM 仅占 14%)。此 PR 使其运行在 SGLang 原生融合 Qwen3 内核上,显著提升性能。

PR 质量高,包含详尽的性能与准确性测试。实现模式(包装骨干 + Pooler + 手动 prefix remap)可供其他 embedding 模型注册参考。建议合并。

讨论亮点

PR 没有公开 review 讨论;maintainer mickqian 直接批准。作者在 body 的 notes 中提到了两个值得关注的设计决策:pooler 硬编码为 LAST+L2(与 harrier/Qwen3-Embedding 一致)以及缺少 PP 支持。作者建议将自动 pooling 选择作为后续跨架构改进。

实现拆解

  1. 新增模型文件 python/sglang/srt/models/qwen3_embedding.py:定义 Qwen3Model 类继承 nn.Module,包装 Qwen3TransformerModel。构造函数中初始化 backbone 和 Pooler(pooling_type=PoolingType.LAST, normalize=True)
  2. 权重加载 load_weights:处理裸 backbone 检查点缺少 model. 前缀的情况,将 layers.embed_tokens.norm. 重映射到 model.*;跳过 rotary_emblm_head 和无关张量;处理 stacked params mapping(qkv_proj, gate_up_proj)。
  3. 白名单 python/sglang/srt/configs/model_config.py:在 is_generation_model() 函数中添加 'Qwen3Model' 到非生成模型列表,一行改动。
  4. 单元测试 test/registered/unit/models/test_qwen3_embedding_registration.py:四个 CPU 测试覆盖 EntryClass 身份、注册解析、非生成分类、现有架构无影响。
  5. 文档 docs_new/docs/supported-models/embedding_models.mdx:在支持嵌入模型列表中添加 'Qwen3 (bare backbone)' 条目。
文件 模块 状态 重要度
python/sglang/srt/models/qwen3_embedding.py 嵌入层 added 9.05
test/registered/unit/models/test_qwen3_embedding_registration.py 测试 added 7.63
python/sglang/srt/configs/model_config.py 模型配置 modified 4.79
docs_new/docs/supported-models/embedding_models.mdx 文档 modified 3.05

关键符号

Qwen3Model.__init__ Qwen3Model.forward Qwen3Model.load_weights Qwen3Model.get_input_embeddings

关键源码片段

test/registered/unit/models/test_qwen3_embedding_registration.py test-coverage

单元测试覆盖注册和分类验证,确保正确性和无回归。

"""Unit tests for native registration of the bare ``Qwen3Model`` embedding arch.Checkpoints such as ``microsoft/harrier-oss-v1-0.6b`` declare
``architectures=["Qwen3Model"]`` (a bare Qwen3 backbone). These must resolve to
the native SGLang implementation (``sglang.srt.models.qwen3_embedding.Qwen3Model``)
and be served as an embedding model, NOT fall back to the Transformers backend.
"""from sglang.test.ci.ci_register import register_cpu_ciregister_cpu_ci(est_time=10, suite="base-a-test-cpu")import unittestfrom sglang.srt.configs.model_config import is_generation_model
from sglang.test.test_utils import CustomTestCase
​
​
class TestQwen3ModelEmbeddingRegistration(CustomTestCase):
    def test_entry_class_is_native_qwen3model(self):
        """验证 EntryClass 的 __name__ 和 __module__ 均指向原生实现"""
        from sglang.srt.models import qwen3_embedding
        entry = qwen3_embedding.EntryClass
        self.assertEqual(entry.__name__, "Qwen3Model")
        self.assertEqual(
            entry.__module__, "sglang.srt.models.qwen3_embedding"
        )
        # 确保具有 forward 和 load_weights 方法
        self.assertTrue(hasattr(entry, "forward"))
        self.assertTrue(hasattr(entry, "load_weights"))
​
    def test_registry_resolves_native_not_transformers_fallback(self):
        """ModelRegistry.resolve_model_cls 应返回原生类,而非回退"""
        from sglang.srt.models.registry import ModelRegistry
        model_cls, resolved_arch = ModelRegistry.resolve_model_cls("Qwen3Model")
        self.assertEqual(resolved_arch, "Qwen3Model")
        self.assertEqual(model_cls.__name__, "Qwen3Model")
        self.assertEqual(
            model_cls.__module__, "sglang.srt.models.qwen3_embedding"
        )
        self.assertNotIn("Transformers", model_cls.__name__)
​
    def test_bare_qwen3model_classified_as_embedding(self):
        """无论 --is-embedding 标志如何,该架构均被视为非生成模型"""
        self.assertFalse(is_generation_model(["Qwen3Model"]))
        self.assertFalse(is_generation_model(["Qwen3Model"], is_embedding=False))
        self.assertFalse(is_generation_model(["Qwen3Model"], is_embedding=True))
​
    def test_existing_qwen3_archs_unaffected(self):
        """确保已有的 Qwen3 架构(如 Qwen3ForCausalLM)行为不变"""
        # Qwen3ForCausalLM 默认为生成模型,仅在 --is-embedding 下变为嵌入
        self.assertTrue(is_generation_model(["Qwen3ForCausalLM"]))
        self.assertTrue(
            is_generation_model(["Qwen3ForCausalLM"], is_embedding=False)
        )
        self.assertFalse(
            is_generation_model(["Qwen3ForCausalLM"], is_embedding=True)
        )
        # Sequence classification / reward 类始终为非生成
        self.assertFalse(
            is_generation_model(["Qwen3ForSequenceClassification"])
        )
        self.assertFalse(is_generation_model(["Qwen3ForRewardModel"]))

评论区精华

Pooling 策略是否应自动适配配置 设计

作者在 notes 中指出 pooler 硬编码为 LAST+L2 normalize(与 harrier/Qwen3-Embedding 约定一致),未来可以考虑从 1_Pooling/config.json 自动选择 pooling type,但超出本 PR 范围。

结论:当前保持硬编码,留作后续改进。 · 已解决

风险与影响

兼容性风险:新增文件不影响现有 Qwen3ForCausalLM 路径。白名单仅作用于架构字符串 'Qwen3Model',不会误伤其他架构。但如果未来有生成模型也使用此架构名,则会被错误分类为 embedding,不过这不符合 Hugging Face 惯例(backbone 用于 embedding)。性能风险:原生路径更快,无退化。测试覆盖:单元测试验证注册逻辑,但缺少端到端集成测试(例如启动服务器并调用 /v1/embeddings)。weight loading 针对标准 bare backbone 检查点,但某些变体(如使用 'model.' 前缀的)也能处理(因为 load_weights 会检查前缀)。

直接影响使用 microsoft/harrier-oss-v1-0.6b 等 arch=Qwen3Model 的用户:吞吐提升 32%,精度保持不变。对现有 Qwen3 用户无影响。文档更新有助于用户发现该支持。

新架构注册 权重前缀映射假设 缺少集成测试

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论