
在大模型LLM推理架构的生产化落地中许多技术团队最开始往往采用统一的推理池来承接全公司的所有请求。然而随着业务复杂度的提高集群内部很快分化出两类性格迥异的极端流量负载实时在线交互会话Online Interactive Sessions来自电商搜索、智能客服、实时对话助手。这类请求的 Prompt 长度较短通常在 200 到 1500 Token 之间但对首字时延TTFT 300ms和字间延迟TBT 30ms极其苛刻买家在屏幕前稍微卡顿半秒就会流失长文本离线/准实时批处理Batch Long-Context Processing来自商家资质审核、大促合同条款分析、长篇竞品研报总结。单次请求的输入 Prompt 往往长达32k、64k 甚至 128k Token。当这两种流量在同一个 GPU 推理集群中狭路相逢时会瞬间引爆灾难性的**“队头计算霸占Head-of-Line Compute Blocking”某一个长文本请求进入推理引擎后GPU 核心为了完成其超长 Prompt 的预填充计算Prefill Phase必须执行极大规模的张量自注意力矩阵乘法独占 GPU Tensor Core 长达3 到 5 秒之久在这漫长的数秒内底层推理调度器被彻底卡死全网成百上千个正在流式打字的轻量客服对话全部陷入长达数秒的“死等停滞”**后续新到达的在线请求甚至无法获得 1 毫秒的调度机会TTFT 发生毁灭性暴增为了打破“大象长任务踩死蚂蚁短会话”的调度死结我们在推理集群与网关调度层构建了基于分块预填充Chunked Prefill与动静分流的防饥饿混合调度架构。一、为什么长文本 Prefill 会霸占整个推理引擎为了深刻理解这一性能黑洞我们必须看清 GPU 推理执行的底层时间片特征传统全量 Prefill 模式 (粗暴霸占): 超长请求 (64k Token) ── [进入 GPU 执行一次性全量 Prefill] │ ▼ (Tensor Core 全力计算持续整整 4.2 秒!) ┌─────────────────────────────────────────────────────────────┐ │ 在这 4.2 秒内: │ │ 1. 所有在线短请求的 Prefill 无法插入 (TTFT 暴增至 4 秒以上) │ │ 2. 正在进行中的 50 个流式会话的 Decode 被强行中断 (光标卡死)│ └─────────────────────────────────────────────────────────────┘ 分块预填充 (Chunked Prefill) 协同调度模式: 将 64k 长文本切分成 64 个小 Chunk (每块 1024 Token): Step 1: [计算 Chunk 1 (耗时 30ms)] [混合处理 50 个在途 Decode (耗时 15ms)] Step 2: [计算 Chunk 2 (耗时 30ms)] [混合处理 50 个在途 Decode (耗时 15ms)] ... 结果: 在线用户的流式打字丝滑无感长文本批处理在间隙中平稳推进!在 Transformer 架构中Prefill 计算是典型的计算密集型Compute-Bound吞吐巨大但耗时长而 Decode 生成阶段则是典型的显存带宽受限型Memory-Bound耗时极短但对延迟极其敏感。如果强行把一个数十 K 的超大计算块一次性塞进 GPUGPU 会将整块计算作为一个不可分割的巨型 CUDA Kernel 执行在此期间任何其他请求的轻量计算都无法抢占二、分块预填充Chunked Prefill的底层调度原理分块预填充Chunked Prefill的核心思想是将长长庞大的 Prompt 切割为多个固定预算大小的计算切片Chunk Budget例如每次只计算 512 或 1024 个 Token时间片配额预算Token Budgeting调度器为每一个前向计算步Iteration Step设定一个严格的“最大 Prefill Token 上限”如max_num_batched_tokens 2048算力混合交织Piggybacking / Co-scheduling在单次 Step 中调度器优先保障所有处于 Decode 阶段的在途请求执行自回归吐字消耗少量的显存带宽与计算随后将剩余的算力预算额度分配给长文本任务的一个分块 Chunk 执行局部 Prefill 计算并累加存储到 PagedAttention 的 KV Cache 中分段渐进式就绪长任务被拆解在几十个平滑的 Step 中逐步完成其 Prefill 阶段既没有让长任务过度饥饿又彻底抹平了在线请求的长尾延迟毛刺。三、基于 Python 的 Chunked Prefill 混合调度模拟引擎实现以下展示我们在推理引擎调度层自研的核心调度器代码骨架展示如何动态拆解长上下文并保障在线请求的绝对低延迟import time from typing import List, Dict, Optional class InferenceRequest: def __init__(self, req_id: str, prompt_tokens: int, is_interactive: bool): self.req_id req_id self.total_prompt_tokens prompt_tokens self.processed_prompt_tokens 0 self.is_interactive is_interactive self.is_prefill_done False self.generated_tokens 0 self.arrival_time time.time() self.first_token_time: Optional[float] None class ChunkedPrefillScheduler: def __init__(self, max_batched_tokens: int 2048, chunk_size: int 512): self.max_batched_tokens max_batched_tokens self.chunk_size chunk_size self.waiting_queue: List[InferenceRequest] [] self.active_decode_sessions: List[InferenceRequest] [] def add_request(self, req: InferenceRequest): # 交互式短请求插入队头长文本批处理排在队尾 (优先级插队) if req.is_interactive: self.waiting_queue.insert(0, req) else: self.waiting_queue.append(req) def schedule_one_step(self): 单步调度混合编排 Decode 与分块 Chunked Prefill allocated_tokens 0 step_decode_requests [] step_prefill_chunks [] # 1. 绝对第一优先级满足所有在线在途会话的 Decode (每次仅占 1 Token) for req in self.active_decode_sessions: if allocated_tokens 1 self.max_batched_tokens: allocated_tokens 1 step_decode_requests.append(req) # 2. 第二优先级使用剩余的计算预算额度分配给 Prefill (分块按需切分) remaining_budget self.max_batched_tokens - allocated_tokens for req in list(self.waiting_queue): if remaining_budget 0: break needed_tokens req.total_prompt_tokens - req.processed_prompt_tokens # 计算本次允许计算的 Chunk 长度不超过分块上限且不超过剩余预算 tokens_to_compute min(needed_tokens, self.chunk_size, remaining_budget) step_prefill_chunks.append((req, tokens_to_compute)) remaining_budget - tokens_to_compute # 3. 模拟执行计算 self._execute_step(step_decode_requests, step_prefill_chunks) def _execute_step(self, decodes: List[InferenceRequest], prefills: List): # 执行 Decode 阶段 for req in decodes: req.generated_tokens 1 # 模拟生成完毕 (生成 50 Token 结束) if req.generated_tokens 50: self.active_decode_sessions.remove(req) # 执行 Chunked Prefill 阶段 for req, chunk_tokens in prefills: req.processed_prompt_tokens chunk_tokens # 判断该请求的 Prefill 是否全部切片计算完毕 if req.processed_prompt_tokens req.total_prompt_tokens: req.is_prefill_done True req.first_token_time time.time() # 从等待队列移出晋升入活跃 Decode 阶段 self.waiting_queue.remove(req) self.active_decode_sessions.append(req) def get_stats(self) - Dict[str, float]: return { waiting_count: len(self.waiting_queue), active_decode_count: len(self.active_decode_sessions) }四、网关层的动静双轨物理分流策略除了在单引擎内开启分块预填充我们在大促网关层还配置了**“动静分流与异构集群双轨隔离”**外部混合请求洪峰 │ [检查 Prompt 长度与业务属性] / \ (短文本 / 在线客服 2k) (长文档 / 离线报表 16k) / \ ┌──────────────────────┐ ┌──────────────────────┐ │ 实时专享 GPU 推理集群 │ │ 批处理弹性 GPU 推理池 │ │ - 保持极低排队率 │ │ - 开启 Chunked Prefill│ │ - 极速响应 TTFT │ │ - 搭配 DeepSeek 算力 │ └──────────────────────┘ └──────────────────────┘在线专属集群专供在线交易主链路单请求上下文严格限制在 4k 以内确保 GPU 始终处于超轻量计算状态TTFT 稳固锁定在 100ms 以内批处理打散集群专门接纳数万字的长研报与资质审核任务利用 DeepSeek 在大上下文上的性价比优势结合分块预填充机制以流水线方式平滑消化。五、压测对比与实战调优收益在模拟 200 个并发在线客服会话同时突发注入 20 个 64k 长文本总结的恶劣对撞压测中性能指标改善显著关键监控指标传统全量 Prefill 模式Chunked Prefill 动静分流收益对比在线客服 TTFT P99 时延4,200 ms (发生严重卡顿)145 ms首字时延优化 96.5%在线长连接字间延迟 (TBT)突发卡顿高达 3,800 ms稳定在 28 ms彻底消除打字断续感长文本批处理完成时效整体阻塞多任务频繁超时平稳流转吞吐提升 35%批处理吞吐稳步增长GPU 计算单元整体利用率忽高忽低 (算力断层严重)全时段平稳维持在 91%算力饱和度极致压榨通过分块预填充与时间片切分我们在高密度的大模型混合负载下既守住了在线交互极致低延迟的底线又将长文本计算的强大吞吐发挥得淋漓尽致。