# Phase 12: Continuous Batching + Request Scheduler — Design Document ## Goal 实现 iteration-level 请求调度器,支持多请求并发执行和动态 batch 管理。这是 LLM serving 系统的核心调度逻辑。 ## 核心概念 ### Static Batching vs Continuous Batching **Static(朴素)**: ``` Batch 1: [req1, req2, req3] → 等所有完成才开始下一批 问题: req1 10 token 就完了,req3 要 200 token → req1 的 slot 空转 ``` **Continuous(本阶段目标)**: ``` Iteration 1: [req1, req2, req3] → req1 完成! slot 释放 Iteration 2: [req2, req3, req4] → req4 立即填入 每一个 iteration(一次 forward pass)重新决定哪些请求参与 ``` ## 核心组件 ### Sequence ```rust pub struct Sequence { pub id: SeqId, pub prompt_tokens: Vec, pub generated_tokens: Vec, pub status: SequenceStatus, pub sampling_params: SamplingParams, pub kv_cache_handle: KVCacheHandle, // 该 seq 的 KV cache 资源 pub arrival_time: Instant, pub output_sender: tokio::sync::mpsc::Sender, } pub enum SequenceStatus { Waiting, // 等待调度 Prefilling, // 正在 prefill Decoding, // 正在逐 token decode Finished, // 完成 (EOS / max_len) } ``` ### Scheduler ```rust pub struct Scheduler { waiting: VecDeque, // 等待队列 running: Vec, // 正在执行 max_batch_size: usize, // 最大并发数 block_manager: BlockManager, // KV cache 资源管理 } ``` ### 调度循环 ```rust loop { // 1. 回收已完成的 sequence,释放 KV cache // 2. 从 waiting 中 admit 新请求(如果有空位+显存) // 3. 对 running 中的所有 seq 做一步 forward // - 新加入的做 prefill // - 已在运行的做 decode // 4. 对每个 seq 的 logits 做 sampling // 5. 发送新 token / 完成信号 } ``` ## 当前状态 (Phase 12 初版) 当前实现是 **单请求顺序执行**(max_batch_size=1),是 continuous batching 的退化形式: - 一次只处理一个请求 - 完成后才接受下一个 - 无 preemption、无 batching 这是合理的起步——先跑通单请求 E2E,后续扩展为真正的并发 batching。 ## 后续扩展 (Phase 15+) 1. **多请求 batch forward**: 将多个 seq 的 token 拼接为一个 batch 输入 2. **Prefill-Decode 分离**: prefill (compute-bound) 和 decode (memory-bound) 分开调度 3. **Preemption**: 显存不足时暂停低优先级 seq 4. **动态 batch size**: 根据 KV cache 使用量调整 ## Test Plan - [x] 单请求 E2E: 提交请求 → 收到 token 流 → 完成信号 - [ ] (后续) 多请求并发: 提交多个请求,验证都能正确完成 - [ ] (后续) 短请求完成后新请求立即加入 ## Takeaways 1. **单请求是 continuous batching 的特殊情况 (batch_size=1)**:当前实现的 engine 循环已经是正确的调度结构——receive request → prefill → decode loop → done → next request。扩展为多请求只需在 decode loop 中处理多个 sequence。 2. **Engine 在独立 OS thread 上跑是正确的设计**:GPU 操作是同步阻塞的(cudaDeviceSynchronize),如果放在 tokio runtime 中会 block 整个 async runtime。独立线程 + channel 通信是标准模式。 3. **std::sync::mpsc::SyncSender(capacity=1) 实现了天然的背压**:当 engine 忙时,新请求会 block 在 channel send 上,不会积压。