Qwen3.8-27B 实测:RTX 3090 跑 Q4 拿 58 tok/s,Jetson Thor 跑 BF16 只有 4.1 tok/s
Qwen3.8-27B 是个尴尬的尺寸:BF16 权重 51 GB,单张 24 GB 的 RTX 3090 装不下;而 Jetson AGX Thor 有 122 GB 统一内存,装得下,却慢到让人怀疑人生。
这篇文章记录同一个模型在这两台机器上的实测结果,以及为什么 14 倍的差距几乎可以用两个数字算出来。
测试环境
| RTX 3090 工作站 | Jetson AGX Thor | |
|---|---|---|
| GPU | GeForce RTX 3090 | NVIDIA Thor(sm110) |
| 显存 / 统一内存 | 24 GB GDDR6X | 122 GB LPDDR5X |
| 标称内存带宽 | 936 GB/s | 273 GB/s |
| 驱动 | 580.173.02 | 580.00 / JetPack R38.4.0 |
| 系统 | Ubuntu 22.04.5 LTS | Linux 6.8.12-tegra |
| 推理框架 | Ollama 0.32.14 | vLLM 0.25.2(sm110 自编译) |
| 模型格式 | GGUF Q4_K_M,17 GB | Safetensors BF16,51.1 GiB |
模型本身:Qwen3.8-27B,27.3B 稠密参数,qwen35 架构(线性注意力 + 每 4 层一个全注意力的混合排布),原生 262K 上下文,带 460M 的视觉塔,支持 thinking。
为什么两边用了不同的量化
这不是我懒得对齐,是硬件决定的,值得先说清楚,否则后面的数字会被误读。
3090 侧只能跑 Q4。 BF16 权重 51 GB,24 GB 显存装不下,没有第二种选择。Q4_K_M 压到 17 GB,剩 7 GB 放 KV cache。
Thor 侧只能跑 BF16。 我把两台 Thor 都翻了一遍,ollama 的 manifest 目录里没有 qwen3.8 的任何 GGUF,docker 里也没有另一份 ollama 实例。机器上现成的是 HuggingFace 的 BF16 权重(18 个 safetensors 分片,52 GB)。要做同量化对比就得再下一份 17 GB 的 GGUF,而用现成的 BF16 恰好能回答另一个更有意思的问题:Thor 那 122 GB 统一内存,装得下大模型这件事本身值多少钱?
所以下面的对比是「两台机器各自能做到的最好情况」,不是同条件对照实验。
实测结果
四个场景,每个单并发跑 3 次、4 并发跑 2 轮,temperature=0,最多 256 token 输出。
解码速度(tok/s,越高越好)
| 输入长度 | 并发 | RTX 3090 (Q4) | Thor (BF16) | 倍数 |
|---|---|---|---|---|
| 136 tok | 1 | 58.2 | 4.1 | 14.2× |
| 760 tok | 1 | 46.1 | 4.1 | 11.2× |
| 2872 tok | 1 | 54.0 | 4.1 | 13.2× |
| 760 tok | 4 | 46.2 | 4.0 | 11.6× |
首 token 延迟(秒,越低越好)
| 输入长度 | 并发 | RTX 3090 | Thor |
|---|---|---|---|
| 136 tok | 1 | 0.63 | 0.52 |
| 760 tok | 1 | 0.76 | 0.89 |
| 2872 tok | 1 | 1.30 | 2.86 |
| 760 tok | 4 | 2.79 | 3.21 |
聚合吞吐(tok/s,全部并发请求合计)
| 场景 | RTX 3090 | Thor |
|---|---|---|
| 760 tok,单并发 | 26.9 | 4.0 |
| 760 tok,4 并发 | 34.5(1.28×) | 15.1(3.74×) |
解读一:两台机器都贴着带宽 roofline 跑
自回归解码每生成一个 token,就要把全部权重从内存里读一遍。所以理论上限是:
解码速度上限 = 内存带宽 ÷ 权重大小
代进去:
- Thor:273 GB/s ÷ 54.9 GB ≈ 5.0 tok/s,实测 4.1,达到理论值的 82%
- 3090:936 GB/s ÷ 17 GB ≈ 55 tok/s,实测 58.2,约等于甚至略超 roofline
3090 超过 100% 不是测出了永动机,而是分母偏大:那 17 GB 里含 460M 的视觉塔和 clip projector,纯文本解码根本不读;另外 Ollama 的 eval_duration 计数器只统计解码循环,不含框架开销。把视觉部分刨掉按 16.3 GB 算,实测对应约 949 GB/s,正好压在 936 GB/s 的标称值上。
结论是:两台机器都不是「算力不够」,是「读权重读不过来」。 这也解释了为什么 Thor 的 4.1 tok/s 在四种输入长度下纹丝不动——上下文从 136 涨到 2872,解码速度一点没变,因为瓶颈从头到尾就是那 51 GB 权重的搬运。
差距可以拆成两个乘数:
带宽比 936 / 273 = 3.4 倍
权重比 54.9 / 17 = 3.2 倍
预期差距 3.4 × 3.2 ≈ 11 倍 实测 11~14 倍
也就是说,14 倍里有大约一半来自量化(能不能压到 Q4),另一半来自内存带宽(GDDR6X vs LPDDR5X)。剩下的零头来自 vLLM 跑在 --enforce-eager 下没开 CUDA Graph,以及两边计数口径不同。
解读二:Thor 输在单流,扳在并发
单看 tok/s,Thor 被打得很难看。但把并发拉到 4,情况变了:
- 3090 从 26.9 涨到 34.5,只有 1.28 倍
- Thor 从 4.0 涨到 15.1,涨了 3.74 倍,几乎是线性
原因还是内存。解码是带宽绑定,但多个请求可以共用同一次权重读取——把 4 个请求batch 在一起,权重只读一遍,算 4 份输出,边际成本几乎为零。Thor 有 53 GB 的 KV cache 空间(vLLM 报告的最大并发是 79 路),batch 想开多大开多大。
3090 涨不上去,是因为 17 GB 权重加上 KV cache 已经把 24 GB 显存占满了,Ollama 在这个配置下并行度上不去,4 个请求基本在排队。
所以这两台机器的定位其实很不一样:
- 3090 适合单人交互:一个人聊天、写代码,58 tok/s 是流畅的
- Thor 适合多路后台:4.1 tok/s 对人来说太慢,但 15 tok/s 的聚合吞吐喂给 4 路异步任务(批量打标、日志分析、多摄像头理解)是能用的,而且它能装下 3090 装不下的模型
解读三:prefill 是另一套账
预填充阶段是计算绑定而非带宽绑定,所以规律和解码完全不同。Thor 在 2872 token 输入下的 prefill 约 1000 tok/s,TTFT 2.86 秒——比 3090 慢,但没有慢到 14 倍。短输入下 Thor 的 TTFT(0.52s)甚至比 3090(0.63s)还快。
有个坑要提醒:Ollama 报的 prefill 数字不能直接信。 我的测试里同一个 prompt 重复跑 3 次,第二次开始命中了 prefix cache,prompt_eval_duration 只统计新算的 token,于是 2832 token 的 prefill 被报成 11227 tok/s。这个数字是假的。TTFT 的 min(0.51s)和 mean(1.30s)差了 2.5 倍,就是缓存命中的痕迹。vLLM 侧同样默认开了 prefix caching,但 TTFT 三次都稳定在 2.83~2.86s,没看出缓存收益。
踩到的三个坑
1. Thor 上「内存」和「显存」是同一块,vLLM 的启动检查会拦你。
第一次起服务我按习惯写了 --gpu-memory-utilization 0.72,直接被拒:
ValueError: Free memory on device cuda:0 (68.15/122.82 GiB) on startup is
less than desired GPU memory utilization (0.72, 88.43 GiB).
Thor 上这个参数是按全机 122 GB 的比例算的,不是按空闲部分算。当时机器上 ollama 常驻着一个 44 GB 的模型,实际可用只有 68 GB。把 ollama 里的模型全 ollama stop 掉,再把参数降到 0.53,才起得来。
2. 统一内存的分配泄漏比独显严重得多。
测完 Qwen 我删掉 vLLM 容器,结果 110 GB 内存没有回收——全机进程 RSS 加起来才 2 GB,nvidia-smi 看不到任何计算进程,也没有残留的 containerd shim,但 free 就是显示 111 GB 被占。而且这不是强杀导致的:另一个 SGLang 容器用 docker stop 正常退出,同样留下 85 GB 收不回来。独显上进程一死显存就回来了;Thor 上 CUDA 分配泄漏会直接表现成系统内存被吃光,观察两分半没有任何回落迹象,最后只能重启整机。在 Thor 上规划大模型服务,要把「换一个模型跑」当成一次重启来安排。
3. 别假设「装过了」就等于「能跑」。
我一开始以为四台机器(rog3090、giga3090、thor、thor-jd)都装好了这个模型。实际翻下来:只有 rog3090 的 ollama 里有 qwen3.8:27b;giga3090 根本没装 ollama,GPU 还被另一个任务占着 15.8 GB;两台 Thor 的 ollama manifest 里都没有这个模型,只有 thor 上有一份 HF 的 BF16 权重。四台里能立刻开跑的只有一台。
该怎么选
| 你的场景 | 建议 |
|---|---|
| 单人本地助手,要打字跟得上 | RTX 3090 + Q4 量化。58 tok/s,够用且便宜 |
| 模型大到 24 GB 装不下 | Thor,它能装下,慢也比跑不起来强 |
| 多路异步后台任务 | Thor + vLLM,把 batch 开起来吃聚合吞吐 |
| 既要大模型又要快 | 这两台都不是答案,该看 A100/H100 那一档 |
如果非要在 Thor 上追单流速度,唯一有效的方向还是减小权重:换 FP8 或 NVFP4 量化,权重砍到 25 GB 左右,按 roofline 就能到 8~10 tok/s。这是带宽绑定场景下唯一的杠杆——优化 kernel 没用,因为算力本来就没跑满。
测试方法
两边都用流式接口,记录客户端 TTFT,解码速度取服务端计数器(Ollama 用 eval_count/eval_duration,vLLM 用 usage 里的 completion_tokens 配流式时间戳)。每个场景先跑一次预热,单并发 3 次取平均,4 并发 2 轮。temperature=0,max_tokens=256。
一个口径差异要说明:vLLM 侧模型输出了 thinking 段落(平均 163~196 token),Ollama 侧关掉了 thinking(平均 44~49 token)。这影响输出的 token 总数,但不影响每 token 的解码速度,而后者才是本文比较的对象。