在双 RTX 3090 上跑满 Qwen3.8-Flash-Next:Strata 本地部署与调优
从选型、绕过驱动和网络的坑,到 120 tok/s 稳态解码、256K 原生上下文、多模态与局域网服务。全部数字来自实测,测量方法和条件随数据一并给出。
0. 硬件底牌与目标
| 部件 | 配置 | 对部署的影响 |
|---|---|---|
| GPU | 2 × RTX 3090 24GB(共 48GB,sm_86) | 每多 1GB 显存约多驻留 700 个 MoE 专家 |
| CPU | Xeon E5-2666 v3(10C20T,2.9GHz,AVX2) | VRAM 未命中专家靠它补算 |
| 内存 | 62GB DDR4 四通道 | 决定能驻留多大的专家集 |
| 主板 | X99,PCIe 3.0;GPU0 仅 x8 布线 | 实测 PCIe 6.1 GB/s(GPU1 x16 为 10.4),限制长提示预填充 |
| 系统 | Ubuntu 24.04,NVIDIA 驱动 570.211.01 | 比 Strata 要求的 580 低 |
| 磁盘 | NVMe,~294GB 可用 | 模型 78GB + pack 55GB + MTP 6GB |
| 网络 | huggingface.co / github.com 不可达,ModelScope / hf-mirror 通畅 | 第二道坎,也是篇幅最多的一章 |
目标:开发 + 测试两用,质量优先(保留全部专家),解码 ≥ 80 tok/s,不落盘(专家零 SSD 读取)。
1. 选型:为什么是 IQ3_S
-
Strata 跑的是 Qwen3.8-Flash-Next MoE(512 专家/层),有四个量化尺寸和三个"版本"(原版 / Coder / Swift / Unsloth 4-bit)。决策依据全部来自项目文档的实测数字,不是拍脑袋:
-
原版 IQ3_S (3.5-bit,保留全部 512 专家):文档原话 "matches the full model on the published tests"。2×3090 的社区实测(#1447,同款双卡、32GB 内存、resident 模式)解码 137 tok/s。
-
Coder(IQ1_M):砍到 256 专家换代码能力(LiveCodeBench 保 99%),但中文会出错或循环(#438)。
-
IQ2_XS / Q2_0:62GB 内存装得下 IQ3_S,没理由牺牲质量。
-
Swift 1.5:同速度、想得更短,对复杂推理任务反而减分。
结论:--family qwen --model IQ3_S --gpus 0,1。
- 选型阶段的一个重要点:显存比内存更值钱。每多 1GB 显存 ≈ 700 个专家常驻 GPU,被 GPU 接住的专家就不用 CPU 算。48GB 显存 + 62GB 内存的组合,可以把全部专家放在"显存 + 锁页内存"里。
2. 第一道坎:驱动 570 < 580
- setup 直接拒绝:
the NVIDIA driver is too old (570.211.01; 580 or newer is needed)。 - 不想动驱动(生产机,升级有风险),答案就在报错文本里:
--cuda 12。Strata 为老卡保留了一个 CUDA 12.9 构建的实验引擎(-DSTRATA_EXPERIMENTAL_SM60=ON),CUDA 12 的 minor-version compatibility 把 Linux 驱动下限降到 525,570 轻松通过。
读 setup.py 确认了三件事:
- 选 CUDA 12 引擎时驱动检查阈值切换(
min_driver = CUDA12_MIN_DRIVER if cuda_tk == 12 else MIN_DRIVER,即 525 vs 580); - release 里只有 Windows 的 cuda12 资产,Linux 必须本地编译;
- 实验构建里那些老卡专用路径(Volta 注意力、Pascal BF16)只影响 sm_75 以下,3090 是 sm_86,内核与 CUDA 13 版完全相同,零速度损失。
- 本机恰好有
/usr/local/cuda-12.8工具链,编译路线成立。
3. 第二道坎:GitHub 和 Hugging Face 都不通
-
这是整个部署里最花时间的一章。
huggingface.co → 超时(000)
github.com → 超时(000),setup 进程卡在 SYN-SENT 状态
api.github.com → 200(能查 release 元数据)
objects.githubusercontent.com → 通
hf-mirror.com → 200
modelscope.cn → 302,0.13s
setup 默认的四个下载源里,两个死了(GitHub 引擎、HF 模型),两个活了(pip 已配清华镜像、NVIDIA 工具链本机就有)。
3.1 llama.cpp 源码:预置 zip + .done 标记
- Linux 编译引擎需要 pinned commit 的 llama.cpp(Strata 用它的 ggml 和 gguf-py)。setup 的
download()只认<file>.done标记才会跳过联网校验,它还是会去 HEAD github.com 然后挂死 5 次×60s。正确方法:
bash
# 文件名必须精确匹配 setup 期望:commit 前 7 位
curl -L -o third_party/llama.cpp-3cf0325.zip \
"https://gh-proxy.com/https://github.com/ggml-org/llama.cpp/archive/3cf03257f219afbe7334045ff7c6a06ac68c627d.zip"
touch third_party/llama.cpp-3cf0325.zip.done
- 38MB,1.2 秒下完。这个
.done标记机制是读 setup.py 源码才发现的,文档里没有。
3.2 --build:跳过不存在的下载
- 即使加了
--build(本地编译、不用现成引擎),setup 在 Step 4 仍先尝试从 GitHub 拉引擎包,连接挂死。--build跳过的是"用现成包",不是"跳过联网检查",所以 llama.cpp 的预置必须在第一次--build之前完成。
3.3 模型权重:ModelScope + modelscope CLI 并行下载
--source modelscope让 setup 把 HF 的 pinned 仓库映射到 ModelScope 同名仓库(同路径、同尺寸,文件按 ModelScope 自己发布的 SHA-256 校验)。MTP 草稿层的获取脚本同样认STRATA_SOURCE=modelscope。- 但 setup 自带的下载器是单流 urllib,对 78GB 的权重太慢。改用 conda base 里的 modelscope CLI 并行下载(这是和setup 流程并行跑的引擎编译不需要模型):
bash
# 只下 IQ3_S 目录,四个尺寸全下要 300GB
modelscope download --model ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF \
--include "IQ3_S/*" --local_dir /mnt/data/Strata-data/gguf-src --max-workers 8
- 78GB、双分片并行,29 分钟跑完(约 30MB/s)。期间引擎编译同步完成,互不等待。
3.4 --gguf-dir:让 setup 认本地权重
- setup 支持
--gguf-dir(#305):分片存在时跳过全部下载,且--gguf-dir模式下连.done标记都不需要(源码里have_model = all(s.exists() and (done(s) or a.gguf_dir) ...))。最终安装命令:
bash
./setup.sh --yes --family qwen --model IQ3_S --gpus 0,1 --cuda 12 \
--source modelscope --gguf-dir /mnt/data/Strata-data/gguf-src/IQ3_S \
--data-dir /mnt/data/Strata-data --no-start
- 一个细节:引擎编译完成后 setup 会自动进入模型下载步骤。必须盯着日志,一见 "engine compiled" 立刻杀掉 setup,把带宽全留给 CLI。
4. 首次启动与"零落盘"验证
bash
./run-iq3_s.sh
# [strata] loading the experts into RAM (about 55 GB) and locking part of them for the GPU.
60 秒就绪。启动日志把部署质量交代得清清楚楚:
bash
layer split auto: K=20 ... the caches hold 17688 of 24576 profiled pairs (~98.9% of the routed mass)
PCIe probe: 6.1 GB/s host->device (CUDA0) / 10.4 GB/s (CUDA1)
R4 hit path ON - resident experts are computed on the GPU
跑了两个请求后查日志,"不落盘"达标:
resident RAM: ... 0 blob reads from the file(稳定使用时从文件读专家为 0)- 专家缓存命中率 97-99%,仅 0.1-0.2% 的路由需要跨卡/PCIe 补取
- 内存 58/62GB(锁页专家副本),
/metrics的file_blobs停在 0
5. 速度调优
5.1 MTP 草稿窗口深度(--spec)
--spec |
稳态解码(1000-token × 3) | 备注 |
|---|---|---|
| 4(默认) | 116.1 / 117.0 tok/s | 基线 |
| 6 | 119-122 tok/s | ✓ 拐点,最终采用 |
| 8 | 106-115 tok/s | 窗口验证成本线性涨,接受率撑不住 |
- 草稿接受率是关键中间量:spec 6 时 ~80%,spec 8 时掉到让每个窗口携带的"有效 token"反而变少。
5.2 --adapt-async 1 的陷阱:文档数字不可盲抄
- 文档(#1447)在同款 2×3090 上实测 +6%(137→145)。我加上后没效果 ,读启动日志才发现:
--adapt-async 1 is off (it needs the resident RAM mode, --resident-experts)。setup 的常驻变体和显式--resident-experts是两回事。显式打开后跑了三组:
bash
resident RAM: 21.59 GiB of experts in RAM, 4800 exchanged with the VRAM tier
1000 generated: 116.4 / 107.1 / 112.2 tok/s ← 比不开还慢
- 原因:这台机器 98.9% 命中率下几乎没有交换可做,异步交换的开销变成纯成本;而且显式模式只驻留 21.6GB、其余靠页缓存,布局反而更差。已撤销。
5.3 最终成绩
| 配置 | 稳态解码 |
|---|---|
| IQ3_S + spec 6 + 双卡 K=20 + 常驻切分 | 113-122 tok/s(1000-token 连跑) |
| 短上下文含冷启动 | 首轮 ~73,热身后稳定过百 |
6. 256K 原生长上下文:Agent 场景实测
- 上下文从 131072 改
--max-context 262144(模型原生训练窗口)。setup 菜单警告"256K 需要 ~78GB 内存",但那是对"专家全在内存"的布局说的;我们的 KV 留显存(int8 下 256K 仅 ~3.6GB),内存纹丝不动(57GB)。文档 #406 也有 64GB 跑 IQ3_S@256K 的先例。
| 指标 | ~105K 上下文 | ~213K 上下文 |
|---|---|---|
| 冷读预填充 | 3,127 tok/s(34s) | 3,494 tok/s(61s) |
| 持续解码 | 70.2 tok/s | 66.6 tok/s |
| 内存/显存 | 57GB / 23.8+24.0GB | 不变 |
Agent 关键路径,前缀复用,同一长提示词追加一轮后:
bash
prompt 105683 tokens = 98304 reused + 7379 read in 4820 ms
-
TTFT 从 34 秒降到 4.8 秒。98,304 = 12 × 8192,恰好是分块边界。
-
另外:256K 配置下短上下文解码比 128K 配置低 ~10 tok/s(预分配 KV 挤掉 ~700 专家槽位,命中率仍有 99.2%),上下文长度按实际负载选,不是越大越好。
7. 多模态:开启的正确姿势
- 安装时图省显存关了图片能力,后补开。过程有一个必须预防的坑:
setup --setup会重写配置文件 ,把调好的--spec 6重置回 4。对策:命令行显式带上要保留的参数,装完复查一遍配置:
bash
./setup.sh --yes --setup --family qwen --model IQ3_S --vision gpu \
--context 262144 --gpus 0,1 --cuda 12 --source modelscope \
--gguf-dir /mnt/data/Strata-data/gguf-src/IQ3_S --data-dir /mnt/data/Strata-data --no-start
- mmproj(0.9GB)从 ModelScope 正常拉取,CUDA 视觉编码器编译 275 个目标,一次性 ~7 分钟
- 装完手动把 spec 改回 6(旧配置被 setup 自动备份为
.bak) - 实测:图片 OCR 逐字准确;文本解码零回退(106-118 tok/s,编码器 1.4GB 从显存余量出,没挤专家缓存)
8. 局域网服务与一次被拦截的"高危"
- 0.0.0.0 暴露必须配 API key,这是 Strata 文档的硬规则,也是常识。实操中还收获了一次工具链的安全教育:
- 我把密钥直接写进启动脚本 → 被本地安全扫描(Mimosa)拦截:硬编码凭据,高危;
- 改为正确做法:密钥存独立文件
.strata-api-key(umask 077,权限 600),脚本运行时读取; strata.sh直接启动server.py并显式传--host 0.0.0.0 --api-key------不经过run-iq3_s.sh(它会被 setup 重装覆盖)。
验证:无 key 请求 401,带 key 200,ss -tlnp 确认 0.0.0.0:8080。客户端三行接入:
bash
Base URL: http://<LAN-IP>:8080/v1 (OpenAI / Anthropic 双兼容)
API Key: cat /mnt/data/code/Strata/.strata-api-key
9. 运维脚本与踩坑合集
- 所有经验最终沉淀为一个 60 行的
strata.sh(start/stop/restart/status):
bash
./strata.sh start|stop|restart|status
bash
PORT=8080
DIR="/code/Strata"
RUN="$DIR/run-iq3_s.sh"
# LAN access: listen on all interfaces WITH a required API key (docs/AI_SETUP.md ground rule).
# The key lives in .strata-api-key (chmod 600), read at start - never hardcode it here.
HOST="0.0.0.0"
API_KEY="$(cat "$DIR/.strata-api-key" 2>/dev/null)"
[ -z "$API_KEY" ] && echo "ERROR: $DIR/.strata-api-key missing" && exit 1
port_busy() { curl -s --max-time 2 "http://127.0.0.1:$PORT/health" > /dev/null 2>&1; }
wait_port_free() {
i=0
while port_busy; do
i=$((i + 1))
[ "$i" -gt 60 ] && echo "ERROR: port $PORT still busy after 60 s" && return 1
sleep 1
done
}
case "$1" in
start)
if port_busy; then
echo "Strata already running: http://127.0.0.1:$PORT"
exit 0
fi
cd "$DIR" || exit 1
setsid nohup .venv/bin/python serve/server.py --engine strata \
--config "$DIR/strata-iq3_s.json" --port "$PORT" \
--host "$HOST" --api-key "$API_KEY" \
> "$DIR/strata-server.out" 2>&1 < /dev/null &
i=0
until curl -s --max-time 2 "http://127.0.0.1:$PORT/health" | grep -q '"loaded": true'; do
i=$((i + 1))
[ "$i" -gt 60 ] && echo "still loading... (first start takes longer)" && i=0
sleep 5
done
echo "ready: http://127.0.0.1:$PORT"
;;
stop)
pkill -f "serve/server[.]py" 2>/dev/null
pkill -f "engine-cuda12/strat[a]" 2>/dev/null
wait_port_free && echo "stopped"
;;
restart)
"$0" stop && "$0" start
;;
status)
if port_busy; then
echo "running: $(curl -s --max-time 2 http://127.0.0.1:$PORT/health)"
else
echo "not running"
fi
;;
*)
echo "usage: $0 start|stop|restart|status"
exit 2
;;
esac
它解决的问题:
- 重启端口竞态 :旧引擎释放 55GB 锁页内存要数秒,
stop后立刻start会撞 "port 8080 already in use"。脚本里等待端口释放(最多 60s)再启动。 - pkill 自匹配 (踩了两次):
pkill -f "setup.py --yes"会匹配到自己这条 bash 命令的命令行,把自己的 shell 杀掉、静默无输出。模式里加字符类:pkill -f "serve/server[.]py"。 - setup 重写配置 :任何
setup --setup之后都要复查 spec/context 是否还在。 - server.py 会自动重启静默引擎(#481):想真正停机要连 server.py 一起停,只杀引擎会被拉起来。
10. 最终部署情况
| 项 | 值 |
|---|---|
| 模型 | Qwen3.8-Flash-Next GSQ-RCO IQ3_S(全 512 专家) |
| 引擎 | Strata 0.1.42,CUDA 12.8 本地编译(sm_86,驱动 570 免升级) |
| 布局 | 双 3090 层切分 K=20,专家常驻"显存+锁页内存",SSD 零读取 |
| 上下文 | 262144(原生),KV int8 留显存 |
| 草稿 | MTP,--spec 6 |
| 多模态 | 开启(mmproj + CUDA 视觉编码器) |
| 服务 | 0.0.0.0:8080 + 强制 API key(600 权限密钥文件) |
| 成绩 | 短上下文 113-122 tok/s;100K→70;213K→66.6;预填充 ~3,500 tok/s |