快手 AI Infra 实习 一面 (1)
Q: 介绍模型量化和 FlashAttention,实际优化效果如何?
模型量化:
将模型权重/激活从高精度(FP32/FP16)压缩到低精度(INT8/INT4/FP8),核心收益来自两方面:
- 减少显存占用:8B 模型 FP16 需 16GB,INT4 只需 4GB
- 减少内存带宽消耗:LLM Decode 阶段是 memory-bound(每个 token 需读取全部权重),带宽减少直接转化为加速
常见方案和效果:
| 方案 | 类型 | 加速比 | 精度损失 |
|---|---|---|---|
| W8A8 (SmoothQuant) | PTQ | 1.5-2x | PPL +0.1-0.3 |
| W4A16 (GPTQ/AWQ) | PTQ | 2-3x | PPL +0.3-1.0 |
| FP8 (E4M3) | PTQ | ~2x (Hopper) | PPL +0.05-0.1 |
| QAT | 训练时 | 类似 PTQ | 损失更小 |
为什么量化能加速 LLM:Decode 阶段每 token 只做 batch=1 的 GEMV,算术强度极低(< 1 FLOP/Byte),完全 memory-bound。INT4 权重减少 4x 数据量 = 理论 4x 加速(实际 2-3x,因为有 dequant 开销)。
FlashAttention:
通过 tiling 将 Attention 计算分块在 GPU SRAM(~20MB)中完成,核心创新是 Online Softmax 实现精确分块计算。
实际效果:
- 训练端:前向+反向加速 1.5-3x(seq_len 越长加速越明显)
- 推理端:Prefill 阶段加速 2-4x(大 batch/长序列场景)
- 显存节省:O(N²) → O(N),seq_len=4096 时单层 attention 节省约 32MB/head
- Decode 阶段:加速不明显(已经是 seq=1 的 GEMV,不存在 N×N 矩阵)
FlashAttention 的局限:
- 对短序列加速不大(overhead 抵消收益)
- 需要 head_dim 为特定值(64/128/256)
- 不支持所有 attention mask 模式(需要特殊适配)
Q: 介绍 TensorRT,底层如何加速?
TensorRT 是 NVIDIA 的推理优化引擎,将训练好的模型转换为高效的 inference engine。底层加速机制:
1. 图优化(Graph Optimization):
- 层融合:将 Conv+BN+ReLU 融合为一个 kernel(减少 3 次 kernel launch + 2 次中间 tensor 读写)
- 常量折叠:编译期计算固定输入的子图
- Dead Code Elimination:移除无用计算分支
- Transpose/Reshape 消除:通过调整 kernel 内部 layout 消除显式 transpose
2. 量化(Quantization):
- 支持 INT8/FP8 量化,利用校准数据(calibration dataset)确定每层的 scale
- 校准策略:MinMax、Entropy(KL 散度最小化)、Percentile
- 量化感知:自动识别对量化敏感的层保持高精度
3. Kernel Auto-Tuning:
- 对每个层/融合后的子图,枚举所有可能的 kernel 实现并实际运行 benchmark
- 针对具体硬件(SM 数量、cache 大小)和输入形状选择最优 kernel
- 这就是为什么 TensorRT build engine 很慢(在做穷举搜索)
4. 内存优化:
- Tensor 复用:生命周期不重叠的 tensor 共享同一块显存
- 显存池化:减少 cudaMalloc/cudaFree 的调用
- Workspace 管理:为临时计算分配高效的工作区
5. 精度混合(Mixed Precision):
- 不同层根据敏感度使用不同精度
- 对精度敏感的层保持 FP16/FP32,不敏感的层用 INT8
- 自动寻找精度-性能的最优 pareto 点
6. 动态 Shape 支持:
- 通过 Optimization Profile 定义 min/opt/max 输入尺寸
- 为最常见的 opt 尺寸做最深度优化
- 运行时根据实际输入在已优化的 kernel 中选择
Q: 介绍 vLLM 框架和 PagedAttention?
vLLM 是面向 LLM 推理的高吞吐引擎,核心设计目标是最大化单 GPU 上能同时服务的请求数:
核心特性:
- PagedAttention:KV Cache 分页管理,将显存利用率从 20-40% 提升到接近 100%
- Continuous Batching:每个 forward iteration 动态加入/移除请求(比 static batching 吞吐高 2-10x)
- Prefix Caching:相同 system prompt 的请求自动共享 KV Cache
- Scheduler + Preemption:显存不足时将低优先级请求的 KV Cache swap 到 CPU
PagedAttention 详解:
传统 KV Cache 管理为每个请求预分配 max_seq_len 的连续显存——导致 60-80% 的显存浪费(内部碎片 + 外部碎片)。
PagedAttention 的设计:
1 | 物理显存被划分为固定大小 Block(如 16 tokens 的 KV) |
PagedAttention 的关键优势:
- 无碎片:按需分配,用多少占多少。不需要预留 max_seq_len 的连续空间
- 按需增长:序列每生成 16 个 token 才分配一个新 block
- Copy-on-Write 共享:Beam search 中多个 beam 共享公共前缀的 block,只在产生分歧时复制
- 利用率:论文报告从 20-40% → 接近 100%,意味着同等显存可以服务 2-4x 更多的并发请求
Attention Kernel 适配:原始 attention kernel 假设 KV 是连续存储的;PagedAttention kernel 需要根据 block table 做间接寻址,获取分散的 KV block 做计算。性能开销约 3-5%,但整体吞吐因 batch 增大而大幅提升。
Q: Qwen 模型占多少内存?如何部署?
以 Qwen-7B(FP16)为例进行估算:
显存占用:
- 模型参数:7B × 2 bytes = 14 GB
- KV Cache(batch=1, seq=2048, 32层, 32 heads, 128 dim):2×32×32×128×2048×2 ≈ 1 GB
- 激活值 + 框架开销:~2-4 GB
- 总计约 18-20 GB
常见部署方式:
| 场景 | 方案 | 显存需求 | 适合硬件 |
|---|---|---|---|
| 低资源部署 | INT4 量化 (GPTQ/AWQ) | ~5 GB | RTX 3060/4060 |
| 标准推理 | FP16 + vLLM | ~20 GB | RTX 4090/A10 |
| 高吞吐生产 | FP8 + TensorRT-LLM | ~10 GB | H100/L40S |
| 超大模型(72B) | TP=4 + FP16 | ~40 GB/卡 | 4×A100 |
部署最佳实践:
- 单卡 7B:vLLM + AWQ INT4 量化,单张 RTX 4090 可跑,吞吐约 30-50 tokens/s
- 生产环境:vLLM/TensorRT-LLM + Continuous Batching,关注 QPS 和 P99 延迟
- 多卡大模型:Tensor Parallel 切分(如 72B 用 4-8 卡 TP)
Q: PyTorch 核心基础功能是什么?如何对 GPU 进行管理?
PyTorch 核心功能:
- 动态计算图(Define-by-Run):每次 forward 都重新构建图,支持 Python 原生控制流(if/for),调试直观
- 自动微分(Autograd):记录前向操作形成 DAG,反向时自动计算梯度。
tensor.backward()触发 - Tensor 运算:N-维数组 + 丰富的数学操作,API 类似 NumPy 但支持 GPU 加速
- nn.Module:模块化网络构建,支持嵌套、参数管理、序列化
- 多后端:CPU/CUDA/MPS/XPU,通过 dispatcher 统一路由
GPU 管理机制:
设备管理:
1 | torch.cuda.set_device(0) # 设置默认设备 |
显存管理(Caching Allocator):
- PyTorch 使用缓存分配器:首次 cudaMalloc 预分配大块显存(如 512MB),后续 tensor 分配从缓存中取 block
- 好处:避免频繁 cudaMalloc/cudaFree(每次 ~1ms),分配降至 ~1μs
torch.cuda.memory_allocated():当前被 tensor 占用的显存torch.cuda.memory_reserved():被 PyTorch 缓存占用的总显存(包含未使用的缓存 block)torch.cuda.empty_cache():释放未被 tensor 占用的缓存(不释放活跃 tensor 的显存)- OOM 时 PyTorch 会自动尝试 empty_cache 并重试分配
CUDA Stream 管理:
1 | stream = torch.cuda.Stream() |
Q: 模型训练和推理在资源消耗上有什么区别?训练有哪些性能优化手段?
资源消耗对比:
| 维度 | 训练 | 推理 |
|---|---|---|
| 显存 | 参数 + 梯度 + 优化器状态 + 激活值 ≈ 16-20x 参数量 | 参数 + KV Cache + 少量激活 ≈ 2-4x 参数量 |
| 计算量 | 前向 + 反向 ≈ 3x 前向 FLOPs | 仅前向,Decode 时每步只算 1 token |
| 通信 | 梯度 AllReduce(多卡必需) | TP 的 AllReduce(可选) |
| 带宽需求 | 相对均衡(大 batch GEMM 是 compute-bound) | Decode 强 memory-bound |
具体对比(8B 模型,FP16):
- 训练每卡显存:参数 16GB + 梯度 16GB + Adam 48GB + 激活
20GB = **100 GB**(需要 ZeRO 分片) - 推理每卡显存:参数 16GB + KV Cache 1-4GB + 开销 2GB = ~20 GB
训练性能优化手段:
- 混合精度训练(AMP):前向反向用 BF16/FP16,主权重用 FP32。计算量减半 + 显存减半 + 带宽减半
- 梯度累积:小 batch_size 多步累积梯度再更新,模拟大 batch 但不增加显存
- ZeRO 显存优化:分片优化器状态(ZeRO-1)→ 梯度(ZeRO-2)→ 参数(ZeRO-3),极大突破单卡显存限制
- 3D 并行:DP(数据)+ TP(张量)+ PP(流水线),大规模集群的标准方案
- 梯度 Checkpoint(激活重算):不保存中间激活,反向时重新前向计算。时间增加 ~33%,显存可节省 60-70%
- FlashAttention:Attention 显存从 O(N²) 降至 O(N),同时加速 1.5-3x
- 计算通信重叠:反向传播时边算梯度边 AllReduce,通信完全隐藏在计算背后
Q: GPU 基础的物理执行单元是什么?
GPU 的基本物理执行单元是 SM(Streaming Multiprocessor)。以 A100 为例(108 个 SM),每个 SM 是一个独立的计算核心,包含:
计算资源:
- CUDA Core(FP32/INT32):64 个/SM,执行标量浮点/整数运算
- Tensor Core(第三代):4 个/SM,执行 4×4×4 的矩阵乘加运算(FP16/BF16/TF32/INT8)
- FP64 Core:32 个/SM(HPC 用)
- SFU(Special Function Unit):16 个/SM,执行 sin/cos/exp/sqrt 等超越函数
存储资源:
- 寄存器文件:65536 个 32-bit 寄存器/SM(总 256KB/SM)
- Shared Memory/L1 Cache:192KB/SM(可配置 shared:L1 比例)
- L2 Cache:40MB(全 GPU 共享)
调度单元:
- Warp Scheduler:4 个/SM,每 cycle 各选一个就绪 warp 发射指令
- Dispatch Unit:将指令分发到相应执行单元
执行层次(从小到大):
1 | Thread(标量)→ Warp(32 线程,SIMT 执行)→ Thread Block(多个 Warp,共享 Shared Memory)→ Grid(所有 Block) |
关键特性:
- Warp 是最小调度单位(32 线程锁步执行同一指令)
- 一个 SM 可同时驻留多个 Block(受寄存器/shared memory 限制)
- 通过大量 Warp 切换隐藏内存延迟(零开销上下文切换)
Q: 手撕:将有序数组转化为平衡二叉搜索树?
(编程题)