先说明一件事:Qwen 3.8 Flash Next 125B 这个写法在不同渠道的叫法并不统一,参数量、激活参数量、开源了哪些量化格式,都需要以官方模型仓库和官方文档为准。本文不给具体版本号和下载地址,但把从零到压测的完整流程拆开讲清楚——这套方法对任何一个「百亿到千亿参数级别、准备塞进单张 24GB 卡」的模型都适用。
这篇能做出什么
按下面的步骤走完,能得到三样东西:
1. 一个本地可用的 OpenAI 兼容接口,模型是 125B 级别的稀疏(MoE)权重,跑在单张 RTX 4090 上;
2. 一份可复现的吞吐测试脚本,能分别读出首 token 延迟(TTFT)、单流解码速度、以及并发聚合吞吐;
3. 一套调参对照表,知道每个旋钮拧大拧小分别牺牲什么。
关于「100T/s」,先对齐口径,否则后面全是白忙:
口径 A:单流解码速度。一个请求、一次生成,每秒吐出多少 token。这个数由「每 token 需要从显存读取多少权重字节」决定。RTX 4090 的显存带宽在 1 TB/s 量级(具体以官方规格为准)。
口径 B:聚合吞吐。N 个请求同时打进来,所有请求输出 token 的总和除以墙钟时间。批处理会把同一份权重读取摊到多个请求上,这个数轻松做到单流的数倍。
判断能不能碰到 100:
- 如果是 125B 稠密模型,即使 4 bit 量化,每生成一个 token 也要过一遍几十 GB 的权重,单流上限远低于 100 tok/s,基本不用试。这种情况下只能走「把部分权重放内存 + 批处理」的路子,目标放在聚合吞吐上。
- 如果是 MoE 稀疏模型,且激活参数量只有几 B 量级,4 bit 下每 token 需要读取的权重降到几 GB,单流几十到上百 tok/s 是有希望的,剩下的靠 KV cache 格式、批处理和卸载策略去榨。
所以第一步不是装环境,是确认你手上的到底是不是稀疏模型、激活参数是多少。
前置条件清单
| 项目 | 要求 | 说明 |
|---|---|---|
| GPU | RTX 4090 24GB | 单卡 |
| 系统内存 | 建议 128GB 起 | 4 bit 的 125B 权重文件通常几十 GB,卸载时需要内存兜底 |
| 磁盘 | NVMe,预留 150GB+ | 模型分片下载 + 转换中间文件 |
| 系统 | Linux(Ubuntu 系) | 推理引擎对 Linux 支持更完整 |
| 驱动 | 较新的 NVIDIA 驱动 | 版本以官方文档当前版本为准 |
| Python | 3.10 及以上 | 建议独立虚拟环境 |
| 账号 | 模型仓库下载权限 | 部分仓库需要先登录 |
时间预期:下载几十 GB 的权重、首次跑起来编译内核,加起来通常要一两个小时,不要以为是卡死了。
第 1 步:确认模型与量化版本
去官方模型仓库确认四件事:准确的模型 ID、总参数量、激活参数量、官方提供了哪些量化版本。
量化格式的选择大致是这样:
| 格式 | 位宽 | 适合场景 | 注意点 |
|---|---|---|---|
| GGUF(Q4_K_M 等) | 4 bit 上下 | llama.cpp 生态,CPU+GPU 混合 | MoE 可以把专家层留在 CPU |
| AWQ | 4 bit | vLLM 等 GPU 引擎 | 需要预量化权重,不是自己转 |
| GPTQ | 3~4 bit | vLLM 等 | 同上,注意 group size |
| bitsandbytes NF4 | 4 bit | 快速试跑 | 内核效率通常不如前两者 |
| FP8 | 8 bit | 新架构 GPU | Ada 上的支持情况以内核实现和引擎文档为准 |
经验法则:优先选官方已经放出来的 4 bit 量化,别自己从 FP16 现转,既费时间又容易掉质量。位宽低于 4 bit(2~3 bit)虽然能塞进显存,但输出质量退化会很明显,建议只在实在装不下时作为临时方案。
下载:
```bash
pip install -U "huggingface_hub[cli]"
需要登录的仓库先执行
hf auth login
export HF_HOME=~/hf
hf download <org>/<model-id> --local-dir ~/models/<local-name>
```
第 2 步:搭好运行环境
```bash
python3 -m venv ~/venvs/bigmodel
source ~/venvs/bigmodel/bin/activate
pip install -U pip wheel
```
装推理引擎。GPU 版推理框架的 wheel 通常自带 CUDA runtime,系统里不一定需要单独装完整 CUDA Toolkit,但驱动版本要够新。版本以官方文档当前版本为准:
```bash
pip install -U vllm
pip install -U openai
```
装完先做一次自检:
```bash
nvidia-smi
python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))"
```
cuda.is_available() 是 False,后面所有事都别做,先把驱动和 wheel 的匹配问题解决。
第 3 步:先跑通,别急着调速度
第一次启动,把上下文长度压小,先把服务拉起来看到输出:
```bash
vllm serve ~/models/<local-name> \
--served-model-name big \
--tensor-parallel-size 1 \
--max-model-len 8192 \
--gpu-memory-utilization 0.90 \
--max-num-seqs 8 \
--port 8000
```
注意 --tensor-parallel-size 1——单卡不要开张量并行。
冒烟测试:
```bash
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "big",
"messages": [{"role": "user", "content": "用 200 字解释稀疏专家模型的基本原理"}],
"max_tokens": 256,
"temperature": 0
}'
```
能正常返回,再进入下一步。如果这一步就 OOM,先把 --max-model-len 降到 4096,再降 --gpu-memory-utilization。
第 4 步:显存与内存卸载
24GB 装不下 125B 的 4 bit 权重,必然要卸载。两条路:
路线一:vLLM 权重卸载到内存
```bash
vllm serve ~/models/<local-name> \
--served-model-name big \
--max-model-len 8192 \
--gpu-memory-utilization 0.88 \
--cpu-offload-gb 48 \
--swap-space 24 \
--max-num-seqs 16 \
--port 8000
```
--cpu-offload-gb 越大,能塞下的权重越多,但每生成一个 token 都要通过 PCIe 把被卸载的部分搬到 GPU。PCIe 带宽比显存带宽低一个数量级,这是单流速度的主要杀手。观察到 GPU 利用率常年个位数、CPU 吃满,基本就是栽在这里。
路线二:llama.cpp 的 MoE 专家层卸载
对 MoE 模型,更划算的做法是只把注意力层和共享层放 GPU,把专家层留在 CPU,因为每个 token 只激活少数专家。
```bash
在 llama.cpp 源码目录下,仓库地址以官方页面为准
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j"$(nproc)"
./build/bin/llama-server \
-m ~/models/<model>-Q4_K_M.gguf \
-ngl 99 \
--n-cpu-moe 40 \
-c 16384 \
-fa \
-np 8 \
--host 0.0.0.0 --port 8001
```
--n-cpu-moe 是要留在 CPU 的 MoE 层数,从小往大调,直到刚好不 OOM。-fa 打开 flash attention,长上下文下省显存也提速。
两条路线都试一遍,用同一段 prompt 对比解码速度,选快的那条。
第 5 步:批处理与吞吐调优
单流调不动的时候,转向聚合吞吐。关键旋钮:
| 参数 | 作用 | 调大的代价 |
|---|---|---|
--max-num-seqs | 同时处理的序列数 | 显存占用上升,单流延迟变差 |
--max-num-batched-tokens | 单次前向的 token 上限 | 显存压力大,prefill 抢占 decode |
--gpu-memory-utilization | 预留给 KV cache 的显存比例 | 过高容易运行时 OOM |
--kv-cache-dtype fp8 | KV cache 减半 | 精度略降 |
--enable-chunked-prefill | 长 prompt 分块 | 混合请求下延迟更平滑 |
--max-model-len | 上下文上限 | 越大 KV cache 越吃显存 |
一个典型的调优起手式:
```bash
vllm serve ~/models/<local-name> \
--served-model-name big \
--max-model-len 16384 \
--gpu-memory-utilization 0.92 \
--kv-cache-dtype fp8 \
--enable-chunked-prefill \
--max-num-seqs 32 \
--max-num-batched-tokens 8192 \
--cpu-offload-gb 32 \
--port 8000
```
如果模型本身带 MTP(多 token 预测)头,或者能配一个同系列的小草稿模型做投机解码,单流解码速度还能再上一截。支持情况以引擎文档为准,不要硬套参数。
第 6 步:压测与瓶颈定位
先测单流,把 TTFT 和解码速度分开看:
```python
import time
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="dummy")
t0 = time.perf_counter()
stream = client.chat.completions.create(
model="big",
messages=[{"role": "user", "content": "写一段 300 字的产品介绍"}],
max_tokens=256,
temperature=0,
stream=True,
)
ttft = None
n = 0
t_last = t0
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
n += 1
if ttft is None:
ttft = time.perf_counter() - t0
t_last = time.perf_counter()
print(f"TTFT {ttft:.3f}s")
print(f"解码阶段 {n / (t_last - t0 - ttft):.1f} tok/s")
```
再测并发聚合吞吐,这才是「100T/s」通常对应的口径:
```python
import asyncio, time
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://127.0.0.1:8000/v1", api_key="dummy")
async def one(i):
r = await client.chat.completions.create(
model="big",
messages=[{"role": "user", "content": f"写一段 {200 + i % 50} 字的说明文字"}],
max_tokens=256,
temperature=0.7,
)
return r.usage.completion_tokens
async def main(n=32):
t0 = time.perf_counter()
res = await asyncio.gather(*[one(i) for i in range(n)])
wall = time.perf_counter() - t0
total = sum(res)
print(f"并发 {n}:墙钟 {wall:.2f}s,总输出 {total} tokens,聚合 {total / wall:.1f} tok/s")
asyncio.run(main())
```
把并发数从 4、8、16、32 依次跑一遍,画出吞吐曲线。拐点出现在哪里,就说明 --max-num-seqs 调到头了——再加并发只会让延迟涨、吞吐不涨。
边跑边看 GPU:
```bash
nvidia-smi dmon -s pucvmet -d 1
```
- GPU 利用率高、显存吃满 → 正常,继续加并发
- 利用率低但 CPU 打满 → 卸载太多,PCIe 或系统内存成瓶颈
- 利用率忽高忽低 → KV cache 不够,序列在排队等显存
常见坑与排错
启动就 OOM。 依次降 --max-model-len、降 --gpu-memory-utilization、加 --cpu-offload-gb。别一上来就开 128K 上下文。
跑一会儿才 OOM。 这是 KV cache 被长请求吃光,属于运行时 OOM。加 --kv-cache-dtype fp8,或者给 --max-model-len 设一个业务上真正需要的值。
把 GGUF 当成 transformers 权重加载。 两种格式走两套引擎,路径别搞混。
误判模型类型。 稠密 125B 和 MoE 125B 的调优策略完全不同。先看激活参数量,再决定目标定在单流还是聚合。
只看端到端时间。 端到端里混了 prefill 时间,不能当解码速度。测吞吐要分开 TTFT 和解码阶段。
长跑掉速。 连着压测十几分钟后速度下滑,先查功耗墙和温度墙:
```bash
nvidia-smi -q -d PERFORMANCE
```
分片下载不全。 下载中断后重跑,确认文件数量完整再启动,否则加载时报「文件缺失」很难一眼看出原因。
驱动与 torch 不匹配。 报 no kernel image is available 这类错误,基本都是驱动太旧或 wheel 与显卡架构不匹配。
显存被别的东西占了。 桌面环境、浏览器硬件加速、之前没退干净的进程都会吃显存。压测前先清一遍。
下一步建议
1. 补质量评估。 速度达标不等于能上线。拿真实业务 prompt 做一轮人工对比,确认量化后的输出质量在可接受范围内。
2. 做量化横向对比。 同一套 prompt,跑 AWQ / GPTQ / GGUF 几种量化,记录速度和质量,选一个平衡点。
3. 固化配置。 把验证过的启动参数写进 systemd unit 或容器 compose 文件,加上 --restart unless-stopped。
4. 加监控。 把引擎暴露的 Prometheus 指标接进看板,盯 TTFT、队列长度、KV cache 使用率,而不是等用户投诉。
5. 考虑多卡。 如果单卡吞吐到顶,再评估张量并行或多实例部署,这时候显存和 PCIe 拓扑会变成新的约束。
6. 跟官方更新。 模型和推理引擎迭代都很快,关注官方仓库的更新说明,新内核有时能带来一波免费提速。
