20K 长上下文崩溃排查:-ub 切块是 Intel 核显 Vulkan 的救命参数

把 supergemma4-26B 当 llama-server 常驻服务用的时候,我撞上了一个硬钉子:16K 上下文稳定,一到 20K 就 HTTP 500,报错是 vk::Queue::submit: ErrorDeviceLost。这篇记录完整的排查过程,以及最终救命的参数——-ub 物理批次切块。

环境:Intel Arc 140T(共享显存已调至 90%,预算 ~21.6GB)/ 23.5GB 内存 / llama.cpp b10621 / supergemma4-26B(15.6GB)。2026-09-02 实测。

核心发现:KV 预分配是启动预占、运行仍爆的根源

不显式指定 -c 时,llama-server 按 n_ctx≈228608 启动一次性预分配 KV(日志 n_ctx_slot = 262144)。KV q8 每 token 约 22.5KB → 预占 ~6.4GB;加上权重 15.6GB,启动完成进程已 ~21.6GB,贴着 90% 预算上限

但「预占」只覆盖 KV,不覆盖 Flash Attention 的 prefill 临时工作区——后者按请求实际长度运行时分配。20K 一次灌入时临时区达数 GB,此时预算已满 → 驱动任何新分配失败 → 连 createComputePipeline 都报 ErrorOutOfDeviceMemory这就是「q8 了还差 3-4G」的根源

关键参数表(b10621 验证)

参数作用本机实测
-ub N(默认 512)物理批次,决定 prefill 切块大小-ub 128/256 分块 → 20K 稳定;-ub 4096 不分块 → 必崩
-b N(默认 2048)逻辑批次上限-b 65536 无收益,real 切块仍由 -ub 决定
-c N上下文 / KV 预分配上限-c 65536 省 ~4.5GB KV,给 prefill 临时区留余量
--n-cpu-moe N前 N 层 MoE 专家权重放 CPU省显存,但生成降速 ~30%,不建议
--no-mmap关 CPU 侧 mmap需双横线(-no-mmap 报 invalid argument)
-fa onFlash Attention必须,否则输出全进 reasoning_content
-rea off关闭思考块必须,否则 content 为空
-ctk/-ctv q8_0KV 量化省一半内存开启
--dynamic-kv-cacheKV 动态增长本版本不存在,invalid argument

不分块(-ub 4096)单次 20K:全部失败

组合20K 结果
-ub 4096 -fa on❌ ErrorDeviceLost
+ --no-mmap❌ 连接重置 / 崩
+ --no-mmap + -c 65536(KV 缩 64K)❌ 进程崩溃
+ GGML_VK_PREFER_HOST_MEMORY=1❌ 进程崩溃
+ --n-cpu-moe 12❌ 连接重置(attention 仍全量 GPU)

结论:Intel 核显 Vulkan 后端的 device-local 内存管理在大 ubatch prefill 时不稳定(驱动级 bug,社区 issue #18808 / #18946 同型),非参数可解。20K 必须 -ub ≤ 256 分块。

分块实测(20K,prompt=19827,-rea off)

配置prefillgenerationwall(20K)结论
-ub 256(含 -fa on)69.3 t/s11.1 t/s286 s✅ 推荐
-ub 12865.0 t/s9.8 t/s305 s✅ 稳定但略慢
-ub 64k(-b 65536)无收益,不采用
-c 65536 + --n-cpu-moe 4速度无差异,仅省显存

生成 11.1 t/s(20K 长上下文预热后略低于 short 的 21.4 t/s,属长上下文 attention 开销)。30K 亦通过,且命中前缀缓存 cached=19670。

-ub / -c 上限扫描:为什么小批次反而能跑

两种内存维度要区分:-ub 控制「并发瞬时峰值」(临时激活 buffer),-c 控制「常驻容量」(KV 缓存),两者挤占同一 21.6GB 预算。

配置测试长度-ub结果prefill (t/s)
-c 6553632K (31727)25647.4
-c 6553632K (31727)28846.9
-c 6553632K (31727)320❌ 500/崩
-c 6553632K (31727)384❌ 500/崩
-c 6553632K (31727)512❌ 500/崩
-c 3276816K (15827)25681.7
-c 3276816K (15827)51284.0
-c 3276816K (15827)1024❌ 500/崩

结论:缩 -c 省 KV 确能提升 -ub 上限。32K 的 -ub 稳定上限 ≈ 288-320 之间,-ub 1024 在此 iGPU 上必崩。

最终定型配置

目标最佳配置prefill
32K 上下文-c 65536 -ub 25647 t/s
16K + 高填充-c 32768 -ub 51284 t/s

省内存三大杠杆(可叠加):① -c 缩 KV;② -ctk/-ctv q8_0 减半;③ -ub 调小控 FA 临时区。调优只动 -ub,-b 保持默认或 ≥-ub 即可(-b 只有设 ≤-ub 时才生效,变小块更慢)。

--n-cpu-moe 实测:固定开销,不建议

--n-cpu-moeprefill (t/s)gen (t/s)相对基线
0(全 GPU 基线)~11619.2~19.4100%
4(前 4 层 → CPU)~11213.5~13.6~70%
8(前 8 层 → CPU)~10012.9~13.3~68%
12(前 12 层 → CPU)~10012.8~13.3~68%

只要开了 --n-cpu-moe(哪怕只卸 4 层),生成速度就从 ~19 t/s 掉到 ~13 t/s;卸到 12 层几乎无进一步下降。原因是 CPU 卸载引入固定管线同步开销,一旦启动,所有层都受此瓶颈约束。Intel iGPU(UMA)下尤其伤——GPU↔CPU 共享内存控制器,同步开销远大于独显 PCIe。对你 20K 长上下文的场景,仅省显存、对预填充无帮助、生成反而降速 30%,不建议使用

一句话总结:Intel 核显跑 20K+ 上下文,核心就一句:-ub 256 分块,-c 65536 缩 KV,q8 量化,-fa on + -rea off 拉满。

Serving supergemma4-26B as a resident llama-server, I hit a wall: 16K context was stable, 20K threw HTTP 500 with vk::Queue::submit: ErrorDeviceLost. This post documents the whole debugging journey and the lifesaving flag — -ub physical batch chunking.

Env: Intel Arc 140T (shared memory raised to 90%, ~21.6GB budget) / 23.5GB RAM / llama.cpp b10621 / supergemma4-26B (15.6GB). Tested 2026-09-02.

Key finding: KV preallocation explains 'preallocated yet still OOM'

Without an explicit -c, llama-server preallocates KV at startup for n_ctx≈228608 (log: n_ctx_slot = 262144). At q8, KV is ~22.5KB/token → ~6.4GB preallocated; plus 15.6GB of weights, the process sits at ~21.6GB after boot — right at the 90% budget cap.

But 'preallocation' covers only KV, not Flash Attention's prefill scratch space — that's allocated at request time by actual length. A single 20K feed needs GBs of scratch while the budget is already full → any new allocation fails → even createComputePipeline reports ErrorOutOfDeviceMemory. That's why 'even q8 was 3-4GB short'.

Key flags (verified on b10621)

FlagPurposeMeasured here
-ub N (default 512)Physical batch; sets prefill chunk size-ub 128/256 chunked → 20K stable; -ub 4096 unchunked → crash
-b N (default 2048)Logical batch cap-b 65536 no gain; real chunking still driven by -ub
-c NContext / KV preallocation cap-c 65536 saves ~4.5GB KV, leaving headroom for prefill scratch
--n-cpu-moe NFirst N MoE expert layers on CPUSaves VRAM but cuts decode ~30%; not recommended
--no-mmapDisable CPU-side mmapNeeds double dash (-no-mmap → invalid argument)
-fa onFlash AttentionRequired; otherwise output goes to reasoning_content
-rea offDisable thinking blocksRequired; otherwise content is empty
-ctk/-ctv q8_0Quantized KV halves memoryOn
--dynamic-kv-cacheDynamic KV growthDoesn't exist in this build; invalid argument

No chunking (-ub 4096), single 20K feed: all fail

Combination20K result
-ub 4096 -fa on❌ ErrorDeviceLost
+ --no-mmap❌ connection reset / crash
+ --no-mmap + -c 65536 (KV cut to 64K)❌ process crash
+ GGML_VK_PREFER_HOST_MEMORY=1❌ process crash
+ --n-cpu-moe 12❌ connection reset (attention still full GPU)

Conclusion: Intel iGPU Vulkan's device-local memory management is unstable on large ubatch prefills (driver-level bug; community issues #18808 / #18946 are the same shape). Not fixable by flags. For 20K, you must chunk with -ub ≤ 256.

Chunked measurements (20K, prompt=19827, -rea off)

Configprefillgenerationwall(20K)Verdict
-ub 256 (with -fa on)69.3 t/s11.1 t/s286 s✅ recommended
-ub 12865.0 t/s9.8 t/s305 s✅ stable but slower
-ub 64k (-b 65536)no gain, dropped
-c 65536 + --n-cpu-moe 4no speed change, saves VRAM only

Decode at 11.1 t/s (slightly under the short-context 21.4 t/s — long-context attention overhead). 30K also passed, with prefix cache hitting cached=19670.

Scaling -ub / -c: why smaller batches actually run

Two memory dimensions: -ub controls the instantaneous concurrency peak (scratch activation buffers), -c the resident capacity (KV cache). Both compete for the same 21.6GB budget.

ConfigLength-ubResultprefill (t/s)
-c 6553632K (31727)25647.4
-c 6553632K (31727)28846.9
-c 6553632K (31727)320❌ 500/crash
-c 6553632K (31727)384❌ 500/crash
-c 6553632K (31727)512❌ 500/crash
-c 3276816K (15827)25681.7
-c 3276816K (15827)51284.0
-c 3276816K (15827)1024❌ 500/crash

Bottom line: shrinking -c (less KV) raises the -ub ceiling. For 32K, the stable -ub cap is ~288-320; -ub 1024 always crashes on this iGPU.

Final recommended configs

GoalBest configprefill
32K context-c 65536 -ub 25647 t/s
16K + heavy prefill-c 32768 -ub 51284 t/s

Three stacking memory levers: ① -c to shrink KV; ② -ctk/-ctv q8_0 to halve it; ③ -ub down to cap FA scratch. Tune -ub only; leave -b default or ≥-ub (-b only bites when ≤-ub, making smaller slower batches).

--n-cpu-moe measured: fixed overhead, not recommended

--n-cpu-moeprefill (t/s)gen (t/s)vs baseline
0 (full GPU baseline)~11619.2~19.4100%
4 (first 4 layers to CPU)~11213.5~13.6~70%
8 (first 8 layers to CPU)~10012.9~13.3~68%
12 (first 12 layers to CPU)~10012.8~13.3~68%

Enabling --n-cpu-moe (even just 4 layers) drops decode from ~19 t/s to ~13 t/s; going to 12 layers barely adds loss. CPU offload imposes a fixed pipeline sync cost that, once present, bottlenecks every layer. It hurts extra on Intel iGPU (UMA) where GPU↔CPU share the memory controller — sync overhead far exceeds a discrete PCIe link. For 20K long-context work it only saves VRAM, doesn't help prefill, and cuts decode 30% — skip it.

TL;DR:For 20K+ context on an Intel iGPU: -ub 256 to chunk, -c 65536 to shrink KV, q8 quantization, plus -fa on and -rea off.
返回文章列表