把 Qwen3.8 Flash-Next 装进 Jetson AGX Thor:四条部署路线、长上下文与 SSD 流式推理实录

测试平台:NVIDIA Jetson AGX Thor Developer Kit,128 GB 统一内存,Jetson Linux R38.4,CUDA 13.0

测试与核对日期:2026 年 9 月 1 日

Qwen3.8 Flash-Next 是一类很容易让人产生错觉的模型:它每个 token 只激活约 6B 参数,看上去仿佛很适合单机部署;可真正下载权重、启动推理引擎之后,才会发现“激活参数少”并不等于“常驻内存少”。

它的主模型约 125B 参数,另有约 51B 参数的 N-gram Embedding 查找表——部署代码与相关项目通常称其为 PLE——完整逻辑规模接近 180B。与此同时,模型还引入 Gated DeltaNet(GDN)、Qwen Sparse Attention(QSA)、Gated Residual 和原生 MTP。它不是把传统 Transformer 做得更大,而是把计算、状态和存储重新分配了一遍。Qwen 官方说明把这种设计称为 Qwen4 架构的早期预览。

这篇文章记录的是一次真实的移植与取舍过程:从 vLLM 的 NVFP4 支持,到 llama.cpp 的 GGUF 路线,再到量化 PLE 与 SGLang SSD-stream。最终目标不是证明某个框架“最好”,而是在 Jetson AGX Thor 的 128 GB 统一内存里,找到一套能够长期运行、支持原生 262K 上下文、具备实际并发能力的服务方案。

先说结论

在这台 Thor 上,几条路线最终呈现出相当清晰的分工:

路线能否运行主要优点主要问题适合场景
vLLM + 普通 NVFP4部分模型可用API 成熟、吞吐框架完整Flash-Next 的 PLE、GDN/QSA 和 SM110 kernel 支持仍在演进跟踪上游、验证 NVFP4 后端
llama.cpp + IQ4_XS GGUF可用部署简单、行为稳定、长上下文参数直观单路约 18–22 tok/s;MTP 对并发不利;超原生上下文需要缩放单用户、桌面助手、兼容性优先
vLLM + NVFP4 PLE勉强可用完整权重约 101.6 GiB,理论上可全部塞入统一内存依赖外部插件和回退 kernel,Thor 实测约 14.5 tok/s,内存余量紧验证“全内存常驻”是否可行
SGLang + NVFP4 + PLE SSD-stream最终采用原生 262K;8 并发实测约 102.5 tok/s;MTP 与 CUDA Graph 可用依赖实验性分支与 Thor 补丁;需要高速本地 NVMe长期 API 服务、多请求并发
全 dense-path NVFP4(WIP)Thor 尚未验证Spark 上单路可达 48–60 tok/s面向 SM121,项目仍在快速变化后续重点观察

最终服务运行在 0.0.0.0:8000,对外保留兼容别名 Qwen/Qwen3.6-27B-NVFP4;这个名字只是客户端兼容层,底层实际模型是 Qwen3.8 Flash-Next SSD-stream,并不代表模型被换回了 Qwen3.6。

Thor 与 Spark 很像,但不是同一块 GPU

Jetson AGX Thor 与 DGX Spark 都是 Arm64、Blackwell、128 GB 统一内存平台,因此很多 Spark 项目看上去只要换个镜像就能搬到 Thor。真正开始运行 CUDA 扩展时,差异才会显现。

Thor 实机报告的 Compute Capability 是 11.0,即 sm_110;DGX Spark 的 GB10 则是 12.1,即 sm_121。NVIDIA 官方计算能力表也将两者明确列在不同档位。这是硬件属性,不会随着 JetPack 或 CUDA 升级而改变。

因此,软件可以逐渐同时支持两者,二进制却不能默认通用:

  • 只包含 sm_121 cubin 的扩展,在 Thor 上会报 no kernel image is available for execution;
  • 如果源码具备通用实现,可以重新编译为 sm_110;
  • 如果 FP4、GDN、QSA 或 MoE kernel 在代码里明确限制为 SM120/SM121,仅重新编译仍然不够,还需要补充 SM110 实现或选择可靠的回退后端;
  • “容器能够启动”只说明 Python 和动态库能加载,不等于每个 CUDA kernel 都能执行。

我们使用的环境没有识别错误:nvidia-smi 返回 NVIDIA Thor, 11.0,PyTorch 返回设备能力 (11, 0),并且安装包包含 sm_110 架构。后面遇到的多数问题,本质上都是新模型、新算子与 Thor kernel 支持之间的时间差。

第一条路:vLLM 能跑 NVFP4,不等于已经吃透 Flash-Next

最初的排障对象其实是 unsloth/Qwen3.8-27B-NVFP4,它不是本文的 180B 级 Flash-Next,但它暴露了 Thor 上 vLLM 镜像的一个基础问题。

ANIMA 的 vLLM 0.23 镜像会枚举所有量化后端。某些仅在其他平台提供的可选后端,在 Arm64 上导入失败后被记录为 None;后续自动检测却仍然无条件调用:

method.override_quantization_method(...)

于是模型甚至来不及选择可用的 compressed-tensors/NVFP4 路径,就以如下错误退出:

AttributeError: 'NoneType' object has no attribute
'override_quantization_method'

修复方式并不复杂:遍历量化方法时跳过不可用的 None 配置,并让镜像里的补丁脚本具备幂等性。修复后,27B NVFP4 模型在 Thor 上完成了端到端加载:权重占用 21.31 GiB,--max-model-len 128000,KV cache 容量为 685,176 tokens,OpenAI 兼容接口也能正常返回。相关修复已经提交至 RobotFlow-Labs/anima-vllm-thor PR #1。

但这次成功只能证明 Thor 上的 vLLM NVFP4 基础链路可用,不能外推为 Flash-Next 已经解决。Flash-Next 还额外带来了:

  1. 约 51B 参数的 PLE 表;
  2. 混合 GDN/QSA 状态与 KV cache;
  3. 新模型结构对应的权重命名和量化元数据;
  4. 对 QSA、GDN、FP4 MoE、MTP 等专用 kernel 的要求。

我们遇到过 lm_head.weight_scale 与 checkpoint 元数据不匹配,也遇到过 SM110 上缺少具体 kernel 的情况。它们不是简单增加 --gpu-memory-utilization 就能解决的显存不足,而是模型格式、加载器和算子后端三层问题叠加。对于更新如此快的架构,把“能 import”误认为“能高效推理”,往往是最昂贵的误判。

第二条路:llama.cpp,最先稳定跑通的保底方案

llama.cpp 的优势是依赖面更窄,GGUF 量化和内存分配也更透明。使用 Unsloth 的 UD-IQ4_XS 三分片权重和 BF16 mmproj 后,模型可以稳定启动,并将全部可卸载层放到 GPU:

./llama-server \
  --model Qwen3.8-Flash-Next-UD-IQ4_XS-00001-of-00003.gguf \
  --mmproj mmproj-BF16.gguf \
  --n-gpu-layers all \
  --flash-attn on \
  --ctx-size 524288 \
  --parallel 2 \
  --cache-type-k q4_0 \
  --cache-type-v q4_0 \
  --batch-size 1024 \
  --ubatch-size 256 \
  --threads 12 \
  --host 0.0.0.0 \
  --port 8000

500K 上下文并不是免费的午餐

Qwen3.8 Flash-Next 的原生上下文是 262,144 tokens。上面的 524,288 配置使用 YaRN 将窗口放大两倍:

--override-kv qwen4exp.context_length=int:524288 \
--rope-scaling yarn \
--rope-scale 2 \
--yarn-orig-ctx 262144

这能扩大可接受的序列长度,却不等于模型在 500K 位置仍保持与原生窗口完全相同的召回质量。另一方面,--ctx-size 在多 slot 模式下是每个 slot 的上下文规模;KV cache 通常在启动时已经按配置保留,并不是请求走到 500K 时才突然多占一份显存。真正需要担心的是启动时的总预算、临时计算缓冲,以及统一内存被其他进程或文件缓存挤压后的余量。

在 Thor 上,q8_0 KV 更稳妥,但 524K × 2 路的预算过大;改为 q4_0 K/V 后两套 slot 可以装下。代价是 KV 量化误差增加,因此它适合“容量优先”的部署,而不应在没有长上下文质量测试时被描述为无损方案。

MTP:单路略快,并发反而更慢

我们使用独立的 Qwen3.8-Flash-Next-MTP-Q4_K_M.gguf,测试了多个 --spec-draft-n-max。较小的 draft 深度更适合 Thor;继续增大候选数,会把更多时间花在草稿模型和验证上,收益很快转为负数。

在 q4_0 KV、两个 524K slot 的相同配置下:

场景不启用 MTP启用 MTP观察
单请求,160 输出 tokens18.47 tok/s19.76 tok/s平均约提升 7%
两请求同时生成,各 256 tokens31.98 tok/s 聚合21.48 tok/s 聚合MTP 明显拖慢并发

单路 MTP 的接受率看起来并不差,多数测试在 0.66–0.83,平均接受长度约 2.8–3.7;问题在于 Thor 上 draft 计算和验证并非免费。当两个 slot 同时工作时,这部分开销放大,吞吐反而下降。因此 llama.cpp 路线若面向双路并发,最终更适合关闭 MTP;若只服务一个交互会话,可以保留较小的 draft 上限,但应先用自己的提示词分布复测。

测试过程中还发现,混合注意力/递归模型在 MTP 接受 token 越过 EOG 后,会破坏下一轮请求的前缀复用,迫使服务重新 prefill 上一轮答案。问题、复现数据和候选修复已经记录在 llama.cpp issue #28049。截至本文测试日,它仍是开放 issue,不是已经合入主线的 PR。

llama.cpp 最终没有成为常驻服务,不是因为它不可用。恰恰相反,它是当前最稳健、最容易理解的备用方案;只是当目标从“一个人能聊”变成“多个客户端共享 8000 端口”时,它的吞吐上限和 MTP 并发表现不够理想。

第三条路:把 PLE 也量化,能塞下,但不够快

provsalt/Qwen3.8-Flash-Next-NVFP4-PLE-NVFP4 采取了最直接的容量策略:保留 NVFP4 backbone,再把原本约 95.37 GiB 的 BF16 PLE 表压缩为约 26.82 GiB,使完整 checkpoint 从约 170.19 GiB 降至约 101.64 GiB。模型卡给出的目标平台是单台 128 GB DGX Spark。

这套权重并不能由 stock vLLM 直接加载,它依赖外部 qwen38-nvfp4-ple 插件:PLE 保留在 CPU 侧,只把命中的行解码为 BF16。移植到 Thor 后,我们还需要为 QSA、稀疏注意力和 MoE 选择 SM110 可用的实现,部分路径只能回退到 Marlin 或 Triton。

最终它确实可以生成,但表现更接近“证明能跑”:单路生成约 14.49 tok/s,MTP 路径又受到 Thor GDN/MTP kernel 支持限制。模型卡本身也明确说明,其默认验证仅为 4096 上下文、单序列、2 GiB KV cache,未验证长上下文和并发。

更棘手的是内存边界。一次配置稍有扩张,vLLM 就在权重编译完成后报告 Available KV cache memory: -2.39 GiB。统一内存平台没有一堵真正隔离 CPU 与 GPU 的墙:所谓 CPU-offload、GPU 权重、页缓存和运行时工作区,最终都在争用同一组 LPDDR。把 checkpoint 做到 101.6 GiB 并不意味着剩余的二十几 GiB 都能放心交给 KV cache。

这条路线的价值主要在于验证了一件事:PLE 量化确实能够让完整模型进入 128 GB 机器;但如果解码路径需要大量通用回退,它未必比 SSD 流式读取更快。

第四条路:让 47.68 GiB PLE 留在 SSD

Flash-Next 的 PLE 有一个非常特殊的访问模式:每个 token 只查询少量行,不需要扫描整张表。既然如此,把几十 GiB 查找表永久留在内存中并不经济;更自然的做法是让它留在 NVMe,只读取当前需要的页面,并让 I/O 与 GPU 计算重叠。

这正是 SGLang PR #36567 和 garnermccloud/Qwen3.8-Flash-Next-NVFP4-SSD-Stream 的思路:

  1. 从正常权重加载中移除 128 个 PLE shard;
  2. 将其整理为一个 51,200,245,760 字节的只读 sidecar;
  3. 通过 Rust io_uring reader 按 4 KiB 页面读取命中行;
  4. 使用 pinned staging buffer 和独立 CUDA stream 完成 FP8→设备侧转换;
  5. 在模型运行到 PLE 层之前发起读取,尽量隐藏 NVMe 延迟。

这不是传统意义上的“把大模型放进 swap”。流式读取的只有访问稀疏、天然适合查表的 PLE;主模型仍按正常推理路径工作。启动日志显示,47.68 GiB sidecar 只使用两个 16 MiB staging buffer 和 32 MiB 注册页池。这里的 staging_slots=2 是 I/O 双缓冲,不是只能服务两个请求,更不是单并发限制。

需要强调的是,截至测试日,这项工作仍是实验性扩展:SGLang PR #36567 处于开放状态,并且叠加在 Qwen4 模型支持分支上,尚未进入 main。它原先提供的稀疏 GQA 回退面向 SM121;Thor 上需要补充 SM110 后端选择和兼容修改。

单并发不是 SSD-stream 的限制,而是启动器的保守默认值

项目的 Spark 自动配置默认写死了:

max_running_requests = 1
max_mamba_cache_size = 5
cuda_graph_max_bs_decode = 1
max_total_tokens = 32768(早期测试配置)

因此四个客户端同时请求时,调度器只能串行处理,看起来像“SSD-stream 只能单并发”。把调度、Mamba state 和 CUDA Graph 的 batch 上限一起扩大后,吞吐会发生质变。

我们的测试使用固定的短提示词集合,temperature=0,每请求生成 256 tokens,以总输出 tokens 除以墙钟时间计算聚合吞吐。它不是跨硬件标准基准,适合用于比较同一台 Thor 上的配置变化:

服务配置并发请求聚合吞吐
默认单路配置121.56 tok/s
默认单路配置,4 客户端排队430.50 tok/s
真正的 batch 4465.15 tok/s
真正的 batch 8895.90 tok/s
batch 8 + ReplaySSM8102.54 tok/s

同一套 ReplaySSM 配置下,单路约 25.30 tok/s,四路约 68.39 tok/s。日志中的 batch 8 瞬时生成吞吐通常落在 110–123 tok/s;端到端结果略低,因为还包括 prefill、调度和客户端等待。

SSD 的冷启动效应也非常明显。重启后的首轮 batch 8 曾只有约 76.96 tok/s,随后上升到 87.47 tok/s,页缓存与访问热点稳定后达到约 103 tok/s。因此只跑一次就下结论,往往测到的是存储预热,而不是稳定解码能力。

ReplaySSM 为什么有效

Flash-Next 不是纯注意力模型。除了普通 KV cache,还要为 GDN/Mamba 状态保留空间;MTP 在验证草稿 token 时,又需要处理中间递归状态。普通配置下,batch 8 的 intermediate SSM cache 会额外占用约 3.8 GB。

启用 --enable-linear-replayssm-spec 后,服务不再为每条推测路径保存完整中间 SSM 状态,而是保留约 77 MiB 的紧凑回放环形缓冲,并在需要时重放较短的状态变化。最终 batch 8 的状态分配为:

项目实测分配
GDN/Mamba conv state0.07 GB
GDN/Mamba SSM state3.59 GB
intermediate SSM state0 GB
intermediate conv window0.04 GB
ReplaySSM ring约 77 MiB

这既释放了约 3.8 GB,也降低了 MTP 验证的状态搬运压力。最终 MTP 接受长度通常约 2.3–3.0,接受率随提示词在约 0.44–0.66 之间变化。它没有让单路速度翻倍,却让高并发配置同时获得更好的内存余量和聚合吞吐。

长上下文:三个参数不能混为一谈

最终我们没有对 SGLang 服务启用 RoPE/YaRN 缩放,而是保留模型原生的 262,144-token 窗口:

context_length       = 262144   # 单个请求允许的最大上下文
max_total_tokens     = 393216   # 所有运行中请求共享的 KV token 池
max_running_requests = 8        # 同时进入调度器的请求上限

这里最容易出现的误解,是把 max_total_tokens 当成“每个并发都能用这么多”。实际上它是共享池:一个请求最多可走到 262K;如果它已经占用 262K,其他正在运行的请求只能分享剩余约 131K。八个请求都很短时,则可以共同利用整个 393K 池。

实际启动分配如下:

缓存393,216 tokens 的占用
主模型 BF16 K cache4.50 GB
主模型 BF16 V cache4.50 GB
draft K cache0.38 GB
draft V cache0.38 GB

合计约 9.76 GB,折算后每个共享 token 大约需要 24–26 KiB。缓存分配完成、CUDA Graph 捕获结束后,SGLang 仍报告约 10.28 GB 可用统一内存。这足以支持正常波动,但已经不是可以随意再塞一个大型进程的余量。

这套配置验证了 262K 请求上限和 393K KV 池能够成功分配,也通过了短请求与并发稳定性测试;它尚未完成一轮完整的 262K needle-in-a-haystack 或多轮语义召回测试。因此准确的表述是“原生 262K 容量已配置并成功启动”,而不是“262K 质量已经完整验证”。

最终启动配置

最终使用的启动脚本如下。sglang-ssd-stream 自带一组保守默认值,-- 后面的 SGLang 参数会覆盖这些默认值;所以在 ps 或 systemd 状态中可能同时看到 max-running-requests 1 和 8,实际生效的是后置的 8。

#!/usr/bin/env bash
set -euo pipefail

export HF_ENDPOINT="https://hf-mirror.com"
export HF_XET_HIGH_PERFORMANCE="1"
export SGLANG_SSD_STREAM_HOME="/home/nvidia/models/sglang-ssd-stream-runtime"
export SGLANG_SANITIZE_NAN_LOGITS="1"
export MAX_JOBS="1"
export FLASHINFER_NVCC_THREADS="1"
export CMAKE_BUILD_PARALLEL_LEVEL="1"

exec /home/nvidia/.local/bin/sglang-ssd-stream serve \
  --host 0.0.0.0 \
  --port 8000 \
  --context 262144 \
  -- \
  --served-model-name Qwen/Qwen3.6-27B-NVFP4 \
  --max-running-requests 8 \
  --max-mamba-cache-size 33 \
  --cuda-graph-max-bs-decode 8 \
  --max-total-tokens 393216 \
  --chunked-prefill-size 2048 \
  --mem-fraction-static 0.95 \
  --enable-linear-replayssm-spec

max_mamba_cache_size=33 并不是随意挑选的数字。当前实现要求 Mamba cache 容量高于最大并发数的四倍;八路并发至少需要超过 32,因此取 33。若只修改 max-running-requests 而忘记同时扩大 Mamba cache 和 CUDA Graph batch,得到的不是完整并发,有时甚至会在调度或图捕获阶段失败。

服务由 systemd 管理,并启用 Restart=on-failure。模型加载约 4 分钟:实测 load_weight=236.77 s,完整 scheduler 启动约 255.12 s,API 就绪约 264.22 s。对如此大的模型而言,健康检查和服务管理必须接受“分钟级启动”,不能按普通 Web 服务设置十几秒的启动超时。

还有没有更快的路线?

值得关注的是 hn7305/Qwen3.8-Flash-Next-NVFP4-Spark。RadixArk 的基础 NVFP4 checkpoint 主要量化 routed experts,attention、GDN 投影和 shared expert 等 dense path 仍保留 BF16;这个 WIP 项目进一步量化 dense path,希望降低每个生成 token 的内存带宽压力。

其模型卡报告的 DGX Spark 数据很有吸引力:单路约 48–60 tok/s,16 路聚合约 190.7 tok/s。但它也明确标记为 Work in progress,数据来自一台 SM121 Spark,版本间仍在变化。Thor 是 SM110,不能直接假设这些 kernel 与性能能够复现。

这条路线说明,SSD-stream 并不是性能终点。当前 Thor 方案已经把 PLE 的内存问题解决,下一步瓶颈很可能转向 dense path 的 BF16 带宽。若未来上游提供经过验证的 SM110 dense NVFP4 kernel,它可能比继续微调 KV cache 或 speculative 参数带来更显著的提升。

如何选择

如果目标是个人使用、最少补丁和较强的可控性,llama.cpp 仍然是最舒服的选择。关闭并发 MTP、根据实际窗口选择 q8_0 或 q4_0 KV,它能以较少的框架不确定性稳定工作。

如果目标是让多个客户端共享一台 Thor,并且希望保留原生 262K 上下文能力,那么 SGLang + SSD-stream 是本次测试里最均衡的方案。它把真正适合流式访问的 PLE 留在 NVMe,把统一内存让给主模型、GDN 状态、KV cache 和 CUDA Graph;配合 ReplaySSM 后,八路聚合吞吐从约 30 tok/s 的“伪并发”提高到约 102.5 tok/s。

如果目标是追求未来的单路极限,应该继续关注 dense-path NVFP4、SM110 原生 QSA/GDN kernel 以及 SGLang 上游合并进度,而不是继续无止境地压低 KV 精度。KV cache 决定能容纳多少上下文;真正决定每 token 解码速度的,仍是权重带宽、kernel 命中情况与推测解码的有效接受率。

结语

在统一内存设备上部署大模型,最危险的思维方式是只盯着一个数字:权重大小、激活参数、gpu_memory_utilization,或者单次运行的 tok/s。Qwen3.8 Flash-Next 把这个问题展现得尤其彻底:主模型、PLE、KV cache、递归状态、MTP 草稿和 SSD 页缓存分别服从不同的资源规律。

Thor 最终能把它跑好,并不是因为 128 GB 恰好“塞下了 180B”,而是因为我们没有要求所有数据都以同一种方式常驻:高频计算留给 GPU,稀疏查表交给 NVMe,长上下文由共享 KV 池承载,递归验证状态通过 ReplaySSM 压缩,客户端兼容则留在 API 别名层解决。

这大概也是新一代本地大模型部署最值得借鉴的经验:真正有效的优化,通常不是把所有东西再压缩一点,而是先弄清楚——哪些数据必须常驻,哪些数据只需在正确的时刻出现。


参考项目

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。