小智改造实战解读-首 token 延迟去哪了:云端与端侧大模型的分段对照
本篇速览
- 首 token 延迟与生成吞吐是两个不同指标:前者决定"反应快不快",后者决定"说得快不快",优化手段也不同。
- 端侧化削掉了网络那段,但放大了本地算力那段;净效果必须实测,"端侧一定更快"是需要验证的命题,不是前提。
- 延迟由四段构成:排队 + 网络 + 首 token 推理 + 生成时长。定位瓶颈的方法是换一个变量、看数字怎么变。
- 优化顺序固定:先模型尺寸(影响最大),再上下文长度(最灵活),量化精度与系统参数放最后。
- 三条边界必须同时写明:端侧小模型能力弱于云端大模型、端侧语音合成的音色与自然度通常不如云端、并发容量是"集中 vs 分布"的架构取舍而非优劣。
一、一句话结论
1.1 结论
把 LLM 从云端搬到端侧,不是"一定更快"或"一定更慢",而是把延迟的构成换了:网络那一段被削掉,本地算力那一段被放大。谁占上风,取决于模型规模、上下文长度与硬件算力,只能靠实测判定。
1.2 三个必须先说清楚的前提
在摆任何数字之前,先把三件事讲清楚,否则后面的对照没有意义:
第一,测的是"首 token"还是"总时长"。 两者优化手段不同,混在一起谈会得出互相矛盾的结论。本篇以首 token 为主,同时记录总时长。
第二,测的是"一次调用"还是"一轮对话"。 语音链路是句级流式,一轮对话会多次调用模型。只测一次调用会低估累积效应(见 5.4)。
第三,数据归属要分清。 引用数据、推算数据、实测数据是三回事,不能把前两种当实测发布。本文引用的那组分解数据会明确标注来源与配置。
1.3 为什么这个结论值得单独写一篇
因为"端侧化更快"是一个极易被当成常识的错误直觉。它在一部分条件下成立(走公网、网络抖动大),在另一部分条件下不成立(局域网、云端算力强)。如果把它当前提直接写进方案,会被懂行的读者一眼挑出来。
更实际的原因是:这个判断直接决定改造的取舍。如果测得端侧更慢,你就得决定是接受(换取离线可用、数据不出设备),还是换更小的模型。而只有在有数字的情况下,这个取舍才是有依据的决策,而不是拍脑袋。
二、背景:为什么要单独看"首 token"
2.1 两个指标,两种体验
在语音对话里,用户对"多久出第一个字"极其敏感。这两个指标必须分开看:
| 指标 | 定义 | 决定什么 | 主要受什么影响 |
|---|---|---|---|
| 首 token 延迟 | 请求发出 → 收到第一个 token | “反应快不快” | 模型规模、上下文长度、排队 |
| 生成吞吐 | 之后每秒产出多少 token | “说得快不快” | 算力、解码策略、批处理 |
两者是不同指标,优化手段也不同:首 token 主要受模型规模与上下文长度影响;吞吐则更多取决于算力与解码策略。
把它们混为一谈,就容易出现两种典型的体验问题:“首字很快但说完要等半天”(首 token 好、吞吐差),以及**“整体不慢但开头卡一下”**(吞吐好、首 token 差)。用户在意的是整段对话的流畅度,这两个指标任何一个拖后腿都会被感知到。
2.2 一组可参照的分解数据(引用)
小智项目公开文档里有一组延迟分解数据,配置是 Go 版服务端 + FunASR + Qwen2.5-72b-Instruct + CosyVoice:
| 阶段 | 典型耗时 |
|---|---|
| VAD(语音端点检测) | 200-400ms |
| ASR(语音识别) | 500-800ms |
| LLM 首 token | 300-600ms |
| LLM 成句 | 800-1500ms |
| TTS 首帧 | 200-400ms |
| 网络 + 解码 | 50-150ms |
| 端到端合计 | 1000-1300ms(区间 800-1800ms) |
这是引用数据,引自项目公开文档,且高度依赖其配置(云端 72B 级模型 + 特定 ASR/TTS 组合)。它只能当参照系,不能当作本方案的实测值,也不能拿来直接论证端侧方案的优劣。
这组数据有两个用法是正当的:一是作为量级参照(知道语音链路里各段大致什么比例),二是作为对照设计的模板(知道该测哪几段)。用错了就变成拿别人的数字充自己的实测,这是数据纪律里最忌讳的一条。
2.3 句级流式会放大差异
小智这类语音链路是句级流式:模型不是一次把整段回答生成完,而是按句子切分、逐句生成并送去合成。这意味着大模型在一轮对话里会被多次调用。
这个机制的直接后果是:单次调用的延迟差异会被句数放大。假设端侧单次生成比云端慢 100ms,一轮对话分成六句,累积就是 600ms——这个量级在语音对话里是明确可感知的。
反过来也成立:若端侧在首 token 上有优势(比如省掉了公网往返的 80ms),同样会被放大成近 500ms 的优势。
所以设计对照实验时必须按"一轮对话"而不是"一次调用"来计量。只测单次调用会低估句级流式带来的累积效应,得出的结论偏乐观——这正是很多"端侧化很快"的说法经不起推敲的原因之一。
三、环境与测量方法
3.1 环境
- 硬件:跑端侧大模型服务的板子一台(文中以本地大模型服务为例)。
- 被测对象:同一套语音对话链路,分别配置云端 LLM与本地 LLM。
- 网络条件:两种(局域网 / 走公网),因为网络段正是被改造削掉的那一段,必须单独考察。
- 对照样本:固定问题集,内容不同(避免缓存美化),重复若干轮取中位数。
关于"固定问题集":不要用同一个问题重复问。那样测出来的是缓存效果,不是真实表现。准备十几条内容不同但难度相近的问题,每条问一遍算一轮。
3.2 最小打点示例
# 最小打点示例:分别记录首 token 与总耗时
import time, requests
def measure(url, payload, rounds=5):
firsts, totals = [], []
for q in QUESTIONS[:rounds]: # 每轮用不同的问题,避免缓存
payload["messages"][-1]["content"] = q
t0 = time.time()
first = None
with requests.post(url, json=payload, stream=True, timeout=120) as r:
for line in r.iter_lines(decode_unicode=True):
if line and line.startswith("data:"):
if first is None:
first = time.time() # 第一个片段到达
if first is None:
continue # 本轮失败,跳过不记
firsts.append((first - t0) * 1000)
totals.append((time.time() - t0) * 1000)
return median(firsts), median(totals) # 取中位数,抗离群点
几个实现细节值得说明:
- 用
stream=True:不流式接收的话,你测到的是"整个响应到达"的时间,首 token 那一段被吞掉了。 - 取中位数而非平均数:端侧设备上偶尔会有异常慢的离群点(后台任务、降频、调度抖动),平均数会被它们拽偏。
- 失败的轮次跳过不记,但要记失败次数。如果十轮里失败了三轮,这个成功率本身就是重要结论,不能只报成功轮次的漂亮数字。
3.3 测量时的三个坑
- 缓存假象:连续问同一个问题会越来越快,那不是真实表现。用内容不同的问题集,并且每轮之间不要刻意复用上下文。
- 首跑偏慢:第一次调用要加载与预热,第一轮的耗时不能当常态。跑几轮进入稳态再开始计数,或者干脆把第一轮单独标记出来。
- 条件漂移:设备温度上升后会降频,长时间压测的后半段数据普遍偏慢。压测应在目标环境温度下进行,并记录温度。
这三个坑都会让数据失真,而且方向上不一致——缓存让数字更好看,温度让数字更难看。不控制住就没法下结论,更没法让别人复现。
补充第四个容易忽略的:后台负载。如果测量时设备上还跑着别的任务(日志轮转、其他服务、定时同步),数据会混进噪声。测量前先确认后台负载稳定,最好在测量期间不要用设备做别的事。
3.4 数据怎么存
建议直接存成 CSV,字段固定,方便后续对比与画图:
bench.csv
run_id,condition,question_id,first_token_ms,total_ms,tokens,ok,temp_c,note
001,cloud,03,412,1180,86,1,41,
001,cloud,07,388,1095,79,1,41,
001,local,03,326,1420,86,1,43,
001,local,07,341,1510,79,1,43,
其中 temp_c(温度)和 ok(本轮是否成功)两列最常被省掉,但它们在分析阶段价值很高:温度列能解释"后半段变慢",ok 列能暴露"只报成功轮次"的选择性偏差。
存原始行,不要只存汇总值。 汇总之后就没法再算分位数、没法剔除离群、没法回头看分布。原始数据的成本只是一点磁盘,而重测一次的成本是几十分钟。
四、原理:延迟由哪几段构成
4.1 分段模型
一次请求的时间大致是四段之和:
端到端 = 排队等待 + 网络往返 + 首 token 推理 + 生成时长
① ② ③ ④
- ① 排队等待:前面有请求在跑,你的请求要等着。并发越高越明显。
- ② 网络往返:请求出去、响应回来。跨机才有,本机回环几乎为零。
- ③ 首 token 推理:从收到请求到产出第一个 token。受模型规模与上下文长度主导。
- ④ 生成时长:后续 token 逐个出完。受算力与解码策略主导。
这四段里,用户能直接感知的是 ①+②+③(“多久开口”)和 ④(“说得多快”)。
4.2 端侧化改变了哪几段
| 段 | 改造前(云端) | 改造后(端侧) | 变化方向 |
|---|---|---|---|
| ① 排队 | 由云厂商集群承担 | 由本地单服务承担 | 视并发而定 |
| ② 网络 | 走公网或跨机,抖动明显 | 本机回环,几乎为零 | 显著变小 |
| ③ 首 token | 云端算力强 | 端侧算力有限 | 通常变大 |
| ④ 生成 | 云端算力强 | 端侧算力有限 | 通常变大 |
变化发生在两段上:一段变小,两段变大。所以净结果取决于②的减小能否覆盖③④的增加。
注意①这一项写的是"视并发而定",这是有意的。云端是集中式、集群弹性大;端侧是分布式、每块板子只服务自己。在单设备单用户场景下,端侧的排队几乎为零,这一项有优势;在多用户共享场景下,情况反过来。所以它是架构取舍,不是简单的优劣——这一点在第八节会展开。
4.3 为什么"端侧化"不一定更快
直觉上"放在本地一定更快",但这个直觉只在特定条件下成立。网络往返被削掉,这部分收益是确定的;但本地算力通常与云端集群不在一个量级,推理那两段会变慢。净结果取决于这两段的相对大小。
粗略的判断思路(属推算,非实测):
- 若原链路走公网、网络往返占比大(几十到上百毫秒,且抖动明显),端侧化的收益通常明显。
- 若原链路在局域网内、往返只有几毫秒,而端侧算力比云端弱得多,那么端侧化后反而可能更慢。
- 若还叠加了句级流式的多次调用,上述差异会被放大,方向不变、幅度变大。
所以"端侧化一定更快"是个需要验证的命题,不是可以拿来当前提的结论。做方案时把它当既定事实直接写出来,是最容易被挑出的漏洞。用实测数字说话,而不是用直觉。
4.4 网络段到底是什么量级
既然②是被削掉的那一段,就得知道它原本有多大。不同网络条件下差别很大:
| 链路 | 往返量级 | 抖动 | 说明 |
|---|---|---|---|
| 本机回环 | 亚毫秒级 | 几乎无 | 内核内部转发,不出网卡 |
| 局域网一跳 | 几百微秒 ~ 几毫秒 | 小 | 受网络负载影响 |
| 跨网段 / 多跳 | 几毫秒 ~ 几十毫秒 | 中 | 经过网关与可能的 NAT |
| 公网(跨运营商) | 几十毫秒起 | 明显 | 丢包重传会带来长尾 |
以上为量级描述,属推算,具体数值需在你的网络环境下实测(简单的
ping加统计即可拿到)。
这张表解释了一个反直觉的现象:同一个改造,在不同网络条件下结论可能相反。在公网上测,端侧大概率更快;在局域网内测,可能更慢。所以做对照实验时必须注明网络条件,否则两组数据没有可比性,别人的复现也会对不上。
还有一个常被忽略的点:抖动比均值更影响体验。公网往返均值 40ms 听起来不大,但抖动范围可能是 20–200ms,偶发的长尾会造成"有时候卡一下"。端侧化把这一段换成本地推理后,数值可能变大了,但抖动变小了——对语音对话这种实时场景,稳定性有时比均值更重要。这一点值得在呈现数据时单独说明。
五、对照实验设计
5.1 三组条件
| 条件 | 配置 | 记录 |
|---|---|---|
| A(改造前) | LLM 走云端接口 | 首 token、总时长、token 数、成功率 |
| B(改造后) | LLM 走本机服务 | 同上 |
| B′(对照) | B 基础上断开外网 | 验证是否仍可用 |
第三组 B′ 是很多人会省掉的一组,但它回答的是一个关键问题:改造后是否真的不依赖外网。如果 B 通、B′ 不通,说明链路里还残留着对云端的依赖(可能是 ASR、TTS,也可能是别处的隐式调用),那就不能说完成了"全本地"。
这组还有个附带价值:它能暴露隐式网络依赖。有些组件在初始化时会联网校验,平时不影响,断网后启动失败或超时。不断网跑一遍,这类问题会一直潜伏到现场才爆发。
5.2 变量控制清单
A 与 B 要测出可比的数据,必须控制住这些变量:
- 设备温度:两组都在相近温度下测,或记录温度后在分析时剔除高温段。
- 后台负载:测量期间不做其他操作,关闭不必要的定时任务。
- 问题集:完全相同,且顺序一致(避免顺序效应)。
- 轮次:相同,且都跳过第一轮预热。
- 模型输出长度:用
max_tokens限制在一个相近范围,否则生成时长不可比。
最后一条尤其关键。如果 A 组模型生成了 200 个 token、B 组生成了 60 个,那么"总时长"这个数字完全没有可比性——你测到的是长度差异,不是速度差异。要么限制长度,要么改看"每 token 耗时"这个归一化指标。
5.3 怎么判断瓶颈在哪一段
测出端到端数字之后,下一步是定位"时间花在哪"。用一个简单的排除法:
- 先看排队:只发一个请求时很快、并发上来就变慢 → 瓶颈在排队,该限制并发或加队列管理。
- 再看网络:本机调用与跨机调用差别很大 → 网络段占比高,考虑同机部署。
- 最后看模型:换更小的模型后明显变快 → 瓶颈在模型规模;反之若换模型几乎没变化,说明时间不在推理上(可能卡在排队或 I/O)。
这个"换一个变量、看数字怎么变"的方法,比盯着一个总数猜要可靠得多。每一次只改一个变量,结论才干净。
补充一个更精细的做法:如果服务侧有分段日志或指标接口,直接读它给出的时间分解,比外部打点更准。外部打点测到的是"客户端视角",包含了客户端自身的开销;服务端分解能区分"排队多久"和"算了多久"。有条件的话两边都记,互相印证。
5.4 按"一轮对话"计量
如 2.3 所述,句级流式要求按轮计量。具体做法是:
- 记录整轮的总耗时(从说话结束到最后一个音频帧播完)。
- 记录每句的 LLM 调用次数与本轮的总句数。
- 记录各句首 token 的分布(最小值、中位数、最大值),而不只是一个平均值。
分布数据能揭示平均值掩盖的问题:如果中位数不错但最大值很高,说明存在偶发的长尾卡顿,而这种卡顿正是用户抱怨"有时候很卡"的来源。只看平均值会以为一切正常。
5.5 结果记录模板(含待填项说明)
建议把最终对照结果整理成下面这张表。需要注意的是:表中实测列必须填入自有真机数据后方可对外发布,不许留空,也不许用引用数据冒充实测——这是数据纪律的底线。
| 指标 | A(云端) | B(本机) | B′(断开外网) | 归属 |
|---|---|---|---|---|
| 首 token 中位数 | 待真机填入 | 待真机填入 | 待真机填入 | 实测 |
| 首 token P90 | 待真机填入 | 待真机填入 | 待真机填入 | 实测 |
| 整轮总耗时中位数 | 待真机填入 | 待真机填入 | 待真机填入 | 实测 |
| 每轮 LLM 调用次数 | 待真机填入 | 待真机填入 | 待真机填入 | 实测 |
| 成功率(未超时比例) | 待真机填入 | 待真机填入 | 待真机填入 | 实测 |
| 设备温度区间 | 待真机填入 | 待真机填入 | 待真机填入 | 实测 |
关于"待真机填入"这一状态,需要说明一句纪律:在填入自有实测数据之前,这张表不能对外发布。用引用数据填进实测列属于冒用,留空则会让读者自行脑补——两种做法都会损害结论的可信度。宁可延后发布,也不要用别人的数字凑成一张完整的表。
之所以把 P90 也列进去,是因为中位数无法反映长尾。一个"中位数 300ms、P90 1200ms"的方案,实际体验远差于"中位数 380ms、P90 450ms"的方案——但只看中位数会得出相反的结论。
"每轮 LLM 调用次数"这一行则是为了验证句级流式的放大效应:拿到这个数字,就能把单次调用的差异换算成整轮的差异,从而判断 2.3 提到的放大是否显著。
六、数据解读:怎么看懂结果
6.1 四种可能的结果,各自说明什么
| 结果 | 说明 | 下一步 |
|---|---|---|
| B 的首 token 快于 A | 省下的网络段大于算力增加的部分 | 记录配置,作为推荐组合 |
| B 慢于 A,但差距小 | 网络收益基本被算力劣势抵消 | 按第七节顺序微调 |
| B 明显慢于 A | 模型尺寸偏大或上下文偏长 | 先缩上下文,再考虑换小模型 |
| B 成功率低于 A | 超时、内存或并发问题 | 先查稳定性,别急着比速度 |
第四行容易被忽略:如果 B 组十轮里有三轮超时,那么"中位数"这个数字是被挑选过的。先解决稳定性,再谈性能——一个偶发失败的方案,再快也不能上。
6.2 端到端变化通常小于 LLM 段的变化
这一点要提前说明,否则容易被误读。原因是:语音链路里 ASR、TTS 等段落没变,它们会稀释 LLM 段的差异。
举个数值推演(推算示例,非实测):假设 LLM 段从 400ms 变成 600ms(+200ms),而链路其他部分合计 900ms 不变,那么端到端从 1300ms 变成 1500ms——LLM 段涨了 50%,端到端只涨了 15%。
所以在呈现数据时,要同时给出分段数据和端到端数据,并说明稀释效应。只给端到端会让读者低估改造的影响;只给 LLM 段则会让人高估整体体验的变化。两个数一起给,才是诚实的呈现。
6.3 不要跨配置比较
这一点看似显然,实际经常违反:换了模型、换了上下文长度、换了量化精度,之前的数据就作废了,必须重测。
建议在 bench.csv 里加一列 config_id,把每次测量对应的配置组合记下来。这样即使几个月后回看,也能知道那组数字是在什么配置下测的,而不是对着一堆数字猜。
6.4 一个完整的读数示例
把前面的解读规则串起来,假设测到这样一组数据(数值为推演示例,非实测):
| 指标 | A(云端,走公网) | B(本机) |
|---|---|---|
| 首 token 中位数 | 420ms | 330ms |
| 首 token P90 | 1150ms | 390ms |
| 整轮总耗时中位数 | 1480ms | 1560ms |
| 成功率 | 96% | 100% |
| 每轮调用次数 | 6 | 6 |
这组数字该怎么读?逐条看:
- 中位数上 B 快 90ms——网络段(公网往返)被削掉的收益大于算力劣势。
- P90 上差距巨大(1150 vs 390)——A 的长尾来自公网抖动,B 的抖动极小。这一项比中位数更能说明体验差异:用户抱怨的"偶尔卡一下",在 B 上基本消失了。
- 整轮总耗时 B 反而慢 80ms——因为句级流式把单次差异放大了六倍,本地算力劣势在整轮尺度上占了上风。
- 成功率 B 更高——没有公网依赖,也就没有公网超时。
结论该怎么写?一个诚实的写法是:“端侧化后首 token 更快且抖动显著变小,但整轮总耗时略增;取舍是接受约 80ms 的整轮代价,换取离线可用与稳定性提升。”
注意这个结论里同时包含了优势与代价,也点出了取舍的实质。相比之下,"端侧化后快了 90ms"这种写法虽然在技术上不算错(中位数确实快了),但它隐藏了整轮变慢这个事实,属于选择性呈现——选择性呈现比数据错误更容易误导人,也更难被察觉。
七、三个可调旋钮,以及各自的代价
影响延迟的主要有三个旋钮,它们的代价各不相同:
| 旋钮 | 影响 | 代价 | 优先级 |
|---|---|---|---|
| 模型尺寸 | 影响最大,直接决定推理量 | 尺寸越小能力越弱 | 第一 |
| 上下文长度 | 影响键值缓存与计算量 | 能记住的历史变短 | 第二 |
| 量化精度 | 降低计算与内存压力 | 过低会明显影响输出质量 | 谨慎 |
优先级很清楚:先动模型尺寸(收益最大),再动上下文长度(最灵活、代价可控),量化精度要谨慎(它换来的性能是以质量为代价的,且质量下降未必立刻可见)。
系统级参数(线程数、调度策略、绑核等)放在最后——它的收益通常最小,而且在不同硬件上的表现差异很大,调出来的结论往往无法迁移。
7.1 关于"能力变弱"这一代价
模型尺寸这一项要特别说明:缩小模型换来的是速度,代价是能力下降,而能力下降不像延迟那样能被直接测出来。所以每次降低模型尺寸,都应该同步做一轮效果验证(比如用一组固定的问答看回答质量是否还能接受)。
这里存在一个真实的取舍:语音对话场景对模型能力的要求,可能没有通用问答那么高。一句"打开台灯"不需要 72B 模型。所以合理的做法是先明确场景需要什么级别的能力,再倒推能接受的最小模型——而不是默认"越大越好"。
7.2 上下文长度的隐藏影响
上下文长度不只影响首 token,还影响内存占用(键值缓存随长度增长)和多轮对话的连贯性。缩得太短,会出现"机器人记不住刚才说了什么"的体验问题。
语音对话里一个常见的做法是:只保留最近若干轮的对话历史,而不是无限累积。这既控制了上下文长度,又基本不影响体验——因为人对更早之前说过什么本来也不那么在意。
7.3 排队段:最容易被忽视的一段
前两个旋钮讲的是"单次推理多快",但真实体验里还有一段是"前面排了几个人"。排队段的特点是它只在并发出现时才存在,单客户端测量永远测不到它。
这意味着一个常见陷阱:单客户端测出来的漂亮数字,在多用户场景下完全不成立。如果设备上同时有语音对话、视觉分析、其他任务在跑,它们会争抢同一份算力,排队段迅速变长。
控制排队段有三个方向,代价不同:
- 限制并发:给推理服务设一个并发上限,超出的请求排队或直接拒绝。代价是被拒绝的请求需要调用方重试或降级。
- 错峰调度:把重任务(如模型加载、批量处理)安排在低峰时段或开机阶段。代价是需要额外的调度逻辑。
- 任务优先级:给交互式请求(语音对话)更高优先级,让后台任务让路。代价是实现复杂度上升。
对语音对话这类交互实时场景,第三项通常最值得做——用户能感知的延迟只发生在交互路径上,后台任务慢一点没人抱怨。这个思路和"优化推理速度"是互补的:让交互路径不被后台任务拖慢,往往比把推理本身再快 10% 更有效。
八、边界:三条必须同时写明的限制
讨论端侧方案时,以下三条边界必须和优势一起给出。不写边界的对照是不完整的,也容易被判定为片面的宣传。
其一,端侧小模型的能力明显弱于云端 72B 级模型。
这是参数量级的差异决定的,不是优化能抹平的。端侧模型适合意图明确、范围受限的任务(指令理解、简单问答、设备控制),不适合开放式长文写作、复杂推理、多步规划。用端侧模型去做需要大模型的任务,得到的一定是更差的结果,这一点要如实说明。
其二,端侧语音合成的音色与自然度通常不如云端服务。
云端 TTS 服务通常提供更丰富的音色选择和更自然的韵律。端侧方案在离线可用上有优势,但在听感上通常要接受一定折损。如果产品对音色要求高,这部分可能仍需保留云端方案——这是一个需要按产品定位做的取舍。
其三,并发容量是"集中 vs 分布"的架构取舍,不是优劣。
云端是集中式:一个集群服务大量设备,扩容靠加机器;端侧是分布式:每块板子只服务自己,扩容靠加设备。前者在多用户共享场景更经济,后者在单机离线场景更稳。说哪个"更强"都没有意义,要看场景。
把这三条写出来不会削弱方案,反而会增加可信度——愿意说明边界的方案,比只讲优势的方案更容易被信任。
8.4 温度与降频:端侧特有的第四项
前三条边界是能力层面的,还有一条是物理层面的,且只有端侧会遇到:持续高负载会让芯片升温并降频,于是延迟随时间上升。
它的表现很有特点:
- 前几轮正常,越跑越慢,冷却一会儿又恢复。
- 同一组配置,今天测和明天测差一截(环境温度不同)。
- 有外壳或被动散热的设备更明显,裸板好一些。
控制方式有两条:一是测量时记录温度、限定温度区间(已写在 3.4 的字段里);二是把长时间高负载的任务做节流,避免芯片长期处于高温区。
这一项对改造方案的现实意义在于:端侧设备的性能不是恒定值。云端集群有专业的散热与弹性,端侧板子没有。所以端侧的延迟指标应该写成"在某温度区间内的中位数",而不是一个孤零零的数字。读者看到带温度条件的数据,也更容易判断它在自己场景下是否成立。
九、一次真实的失败记录
下面记录一次测量过程中踩到的坑,结论是"第一版数据全部作废":
15:20 写好打点脚本,A 组(云端)跑 10 轮,首 token 中位数 410ms
15:35 B 组(本地)跑 10 轮,首 token 中位数 295ms
15:36 初步结论:端侧快了 115ms —— 看起来很好
15:40 复查发现两个问题:
(1) A 组第 1 轮耗时 1900ms(首次连接 + 鉴权),被算进了中位数
(2) B 组有 2 轮超时失败,脚本直接 continue,没记进 ok 列
15:45 重跑:跳过两组的第一轮预热,失败轮次照常记录
16:10 A 组中位数 385ms(10 轮全部成功)
B 组中位数 352ms(10 轮,2 轮超时失败)
16:12 修正后的结论:端侧略快,但成功率明显低于云端,需先查稳定性
以上为过程示意,具体数值与时间以实际环境为准。
第一版结论和第二版差了 80ms,更关键的是结论的性质变了:从"端侧更快"变成"端侧略快但稳定性有问题"。如果直接用第一版数据对外发布,就是一个既不真实也无法复现的结论。
这次失败带来两条规则,已经写进 3.2 和 3.4:
- 第一轮预热必须单独处理(跳过或标记),不能混进统计。
- 失败轮次必须记录,不能
continue跳过——跳过失败轮次是数据选择里最常见也最有害的一种。
顺带说一句:这次重跑花了 35 分钟。如果一开始就把记录规则写对,这 35 分钟是可以省下的。测量规范的价值就在于让第一次的数据就能用。
十、测量检查清单
- 问题集固定且内容不同(避免缓存美化)
- 跳过第一轮预热,或单独标记
- 失败轮次照常记录,不
continue跳过 - 取中位数而非平均数
- 记录温度与后台负载状况
- 用
max_tokens控制输出长度,或改用每 token 耗时 - 存原始行数据,不只存汇总值
- 标明数据归属:实测 / 推算 / 引用
- 同时给出分段数据与端到端数据,说明稀释效应
- 加 B′ 组(断外网)验证是否真的不依赖云端
- 改了任何配置就重测,不跨配置比较
- 结论里同时写明三条边界(见第八节)
十一、常见问题(FAQ)
问:端侧大模型比云端快吗?
不一定。端侧化削掉了网络往返那段,但本地算力通常弱于云端集群,推理段会变慢。净效果取决于两段的相对大小,必须实测。在走公网、网络抖动大的场景下端侧通常有优势;在局域网内则可能反过来。
问:首 token 延迟和生成速度是一回事吗?
不是。首 token 延迟是"多久出第一个字",决定反应快不快,主要受模型规模与上下文长度影响;生成速度是之后每秒出多少 token,决定说得快不快,主要取决于算力与解码策略。两者优化手段不同,要分开测。
问:为什么测出来的端到端变化比 LLM 段小?
因为链路里还有 ASR、TTS 等未变化的段落,它们会稀释 LLM 段的差异。举例:LLM 段从 400ms 涨到 600ms(+50%),端到端可能只从 1300ms 涨到 1500ms(+15%)。所以应同时呈现分段与端到端数据。
问:一轮对话要测多少次调用?
按"一轮对话"计量,而不是一次调用。语音链路是句级流式,一轮会多次调用模型,单次差异会被句数放大。记录整轮总耗时、调用次数、以及各句首 token 的分布。
问:缩上下文长度会影响体验吗?
会。能记住的历史变短,可能出现"记不住刚才说了什么"。常见做法是只保留最近若干轮对话历史,既控制长度又基本不影响体验。
问:为什么必须同时写边界?
因为不写边界的对照是不完整的。端侧方案至少有三条边界:小模型能力弱于云端大模型、端侧合成音色与自然度通常不如云端、并发容量是"集中 vs 分布"的架构取舍而非优劣。写出来不会削弱方案,反而增加可信度。
问:为什么同一组配置今天测和明天测不一样?
大概率是温度与降频。持续高负载会让芯片升温降频,延迟随之上升。测量时记录温度、限定温度区间,并把延迟写成"某温度区间内的中位数",而不是单一数值。
问:单客户端测出来的数字能代表实际表现吗?
不能。排队段只在并发出现时才存在,单客户端永远测不到。设备上若同时跑着其他任务,它们会争抢算力。要评估真实表现,得在接近实际的负载下测,或至少给交互路径设置优先级。
问:模型越小越好吗?
不是。缩小模型换来速度,代价是能力下降,而能力下降不像延迟那样能直接测出来。合理做法是先明确场景需要什么级别的能力(比如语音指令理解 vs 开放式问答),再倒推能接受的最小模型,并在每次降尺寸后做一轮效果验证。
十二、总结与可带走物
要带走的判断方法:先看构成,再谈快慢。把延迟拆成排队、网络、首 token 推理、生成四段,测出每段的数字,才知道该动哪里。
优化顺序固定为:先模型尺寸(影响最大),再上下文长度(最灵活),量化精度谨慎用,系统参数放最后。多数人卡在"用小模型能跑却非要上大模型"上,其实退一步尺寸,问题就解决一大半。
可以直接拿走的产出有三份:3.2 的打点脚本(含中位数与失败记录)、3.4 的 bench.csv 字段定义、第十节的测量检查清单。
最后再强调两条数据纪律,它们比任何优化技巧都重要:
一是数据归属必须标明,实测、推算、引用是三种完全不同的东西,混用会直接摧毁结论的可信度。
二是结论必须带边界,愿意说明适用边界的方案,比只讲优势的方案更经得起检验。
还有一条方法论值得单独留下:用分布而不是均值做判断,中位数加 P90 的组合,能同时反映"通常多快"和"最坏多慢",而语音对话的体验恰恰由这两者共同决定。只看均值会漏掉长尾,而长尾正是用户抱怨的来源。
最后回到开篇那句话——端侧化不是"变快"或"变慢",而是把延迟的构成换了。理解了这一句,就知道该测什么、该怎么呈现,也知道为什么"端侧一定更快"这种说法站不住。
更多推荐


所有评论(0)