Prhub

#24133 [NPU]: Optimize xgrammar token bitmask on NPU with AscendC

原始 PR 作者 ChefWu551 合并时间 2026-05-29 12:38 文件变更 2 提交数 6 评论 7 代码增减 +4 / -37

执行摘要

用 AscendC 算子替换 NPU xgrammar bitmask CPU fallback

PR body 明确说明:“When speculative decoding is enabled and the request uses JSON Schema output format, SGLang may trigger the xgrammar backend. On NPU, the previous xgrammar token bitmask path used the torch CPU fallback, which introduces extra CPU/NPU data movement and hurts inference latency.” 因此需要集成 NPU 原生算子以降低延迟。

该 PR 性能提升明确,改动清晰,建议合并。后续可考虑针对 sgl_kernel_npu 导入添加 try-except 并以日志告警替代崩溃,增强运行时鲁棒性。同时推荐为 NPU 分支添加单元测试,验证算子 in-place 语义。

讨论亮点

算子命名空间与 in-place 行为:gemini-code-assist[bot] 质疑 torch.ops.npu.apply_token_bitmask 是否为 in-place 操作,以及命名空间是否正确。作者反馈已在 sgl-kernel-npu PR#442 中确认该算子使用 TORCH_LIBRARY_FRAGMENT(npu, m) 注册,且测试已验证 in-place 语义。

健壮性争议:gemini-code-assist[bot] 建议为 sgl_kernel_npu 的导入添加 try-except 并保留 Torch fallback 路径,防止依赖缺失时崩溃。作者未采纳该建议,最终实现为不带备用的硬依赖,仅在 NPU 设备分支导入。

实现拆解

  1. 移除旧导入:在 python/sglang/srt/constrained/xgrammar_backend.py 中删除 from sglang.srt.constrained.torch_ops.bitmask_ops import apply_token_bitmask_inplace_torch
  2. 替换 NPU 分支:在 XGrammarGrammar.apply_vocab_mask 方法的 elif logits.device.type == "npu" 分支中,原本调用 apply_token_bitmask_inplace_torch(logits, vocab_mask),改为使用 torch.ops.npu.apply_token_bitmask(logits, vocab_mask);并在该分支内通过 import sgl_kernel_npu 进行懒加载。
  3. 删除冗余文件:移除 python/sglang/srt/constrained/torch_ops/bitmask_ops.py,该文件仅包含被替代的 Torch fallback 函数。
  4. 依赖管理:本次变更引入对 sgl-kernel-npu 包的运行时依赖(仅 NPU 路径触发),该包作为项目可选依赖管理,非 NPU 设备不受影响。
文件 模块 状态 重要度
python/sglang/srt/constrained/xgrammar_backend.py 约束解码 modified 6.25
python/sglang/srt/constrained/torch_ops/bitmask_ops.py 函数模块 removed 5.4

关键符号

apply_vocab_mask apply_token_bitmask_inplace_torch

关键源码片段

python/sglang/srt/constrained/xgrammar_backend.py dependency-wiring

主要修改文件:删除了 CPU fallback 导入,在 apply_vocab_mask 的 NPU 分支中替换为 AscendC 算子调用,是整个优化的集成点。

def apply_vocab_mask(self, logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
    # 根据 logits 所在的设备类型选择不同的 bitmask 应用后端
    if logits.device.type in {"cuda", "xpu", "musa"}:
        if _is_hip:
            apply_token_bitmask_inplace_cuda(logits, vocab_mask)
        else:
            apply_token_bitmask_inplace_triton(logits, vocab_mask)
    elif logits.device.type == "npu":
        # 优化:使用 AscendC 原生算子替代 Torch CPU fallback,避免 CPU/NPU 数据拷贝
        import sgl_kernel_npu # noqa: F401 # 触发算子注册
        torch.ops.npu.apply_token_bitmask(logits, vocab_mask)
    else:
        raise RuntimeError(f"Unsupported device: {logits.device.type}")
python/sglang/srt/constrained/torch_ops/bitmask_ops.py deletion

被删除的文件,仅包含被替换的 apply_token_bitmask_inplace_torch 函数,不再需要。

def apply_token_bitmask_inplace_torch(
    logits: torch.Tensor,
    bitmask: torch.Tensor,
) -> None:
    # 此函数已被 NPU 原生算子替换,不再使用
    vocab_size = logits.shape[-1]
    # 以下实现将 bitmask 从 NPU 拷贝到 CPU,逐 token 展开,再拷贝回 NPU
    bitmask_cpu = bitmask.detach().cpu()
    token_ids = torch.arange(vocab_size, device="cpu", dtype=torch.int32)
    word_idx = token_ids // 32
    bit_idx = token_ids % 32
    words = bitmask_cpu[:, word_idx].to(torch.int32)
    allowed = ((words >> bit_idx) & 1).to(torch.bool)
    allowed = allowed.to(logits.device, non_blocking=True)
    logits.masked_fill_(~allowed, float("-inf"))

评论区精华

算子命名空间与 in-place 行为确认 正确性

gemini-code-assist[bot] 质疑 `torch.ops.npu.apply_token_bitmask` 是否是 in-place 操作,以及命名空间是否正确。作者回复已检查 sgl-kernel-npu PR#442,确认操作符使用 `TORCH_LIBRARY_FRAGMENT(npu, m)` 注册且测试验证为 in-place。

结论:作者澄清了命名空间和 in-place 行为,但建议添加 try-except 回退来增强健壮性。 · 已解决

移除 torch fallback 的健壮性 设计

gemini-code-assist[bot] 建议保留 `apply_token_bitmask_inplace_torch` 作为 fallback,如果 sgl_kernel_npu 缺失不会崩溃。作者回应删除了判断逻辑,仅在 NPU 条件下导入包。

结论:作者未采纳保留 fallback 的建议,最终实现为不带回退的硬依赖。 · 已解决

风险与影响

  1. NPU 算子依赖强耦合:若 sgl-kernel-npu 未安装或版本不兼容,NPU 分支启动时会直接抛出 ImportError,无降级手段。当前项目已整合该包作为 NPU 平台依赖,风险可控但运维需确保环境一致。
  2. 算子语义假设:假设 torch.ops.npu.apply_token_bitmask 为 in-place 操作,若后续 sgl-kernel-npu 变更其语义(改为返回新 tensor),将导致 apply_vocab_mask 无效果,引入隐蔽的正确性问题。
  3. 无新增单元测试:本次变更未为 NPU 算子路径添加专用测试,主要依赖 sgl-kernel-npu 仓库的测试和整体 CI。

影响范围:限定于 NPU 设备上启用 xgrammar 约束解码的场景(通常配合 speculative decoding 和 JSON Schema 输出)。对 CUDA、XPU 等其他设备无影响。
性能收益:单次 bitmask 应用延迟从 4.48ms 降至 0.171ms(约 26 倍加速),属于该 critical path 的显著优化。
维护成本:减少一个文件(bitmask_ops.py),简化导入结构。
部署要求:NPU 环境必须安装 sgl-kernel-npu 包(随项目 NPU 依赖自动安装)。

缺少回退路径 NPU 算子依赖 无新增单元测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论