关于 llm-serving-capacity-planner
面向 SGLang 和 vLLM 运维人员的日志分析工具,用于规划或诊断服务容量。它拆解日志中的 GPU 内存使用,并按指定 Token 长度估算并发请求数,也可选用逐卡及模型配置数据进行校验。
主要功能
- 01从启动日志提取内存数据
- 02拆分权重、KV、图及框架开销
- 03对比逐卡 nvidia-smi 数据
- 04按请求 Token 长度估算并发
- 05支持 JSON 报告
适用场景
- 01估算 GPU 配置的请求承载量
- 02排查内存相关的 OOM 故障
- 03比较配置变更前后的 KV 容量
- 04用 GPU 数据核验日志内存数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bbuf/ ai-infra-auto-driven-skills --skill llm-serving-capacity-planner安装后在 Claude Code 中输入/llm-serving-capacity-planner确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/gpu-specs.jsonlog-patterns.mdscripts/capacity_analyzer.py详细介绍
LLM 服务容量规划器
概述
当服务日志中包含足够的内存信息,可以说明 GPU HBM 的去向时,请使用此工具。分析器会读取 SGLang/vLLM 启动日志,提取权重加载、KV 池、CUDA 图、框架开销和令牌容量相关信息,然后估算常见令牌长度下可同时处理的请求数。
对于 DeepSeek-V4.1,在使用 528 B/288 B 进行估算前,请阅读 KV 布局和页面分配约定。这些数值表示每个存储令牌的载荷和缩放值,并非页面填充后的分配量或模型 KV 总字节数。请根据固定版本的源码和日志,核实实际池布局、压缩率和共享情况。
输入
运行分析前,请收集或核实以下输入:
| 项目 | 重要性 | 获取方式 | 用户未提供时的默认值 |
|---|---|---|---|
| 日志文件路径 | 主要输入;所有内存数据均来自此文件 | 请用户提供服务启动日志 | —(必需) |
| GPU 类型 | 决定用于分解验证的 HBM 总量 | 询问用户或从日志推断 | 如有可能,则从日志自动检测 |
| nvidia-smi 输出 | 提供每个 rank 的实际内存,用于交叉验证 | 使用 nvidia-smi --query-gpu=index,memory.used,memory.free --format=csv,noheader > smi.txt 捕获 | —(可选,但建议提供) |
| 模型 config.json | 用于计算理论 KV 缓存字节数并分析复制因子 | 请用户提供模型的 config.json 路径 | —(可选,改用日志数据) |
| 请求令牌长度 | 决定并发估算的分母 | 询问用户 | 4096、6144、8192 |
工作流
步骤 1:收集服务日志
用户应提供 SGLang 或 vLLM 服务实例的启动日志。分析器所需的关键日志行包括:
Load weight begin. avail mem=XX GBserver_args={'model_path': '...', 'tp_size': 8, ...}(已解析参数)Load weight end. ... avail mem=X GB, mem usage=X GB.(权重占用)DSV4 memory calculation: ... bytes_per_full_token=X, available_bytes=X GB, ... full_token=N[label] KV Cache is allocated. dtype: ..., #tokens: N, K size: X GB, V size: Y GB(或KV size)Mamba Cache is allocated. ... conv_state size: X GB, ssm_state size: Y GBMemory pool end. avail mem=XX GBCapture target|draft decode|verify|prefill CUDA graph end. elapsed=X s, mem usage=X GB, avail mem=X GB.Post-capture KV sizing: KV cache allocated. ... KV size: X GB, avail mem=X GB(需选择启用)max_total_num_tokens=XX, ... chunked_prefill_size=-1|N, ... available_gpu_mem=XX GB
对于旧版或私有分支日志,仍支持旧格式的 ServerArgs(...)、Memory profiling: 和 Capture cuda graph end.;这些不是当前主分支的日志输出格式。
当前 vLLM V1 日志则会显示:
Initial free memory: ...; Requested memory: ...(仅 DEBUG 级别)Model loading took ... GiBAvailable KV cache memory: ... GiBGraph capturing finished ... took ... GiB<device> KV cache size: ... tokens, Maximum concurrency for ... tokens per request: ...xFree memory on device (F/T GiB) ... Current kv cache memory in use is K GiB(INFO 级别)CUDA graph pool memory: A GiB (actual), E GiB (estimated), ...Initial free memory X GiB, reserved Y GiB memory for KV Cache ...(明确的 KV 字节数)Maximum concurrency for ... tokens per request: ...x
如果日志来自运行中的实例,请在启动时将 stdout/stderr 重定向到文件,以捕获日志。
步骤 2:可选地捕获 nvidia-smi 数据
用于比较每个 rank 的内存:
docker exec <container> nvidia-smi --query-gpu=index,memory.used,memory.free --format=csv,noheader > smi.txt
步骤 3:运行分析器
python3 skills/llm-serving-capacity-planner/scripts/capacity_analyzer.py \
--log-file /path/to/sglang.log \
--nvidia-smi-file /path/to/smi.txt \
--gpu h200 \
--config-json /path/to/config.json
JSON 格式输出(用于自动化):
python3 skills/llm-serving-capacity-planner/scripts/capacity_analyzer.py \
--log-file /path/to/sglang.log \
--format json
步骤 4:查看并解读结果
分析器会输出:
- 内存细分表:每个类别(权重、KV 池、CUDA 图、框架、其他)的 GiB、MiB、占比和推导方式
- 各 rank 对比:所有 TP rank 的 nvidia-smi 数据
- KV 池详情:池配置、KV 数据类型、复制因子、每个 token 的字节数计算
- 并发估算:不同 token 长度下的最大并发请求数
- 调优说明:可能提升容量的配置更改
对于 vLLM,报告还会包含 vLLM Startup Evidence 部分。日志中未提供的值仍保持未知;不要将其转换为仅适用于 SGLang 的字段。
适用场景
- 启动 LLM 服务实例后,了解 GPU 内存的分布情况
- 比较不同的
--mem-fraction-static值及其对 KV 池容量的影响 - 规划部署容量:了解给定 GPU 配置可支持多少并发请求
- 排查 OOM 问题:确定占用内存最多的类别
- 评估 fp8 KV 缓存或 EP 是否能提高并发能力
关键概念
mem-fraction-static
检查的 SGLang 源码会从模型加载前的可用内存中预留运行时余量:headroom = pre_model_load_memory * (1 - mem_fraction_static)。
KV 预算以当前可用内存为基础,扣除该余量和其他特定于模型的预留内存。它不是 post_weight_free * fraction。
捕获后可能会再次调整容量池。请以最终记录的容量和各 rank 的本地证据为准;参见源代码约束。
默认值会根据硬件和配置自动解析。请从 server_args={...} 中读取解析后的值。值越低,运行时余量越大,但 KV 容量会降低。如果未提供该值,请勿推断为 0.88。
KV 头复制
对于标准 GQA/MHA,每个 rank 存储 max(1, num_key_value_heads // tp_size) 个 KV 头。当 TP 大于头数时,每个头会在一组 rank 中复制,而不是在每个 rank 上复制整组头。例如,对于 kv_heads=4, tp=8,每个 rank 存储一个头;对于 kv_heads=1, tp=8,八个 rank 都存储这个单独的头。MLA 则会存储一个复制的潜在表示和 RoPE 键。理论计算假设所有配置的层都位于该 rank 上;对于流水线并行,请使用各 rank 的本地层数。
SWA(滑动窗口注意力)压缩
DeepSeek-V4 等模型使用 CSA(压缩滑动注意力)和 HCA
(分层上下文注意力)及滑动窗口。这会降低每个 token 的 KV 缓存字节数,相较于理论上的全注意力计算值更少。日志中报告的
bytes_per_full_token 已考虑这种压缩。
报告清单
包含:
- 服务配置:模型、GPU、TP/PP/EP、mem-fraction-static、kv-cache-dtype
- 内存细分表:类别 / GiB / MiB / 占比 / 推导来源
- 各 rank 的 nvidia-smi 对比:每个 TP rank 的已用和可用内存
- KV 池详情:池大小、bytes_per_full_token、KV 数据类型、复制因子、理论上的每 token KV 计算(提供 config.json 时)
- 并发估算表:请求 token 长度 / token 限制 / 请求限制 / 最大并发数
- 基于可用内存和配置的调优说明
2026-08-23 运行时说明
SGLang v0.5.18 将编译内核缓存统一存放在 SGLANG_CACHE_DIR 下。升级后的首次启动可能会占用更多“其他”内存,且启动时间更长,因为 Triton / FlashInfer / DeepGEMM / Inductor 会重新编译一次。重叠式检查点暂存(--startup-weight-load-mode overlap)也会改变启动时的内存变化曲线:权重页和 CUDA 图捕获可能会重叠,因此不要将更短的权重加载时间视为更小的权重占用。
已知限制
| 限制 | 详情 | 解决方法 |
|---|---|---|
| 框架特定的检查点 | SGLang 和 vLLM 暴露的内存检查点不同,因此部分字段无法一一对应比较 | 将未知字段标记为未知,并仅比较共同且明确的证据 |
| SWA 压缩模型 | 对于 CSA/HCA 注意力,无法根据模型配置独立计算每个 token 的 KV 字节数——需要框架内部的 SWA 窗口参数 | 直接使用日志中的 bytes_per_full_token |
| DeepGEMM JIT 内存 | 由于日志中未明确报告 DeepGEMM JIT 编译内存,分析器会将其归类为“其他” | 与 nvidia-smi 总量比较,以准确核算 |
| PP(流水线并行) | 内存分解按 rank 进行;PP 配置中各阶段的内存可能不均衡 | 为每个 PP 阶段指定 --target-rank |
| MoE 专家缓冲区 | 某些框架会为专家路由分配额外缓冲区,但不会单独报告 | 根据分配时机,计入“模型权重”或“其他” |
参考资料
references/log-patterns.md:用于内存分析的日志行模式及其语义。references/gpu-specs.json:h20、h100、h200和b200别名对应的 GPU HBM 规格。scripts/capacity_analyzer.py:核心分析脚本。
内存容量来源
随附的硬件表记录的是厂商标称的 GB 最大值,而非可用的 GiB。优先使用
nvidia-smi 显示的已用量和空闲量,或 vLLM 日志中的总量。B200 保留了 DGX 180GB SKU;
其他 Blackwell SKU 可能有所不同。DGX Spark 的 128GB 是统一系统内存,
并非专用 HBM。L20 是官方的 48GB SKU;不要根据名称推断
l20n 的容量。GB200 记录的是物理 192GB 封装的最大值;其已部署的 SKU 和
定制 SKU 需要通过测量确定总量。每个新增硬件条目均附有官方
来源 URL。