已经确定要在本地跑 Step Edge 之后,第一个动作不是挑一个量化档位下载下来试,而是把设备的可用内存和运行时的峰值占用量清楚。量化档位决定权重体积,权重体积决定加载后的峰值内存;如果峰值内存已经贴近上限或触发换页,后面的首字延迟和生成速度对比就没有参考价值了。可以先按“内存筛候选、固定输入比精度、同一段文本测延迟”的顺序走一遍,每次换档位只重装一次,而不是每换一次都靠手感判断。
在 Step Edge 端侧部署里,量化档位应当由测量结果倒推:先读可用内存,用权重体积算式筛出能加载的候选集,再用同一组固定输入比较输出差异,并在同一段文本上记录首字时间与生成速度的中位数。适用场景是单机自用、可以反复重装的环境;如果设备内存余量本来就紧,优先保住峰值内存、宁可退一档,不要靠 swap 把低内存档位硬撑起来。
把部署目标拆成内存、延迟、精度三条可测指标
先定清楚测什么,才不会被“感觉还行”带偏。三条指标各给一句话定义和一种落地测法:
- 内存:进程在完成一次完整生成过程中的峰值常驻内存(RSS 峰值),不是刚启动时的占用。测量方式是看
/proc/<pid>/status里的VmHWM,或者用/usr/bin/time -v包住启动命令读Maximum resident set size。 - 延迟:拆成首字时间和持续生成速度两段,前者是从发起请求到第一个 token 返回,后者是首字之后到生成结束的 token 产出速率。测量方式是在客户端打时间戳,同一输入重复若干次取中位数。
- 精度:在固定提示词、固定输入长度、固定生成参数下,各档位输出与高精度档位输出的差异。测量方式是逐档保存完整输出,按场景关心的点逐条比对,而不是看单条回复“像不像人话”。
内存这一步可以先用两条命令建立基线,替换掉你自己的启动命令即可:
grep -E 'MemTotal|MemAvailable' /proc/meminfo
/usr/bin/time -v ./run_step_edge `--model` ./step-edge `--quant` <候选档位> 2>&1 | grep -E 'Maximum resident'
# 进程已经在跑时,读峰值而不是当前值
grep VmHWM /proc/$(pgrep -f run_step_edge)/status用 MemAvailable 而不是 MemFree 做判断更稳妥,因为后者会把可回收的页缓存算成已用。峰值内存要覆盖一次完整生成,包含长上下文时的中间激活,所以测量时把输入长度拉到目标场景的上限再读 VmHWM。
按内存上限先选出可行的量化档位候选集
这一步的目的是缩小试验范围,减少重装次数。从可用内存反推可加载的权重体积,可以先写成一个粗略算式:
可用于权重的字节 ≈ MemAvailable − 运行时框架常驻 − 中间激活峰值 − 系统余量
每权重平均位数 ≈ 可用于权重的字节 × 8 ÷ 模型参数量算出来的“每权重平均位数”一般不会正好落在某个整数档位上,用它去卡一个区间即可:算出来明显高于 8 位,说明高精度档位也在范围内;落在 4 到 8 位之间,就优先试 4 位档和中间档;算出来低于 4 位,要么缩短上下文、要么接受更激进的量化,要么换设备。中间激活峰值随上下文长度增长,这一点需要单独测——按短输入的峰值去选档位,长输入时很容易爆。
先按不超过可用内存的一半左右试算权重体积,再留出回调空间,是个保守起点,具体比例还是要结合运行框架实测的常驻开销确认。候选集通常控制在两到三个档位,超过三个就意味着你还没把内存账算清楚。
用同一组固定输入对比各档位的输出
精度对比要可复现,关键是固定住输入和采样参数。下面是一个可以直接改用的固定题骨架,把正文换成你目标场景里的真实样本,长度保持一致:
系统提示:你是一个信息抽取助手,只输出原文中出现过的信息。
用户输入:把下面这段文本概括为三条要点,并列出其中出现的机构名与日期。
正文:(固定一段约 N 字的文本,各档位使用完全相同的字节内容)
生成参数:temperature、top_p、max_tokens 各档位保持一致
判分方式:逐条比对三条要点是否齐全、机构名与日期是否抄错、是否出现原文没有的内容记录差异时看这几类可观察现象:输出是否被截断、要点数量是否变少、专有名词或数字是否写错、是否出现重复循环、中英文是否混杂。这些都能从保存下来的文本里直接看出来,不需要主观打分。
“够用”的判定要绑定场景:如果目标是摘要或分类,措辞变化可以接受,但关键实体不能丢;如果是抽取结构化字段,要求字段齐全且能被解析器直接解析;如果是开放式对话,容忍度可以放宽。把判定标准写在对比表旁边,后面换档位时才有共同口径。
在同一段文本上测首字延迟与持续生成速度
延迟数据要变成选档位的硬约束,前提是计时点定义一致。建议统一定义三个时刻:发起请求(客户端发出请求的时刻)、首字返回(收到第一个流式片段的时刻)、生成结束(收到结束标记的时刻)。首字时间 = 首字返回 − 发起请求;生成时长 = 生成结束 − 首字返回;生成速度 = 该段产出的 token 数 ÷ 生成时长。
同一输入重复跑若干次取中位数,避免第一次运行的冷启动把结论带偏。下面是一个通用的打点骨架,流式调用换成你所用量化运行时的接口即可:
import time, statistics
def once(send_stream):
t0 = time.perf_counter()
first = None
tokens = 0
for chunk in send_stream():
if first is None:
first = time.perf_counter()
tokens += 1
end = time.perf_counter()
ttft = first - t0
speed = tokens / (end - first) if end > first else float('nan')
return ttft, speed
ttfts, speeds = [], []
for _ in range(5):
a, b = once(send_stream)
ttfts.append(a); speeds.append(b)
print('首字中位数', statistics.median(ttfts))
print('速度中位数', statistics.median(speeds))如果运行时没有返回精确的 token 计数,用字符数近似也可以,但各档位必须用同一种口径,否则速度数字之间不可比。测延迟时把线程数也固定住,否则你测到的是线程配置的差异,不是量化档位的差异。
把结果写进部署配置并记录选择理由
选定档位后,把它和对应的线程参数写在同一份配置里,字段名以你所用运行时为准,下面只是示意位置:
# config.yaml —— 字段名按你的运行框架替换
model:
path: ./step-edge
quant: q4 # 与候选集里的档位命名对齐
runtime:
threads: 4 # 与测延迟时保持一致
context_length: 2048 # 与测峰值内存时的输入上限一致配置旁边配一张对比记录表,和配置一起提交进仓库,换设备或升级运行时之前先复测这三条指标。表至少包含这些字段:
| 字段 | 记录内容 |
|---|---|
| 档位名与权重体积 | 候选集里的档位标识、磁盘上的权重文件体积 |
| 运行参数 | 线程数、上下文长度、可用内存快照 |
| 峰值内存 | VmHWM 读数,注明输入长度 |
| 延迟 | 首字时间中位数、生成速度中位数、重复次数 |
| 精度结论 | 固定题目上的输出差异类型,以及是否满足场景判定标准 |
| 选择理由 | 为什么选这一档、放弃了哪一档、放弃的原因 |
选择理由写成可核查的句子,例如“峰值内存接近可用上限,切到更长上下文时余量不足,因此退一档”;或者“输出在固定题目上丢失机构名,不满足字段抽取要求”。这样的记录能让下一个换设备的人直接看出约束在哪里,而不是重新从零试错。