llm-d 架构解析(下):从 P/D 分离到弹性扩缩与 Batch Serving
引言上篇:从 InferencePool 到 KV Cache-Aware Routing围绕 llm-d 的 Router 展开,讨论了 InferencePool、EPP、Load-Aware Routing、Prefix Cache-Aware Routing,以及 KV Cache Indexer 如何将单实例内部的缓存状态转化为可用于全局调度的信号。 当请求调度能力建立之后,推理系统仍然需要解决三个更进一步的问题: Prefill 与 Decode 的计算特征不同,是否可以拆分并独立优化? KV Cache 不仅要“知道在哪里”,还要解决容量不足与跨实例复用问题; 当负载持续变化时,如何在保证 SLO 的同时完成扩缩容,并兼顾 Batch 等非实时工作负载? 这些问题共同构成 llm-d 在分布式推理阶段的核心能力。本文将重点讨论 Disaggregated Serving、KV Cache Infrastructure、Autoscaling,以及 Batch / Async Serving。 1. Prefill / Decode Di...
llm-d 架构解析(上):从 InferencePool 到 KV Cache-Aware Routing
引言vLLM、SGLang 等推理引擎解决的核心问题,是如何在单个 Model Server 内高效完成模型加载、请求批处理、Prefill、Decode 与 KV Cache 管理。当系统从单实例扩展为由多个 Model Server 组成的推理集群后,新的问题随之出现: 一个请求应当被发送到哪个 Model Server? 如何避免部分实例过载而另一些实例空闲? 如果某个实例已经缓存了请求前缀对应的 KV Cache,能否优先复用? 当实例数量动态变化时,如何维护可用 Endpoint 的视图? llm-d 关注的正是这一层问题。它并不替代 vLLM 或 SGLang,而是在多个推理实例之上提供面向 LLM 工作负载的请求路由、Endpoint 调度与 KV Cache 感知能力。 本文聚焦 llm-d 的核心架构与智能路由机制。P/D Disaggregation、KV Offloading、Autoscaling 与 Batch Serving 等能力将在下篇展开。 1. llm-d 的定位可以将 llm-d 与推理引擎的职责划分为两个层次: 层...
从源码读 vLLM Scheduler:一轮调度如何分配请求与 KV Cache
vLLM 的 Scheduler 并不执行模型,也不实现 attention kernel。它更像推理引擎的调度台:每一轮决定哪些请求前进、各前进多少 token,以及有限的 KV Cache 是否还能容纳它们。 这张图刻意只保留主干。waiting 与 running 是请求状态;KVCacheManager 是内存准入者;worker 只执行 Scheduler 写好的“派工单”。下文对应 vLLM commit 7fbd44c。 先建立一个正确的心智模型一个请求的状态可以先简化为: 1waiting → running → finished waiting:请求已被接收,但尚未获得计算和 KV Cache 资源; running:请求已拥有 KV block,可以继续 prefill 或 decode; finished:命中 EOS、stop 条件、max_tokens,或被取消;相关 KV block 会被回收。 这里最反直觉的一点在 Scheduler.schedule():它没有严格的“prefill 阶段”和“decode 阶段”。每个请求只维护已...
从 SGLang 看现代 LLM 推理引擎:调度、缓存与延迟权衡
对外看,LLM 推理引擎只是把 OpenAI 风格的请求转换成一串 Token;对内看,它更像一个专门为自回归模型设计的操作系统:在不断变化的请求之间分配 GPU 计算、显存和执行时间,同时维护每个请求持续增长的状态。 真正困难的并不是完成一次模型 Forward,而是持续回答三个问题: 下一轮 GPU 应该处理哪些请求? 有限显存应该保留哪些 KV Cache? 怎样在首 Token 延迟、生成流畅度和系统吞吐之间取舍? SGLang、vLLM 等现代推理引擎的大部分优化,都可以放回这三个问题中理解。 推理引擎面对的是一组彼此冲突的工作负载一个生成请求在执行层面分为两个阶段: 123Prompt ── Prefill ──> First Token ── Decode ──> Remaining Tokens │ │ └── 创建 KV Cache └── 读取并扩展 KV Cache Prefill 一次处理整段输入,矩阵规模较大,通常更容易利...
Kserve的学习和使用(上)
从 vllm serve 进入模型服务时,最先看到的是推理引擎本身:PagedAttention、Continuous Batching、KV Cache、TTFT、TPOT,以及 GPU 上如何更高效地完成 Prefill 和 Decode。 但视角一旦从“启动一个推理进程”切换到“在 Kubernetes 中长期运行一批模型服务”,问题会立刻变多: 模型服务如何声明和部署 不同推理框架如何统一接入 模型文件如何下载到 Pod 服务如何暴露、扩缩容和灰度 多个模型之间如何编排 控制器如何感知服务状态并完成故障恢复 vllm serve 解决的是推理引擎和服务进程的问题。KServe 解决的则是模型服务在 Kubernetes 上的平台化管理问题。 这篇文章记录一次从本地安装 KServe 到理解核心抽象的过程,重点放在 Control Plane、Data Plane、InferenceService、ServingRuntime 和 InferenceGraph。LLM Runtime、vLLM 集成和 GPU 环境会放到后续文章继续展开。 KServe 是什么KServ...
InferLens:给自建 LLM 推理服务的一次轻量体检
最近在写一个小工具:InferLens。 它现在还很早期,目标也不大:我本地起了一个 vLLM,或者手里有一个 OpenAI-compatible API,我想用一个命令确认它到底能不能正常流式返回、首 token 大概有多慢、vLLM 的 metrics 有没有变化。 说白了,它不是平台,也不是监控系统。现在更像一个推理服务的 ping。 为什么写它调 LLM 推理服务的时候,我经常遇到一些不算大、但很烦的问题: 服务能连上,但是 stream 里没有真正的 token 首 token 很慢,但不知道慢在 headers、首 chunk,还是后面的生成 vLLM 的 /metrics 接上了,但每次都要自己对照 第三方 API 说兼容 OpenAI,但 stream 字段可能长得不完全一样 offline 推理能跑,不过模型加载时间和生成时间混在一起 这些问题都可以手写 curl 或临时 Python 脚本解决,但每次都临时写一遍就很累,而且结果也不太好复现。 所以我想先把最常用的检查动作收进一个 CLI 里。 现在有什么当前版本是 v0.0.2,inferlens pin...
Linux 用户、用户组与权限访问机制:从身份到访问检查
Linux 的权限系统看起来很朴素:用户、用户组、rwx 三组权限位。可是只要真的排查过“为什么这个进程能写这个文件”“为什么 root 装出来的文件被某个用户拿不到”“为什么目录有读权限却进不去”,就会发现它并不只是 chmod 755 这么简单。 这篇文章不从某个项目出发,而是从 Linux 自身的模型出发,梳理用户、用户组、进程身份、文件权限、目录权限、ACL、setuid/setgid/sticky bit,以及一次访问检查大致是如何发生的。 一、用户不是名字,而是 UID我们平时说的 root、nginx、mysql、test,本质上只是名字。内核真正识别的是数字 ID: 12UID: user idGID: group id /etc/passwd 里保存的是用户基本信息: 1test:x:982:982::/opt/test-agent:/sbin/nologin 字段拆开是: 1用户名:密码占位:UID:GID:描述:home:shell 这里真正决定身份的是: 12uid = 982gid = 982 用户名只是用户态工具展示和解析时...
理解vLLM的PagedAttention:从KV Cache到Beam Search
在做大语言模型推理优化时,很多人一开始会把注意力集中在算力上,比如 FlashAttention、量化、算子融合等。但在真实的在线服务里,一个同样关键、甚至更容易先把系统拖慢的问题其实是 KV cache 的管理。 vLLM 的 PagedAttention 之所以重要,不是因为它重新发明了 attention 的数学公式,而是因为它重新设计了 KV cache 在显存里的组织方式。这件事直接影响吞吐、延迟,以及服务能同时容纳多少并发请求。 这篇文章会重点解释: 为什么传统 KV cache 管理方式容易浪费显存 PagedAttention 到底“paged”在什么地方 它为什么对 beam search 这种共享前缀、后续分叉的场景特别友好 先看问题:推理阶段真正膨胀的是KV Cache在自回归生成中,每生成一个新 token,模型都会把这一时刻每一层的 K 和 V 保存下来,后续 token 做 attention 时直接复用这些历史结果,而不是每一步都从头再算一遍。 这就是 KV cache。 KV cache 的几个特点决定了它很难管: 它会随着输出 token ...
一次控制器状态机在高并发下的竞态复盘
背景线上某控制器负责管理 WorkItem 资源。删除流程里偶发出现: 日志显示已经把状态写成 Deleting 紧接着同一轮 reconcile 又读到旧状态 Failed 状态机落入 default 分支,提前 return nil 在特定 predicate 组合下,不会被 status 变更再次触发,最终表现为“删除卡死” 现象与日志下面是一次典型时间线(省略无关字段): 12310:14:07.447 entering delete flow, setting phase to Deleting10:14:07.460 delete admission check passed10:14:07.460 unexpected phase during deletion: Failed 注意第三行和第一行间隔只有十几毫秒。 关键代码路径删除状态机(简化后)大致是: 1234567891011121314151617181920func (r *Controller) reconcileDelete(ctx context.Context, item *v1.WorkI...
PromQL 深度实战:语法体系、常用算子与 Grafana 场景化查询
很多人把 PromQL 当成“画图 SQL”:会写 sum(rate(...)) 就能应付 80% 的面板,但一旦遇到多指标关联、实例集合变化、窗口语义、告警抖动,查询就开始失控。本文不绑定任何具体业务指标,而是从 Prometheus 的数据模型与 PromQL 的算子体系出发,系统梳理常用指令,并给出 Grafana 中可复用的查询范式。 1. 先建立数据模型:你在操作的不是“一个数”Prometheus 存储的是时间序列(Time Series)。一条序列由两部分唯一确定: 指标名(metric name):如 http_requests_total、node_cpu_seconds_total 标签集(labels):如 {job="api", instance="10.0.0.1:8080", method="GET", code="200"} 每个时间点上的采样是 (timestamp, value)。因此 PromQL 的输入/输出从来不是“单个 fl...










