【大模型面试突击】10_推理部署与优化

2026大模型面试:推理部署与优化必考28题(含答案)

精选自140道采集题目,保留最高频最核心的28题 | 难度:⭐基础 ⭐⭐进阶 ⭐⭐⭐深入

答案风格:一句话秒答 → 展开来说 → 面试加分


一、模型量化(GPTQ/AWQ/INT8/INT4)(6题)

1. ⭐ [高频] FP32/FP16/BF16/INT8/INT4各自的显存占用如何计算?请给出加载X B参数模型所需的显存公式。

一句话秒答: 每个参数占的字节数分别是4/2/2/1/0.5,所以加载一个X B的模型,FP16大约需要 2X GB显存,INT8就是 X GB,INT4就是 0.5X GB。

展开来说:

这事儿其实就是个乘法题,但面试官想听你说清楚背后的逻辑。

首先搞清楚每种精度一个参数占多少字节:

精度 每参数字节数 7B模型显存 70B模型显存
FP32 4 bytes ~28 GB ~280 GB
FP16/BF16 2 bytes ~14 GB ~140 GB
INT8 1 byte ~7 GB ~70 GB
INT4 0.5 byte ~3.5 GB ~35 GB

公式就是:显存 ≈ 参数量(B) × 每参数字节数 × 1.1~1.2

那个1.1到1.2的系数是留给KV Cache、激活值、CUDA context这些额外开销的。实际部署的时候,如果你开了比较大的batch size,KV Cache可能比模型本身还吃显存,这点很多人容易忽略。

还有一个细节:BF16和FP16虽然都是2字节,但BF16的指数位更多(8位 vs FP16的5位),数值范围更大,训练时更稳定不容易溢出。所以A100以上的卡做训练基本都用BF16了。

面试加分: 补一句"实际部署时显存占用不只是模型参数,还要算上KV Cache。对于长上下文场景,KV Cache的显存占用公式是 2 × num_layers × hidden_dim × seq_len × batch_size × 2bytes,这玩意在4k以上长度就开始显著了。" 面试官一听就知道你是真部署过的。


2. ⭐⭐ [高频] 什么是PTQ(训练后量化)和QAT(量化感知训练)?各自适用于什么场景?

一句话秒答: PTQ是模型训练完之后直接量化,快但精度可能掉;QAT是训练的时候就模拟量化过程,精度更好但要重新训练。

展开来说:

这俩就像装修房子------PTQ是毛坯房直接刷层漆就入住,QAT是从设计阶段就考虑好每个细节。

PTQ(Post-Training Quantization):

  • 拿一个训好的FP16模型,用一小批校准数据(几百条就够)统计各层的数值分布
  • 根据分布确定量化的scale和zero_point,直接把权重映射到低精度
  • 整个过程几分钟到几小时,不需要GPU训练
  • GPTQ、AWQ、SmoothQuant这些都属于PTQ
  • 适用场景:快速部署、资源有限不想重新训练、8bit量化精度损失可接受的情况

QAT(Quantization-Aware Training):

  • 在训练(或微调)过程中插入伪量化节点(fake quantization)
  • 前向传播时模拟量化误差,反向传播时用STE(Straight-Through Estimator)把梯度直接传回去
  • 模型在训练过程中就学会了"适应"低精度表示
  • 适用场景:需要极致量化(比如INT4甚至INT2)但又不能接受太大精度损失的场景

实际工程中的选择逻辑很简单:先试PTQ(比如GPTQ W4A16),如果精度掉得可以接受就直接用;掉太多了再考虑QAT。绝大多数情况下,7B以上的模型用GPTQ或AWQ做W4A16量化,精度损失都在1-2个百分点以内,根本不用上QAT。

面试加分: 提一下"现在业界主流其实是PTQ,因为QAT的训练成本太高了。像GPTQ和AWQ在4bit量化下的效果已经非常好了,真正需要QAT的场景是做到2-3bit这种极端量化,或者对精度要求极高的垂直领域模型。"


3. ⭐⭐ [面经] 量化权重和量化激活哪个更难?为什么量化激活更困难?(异常值/outlier问题)

一句话秒答: 量化激活比量化权重难得多,因为激活值里存在少量但数值极大的异常值(outlier),这些outlier会把量化的scale撑得很大,导致大部分正常值的精度严重损失。

展开来说:

权重的分布通常很"乖"------基本是个均匀的、接近高斯的分布,min和max之间差距不大,量化的时候scale选得很合理,每个值都能被精确表示。

但激活就不一样了。LLM的激活值有一个很头疼的特性:绝大多数值都在一个很小的范围内(比如-1到1),但总有那么几个channel会蹦出特别大的值(比如几十甚至上百)。这就是所谓的outlier问题,最早被LLM.int8()那篇论文系统性地揭示出来。

为什么outlier这么致命?假设你有一组激活值 [0.1, 0.2, 0.15, 0.3, 100.0],如果用INT8量化:

  • scale = (100.0 - 0.1) / 255 ≈ 0.39
  • 那0.1量化后就是0,0.2也是0或1,精度全毁了
  • 那个100.0虽然能被表示,但它把其他99%的值都"挤"到了一个很窄的量化范围里

而且这些outlier不是随机出现的,它们总是集中在特定的channel上,而且跨token是一致的。这其实给了我们解决的线索。

解决方案的演进:

  1. LLM.int8():把outlier channel单独拎出来用FP16算,其余channel用INT8------混合精度分解
  2. SmoothQuant:既然outlier在固定channel上,那就做个数学变换,把激活的"难度"迁移到权重上去(per-channel缩放),权重反正好量化
  3. GPTQ/AWQ:干脆只量化权重(W4A16/W8A16),激活保持FP16不动,绕开这个问题

面试加分: 说一句"所以现在主流的LLM量化方案基本都是Weight-Only Quantization(W4A16),就是只量化权重、激活保持FP16。这样既拿到了显存节省,又完美绕开了激活量化的outlier难题。真正需要量化激活的场景是要做W8A8来加速矩阵乘法(利用INT8 Tensor Core),这时候SmoothQuant是标准方案。"


4. ⭐⭐⭐ [面经] GPTQ量化的核心原理是什么?它基于什么数学方法(OBS/OBQ)?

一句话秒答: GPTQ基于OBS(Optimal Brain Surgeon)的思想,逐列量化权重矩阵,每量化一个权重就用Hessian逆矩阵来补偿其他未量化权重,使得整层的输出误差最小化。

展开来说:

GPTQ的祖宗是上世纪90年代LeCun搞的OBD/OBS(Optimal Brain Damage/Surgeon),核心思想是:模型剪枝(或量化)时不是所有参数都一样重要,我们可以用二阶信息(Hessian矩阵)来评估每个参数的重要性,并在去掉某个参数后补偿其他参数来最小化损失。

具体到GPTQ:

  1. 问题定义 :对于每一层,我们要找量化后的权重 W^\hat{W}W^,使得 ∣∣WX−W^X∣∣2||WX - \hat{W}X||^2∣∣WX−W^X∣∣2 最小(X是输入激活)

  2. 逐列量化:不是一次性量化整个矩阵,而是一列一列地处理。量化第i列权重时:

    • 把该列权重round到最近的量化值
    • 计算产生的量化误差
    • 用Hessian逆矩阵 (XXT)−1(X X^T)^{-1}(XXT)−1 把这个误差"分摊"到还没量化的列上
    • 更新后续列的权重值来补偿
  3. 关键加速trick

    • 不需要完整的Hessian矩阵,只需要 H=2XXTH = 2XX^TH=2XXT 的逆(用Cholesky分解高效计算)
    • 一次处理128列(block-wise),用分块矩阵运算加速
    • 整个过程只需要几百条校准数据
  4. 时间复杂度 :对于一个 drow×dcold_{row} \times d_{col}drow×dcol 的权重矩阵,复杂度约 O(drow⋅dcol2)O(d_{row} \cdot d_{col}^2)O(drow⋅dcol2)

实际效果:GPTQ可以在几个小时内把一个175B的模型量化到4bit,精度损失很小。这使得LLaMA-65B可以在单张4090上运行(4bit下约32GB)。

面试加分: 补一个和AWQ的对比------"GPTQ是通过Hessian补偿来减少量化误差,相当于'事后补救';而AWQ是先识别重要权重再量化,相当于'事前规划'。实际效果上两者差不多,但AWQ在推理速度上通常更快,因为它的量化格式对GPU更友好。"


5. ⭐⭐⭐ [面经] AWQ(Activation-aware Weight Quantization)量化的核心思想是什么?它与GPTQ有什么区别?

一句话秒答: AWQ的核心思想是"不是所有权重都一样重要"------通过观察激活值的分布来找出重要权重(对应激活值大的channel),对这些重要权重做缩放保护后再量化,而不是像GPTQ那样做Hessian补偿。

展开来说:

AWQ的直觉特别优雅:

模型里有1%的"显著权重"(salient weights),如果你把它们保持FP16不量化,模型精度就几乎不掉。但问题是,混合精度(一部分FP16一部分INT4)对硬件不友好,没法高效计算。

AWQ的解法是:既然不能不量化这些重要权重,那就在量化之前给它们乘一个缩放因子 sss,放大它们的数值范围,这样量化误差相对就变小了。对应的激活值除以 sss 来保持数学等价。

具体步骤:

  1. 识别重要性:用校准数据跑一遍前向传播,统计每个channel的激活值大小(绝对值均值)
  2. 计算缩放因子 :对每个channel求一个最优缩放因子 sss,使得该channel量化后的误差最小。这是个一维搜索问题,计算量很小
  3. 缩放+量化 :权重乘以 sss,做标准的量化;推理时激活除以 sss(这个可以fuse进前一层的操作里)

AWQ vs GPTQ对比:

维度 GPTQ AWQ
核心方法 Hessian逆补偿(二阶优化) 激活感知缩放(一阶启发式)
量化粒度 逐列处理 逐channel缩放
校准数据需求 ~128条 ~128条
量化速度 中等(需要Hessian逆) 更快(只需简单统计)
推理速度 取决于kernel实现 通常更快(格式更规整)
精度 两者接近,各有胜负 两者接近,各有胜负

面试加分: "工程上我一般的选择策略是:如果用vLLM部署,AWQ和GPTQ都支持得很好,优先试AWQ因为量化更快;如果用llama.cpp部署,用GGUF格式的量化。核心就是看推理框架对哪种格式的kernel优化更好。"


6. ⭐⭐ [高频] bitsandbytes库的LLM.int8()方法的原理是什么?如何处理激活中的异常值?

一句话秒答: LLM.int8()的核心是混合精度分解------把激活矩阵中包含outlier的channel(通常不到1%)单独拎出来用FP16计算,剩余channel用INT8计算,最后把结果加起来。

展开来说:

这是Tim Dettmers那篇很有影响力的论文,第一次系统性地揭示了LLM激活中的outlier问题并给出了实用方案。

具体工作流程:

  1. 检测outlier:对于每个token的激活向量,扫描所有维度,找出绝对值超过阈值(默认6.0)的维度。这些维度就是outlier channel

  2. 矩阵分解:把一个矩阵乘法拆成两部分

    复制代码
    Y = X_normal × W_normal^T  (INT8)
      + X_outlier × W_outlier^T (FP16)
    • X_normal:去掉outlier channel后的激活,做INT8量化后用INT8 matmul
    • X_outlier:只包含outlier channel的激活,保持FP16用FP16 matmul
    • 对应的权重列也做相同的分割
  3. 合并结果:两部分的结果都转回FP16后相加,得到最终输出

关键发现:

  • Outlier channel通常只占总维度的0.1%到1%,所以绝大部分计算还是走INT8,性能影响很小
  • 这些outlier channel在不同token之间是一致的(总是那几个固定的维度),说明这是模型学到的结构性特征
  • 6B参数以上的模型才会出现系统性的outlier,小模型反而没这个问题

面试加分: "LLM.int8()在工程上的主要价值是零精度损失地把显存砍半,但推理速度其实没有提升甚至略慢,因为那个矩阵分解+合并有额外开销。如果追求速度,应该用W4A16(GPTQ/AWQ)或者W8A8(SmoothQuant)。bitsandbytes更适合资源受限时的微调场景(配合QLoRA),而不是高性能推理。"


二、推理框架(vLLM/TensorRT-LLM/TGI)(5题)

7. ⭐ [高频] 常见的LLM推理框架有哪些?各自适用于什么场景?如何选择?

一句话秒答: 主流四个:vLLM(通用性强、上手快)、TensorRT-LLM(极致性能、NVIDIA生态)、TGI(HuggingFace生态)、SGLang(复杂LLM程序)。选择逻辑就是看你要什么------快速上线选vLLM,榨干性能选TRT-LLM,复杂调度选SGLang。

展开来说:

框架 开发者 核心优势 适用场景 上手难度
vLLM UC Berkeley PagedAttention、连续批处理、生态好 通用在线服务
TensorRT-LLM NVIDIA 极致kernel优化、FP8支持 追求极致延迟/吞吐
TGI HuggingFace 与HF生态无缝集成 HF模型快速部署
SGLang UC Berkeley RadixAttention、结构化生成 多轮/复杂LLM程序
llama.cpp Georgi Gerganov 纯CPU/边缘设备 本地推理、嵌入式
MLC-LLM CMU 跨平台编译 手机/浏览器端

选择的决策树很简单:

  1. 是不是NVIDIA GPU?不是的话选择就少了,考虑llama.cpp或MLC-LLM
  2. 要不要极致性能?要的话上TensorRT-LLM,接受较高的工程复杂度
  3. 有没有复杂的多轮/结构化生成需求?有的话SGLang
  4. 以上都不是?vLLM,社区活跃、功能全面、开箱即用

面试加分: "实际工程中我会根据三个维度来决策:第一是吞吐需求(TRT-LLM > vLLM > TGI),第二是开发效率(vLLM ≈ TGI > SGLang > TRT-LLM),第三是功能丰富度比如对量化格式的支持、LoRA热切换等。2025年之后vLLM的性能差距在快速缩小,大部分场景用vLLM就够了。"


8. ⭐⭐ [高频] vLLM的核心优化技术是什么?它相比HuggingFace Transformers和TGI的吞吐量提升分别是多少?

一句话秒答: vLLM的核心武器是PagedAttention------用操作系统虚拟内存分页的思想来管理KV Cache,把KV Cache的显存浪费从60-80%降到不到4%。吞吐量比HF Transformers高14-24倍,比TGI高2-4倍。

展开来说:

先说为什么KV Cache是瓶颈。LLM推理时,每个请求都要维护一个KV Cache,存储所有已生成token的K和V向量。传统实现是给每个请求预分配一块连续显存(按最大序列长度),但实际使用中:

  • 你不知道用户会生成多长,只能按max_length预分配
  • 大量请求提前结束,分配的显存白白浪费
  • 不同请求的KV Cache长度不同,产生大量内存碎片

PagedAttention的解法:

跟操作系统的虚拟内存一模一样。把KV Cache切成固定大小的block(比如每个block存16个token的KV),维护一个block table做映射:

  • 逻辑上每个请求的KV Cache是连续的
  • 物理上这些block可以散布在GPU显存的任意位置
  • 需要新block时动态分配,请求结束立刻回收
  • 内存浪费只发生在最后一个block内部(不到一个block的大小)

除了PagedAttention,vLLM还有:

  • 连续批处理(Continuous Batching):请求完成立即替换,不等整个batch跑完
  • 前缀缓存(Prefix Caching):相同system prompt的请求共享KV Cache
  • LoRA热切换:多个LoRA adapter在线切换,共享base model
  • 投机解码:支持draft model加速

面试加分: "PagedAttention的另一个精妙之处在于它天然支持共享------比如beam search时多个beam可以共享前缀的KV Cache block(copy-on-write),或者多个请求共享相同的system prompt。这在传统实现里需要额外的显存拷贝,PagedAttention只需要多个逻辑指针指向同一个物理block。"


9. ⭐⭐ [高频] 什么是TensorRT-LLM?它与原始TensorRT有何区别和联系?

一句话秒答: TensorRT-LLM是NVIDIA专门为LLM推理打造的框架,底层用TensorRT做图优化和kernel融合,但上层加了一整套LLM专属优化(KV Cache管理、in-flight batching、量化、张量并行)。简单说,TensorRT是通用推理引擎,TensorRT-LLM是LLM专用推理引擎。

展开来说:

原始TensorRT是NVIDIA的通用深度学习推理优化器:

  • 接收ONNX/PyTorch模型,做layer fusion、kernel auto-tuning、精度校准
  • 输出一个优化后的engine,推理时直接加载执行
  • 适用于CV、NLP各种模型,但对LLM的自回归特性支持不好

TensorRT-LLM在此基础上加了LLM的全套buffet:

  1. 模型定义层:用类似PyTorch的Python API定义LLM架构(不需要ONNX导出那套了)
  2. KV Cache管理:Paged KV Cache(借鉴vLLM的思路)
  3. In-flight Batching:NVIDIA版的连续批处理,叫inflight batching
  4. 量化支持:FP8(H100/H200)、INT8、INT4-AWQ/GPTQ,kernel深度优化
  5. 并行策略:张量并行(TP)+ 流水线并行(PP),多GPU/多节点
  6. 自定义Attention kernel:针对不同GPU架构(Ampere/Hopper/Blackwell)手写的极致优化kernel
  7. FP8支持:在H100上利用FP8 Tensor Core,性能比FP16翻倍

用法流程

复制代码
定义模型 → 编译为TRT engine → 加载engine + Triton Server部署

面试加分: "TensorRT-LLM的性能确实是天花板级别的,但它的开发和调试体验相对痛苦------模型需要用它自己的API重新定义,新模型支持有滞后,排查问题也比较困难。所以在团队工程能力足够且对性能有极致追求时才建议用,否则vLLM的ROI更高。"


10. ⭐⭐⭐ [面经] SGLang相比vLLM有哪些独特的优化?RadixAttention是什么?

一句话秒答: SGLang的杀手锏是RadixAttention------用Radix Tree(基数树)自动管理和复用多个请求之间的KV Cache前缀,特别适合多轮对话、few-shot、树状搜索这类有大量前缀重叠的场景。

展开来说:

vLLM的prefix caching是手动的------你要显式指定哪些前缀是共享的。SGLang的RadixAttention把这事自动化了。

RadixAttention原理:

维护一棵全局的Radix Tree(基数树/前缀树),树的每个节点对应一段token序列及其KV Cache:

  • 新请求来了,先在树上查找最长前缀匹配
  • 匹配到的部分直接复用KV Cache,不用重新计算
  • 没匹配到的部分才做prefill
  • 请求结束后,其KV Cache被插入到树中,供后续请求复用
  • 用LRU策略淘汰不常用的缓存节点

这在以下场景效果炸裂:

  • 多轮对话:每轮对话都共享之前所有轮的KV Cache
  • Few-shot learning:所有请求共享相同的few-shot example的KV Cache
  • Tree-of-Thought/beam search:树状分支共享公共前缀
  • 批量处理:相同system prompt的一批请求

SGLang的其他优化:

  1. 结构化生成加速:JSON Schema约束下的生成,用compressed finite state machine加速(比传统逐token约束快很多)
  2. 前端DSL:提供Python级别的LLM编程原语(fork/join/select),让复杂的LLM程序更容易写
  3. Overlap scheduling:计算和通信的overlap优化

面试加分: "选vLLM还是SGLang,关键看你的请求模式。如果是简单的单轮问答、请求之间没什么前缀重叠,两者差别不大;如果是多轮对话、Agent循环调用、或者批量处理有共同system prompt的请求,SGLang的RadixAttention能带来显著的TTFT(首token延迟)降低和吞吐提升。"


11. ⭐⭐ [面经] 对比vLLM、TGI、TensorRT-LLM在模型量化方面的支持情况。

一句话秒答: TensorRT-LLM量化支持最全面(FP8/INT8/INT4/AWQ/GPTQ),vLLM紧随其后(AWQ/GPTQ/FP8/bitsandbytes),TGI相对基础(GPTQ/AWQ/bitsandbytes)。

展开来说:

量化方案 vLLM TGI TensorRT-LLM
FP8 (W8A8) 支持(H100+) 有限支持 深度优化(H100专属kernel)
INT8 (W8A8) 支持 支持 深度优化
GPTQ (W4A16) 支持(Marlin kernel加速) 支持 支持
AWQ (W4A16) 支持(Marlin kernel加速) 支持 深度优化
bitsandbytes 支持 支持 不支持
SmoothQuant 有限支持 不支持 深度优化
GGUF 有限支持 不支持 不支持

几个关键点:

  1. vLLM的Marlin kernel:这是vLLM针对W4A16(GPTQ/AWQ)写的高性能kernel,在A100上能达到接近理论峰值的性能,大幅缩小了和TRT-LLM的差距

  2. TRT-LLM的FP8优势:在H100/H200上,TRT-LLM的FP8支持是最成熟的,能充分利用FP8 Tensor Core,比FP16快接近2倍

  3. TGI的定位:TGI更注重易用性,量化支持够用但不追求极致性能

  4. 量化格式兼容性:不同框架之间的量化模型不一定通用。GPTQ/AWQ模型是框架无关的,但TRT-LLM需要编译为自己的engine格式

面试加分: "选择量化方案时不能只看精度损失,还要看框架的kernel实现质量。同样是W4A16 GPTQ,vLLM用Marlin kernel和TGI用ExLlama kernel的实际推理速度可能差2-3倍。所以我一般的做法是先确定推理框架,再看这个框架对哪种量化格式的kernel优化最好。"


三、FlashAttention与投机解码(5题)

12. ⭐ [高频] 什么是FlashAttention?它解决了标准注意力计算的什么问题?

一句话秒答: FlashAttention是一种IO感知的精确注意力算法,通过分块计算(tiling)和重计算(recomputation)策略,避免在GPU HBM中存储巨大的 N×N 注意力矩阵,将注意力计算的显存从 O(N²) 降到 O(N),同时实际速度还更快。

展开来说:

标准注意力的问题出在哪?

复制代码
标准流程:Q×K^T → S (N×N矩阵,存到HBM) → softmax(S) → P (又一个N×N,存到HBM) → P×V → O

当序列长度N=4096时,这个N×N矩阵有1600万个元素,FP16下占32MB。听起来不多?但这只是一个head一个layer的。而且问题不在于显存占用本身,而在于这些大矩阵要在GPU的HBM(高带宽但相对慢的显存)和SRAM(快但很小的片上缓存)之间来回搬运,IO成为瓶颈。

FlashAttention的核心做法:

  1. 分块(Tiling):把Q、K、V切成小块,每次只加载一小块到SRAM里计算
  2. 在线Softmax:用一个巧妙的在线算法(维护running max和running sum),可以在不知道完整一行的情况下逐块计算softmax
  3. 不存储中间矩阵:N×N的注意力矩阵从不完整地存在HBM中,每个小块算完直接更新输出,SRAM里的临时数据就扔了
  4. 反向传播时重计算:反向传播需要注意力矩阵?没存怎么办?重新算一遍------重计算比从HBM读还快(因为计算便宜IO贵)

结果:

  • 显存:O(N²) → O(N)
  • 速度:快2-4倍(减少了HBM读写)
  • 精度:完全精确,不是近似算法

面试加分: "FlashAttention不是近似注意力,它的计算结果和标准注意力是bit-identical的(不考虑浮点运算顺序的微小差异)。它快的原因不是省了计算量(FLOPs一样),而是省了IO。这是一个很好的例子说明:在现代GPU上,算法的性能瓶颈往往不是计算量而是内存带宽。"


13. ⭐⭐ [高频] FlashAttention的"IO感知"(IO-aware)是什么含义?它如何利用GPU的SRAM和HBM层级结构?

一句话秒答: IO感知就是算法设计时把GPU的内存层级结构(SRAM vs HBM)纳入考量,尽量让数据留在快速的SRAM里完成计算,减少对慢速HBM的读写次数。

展开来说:

先理解GPU的内存层级:

复制代码
GPU内存层级:
┌─────────────────────┐
│   SRAM (片上缓存)     │  ~20MB, ~19TB/s 带宽
│   每个SM约192KB       │  超快但超小
├─────────────────────┤
│   HBM (显存)         │  40-80GB, ~2TB/s 带宽
│   就是你看到的显存大小   │  大但相对慢
└─────────────────────┘
       ↕ 带宽差约10倍

A100为例:HBM带宽2TB/s,SRAM带宽19TB/s,差了将近10倍。

标准注意力的IO问题:

复制代码
Q,K从HBM读到SRAM → 计算S=QK^T → S写回HBM → S从HBM读到SRAM → 
softmax → P写回HBM → P从HBM读到SRAM → P×V → O写回HBM

光一个注意力层就要对HBM读写好多次,每次都是N²规模的数据。

FlashAttention的IO优化:

复制代码
for each block of Q:
    for each block of K, V:
        从HBM加载Q_block, K_block, V_block到SRAM  // 只读小块
        在SRAM中计算局部S = Q_block × K_block^T     // 在SRAM里完成
        在SRAM中更新softmax的running statistics     // 不写回HBM
        在SRAM中累加O_block += softmax(S) × V_block // 还是在SRAM
    把O_block写回HBM                               // 只写输出

总的HBM读写量从 O(N²d + N²) 降到了 O(N²d² / M),其中M是SRAM大小。当M足够大时(实际中通常满足),这等价于 O(N²d / √M),远小于原始的 O(N²)。

面试加分: "这个思想其实和CPU编程里的cache-oblivious算法一脉相承------矩阵乘法的分块优化在HPC领域已经研究了几十年,FlashAttention把这个思路搬到了GPU attention计算上。真正难的是那个在线softmax的数学推导,因为softmax需要知道全局最大值,但分块计算时你看不到全局。Dao用了一个优雅的数学技巧------维护每个block的局部最大值和指数和,在合并时用纠正因子调整。"


14. ⭐⭐ [高频] FlashAttention-2相比FlashAttention-1有哪些改进?

一句话秒答: FlashAttention-2主要做了三个优化:减少非矩阵乘法运算、改善线程块(warp)之间的工作分配、增加并行度(支持序列维度并行),在A100上达到理论峰值FLOPs的72%(FA1只有约50%)。

展开来说:

改进一:减少非matmul FLOPs

FA1里softmax的rescaling操作会产生大量非矩阵乘法运算(逐元素除法、指数运算等)。这些操作用不上Tensor Core,很浪费。

FA2重新组织了计算顺序:

  • 把softmax的rescaling推迟到最后
  • 在内循环里只做矩阵乘法和简单的指数/max运算
  • 非matmul FLOPs减少了约4倍

为什么这很重要?因为A100的Tensor Core做矩阵乘法的吞吐是312 TFLOPS(FP16),但普通CUDA Core的算力只有19.5 TFLOPS,差了16倍。让更多计算走matmul = 更充分地利用硬件。

改进二:优化warp级并行

FA1中,每个thread block内的多个warp都要读Q、K、V,然后需要sync和通信来合并softmax统计量。

FA2改为:

  • Q在warp之间分割(每个warp负责Q的不同行)
  • K和V在所有warp之间共享(通过shared memory广播)
  • 每个warp独立计算自己那部分Q对应的输出,不需要warp间通信
  • 减少了shared memory的读写和同步开销

改进三:序列维度并行

FA1只在batch和head维度做并行,当batch size小或head数少时GPU占用率低。

FA2支持在序列维度额外并行------把长序列的不同block分配到不同的SM上处理,提高GPU利用率。特别是在长序列+小batch场景下效果显著。

性能对比:

  • FA1:A100上达到约50% peak FLOPs
  • FA2:A100上达到约72% peak FLOPs
  • 实际加速:FA2比FA1快约2倍

面试加分: "后来还有FlashAttention-3,针对H100的Hopper架构做了更深层的优化------利用了WGMMA指令(异步warp级矩阵乘法)、TMA(Tensor Memory Accelerator)硬件单元做异步数据搬运、以及FP8支持。在H100上能达到接近740 TFLOPS。不过FA3目前只支持Hopper架构。"


15. ⭐⭐ [高频] 什么是投机解码(Speculative Decoding)?它的核心思想是什么?

一句话秒答: 投机解码是用一个小而快的draft模型先"猜"若干个token,然后用大模型一次性并行验证这些token,猜对的直接接受,猜错的从错误位置开始用大模型的分布重新采样。核心思想是"用并行验证替代串行生成"。

展开来说:

LLM解码的痛点是什么?自回归生成天生是串行的------每生成一个token都要跑一次完整的大模型forward。但这些forward大部分时间GPU是空闲的(memory-bound,计算单元吃不饱)。

投机解码的思路就是:既然GPU算力有富余,不如让它同时做更多有用的事。

工作流程:

复制代码
1. Draft阶段:小模型(比如7B)自回归生成K个token(比如K=5)
   小模型生成:[t1, t2, t3, t4, t5]

2. Verify阶段:大模型(比如70B)一次forward,同时计算这5个位置的概率分布
   大模型验证:把[原始context, t1, t2, t3, t4, t5]一起喂进去
   
3. Accept/Reject:从左到右逐个检查
   - t1: 大模型也会生成t1?接受 ✓
   - t2: 大模型也会生成t2?接受 ✓  
   - t3: 大模型不会生成t3?拒绝 ✗ → 用大模型在这个位置重新采样得到t3'
   - t4, t5: 作废,因为t3已经错了

4. 输出:[t1, t2, t3'] → 下一轮继续

为什么能加速?

  • 大模型验证K个token只需要1次forward(并行处理),而串行生成K个token需要K次forward
  • 只要draft模型的token接受率够高(比如70-80%),平均每次大模型forward就能产出3-4个有效token
  • 加速比约为:接受的平均token数 / (1 + draft模型开销/大模型开销)
  • 典型加速:2-3倍

面试加分: "投机解码的一个优美之处在于它保证输出分布和原始大模型完全一致------不是近似,是精确等价。这是通过一个精心设计的拒绝采样方案实现的。所以它是一种'免费的午餐'------只加速不损精度。但它对draft模型和target模型的分布相似度有要求,如果小模型太烂全被拒绝,那就没有加速效果。"


16. ⭐⭐⭐ [面经] 投机解码中token被接受或拒绝的判断标准是什么?如何保证最终输出分布与原始模型一致?

一句话秒答: 对于每个draft token,用 min(1, p(x)/q(x)) 的概率接受(p是大模型概率,q是小模型概率),被拒绝后从修正分布 max(0, p(x)-q(x)) 中重新采样。这个方案在数学上保证最终输出分布精确等于大模型的分布。

展开来说:

这是投机解码最精妙的部分,本质上是一种改进的拒绝采样(rejection sampling)。

逐token验证过程:

设draft模型在位置i生成token xix_ixi,此时大模型概率为 p(xi)p(x_i)p(xi),draft模型概率为 q(xi)q(x_i)q(xi)。

  1. 计算接受概率 :α=min⁡(1,p(xi)q(xi))\alpha = \min\left(1, \frac{p(x_i)}{q(x_i)}\right)α=min(1,q(xi)p(xi))

  2. 以概率 α\alphaα 接受 :生成一个均匀随机数 r∼U(0,1)r \sim U(0,1)r∼U(0,1)

    • 如果 r<αr < \alphar<α:接受 xix_ixi,继续验证下一个token
    • 如果 r≥αr \geq \alphar≥α:拒绝 xix_ixi,从修正分布中重新采样
  3. 修正分布 :p′(x)=max⁡(0,p(x)−q(x))∑x′max⁡(0,p(x′)−q(x′))p'(x) = \frac{\max(0, p(x) - q(x))}{\sum_{x'} \max(0, p(x') - q(x'))}p′(x)=∑x′max(0,p(x′)−q(x′))max(0,p(x)−q(x))

为什么这保证了分布一致?

直觉上理解:

  • 如果大模型给某个token的概率比小模型高(p > q),那小模型生成它时一定被接受(接受概率为1)
  • 如果大模型给某个token的概率比小模型低(p < q),那以 p/q 的概率接受------接受的token正好按p的分布出现
  • 被拒绝后,从修正分布 p' 中采样,补上"大模型想要但小模型没怎么给的"那些token

数学上的证明:

token xxx 最终被选中的概率 = 直接被接受的概率 + 被拒绝后重新采样到的概率

P(x)=q(x)⋅min⁡(1,p(x)q(x))+(1−∑x′q(x′)⋅min⁡(1,p(x′)q(x′)))⋅p′(x)P(x) = q(x) \cdot \min\left(1, \frac{p(x)}{q(x)}\right) + \left(1 - \sum_{x'} q(x') \cdot \min\left(1, \frac{p(x')}{q(x')}\right)\right) \cdot p'(x)P(x)=q(x)⋅min(1,q(x)p(x))+(1−x′∑q(x′)⋅min(1,q(x′)p(x′)))⋅p′(x)

展开化简后恰好等于 p(x)p(x)p(x)。

一些工程细节:

  • 验证是并行的:大模型一次forward算出所有位置的概率
  • draft长度K是个超参数:太小加速不明显,太大拒绝率上升浪费draft计算
  • 实际中K一般取4-8,根据接受率动态调整
  • 温度采样时接受率会下降(温度越高分布越平,小模型和大模型差异越大)

面试加分: "实际部署中,投机解码的加速效果取决于三个因素:一是draft模型和target模型的分布KL散度(越小接受率越高),二是draft模型本身的推理速度(越快越好),三是target模型单步推理中计算和IO的比例(越IO-bound越适合投机解码,因为batch验证不增加太多延迟)。对于70B模型配7B draft模型,在greedy decoding下接受率通常在80%以上,加速比2-3倍。"


四、批处理与Prefill-Decode分离(5题)

17. ⭐⭐ [高频] 什么是连续批处理(Continuous Batching)?它如何解决静态批处理的效率问题?

一句话秒答: 连续批处理允许一个请求完成后立即用新请求替补进batch,而不是等整个batch全部完成再换。核心思想是"iteration级调度"替代"request级调度",GPU不再因为短请求等长请求而空转。

展开来说:

静态批处理的问题:

假设batch里有4个请求,生成长度分别是10、50、200、30个token:

复制代码
静态批处理:
请求A: |====|................(padding)...............| 生成10个就完了,但要等200个
请求B: |========================|...................|  50个
请求C: |================================================| 200个
请求D: |=============|..............................|     30个
       ↑ 同时开始                                      ↑ 同时结束
       
大量GPU时间浪费在padding上!

连续批处理(又叫iteration-level scheduling):

复制代码
请求A: |====| → 请求E替补进来
请求B: |========================| → 请求F替补进来
请求C: |================================================|
请求D: |=============| → 请求G替补进来 → 请求H替补进来
       ↑ 每个iteration检查一次,有空位就补新请求

每次decode iteration(生成一个token)之后:

  1. 检查哪些请求已经结束(生成了EOS或达到max_length)
  2. 把结束的请求移出batch,把等待队列里的新请求插入
  3. 对新插入的请求做prefill,对已有请求做decode
  4. 继续下一个iteration

效果:

  • 吞吐量提升2-10倍(取决于请求长度的方差)
  • GPU利用率从30-50%提升到80%+
  • 长尾延迟也改善了(短请求不用等长请求)

vLLM、TGI、TensorRT-LLM现在全都默认用连续批处理。

面试加分: "连续批处理引入了一个新的调度挑战------新请求的prefill是compute-bound的,而已有请求的decode是memory-bound的,两者的计算特性不同。如果prefill太长会blocking掉decode请求,导致已有请求的token延迟抖动。这就引出了后面的chunked prefill和prefill-decode分离。"


18. ⭐ [高频] 大模型推理分为哪两个阶段?Prefill阶段和Decode阶段各自的计算特点是什么?

一句话秒答: Prefill阶段一次性处理整个输入prompt(compute-bound,计算密集),Decode阶段逐token自回归生成(memory-bound,带宽瓶颈)。两个阶段的计算特性截然不同。

展开来说:

Prefill阶段(预填充):

  • 输入:整个prompt(比如1000个token)

  • 计算方式:所有token并行通过transformer

  • 计算特性:Compute-bound------大矩阵乘法,Tensor Core跑满

  • 瓶颈:GPU算力

  • 产出:prompt每个位置的KV Cache + 第一个output token

  • 类比:一口气看完一整篇文章

    输入: [token_1, token_2, ..., token_1000]
    ↓ 并行计算
    输出: KV Cache (1000个位置) + 第一个生成token

Decode阶段(自回归生成):

  • 输入:上一步生成的1个token + 之前所有的KV Cache

  • 计算方式:每次只处理1个新token(但要attend到所有历史token的KV Cache)

  • 计算特性:Memory-bound------每个token的计算量很小,但要从HBM读取整个KV Cache

  • 瓶颈:GPU显存带宽

  • 产出:下一个token

  • 类比:一个字一个字地写回复,每写一个字都要回顾全文

    Step 1: [new_token_1] + KV_Cache → next_token_2
    Step 2: [new_token_2] + KV_Cache → next_token_3
    ...每步都要读完整KV Cache

关键数字对比(以7B模型为例):

指标 Prefill Decode
计算量/token ~14 GFLOP ~14 GFLOP
总计算量 14G × prompt_len 14G × 1
数据读取 ~14GB(模型参数) ~14GB(模型参数) + KV Cache
算术强度 高(batch维度大) 极低(batch=1)
瓶颈 计算 带宽

面试加分: "这两个阶段的不同计算特性是后面所有优化方向的源头------量化和蒸馏主要帮decode阶段(减少要读的参数量),FlashAttention主要帮prefill阶段(减少注意力计算的IO),投机解码帮decode阶段(用并行验证替代串行生成),连续批处理通过增大batch size来让decode阶段也能利用上计算资源。"


19. ⭐⭐ [高频] 为什么Decode阶段是memory-bound而非compute-bound?

一句话秒答: 因为decode每步只处理1个token------要做的计算很少,但要把整个模型参数(几十GB)从HBM读一遍,GPU算力还没跑满数据就读完了。算术强度(FLOPs/Byte)远低于GPU的计算带宽比。

展开来说:

搞清楚这个问题需要理解一个关键概念:算术强度(Arithmetic Intensity)= FLOPs / Bytes

对于一个矩阵乘法 Y = X × W,其中 X 是 (batch, d_in),W 是 (d_in, d_out):

  • FLOPs = 2 × batch × d_in × d_out
  • Bytes读取 = (batch × d_in + d_in × d_out) × 2bytes(FP16)
  • 算术强度 ≈ 2 × batch × d_in × d_out / (d_in × d_out × 2) = batch(当batch远小于d时)

A100的分界线(Roofline模型):

  • 计算能力:312 TFLOPS(FP16 Tensor Core)
  • HBM带宽:2 TB/s
  • 分界点 = 312T / 2T = 156

也就是说,算术强度要达到156以上才是compute-bound。

Decode阶段 batch=1 时:

  • 算术强度 ≈ 1(远小于156)
  • GPU 99%的时间在等数据从HBM传过来
  • Tensor Core绝大部分时间是空闲的

Prefill阶段 prompt_length=1000 时:

  • 相当于 batch=1000
  • 算术强度 ≈ 1000(远大于156)
  • GPU算力被充分利用

这就是为什么:

  • Decode要靠增加batch size来提高利用率(连续批处理的意义)
  • 量化(减少参数字节数→减少IO)对decode加速明显
  • KV Cache的读取也是IO开销的一部分,长序列时更明显

面试加分: "理解了这个Roofline分析,很多推理优化的逻辑就清楚了。比如为什么W4A16量化能在decode阶段几乎线性加速4倍------因为decode是memory-bound,模型参数减少4倍意味着读取量减少4倍,而计算量不变(反量化在计算中完成)。但同样的量化对prefill阶段加速就小得多,因为prefill是compute-bound,减少IO不是瓶颈。"


20. ⭐⭐⭐ [面经] 什么是Prefill-Decode分离(Disaggregated Serving)?为什么要将两个阶段分到不同的GPU上?

一句话秒答: 把prefill和decode调度到不同的GPU(甚至不同的机器)上执行,因为两者的计算特性完全不同------prefill需要大算力,decode需要大带宽。混在一起会互相干扰,分开后各自的硬件利用率都能拉满。

展开来说:

为什么要分离?混合调度的问题:

在连续批处理中,一个GPU同时处理prefill请求和decode请求。但这产生了矛盾:

  1. 资源争抢:一个长prompt的prefill(比如4k token)可能占用GPU几百毫秒,期间所有decode请求都在等,导致它们的inter-token latency(ITL)飙升
  2. 配置冲突:prefill想要更多的计算资源(高GPU频率、大batch),decode想要更多的内存带宽(大KV Cache空间)
  3. SLO不同:prefill关心TTFT(首token延迟),decode关心ITL(逐token延迟),优化目标不同

分离架构(Disaggregated Serving):

复制代码
┌──────────────────┐     KV Cache传输     ┌──────────────────┐
│  Prefill GPU池    │  ──────────────→   │  Decode GPU池     │
│  高算力、大batch   │                     │  大显存、高带宽    │
│  处理所有prefill   │                     │  处理所有decode    │
└──────────────────┘                     └──────────────────┘
         ↑                                        ↑
         └────────── 请求路由 (Scheduler) ──────────┘

工作流程:

  1. 新请求到达,scheduler发给prefill池
  2. Prefill GPU处理完prompt,生成KV Cache
  3. KV Cache通过高速网络(NVLink/InfiniBand)传输给decode GPU
  4. Decode GPU接续生成,直到完成

好处:

  • Prefill GPU可以一直处理大batch的prefill,compute利用率拉满
  • Decode GPU不被prefill打断,ITL稳定
  • 可以独立扩缩容------prefill请求突增时加prefill GPU就行
  • 甚至可以用不同型号的GPU(prefill用算力强的如H100,decode用带宽好的)

挑战:

  • KV Cache传输的网络开销(几百MB到几GB,需要高速互联)
  • 调度复杂度增加
  • 显存利用率可能因为传输中的KV Cache buffer而降低

面试加分: "Google的Sarathi-Serve和Splitwise论文都验证了这个思路。但完全分离不是唯一方案------chunked prefill是一种更轻量的替代:把长prompt的prefill切成多个chunk,每个chunk和decode请求交替执行,这样prefill不会长时间霸占GPU。vLLM和SGLang都已经支持chunked prefill。"


21. ⭐⭐⭐ [面经] Chunked Prefill(分块预填充)是什么?它如何缓解长prompt的prefill对decode请求的干扰?

一句话秒答: 把一个长prompt的prefill计算拆成多个小chunk(比如每chunk 512个token),每处理完一个chunk就穿插一轮decode iteration,这样长prefill不会长时间独占GPU,decode请求的延迟保持稳定。

展开来说:

问题回顾:

假设有一个4096 token的prompt要prefill,在A100上大概需要100ms。如果这100ms内GPU全在做prefill,那batch里所有decode请求都要等100ms才能生成下一个token。用户体感就是:字突然卡了100ms然后又开始吐了。

Chunked Prefill的做法:

复制代码
不分块:
|←--------------- prefill 4096 tokens ------------------→|  decode  |  decode  |
                                       ↑ 这些decode被block了100ms

分块(chunk_size=512):
|prefill 512| decode |prefill 512| decode |prefill 512| decode |...
     ↑每个chunk只占~12ms,decode请求每12ms就能执行一次

具体实现:

  1. 把4096 token的prompt切成8个512-token的chunk
  2. 每个iteration:处理一个prefill chunk + 所有decode请求的一步生成
  3. prefill chunk和decode请求在同一个batch里一起做forward
  4. 8个iteration后prefill完成,KV Cache也攒齐了

关键设计:

chunk大小的选择是个trade-off:

  • chunk太大:单次iteration耗时长,decode延迟还是会抖
  • chunk太小:prefill的总时间变长(因为每个chunk的overhead增加,且不能充分利用GPU算力)
  • 通常512-2048是个sweet spot

一个巧妙的优化是让prefill chunk和decode请求共享一个iteration的计算

  • 把prefill chunk的token和decode token拼成一个batch
  • 一次forward同时处理两者
  • 这样GPU既做了有用的prefill计算,又没耽误decode

效果:

  • TTFT(首token延迟)略有增加(因为prefill被拆成了多步)
  • ITL(逐token延迟)大幅稳定,P99延迟降低数倍
  • 整体吞吐基本不变或略有提升

面试加分: "Chunked prefill其实是prefill-decode分离的一种'软分离'方案------不需要物理上把GPU分成两组,只需要在调度层面做时间片轮转。对于大多数在线服务场景,chunked prefill就够了,不需要搞物理分离那么复杂的架构。vLLM从0.4版本开始默认启用chunked prefill。"


五、模型压缩(蒸馏与剪枝)(3题)

22. ⭐ [高频] 什么是知识蒸馏(Knowledge Distillation)?教师-学生框架如何工作?

一句话秒答: 知识蒸馏就是让一个小模型(学生)去学大模型(教师)的输出分布,而不是直接学标签。教师的soft label包含了类别之间的相似关系等"暗知识",比硬标签更有信息量。

展开来说:

Hinton老爷子2015年的经典工作,思路非常自然:

大模型之所以强,不仅是因为它知道正确答案是啥,还因为它知道各个候选之间的"微妙关系"。比如一个手写数字识别模型,对于一张"7"的图片,它可能输出:

  • 7: 0.85, 1: 0.10, 9: 0.03, 2: 0.02...

这个概率分布里藏着信息:"7"和"1"长得比较像,和"9"也有点关系。这些信息在硬标签 [0,0,0,0,0,0,0,1,0,0] 里是看不到的。

蒸馏框架:

复制代码
教师模型 (大/强)
    ↓ 生成soft targets(用temperature T软化后的概率分布)
学生模型 (小/快)
    ↓ 两个loss之和
Loss = α × KL(student_soft, teacher_soft)  +  (1-α) × CE(student, hard_labels)
         ↑ 蒸馏loss(学教师的分布)           ↑ 任务loss(学正确答案)

关键超参数:

  • Temperature T:越大分布越平滑,暗知识越多但信号越弱。通常T=2~20
  • α:蒸馏loss和任务loss的权重比。通常蒸馏loss权重更大

在LLM时代的蒸馏:

LLM蒸馏和传统蒸馏有些不同:

  1. 输出蒸馏(最常用):直接用教师模型生成高质量数据,学生模型在这些数据上做SFT

    • 其实就是"用GPT-4的输出去训小模型"
    • Alpaca、Vicuna等早期开源模型都是这么来的
    • 严格来说这是"数据蒸馏"而不是传统的"分布蒸馏"
  2. logit蒸馏:学生学教师在每个token位置的完整概率分布

    • 信息量更大,效果更好
    • 但需要教师模型的logit输出,API模型不提供这个
  3. 特征蒸馏:学生学教师的中间层表示

    • 需要结构匹配(比如hidden dim对齐),工程复杂

面试加分: "现在LLM领域最常用的其实是第一种------用大模型生成数据训小模型。像DeepSeek-R1就是用强化学习训出来的大模型再蒸馏到小模型上。这种方式简单有效,但有个争议:是否属于'模型坍缩(model collapse)'------如果所有模型都从GPT-4蒸馏,最终多样性会不会丧失?"


23. ⭐⭐ [高频] 什么是模型剪枝(Pruning)?结构化剪枝和非结构化剪枝有什么区别?

一句话秒答: 剪枝就是把模型里不重要的参数去掉(置零或删除)。非结构化剪枝精细到单个权重(稀疏但硬件不友好),结构化剪枝整行整列甚至整个注意力头地删(粗粒度但硬件友好,能真正加速)。

展开来说:

非结构化剪枝(Unstructured Pruning):

复制代码
原始权重矩阵:          剪枝后(50%稀疏度):
[0.5  0.1  0.8  0.3]   [0.5  0    0.8  0   ]
[0.2  0.7  0.1  0.6]   [0    0.7  0    0.6 ]
[0.9  0.3  0.4  0.2]   [0.9  0    0.4  0   ]
  • 按照某种重要性指标(绝对值大小、梯度、Hessian信息等)把不重要的单个权重置零
  • 可以达到很高的稀疏度(90%+)而精度损失很小
  • 问题:稀疏是不规则的,标准GPU硬件无法加速。你虽然去掉了90%的参数,但矩阵乘法还是那么大,因为零值散布在各处
  • 需要专用的稀疏计算硬件(如NVIDIA Ampere的2:4结构化稀疏支持)

结构化剪枝(Structured Pruning):

复制代码
原始:4个注意力头 → 剪枝后:3个注意力头(删掉整个head)
原始:FFN hidden_dim=4096 → 剪枝后:3072(删掉1024个神经元)
原始:12层transformer → 剪枝后:10层(删掉2层)
  • 按整行、整列、整个head、整层的粒度删除
  • 删完之后模型结构变小了,标准硬件直接加速
  • 问题:粒度太粗,一次删掉这么多参数,精度损失大
  • 通常需要删完之后做一轮fine-tuning来恢复精度

在LLM中的实践:

方法 类型 代表作 特点
SparseGPT 非结构化 2023 OBS思想,50%稀疏度精度损失小
Wanda 非结构化 2023 极简------用权重绝对值×激活范数作为重要性
LLM-Pruner 结构化 2023 删除整个attention head和FFN神经元
SliceGPT 结构化 2024 通过正交变换减少embedding维度
层删除 结构化 多种 直接删掉模型的某些层

NVIDIA的2:4稀疏是个有趣的中间方案:每4个连续权重里必须有2个是零。这是一种规则稀疏模式,Ampere架构的Tensor Core原生支持,能获得约2倍加速。

面试加分: "目前LLM剪枝在工业界的应用不如量化广泛,主要因为:第一,非结构化剪枝在通用GPU上没有加速效果;第二,结构化剪枝的精度损失通常比INT4量化更大。但2:4结构化稀疏+INT8量化的组合(稀疏量化)有潜力,NVIDIA的TensorRT-LLM已经支持了这种模式。"


24. ⭐⭐⭐ [面经] 量化、稀疏、蒸馏三种模型压缩技术各自的适用场景和组合方式?

一句话秒答: 量化降精度(4/8bit,最成熟最常用),稀疏去参数(置零,需要硬件支持),蒸馏换架构(大教小,最灵活)。三者正交可以组合,实践中"蒸馏得到小模型 → 量化部署"是最常见的路线。

展开来说:

先用一张表把三者拎清楚:

维度 量化 稀疏/剪枝 蒸馏
核心思想 降低数值精度 去掉不重要参数 大模型教小模型
压缩方式 每参数字节数减少 参数总量减少 模型架构变小
典型压缩比 2-8x(FP16→INT4) 2-10x(稀疏度50-90%) 任意(取决于学生大小)
精度影响 小(INT8几乎无损) 中等 取决于学生容量
硬件要求 标准GPU 需要稀疏硬件支持 标准硬件
工程复杂度 低(PTQ一键完成) 中(需要硬件匹配) 高(需要训练)
实际加速 显著(W4A16 2-4x) 依赖硬件(2:4可2x) 直接(模型小了)
工业采用度 极高 低到中

各自的适用场景:

量化 → 第一选择,几乎所有场景都适用:

  • 大模型部署:70B模型INT4量化后单机4卡就能跑
  • 边缘设备:手机上跑7B模型必须INT4/INT3
  • 成本优化:减少GPU数量 = 省钱

蒸馏 → 当你需要一个更小架构的模型时:

  • 从70B蒸馏到7B,然后7B再量化部署
  • 从通用模型蒸馏出垂直领域专用小模型
  • 对延迟有极致要求,需要真正的小模型

稀疏 → 目前比较前沿,落地较少:

  • 有专用硬件支持时(NVIDIA 2:4稀疏)
  • 和量化结合(稀疏+INT8,理论4倍压缩+2倍加速)
  • 需要保持模型架构不变但减少计算量的场景

组合方式:

最实用的组合路线是:

复制代码
路线1(最常见):
大模型 → 蒸馏 → 小模型 → PTQ量化(GPTQ/AWQ INT4) → 部署
例:DeepSeek-R1 671B → 蒸馏 → 7B → INT4 → 单卡部署

路线2(性能导向):
大模型 → PTQ量化(FP8/INT8) → 高性能部署
例:LLaMA-70B → FP8量化 → 2×H100部署

路线3(极致压缩,前沿):
大模型 → 2:4稀疏 → INT8量化 → 专用硬件部署
理论上获得稀疏2x + 量化2x = 4x加速

面试加分: "实际工程中我的决策逻辑是这样的:先确定目标硬件和延迟SLO,反推需要多大的模型。如果现有模型太大就先蒸馏/选个小模型,然后用量化来进一步压缩和加速。稀疏在通用GPU上收益有限,目前我只会在有NVIDIA Ampere以上的卡且要做INT8部署时才考虑2:4稀疏。三者的投入产出比排序是:量化 >> 蒸馏 > 稀疏。"


六、部署工程实践与综合设计(4题)

25. ⭐⭐ [高频] 张量并行(Tensor Parallelism)和流水线并行(Pipeline Parallelism)在推理中各自的原理和适用场景?

一句话秒答: 张量并行是把一层的矩阵乘法切开分到多卡上并行算(层内并行),流水线并行是把不同的层分到不同卡上串行算(层间并行)。推理场景下张量并行用得最多,因为它能直接降低单次forward的延迟。

展开来说:

张量并行(Tensor Parallelism, TP):

以一个FFN层 Y = XW 为例,W的shape是 (d, 4d):

复制代码
TP=2时,把W沿列切成两半:
GPU 0: Y_0 = X × W[:, :2d]    ← 只算一半
GPU 1: Y_1 = X × W[:, 2d:]    ← 算另一半
然后 Y = [Y_0, Y_1]           ← AllGather合并结果

对于Attention层,更自然------每个GPU负责一部分attention head。

特点:

  • 每次forward需要2次AllReduce通信(attention后一次,FFN后一次)
  • 通信量 = O(batch × seq_len × hidden_dim)
  • 需要高带宽互联(NVLink)------ PCIe带宽不够,跨卡通信成瓶颈
  • 适用:同一台机器内的多卡(NVLink连接),对延迟敏感的场景

流水线并行(Pipeline Parallelism, PP):

复制代码
PP=2时:
GPU 0: Layer 0-15(前半层)
GPU 1: Layer 16-31(后半层)

数据流:GPU 0处理完 → 中间激活传给GPU 1 → GPU 1继续处理

特点:

  • 通信只发生在层的边界(传递中间激活),通信量较小
  • 存在"气泡"问题------GPU 0在等GPU 1处理时是空闲的
  • 训练时用micro-batch来填充气泡,但推理时batch小气泡严重
  • 适用:跨机器(通信带宽受限),模型特别大需要很多卡的场景

推理场景的选择:

场景 推荐方案 原因
单机2-8卡 纯TP NVLink带宽够,延迟最低
跨机器 TP + PP组合 机内TP(NVLink),机间PP(InfiniBand)
70B on 2×A100 TP=2 70B FP16需要140GB,2卡TP正好
70B on 8×A100 TP=8 更低延迟(每卡只算1/8层宽度)

典型配置:

  • 2卡:TP=2
  • 4卡:TP=4
  • 8卡同机:TP=8
  • 16卡跨机:TP=8 × PP=2

面试加分: "还有一种推理专用的并行策略叫Sequence Parallelism(序列并行),把长序列切分到多卡上处理,主要解决长上下文(100k+)场景下单卡显存放不下KV Cache的问题。Ring Attention就是这个思路的实现。另外vLLM等框架已经自动处理了TP的通信,用户只需要指定 tensor_parallel_size=N 就行了。"


26. ⭐ [高频] 大模型生成时的参数(temperature、top-k、top-p、repetition_penalty)怎么设置?各参数的作用是什么?

一句话秒答: temperature控制随机性(越高越发散),top-k只保留概率最高的k个token,top-p(nucleus sampling)只保留累计概率达到p的token集合,repetition_penalty惩罚已出现的token防止复读机。

展开来说:

这些参数本质上都是在修改模型输出的概率分布,控制"模型有多大胆"。

Temperature(温度):

python 复制代码
# 原始logits经过softmax前,先除以temperature
probs = softmax(logits / temperature)
  • T=0(或极小值):贪心解码,总是选概率最高的token → 确定性输出
  • T=0.1~0.5:较保守,输出稳定,适合事实性问答、代码生成
  • T=0.7~1.0:正常随机性,适合一般对话
  • T=1.5+:很发散,适合创意写作(但容易胡说八道)

直觉:温度越高,概率分布越平(各token概率更接近),采样越随机。

Top-k:

python 复制代码
# 只保留概率最高的k个token,其余概率置零,重新归一化
top_k_tokens = sorted(probs)[-k:]
  • k=1:等价于贪心解码
  • k=10-50:常用范围
  • 问题:固定k不够灵活------有时候分布很集中(只有2-3个合理选项),k=50就太多了

Top-p(Nucleus Sampling):

python 复制代码
# 按概率从大到小排列,累加到p为止
sorted_probs = sort(probs, descending=True)
cumsum = cumulative_sum(sorted_probs)
cutoff_index = first_index_where(cumsum >= p)
# 只保留这些token
  • p=0.9:保留累计概率90%的最小token集合
  • 比top-k更灵活------当分布集中时自动选少量token,分布平坦时自动选多个
  • 业界最常用的采样策略

Repetition Penalty:

python 复制代码
# 对已出现的token的logit做惩罚
for token in generated_tokens:
    if logits[token] > 0:
        logits[token] /= penalty  # 降低正logit
    else:
        logits[token] *= penalty  # 负logit更负
  • penalty=1.0:无惩罚
  • penalty=1.1~1.3:轻度防重复
  • 太大会导致模型刻意回避用词,输出变得不自然

实用配置推荐:

场景 temperature top_p top_k rep_penalty
代码生成 0~0.2 0.95 - 1.0
事实问答 0~0.3 0.9 - 1.0
日常对话 0.7 0.9 - 1.1
创意写作 0.9~1.2 0.95 - 1.2

面试加分: "工程上一个容易踩的坑是temperature和top_p/top_k的作用顺序------先除temperature再做top_p截断。如果你先做top_p再除temperature,效果完全不同。另外,对于需要确定性输出的场景(比如评测、回归测试),一定要设temperature=0并且设固定的random seed,否则结果不可复现。"


27. ⭐⭐⭐ [字节/高频] 请完整描述一个LLM推理服务从接收请求到返回结果的全链路流程。

一句话秒答: 请求进来 → 限流/鉴权 → tokenize → 加入调度队列 → 调度器分配batch slot → prefill生成KV Cache → 连续decode直到EOS → detokenize → 流式返回 → 释放KV Cache显存。

展开来说:

这题面试官想看你对整个系统的全局理解,从网络层一直到GPU计算。我把它分成几个层次来讲。

Layer 1:接入层(API Gateway)

复制代码
客户端 → HTTPS请求 → 负载均衡器(Nginx/ALB)→ API Gateway
  • 鉴权(API Key验证)
  • 限流(Rate Limiting,防止单用户打爆服务)
  • 请求校验(参数合法性检查)
  • 路由(根据模型名路由到对应的推理集群)

Layer 2:请求预处理

复制代码
API Server → Tokenizer → 请求队列
  • 收到原始文本,用模型对应的tokenizer编码成token IDs
  • 拼接system prompt + user message(如果是chat格式)
  • 检查token长度是否超过模型的max_seq_len
  • 生成一个请求对象(包含token IDs、采样参数、请求ID等)
  • 放入调度器的等待队列

Layer 3:调度器(Scheduler)------核心

复制代码
调度器每个iteration做一次决策:
1. 从等待队列里取新请求
2. 检查GPU显存是否够分配KV Cache
3. 如果够 → 加入running batch(做prefill)
4. 如果不够 → 选择性地preempt一些低优先级请求(swap KV Cache到CPU)
5. 对running batch里的所有请求执行一步forward

调度策略:

  • FCFS(先来先服务)最简单
  • Shortest-Job-First对平均延迟更好
  • 优先级队列支持VIP用户

Layer 4:GPU计算

复制代码
Prefill(新请求):
  Token IDs → Embedding → N层Transformer(并行处理所有prompt token)→ 
  产出KV Cache + 第一个output token

Decode(已有请求):
  上一个token → Embedding → N层Transformer(attend to KV Cache)→ 
  新token的logits → 采样策略(temperature+top_p)→ 下一个token
  → 更新KV Cache

显存管理(PagedAttention):

  • 每个请求的KV Cache以block为单位动态分配
  • block table维护逻辑到物理的映射
  • 请求完成后block立即回收

Layer 5:输出处理

复制代码
每生成一个token → detokenize → 流式返回(SSE/WebSocket)
  • 支持Streaming(Server-Sent Events),每个token生成后立即推给客户端
  • 累积finish_reason(EOS token / max_length / stop sequence)
  • 统计usage信息(prompt_tokens, completion_tokens)

Layer 6:终止与清理

复制代码
触发终止条件 → 标记请求完成 → 释放KV Cache blocks → 返回最终响应 → 计费

终止条件:

  • 生成了EOS token
  • 达到max_tokens限制
  • 命中stop sequence
  • 客户端断开连接(early termination)

完整时间线(以7B模型,1000 token prompt,生成200 token为例):

复制代码
t=0ms     请求到达Gateway
t=1ms     鉴权+限流通过
t=2ms     Tokenize完成
t=5ms     调度器分配slot
t=5-25ms  Prefill(1000 token)← TTFT ≈ 25ms
t=25-825ms Decode 200 tokens(每token ~4ms)← 流式输出中
t=825ms   EOS,返回完整响应,释放显存

面试加分: "实际生产中还要考虑几个工程细节:一是模型多副本的负载均衡(按GPU利用率或队列长度路由);二是请求取消(客户端关闭页面时要及时释放GPU资源);三是超时处理(单个请求不能无限占用GPU);四是可观测性(Prometheus监控TTFT/ITL/吞吐/队列深度等指标)。这些看起来不起眼,但在高并发场景下每个都能出事故。"


28. ⭐⭐⭐ [阿里/高频] 假设你要部署一个70B参数的模型,请设计完整的推理部署方案(包括量化、并行策略、框架选择、硬件配置)。

一句话秒答: 70B FP16需要~140GB显存,基础方案是2×A100-80G用TP=2跑FP16;性价比方案是AWQ INT4量化后单机2×A100甚至单张A100-80G(35GB);极致性能方案是4×H100用FP8+TensorRT-LLM。

展开来说:

这种系统设计题,面试官想看你的全局思维和工程判断力。我按"先确定约束 → 再选方案"的思路来讲。

Step 1:明确需求和约束

先反问面试官(或假设)几个关键问题:

  • 目标QPS是多少?(10 QPS vs 1000 QPS差别巨大)
  • 延迟SLO?(TTFT < 1s? ITL < 50ms?)
  • 平均请求长度?(短对话 vs 长文档处理)
  • 预算约束?(烧得起H100还是只有A100/4090)
  • 精度要求?(能接受INT4还是必须FP16)

我假设一个典型的在线服务场景:

  • 目标:50 QPS,TTFT < 2s,ITL < 100ms
  • 输入平均500 token,输出平均200 token
  • 预算适中,优先性价比

Step 2:硬件选型

复制代码
70B模型显存需求:
- FP16: 70B × 2 bytes = 140 GB(至少2×A100-80G)
- INT8: 70B × 1 byte = 70 GB(1×A100-80G紧凑可行)
- INT4: 70B × 0.5 byte = 35 GB(1×A100-80G宽裕)
  + KV Cache: 假设batch=32, seq_len=2048
  = 2 × 80层 × 8192 hidden × 2048 × 32 × 2bytes ≈ 几十GB

综合考虑KV Cache开销,我选:2×A100-80G(160GB总显存)

Step 3:量化策略

复制代码
方案A(保守):FP16,TP=2,2×A100-80G
- 显存:140GB模型 + KV Cache → 刚好够
- 精度:无损
- 缺点:KV Cache空间紧张,batch size受限

方案B(推荐):AWQ INT4 (W4A16),TP=2,2×A100-80G
- 显存:35GB模型 + 大量KV Cache空间
- 精度:PPL损失1-2%,实际体验几乎无差别
- 优点:大batch size,高吞吐
- 每卡只需存约17.5GB模型参数,剩余62.5GB给KV Cache

方案C(极致性价比):AWQ INT4,单卡A100-80G
- 显存:35GB模型 + 45GB给KV Cache
- 能跑但batch size受限,适合低QPS场景

我选方案B。

Step 4:推理框架选择

复制代码
vLLM:
- 优点:开箱即用、社区活跃、AWQ支持好(Marlin kernel)
- TP=2一行配置:--tensor-parallel-size 2
- 自带continuous batching、PagedAttention、prefix caching
- 推荐指数:⭐⭐⭐⭐⭐

TensorRT-LLM:
- 优点:性能极致(可能比vLLM快20-30%)
- 缺点:工程复杂度高、模型编译耗时、debug困难
- 推荐指数:⭐⭐⭐(如果团队有经验的话⭐⭐⭐⭐)

我选vLLM(快速上线)+ 后续考虑迁移到TRT-LLM(榨取性能)。

Step 5:完整部署架构

复制代码
                    ┌──────────────┐
                    │  Nginx LB    │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              ↓            ↓            ↓
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ vLLM实例1 │ │ vLLM实例2 │ │ vLLM实例3 │  ← 多副本
        │ 2×A100   │ │ 2×A100   │ │ 2×A100   │     水平扩展
        │ TP=2     │ │ TP=2     │ │ TP=2     │
        └──────────┘ └──────────┘ └──────────┘

vLLM启动命令:

bash 复制代码
python -m vllm.entrypoints.openai.api_server \
    --model /models/llama-70b-awq \
    --quantization awq \
    --tensor-parallel-size 2 \
    --max-model-len 4096 \
    --gpu-memory-utilization 0.9 \
    --enable-prefix-caching \
    --max-num-seqs 256

Step 6:监控与运维

关键监控指标:

  • TTFT(首token延迟):P50 < 500ms,P99 < 2s
  • ITL(逐token延迟):P50 < 30ms,P99 < 100ms
  • 吞吐:requests/s 和 tokens/s
  • GPU利用率:目标80%+
  • 队列深度:实时监控,超阈值告警
  • 显存使用:KV Cache使用率

自动扩缩容策略:

  • 队列深度 > 100 或 P99延迟 > SLO → 扩容
  • GPU利用率 < 30% 持续10分钟 → 缩容

面试加分: "如果预算升级到H100/H200,我会调整为:用FP8量化(70GB显存,比INT4精度更好)+ TensorRT-LLM + TP=2或4。H100的FP8 Tensor Core能提供接近FP16两倍的吞吐,是目前70B模型的最优部署硬件。另外如果是Mixture-of-Experts模型(比如Mixtral),部署策略又不一样------Expert并行需要额外考虑。"


全文总结 :推理部署这块知识体系可以用"一条主线+四个支柱"来概括。主线是从请求到响应的全链路数据流。四个支柱是:量化 (减少每个参数的字节数)、并行 (把大模型拆到多卡上)、调度 (连续批处理、Prefill-Decode分离让GPU不空转)、算法(FlashAttention减少IO、投机解码减少串行步数)。面试时把这个框架一摆,面试官就知道你是有体系化思考的。

相关推荐
八月的冰可乐2 小时前
【无标题】
ai·面试
莫寒清5 小时前
Java 线程池详解
java·面试
小李独爱秋8 小时前
模拟面试:说一下数据库主从不同步的原因。
运维·服务器·mysql·面试·职场和发展·性能优化
逆境不可逃8 小时前
LeetCode 热题 100 之 41.缺失的第一个正数
算法·leetcode·职场和发展
茶杯梦轩9 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
服务器·后端·面试
莫寒清11 小时前
Java 中 == 与 equals() 的区别
java·面试
天真小巫11 小时前
2026.2.21总结
职场和发展
学历真的很重要11 小时前
【系统架构师】第三章 数据库系统知识 - 数据库基础到关系代数(详细版)
数据库·学习·职场和发展·系统架构·系统架构师
NEXT0612 小时前
BFC布局
前端·css·面试