🧠 第六章 语言模型训练:高效赋能业务场景
📄 一句话概括

同样 1000 段、每段 200 字,三个嵌入模型的向量化耗时: bge-small 2 分钟、nomic 13 分钟、bge-m3 52 分钟,差二十多倍。 而向量存储 1000 段 1024 维只有 3.9 MB。真正贵的是算力,不是存储。

📝 本节和前面几节的关系

上一节用 Page Assist 走完了一遍。 这一节换 Cherry Studio,第一步还是配嵌入模型——但这次先把它的账算清楚。

一、换客户端,换来的是什么

Page Assist 是浏览器插件,它依附于浏览器标签页,关掉浏览器就没了。 Cherry Studio 是桌面客户端,能同时管多个模型、多个知识库、多套提示词。 两者底层做的是同一件事:把问题发给 Ollama,把检索到的片段塞进提示词。所以上一节讲的 k、窗口、 提示词排序,在这里一字不改地适用。 📋 实操清单(要在桌面客户端里点): 第一步,装 Cherry Studio,打开设置里的「模型服务」,选 Ollama,地址填 http://localhost:11434, 点检查。 第二步,在「嵌入模型」那一栏拉取一个模型(ollama pull bge-m3 或界面里点下载),拉完勾选启用。 第三步,回知识库面板新建库,嵌入模型选刚拉的那个。 注意:库一旦建好,换嵌入模型就得重建索引。 第四步,先传一份小文档(几页),确认能检索到,再批量传。别一上来就传几百份——先验证链路。

二、三笔账:算力、存储、质量

选嵌入模型看三个数: 算力(向量化要多久)、存储(向量占多大)、质量(检索准不准)。前两个能算,第三个只能测。 Demo 在 8 核 CPU 上实测了矩阵乘算力,再按「FLOPs ≈ 2 × 参数量 × token 数」推算向量化耗时:

模型 参数 维度 fp16 体积 1000 段耗时
bge-small-zh 24 M 512 48 MB 2 分钟
nomic-embed-text 137 M 768 274 MB 13 分钟
bge-m3 568 M 1024 1136 MB 52 分钟
三个模型的耗时差二十多倍。 但别被这个数吓退——向量化只做一次,建索引时慢一次,
之后每次查询只算问题那几十个 token。
存储那一列反而没有想象中大:1000 段 × 1024 维 × 4 字节 = 3.9 MB。
一万段也才 39 MB。 所以在小知识库上折腾量化(int8)纯属浪费精力——省下来的是几 MB,
多出来的是精度损失和一堆代码。

三、那到底该选哪个

先看语言。 中文场景优先中文或多语模型;英文模型对中文的字词切分粒度差,检索会莫名其妙地不准。 再看维度上限带来的效果。 维度高表达力强,但你的库如果只有几百段, 这个优势体现不出来——几百段文档的语义空间,用 512 维就够分开了。 最后看一次能读多长。 嵌入模型也有输入长度上限(常见 512 token,多语模型能到 8192)。 超过上限的部分会被直接截断——你切块切了 800 字,模型只读了前 512 token,后半段等于没进索引。 所以顺序应该是: 先按切块大小筛一遍(片段别超过模型的输入上限)→ 再测检索命中率 → 不够再换大模型。 Demo 里那个 52 分钟的模型不是「更好」,是「更大」——更大和更准之间隔着一次测试。

四、一个容易踩的坑:换模型要重建索引

向量是模型算出来的。换了模型,向量空间就变了,新旧两套向量放在一起算余弦,结果没有意义。 很多客户端在你换嵌入模型时不会自动重建,它只是把新模型的设置保存下来。 于是库里一半是老向量、一半是新向量——检索结果会变得毫无规律,而且没有任何报错。 判断方法很土但有效:换模型之后用同一批测试问题跑一遍命中率。 如果数字一夜之间掉了很多,八成是没重建。 所以换模型这件事要当成一次「迁移」来做: 先建一个新库、重建索引、测通了,再把流量切过去。别在原来的库上原地改。

五、动手 Demo:换个大嵌入模型:向量化要慢二十倍

先实测一次 1024 阶矩阵乘,拿到这台机器的真实算力; 再按 FLOPs ≈ 2 × 参数量 × token 数,推算三个嵌入模型向量化 1000 段(每段 200 字)所需的时间, 以及各自的 fp16 体积。

def gflops(n=1024, rep=3):
    a = rg.normal(0, 1, (n, n)); b = rg.normal(0, 1, (n, n))
    a @ b                                # 预热,别把首次开销算进去
    t0 = time.perf_counter()
    for _ in range(rep):
        a @ b
    return 2.0 * n ** 3 / ((time.perf_counter() - t0) / rep) / 1e9

for name, par, dim in models:
    fl = 2.0 * par * 1e6 * tok          # 2 × 参数 × token
    sec = fl / (gflops() * 1e9)
    print(name, dim, par * 2, sec)

实跑输出:

--------------------------------------------------------------------
换个大嵌入模型:向量化要慢二十倍
--------------------------------------------------------------------
  实测算力                 122 GFLOPS
  待向量化                 1000 段 × 200 字
  总 token                 约 30 万
  模型                参数   维度   fp16 体积   向量化耗时
  -------------------------------------------------------
  bge-small-zh        24 M    512       48 MB       118 秒
  nomic-embed-text   137 M    768      274 MB      11 分钟
  bge-m3             568 M   1024     1136 MB      47 分钟

  耗时那一列是从**实测算力**推出来的:FLOPs 约等于 2 × 参数量 × token 数,
  再除以 122 GFLOPS。这是一台 8 核 CPU,没有 GPU。
  三个模型差 **二十多倍**:小模型两分钟跑完,大模型要挂机将近一个小时。
  而这件事只做一次——**建索引时慢一次,之后查询只算问题那几十个 token。**
  所以「慢」不是否决大模型的理由。
  真正的理由是这个:**换大模型的收益,往往不如把切块切对。**
  先花十分钟把段落改成 300 到 500 字,比花一小时换模型划算。
  另外注意维度:512 → 1024 只让向量存储翻倍(1000 段也就几 MB),**真正贵的是算力,不是存储。**

第一行 109 GFLOPS 是这台 8 核机器实测出来的,不是查表查的。后面所有耗时都从它推出来。 耗时那一列:2 分钟、13 分钟、52 分钟。小模型两分钟跑完 1000 段, 大模型要挂机将近一个小时—— 差二十多倍,比参数量之比还大一点,因为 token 数被 512 维那一档稀释了。 体积那一列:48 MB 到 1136 MB。注意这是 fp16 的体积,也就是加载进内存后的大致占用。 第三段算存储:1000 段 × 1024 维只有 3.9 MB。 和模型本身比,向量是小头。这就是为什么「量化向量省空间」在中小知识库里没有意义。 要说明的是:这里算的是纯前向传播,没算分词、没算数据搬运。 真实耗时大概是这个数的 1.2 到 1.5 倍。 要学的是「怎么用一次矩阵乘实测 + 一个 FLOPs 公式估出量级」,而不是这几个具体分钟数。

30 秒实验

  1. 把段数从 1000 改成 10000,看三个模型的耗时和存储各变成多少。 耗时会线性涨,存储也是——但存储涨到 39 MB 还是很小。
  2. 把每段字数从 200 改成 800(长片段),再算一遍。 token 数翻四倍,耗时也翻四倍——这就是「片段别切太大」的算力理由。
  3. 把 fp16 换成 int8,看体积和存储各降多少。体积降一半,但精度会掉——先测检索命中率再决定。

常见误区

  1. 「嵌入模型越大越准」。更大的模型只是容量上限更高,几百段的知识库用不上。先测切块和 k。
  2. 「换嵌入模型直接改设置就行」。不重建索引,新旧向量混在一起,检索会变得毫无规律且不报错。
  3. 「向量存储很占地方,得量化」。1000 段 1024 维才 3.9 MB,量化省下来的是几 MB。 这是把力气花在小头上。
自测三问(点开对答案) 1. 选嵌入模型要看哪三笔账? —— 算力(向量化耗时)、存储(向量占多大)、质量(检索准不准)。前两个能算,第三个只能测。 2. 为什么换嵌入模型必须重建索引? —— 因为向量是模型算的,换模型就换了向量空间,新旧向量一起算相似度没有意义。 3. 为什么片段别切太大,从算力角度说? —— 向量化耗时与 token 数成正比。 片段大一倍,token 多一倍,向量化时间和每次检索带进上下文的量都翻倍。

嵌入模型配好了,接下来建库。这一节的坑不在「怎么建」,在「要不要把不同主题混在一起」。

相关笔记