蚂蚁 AI Infra 实习 一面 (1)
Q: 如何判断一个算子要不要优化?优化的具体思路?
判断是否值得优化——投入产出比分析:
- 是否为耗时热点:通过 Nsight Systems profiling 确认。经验法则:占总推理时间 > 5-10% 的 kernel 值得投入优化
- 是否为高频算子:如果该算子在整网中出现 N 次(如每层一个 LayerNorm = 80 次),单点优化 10% = 整网优化 N×10%
- 是否有优化空间:用 NCU 的 SOL(Speed of Light)判断当前实现离硬件上限有多远。如果已达 80%+ SOL 则收益有限
优化的系统化思路:
1 | Step 1: Profiling 确认瓶颈类型 |
Q: 如何推进一个还没量化的模型做量化?
完整的量化落地流程:
Phase 1:分析与规划
- 确定模型大小和目标硬件(决定可用方案范围)
- 确定性能目标:需要多少吞吐?延迟 SLA?
- 确定精度底线:PPL 增加不超过多少?关键 benchmark 下降不超过多少?
Phase 2:方案选择
| 目标 | 推荐方案 | 理由 |
|---|---|---|
| 极致速度,精度容忍 | W4A16 (AWQ/GPTQ) | 权重减 4x,decode 加速 2-3x |
| 均衡速度+精度 | W8A8 (SmoothQuant) | INT8 Tensor Core 加速,精度损失小 |
| Hopper 硬件 | FP8 (Transformer Engine) | 原生支持,自动管理 scale |
| 极致压缩(端侧) | W4A8 或 W2 (QuIP#) | 模型极小但精度损失较大 |
Phase 3:执行量化
- 准备校准数据:选取有代表性的 128-512 条数据
- 运行量化工具:
AutoGPTQ、AutoAWQ、llm-compressor - 生成量化模型文件
Phase 4:评估与调优
- 精度评估:PPL + 下游 benchmark(MMLU、GSM8K、HumanEval)
- 性能测试:实际吞吐(tokens/s)和延迟(TTFT/TPOT)
- 如果精度不达标:尝试增大 group_size、混合精度(敏感层保持高精度)、换更好的量化方法
Q: AWQ 量化后为什么整体还能加速(明明多了反量化步骤)?
这是一个核心认知——LLM Decode 阶段是 memory-bound,不是 compute-bound:
原因分析:
Decode 阶段每步只处理 1 个 token(或少量 token),实际执行的是 GEMV(矩阵向量乘):
1 | 输出 [1, hidden] = 输入 [1, hidden] × 权重 [hidden, hidden] |
算术强度 AI = 2×hidden / (hidden×hidden×bytes + hidden×bytes) ≈ 2/hidden ≈ 0.0005
远远小于硬件拐点(~156 for A100),完全是 memory-bound!
关键推理:
- 性能瓶颈 = 权重读取带宽:每步需要从 HBM 读取全部模型权重
- INT4 权重减少 4x 数据量:FP16 每个权重 2 bytes → INT4 每个权重 0.5 bytes
- 读取时间减少 4x → 理论加速 4x
- 反量化开销极小:dequant(INT4→FP16)是在寄存器内做的简单乘加运算(compute),而算子本身是 memory-bound,compute 资源是闲置的
打个比方:你在等货车(内存带宽)送货,货物减少了 4x 所以等待时间减少 4x。虽然收到货后需要额外 1 秒拆包(dequant),但这 1 秒的”计算”完全被等待带宽的时间覆盖。
融合实现:AWQ/GPTQ 的推理 kernel 将 dequant 融合在 GEMM 内部——读入 INT4 数据后立即在寄存器做 dequant 再计算,不产生额外的 HBM 访问。
Q: 为什么不直接用 Nsight/NCU 而要自己写 timing 工具?
NCU 的局限:
- 开销巨大:NCU replay 模式下 kernel 执行速度慢 10-100x,一次完整 profiling 可能需要数十分钟
- 粒度太细:分析单个 kernel 的每条指令利用率,信息量过大
- 干扰执行:改变了真实的调度行为(replay + barrier),结果可能与实际运行有偏差
- 不适合日常开发:每次代码修改后做完整 NCU profiling 太重
自写 timing 工具的优势:
- 开销极低:CUDA event 计时开销 < 1μs,不影响正常执行
- 嵌入开发流程:每次构建都可以运行,秒级反馈
- 自定义维度:按算子类型汇总、按层汇总、按请求汇总
- 粗粒度定位:快速找到耗时 Top-5 的算子类别
- 适合 A/B 对比:快速比较优化前后的性能变化
最佳实践——两者结合:
- 日常开发用自写 timing 工具快速定位热点(”哪类算子最慢?”)
- 对热点算子用 NCU 深入分析(”它为什么慢?瓶颈在哪?”)
- 优化后再用 timing 工具确认整网收益
Q: 静态图和动态图的区别?为什么要转静态图做优化?
动态图(Define-by-Run)——PyTorch 的 eager mode:
- 每次 forward 即时执行每个操作,不构建全局图
- 优点:Python 原生控制流、调试方便(pdb 可断点)、灵活
- 缺点:无法看到全局结构,不能做跨 kernel 优化
静态图(Define-and-Run)——TorchScript/TensorRT/XLA:
- 先定义/捕获完整计算图,编译优化后再执行
- 优点:全局优化、高效执行、可部署
- 缺点:不灵活、动态控制流需要特殊处理
为什么推理优化需要静态图:
| 优化类型 | 为什么需要全局图 |
|---|---|
| 算子融合 | 需要知道两个 kernel 是生产者-消费者关系 |
| 内存规划 | 需要知道所有 tensor 的生命周期才能复用 |
| Layout 优化 | 需要全局分析最优的数据布局 |
| 常量折叠 | 需要识别不依赖输入的子图 |
| 死代码消除 | 需要全局数据流分析 |
| Kernel 调度 | 需要知道执行顺序才能优化 stream 分配 |
PyTorch 的方案:
torch.compile(Inductor):JIT 捕获动态图为静态图 → TorchInductor 编译优化 → 生成高效 Triton/CUDA kernel- 实际加速通常 1.5-3x(主要来自融合和 Triton kernel)
Q: vLLM 的核心机制?
vLLM 的核心设计目标:最大化单位显存下的并发请求数和吞吐量。
六大核心机制:
- PagedAttention:KV Cache 分页管理(16 tokens/block),消除碎片,利用率 ~100%
- Continuous Batching:每个 iteration 动态调整 batch(加入完成的请求 slot 给新请求),吞吐 2-10x
- Prefix Caching:相同前缀自动共享 KV Cache block(如 system prompt),避免重复 Prefill
- Scheduler + Preemption:
- 显存不足时按 FCFS 优先级抢占
- Swap mode:KV Cache 暂存 CPU,恢复时加载回
- Recompute mode:释放 KV Cache,恢复时重新 Prefill
- KV Cache 量化:支持 FP8 KV Cache,显存减半
- Speculative Decoding:集成投机解码,大小模型协同加速
Q: 手撕:复制带随机指针的链表?
(编程题)