如何测量本地大语言模型的每秒令牌数
不要照搬 GPU 厂商公布的 tok/s。用并发度扫描测量 TTFT、输出吞吐量和饱和点,再计算租用 GPU 何时低于按令牌付费。
每秒生成令牌数为何决定 GPU 是否划算
每秒生成令牌数表示服务器生成输出文本的速率。这个数值决定租用 GPU 是否比按令牌支付 API 费用更便宜。GPU 服务器按小时计费,无论处于忙碌还是空闲状态都一样。托管 API 按令牌计费。因此,只有在付费的大多数小时内保持足够高的输出速率,GPU 才更划算。
这意味着您需要实际测量结果,而不是引用其他地方看到的数值。本节定义了值得记录的4个数值,然后提供生成这些数值的命令,以及将它们换算为决策依据的计算方法。
为什么发布的每秒 token 数不是您的实际数值
DigitalOcean 在 2026 年 7 月发布了单块 NVIDIA H200 在 vLLM 下使用 llama3.3-70b-instruct、FP8(8 位浮点数)运行时的吞吐量数据。这些数据有参考价值,但不代表您的实际结果。
The data behind this chart
[
{
"config": "H200, one stream",
"tok_s": "47"
},
{
"config": "H100, saturated",
"tok_s": "236"
},
{
"config": "H200, saturated",
"tok_s": "2,036"
},
{
"config": "H200, saturated, in+out",
"tok_s": "4,071.6"
}
]上表中的每一行都引用自该页面,其中两行是页面给出的范围下限,因此应将这两行视为最低值。表中的数据均非我们实测。
先看最后两行。标题数据为 4,071.6 tok/s,而仅计算输出的速率为 2,036 tok/s。标题数据同时计算输入和输出 token。该测试使用 1,024 个输入 token 和 1,024 个输出 token,因此标题数据中几乎一半是输出速率。这个区分很重要,因为计费依据是输出部分,而且输出部分速度更慢。Prefill(读取提示词)一次处理所有输入 token。Decode(生成答案)每次只生成一个 token。总吞吐量的标题数据将处理成本较低的部分与成本较高的部分取平均。
再看第一行。同一块 H200 每次只处理一个请求时,速率为 47 tok/s,因此在相同硬件上,饱和吞吐量高出 40 多倍。原因是单个 decode 步骤的大部分时间都在等待显存,而并发请求可以填补这段空闲时间。第二行的 236 tok/s 是单块 H100 运行同一模型时的速率,其性能受到 KV cache(key 和 value cache,即服务中的会话为每个请求保留在显卡上的内存)限制。对于 70B 模型,80 GB 显存可容纳的并发请求更少,因此更早达到饱和。
更换模型或调整输入与输出的比例后,上述每个数值都会变化。发布的数据用于建立预期,而不是确定预算。这与在磁盘和网络方面诚实地测试 VPS 性能适用同一原则。
影响最大的四个指标
- 首个 token 时间(TTFT)。 从发送请求到收到第一个输出 token 之间的延迟。它由预填充时间和排队时间组成。用户会直接感受到这个指标。
- 每个流的每秒输出 token 数。 一个回答开始生成后,输出的速度。达到约 20 tok/s 后,速度已经超过大多数人的阅读速度,因此继续提高这一指标的收益很小。
- 饱和状态下的总输出吞吐量。 服务器满负载运行时,所有并发流的输出吞吐量总和。这是容量指标,也是决定 GPU 成本回报的指标。
- 并发条件下的 p50 和 p99 TTFT。 p50 表示中位请求。p99 表示 100 个请求中有 99 个请求低于该值。排队问题总是先体现在 p99 上。
服务器空闲时,前两个指标通常更好。服务器繁忙时,第三个指标通常更好。它们彼此制约,因此没有单一指标可以描述一个推理服务服务器。
测量前固定输入和输出长度
吞吐量取决于流量的形态。包含 4,000 个 token 的提示词和生成 50 个 token 的回答属于预填充密集型工作。包含 200 个 token 的提示词和生成 2,000 个 token 的回答属于解码密集型工作。同一台服务器在这两种情况下报告的每秒 token 数会有很大差异。因此应统一输入输出比例,将该比例写在记录的每个数值旁边,并且绝不要跨比例比较。输入 1,024、输出 1,024 是合理的默认值,因为多家厂商都公布了这一比例下的数据。如果您了解实际流量,请使用实际流量。
同时固定输出长度。模型在生成 60 个 token 后遇到停止 token,会产生更短的运行结果,看起来速度更快,因为 TTFT 在总耗时中所占的比例更大。vLLM benchmark 客户端中的 --ignore-eos 标志会让每个请求严格生成指定数量的 token,因此两次运行结果具有可比性。模型选择对这些数值的影响大于任何标志:将 Qwen 3 模型装入单个 VPS GPU介绍了这一选择的内存方面。
先测量单个流
先从最简单的情况开始。这是合理性检查,也是性能上限。Ollama 会输出自己的计时结果。
ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."需要读取的字段是 eval rate,表示每秒输出的 token 数。prompt eval rate 是预填充速率,load duration 是将模型加载到 VRAM 所用的时间。冷启动后的首次调用中,load duration 通常很大,因此 total duration 会产生误导。将命令运行两次,读取第二次的结果。Ollama 默认会在模型空闲五分钟后将其卸载,因此两次运行之间长时间暂停会再次进入冷启动状态。
API 也会返回相同的字段,更适合编写脚本。
curl -s http://127.0.0.1:11434/api/generate -d '{
"model": "llama3.1:8b",
"prompt": "Write 200 words about disk scheduling.",
"stream": false
}' | jq '{eval_count, eval_duration,
tok_s: (.eval_count / (.eval_duration / 1000000000))}'eval_duration 的单位是纳秒,因此除以 1,000,000,000 即可得到秒数。这正是 Ollama API 文档建议用于计算每秒 token 数的除法。如果服务器尚未启动,请参阅 在 VPS 上使用 Ollama 自托管 LLM,其中介绍了安装过程和 systemd 单元。
TTFT 需要使用流式请求,curl 可以为请求计时。
curl -s -o /dev/null -N \
-w 'ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen/Qwen3-8B",
"messages":[{"role":"user","content":"Write 200 words about disk scheduling."}],
"stream":true,"max_tokens":256}'time_starttransfer 表示响应正文的第一个字节到达的时刻。对于流式聊天补全,该字节属于第一个服务器发送事件。它可能是第一个内容 token,也可能是紧接其前发送的仅包含角色信息的增量。因此,应将该值视为 TTFT,误差约为一个事件。对于比较同一服务器上的两次运行,这一数值已经足够准确。
单流数据会从两个方面高估主机性能。TTFT 是可能达到的最佳值,因为你前面没有排队请求。每个流的速率也是可能达到的最佳值,因为整张卡只处理一个请求。这两个指标都不能说明主机能够承载多少负载。
如何运行并发扫描?
扫描会在逐步提高的并发度下运行同一个固定负载,并记录每个步骤的结果。vLLM 提供了相应客户端,该客户端使用 OpenAI API,因此也适用于 Ollama 以及其他兼容 OpenAI API 的服务。
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts 160 \
--max-concurrency 16 \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99--max-concurrency限制同时处理的请求数,也是要扫描的变量。--num-prompts表示发送的请求总数,因此应将其设置为并发度的约 10 倍,以获得稳定的平均值。摘要会先输出 Output token throughput (tok/s): 和 Total token throughput (tok/s):,然后在 Time to First Token 标题下输出 Mean TTFT (ms):、Median TTFT (ms): 和 P99 TTFT (ms):。
该输出没有提供每个流的速率,但只需进行一次除法即可得到。Mean TPOT (ms):表示首个输出 token 之后,每个输出 token 的平均耗时,因此每个 token 耗时 25 ms 时,每个流的速率为每秒 40 个 token。用输出吞吐量除以并发度也会得到相同结果。
然后循环运行,并保存每次运行的结果。
for C in 1 4 16 32 64 128; do
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts $(( C * 10 )) \
--max-concurrency "$C" \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99 \
--save-result --result-filename "sweep-c$C.json"
done:::details读取保存的 JSON 文件
每次运行都会写入一个文件,因此可以一次性从所有文件中提取所需字段。
for f in sweep-c*.json; do
jq -r --arg f "$f" \
'[$f, .output_throughput, .total_token_throughput,
.median_ttft_ms, .p99_ttft_ms] | @tsv' "$f"
doneoutput_throughput表示每秒输出的 token 数。total_token_throughput会将输入 token 也计入,因此在输入输出比例为 1:1 时,该值接近翻倍。p99_ttft_ms之所以存在,是因为 --metric-percentiles包含了 99;如果请求一个未请求的百分位数,jq 会输出 null。
:::
并发扫描实际能说明什么?
The data behind this chart
[
{
"label": "1 stream",
"per_stream_tok_s": 92,
"total_tok_s": 92,
"ttft_p50_ms": 48,
"ttft_p99_ms": 61
},
{
"label": "4 streams",
"per_stream_tok_s": 88,
"total_tok_s": 352,
"ttft_p50_ms": 71,
"ttft_p99_ms": 96
},
{
"label": "16 streams",
"per_stream_tok_s": 71,
"total_tok_s": 1136,
"ttft_p50_ms": 152,
"ttft_p99_ms": 244
},
{
"label": "32 streams",
"per_stream_tok_s": 54,
"total_tok_s": 1728,
"ttft_p50_ms": 287,
"ttft_p99_ms": 498
},
{
"label": "64 streams",
"per_stream_tok_s": 34,
"total_tok_s": 2176,
"ttft_p50_ms": 611,
"ttft_p99_ms": 1240
},
{
"label": "128 streams",
"per_stream_tok_s": 18,
"total_tok_s": 2304,
"ttft_p50_ms": 1490,
"ttft_p99_ms": 3820
}
]这些 6 行只是说明在一台可租用的小型 GPU 服务器上,并发扫描可能呈现的形态,数量级合理。它们不是对您服务器的测量结果,也不是厂商数据。运行上面的循环,然后替换为您自己的数据。
请关注曲线形态,因为可泛化的是形态。在单个流时,整台服务器的吞吐量为每秒 92 个 token,p99 TTFT 为 61 ms。在 128 streams 个流时,总吞吐量达到每秒 2304 个 token,是前者的 25 倍;但每个独立流的吞吐量降至每秒 18 个 token,p99 TTFT 升至 3820 ms。总吞吐量会上升,是因为批处理将原本空闲的内存等待转化为有用工作。单流速度会下降,是因为相同的计算资源现在需要共享。
最后一次翻倍最能说明问题。从 64 个流增加到 128 个流时,总吞吐量仅增加不到 6%,而 p99 TTFT 大约增加到原来的 3 倍。这表示 KV cache 已满,请求开始排队,而不是继续执行。更合适的运行点更早出现:在 32 个流时,服务器仍能达到每秒 1728 个 token,即峰值的 75%;每个流的吞吐量为每秒 54 个 token,p99 TTFT 为 498 ms。将该点报告为您的容量。曲线峰值不能作为面向用户的服务容量。
Ollama 和 vLLM 衡量的不是同一件事
对默认的 Ollama 服务器运行这组测试,总吞吐量几乎不会变化。OLLAMA_NUM_PARALLEL 的默认值为 1,因此一次只处理一个请求,其余请求都会等待。正是队列导致 p99 TTFT 升高,而总输出量保持不变。在进行任何测量前,先提高该值。
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_NUM_PARALLEL=8"使用 sudo systemctl restart ollama 重启,然后确认模型仍能装入显存。每个并行槽都会分配自己的上下文窗口份额,因此 Ollama 文档指出,2K 上下文配合 4 个并行请求时,会分配 8K 上下文。将并行槽数量提高到一定程度后,模型就会超出显存。检查 ollama ps:如果 PROCESSOR 列显示类似 48%/52% CPU/GPU 的内容,说明模型有一部分位于 CPU 上。此时,随着并发数增加,吞吐量会下降而不是上升。超过并行槽数量后,请求会排队,最多排到 OLLAMA_MAX_QUEUE,默认值为 512;达到上限后,服务器会返回 503。
vLLM 使用连续批处理,因此会在槽位空闲后将新请求加入正在运行的批次。只要 KV 缓存尚未耗尽,其性能曲线就会继续上升。Ollama 针对单个模型、单台机器和较低的部署成本进行了优化。因此,两个引擎对同一组测试会给出不同结果,这正是Ollama 与 vLLM 作为服务引擎的对比的主题。记录每个数值对应的引擎和版本。
测量了错误指标的五种情况
- 客户端距离服务器太远。 通过互联网从笔记本电脑进行基准测试时,每次 TTFT 都会包含一次往返延迟,因此测量到的主要是家庭网络连接。请在与服务器相同的区域运行客户端。
- 模型处于冷启动状态。 第一个请求需要加载权重;在 vLLM 上还可能需要执行图捕获。发送一批预热请求,然后丢弃结果。
- 前缀缓存代替模型完成了请求。 vLLM 默认启用自动前缀缓存,因此反复发送相同提示词时,测量的是缓存,而不是 prefill,TTFT 会降到实际值的一小部分。
--dataset-name random可避免这一问题,因为每个提示词都不同。为确保禁用缓存,请使用--no-enable-prefix-caching启动服务器。 - 输出长度过短。 使用 32 个 token 的回答时,TTFT 会主导每个请求,因此您看到的每秒 token 数实际上描述的是 prefill。请使用
--ignore-eos,并设置符合实际的输出长度。 - 报告的并发数为 1。 这是表格中最容易看的数字,但与成本无关。
将测得的数值转化为决策
使用压测得到的饱和输出吞吐量,而不是单流速率,并将其与按 token 计费的价格进行比较。盈亏平衡点只需进行一次除法:
break_even_tok_s = (price_per_hour / price_per_million_output_tokens) * 1000000 / 3600使用 DigitalOcean 2026 年 7 月的价格计算。其 H200 专用推理端点价格为每小时 $4.47,无服务器等效服务的价格为每百万 token $0.65。因此,4.47 除以 0.65 得到每小时 6.88 million tokens,再除以 3,600 秒,约为每秒 1,910 output tokens。价格来自 DigitalOcean。除法结果由我们计算。
决定结果的关键词是 持续。如果每天只有两小时在饱和状态下达到每秒 1,910 tokens,就不能称为持续每秒 1,910 tokens,因为其余二十二小时同样需要付费。对于价格更低、每小时 $3.44 的 GPU Droplet,DigitalOcean 给出的交叉点是 72.2 percent 的持续平均利用率;低于该利用率时,按 token 计费的方案更划算。通常导致自托管成本失控的不是 token 生成速度慢,而是 GPU 空闲时段。
因此,决策有两个输入。压测结果给出上限。流量模式决定你实际获得这一上限的比例。将两者相乘,然后将结果代入GPU VPS 与按 token 计费 API 的盈亏平衡点,即可根据你的用量得出结论。
FAQ
自托管 LLM 的每秒令牌数达到多少才算合适?
这个问题有两个答案,因为这个指标有两种用途。对于阅读输出的单个用户,每个流的输出速度大约超过 20 tokens/s 后,就已经快于阅读速度,因此继续提高速度没有实际帮助。对于成本,关键是达到饱和状态时的总输出吞吐量;达到盈亏平衡点即可称为合适。按每百万 tokens $0.65、服务器每小时成本 $4.47 计算,在 July 2026 的价格下,持续输出速度的最低要求约为 1,910 tokens/s。单个大模型流通常无法达到这一速度,这正是需要批处理的原因。
为什么我增加并发请求后,Ollama 的吞吐量仍然不变?
OLLAMA_NUM_PARALLEL 的默认值为 1,因此服务器会对每个模型一次处理一个请求,其余请求会排队,直到达到 OLLAMA_MAX_QUEUE(默认为 512),随后返回 503。总输出量保持不变,而 p99 TTFT 持续上升;这说明存在请求队列,而不是 GPU 已达到繁忙状态。在 systemd drop-in 中设置该变量并重启服务,然后检查 ollama ps,因为每个并行槽位都会增加已分配的上下文,可能导致部分模型卸载到 CPU。
我应该测量首个令牌耗时,还是每秒令牌数?
两者都应测量,因为负载升高时,这两个指标的变化方向相反。TTFT 决定用户感受到的响应速度,而饱和输出吞吐量决定实际计费结果。在每个并发级别记录 p50 和 p99 TTFT,然后选择 p99 TTFT 仍能接受的最高并发数。将该并发级别下的吞吐量作为容量,而不是使用曲线顶部的最大值。
每秒令牌数更高,是否总是意味着每个令牌的成本更低?
不是。每个令牌的成本等于每小时价格除以服务器在该小时实际生成的令牌数,因此,即使服务器速度很快,但大部分时间处于空闲状态,每个令牌的成本仍然很高。决定成本的是利用率,而不是峰值速度。还要注意单位:标注的总令牌吞吐量通常包含输入令牌,因此在输入与输出比例为 1:1 时,该数值接近于计费输出速率的两倍。