如何测量本地大模型每秒生成多少 Token
不要直接套用云厂商数据。用并发扫描测量 TTFT、输出 tok/s 和饱和点,再判断租用 GPU 何时比按 token 计费更划算。
每秒 token 数决定 GPU 是否划算
每秒 token 数表示服务器生成输出文本的速率。这个数值决定租用 GPU 是否比按 token 支付 API 费用更便宜。GPU 服务器按小时计费,无论处于繁忙还是空闲状态。托管 API 按 token 计费。因此,只有在付费的大多数小时内保持足够高的输出速率,GPU 才更划算。
因此,您需要实际测量结果,而不是某处读取的数字。本节定义值得记录的 4 个数值,然后提供生成这些数值的命令,以及将它们换算为决策依据的计算方法。
为什么已发布的每秒 token 数不是您的实际数值
DigitalOcean 在 2026 年 7 月发布了单个 NVIDIA H200 在 vLLM 下使用 FP8(8 位浮点)运行 llama3.3-70b-instruct 的吞吐量数据。这些数据有参考价值,但不代表您的实际性能。
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 性能的原则相同。
真正重要的4个数字
- 首个 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 数所规定的换算方式。如果服务器尚未启动,请参阅 使用 Ollama 在 VPS 上自行托管 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,也可能是在该 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 倍,以获得稳定的平均值。摘要会在 Time to First Token 标题下输出 Output token throughput (tok/s): 和 Total token throughput (tok/s):,以及 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 文档指出,4 个并行请求使用 2K 上下文时,会分配 8K 上下文。将槽数量提高到一定程度后,模型会超出显存。检查 ollama ps:如果 PROCESSOR 列显示类似 48%/52% CPU/GPU 的内容,表示模型的一部分位于 CPU 上。此时,继续增加并发数反而会降低吞吐量,而不是提高吞吐量。超过并行槽数量后,请求会排队,最多达到 OLLAMA_MAX_QUEUE;默认值为 512。超过该值后,服务器会返回 503。
vLLM 使用连续批处理,因此会在槽位空闲后,将新请求加入正在运行的批次。只要 KV 缓存未耗尽,其性能曲线就会持续上升。Ollama 针对单个模型、单台机器和较低的部署成本进行优化。因此,对同一组测试,两个引擎会给出不同结果;这正是Ollama 与 vLLM 作为服务引擎的比较的核心内容。记录生成每个数值的引擎及其版本。
测量了错误指标的 5 种情况
- 客户端距离服务器太远。 通过互联网从笔记本电脑运行基准测试时,每次 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,无服务器版本的价格为每百万 tokens $0.65。因此,4.47 除以 0.65 等于每小时 6.88 million tokens,再除以 3,600 秒,约为每秒 1,910 个输出 tokens。价格来自 DigitalOcean,除法是我们计算的结果。我们在 7 October 2026 核查时,H200 端点仍为每小时 $4.47,但 DigitalOcean 已于 18 August 2026 弃用 serverless Llama 3.3 70B,并引导用户改用 Llama 4 Maverick;后者列出的价格为每百万输出 tokens $0.87。这是不同的模型,因此必须单独进行压测,比较才有意义。
决定结果的词是持续。在饱和状态下达到每秒 1,910 个 tokens,并不等于持续达到每秒 1,910 个 tokens,尤其是每天只运行两小时,因为其余二十二小时也需要付费。将显卡租用为 GPU Droplet 也不会降低门槛:截至 7 October 2026,DigitalOcean 的 Droplet 价格中,H200 按需价格为每小时 $4.47,与端点相同;而其最便宜的 NVIDIA Droplet——每小时 $0.76 的 RTX 4000,以及每小时 $1.57 的 L40S 或 RTX 6000——容量不足以使用 FP8 容纳 70B 模型。因此,按照上述数据,一个饱和吞吐量为每秒 2,036 个输出 tokens 的 H200,必须在每个付费小时内平均达到其峰值的约 94%,才能与每百万 tokens $0.65 的价格持平;低于此利用率时,按 token 计费更具优势。通常导致自托管成本失控的不是 token 生成速度低,而是闲置的 GPU 小时。
因此,你的决策有两个输入。压测结果给出上限。流量模式决定你实际获得的上限比例。将两者相乘,然后转到GPU VPS 与按 token 计费 API 的盈亏平衡点,根据你的用量读出结论。
FAQ
自托管 LLM 的每秒令牌数达到多少算不错?
这个问题有两个答案,因为这个指标有两种用途。对于阅读输出的单个用户,每个流每秒输出约 20 个令牌就已经快于阅读速度,更高的速度没有实际帮助。对于成本,重要的是达到饱和时的总输出吞吐量;“不错”意味着该吞吐量能超过盈亏平衡点。按每百万个令牌 $0.65、服务器每小时成本 $4.47 计算,以 2026 年 7 月的价格为准,持续输出速度的盈亏平衡点约为每秒 1,910 个令牌。大型模型的单个流无法达到这一速度,因此才需要批处理。
为什么增加并发请求后,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 时,该数值接近于计费输出速率的两倍。