执行摘要
- 一句话:启用 csgmv 后端与 MoE 虚拟专家组合,修复 CUDA graph 崩溃
- 推荐动作:建议阅读。PR 展示了如何在不同 LoRA 后端(triton vs chunked)之间抽象 per-request 路由信息,修复了因语义差异导致的崩溃。设计上采用可选回退模式,保证了向后兼容。但需注意代码中的
torch.arange pin_memory 错误需要尽快修复,且应补全测试。
功能与动机
之前 --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 后端也能利用虚拟专家加速。
实现拆解
- 移除配置限制:在
server_args.py 的 check_lora_server_args 中删除了断言 assert self.lora_backend == "triton",允许 --lora-use-virtual-experts 与任意后端组合。
- 扩展数据结构:在
lora/utils.py 的 LoRABatchInfo 数据类中添加两个可选字段 req_seg_indptr(形状 bs+1)和 req_weight_indices(形状 bs),用于存储 per-request 的 segment 边界和 adapter 索引。
- 实现 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。
- 消费 per-request 字段:在
lora/layers.py 的 _get_lora_info 方法中,优先读取 batch_info.req_seg_indptr 和 batch_info.req_weight_indices,若为 None 则回退到原有 seg_indptr 和 weight_indices,确保向后兼容。
- 配套修复:commit 中还包含了将
seg_indptr[0] = 0 替换为 seg_indptr[0:1].zero_() 的 CUDA graph 兼容性修复,避免 CPU-GPU 同步点。
关键文件:
python/sglang/srt/lora/backend/chunked_backend.py(模块 LoRA后端;类别 source;类型 core-logic;符号 _build_req_seg_indptr): 核心实现:新增_build_req_seg_indptr方法,修改prepare_lora_batch和init_cuda_graph_batch_info以支持per-request字段,是修复CUDA graph崩溃的关键文件。
python/sglang/srt/lora/layers.py(模块 LoRA层;类别 source;类型 core-logic;符号 _get_lora_info): 修改了MoE LoRA层的_get_lora_info方法,优先使用per-request字段,确保虚拟专家使用正确的token→adapter映射。
python/sglang/srt/lora/utils.py(模块 数据模型;类别 source;类型 core-logic;符号 LoRABatchInfo): 扩展了LoRABatchInfo数据结构,添加per-request字段定义,是后两个变更的基础。
python/sglang/srt/server_args.py(模块 服务配置;类别 source;类型 configuration): 简单删除了一行断言,是功能开启的配置入口。改动量小但语义重要。
关键符号:_build_req_seg_indptr, _get_lora_info, prepare_lora_batch, init_cuda_graph_batch_info
关键源码片段
python/sglang/srt/lora/backend/chunked_backend.py
核心实现:新增_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
修改了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
扩展了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 指出 _build_req_seg_indptr 中使用的 torch.arange(..., pin_memory=True) 在 PyTorch 中非法,会引发 TypeError,建议改为 .pin_memory()。PR 合并时该问题未修正,存在潜在运行时风险。
- 缺少测试覆盖:Copilot 建议增加回归测试,使用 MoE 模型以
--lora-backend csgmv --lora-use-virtual-experts 启动(最好启用 CUDA graph),以验证组合正确性并防止崩溃重现。PR 最终未包含此类测试。
- torch.arange 不支持 pin_memory 参数 (correctness): 作者未回复,合并时该行代码仍存在问题,存在潜在运行时错误风险。
- 缺少对 csgmv + virtual experts 组合的测试覆盖 (testing): PR 未包含此类测试,测试缺口持续存在。
风险与影响
关联脉络
- PR #17913 [Feature] add LoRADrainer to address high P99 TTFT: 同属 LoRA 功能线,之前的 LoRA 性能优化工作。
- PR #24334 extract adjust_hybrid_swa_layers_for_pp: 同仓库近期重构,虽不直接相关但体现了 LoRA 模块的持续演进。
参与讨论