Prhub

#24007 (1/n - prefill optimize)feat(lora): enable csgmv backend with virtual experts for MoE LoRA

原始 PR 作者 yushengsu-thu 合并时间 2026-05-04 09:44 文件变更 4 提交数 5 评论 4 代码增减 +56 / -11

执行摘要

启用 csgmv 后端与 MoE 虚拟专家组合,修复 CUDA graph 崩溃

之前 --lora-use-virtual-experts 限制仅适用于 triton 后端,但 MoE 虚拟专家实际使用独立的 merged_experts_fused_moe_lora_add kernel,与 Dense-LoRA 后端无关。csgmv 后端在结合虚拟专家时,因其 chunked-segment 语义与 MoE 期望的 per-request 映射不一致,导致 token-to-adapter 错误和 CUDA graph 崩溃。本 PR 旨在解除限制并修复崩溃,使 csgmv 后端也能利用虚拟专家加速。

建议阅读。PR 展示了如何在不同 LoRA 后端(triton vs chunked)之间抽象 per-request 路由信息,修复了因语义差异导致的崩溃。设计上采用可选回退模式,保证了向后兼容。但需注意代码中的 torch.arange pin_memory 错误需要尽快修复,且应补全测试。

讨论亮点
  1. torch.arange 不支持 pin_memory:Copilot 指出 _build_req_seg_indptr 中使用的 torch.arange(..., pin_memory=True) 在 PyTorch 中非法,会引发 TypeError,建议改为 .pin_memory()。PR 合并时该问题未修正,存在潜在运行时风险。
  2. 缺少测试覆盖:Copilot 建议增加回归测试,使用 MoE 模型以 --lora-backend csgmv --lora-use-virtual-experts 启动(最好启用 CUDA graph),以验证组合正确性并防止崩溃重现。PR 最终未包含此类测试。

实现拆解

  1. 移除配置限制:在 server_args.pycheck_lora_server_args 中删除了断言 assert self.lora_backend == "triton",允许 --lora-use-virtual-experts 与任意后端组合。
  2. 扩展数据结构:在 lora/utils.pyLoRABatchInfo 数据类中添加两个可选字段 req_seg_indptr(形状 bs+1)和 req_weight_indices(形状 bs),用于存储 per-request 的 segment 边界和 adapter 索引。
  3. 实现 per-request 计算:在 chunked_backend.py 中新增静态方法 _build_req_seg_indptr,根据 ForwardBatch 构建 CPU pinned 的 per-request 累积长度;在 init_cuda_graph_batch_info 中预分配 CUDA graph 缓冲区;在 prepare_lora_batch 中计算 per-request 信息并通过异步复制填充到 batch_info
  4. 消费 per-request 字段:在 lora/layers.py_get_lora_info 方法中,优先读取 batch_info.req_seg_indptrbatch_info.req_weight_indices,若为 None 则回退到原有 seg_indptrweight_indices,确保向后兼容。
  5. 配套修复:commit 中还包含了将 seg_indptr[0] = 0 替换为 seg_indptr[0:1].zero_() 的 CUDA graph 兼容性修复,避免 CPU-GPU 同步点。
文件 模块 状态 重要度
python/sglang/srt/lora/backend/chunked_backend.py LoRA 后端 modified 6.99
python/sglang/srt/lora/layers.py LoRA 层 modified 6.14
python/sglang/srt/lora/utils.py 数据模型 modified 5.34
python/sglang/srt/server_args.py 服务配置 modified 4.1

关键符号

_build_req_seg_indptr _get_lora_info prepare_lora_batch init_cuda_graph_batch_info

关键源码片段

python/sglang/srt/lora/backend/chunked_backend.py core-logic

核心实现:新增 _build_req_seg_indptr 方法,修改 prepare_lora_batch 和 init_cuda_graph_batch_info 以支持 per-request 字段,是修复 CUDA graph 崩溃的关键文件。

    @staticmethod
    def _build_req_seg_indptr(forward_batch: ForwardBatch) -> torch.Tensor:
        """
        Build per-request cumulative token boundaries on CPU (pinned).        `pin_memory()` 被单独调用,因为 `torch.arange` 不接受 `pin_memory` 参数。
        """
        bs = forward_batch.batch_size
        if forward_batch.forward_mode.is_decode():
            # Decode 阶段每个请求只有 1 个 token,indptr 为 [0, 1, 2, ..., bs]
            indptr = torch.arange(bs + 1, dtype=torch.int32).pin_memory()
        else:
            # Extend 阶段使用真实序列长度累积,生成 per-request 的 token 边界
            seg_lens = generate_sequence_lengths(forward_batch, device="cpu")
            indptr = torch.zeros(bs + 1, dtype=torch.int32, pin_memory=True)
            torch.cumsum(seg_lens, dim=0, out=indptr[1:])
        return indptr
​
    def init_cuda_graph_batch_info(self, max_bs_in_cuda_graph, num_tokens_per_bs):
        # ... 原有代码 ...
        # 新增两个预分配缓冲区,用于 CUDA graph 场景下的 per-request 信息
        self.cuda_graph_batch_info = LoRABatchInfo(
            # ... 原有字段 ...
            req_seg_indptr=torch.zeros(max_bs_in_cuda_graph + 1, dtype=torch.int32),
            req_weight_indices=torch.zeros(max_bs_in_cuda_graph, dtype=torch.int32),
        )
​
    def prepare_lora_batch(self, forward_batch, weight_indices, lora_ranks, scalings, use_cuda_graph):
        # ... 原有逻辑构建 seg_weight_indices, seg_indptr ...
        bs = forward_batch.batch_size
        # 为每个请求构建 uniform 的 adapter 索引和 token 边界
        req_wi_tensor = torch.tensor(
            weight_indices, dtype=torch.int32, pin_memory=True, device="cpu"
        )
        req_seg_indptr_cpu = self._build_req_seg_indptr(forward_batch)
​
        # 在非 CUDA graph 和 CUDA graph 分支中,都异步复制 per-request 字段到 device
        batch_info.req_seg_indptr[: bs + 1].copy_(req_seg_indptr_cpu, non_blocking=True)
        batch_info.req_weight_indices[:bs].copy_(req_wi_tensor, non_blocking=True)
python/sglang/srt/lora/layers.py core-logic

修改了 MoE LoRA 层的 _get_lora_info 方法,优先使用 per-request 字段,确保虚拟专家使用正确的 token→adapter 映射。

    def _get_lora_info(self):
        """Build LoRAInfo for the current batch."""
        # ... 原有代码 ...
        # 优先使用 per-request 字段,若后端未提供则回退到原有 batch-level 字段
        wi = (
            batch_info.req_weight_indices
            if batch_info.req_weight_indices is not None
            else batch_info.weight_indices
        )
        # 根据 adapter_enabled 的构造使用 wi
        adapter_enabled = ... # 使用 wi 代替原来的 weight_indices
​
        seg_indptr = (
            batch_info.req_seg_indptr
            if batch_info.req_seg_indptr is not None
            else batch_info.seg_indptr
        )
        req_to_lora = wi
​
        return LoRAInfo(
            # ... 其他字段 ...
            seg_indptr=seg_indptr,
            req_to_lora=req_to_lora,
        )
python/sglang/srt/lora/utils.py core-logic

扩展了 LoRABatchInfo 数据结构,添加 per-request 字段定义,是后两个变更的基础。

@dataclass
class LoRABatchInfo:
    # ... 原有字段 ...
    has_active_lora: bool = False
​
    # Per-request segment indptrs, shape (bs + 1,). Required by MoE virtual
    # experts which map tokens to requests regardless of the dense-LoRA
    # backend's internal segmentation. For the triton backend these are
    # identical to seg_indptr/weight_indices; for csgmv they differ because
    # its segments are chunked across adapters.
    req_seg_indptr: Optional[torch.Tensor] = None
​
    # Per-request adapter index, shape (bs,).
    req_weight_indices: Optional[torch.Tensor] = None

评论区精华

torch.arange 不支持 pin_memory 参数 正确性

Copilot 指出 `torch.arange(..., pin_memory=True)` 不是合法签名,会引发 TypeError,建议使用 `.pin_memory()` 方法或在创建后调用。

结论:作者未回复,合并时该行代码仍存在问题,存在潜在运行时错误风险。 · unresolved

缺少对 csgmv + virtual experts 组合的测试覆盖 测试

Copilot 建议添加回归测试,使用 MoE 模型以 `--lora-backend csgmv --lora-use-virtual-experts` 启动并启用 CUDA graph,以验证组合正确性。

结论:PR 未包含此类测试,测试缺口持续存在。 · unresolved

风险与影响

  1. torch.arange(pin_memory=True) 非法:在 chunked_backend.py_build_req_seg_indptr 中,pin_memory=True 参数不被 torch.arange 支持,运行时将抛出 TypeError。虽然该路径仅在 csgmv + virtual experts 激活时执行,但可能阻塞用户使用。需要后续修复。
  2. 缺少自动回归测试:PR 未新增任何测试用例,未来任何修改可能再次触发 CUDA graph 崩溃或 token-to-adapter 映射错误,风险较高。
  3. 兼容性:per-request 字段通过 Optional 安全回退,对 triton 等其他后端无影响,风险可控。

用户:可使用 --lora-backend csgmv--lora-use-virtual-experts 组合,获得与 triton 后端几乎一致的吞吐量(约 1.7-2.2% 提升),同时解锁 csgmv 后端的其他优势(如 chunked 调度)。
系统:每个 batch 额外增加两个小张量(bs+1bs),显存开销极小。
团队:统一了 LoRA 后端的语义,消除历史限制,为进一步优化 MoE LoRA 铺平道路。

缺少测试覆盖 torch API 兼容性问题

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论