最近在运营遥遥领先910B时经常遇到这么个情况:客户长上下文场景token吐的好好的,突然卡住了,然后在漫长的等待过后返回一个请求超时报错。怀疑是调度出大问题且求助FAE无果后,只能自己读代码,尝试盘一下整个sheduler的逻辑。

先上结论:MindIE可以支持PD动态调度,但Prefill优先级相对更高。因此高并发场景、长对话下decode流被持续打断,造成服务端超时报错。

从MindIE LLM 2.2.RC1的参数说明文档可以找到这么一段描述

  • prefillWaitTime = prefillTimeMsPerReq * decodeReqNum
  • accumulatedDecodeWasteTime = accumulatedDecodeWasteTime + decodeTimeMsPerReq * (maxBatchSize - decodeReqNum)
  • 当prefillWaitTime > accumulatedDecodeWasteTime时,表示Decode请求积压过多,则下一次推理是Decode。
  • 当prefillWaitTime <= accumulatedDecodeWasteTime时,表示连续多次Decode浪费的时间过多,则下一次推理是Prefill。

这边prefillTimeMsPerReq与decodeTimeMsPerReq都是[0,1000]的可设置常量,maxBatchSize代表decode阶段最大并行处理请求数,也是个常量。

为了方便推导,这边把第n轮的prefillWaitTime记为p(n)、accumulatedDecodeWasteTime记为d(n)、decodeReqNum记为r(n);整理后我们可以得到:

p(n) = P * r(n)

d(n) = d(n-1) + D * (M - r(n)) = D*M*n - D*\sum _{i=1}^n r(i)

src/scheduler/policy/stage_policy/tpt_stage_policy.cpp代码中我们可以发现,这里的r(n)即decodeTimeMsPerReq就是当前正在运行的 Decode 请求数量,r(n)小于M(decode阶段最大并行处理请求数)。

PDPriorityType TptStagePolicy::Apply(ConcurrentDeque<SequenceGroupSPtr> &waiting,
                                     ConcurrentDeque<SequenceGroupSPtr> &running,
                                     [[maybe_unused]]ConcurrentDeque<SequenceGroupSPtr> &swapped)
{
    if (waiting.Size() > 0) {
        uint64_t prefillCostTime = schedulerConfig_->prefillTimeMsPerReq * running.Size();
        if (running.Size() <= schedulerConfig_->maxBatchSize) {
            decodeWasteTime_ +=
                schedulerConfig_->decodeTimeMsPerReq * (schedulerConfig_->maxBatchSize - running.Size());
        }
        if (prefillCostTime > decodeWasteTime_) {
            return PDPriorityType::DECODE_FIRST;
        } else {
            decodeWasteTime_ = 0;
            return PDPriorityType::PREFILL_FIRST;
        }
    } else {
        decodeWasteTime_ = 0;
        return PDPriorityType::DECODE_FIRST;
    }
    return PDPriorityType::PREFILL_FIRST;
}

那现在我们再回到文章最开始的问题,混部场景下Prefill优先更容易触发还是Decode优先更容易触发?观察 d(n) - p(n) = D*M*n - D*\sum _{i=1}^n r(i)- P * r(n) 我们可以发现,只有r(n) 越大、且上升的越快,才会触发decode优先。

因为r(n)不超过M,所以f(n) = d(n)-p(n)可以看成是个f(n) = A*n^2-B*n的函数,只有A<<B且n不大时,f(n) < 0即触发下一轮执行decode

综上所述,在长上下文场景中,如果正在执行的req已经进入了千位以后token生成,那即使是prefillTimeMsPerReq与decodeTimeMsPerReq一个是1一个是1000,依旧无法避免现有decode被连续prefill打断,最终造成超时。

P.S.:要么修改这个莫名其妙的服务端超时阀值,要么增加chunked prefill功能,要么优化这个判断公式,要么服务端前加个buffer🐶

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐