@Kay2289123: 分享一份最近整理的AI基础设施入门阅读清单——建议收藏并慢慢阅读……

X AI KOLs Timeline 新闻

摘要

这篇文章分享了一份精选的AI基础设施入门阅读清单,重点关注大型模型推理主题,如vLLM、连续批处理和性能基准测试。

分享一份最近整理的AI基础设施入门阅读清单——建议收藏并慢慢阅读。 AI已经可以生成很多优质内容,使得解释概念、翻译论文和组织材料变得更加容易。但在当今时代,能够静下心来,彻底阅读一篇好文章,并思考不理解的部分——我认为这更加稀有和有价值。 这次我整理的材料主要关注大型模型推理。从KV Cache和内存管理,到请求调度和推理加速,再到如何判断性能是否真正提升。 对于从事工程的人员,可以深入源代码和实验进行跟进。对于通常关注AI行业、算力和存储的人员,可以先扎实掌握原理。 阅读文章时,选择那些值得反复阅读的。 1. 如果只能选一篇,我会选择Inside vLLM。 Inside vLLM: Anatomy of a High-Throughput LLM Inference System,作者Aleksa Gordić https://aleksagordic.com/blog/vllm 它将许多分散的概念编织成一个真实系统:请求到达时如何排队、KV Cache如何分配、如何执行,最终如何返回结果。 PagedAttention、连续批处理、分块预填充、前缀缓存、推测解码、P/D分离、多GPU和性能测试——都可以在这条主线中找到位置。 这篇文章很适合作为主线,以下材料可补充任何缺口。 2. 要真正理解连续批处理,从Anyscale的这篇开始。 Continuous Batching blog https://anyscale.com/blog/continuous-batching-llm-inference… LLM用户的响应长度不一——有些已完成生成,有些仍在进行。已空出的位置能否立即承担下一个请求? 这篇文章使用静态批处理与连续批处理的对比图表,非常直观地解释了问题。阅读后,你会明白GPU效率也取决于请求如何调度。 建议重点关注图表和机制。 3. NVIDIA的两篇文章:一篇构建宏观图景,一篇教你如何评估性能。 Mastering LLM Techniques: Inference Optimization https://developer.nvidia.com/blog/mastering-llm-techniques-inference-optimization… 非常适合建立基本理解:预填充(处理输入)和解码(逐步输出生成)之间的区别,模型权重和KV Cache分别占用的空间,以及量化、批处理和多GPU并行各自解决的问题。 LLM Inference Benchmarking: Fundamental Concepts https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/… 我强烈推荐这篇。用户等待第一个token的时间、后续生成的速度、系统每秒能服务多少请求——这些都是不同的问题。TTFT、ITL、TPOT等指标需要与测试方法结合来看。 4. 要更深入地了解数学原理,阅读kipply的Transformer Inference Arithmetic。 Transformer Inference Arithmetic 这是一篇2022年的文章。它引导你从计算量、内存容量、带宽和通信开销来估算为什么推理有快有慢,以及批大小变化时会发生什么。 硬件和模型示例已过时,因此可作为选读材料。
查看原文
查看缓存全文

缓存时间: 2026/09/12 02:44

分享一份最近整理的 AI Infra 入门阅读清单——建议收藏并慢慢阅读。AI 现在已经能生成大量优质内容,让概念阐释、论文翻译、资料整理变得更容易。但在这个时代,能够静下心来细读一篇好文章、琢磨不懂的部分——我认为这反而更加稀有和宝贵。这次整理的材料主要聚焦大模型推理。从 KV 缓存与内存管理,到请求调度与推理加速,再到如何判断性能是否真的提升。从事工程实践的同学可以顺藤摸瓜深入源码和实验;平时关注 AI 产业、算力和存储的同学,可以先打牢原理基础。阅读文章时,挑选那些值得反复精读的。

  1. 若只选一篇,我会选 Inside vLLM。Inside vLLM: Anatomy of a High-Throughput LLM Inference System, by Aleksa Gordić
    https://aleksagordic.com/blog/vllm
    它将许多分散概念串联成一个真实系统:请求如何进入、如何排队、KV 缓存如何分配、如何执行、最终如何返回结果。PagedAttention、连续批处理、分块预填充、前缀缓存、推测解码、P/D 分离、多 GPU、性能测试——所有这些都能在这条主线上找到位置。这篇非常适合作为主线,后续材料用来填补细节空白。
  2. 要真正理解连续批处理,先读 Anyscale 这篇。Continuous Batching blog
    https://anyscale.com/blog/continuous-batching-llm-inference…
    LLM 用户的响应长度各异——有些已完成生成,有些仍在进行。已释放的槽位能否立即承接下一个请求?本文通过静态批处理与连续批处理的对比图表,非常直观地解释了这个问题。读完你会明白,GPU 效率也取决于请求的调度方式。建议重点关注图表和机制部分。
  3. NVIDIA 的两篇文章:一篇构建全景认知,一篇教你如何评估性能。
    Mastering LLM Techniques: Inference Optimization
    https://developer.nvidia.com/blog/mastering-llm-techniques-inference-optimization…
    非常适合建立基础认知:预填充(处理输入)与解码(逐步生成输出)的区别、模型权重与 KV 缓存各自占用什么空间、量化、批处理和多 GPU 并行分别解决什么问题。
    LLM Inference Benchmarking: Fundamental Concepts
    https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/…
    同样强烈推荐。用户等待第一个 token 的时长、后续生成速度、系统每秒可服务多少请求——这些是不同问题。TTFT、ITL、TPOT 等指标需结合测试方法论来看。
  4. 想要更深入数学细节,可读 kipply 的 Transformer Inference Arithmetic。
    Transformer Inference Arithmetic
    这是一篇 2022 年的文章。它引导你从计算量、内存容量、带宽和通信开销出发,估算推理快慢的原因,以及 batch size 变化时会发生什么。硬件和模型示例已过时,可作为选读。

Inside vLLM:高吞吐量 LLM 推理系统解析

  • Aleksa Gordić
    来源:https://www.aleksagordic.com/blog/vllm

在本文中,我将逐步介绍构成现代高吞吐量 LLM 推理系统的所有核心组件和高级特性。我将重点拆解 vLLM[1] 的工作原理。
本文是系列文章的第一篇。它从宏观入手,然后层层深入细节(采用倒金字塔方式),让你在不淹没于琐碎细节的情况下,对完整系统形成准确的高层心智模型。后续文章将深入探讨特定子系统。

本文分为五个部分:

  1. LLM 引擎与引擎核心 (https://www.aleksagordic.com/blog/vllm#cpt1):vLLM 基础(调度、分页注意力、连续批处理等)
  2. 高级特性 (https://www.aleksagordic.com/blog/vllm#cpt2):分块预填充、前缀缓存、引导式与推测解码、分离式 P/D
  3. 扩展 (https://www.aleksagordic.com/blog/vllm#cpt3):从单 GPU 到多 GPU 执行
  4. 服务层 (https://www.aleksagordic.com/blog/vllm#cpt4):分布式/并发 Web 架构
  5. 基准测试与自动调优 (https://www.aleksagordic.com/blog/vllm#cpt5):测量延迟与吞吐量

📝 说明

  • 分析基于提交 42172ad (https://github.com/vllm-project/vllm/tree/42172ad)(2025 年 8 月 9 日)。
  • 目标读者:任何对最先进 LLM 引擎工作原理感兴趣的人,以及有兴趣为 vLLM、SGLang 等项目做贡献的人。
  • 我将聚焦于 V1 引擎 (https://docs.vllm.ai/en/latest/usage/v1_guide.html)。我也研究过 V0(现已弃用 (https://github.com/vllm-project/vllm/issues/18571)),这对于理解项目演进很有价值,许多概念仍然适用。
  • 关于 LLM 引擎/引擎核心的第一部分可能有点难懂/枯燥——但博客的其余部分有很多例子和可视化。:)

LLM 引擎与引擎核心

LLM 引擎是 vLLM 的基本构建块。它本身就能实现高吞吐量推理——但仅限于离线场景。目前还无法通过 Web 为其提供客户服务。我们将使用以下离线推理代码片段作为示例(改编自 basic.py (https://github.com/vllm-project/vllm/blob/main/examples/offline_inference/basic/basic.py))。

from vllm import LLM, SamplingParams  

prompts = [  
    "Hello, my name is",  
    "The president of the United States is",  
]  
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)  

def main():  
    llm = LLM(model="TinyLlama/TinyLlama-1.1B-Chat-v1.0")  
    outputs = llm.generate(prompts, sampling_params)  

if __name__ == "__main__":  
    main()  

📝 环境变量:

  • VLLM_USE_V1=“1” # 我们使用引擎 V1
  • VLLM_ENABLE_V1_MULTIPROCESSING=“0” # 我们在单进程中运行

此配置是:

  • 离线(无 Web/分布式系统架构)
  • 同步(所有执行在单个阻塞进程中发生)
  • 单 GPU(无数据/模型/流水线/专家并行;DP/TP/PP/EP = 1)
  • 使用标准 transformer[2](支持混合模型如 Jamba 需要更复杂的混合 KV 缓存内存分配器)

从这里开始,我们将逐步构建一个在线、异步、多 GPU、多节点的推理系统——但仍然服务于标准 transformer。在此示例中,我们做两件事:

  1. 实例化引擎
  2. 调用其 generate 方法,从给定提示中进行采样

让我们先分析构造函数。

LLM 引擎构造函数

引擎的主要组件包括:

  • vLLM 配置(包含配置模型、缓存、并行等的所有旋钮)
  • 处理器(通过验证、分词和处理,将原始输入转换为 EngineCoreRequests)
  • 引擎核心客户端(在我们的示例中,使用 InprocClient,它基本等同于 EngineCore;我们将逐步构建到 DPLBAsyncMPClient,以实现规模化服务)
  • 输出处理器(将原始 EngineCoreOutputs 转换为用户看到的 RequestOutput)

📝 注意:随着 V0 引擎弃用,类名和细节可能会变化。我将强调核心思想而非确切签名。我会抽象掉部分但非全部细节。

引擎核心本身由几个子组件构成:

  • 模型执行器(驱动模型的前向传播,目前我们处理的是 UniProcExecutor,它在单个 GPU 上有单个 Worker 进程)。我们将逐步构建到支持多 GPU 的 MultiProcExecutor。
  • 结构化输出管理器(用于引导解码——稍后介绍)。
  • 调度器(决定哪些请求进入下一个引擎步骤)——它进一步包含:
    1. 策略设置——可以是 FCFS(先来先服务)或 priority(高优先级请求先处理)
    2. waiting 和 running 队列
    3. KV 缓存管理器——分页注意力[3] 的核心

KV 缓存管理器维护一个 free_block_queue——一个可用 KV 缓存块池(通常数十万块,取决于显存大小和块大小)。在分页注意力中,这些块作为将 token 映射到其计算出的 KV 缓存块的索引结构。

LLM 引擎构造函数
本节描述的核心组件及其关系

标准 transformer 层(非 MLA[4])的块大小计算如下:
2(键/值)* block_size(默认=16)* num_kv_heads * head_size * dtype_num_bytes(例如 bf16 为 2)

在模型执行器构造期间,会创建一个 Worker 对象,并执行三个关键过程。(稍后,使用 MultiProcExecutor 时,这些相同过程将在不同 GPU 上的每个 worker 进程中独立运行。)

  1. 初始化设备:

    • 为 worker 分配一个 CUDA 设备(例如 “cuda:0”)并检查模型数据类型是否受支持(例如 bf16)
    • 验证在给定的 gpu_memory_utilization(例如 0.8 → 总显存的 80%)下是否有足够显存
    • 设置分布式设置(DP / TP / PP / EP 等)
    • 实例化 model_runner(包含采样器、KV 缓存以及前向传播缓冲区,如 input_ids、positions 等)
    • 实例化 InputBatch 对象(包含 CPU 端的前向传播缓冲区、用于 KV 缓存索引的块表、采样元数据等)
  2. 加载模型:

    • 实例化模型架构
    • 加载模型权重
    • 调用 model.eval()(PyTorch 的推理模式)
    • 可选:对模型调用 torch.compile()
  3. 初始化 KV 缓存:

    • 获取每层的 KV 缓存规格。历史上这总是 FullAttentionSpec(同构 transformer),但对于混合模型(滑动窗口、类似 Jamba 的 Transformer/SSM),它变得更复杂(参见 Jenga[5])
    • 运行一次虚拟/分析前向传播,并捕获 GPU 内存快照,以计算在可用显存中可容纳多少 KV 缓存块
    • 分配、重塑并将 KV 缓存张量绑定到注意力层
    • 准备注意力元数据(例如将后端设置为 FlashAttention),稍后在前向传播期间由内核使用
    • 除非提供 --enforce-eager,否则对每个预热批大小进行虚拟运行并捕获 CUDA 图。CUDA 图将整个 GPU 工作序列记录到一个 DAG 中。稍后在前向传播期间,我们启动/重放预烘焙的图,以减少内核启动开销,从而提高延迟。

我在这里抽象了许多底层细节——但这些是我现在要介绍的核心部分,因为后续章节会反复引用它们。

现在引擎已初始化,让我们继续讨论 generate 函数。

Generate 函数

第一步是验证并将请求输入引擎。对于每个提示,我们:

  1. 创建一个唯一的请求 ID 并捕获其到达时间
  2. 调用输入预处理器对提示进行分词,并返回一个包含 prompt、prompt_token_ids 和 type(文本、token、嵌入等)的字典
  3. 将此信息打包到 EngineCoreRequest 中,添加优先级、采样参数和其他元数据
  4. 将请求传递给引擎核心,引擎核心将其包装在 Request 对象中并将其状态设置为 WAITING。此请求随后被添加到调度器的 waiting 队列(如果是 FCFS 则追加,如果是优先级则堆推送)

此时,引擎已被输入,执行可以开始。在同步引擎示例中,这些初始提示是我们将处理的全部——没有在运行期间注入新请求的机制。相比之下,异步引擎支持这一点(即连续批处理[6]):在每个步骤之后,新旧请求都会被考虑。由于前向传播将批次展平为单个序列,并且自定义内核高效处理它,即使在同步引擎中也基本支持连续批处理。

接下来,只要有要处理的请求,引擎就反复调用其 step() 函数。每个步骤有三个阶段:

  1. 调度:选择在此步骤中运行哪些请求(解码,和/或(分块)预填充)
  2. 前向传播:运行模型并采样 token
  3. 后处理:将采样的 token ID 附加到每个 Request,反 token 化,并检查停止条件。如果请求完成,则进行清理(例如将其 KV 缓存块返回到 free_block_queue)并提前返回输出

📝 停止条件是:

  • 请求超过其长度限制(max_model_length 或其自身的 max_tokens)
  • 采样的 token 是 EOS ID(除非启用了 ignore_eos——这在我们想强制生成特定数量输出 token 的基准测试时很有用)
  • 采样的 token 与采样参数中指定的任何 stop_token_ids 匹配
  • 输出中存在停止字符串——我们在首次出现停止字符串处截断输出,并在引擎中中止请求(注意 stop_token_ids 会出现在输出中,但停止字符串不会)。

引擎循环
引擎循环

在流式传输模式下,我们将在生成时发送中间 token,但我们现在忽略这一点。接下来,我们将更详细地检查调度。

调度器

推理引擎处理两种主要类型的工作负载:

  1. 预填充请求——对所有提示 token 进行前向传播。这些通常是计算受限的(阈值取决于硬件和提示长度)。最后,我们从最终 token 位置的概率分布中采样单个 token。
  2. 解码请求——仅对最近一个 token 进行前向传播。所有早期的 KV 向量都已缓存。这些是内存带宽受限的,因为我们仍然需要加载所有 LLM 权重(和 KV 缓存)才能计算一个 token。

在基准测试部分 (https://www.aleksagordic.com/blog/vllm#cpt5),我们将分析所谓的 GPU 性能 roofline 模型。那将更详细地探讨预填充/解码性能概况。

V1 调度器可以在同一步骤中混合两种类型的请求,这得益于更智能的设计选择。相比之下,V0 引擎一次只能处理预填充或解码。

调度器优先处理解码请求——即那些已在 running 队列中的请求。对于每个这样的请求,它:

  1. 计算要生成的新 token 数量(不总是 1,由于推测解码和异步调度——稍后详述)。
  2. 调用 KV 缓存管理器的 allocate_slots 函数(详见下文)。
  3. 通过减去步骤 1 中的 token 数量来更新 token 预算。

之后,它处理来自 waiting 队列的预填充请求,它:

  1. 检索已计算的块数(如果禁用前缀缓存则返回 0——我们稍后会介绍)。
  2. 调用 KV 缓存管理器的 allocate_slots 函数。
  3. 将请求从 waiting 弹出并移至 running,将其状态设置为 RUNNING。
  4. 更新 token 预算。

现在让我们看看 allocate_slots 的功能,它:

  1. 计算块数——确定必须分配多少个新的 KV 缓存块(n)。每个块默认存储 16 个 token。例如,如果一个预填充请求有 17 个新 token,我们需要 ceil(17/16) = 2 个块。
  2. 检查可用性——如果管理器池中没有足够的块,则提前退出。取决于它是解码还是预填充请求…

相似文章

AI推理工程指南(阅读时间约17分钟)

TLDR AI

本指南解释了AI推理工程这一学科,涵盖了预填充和解码阶段的划分、从封闭模型到开放模型的转变,以及针对延迟、吞吐量和成本的优化技术。