deepinfer-schedule-type

deepinfer/csrc/engine/scheduler.hpp · a679bb8 · L275–343 · 固定 commit 上游

这是固定版本源码的只读摘录;可在线阅读,也可通过上游链接查看原文件。

275    std::unique_ptr<SchedulerOutputs> _schedule() {
276        py::gil_scoped_release release; // otherwise other python threads would be blocked
277        BlockManagerMutexAcquire acquire{block_manager, state_manager};
278
279        // 上一个 step 的 SchedulerOutputs 借用这三个 vector 的存储(见 pybind 的
280        // clone_states/clone_blocks/zero_states property),Python 侧必须在下一次
281        // schedule 前消费完,这里 clear 后复用。
282        clone_states.clear();
283        zero_states.clear();
284        clone_blocks.clear();
285
286        tokens_chunked_prefill_remaining = 0;
287
288        switch (engine_type) {
289        case EngineType::PROMPT:
290            // PROMPT 引擎只做 prefill,即使没调度到请求也返回(空的)prefill 结果
291            return _schedule_prefill(acquire);
292        case EngineType::COMPLETION:
293            // COMPLETION 引擎只做 decode
294            return _schedule_decode(acquire);
295        case EngineType::NORMAL: {
296            // NORMAL 引擎:决定这一步全组做 prompt 还是 decode。dp 内必须同型,靠投票统一。
297            //
298            // 两类尝试点:
299            //   ALL 点(每 prefill_attempt_interval 个 decode step):全员都有 prefill 活才做,
300            //     防止一个 rank 干活、其余 rank 陪跑空 prompt step。
301            //   ANY 点(每 prefill_any_attempt_interval 个 decode step):一个 rank 有活就一起做,
302            //     给孤立 rank 兜底——它的 prefill 最多等这么多个 decode step。
303            //   两类点重合时按 ANY 算。
304            //
305            // prompt step 会把 decode_step 清零(见 schedule()),所以刚做完 prefill 的下一步
306            // decode_step==0,又落在 ALL 点上:想连着 prefill 同样要全员有活。
307            // decode_step 各 rank 一致,投票的次数和语义全组对得上,不会卡在 allreduce。
308            bool is_any_point = (decode_step != 0) && (decode_step % prefill_any_attempt_interval == 0);
309            bool is_all_point = (decode_step % prefill_attempt_interval == 0);
310            if (!is_any_point && !is_all_point) {
311                return _schedule_decode(acquire);
312            }
313            bool want = (get_gpu_cache_usage() <= GPU_CACHE_PREFILL_WATERMARK) && _has_prefill_waiting();
314            bool do_prefill;
315            if (is_any_point) {
316                do_prefill = (_dp_allreduce(want ? 0 : 1, c10d::ReduceOp::MIN) == 0); // ANY:任一 rank 想做即触发
317            } else {
318                do_prefill = (_dp_allreduce(want ? 1 : 0, c10d::ReduceOp::MIN) == 1); // ALL:全员想做才触发
319            }
320            if (!do_prefill) {
321                return _schedule_decode(acquire);
322            }
323            std::unique_ptr<SchedulerOutputs> outputs;
324            if (want) {
325                outputs = _schedule_prefill(acquire);
326            }
327
328            // 第二轮投票:确认这轮真有 rank 排出了 prefill。want 只看了 waiting 非空,
329            // 但 _schedule_prefill 仍可能空手(slot 满、KV 块不够)。
330            // 全组都空手时必须转 decode:硬做空 prompt step 会把 decode_step 清零,
331            // 下一步接着投票、接着空手——running 满 + waiting 非空就死循环了。
332            bool any_scheduled = (_dp_allreduce(outputs ? 0 : 1, c10d::ReduceOp::MIN) == 0);
333            if (!any_scheduled) {
334                return _schedule_decode(acquire);
335            }
336            if (outputs) {
337                return outputs;
338            }
339            // 组内确有 rank 在做 prompt:本 rank 陪跑空 prompt step 保持步型一致
340            return _make_outputs({}, true, 0, {}, {}, {}, 0);
341        }
342        }
343        TORCH_CHECK(false, "unknown engine_type ", int(engine_type));