跳转至

scheduler耗时过长

问题背景

在服务化推理场景中,调度器(Scheduler)负责将到达的推理请求按一定策略组Batch并下发到NPU执行。当调度器自身耗时占比过高时,会导致NPU设备空闲等待,整体吞吐下降、请求排队时间增加。在Host Bound场景下(如小模型推理、Decode阶段),单个算子的Host下发时间超过Device执行时间后,调度开销成为系统瓶颈。调度策略配置不当、动态调度优先级切换、组Batch逻辑复杂以及未启用匹配的图模式调度或模型下沉,都会增加scheduler耗时。

问题来源

推理

问题现象

用户通常先看到推理服务整体吞吐低于预期,NPU利用率偏低但请求排队(waiting)数量持续增长。在Grafana中按调度指标拆开后,常见现象是:

  • waiting_batch_size指标持续处于高位,而batch_size(running请求数)未达到配置的maxBatchSize上限。
  • 从request_status.csv中可观察到大量请求长期处于waiting状态,running状态请求数波动较大。
  • NPU执行流上出现间歇性空闲,Timeline视图中连续的Schedule色块之间存在明显的空闲间隙(bubble)。

使用msServiceProfiler采集数据后,在batch.csv中观察到name为batchFrameworkProcessing(组Batch阶段)的during_time(ms)显著偏高,与modelExec(模型执行阶段)耗时之比异常。典型表现为:组Batch耗时占总Batch耗时的比例偏高,而正常场景下该比例通常较低。在Host Bound模型(如小参数量的Encoder模型或Decode阶段的小Batch场景)中尤为明显。

定位过程

步骤 1:先确认调度瓶颈是否存在

在 Grafana 中查看 batch_size、waiting_batch_size、scheduler:duration、KVCache 水位和 NPU 利用率。调度瓶颈的在线判定条件为:waiting_batch_size 持续增长,scheduler:duration 分位数同步升高,模型执行耗时保持同负载基线,同时 KVCache 未触及容量下限。batch_size 未达到上限和 NPU 利用率低作为旁证。KVCache 水位耗尽时,先按 KV Block 不足案例分析,不能直接归因到调度器。

调度耗时 P99 使用以下查询:

histogram_quantile(
  0.99,
  sum by (le, instance, dp) (
    rate(vllm_profiling_scheduler:duration_bucket[5m])
  )
)

模型执行平均耗时使用以下查询:

sum by (instance, dp, phase) (
  rate(vllm_profiling_executor:execute_model:duration_sum[5m])
)
/
sum by (instance, dp, phase) (
  rate(vllm_profiling_executor:execute_model:duration_count[5m])
)

两个查询使用相同的异常窗口、实例、DP 域和阶段。scheduler P99 上升而模型执行平均耗时稳定,一级定界结论为调度阶段变慢。

步骤 2:用Profiler采集调度阶段数据

配置ms_service_profiler_config.json,设置domain为"Schedule; Request; ModelExecute",开启数据采集。采集完成后执行解析命令:

python3 -m ms_service_profiler.parse --input-path ${PATH}/prof_dir/

解析生成batch.csv、request.csv、forward.csv、chrome_tracing.json等文件。

步骤 3:分析batch.csv中的调度耗时

在batch.csv中筛选name为batchFrameworkProcessing的行,统计其during_time(ms)的分布(平均值、P90、P99)。同时筛选name为modelExec的行,计算组Batch耗时与模型执行耗时的比值。如果组Batch耗时占比偏高,说明调度阶段存在瓶颈。

按dp_rank维度分别统计各DP域的调度耗时,判断是否为特定DP域调度慢。按batch_type(prefill/decode)分别统计,判断是Prefill调度慢还是Decode调度慢。

步骤 4:用服务化拆解工具细粒度拆解Batch执行阶段

msserviceprofiler split --input-path /path/to/input --decode-batch-size 1 --decode-number 100

拆解后查看decode.csv中各子阶段(如数据下发、模型执行、数据接收等)的耗时分布,确认调度阶段的具体耗时瓶颈在哪个子环节。

步骤 5:通过Timeline视图确认bubble位置

打开chrome_tracing.json,在Timeline视图中观察Schedule色块与ModelExecute色块之间的时序关系。如果存在大量bubble(空闲间隙),且bubble出现在组Batch阶段而非模型执行阶段,可确认调度器是瓶颈。

结合forward.csv中的bubble_time(ms)字段,统计forward之间的空泡时间。如果空泡时间占比较高,说明调度下发不及时导致NPU等待。

步骤 6:可选使用 Tracing 定位调度 Span

需要把阶段结论下钻到具体请求时,在 Jaeger 中使用 Trace ID、request ID 或 Metrics 异常窗口找到目标 Trace。存在正常基线时,选择输入、输出 Token 规模和请求阶段一致的正常 Trace 与异常 Trace,按以下顺序对比:

  1. 使用 request.id、request.ids 或 Span Links 确认 scheduler、model 与目标请求的关联。
  2. 对比 vllm.scheduler.schedule 的时长和 batch.request_count、batch.scheduled_tokens 属性。
  3. 对比 vllm.model.execute 和 vllm_ascend.model_runner.execute 的时长。
  4. 同一 Trace ID 场景直接对比 scheduler 与 model 之间的空白区间;Span Links 场景沿 Link 分别打开关联 Span,不使用两个独立 Trace 的时间差计算空白。

异常观测组的 scheduler Span 稳定高于同负载正常观测组,且 model 与 model runner Span 保持基线时,Tracing 将瓶颈定位在 Scheduler 执行阶段。scheduler Span 本身稳定、但同一 Trace ID 或统一 Perfetto Timeline 中 scheduler 与 model 之间空白增长时,瓶颈位于排队、同步或资源等待,不能输出“Scheduler 自身耗时过长”的结论。

步骤 7:用多维度解析工具获取整体统计

msserviceprofiler analyze --input-path=/path/to/input

查看batch_summary.csv中prefill和decode的batch数量和执行时间统计,从服务整体维度判断调度效率。

问题根因

调度器耗时过长的常见根因包括:

  1. Host Bound场景:小模型或Decode阶段单算子执行时间极短,但Host调度每个算子的下发流程耗时相对较长,导致Device频繁空闲等待。这是昇腾NPU上Host调度模式的固有问题,可通过CANN的图模式调度或模型下沉调度解决。

  2. 调度策略配置不当:maxBatchSize设置过小导致频繁组Batch;动态调度优先级在单并发场景下引入额外的策略切换开销;maxPrefillBatchSize与maxBatchSize配比不合理导致Prefill和Decode调度互相阻塞。

  3. 组Batch逻辑复杂度过高:请求数量大、请求长度差异大时,调度器需要遍历大量候选请求进行组Batch匹配,匹配算法复杂度随请求数增长。

  4. 框架适配问题:调度器与昇腾NPU的Task下发机制未充分优化,未启用图模式调度或模型下沉,仍使用单算子模式逐算子下发。

  5. 资源竞争:调度器线程与其它服务线程竞争CPU资源,导致调度延迟抖动。

解决方法

  • Host Bound:启用CANN图模式调度或模型下沉,减少Host下发开销。
  • 调度策略不当:调整maxBatchSize和maxPrefillBatchSize配比,关闭不必要的动态调度优先级。
  • 组Batch逻辑复杂:优化组Batch算法,减少候选请求遍历范围。
  • 框架适配:确认已启用图模式调度或模型下沉,避免单算子模式。
  • 资源竞争:确保调度线程有足够CPU资源,避免与其它服务线程竞争。

处理后使用与问题复现时相同的模型、版本、并发、输入/输出 Token 分布和服务参数复测。以下条件全部满足,处理结果才通过验证:

  • scheduler:duration P99 和 batchFrameworkProcessing P99 回到同负载基线范围。
  • waiting_batch_size 不再持续增长。
  • vllm.scheduler.schedule Span 与同一时间线中的调度空白区间恢复到基线。
  • 模型执行耗时没有增加,NPU 利用率和吞吐恢复。
  • TTFT、TPOT、错误率和显存没有回退。

定位方法论总结

本案例展示的是联合分析路径:先用 ms-service-metric 确认 waiting 增长、scheduler:duration 升高、模型执行稳定且 KVCache 容量正常;再用 Tracing 将目标请求定位到 vllm.scheduler.schedule,并排除 model 和 model runner 执行变慢;然后用 msServiceProfiler 的 batch.csv 对比组 Batch 耗时与模型执行耗时,使用统一 Timeline 确认调度空白区间。只使用 Metrics 时可以输出总体调度阶段结论,只使用 Tracing 时可以输出目标请求的调度路径结论;三类数据一致时可以同时确认影响范围、请求路径和设备时间线。

核心判断逻辑:如果组Batch耗时占比高且Timeline上存在大量调度间隙,则瓶颈在调度器;如果模型执行耗时占比高,则瓶颈在模型侧。

对工具的改进建议

ms-service-metric

当前在线监控已能查看waiting_batch_size、batch_size等调度指标。建议增加调度效率指标,如"调度耗时/模型执行耗时"比值、"平均组Batch延迟"等,便于在线快速识别调度瓶颈。增加Host Bound风险提示,当检测到小模型或Decode阶段NPU利用率持续偏低时,自动提示可能存在Host Bound。

msServiceProfiler

当前Profiler已能通过batch.csv对比组Batch耗时与模型执行耗时。建议在batch.csv中增加调度子阶段拆解字段,如"请求匹配耗时"、"Token分配耗时"、"Task下发耗时"等,便于直接定位调度瓶颈子环节。在Timeline视图中增加调度效率面板,自动计算并展示组Batch耗时占比、bubble占比等关键指标。

服务化拆解工具

当前拆解工具需要手动指定batch_size或rid。建议增加自动识别耗时异常Batch的能力,自动选取耗时最长的Top N个Batch进行拆解分析。