5.7 节实验:在你自己的机器上测小模型单流与并发速度,对比 4 bit 和 8 bit 两种量化(MLX、llama.cpp、vLLM)
| 文件 / 目录 | 说明 | 大小 |
|---|---|---|
| 📁 ../ | 返回上级目录 | - |
| 📁 sample_output/ | 子目录 | - |
| 📄 README.md | 6.4 KB | |
| 📄 bench.py | 4.8 KB | |
| 📄 bench_mlx.py | 2.9 KB | |
| 📄 requirements.txt | 6 B |
这份代码回答三个问题。一个小模型在你的机器上一路能跑多快;同时来好几路请求时,每一路慢多少、总共能出多少 token;同一个模型的两种量化(4-bit 和 8-bit)速度差多少。宣传里的 tok/s 往往只报其中一个数,这份代码把它们放在一张表里。
做法:起一个本地推理服务,同时发 1、4、8、16 路请求(显卡上再加 32 路)。每个请求约 800 token 输入,让模型从 1 往上数数,用 max_tokens 卡在 256 个输出 token,保证每一路的活一样多。记三个数:首 token 延迟、每一路的生成速度、所有路加起来每秒出多少 token。
有两个脚本:
bench.py:对任何兼容 OpenAI 接口的本地服务测速,llama.cpp 的 llama-server、vLLM 都可以。bench_mlx.py:Mac 上的 MLX,直接在 Python 进程里调 mlx-lm 测,输出同样的表。模型都是 Qwen3.5-2B(后训练版,关闭思考模式)。
cd 5_7_local_speed
uv venv && source .venv/bin/activate # 没有 uv 就用 python3 -m venv .venv
uv pip install -r requirements.txt # 只有 httpx
llama.cpp(Mac 或 NVIDIA)。 Mac 上 brew install llama.cpp;NVIDIA 显卡按 llama.cpp 仓库的说明编译 CUDA 版。-np 是服务能同时处理几路,要不小于你测的最大并发:
llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q4_K_M --no-mmproj -ngl 99 -c 65536 -np 16 --port 8080
python bench.py --url http://127.0.0.1:8080 --label "llama.cpp Q4_K_M"
# 停掉服务,换成 Q8_0 再测一遍
llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q8_0 --no-mmproj -ngl 99 -c 65536 -np 16 --port 8080
python bench.py --url http://127.0.0.1:8080 --label "llama.cpp Q8_0"
MLX(Mac)。
uv pip install mlx-lm
python bench_mlx.py --model mlx-community/Qwen3.5-2B-4bit --label "MLX 4bit"
python bench_mlx.py --model mlx-community/Qwen3.5-2B-8bit --label "MLX 8bit"
vLLM(NVIDIA,Linux)。 两种精度:原始的 bf16,和 vLLM 在加载时把权重转成的 FP8:
uv pip install vllm
vllm serve Qwen/Qwen3.5-2B --port 8000 --max-model-len 4096 --language-model-only
python bench.py --url http://127.0.0.1:8000 --label "vLLM bf16" --concurrency 1 4 8 16 32
vllm serve Qwen/Qwen3.5-2B --port 8000 --max-model-len 4096 --language-model-only --quantization fp8
python bench.py --url http://127.0.0.1:8000 --label "vLLM fp8" --concurrency 1 4 8 16 32
每次结果打印成一张表,同时存进 results/。
我们的样例输出在 sample_output/(2026-10-23)。Mac Studio(M3 Ultra)上的结果,每格是“每一路的生成速度 / 所有路加起来”,单位 token/秒:
| 并发路数 | llama.cpp Q4_K_M | llama.cpp Q8_0 | MLX 4-bit | MLX 8-bit |
|---|---|---|---|---|
| 1 | 183 / 163 | 160 / 146 | 272 / 244 | 193 / 178 |
| 4 | 82 / 269 | 94 / 301 | 197 / 606 | 168 / 537 |
| 8 | 48 / 307 | 56 / 344 | 149 / 830 | 127 / 738 |
| 16 | 30 / 360 | 31 / 380 | 87 / 923 | 83 / 896 |
一张 RTX 5090(32 GB)上的结果,同样是“每一路 / 所有路加起来”:
| 并发路数 | vLLM bf16 | vLLM FP8 | llama.cpp Q4_K_M | llama.cpp Q8_0 |
|---|---|---|---|---|
| 1 | 323 / 309 | 390 / 368 | 490 / 401 | 414 / 348 |
| 4 | 264 / 951 | 372 / 1280 | 290 / 851 | 264 / 804 |
| 8 | 256 / 1771 | 369 / 2416 | 160 / 881 | 159 / 873 |
| 16 | 229 / 3112 | 319 / 4081 | 76 / 855 | 74 / 845 |
| 32 | 226 / 5647 | 254 / 6073 | 39 / 879 | 39 / 895 |
几个现象:
mlx_lm.server,0.32.2 版),单流只有 41–74 token/秒,并发 8 路时直接拒绝连接;所以 MLX 用 bench_mlx.py 在进程里测,这测的是引擎本身。实际部署时,服务层同样会吃掉速度。这些数字只说明“能跑多快”,不说明量化后质量掉了多少。量化对质量的影响,看公开的评测(正文 5.7 有引用),或者在你自己的任务上跑一遍对比。
换模型:llama.cpp 换 -hf 后面的 GGUF 仓库和量化名,MLX 换 --model,vLLM 换 serve 后面的模型名。换负载:--max-tokens 改输出长度;想测长输入,改 bench.py 里的 FILLER(现在约 800 token)。--concurrency 改成你预计的同时在线人数。最该测的是你实际会用的组合:你会部署的那个推理栈、你会用的量化、你预计的并发。