想象你经营一家生意火爆的餐厅。客人点完菜,后厨要先“备菜”:把整桌宴席的食材一次性洗净、切好、配好料。备菜可以让七八位厨师同时动手,效率很高,但它会占满整个厨房。真正上菜时却只能一道一道来——一桌 100 道菜的超级大单能把后厨锁死半天,其他客人的菜全被堵在灶台上。这就是大模型推理中“长文本请求拖垮一切”的困境。 大模型服务也分两个阶段:先把整段提示词一次性算完、生成一份“半成品料包”(KV Cache),这叫预填充(Prefill);再逐字生成回答,叫解码(Decode)。提示词越长,预填充越久,其他请求就越要排队干等,用户等到第一个字的延迟(TTFT)飙升。 Chunked Prefill(分块预填充)的思路非常朴素:把备菜切成小份,见缝插针地插在上菜间隙完成。后厨不再被大单锁死,所有客人都能更快吃上第一口菜,餐厅整体翻台率(吞吐)也大幅提升。本文将用这套厨房故事,带你彻底看懂这项大模型部署工程师必须掌握的加速技术。

岗位关联

对 AI 从业者的价值:Chunked Prefill 是 vLLM 等主流推理框架内置的核心加速手段,掌握它能显著降低长文本请求的延迟、提升 GPU 吞吐。无论你是大模型部署工程师、推理平台开发者,还是面试 AI 推理岗位的候选人,这都是绕不开的高频考点。

目录
  1. 可视化演示
  2. 1. 背景与痛点:超级大单锁死后厨
  3. 2. 分块处理原理:把备菜切碎,见缝插针
  4. 3. KV缓存管理:半成品料包的冰箱学问
  5. 4. 连续批处理配合:智能排单系统
  6. 5. 延迟吞吐权衡:翻台率与用餐体验的博弈
  7. 6. 工程落地实践:从菜谱到看板
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

通过FIFO与LRU两种页面置换策略的步进式演示,直观对比不同算法在缺页处理和命中率上的差异。

本节配有交互动效(进入视口后自动加载,约 3KB)。

FIFO先进先出置换 缺页时替换最先进入内存的页面 维护装入顺序队列,简单高效 victim = queue.front()

1. 背景与痛点:超级大单锁死后厨

餐厅后厨最怕什么?怕一桌 100 道菜的超级大单。按照老规矩,后厨得先停下来,给这桌客人把所有食材一次性备齐。七八个厨师在案板上忙得热火朝天,其他桌客人的菜只能放在灶台边干等。备菜虽然可以并行,但它把厨房占得死死的——等这桌菜备完,别的客人早就等得不耐烦了。

大模型的“后厨”也是这样运作的。收到一次请求,模型先进入 Prefill(预填充)阶段:把用户输入的整段提示词一次性前向计算一遍,算好每个 token 的 Key 和 Value,存进 KV Cache 这份“半成品料包”。这个阶段并行度高、算得快,但要占满整块 GPU。接着进入 Decode(解码)阶段:逐字生成回答,每个 token 依赖前一个 token,只能串行,好在单步开销很小。

问题就出在提示词长度上。用户贴一篇论文、一长串聊天记录,提示词可能有几千甚至上万个 token,Prefill 的计算量随长度线性增长。一个超长请求的 Prefill 会长时间独占 GPU,把正在解码的请求全部堵住,导致所有请求的首字延迟(TTFT)集体飙升,系统吞吐跌入谷底。

回到厨房场景:传统方式就像“来了大单就全员停摆备菜”,等备完菜才开始上菜。其他桌客人等得越久,餐厅差评(延迟超时)就越多。要改变局面,必须从排队规则下手——这正是 Chunked Prefill 登场的理由。

TTFT≈L×CB\displaystyle \mathrm{TTFT} \approx \frac{L \times C}{B}
TTFT(首字延迟)约等于提示词长度 L 乘以单个 token 的计算成本 C,再除以 GPU 算力 B。它解释了“提示词越长,用户等第一个字越久”这个直觉。
  1. TTFT(Time To First Token)从用户发出请求到收到第一个输出字的延迟,是衡量“首屏体验”的核心指标。
    TTFT\mathrm{TTFT}
  2. L(提示词长度)输入 prompt 的 token 数。越长,预填充阶段要处理的数据越多。
    LL
  3. C(单 token 计算量)每个输入 token 走完一次前向传播所需的计算量,由模型结构决定。
    CC
  4. B(GPU 算力)GPU 每秒能完成的计算量。硬件越强 B 越大,单位耗时越低。
    BB

2. 分块处理原理:把备菜切碎,见缝插针

既然“一次备齐”会锁死厨房,聪明的店长改了规矩:把超级大单的备菜切成小份,每备完 10 道菜的量,就停下来先给其他桌炒两道菜,再回来备下一份。备菜和上菜交替进行,谁也不用等太久。

Chunked Prefill 正是这个思路。调度器把长提示词按块长切分(比如每 512 个 token 一块),每次只对一块做 Prefill 计算,算完立刻把结果写进 KV Cache,然后主动让出 GPU,交给调度器安排别的请求。下一次轮到它时,从断点继续算下一块,不必回头重算之前的部分。

块与块之间为什么能平滑衔接?因为注意力计算天然具有“前缀增量”特性:处理第 k 块时,只需要把第 k 块的 query 与已有的 KV Cache(前 k-1 块的结果)做注意力运算,再把本块新产生的 K/V 追加进缓存。也就是说,上下文信息通过 KV Cache 累积,分块只是把一次大计算拆成多次小计算,数学结果与整体 Prefill 完全一致。

分块让“备菜”和“上菜”在同一张灶台上交替进行。对厨师(GPU)来说,几乎没有纯等待的空档;对客人(请求)来说,最长的等待时间从“一整桌宴席的备菜时长”缩短为“一小份备菜的时长”。切得越碎,穿插越灵活,代价则是换手次数变多——这个权衡我们留到第五节细讲。

Nchunk=⌈LBc⌉\displaystyle N_{\text{chunk}} = \left\lceil \frac{L}{B_c} \right\rceil
一条长提示词会被切成多少块?总长度 L 除以块长 B_c,再向上取整——最后一块哪怕只有一个 token,也得占用一次调度机会。
  1. N_chunk(分块总数)提示词被切成的块数,决定该请求要分几轮才能完成预填充。
    NchunkN_{\text{chunk}}
  2. L(提示词总长度)整段输入的 token 数。
    LL
  3. B_c(块长)每个 chunk 包含的 token 数,是推理引擎可配置的超参数。
    BcB_c
  4. 向上取整符号最后一块不满 B_c 时也按一块计算,避免丢掉尾部 token。
    ⌈⋅⌉\left\lceil \cdot \right\rceil

3. KV缓存管理:半成品料包的冰箱学问

分块备菜之后,半成品料包怎么放?聪明的后厨在冰箱里做了一排排小格子,每份料包按固定份量装盒,做完一盒就放一盒,不用一次性腾出一大块冷藏区。冰箱永远整整齐齐,也不会因为一桌大菜挤占其他客人的空间。

这里的“料包”就是 KV Cache。传统整体 Prefill 要求一次性为整段长提示词分配连续显存,像搬进一个大冰柜;Chunked Prefill 则让 KV Cache 增量写入——每算完一个 chunk,就把这一段的 K/V 追加进缓存,内存按需增长,用多少占多少。

显存管理通常依赖 PagedAttention:显存被切成固定大小的 block(页),每个 block 可容纳固定数量 token 的 K/V。一个序列的 KV Cache 由若干 block 组成的列表管理,物理上可以不连续,逻辑上保持连续,从而几乎消灭显存碎片。更妙的是,当多个请求共享相同前缀(同一段 system prompt、同一篇文档)时,这些 block 可以被直接复用,配合引用计数做写时复制,多请求共享一份预填充结果——好比几桌客人点的都是同一道招牌菜,后厨只备一次,分桌出餐。

有了“分格冰箱”和“按需装盒”,Chunked Prefill 才能放心地把料包一盒盒往里放:既不会撑爆显存,也不会因为反复搬运产生碎冰(碎片)。缓存管理理顺了,分块调度的地基才算打牢。

MKV=2×Nlayers×dhead×Nkv_heads×L×2 bytes\displaystyle M_{\text{KV}} = 2 \times N_{\text{layers}} \times d_{\text{head}} \times N_{\text{kv\_heads}} \times L \times 2 \, \text{bytes}
单个序列的 KV Cache 显存 = 2(K 和 V 两份)× 层数 × 每个注意力头的维度 × KV 头数 × 序列长度 × 每个数值占 2 字节(FP16)。
  1. 2(双份缓存)每个 token 既要缓存 Key 也要缓存 Value,所以先乘 2。
    22
  2. N_layers(层数)Transformer 每一层都有注意力,每层都要存一份 K/V。
    NlayersN_{\text{layers}}
  3. d_head 与 N_kv_heads(头维度与头数)注意力头的总维度,决定单个 token 的缓存大小。
    dhead×Nkv_headsd_{\text{head}} \times N_{\text{kv\_heads}}
  4. L(序列长度)分块模式下它会随 chunk 逐个追加,最终等于提示词长度加输出长度。
    LL
  5. 2 bytes(FP16 字节数)半精度下每个数值占 2 字节,混合精度推理的常见配置。
    2 bytes2 \, \text{bytes}

4. 连续批处理配合:智能排单系统

老后厨是“一批菜炒完再排下一批”;新后厨上了套智能排单系统:每做完一道菜就扫一眼全局,谁快出锅谁先来,备菜只在灶台空下来时见缝插针。这套系统就是连续批处理(Continuous Batching)。

传统批处理(Static Batching)要等整批请求全部生成完才腾出 GPU,慢的请求拖累快的请求;Continuous Batching 则在每次迭代(iteration)结束时动态调整批次:生成完成的请求退出,新请求进入,谁 ready 谁上。Chunked Prefill 恰好在这个调度框架里找到了自己的生态位。

每个迭代周期,调度器优先放行需要解码的请求——因为它们每被耽搁一个迭代,词间延迟(ITL)就多抖一下;随后检查 GPU 还剩多少算力,从 prefill 队列里取一个 chunk 塞进去。解码腾出的“算力缝隙”被 chunk 填满,GPU 利用率从“过山车”变成“满负荷平路”。这便是算力重叠(overlap)的威力:显式的等待被隐藏在别人的计算时间里,餐厅的灶台永远不会因为等料而空烧。

排单系统让厨房始终处于“有人在切菜、有人在炒菜”的满负荷状态。每道菜出锅的间隙,备菜师傅就补一份料;每个解码迭代的间隙,调度器就塞一个 chunk。整条流水线严丝合缝,客人的等待时间被悄悄藏进了别人的忙碌里。

U=TcomputeTcompute+Tidle\displaystyle U = \frac{T_{\text{compute}}}{T_{\text{compute}} + T_{\text{idle}}}
GPU 利用率 = 真正在算预填充或解码的时间 ÷ 总算力时间。Chunked Prefill 的核心贡献就是减少 T_idle,把空闲的算力缝隙填满。
  1. U(GPU 利用率)衡量硬件被“用到刀刃上”的比例。
    UU
  2. T_compute(有效计算时间)GPU 真正在执行矩阵乘、注意力等计算的时间。
    TcomputeT_{\text{compute}}
  3. T_idle(空闲等待时间)GPU 无活可干、空转等待的时间,传统模式下长 prefill 会制造大量这样的空档。
    TidleT_{\text{idle}}

5. 延迟吞吐权衡:翻台率与用餐体验的博弈

切菜块头大有块头大的好处:一次切 20 道菜的量,厨师手臂抡得开、效率高,但灶台被占住的时间也长。块头小则灵活,随时能从炒菜切回备菜,但频繁换手同样有损耗。餐厅必须在“单桌体验”和“整体翻台”之间找平衡。

Chunked Prefill 不是免费的午餐。块长 B_c 越大,每次 Prefill 的矩阵乘法并行度越高,计算效率越好;但单个 chunk 占用 GPU 的连续时间变长,其他请求被插队等待的时间也随之变长,P99 延迟上升。块长越小,抢占越频繁、延迟越平稳,但 chunk 过小时内核启动开销占比增大,吞吐反而下滑。

实践中,块长通常设在系统“一个迭代能消化的 token 预算”附近,例如 max_num_batched_tokens 的 1/2 到 1 倍。同时要为不同优先级的请求配置差异化 SLO:在线对话要求 TTFT 低于 500ms,宁可给小块长;离线批量任务不在乎延迟,就给大块长换吞吐。毕竟,餐厅既有赶时间的商务客,也有不着急的聚餐客,一张菜单(一套策略)服务所有人是最低效的。

店长的智慧在于“按客分策”:对赶时间的客人提高优先级、切小块;对不着急的客人用大块换效率。延迟与吞吐从来不是“谁战胜谁”的关系,而是同一块砧板上的两种切法,关键看你要为哪一类客人保体验。

TTFTp99≈⌈LBc⌉×(tchunk+tqueue)\displaystyle \mathrm{TTFT}_{\text{p99}} \approx \left\lceil \frac{L}{B_c} \right\rceil \times \left( t_{\text{chunk}} + t_{\text{queue}} \right)
最差情况下的首字延迟约等于块数乘以“每块计算时间 + 排队等待时间”。块长越小、块数越多,但每块更短更灵活;块长越大、块数越少,可单块占用的连续时间也越长。
  1. TTFT_p99(99 分位首字延迟)最差 1% 用户的首字体验,衡量系统稳定性。
    TTFTp99\mathrm{TTFT}_{\text{p99}}
  2. 块数由提示词总长 L 与块长 B_c 决定,是延迟的放大倍数。
    ⌈LBc⌉\left\lceil \frac{L}{B_c} \right\rceil
  3. t_chunk(单块计算耗时)块长越大,单块算得越久。
    tchunkt_{\text{chunk}}
  4. t_queue(排队等待耗时)chunk 在调度队列中等待解码让路的时长,受当前负载影响。
    tqueuet_{\text{queue}}

6. 工程落地实践:从菜谱到看板

好方法还得有好管理。成熟的餐厅会把排单逻辑写进 SOP,用看板实时盯着每桌等菜时间与后厨空闲率;大模型服务的落地也一样,把 Chunked Prefill 从论文变成线上能力,离不开框架支持、指标观测和调优经验。

主流推理框架已把 Chunked Prefill 内置为开箱即用的选项:vLLM 通过 --enable-chunked-prefill 开启,配合 max_num_batched_tokens 控制每个迭代最多处理的 token 数;SGLang 在 RadixAttention 之上天然支持分块与前缀复用;TensorRT-LLM 也提供了 in-flight batching 与 chunked context 能力。对部署工程师来说,这不是“要不要造轮子”的问题,而是“怎么把轮子调顺”的问题。

工程调优讲究“先观测、后动手”。核心指标包括:TTFT(首字延迟)、ITL(词间延迟)、吞吐(token/s)和 GPU 利用率。如果 GPU 利用率低而队列里总有长 prompt,优先调大 batch 预算;如果 P99 延迟超标,再考虑调小块长。同时要认清适用边界:长上下文场景(文档问答、代码库分析、多轮对话)收益最明显;对几十 token 的短请求,分块反而引入额外调度开销,有时直接关闭更省心。

SOP 定好了、看板挂起来了,后厨才能稳定出菜。Chunked Prefill 的落地也是如此:框架负责执行,监控负责反馈,调参负责收尾——三者闭环,长文本推理这台“大胃王”才算真正被驯服。

Nblocks=⌈LBblock⌉\displaystyle N_{\text{blocks}} = \left\lceil \frac{L}{B_{\text{block}}} \right\rceil
单条请求的 KV Cache 需要多少个 block?序列长度 L 除以每 block 可容纳的 token 数 B_block,向上取整。这个数决定系统要为并发请求预留多少显存页。
  1. N_blocks(所需块数)PagedAttention 下为序列分配的物理页数量。
    NblocksN_{\text{blocks}}
  2. L(当前序列长度)提示词长度加上已生成的输出 token 数,随解码逐步增长。
    LL
  3. B_block(每块 token 数)显存分页的基本粒度,常见配置如 16 或 32。
    BblockB_{\text{block}}
  4. 向上取整最后一个不满页也占一个 block,预留时按此估算峰值显存。
    ⌈⋅⌉\left\lceil \cdot \right\rceil

AI 应用场景

场景一:长文档智能客服

阶段:推理服务部署与优化

解决的问题:用户上传几十页合同让 AI 总结,传统方式要把整份文档一次性 Prefill 才能开始回答,动辄几十秒;其他客服请求还被堵在后面排队。Chunked Prefill 将文档切成块穿插处理,长请求与短请求互相不拖累,用户也能更快看到首批总结。

具体案例:某 SaaS 法务助手把推理引擎从静态批处理切换到 vLLM + Chunked Prefill 后,P95 首字延迟从 8.2 秒降到 1.9 秒,GPU 利用率从 41% 提升到 78%,单卡并发请求数翻了一倍。

场景二:高并发对话 API

阶段:推理调度策略

解决的问题:聊天服务中请求长短混杂,少数携带超长历史记录的请求会把短请求全部堵死,造成“一人卡顿、全员陪等”。Chunked Prefill 与 Continuous Batching 配合后,解码请求优先通过,长请求的 prefill 只在算力空闲时插入。

具体案例:某 IM 应用在晚高峰时段开启两者后,短请求的 ITL 抖动下降 70%,免费用户和付费用户的口碑投诉同时明显减少,运维团队不必再为“限流还是扩容”头疼。

场景三:离线批量评测吞吐优化

阶段:吞吐与成本优化

解决的问题:离线评测、批量生成任务对单个请求的延迟不敏感,只关心单位时间完成多少请求。Chunked Prefill 配合大 batch 预算,把 GPU 的算力缝隙全部填满,显著摊薄单 token 成本。

具体案例:某模型评测实验室跑 10 万条样本,开启 Chunked Prefill 并调大 batch 后,吞吐提升 2.3 倍,整轮评测耗时从 11 小时缩短到 4.7 小时,GPU 机器采购预算省下近一半。

Python 演示

python74 行
"""对比传统整体 Prefill 与 Chunked Prefill 的推理调度效果"""import mathimport randomimport statistics  class Req:    def __init__(self, rid, arrive, prompt_len, out_len):        self.rid = rid        self.arrive = arrive        self.prompt = prompt_len        self.out = out_len        self.ttft = -1        self.finish = -1  def gen_requests(n, seed):    random.seed(seed)    reqs, t = [], 0    for i in range(n):        t += random.randint(1, 4)        prompt = random.choice([64, 128, 256, 512, 1024, 2048])        reqs.append(Req(i, t, prompt, random.randint(16, 64)))    return reqs  def simulate(reqs, budget=8, chunk=None, wp=0.5):    """chunk=None 走传统整体 Prefill, 否则走 Chunked Prefill"""    reqs = sorted(reqs, key=lambda r: r.arrive)    pref_left = {r.rid: r.prompt for r in reqs}    done = 0    t = idx = 0    while done < len(reqs):        while idx < len(reqs) and reqs[idx].arrive <= t:            idx += 1        active = reqs[:idx]        pend = [r for r in active if r.ttft < 0]        if chunk is None and pend:            r = min(pend, key=lambda x: x.arrive)            steps = max(1, math.ceil(pref_left[r.rid] * wp / budget))            r.ttft = t + steps            t += steps            continue        work = float(budget)        for r in active:            if r.ttft >= 0 and r.out > 0 and work >= 1:                r.out -= 1                work -= 1                if r.out == 0:                    r.finish = t                    done += 1        if chunk is not None and pend and work >= chunk * wp:            r = min(pend, key=lambda x: x.arrive)            pref_left[r.rid] -= chunk            if pref_left[r.rid] <= 0:                r.ttft = t        t += 1    return t  def main():    reqs = gen_requests(30, seed=7)    for name, chunk in [("传统整体 Prefill", None), ("Chunked Prefill", 8)]:        copy = [Req(r.rid, r.arrive, r.prompt, r.out) for r in reqs]        total = simulate(copy, chunk=chunk)        ttfts = sorted(r.ttft for r in copy if r.ttft >= 0)        print("=== %s ===" % name)        print("总耗时(越小吞吐越高): %d 时间步" % total)        print("平均 TTFT: %.1f | P95 TTFT: %.1f"              % (statistics.mean(ttfts), ttfts[int(len(ttfts) * 0.95) - 1]))  if __name__ == "__main__":    main()
目的:用离散事件模拟器对比两种调度策略在混合长短请求下的 P95 首字延迟与总吞吐,直观展示 Chunked Prefill 的收益来源。
  1. def simulate(reqs, budget=8, chunk=None, wp=0.5):统一模拟入口,chunk=None 表示传统整体 Prefill,传入块长则走 Chunked 路径。
  2. pref_left = {r.rid: r.prompt for r in reqs}记录每个请求还剩多少提示词 token 没算完。
  3. while done < len(reqs):一直模拟到所有请求都生成完毕为止。
  4. pend = [r for r in active if r.ttft < 0]筛出还没完成预填充的“待备菜”请求。
  5. steps = max(1, math.ceil(pref_left[r.rid] * wp / budget))传统模式一次性算完需占用的时间步。
  6. for r in active:每个时间步先给已进入解码的请求让路。
  7. work -= 1每个解码 token 消耗 1 单位算力预算。
  8. if r.out == 0:输出 token 耗尽即标记该请求完成。
  9. if chunk is not None and pend and work >= chunk * wp:解码之后还有剩余算力,就切一个 chunk 填充。
  10. pref_left[r.rid] -= chunk一个 chunk 处理完,扣减剩余量,接近零时记录 TTFT。

常见误区

误区“分块预填充是不是像压缩算法一样,把计算量变小了?那长提示词应该比以前更快才对。”

正解分块并不减少总计算量。无论分不分块,同一个提示词要做的矩阵乘法总量几乎相同。Chunked Prefill 改变的是计算的时间排列——把一大块连续占用拆成小块穿插进解码间隙,提升 GPU 利用率和系统级延迟,而不是给单个请求“减负”。

为何常见:人们容易把“整体变快”理解为“计算变少”,实际上速度提升来自消除空转等待,属于调度层面的收益。

误区“既然分块是为了不让别人等,那把块长调到 1 个 token,抢占最灵活,延迟一定最低。”

正解块长过小会让每次 chunk 的矩阵乘法并行度不足,GPU 的 Tensor Core 跑不满,内核启动开销占比升高;同时调度器频繁换手,排队损耗反而拖垮吞吐。块长需要与 batch 预算匹配,通常在“一个迭代能消化的 token 数”附近找平衡。

为何常见:直觉上“越碎越灵活”,但忽略了 GPU 是“大批量才高效”的处理器,零敲碎打同样有代价。

误区“生产环境一键开启 chunked prefill,所有请求的延迟都应该下降。”

正解分块对长请求自身而言,TTFT 可能不变甚至略增(它的 prefill 被拆散、穿插)。真正受益的是系统整体:其他请求不再被长 prefill 堵死,P99 延迟和吞吐显著改善。对几十 token 的短请求,分块只会引入额外调度开销,关闭反而更好。

为何常见:把“系统变好”与“每个请求都变好”混淆了,忽略了排队论中整体与个体的差异。

误区“每处理一个 chunk,都要把前面所有 token 重新算一遍,不然上下文不是丢了吗?”

正解不需要。注意力计算天然支持增量:处理第 k 块时,用第 k 块的 query 跟已有 KV Cache(前 k-1 块)做注意力即可,再把新 K/V 追加进缓存。分块前后数学结果完全一致,这也是 Chunked Prefill 能落地的前提。

为何常见:把“缓存”误解为“临时文件”,没意识到 KV Cache 本身就是上下文信息的载体。

误区“反正都要做批处理优化,有 Continuous Batching 就够了,Chunked Prefill 是重复功能。”

正解两者是配合关系而非竞争关系。Continuous Batching 决定“每个迭代让谁上 GPU”,Chunked Prefill 解决“长请求一次上太久”的问题。只有调度器能在迭代级让位,分块才有意义;只有分块,调度器才有细粒度的“料”去填充算力缝隙。

为何常见:把两个不同层级的优化当作同层替代品,忽略了调度粒度与任务粒度的区别。

面试题

Q1. Chunked Prefill 主要解决什么问题?

单选题 · easy

  • A.减少模型的参数总量
  • B.降低长提示词阻塞解码← 正确答案
  • C.缩短训练的数据集量
  • D.提升显卡的显存容量

答案:B。长提示词的 Prefill 会长时间独占 GPU,把正在解码的请求堵住。Chunked Prefill 把长 prefill 切成小块穿插执行,降低系统级阻塞。A、C、D 分别涉及参数量、训练数据和显存容量,都不是它的目标。

Q2. 大模型推理中 Prefill 阶段的特点是?

单选题 · easy

  • A.逐字串行地生成输出文本
  • B.并行处理全部输入 token← 正确答案
  • C.只计算最后的一个 token
  • D.完全不占用任何 GPU 资源

答案:B。Prefill 一次性并行处理整段输入,算出 KV Cache,占用大量算力;逐字串行是 Decode 阶段的特点。C、D 与事实不符。

Q3. TTFT 指的是什么?

单选题 · easy

  • A.模型整个训练过程耗时
  • B.收到首个输出字的延迟← 正确答案
  • C.GPU 峰值显存占用率
  • D.网络传输的带宽开销

答案:B。TTFT(Time To First Token)衡量从发请求到收到第一个输出字的时间,是“首屏体验”的核心指标。训练耗时、显存占用和带宽都不是它。

Q4. Chunked Prefill 中,前后两个 chunk 如何衔接?

单选题 · mid

  • A.重新计算前一个 chunk
  • B.通过增量 KV Cache 衔接← 正确答案
  • C.直接丢弃中间的缓存
  • D.把模型权重整体复制

答案:B。前一个 chunk 算出的 K/V 追加进 KV Cache,后一个 chunk 用新 query 与缓存做注意力计算,状态自然延续,无需重算、更不会丢缓存。

Q5. 其他条件不变,块长 B_c 增大,通常会导致?

单选题 · mid

  • A.单块并行计算效率更高← 正确答案
  • B.首字延迟必然会降低
  • C.显存占用必然会减少
  • D.解码速度必然会加快

答案:A。块长增大让单块矩阵乘并行度更高、效率更好,但同时占用 GPU 的连续时间变长。B、C、D 的“必然”表述都过于绝对,延迟、显存与块长并非单调关系。

Q6. Chunked Prefill 与 Continuous Batching 配合的好处有?

多选题 · mid

  • A.解码请求可随时插入执行← 正确答案
  • B.空闲算力被分块所填充← 正确答案
  • C.完全不需要 KV 缓存
  • D.等待时间被有效隐藏← 正确答案

答案:ABD。连续批处理在迭代级动态调度,解码请求优先;Chunked Prefill 用小块填充电解码腾出的算力缝隙,把等待藏在计算里。C 错得离谱——KV Cache 恰恰是必须管理的核心资源。

Q7. 长提示词请求中,KV Cache 的主要作用是?

单选题 · mid

  • A.缓存已算好的注意力键值← 正确答案
  • B.缓存模型训练中的参数
  • C.缓存用户输入的原始文本
  • D.缓存 GPU 运行的实时温度

答案:A。KV Cache 保存已算出的 Key 和 Value,避免解码时重复计算。它缓存的是中间计算结果,不是训练参数、原始文本或硬件温度。

Q8. 下列哪些框架原生支持 Chunked Prefill?

多选题 · hard

  • A.vLLM 推理引擎← 正确答案
  • B.SGLang 运行时← 正确答案
  • C.TensorRT-LLM← 正确答案
  • D.Photoshop 图像软件

答案:ABC。vLLM 通过 --enable-chunked-prefill 开启,SGLang 基于 RadixAttention 天然支持,TensorRT-LLM 提供 chunked context 能力。Photoshop 是图像软件,与推理无关。

Q9. 关于 PagedAttention 的 block 分配,说法正确的是?

单选题 · hard

  • A.以固定块为单位管理缓存← 正确答案
  • B.让每个 token 单独占一页
  • C.块的尺寸对性能毫无影响
  • D.与 Chunked Prefill 互斥

答案:A。PagedAttention 把显存切成固定大小的 block,一个 block 装多个 token 的 K/V,物理可不连续但逻辑连续,从而消除碎片。块大小影响分配粒度,且与 Chunked Prefill 是互补关系。

Q10. 同一 prompt 前缀被多请求共享时,可用的优化手段有?

多选题 · extreme

  • A.复用前缀 KV Cache 结果← 正确答案
  • B.RadixAttention 树结构← 正确答案
  • C.让每个请求重新全量计算
  • D.共享块配合写时复制← 正确答案

答案:ABD。共享前缀时,多个请求可以复用同一段 KV Cache block;SGLang 的 RadixAttention 用树结构组织前缀;PagedAttention 配合引用计数做写时复制(COW),避免共享块被误改。C 是低效的兜底,不是优化。