很多人默认「核显不能跑大模型」,理由是共享显存小、带宽低。我不信这个说法,手头正好有台带 Intel Arc 140T 核显的局域网小主机(235h),23.5 GB 内存,于是把一个 7.9B 的 MoE 模型 Ling-3.0-tiny 砸上去实测了一整轮。
先说结论:核显不但能跑,而且快得超出预期。短上下文 prefill 冲到 556 t/s,比同一台机器的纯 CPU 快 8 倍以上。下面是完整数据。
测试环境
| 项目 | 值 |
|---|---|
| 机器 | 235h / 192.168.31.61 |
| CPU | Intel 14C/14T @ 2.0 GHz |
| 内存 | 23.5 GB |
| GPU | Intel Arc 140T (12 GB UMA) |
| 模型 | Ling-3.0-tiny Q4_K_M · 4.49 GB · bailingmoe3 |
| 参数 | 7.89 B total / 1.3 B active |
| 引擎 | llama.cpp b10621 (Vulkan) |
注意这台机器的 CPU 型号被系统抹成了 "Genuine Intel 0000",但 14 核 14 线程、Max 2.0 GHz 是实打实的。核显走 Vulkan 后端,-ngl 99 全量卸载。
第一步:证明核显真的在跑
这一步很重要。llama-bench 输出表格里有一栏写着 backend = Vulkan,但这不能当证据——我实测 -ngl 0(纯 CPU)时那一栏同样写着 Vulkan。必须找硬证据。
| 证据 | 值 |
|---|---|
| 卸载层数 | 25/25 to GPU |
| Vulkan 权重缓冲 | 4464.69 MiB |
| CPU 权重缓冲 | 0.00 MiB |
| KV 缓冲 (q8_0, c=32768) | 114.75 MiB |
| GPU 引擎利用率(压测中) | 116.7% |
| 核显共享内存占用 | 5.76 GB |
最直接的对照是关掉 GPU 卸载跑一遍纯 CPU:
核显带来约 4 倍的 prefill 加速。7.9B 的 MoE 权重只有 4.49 GB,正好落在 Arc 140T 共享显存的甜点区。
核心结果:1K→24K 上下文速度曲线
这是最想拿给别人看的一张表。数据取自服务端 print_timing 权威日志,排除了 HTTP 往返开销。
| 上下文 | prefill | prefill 耗时 | decode |
|---|---|---|---|
| ~880 (1k) | 556 t/s | ~1.6 s | 40.8 t/s |
| 4,096 (4k) | 332 t/s | ~12.3 s | 33.3 t/s |
| 8,192 (8k) | 178 t/s | ~46.0 s | 27.1 t/s |
| 16,384 (16k) | 88.1 t/s | ~186 s | 19.5 t/s |
| 24,576 (24k) | 58.4 t/s | ~419 s | 15.5 t/s |

两条衰减规律
- prefill 近似减半:556 → 332 → 178 → 88 → 58 t/s,每翻一倍上下文速度砍一半。24k 时光 prefill 就要 7 分钟,这是长上下文最大的时间成本。
- decode 平缓得多:40.8 → 15.5 t/s,全程只掉 62%。因为 decode 的瓶颈是 batch=1 下从共享内存流式读权重和 kernel launch 开销,长上下文 attention 只占小头。
端到端体感:24K 输入 + 128 输出 ≈ 7.1 分钟,其中 98% 花在 prefill。反过来说,24k→8k 能把 prefill 从 419 秒压到 46 秒(9 倍),而任何参数调整最多给你 6%——长上下文优先减少输入长度,而不是调参。
为什么这个模型长上下文不崩显存
这是 bailingmoe3 架构的关键设计。它是混合线性注意力:24 层里 18 层走 recurrent state(只占 19.27 MiB),只有 6 层是全注意力 KV。
n_head_kv = [0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1]
↑ 只有 6 层有 KV,其余 18 层是 recurrent state
所以 32k 上下文的 KV 缓存只有 114.75 MiB——f16 也完全放得下,KV 量化在这里根本不是瓶颈。上下文变长对显存的占用极小,prefill 衰减也比纯 Transformer 平缓得多。
参数怎么调:-ub 扫描
我用 8192 token 的真实全量 prefill 扫了 -ub(物理批大小),每点 3 次不同随机前缀取中位数:
| -ub | min | 中位数 | max |
|---|---|---|---|
| 256 | 139.24 | 158.14 | 160.46 |
| 512 | 172.47 | 173.14 | 174.42 |
| 768 | 175.19 | 177.17 | 180.73 |
| 1024 | 180.82 | 183.49 | 192.98 |
| 1536 | 185.82 | 187.51 | 193.72 |
| 2048 | 178.61 | 183.10 | 190.58 |
| 4096 | 179.58 | 181.51 | 187.28 |
- 256 → 512 提升最大(+9.5%),这是最小的一块跳板。
- 收益在 1024~1536 见顶(~187 t/s),再往上(2048/4096)反而略降——内存带宽已经饱和,更大的物理批次只增加 FA 临时内存占用。
- 推荐 -ub 1536:8k prefill 187.5 t/s,比 768 快 5.8%,且 24k 全量 prefill 未见 OOM。
线程数和 ngl 也有讲究
| 线程 t | pp512 | tg128 |
|---|---|---|
| 4 | 763.63 | 45.25 |
| 6 | 767.57 | 45.58 |
| 8 | 767.73 | 45.29 |
| 10 | 761.51 | 44.98 |
| 12 | 517.17 | 39.14 |
| 14 | 645.53 (unstable) | 38.41 |
4/6/8/10 基本持平(763~768),但 12/14 明显变差。Vulkan 需要 CPU 线程做提交,线程过订阅反而掉速——这台机器只有 14 核,用 8 线程刚好。
至于 -ngl:这个模型只有 25 层,-ngl 30 就已经全量卸载了,跟 -ngl 99 完全没有差别。25 层模型不需要纠结这个参数。
跟纯 CPU 机器对照
同一台机器的纯 CPU 跑法(-ngl 0)vs 核显,差距比想象中大:
| 指标 | 纯 CPU | 核显 Vulkan | 加速比 |
|---|---|---|---|
| pp512 | 191.3 t/s | 767.7 t/s | 8.2× |
| tg128 | 37.7 t/s | 45.3 t/s | 2.4× |
8 条踩坑记录
1. 别用 llama-bench 的 pp512 代表真实长输入
pp512 跑出 768 t/s,但 8k 真实输入只有 178 t/s,差 4 倍多。prefill 吞吐随长度急剧衰减,基准对比必须用相同长度的实测。
2. 测速必须先确认只有一个客户端
-np 1 单 slot 下并发请求是串行排队的。我曾因一个输出被缓冲的"假死"客户端导致误判——同一个 16k 目标从 6 次变成 12 次,总耗时翻倍。
3. Vulkan 服务不能用 SYSTEM 身份跑
以 SYSTEM 在 session 0 跑时 -c 32768 -ctk f16 直接挂死:进程在、端口 listen、但 /health 无响应。改用登录会话(-LogonType Interactive)后一切正常——GPU 上下文需要交互式会话。
4. PowerShell 5.1 读无 BOM 的 UTF-8 脚本会崩
文件里有中文就按 GBK 读,把字符串截断成非法 token → ParserError,脚本一行都不执行。表现极具迷惑性:日志文件 0 字节、stdout 无任何报错。解决办法:测试逻辑全放 Python 3(原生 UTF-8),.ps1 只做纯 ASCII 启动器。
5. Start-Process 起的子进程会随 SSH 断开被杀
即使加了 -RedirectStandardOutput 也不行。解决办法:把 llama-server.exe 注册成计划任务(-LogonType Interactive),用 Start-ScheduledTask 拉起。任务本身就是服务进程,SSH 断开无影响,6~12 秒就绪。
6. 含空格的路径会让 Start-Process 静默失败
-ArgumentList 不会给含空格的路径加引号。我的模型路径 ...\Default Project\... 就中招了:服务静默启动失败、日志 0 字节。解决办法:用计划任务的 -Argument 字符串手工拼引号。
7. scp 传带空格的路径报 ambiguous target
Windows OpenSSH 的 scp 会报 ambiguous target。解决办法:先传到无空格的 C:\temp\,再远程 Move-Item 到模型目录。
8. 测速前记得破前缀缓存
连续 3 次相同的 8k prompt 会全部命中缓存,测出来 prompt eval time ≈ 1 token/~1s,完全是假的。我的做法:prompt 最前面加 64 位随机前缀破坏公共前缀,并在请求体里显式带 "cache_prompt": false。
最终推荐参数
llama-server.exe -m "C:\path\Ling-3.0-tiny-Q4_K_M.gguf" \
-ngl 99 -t 8 -ctk q8_0 -ctv q8_0 -fa on \
-ub 1536 -b 2048 -c 32768 \
--host 0.0.0.0 --port 8088 -np 1 -lv 3 --no-warmup
- 日常用这套:8k prefill 从 177 提到 187.5 t/s(+5.8%),24k 未见 OOM。
- 长上下文优先减输入长度,而不是调参。24k→8k 能省 9 倍时间,任何
-ub调整最多 6%。 - 短上下文(≤8k)体验最好:8k 输入 + 128 输出 ≈ 51 秒,decode 27 t/s 属于可交互范围。
- KV 量化不是瓶颈:32k 上下文仅 114.75 MiB,追求无损可以用
-ctk f16 -ctv f16,显存依然充裕。
