本篇速览

  • 首 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 首 token300-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 怎么判断瓶颈在哪一段

测出端到端数字之后,下一步是定位"时间花在哪"。用一个简单的排除法:

  1. 先看排队:只发一个请求时很快、并发上来就变慢 → 瓶颈在排队,该限制并发或加队列管理。
  2. 再看网络:本机调用与跨机调用差别很大 → 网络段占比高,考虑同机部署。
  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 中位数420ms330ms
首 token P901150ms390ms
整轮总耗时中位数1480ms1560ms
成功率96%100%
每轮调用次数66

这组数字该怎么读?逐条看:

  1. 中位数上 B 快 90ms——网络段(公网往返)被削掉的收益大于算力劣势。
  2. P90 上差距巨大(1150 vs 390)——A 的长尾来自公网抖动,B 的抖动极小。这一项比中位数更能说明体验差异:用户抱怨的"偶尔卡一下",在 B 上基本消失了。
  3. 整轮总耗时 B 反而慢 80ms——因为句级流式把单次差异放大了六倍,本地算力劣势在整轮尺度上占了上风。
  4. 成功率 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:

  1. 第一轮预热必须单独处理(跳过或标记),不能混进统计。
  2. 失败轮次必须记录,不能 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 的组合,能同时反映"通常多快"和"最坏多慢",而语音对话的体验恰恰由这两者共同决定。只看均值会漏掉长尾,而长尾正是用户抱怨的来源。

最后回到开篇那句话——端侧化不是"变快"或"变慢",而是把延迟的构成换了。理解了这一句,就知道该测什么、该怎么呈现,也知道为什么"端侧一定更快"这种说法站不住。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐