Ultra X7 358H 笔记本电脑,Qwen3.6-35B-A3B,之前测试的速度在实际项目中翻车了,实际项目中只有 13 tok/s
AI
2026-10-07
3882 字
19 分钟

在文章《Ultra X7 358H 笔记本电脑,寻找本地用于编码的最强大模型,实测 21.0 tok/s,上下文 256K》中, 本地部署 Qwen3.6-35B-A3B 之后,当时 AI 测试和我自己的简单测试,都把测试结果指向了一个值:20+ tok/s。

后来,在文章《Ultra X7 358H 笔记本电脑,本地三个大模型接入 DeepSeek Harness 桌面版的过程与方法》中, 我又把本地部署的大模型接入了 Deepseek Harness Desktop。

于是,我便用 Deepseek Harness Desktop + Qwen3.6-35B-A3B,准备让它来分析一个实际的博客项目,分析项目完毕,Deepseek Harness Desktop 显示的输出速度,才 13 tok/s!

这着实让我火热的心凉了一大截 :)

老办法,出了问题,有不理解的地方,问 AI。

下面是 AI(Deepseek Harness Desktop + Deepseek V4.1 Flash)对这个问题的回复:

上下文深度对生成速度的影响:实测说明#

结论:23~25 tok/s 只在”浅上下文”下成立,它是最好情况而非常态。
在真实 agentic 会话里,每加深 1K 上下文约损失 0.39 tok/s(约 1.9%);
到 20~30K 深度 时解码速率降到 11~16 tok/s,界面显示(含预填与工具等待)更低至 5~7 折。

这不是故障、不是配置错误、不是换页、也不是机器变热——代价落在逐 token 的解码阶段:
解码时每生成一个 token 都要对已累积的全部上下文做注意力,而预填是批量处理的、几乎不受深度影响。
同一批测试里预填速率始终稳定在 297~331 tok/s,与深度无关,这一条同时排除了换页和配置问题。

⚠️ 但要避免一个想当然的解释:这个代价不能归因于”KV 搬多了”——
按实测有效带宽算账,KV 字节搬运只占额外耗时的约 1%(见 4.2 节)。

  • 记录日期:2026-10-07
  • 机器:Intel Core Ultra X7 358H / Intel Arc B390 iGPU(SYCL,无独显)/ 31.55 GiB 板载内存
  • 模型:Qwen3.6-35B-A3B(UD-Q3_K_M,15.46 GiB,MoE 激活约 3B)
  • 服务:llama-server -ngl 99 -c 262144 -np 1 --cache-type-k q8_0 --cache-type-v q8_0 --jinja,端口 8081
  • 触发本调查的现象:在 DSH 里用它分析项目,实测只有 13 tok/s,而此前验证值是 23 tok/s

问题#

同一个模型、同一台机器、同一个服务,出现了两个相差近一倍的速度数字:

来源数值场景
此前受控测试23~25 tok/s空上下文、单轮短问答
用户在 DSH 里的实测13 tok/sagentic 分析项目、多轮工具调用

需要回答三个问题:

  1. 13 是不是测量错误或被别的东西拖累?(比如 DSH 把预填时间算进去了)
  2. 23~25 是不是过于乐观的”最好情况”?
  3. 如果是深度造成的,代价有多大、能否避免?

分析过程#

先排除服务与配置问题#

/props 与 /slots 显示:n_ctx = 262144、is_processing = false、state 空闲,服务健康。 命令行参数与预期完全一致(-c 262144 -np 1、KV q8_0、--alias qwen36)。

→ 配置和内存分配都没问题,且 /props 的 n_ctx 证明 256K 窗口确实生效了。

从 DSH 会话日志里取真实数据#

会话日志是 append-only 的多帧 zstd:

~\.dsh\sessions\--D-sites-bigfoxme--\session-7a02256b-...\session.v4.jsonl.zstd

踩了一个坑:zstdDecompressSync 和流式 API 都只解出第一个 frame(80811 B 里只得到 199 B)。 解法是按 zstd 魔数 28 B5 2F FD 切帧后逐帧解压 → 79 帧 → 149 条记录。

日志里拿到了三个关键事实:

记录内容含义
request/context{"contextWindow":262144}256K 声明已生效
request/headermaxTokens: 8192,route local-qwen36走的就是本地路由
assistant/message ×18每步 usage,cacheReadTokens 最高 26391真实上下文深度 ≈ 26.4K

另外两点值得记录:

  • 18 条助手消息全部带 reasoning,而日志里 enable_thinking 出现 0 次。 说明 pi-ai 适配器没有传递该参数的能力(它的采样字段只有 temperature/maxTokens/stop), DSH 里思考始终是开着的。不过该会话的推理都很短(42~480 字符),不是速度主因。
  • 第 1 步撞上 503 Loading model 并重试 3 次——模型还在加载就开始提问。

第一次算速率算错了#

我先用 输出token ÷ 整步耗时 算,得到 2.26~11.42 tok/s。但这个指标是错的,因为记录顺序是:

step/start → assistant/message → tool/call → tool/result → step/end

step/end 出现在 工具执行之后,所以整步耗时里混进了读文件、跑命令的时间。必须先剔除。

意外发现:会话里已经存了逐 token 的时间戳#

DSH 的流记录里保存了 token 级到达间隔:

{"type":"text-chunks","time0":1791517980564,"index":1,
"dt":[60,62,63,64,61,66,62,62,61],
"texts":["好的",",","让我","先","了解","项目","整体","结构","。","\n\n"]}

dt 就是每个 token 的毫秒间隔(首个 407 ms 是首字延迟)。稳态约 60~63 ms/token = 16.4 tok/s—— 这是最直接的证据,不需要任何推算。

同时按时间线把每步拆成三段:

阶段平均耗时
预填(step/start → 首个 chunk)6.8 s
模型期(首 chunk → 末 chunk)15.2 s
工具执行 + 其他(末 chunk → step/end)4.5 s

于是得到三个层次的数字,对应不同的问法:

口径数值说明
纯解码(逐 token dt)16.4 tok/s模型真实生成速度
模型期整体11.93 tok/s含 block 切换(推理→正文→工具调用)的间隙
输出 ÷ 整步耗时7.13 tok/s含工具执行与预填

→ 用户看到的 13 落在”模型期整体”这一档,是真实的、可解释的。

活体服务上的受控深度扫描#

为把”深度”从其他因素里分离出来,在正在运行的服务上做扫描(不加载模型、不重启), 读服务端自己返回的 timings.predicted_per_second(权威解码速率):

  • 请求体写入 JSON 文件后用 curl --data-binary @file 发送(避免 PowerShell 吃掉引号)
  • 填充文本由同一句话重复构成,深度递增 → 后续请求能复用 KV 前缀缓存
  • 每点生成 62~221 token,样本量足够

测试结果#

深度曲线(同一服务、连续测量)#

有效上下文解码速率每 token相对浅深度
~26 token21.00 tok/s47.6 ms—
~3.3K19.74 tok/s50.7 ms−6%
~6.6K19.31 tok/s51.8 ms−8%
~13.2K15.91 tok/s62.9 ms−24%
~17.3K14.24 tok/s70.2 ms−32%
21K(另测,400 token 样本)10.72 tok/s93.3 ms−49%

拟合出的经验关系(量级参考,非精确公式):

解码速率 ≈ 21.0 − 0.00039 × 上下文token数 (每 1K 深度约 −0.39 tok/s,−1.9%)

关键对照:预填完全不受深度影响#

同一个 21K 深度的请求里,两项指标对比:

阶段21K 深度浅深度是否随深度退化
预填 prompt_per_second331.0 tok/s339 tok/s(llama-bench pp4096)❌ 基本不变
解码 predicted_per_second10.72 tok/s24.32 tok/s✅ 掉一半

整个扫描区间内预填稳定在 297~331 tok/s。这一条是整个调查里最有价值的证据, 因为它一次性排除了两个竞争解释:

  • 排除换页:权重被换出页面会同时拖垮预填与解码,而预填纹丝不动。
  • 排除配置/量化问题:两者用的是同一套参数、同一块 KV。

排除”机器变热/变慢”#

我做了重活(21K 预填 + 多次长生成)之后立刻复测浅深度:

时刻浅深度解码
重活之前20.5 / 21.4 tok/s
重活之后24.32 tok/s(111 token 样本)
随后用 bench_running_server.py 复测23.17 tok/s(min 21.98 / max 24.36)

浅深度稳定回到 23~24,所以不是热降频、也不是”跑久了变慢”。

顺带发现:前缀”形状”变化要付一次重预填#

bench_running_server.py 的第一次请求首字延迟 17.29 s,第二次只有 0.23 s。 原因是从上一个 21K 填充前缀切换到新提示时,KV 缓存前缀不匹配、需要重新预填。

这解释了真实会话里 平均 6.8 s 的预填耗时:agent 每一步的提示都在变化, 一旦工具结果插在历史中段或发生压缩,前缀就分叉,重预填代价立刻出现。

交叉验证:另一个模型的独立测量#

同一台机器上,dense 27B(Qwen3.8-27B)由另一次实验测得:

深度生成速率
d=05.90 tok/s
d=81925.03 tok/s(−15%)
d=163844.26 tok/s(−28%)

两个不同架构的模型、两次独立测量,16~17K 深度处的衰减量级一致(−28% vs −32%), 说明这是通用现象而非某个模型的怪癖。

结论#

两个数字都对,只是测的不是同一件事#

数值成立条件
23~25 tok/s空/浅上下文(<5K)、单轮短问答、无工具调用 —— 最好情况
13 tok/s26K 上下文、agentic 多轮、每步含预填与工具等待 —— 真实会话的常态

机制:代价定位在”逐 token 的注意力”,具体成因未确立#

已经确定的(实测):代价只出现在解码阶段,预填几乎不受影响。 这一条排除了所有”与阶段无关”的解释——如果是权重被换出页面,预填会同步变慢,而它没有。

一个反直觉的算账(说明不能用”KV 搬运量”解释):

项数值
浅深度每 token 耗时41.1 ms
权重读取 15.46 GiB ÷ 41.1 ms → 有效带宽≈ 404 GB/s
21K 深度每 token 需读的 KV21K × 10.6 KiB ≈ 222 MiB
这些 KV 按 404 GB/s 搬运的理论耗时≈ 0.6 ms
21K 深度每 token 的实际额外耗时52.2 ms

KV 字节搬运只解释了额外耗时的约 1%。所以不能说”解码是内存带宽瓶颈”—— 同理,它也不是”上下文越长、要读的数据越多”这种简单模型。

因此更可能的机制(推断,未单独验证):长上下文下注意力算子本身的效率问题。 本模型的注意力配置是 2 个 KV 头、head_dim 256、且只有 10/40 层有注意力, 长序列下算术密度低、访存模式分散(KV 工作集远超 iGPU 末级缓存), 实际表现可能远低于算力上界。另外该服务的启动参数未显式指定 -fa (flash attention 走 auto),是否影响长序列注意力效率也未验证。

这些只是候选解释,我没有单独验证。 本报告确证的是现象与其量级,不是内核层面的成因—— 这也意味着”换个后端/打开 flash attention 能不能改善”是一个尚未验证的可行方向,值得单独实验。

排除的解释#

候选解释判定依据
换页 / 显存不足❌ 排除预填在 21K 深度仍是 331 tok/s
机器变热 / 跑久变慢❌ 排除重活后浅深度回到 24.32 tok/s
配置或量化错误❌ 排除参数与预期一致,KV 已是 q8_0
DSH 测量算错❌ 不成立13 与逐 token dt(16.4)和模型期(11.93)自洽:显示值已经排除了预填与工具等待。若把工具时间也算进去只有 7.1
思考模式开着⚠️ 次要确实始终开着(适配器无法传 enable_thinking),但该会话推理仅 42~480 字符
模型加载未完成⚠️ 仅第一步第 1 步 503 Loading model 并重试 3 次,只影响首轮
上下文深度✅ 主因单一变量扫描:21.00 → 14.24 tok/s(0→17.3K)

该期待多少#

会话上下文纯解码(服务端 predicted_per_second)DSH 界面显示(≈模型期,已排除预填与工具)若自己算”输出÷整步”(含工具)
空 ~ 5K20 ~ 24 tok/s16 ~ 1910 ~ 14
10 ~ 15K15 ~ 17 tok/s12 ~ 137 ~ 9
20 ~ 30K11 ~ 16 tok/s9 ~ 13(该会话实测 13)6 ~ 8(该会话实测 7.13)

换算系数来自那个会话的实测分解:纯解码 16.4 → 模型期 11.93(×0.73)→ 输出÷整步 7.13(×0.43)。 三个数字都对,问的是不同的东西——讨论速度时务必先说明用哪个口径。

一处诚实的不一致#

曲线外推 26K 应得约 11 tok/s,而真实会话在 26K 处实测 16.4 tok/s(高约 1.5 倍)。 两者方向一致但绝对值有差距,原因未查明。可能因素:填充文本高度重复、 两次测量的机器状态不同、真实提示含工具 schema 结构不同。

→ 因此上面的曲线应作为量级参考,而不是精确公式。

能做什么#

手段有效性说明
控制会话长度(新任务开新会话;长了就压缩)✅ 唯一真正有效深度是主因,26K 会话比 5K 会话慢约一倍
换更省的 KV 量化❌ 无意义已经是 q8_0,且预填证明不是内存问题
调采样参数 / 换量化档位❌ 无意义与逐 token 的 KV 读取成本无关
关掉思考模式⚠️ 有限DSH 侧无接口可传,且该会话推理本来就很短
把服务端 -c 从 262144 降到 65536⚠️ 仅省内存释放约 2 GiB KV(256K 是 2720 MiB,64K 只要 680 MiB),但不能提速解码;且必须同时下调配置里的 contextWindow,而压缩公式在 W < 约 73K 时的行为尚未验证
等模型加载完成再开始提问✅ 避免首轮浪费该会话第 1 步因此重试 3 次

方法学教训(可复用)#

  1. 不要用”输出 token ÷ 整步耗时”衡量模型速度。 在 agent 框架里,这个区间包含工具执行。 要么用逐 token 间隔(dt),要么用服务端的 predicted_per_second。
  2. 小提示的 prompt_ms 不能当预填速率。 15 个 token 的预填里,固定开销占大头 (实测 69.7 ms/token vs 19 个 token 时的 24.8 ms/token)。要测预填必须给足够长的提示。
  3. 读多帧 zstd 日志要按魔数切帧,zstdDecompressSync 与流式 API 都可能只解第一帧 (本次 80811 B 只解出 199 B)。
  4. 调用 llama-server 时把 JSON 写进文件再 curl --data-binary @file。 PowerShell 的 --data-raw $body 会吃掉 JSON 里的双引号,表现为服务端返回 parse error at line 1, column 2('{m'),极易被误判成服务端故障。
  5. 区分”能加载”与”能跑得快”:/props 报告的 n_ctx 只说明窗口分配成功, 与解码速率无关。
  6. RemoteDisconnected 先查进程是否被重启(本次服务在 11:50:07 被换过 PID), 不要一上来就怀疑请求格式。

附:复现方法#

深度扫描(不加载模型,对满载机器安全):

Terminal window
# 1) 生成不同深度的请求体(填充文本用同一句重复,便于前缀缓存复用)
# 每份 body 只改填充长度,问题固定
# 2) 依次发送,取服务端 timings
curl.exe -s -N -X POST http://127.0.0.1:8081/v1/chat/completions `
-H "Content-Type: application/json" --data-binary "@$env:TEMP\sw_16384.json"
# 关注返回里的:prompt_per_second(预填)、predicted_per_second(解码)、cache_n(命中缓存)

浅深度稳态(已在工作区,可直接用):

Terminal window
D:\AI_Tools\unsloth\run.cmd scripts\bench_running_server.py 8081 repeat 3

读会话日志里的逐 token 间隔:按 zstd 魔数 28 B5 2F FD 切帧逐帧解压 ~\.dsh\sessions\<工作区>\<会话id>\session.v4.jsonl.zstd, 然后取 assistant/message.data.stream 里 type == "text-chunks" 的 dt 数组。

附:数据来源与可信度#

数据来源可信度
深度曲线(5 点)、填充/解码对比活体服务 /v1/chat/completions 返回的 timings实测(可复现)
21K 深度 10.72 tok/s同上,400 token 样本实测
会话深度 26.4K、逐 token 60~63 ms用户会话日志的 usage.cacheReadTokens 与 dt 数组实测(原始记录)
浅深度 24.32 / 23.17 tok/s同一服务复测 + bench_running_server.py实测
预填在深度上不变(297~331 tok/s)同上实测
dense 27B 的 −28%本工作区既有实验(见 显存与内存实测报告.md、Qwen3.6-35B-A3B-编码部署报告.md)既有实测
代价只落在解码阶段、预填不受影响实测对照(21K 深度:预填 331 / 解码 10.7)实测
具体成因(算子效率 / 缓存未命中 / 未开 FA)候选解释;已算账排除”KV 字节搬运”(仅占约 1%)推断,未单独验证
曲线外推与真实会话 1.5 倍差异的原因未查明未知

这篇文章是否对你有帮助?

发现错误或想要改进这篇文章?

在 GitHub 上编辑此页