服务能返回一句话,只说明它活着;压测才知道它扛不扛得住
这份实践把“模型能跑”与“服务可发布”分开。先用不需要 GPU 的假流式端点验证计时与证据链,再接 llama.cpp 或 Ollama 做单机入门,最后用 vLLM 0.25.1、真实长度分布、闭环并发与开放到达率,把延迟、goodput、队列、KV、失败、显存与成本收进同一份发布门禁。
比较服务之前,先把会改变答案的变量钉住
同一个模型,把输入从 128 Token 换成 8K,TTFT(Time to First Token,首 Token 延迟)会改变;把输出从 32 换成 512,E2E(End-to-End Latency,端到端延迟)、TPOT(Time per Output Token,平均输出 Token 时间)和吞吐也会改变。固定并发闭环适合找饱和拐点,泊松到达的开放流量才更接近服务端队列承压;二者不能混叫“并发 8”。R07R16
模型与软件
模型 revision、Tokenizer、Chat Template(对话模板)、dtype/量化、框架与镜像 digest;不要只记一个会移动的模型名。
机器与拓扑
GPU 型号/数量/互联、驱动、CUDA、CPU、内存、NUMA 与功耗限制;多卡还要记录并行方式。
真实请求分布
输入与输出长度分布、到达率、共享前缀比例、采样参数、并发、取消、超时与成功判定。
llama.cpp 给你控制力,Ollama 给你更短的启动路径
二者都适合先在个人机器理解模型文件、上下文、量化与 HTTP 接口。llama.cpp 当前 server 同样有并行 slot、Continuous Batching(连续批处理)和 Prometheus metrics;Ollama 则把模型拉取、保活与本地 API 包得更短。它们不是“玩具”的同义词,但单机跑通也不自动等于多租户隔离、准入控制和生产观测。R01R04
明确指定模型文件与上下文
llama-server \
-m /models/model.gguf \
--port 8080 \
-c 8192 \
-np 4
curl http://127.0.0.1:8080/v1/completions \
-H 'Content-Type: application/json' \
-d '{"prompt":"解释 KV Cache","max_tokens":64}' -np 4 是四个 server slot 的示例,-c 是当前 server 的总上下文/KV 合同而不是“每请求 8192”。参数演进很快:复现实验必须固定 tag/commit,并保存该版本 llama-server --help。R01
先观察模型到底跑在 CPU 还是 GPU
ollama serve
ollama run YOUR_MODEL "解释 KV Cache"
ollama ps
curl http://127.0.0.1:11434/api/generate \
-d '{"model":"YOUR_MODEL", "prompt":"解释 KV Cache", "stream":false}' 官方 API 会返回 nanosecond(纳秒)口径的 total/load/prompt_eval/eval duration 与 Token 数;ollama ps 可核对已分配 context 和 CPU/GPU 放置。首次请求的 load time 要与热态分开,单模型并行还会按 context × OLLAMA_NUM_PARALLEL 放大内存。R03R04
学习模型文件与细粒度参数,用 llama.cpp;快速管理本地模型,用 Ollama
前者更容易看清 GGUF、线程、GPU offload、context 与 server slot;后者把拉取、运行、保活和 API 管理做得更顺手。Ollama 只兼容 OpenAI API 的一部分,context 等设置仍遵循自己的模型与 server 合同;真正选型必须测你自己的模型、平台与负载。R05
vLLM 的重点不是“命令更长”,而是把许多请求一起调度
下载包把 vLLM 固定到 2026-07-14 发布的 v0.25.1;它在 v0.25.0 上修复了一个 TorchCodec 启动阻断和一个特定混合 dtype 融合正确性问题。下面仍只是实验起始模板:模型、Tensor Parallel(张量并行)、max model length 与内存比例必须按权重和硬件校验。R12R10
export VLLM_API_KEY='replace-with-a-random-secret'
vllm serve YOUR_ORG/YOUR_MODEL \
--host 127.0.0.1 \
--port 8000 \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.90 \
--max-model-len 16384 \
--generation-config vllm \
--enable-prefix-caching \
--enable-chunked-prefill 先用单请求验正确性
检查 Chat Template、EOS(End of Sequence,序列结束符)、输出上限、结构化输出与流式协议。模型仓库的 generation_config.json 可能覆盖默认采样值,所以实验要显式固定请求参数或 server 生成配置。R13
再用真实长度分布预热
权重加载、kernel、内存池和 Prefix Cache 都有冷态。预热应接近正式 Prompt,并把 cold start(冷启动)与 warm steady state(热稳态)分开报告。
最后扫描并发与到达率
闭环在一个请求结束后补一个;开放到达按独立时间表继续发请求。当前 vllm bench serve 用 --request-rate 控制发起率、--max-concurrency 控制实际同时执行数,两者共同使用时实际 RPS 可能低于目标。R07
示例只监听 loopback。vLLM 除生成接口外还有 health、metrics、tokenize 与操作端点;生产应放在 gateway / reverse proxy 后,仅 allowlist 必需路由并实现认证、限流、超时与请求体上限,不能把开发控制面直接暴露公网。R13
Prefill 建状态,Decode 反复推进;调度器每一步重组 batch
传统静态 batch 要等一组序列全部结束才换下一组;Continuous Batching 会在迭代边界让完成请求退出、新请求进入。PagedAttention(分页注意力)解决 KV block 的动态映射,Prefix Cache 复用已算前缀,Chunked Prefill 把长输入拆进调度预算——四者相关,但不是同一个开关。R06R08R09
每个 decode step 都可换人
提升设备利用率不等于单请求更快。准入过高会让 waiting queue 与 TTFT 尾部上涨,容量点要由 SLO 定而不是 GPU “看起来很忙”。R11
只复用完整、同哈希的 KV block
Token、父 block、LoRA / 多模态附加身份都参与哈希。多租户应按 trust group 使用 cache_salt,避免用延迟侧信道猜缓存内容。R09
长 Prompt 分块进入调度
vLLM V1 在可行时默认开启并优先 decode;较小 token budget 通常利于 ITL,较大 budget 可能利于 TTFT。显式开关用于可复现实验,不代表“开启必快”。R08
按 block 管理序列状态
减少按最大长度连续预留造成的浪费,让不同长度序列动态占 block;总容量、block 粒度、抢占重算与实现 buffer 仍会决定峰值。R06
首 Token、后续 Token、吞吐、goodput 和失败率要一起报
项目在 examples/model-benchmark。压测器只用 Python 标准库,通过 OpenAI-compatible 流式 completions 记录客户端墙钟;采样器记录服务进程 RSS,并在存在 nvidia-smi 时补充整张设备的显存与 GPU 利用率。生产还应把逐请求时间线与 vLLM queue / KV 指标、DCGM(Data Center GPU Manager,数据中心 GPU 管理器)遥测按同一 run ID 对齐,不能把设备总量自动归因给一个进程。R07R11R14R15
包含客户端网络、排队、tokenize、prefill 与流式 flush;长输入和等待队列通常抬高它。
TPOT 把首 Token 后的剩余生成时间摊到输出 Token;ITL 保留每次流式间隔,更能看见抖动。R07
同时报 req/s、output tok/s 与 total tok/s;只报 req/s 会把不同长度请求误当成相同工作量。
吞吐可能靠让队列和尾延迟失控换来;goodput 只计同时通过 TTFT、TPOT、E2E 门槛的完成请求。
平均值会掩盖排队尖峰。超时、429、5xx、空流、OOM、取消和解析失败都要进入结果。
cd examples/model-benchmark
python3 smoke.py python3 vllm_matrix.py \
--mode dry-run \
--output-dir /tmp/vllm-plan
python3 vllm_matrix.py \
--mode protocol-smoke \
--output-dir /tmp/vllm-smoke 完整配置把 baseline、Prefix Cache、Chunked Prefill 与 512 / 2K / 8K、长短混合、并发 1 / 4 / 16 组合起来;每组先 warmup,再跑 3 个正式轮次。矩阵固定 vLLM 0.25.1,用服务端 tokenizer 校准输入、保存 workload 哈希并合并逐轮 JSON。仓库只提供 dry-run 与 CPU 协议验证路径,没有伪造任何 NVIDIA/vLLM 数字;只有 GPU inventory、运行期设备采样、同场景 vLLM /metrics 中 running / waiting / KV cache 三项语义指标、精确版本、轮次、成功率和 usage 全部通过,报告才会标记为可用性能证据。R07R11R12
{
"success": 4,
"failed": 0,
"request_throughput_rps": ...,
"output_token_throughput_per_s": ...,
"goodput_requests": ...,
"request_goodput_rps": ...,
"ttft_p95_ms": ...,
"tpot_p95_ms": ...,
"e2e_p95_ms": ...
} python3 benchmark.py \
--url http://127.0.0.1:8000/v1/completions \
--model YOUR_ORG/YOUR_MODEL \
--prompts prompts.jsonl \
--concurrency 8 \
--repeats 20 \
--api-key-env VLLM_API_KEY \
--timeout 300 \
--slo-ttft-ms 500 \
--slo-tpot-ms 100 \
--slo-e2e-ms 3000 \
--output run-c8.json
# 另开终端;设备数据不是该 PID 的进程级归因
# 没有可用 NVIDIA 运行时会明确记录 gpu_available=false
python3 telemetry.py \
--pid SERVER_PID --duration 60 \
--output telemetry-c8.json 500 / 100 / 3000 ms 只是命令示例,真实阈值必须来自产品交互合同;--timeout 是覆盖持续流式响应的整请求墙钟截止时间。脚本请求并解析服务端最终 usage;只有端点不提供 completion_tokens 时才回退到非空 SSE chunk。报告同时保存 workload SHA-256、稳定 request ID、prompt index/hash、请求上限、nearest-rank 百分位口径与 slo_pass,异步完成顺序不会打乱身份。比较引擎前先确认是否发生回退;vLLM 与 llama.cpp 当前均能提供对应 usage 路径。R07R01R13
官方 Qwen2.5-7B-Instruct Q4_K_M:在 TTFT P95 < 500 ms 的示例门槛下,并发 2 是候选拐点
| 并发 | 成功 | 输出 tok/s | TTFT P95 | TPOT P95 | E2E P95 |
|---|---|---|---|---|---|
| 1 | 8 / 8 | 20.625 | 309 ms | 47.8 ms | 1.61 s |
| 2 | 8 / 8 | 26.688 | 335 ms | 78.9 ms | 2.59 s |
| 4 | 8 / 8 | 26.836 | 674 ms | 147.4 ms | 4.50 s |
并发从 2 到 4,吞吐只从 26.688 增至 26.836 output tok/s,TTFT P95 却从 335 ms 升到 674 ms。每格只有 8 个请求,这里采用 nearest-rank 百分位,因此 P95 实际就是该格样本最大值:它适合复核脚本和发现候选拐点,不能代表生产尾延迟置信度。引擎固定 llama.cpp b9982(commit 99f3dc3)。R02
模型为官方 revision bb5d59e 的两分片 Q4_K_M,文件合计 4,683,073,632 bytes(约 4.36 GiB);服务参数 -ngl 99 -c 4096 -np 4。这不是 NVIDIA/vLLM 成绩,也不能外推到长 Prompt;完整硬件、逐请求 JSON、RSS 遥测和证据边界都在下载包的 REAL_QWEN7B_REPORT.md。R17
量化对照:同一 revision 的 Q2_K 文件为 3,015,940,000 bytes(约 2.81 GiB),但并发 2 的两轮热态吞吐为 26.2~28.6 tok/s,没有按文件缩小比例线性变快;这组性能请求也没有做质量评测,所以不能推导“更低 bit 更好”。它首先改变权重容量,速度与质量仍取决于 kernel、硬件、batch 和任务。R17
优化开关不是越多越好,要对症改变负载
每组比较都保持其他变量不变,并分别报告短输入、长输入、低并发、高并发。某个开关在一种分布上有效,不代表它是全局默认答案。
连续批处理:并发 1 → 2 → 4 → 8…
观察总吞吐何时趋平、TTFT P99 何时陡升。容量点通常在 SLO 拐点之前,而不是 OOM 的最后一格。
Prefix Cache:随机前缀 vs 90% 共享前缀
先用同一请求 warm cache,再测重复长前缀。若请求前面带随机 request ID,Token 前缀不同可能让命中消失。
Chunked Prefill:短 prompt vs 8K/32K 长 prompt
比较长 prefill 进入时,其他 decode 请求的 TPOT 和 TTFT 尾部。不要只看长请求自身完成时间。
量化:BF16/FP16 vs FP8/INT8/INT4
同时检查权重显存、吞吐、首 Token、输出质量与兼容 kernel。逻辑 bit 数不等于进程最终显存,量化元数据与运行时 buffer 仍存在。
上下文:2K → 8K → 32K
固定输出长度,测 TTFT 与 KV 占用;再固定输入,增加输出长度测 TPOT。不要把二者混在一轮里。
负载模型:固定并发 vs 开放到达
闭环扫描用于找饱和拐点;开放到达用独立 request rate 检查队列是否持续增长、实际 RPS 是否追上目标,以及 SLO goodput 是否坍塌。R07R16
export OPENAI_API_KEY="$VLLM_API_KEY"
vllm bench serve \
--backend vllm \
--model YOUR_ORG/YOUR_MODEL \
--endpoint /v1/completions \
--dataset-name custom \
--dataset-path prompts.jsonl \
--custom-output-len -1 \
--num-prompts 80 \
--num-warmups 8 \
--request-rate inf \
--max-concurrency 8 \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,95,99 \
--save-result --save-detailed export OPENAI_API_KEY="$VLLM_API_KEY"
vllm bench serve \
--backend vllm \
--model YOUR_ORG/YOUR_MODEL \
--dataset-name custom --dataset-path prompts.jsonl \
--custom-output-len -1 \
--num-prompts 240 --num-warmups 12 \
--request-rate 2 --max-concurrency 16 \
--goodput ttft:500 tpot:100 e2el:3000 \
--save-result --save-detailed --request-rate inf 配合固定执行并发是闭环压力扫描;有限 request rate 按当前 CLI 的随机到达过程发起,--max-concurrency 只是执行上限。机器追不上时,实际 request rate 可能低于目标,所以必须同时看 achieved rate、queue 与 goodput。vLLM 0.25.1 server 从 VLLM_API_KEY 启用认证,bench 的 OpenAI-compatible client 从 OPENAI_API_KEY 生成 Bearer header,密钥不放进命令参数。仓库 JSONL 同时保留本项目使用的 max_tokens 和 vLLM CustomDataset 使用的 output_tokens;--custom-output-len -1 才会尊重逐请求长度,否则当前默认统一成 256。R07R13
显存给硬上界,SLO 与流量给真正可卖的容量
先估权重与单请求 KV,再减去框架、激活和临时 buffer;随后用实测 output tok/s 与单请求 TPOT 估吞吐上限。最终取内存、计算、尾延迟和错误率中最小的那一个,并用服务端 KV、等待队列与设备遥测验证。R10R11
每请求 KV bytes ≈ 2 × layers × kv_heads × head_dim × context × bytes
前面的 2 是 K 与 V 两份张量。若是 32 层、8 个 KV head、head_dim 128、上下文 8192、每元素 2 bytes,单请求约为 1.0 GiB。这只是 Dense Transformer / GQA 的结构近似:MLA、滑动窗口、KV 量化、block 粒度、padding、并行切分、CUDA Graph 与 allocator 都会改变运行时占用,必须用引擎指标和遥测验证。R06R11
python3 capacity_plan.py \
--params-b 7 --weight-bits 16 \
--layers 32 --kv-heads 8 --head-dim 128 \
--context 8192 --gpu-memory-gib 24 \
--measured-output-tokens-s 600 \
--per-request-tokens-s 30 \
--arrival-rps 2 --mean-output-tokens 200 脚本会分别给出内存并发、计算并发与推荐并发;如果权重和预留后连一个请求都放不下,推荐值就是 0 并输出警告,不会为了给出正数而强行钳成 1。估算只用于筛掉不可能配置,发布值仍由真实压测决定。
arrival_rps × mean_output_tokens 给平均输出 Token 需求。还要为高峰、重试和长度尾部分配缓冲。
用 SLO 内的实测 total/output tok/s,不使用 OOM 边缘或 P99 已超标配置的峰值。
每小时机器成本 ÷ 每小时成功且满足 SLO 的输出 Token × 1,000,000。失败、超时与空闲仍产生机器成本,但不应混入“可交付 Token”分母。
症状先映射到链路,再改参数
不要看到“慢”就同时改 batch、量化和上下文。先看 TTFT、TPOT、排队、KV 使用、GPU 利用率与错误时间线,定位发生在哪一段。vLLM 的 production metrics 已把 waiting/running、queue time、KV cache、prefill/decode、TTFT、TPOT 与 E2E 暴露为可对齐信号。R11
首请求很慢,之后明显变快
可能包含模型加载、权重映射、kernel 初始化或 cache 冷态。把 cold start 与 warm steady-state 分开报告;生产还要测扩容新副本何时真正 ready。
吞吐上涨,但 TTFT P99 暴涨
调度器在高并发下让请求排队。若已超过交互 SLO,这不是免费吞吐;降低准入并发、做优先级/队列隔离,或增加副本。
开启 Prefix Cache 几乎没收益
检查请求 Token 前缀是否逐字一致、共享长度是否覆盖完整 KV block、warmup 是否命中同一模型/LoRA,以及随机时间戳或 ID 是否放在了 Prompt 最前面。R09
长 Prompt 让其他请求的 Token 断断续续
观察 chunked prefill 配置与调度预算;比较开启前后 decode 的 ITL/TPOT,而不只看长请求 TTFT。还要确认上下文没有触发 KV cache thrash。R08
显存看起来够,却启动或高并发 OOM
权重估算漏了量化 scale、CUDA graph、workspace、KV block、通信 buffer 或碎片。记录启动峰值与稳态峰值,降低利用率预留或上下文/并发,并用固定版本重新测。
压测的 tok/s 与服务端报告对不上
客户端可能把 SSE chunk 当 Token,或两边统计范围不同。统一 Tokenizer 与输入/输出范围,并报告 request、output-token、total-token 三种吞吐。
一手来源与证据边界
优先使用论文、官方文档、官方模型卡和代码仓库。页面中的数字只代表来源所述设置,不自动外推到其他模型与数据。
llama-server 的 OpenAI-compatible 路由、并行 slot、连续批处理、metrics、usage 与当前参数入口。
本页 Apple M4 历史实测所用引擎 tag 与 commit 证据;不是当前通用性能基线。
total/load/prompt_eval/eval duration 与 Token 计数;流式请求在最终 done chunk 返回 usage。
模型并存、单模型并行、队列上限、context × parallel 的内存放大与 ollama ps 解释。
示例压测器连接兼容端点的依据;兼容的是部分 API,context 等行为仍需按 Ollama 合同配置。
PagedAttention、block 管理与 vLLM 初始连续批处理设计;论文数字不外推到 2026 引擎和硬件。
request-rate、max-concurrency、TTFT/TPOT/ITL/E2E、goodput、详细结果与当前命令参数。
Chunked Prefill 默认边界、decode 优先调度、max_num_batched_tokens 对 TTFT/ITL 的权衡和 preemption 观测。
按完整 KV block 哈希复用、LRU 淘汰、LoRA/多模态额外哈希与 cache_salt 多租户隔离。
gpu-memory-utilization、KV cache、prefix caching、chunked prefill、scheduler 与当前服务参数。
queue、running/waiting、KV 使用、prefix cache、prefill/decode/TTFT/TPOT/E2E 与成功计数的 Prometheus 指标。
下载包矩阵固定版本;该补丁在 v0.25.0 之上修复 TorchCodec 启动阻断和混合 dtype 融合正确性问题。
Completions、Chat、health、load、metrics 与 utility 端点;生产暴露面仍需网关 allowlist。
实验脚本的设备级显存与利用率快照;不能把设备总量自动归因给单个进程。
集群持续采集显存、功耗、能耗、SM/Tensor/DRAM 活跃度并导出 Prometheus 的生产路径。
闭环 Single Stream、泊松 Server 场景、尾延迟与样本量的标准化参照;本项目结果不是 MLPerf submission。
Apple M4 历史实测的模型、Apache-2.0 许可、Q4_K_M/Q2_K 文件身份与大小。