Prhub

#47040 [Rust Frontend] Avoid LoRA registry scans without active LoRA requests

原始 PR 作者 reidliu41 合并时间 2026-06-30 12:58 文件变更 1 提交数 1 评论 2 代码增减 +80 / -4

执行摘要

避免无 LoRA 请求时的冗余注册表扫描

PR描述指出:在部署中没有LoRA请求时,RequestRegistry::lora_adapter_states() 仍会扫描每个在途请求以产生两个空适配器集合,这是一种不必要的性能开销。该PR通过跟踪活跃LoRA请求数,在计数为零时立即返回空集合,从而在常见无LoRA路径中避免O(N)注册表扫描。

此PR值得精读,因为它展示了如何通过一个简单的计数器优化来避免不必要的全量扫描,是一个典型的性能优化案例。同时,审查者提到的长期方案(PR #45411)也值得关注,可能涉及更根本的重构。

讨论亮点

审查者 BugenZhao 表示LGTM,并指出最终应落地类似https://github.com/vllm-project/vllm/pull/45411的方案以避免维护这个状态,但当前快速修复仍然不错。没有其他讨论或争议。

实现拆解

  1. RequestRegistry 结构体中新增 active_lora_requests: usize 字段,初始化为0,用于精确计数当前活跃的LoRA请求数量。

  2. register 方法中增加计数逻辑:将 lora_name 的映射提前,构建 lora 对象,若 lora.is_some() 则令 active_lora_requests += 1。同时将 lora 对象直接插入 TrackedRequest,避免了重复计算 lora_name.map(...)

  3. lora_adapter_states 方法开头添加守卫判断:若 self.active_lora_requests == 0,则立即返回 (BTreeSet::new(), BTreeSet::new()),跳过原本的全量扫描循环。

  4. remove 方法中递减计数:当移除的请求携带LoRA时(tracked.lora.is_some()),执行 active_lora_requests -= 1,保持计数器准确性。

  5. close 方法中将计数器清零self.active_lora_requests = 0,确保关闭后状态一致。

  6. 新增测试方法 registry_counts_only_active_lora_requests:验证无LoRA请求、有LoRA请求、以及移除请求后计数器的正确性。另外新增测试 registry_clears_lora_count_on_close,确保关闭时计数器清零。

  7. 添加条件编译测试辅助方法#[cfg(test)] fn active_lora_requests(&self) -> usize 用于测试断言。

文件 模块 状态 重要度
rust/src/engine-core-client/src/client/state.rs Rust 客户端 modified 8.02

关键符号

register remove close lora_adapter_states active_lora_requests

关键源码片段

rust/src/engine-core-client/src/client/state.rs core-logic

唯一修改的文件,包含所有核心逻辑变更:新增 `active_lora_requests` 计数器、优化 `register`、`remove`、`close` 和 `lora_adapter_states` 方法,以及新增测试。

// File: rust/src/engine-core-client/src/client/state.rs/// 在 `RequestRegistry` 结构体中新增 `active_lora_requests` 计数器。
#[derive(Debug)]
pub struct RequestRegistry {
    closed: bool,
    requests: HashMap<String, TrackedRequest>,
    active_lora_requests: usize, // <-- 新增:精确跟踪活跃 LoRA 请求数量
    routing_per_engine: BTreeMap<EngineId, EngineRoutingState>,
}impl RequestRegistry {
    pub fn new(engines: &[ConnectedEngine]) -> Self {
        Self {
            closed: false,
            requests: HashMap::default(),
            active_lora_requests: 0, // 初始化为 0
            routing_per_engine: engines
                .iter()
                .map(|engine| (engine.engine_id.clone(), EngineRoutingState::default()))
                .collect(),
        }
    }    /// 注册新请求时,若携带 LoRA 则递增计数器。
    pub fn register(
        &mut self,
        request_id: String,
        lora_name: Option<String>,
        data_parallel_rank: Option<u32>,
    ) -> Result<(EngineId, OutputReceiver)> {
        // ... 省略重复检查与引擎选择 ...
        let lora = lora_name.map(|adapter_name| LoraRequestState {
            adapter_name,
            phase: LoraPhase::Waiting,
        });
        if lora.is_some() {
            self.active_lora_requests += 1; // <-- 递增
        }
        self.requests.insert(
            request_id,
            TrackedRequest {
                sender: tx,
                engine_id: engine_id.clone(),
                lora, // 使用提前构建的 lora
            },
        );
        // ...
    }    /// 移除请求时,若该请求携带 LoRA 则递减计数器。
    pub fn remove(&mut self, request_id: &str) -> Option<(OutputSender, EngineId)> {
        let tracked = self.requests.remove(request_id)?;
        if tracked.lora.is_some() {
            self.active_lora_requests -= 1; // <-- 递减
        }
        // ...
    }    /// 关闭注册表时,清除计数器。
    pub fn close(&mut self) -> Vec<OutputSender> {
        self.closed = true;
        self.active_lora_requests = 0; // <-- 清零
        // ...
    }    /// 核心优化:当没有活跃 LoRA 请求时,立即返回空集合,避免 O(N) 扫描。
    pub fn lora_adapter_states(&self) -> (BTreeSet<String>, BTreeSet<String>) {
        if self.active_lora_requests == 0 { // <-- 守卫条件
            return (BTreeSet::new(), BTreeSet::new());
        }
        let mut running = BTreeSet::new();
        let mut waiting = BTreeSet::new();
        for lora in self.requests.values().filter_map(|tracked| tracked.lora.as_ref()) {
            match lora.phase {
                LoraPhase::Running => running.insert(lora.adapter_name.clone()),
                LoraPhase::Waiting => waiting.insert(lora.adapter_name.clone()),
            };
        }
        (running, waiting)
    }
}

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

该变更风险较低:

  • 仅在 state.rs 中修改,新增的计数器字段和条件守卫逻辑简单清晰。
  • 变更后,当 active_lora_requests 为0时,lora_adapter_states 返回空集合,与之前的行为完全一致(之前扫描后也会返回空集合)。
  • 计数器的增减覆盖了 registerremoveclose 所有入口,不易遗漏。
  • 新增的测试覆盖了核心路径,但未测试并发场景下的计数器正确性(RequestRegistry 并非线程安全?从代码看它通常被单个任务持有,但若未来并行访问可能存在问题)。

影响范围:仅影响Rust前端中 RequestRegistry::lora_adapter_states 方法的性能,该方法是每个引擎输出批次后调用的指标更新路径。影响程度:在无LoRA请求的部署中,避免了O(N)的循环扫描,降低了CPU开销,对于高并发场景可能显著减少延迟。代码改动量很小(+80/-4),且完全向后兼容。

核心路径变更(指标更新)

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论