Liquid AI 在 10 月 27 日放出 LFM3,六款模型一起放权重,全部挂在同一个 LFM3-1.7B-Base 骨干上。文本、视觉(首次支持视频)、音频、全模态、两个决策模型,4-bit 量化后各约 1 GB,GGUF / MLX / ONNX 三种格式齐全,授权是 LFM Open License v1.0。
这不是「又发了个开源模型」。过去一年小模型的做法是每个模态单独一套配方,团队想改就得重学一遍。LFM3 把六款压到一个骨干上,意味着微调流程、数据管线、部署脚本可以复用一套。对做端侧应用的人来说,这件事比跑分更有意义。
发布第二天我把五套 GGUF 全下下来(1.7B、VL-2B、Audio-2B、Omni-2B、d1-2B),在 M4 / 16 GB 的 MacBook 上逐个跑了一遍。下面所有数字都是这台机器上跑出来的,跑不了的我会直说。
先解决版本:brew 装的 llama.cpp 根本不认它
第一刀就砍在版本上。我机器上的 llama.cpp 是 Homebrew 装的 0.7.0(build 11669),直接加载 1.7B 的 Q4_K_M:
0.06.403.059 E llama_model_load: error loading model: unknown model architecture: 'lfm3'
0.06.403.065 E srv load_model: failed to load model模型卡里那句 “LFM3-compatible llama.cpp builds” 没说谎,只是 “compatible” 的范围比想象窄:官方对比测试用的是 b11744,主线对 lfm3 这个 arch 的支持是后加的。brew 的 formula 跟不上这个节奏。
解决办法是自己编译,二十分钟的事。我这台机器没装 cmake,先用 pip 装了一个:
# 环境:macOS 26.5.2 / Apple M4 / 16GB,Python 3.13 venv
python3 -m venv venv && ./venv/bin/pip install cmake
curl -sSL https://codeload.github.com/ggml-org/llama.cpp/tar.gz/refs/heads/master -o master.tar.gz
tar xzf master.tar.gz && cd llama.cpp-master
PATH="$PWD/../venv/bin:$PATH" cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DGGML_METAL=ON -DLLAMA_CURL=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
PATH="$PWD/../venv/bin:$PATH" cmake --build build --target llama-server llama-mtmd-cli -j 8编出来的 build/bin/llama-server 版本号显示 0.7.0-dev (build 0, commit unknown)——tarball 没有 git 信息,正常。它加载 lfm3 一句话都不抱怨。
下载这一步也有坑:huggingface.co 直连不通
国内网络下 huggingface.co 的 API 和 resolve 链接都会超时(我这边 40 秒直接断),但 hf-mirror.com 正常。五套权重加起来 6.8 GB,走镜像几分钟下完:
B=https://hf-mirror.com/LiquidAI
# 每款主权重都是 1.1 GB 的 Q4_K_M,多模态另配 mmproj
curl -L -o LFM3-1.7B-Q4_K_M.gguf $B/LFM3-1.7B-GGUF/resolve/main/LFM3-1.7B-Q4_K_M.gguf
curl -L -o mmproj-VL-Q8.gguf $B/LFM3-VL-2B-GGUF/resolve/main/mmproj-LFM3-VL-2B-Q8_0.gguf
curl -L -o mmproj-Audio-Q8.gguf $B/LFM3-Audio-2B-GGUF/resolve/main/mmproj-LFM3-Audio-2B-Q8_0.gguf
curl -L -o mmproj-Omni-Q8.gguf $B/LFM3-Omni-2B-GGUF/resolve/main/mmproj-LFM3-Omni-2B-Q8_0.gguf下载完核对过字节数:主权重 1,099,627,808 字节,VL 的 mmproj 203,031,872,Audio 的 337,862,080,Omni 的 540,892,768。四个 mmproj 摆在面前,你就能明白「一个骨干」是怎么落地的——语言部分共用,各模态只挂一个几百兆的编码器。
文本 1.7B:65 tok/s,256K 上下文在 16GB 上真的开得起来
启动命令照模型卡的采样参数来:
./build/bin/llama-server -m models/LFM3-1.7B-Q4_K_M.gguf --jinja \
-c 32768 -ngl 99 --temp 0.1 --top-k 50 --repeat-penalty 1.1 \
--port 8091 --host 127.0.0.1加载 6.09 秒(首次),之后同样的权重再起是 1.07 秒。32K 上下文下进程常驻 1.61 GB。
解码速度:普通问答三组分别是 65.2、65.2、67.7 tok/s,结构化抽取那组 58.6,工具调用那次只有 46.2(多了解析开销)。这个数字意味着什么:一屏 300 字的回答大约 5 秒,做本地 agent 的往返够用,做长文写作会明显等。
长上下文我做了两个测试。先是 1637 token 的普通长文本,预热 756 tok/s;再是 36K 的针测试——中间塞一句「内部代号北极星-7,回滚口令 4821」,前后各铺 700 行周会记录,cache_prompt=false 强制重算:
| 项目 | 结果 |
|---|---|
| prompt tokens | 36,457 |
| 预热耗时 | 63.4s(575 tok/s) |
| 回答 | 代号「北极星-7」、口令 4821,均正确 |
| 该轮结束 RSS | 4.22 GB |
-c 262144 也试了,能起,常驻 4.11 GB。所以 16 GB 的机器跑 256K 不是问题——LFM3 只有 6 层全局注意力,KV 缓存本来就小。我只验证到「能加载 + 36K 内答得准」,256K 塞满会不会退化我没测。
跟 10 月中旬那篇 LFM2.5-24B-A2B 的实测 对着看更有意思:同样是 LFM 家族、同样主打端侧,24B 那款在 16 GB 上 Metal 直接 OOM,纯 CPU 只有 2.3 tok/s;到 1.7B 这一代,速度差了近 30 倍,内存差一个数量级。参数规模决定的不是「跑分高低」,是「能不能用」。
坑一:思考默认开着,512 token 的预算会让它一句话都吐不出来
LFM3 的 chat template 默认带 <think>。我第一次提问用的 max_tokens=512,结果是:
- 耗时 7.86 秒
- completion 512 token 全花在 reasoning 上(thinking 段 2510 字符)
finish_reason=length- content 是空字符串
这不是模型坏了,是预算给少了。同样的问题把 max_tokens 提到 1200,4.87 秒、330 token(思考 628 字符)就答完了,「9.9 比 9.11 大」还附了过程。
想要干净直答也可以,传 chat_template_kwargs:
# 环境:Python 3.13,requests 都不用装,标准库即可;服务在 127.0.0.1:8091
import json, urllib.request
body = {
"model": "lfm3",
"messages": [{"role": "user", "content": "用三句话解释 MoE 的显存为什么不随总参数量线性下降。"}],
"max_tokens": 512, "temperature": 0.1, "top_k": 50, "repeat_penalty": 1.1,
"chat_template_kwargs": {"enable_thinking": False}, # 关掉思考,别靠 max_tokens 硬顶
}
req = urllib.request.Request("http://127.0.0.1:8091/v1/chat/completions",
data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})
print(json.loads(urllib.request.urlopen(req).read())["choices"][0]["message"]["content"])关掉之后 1.61 秒、105 token,中文三句话答得挺准。但模型卡自己写了一句提醒:enable_thinking=False 在推理密集的任务上「behavior can be inconsistent」。我的建议是:对话类场景关掉,数学/多步推理留着并把预算提到 1500 以上,别用 512 这种默认值去赌。
工具调用能跑,但两个细节会咬人
--jinja 下工具调用是通的。给一个 get_weather 的 schema,0.54 秒返回 finish_reason=tool_calls,llama.cpp 把 LFM3 自己的 XML 格式(<tool_call><function=...>)正确翻译成了 OpenAI 的 tool_calls JSON。
两个问题:
一是枚举值不听话。 schema 里 unit 写的是 enum: ["c","f"],模型返回 {"city":"北京","unit":"摄氏度"}。语义没错,但下游按枚举校验就会炸。小模型的参数约束靠 prompt 兜不住,服务端最好自己再加一层 JSON Schema 校验。
二是工具回环之后 </think> 会漏进正文。 我把 tool 结果喂回去让它总结,返回的内容是:
北京现在的天气是晴,温度为摄氏度12。</think>北京现在的天气是晴,温度为摄氏度12。一句回答重复两遍,中间夹一个闭合标签。启动日志其实提示过根因:chat template supports preserving reasoning, it is enabled by default。加 --no-reasoning-preserve 或者全程 enable_thinking=False 能规避,但如果你在写 agent 循环,解析正文前先剥掉 </think> 之后的重复段是更稳的做法。
视觉 VL-2B:一张中文看板只花 561 token
LFM3-VL-2B 的编码器是官方新做的(164M 参数),按 2×2 patch 合成一个语言 token,所以图片 token 数很低。我画了一张 900×600 的中文销售看板(KPI 卡 + 六个月柱状图 + 脚注),问两件事:
| 问题 | 耗时 | prompt | 输出 | 结果 |
|---|---|---|---|---|
| 全图有哪些文字数字 | 3.32s | 561 | 125 tok | ¥128,400 / 3,782 / 2.3% / 38、42、40、55、61、68 全部正确 |
| 六个月里最高是哪个月 | 1.91s | 562 | 62 tok | 「10 月,68 万元」正确 |
一张图 561 token,这是个很实在的优势——同样分辨率喂给视觉大模型动辄两三千 token。进程常驻 1.60 GB,跟文本模型差不多。
启动要带 mmproj:
./build/bin/llama-server -m models/LFM3-VL-2B-Q4_K_M.gguf \
--mmproj models/mmproj-VL-Q8.gguf --jinja -c 16384 -ngl 99 \
--temp 0.1 --top-p 1.0 --top-k 50 --min-p 0.0 --repeat-penalty 1.1 \
--port 8094 --host 127.0.0.1注意这里的采样参数和文本模型不一样(多了 top_p 1.0 和 min_p 0.0),模型卡分模型给的,别一套参数走天下。
视频输入我没测。模型卡写得很清楚:llama.cpp 侧的支持还在 review(PR #31252),暂时不含视频。要用视频得走 Transformers 或 vLLM 那条路。
音频 Audio-2B:一句 system prompt 决定它是「转写」还是「翻译」
这是本次最实用的一个发现。我用系统自带的 say 合成了一段 10.9 秒中文语音(16 kHz 单声道,afconvert 转 WAV),内容包含「九月营收十二万八千四百元」:
| 输入方式 | 耗时 | 输出 |
|---|---|---|
system = Perform ASR in Chinese. |
1.18s | 中文逐字正确,155 token |
| 不给 system prompt | 0.81s | 输出的是英文译文,不是中文原文 |
也就是说,默认行为偏向「理解并翻译」,你必须显式告诉它「只做转写、用中文」。这一点跟 10 月 18 日测的 S1-mini 思路相反:那个是专门清洗 ASR 乱稿,这个是端到端听音频给结果,适合做「上传一段录音,直接问内容」的场景。
还有一句要提醒的,llama.cpp 启动时自己就打了警告:
W init_audio: audio input is in experimental stage and may have reduced quality实验阶段,效果不保证。我这一条样本(合成语音、安静无噪)表现很好,但真实会议录音、多人说话、口音重的情况没测,别直接当成生产可用的转写方案。
音频、视觉、全模态共用一套启动姿势,换模型和 mmproj 就行:
# 音频
./build/bin/llama-server -m models/LFM3-Audio-2B-Q4_K_M.gguf \
--mmproj models/mmproj-Audio-Q8.gguf --jinja -c 8192 -ngl 99 --port 8096
# 全模态
./build/bin/llama-server -m models/LFM3-Omni-2B-Q4_K_M.gguf \
--mmproj models/mmproj-Omni-Q8.gguf --jinja -c 8192 -ngl 99 --port 8097决策模型 d1-2B:一次前向出三个概率,不用生成一个字
d1-2B 是 LFM3 家族里最容易被忽略、但工程上最省事的一款。它不生成文本,只输出带校准的概率。llama.cpp 已经内置了 /v1/systemone 接口,把刚才那张看板图当 state 喂进去,一次问三个问题:
# 环境:同上,服务在 127.0.0.1:8095,需加载 d1-2B + mmproj-d1-Q8
import base64, json, urllib.request
b64 = base64.b64encode(open("assets_dashboard.png","rb").read()).decode()
body = {
"state": {"messages": [{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
{"type": "text", "text": "这是一张业务截图,请据此判断。"}]}]},
"questions": {
"is_report": {"type": "choice", "instructions": "这张图属于哪类页面?",
"criteria": {"sales_dashboard": "销售看板", "login_page": "登录页",
"chat_window": "聊天窗口", "code_editor": "代码编辑器"}},
"health": {"type": "score", "instructions": "这个业务近况如何?",
"criteria": ["明显下滑", "略有下滑", "基本持平", "稳步上升"]},
"need_alert": {"type": "noul", "instructions": "是否需要立即人工介入?"}}}
req = urllib.request.Request("http://127.0.0.1:8095/v1/systemone",
data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})
print(json.loads(urllib.request.urlopen(req).read())["answers"])1.69 秒,1055 个输入 token,output_tokens: 0。返回:
is_report→ sales_dashboard 0.9952health→ score 3.0「稳步上升」0.9636need_alert→ noul 0.227
这比让 1.7B 生成一句「这是一个销售看板,整体呈上升趋势」有用得多:概率可以直接进阈值判断,不用再解析自然语言。三种题型(choice / score / noul)覆盖了分类、打分、是/否。跟 10 月 4 日那篇 Kev-4B 是同一条接口,区别是 d1-2B 能直接吃图。
Omni 的翻车现场:三个模态各自都对,合起来反而判错
Omni-2B 是 Liquid 第一款全模态模型,我觉得它最值得写,不是因为它跑得好,而是因为它错得很有意思。
单模态先跑,都对:只给图问营收,1.78 秒答「¥128,400」;只给音频问营收,0.59 秒答「128,400 元」。
然后把图和音频一起给它,问「图里本月营收和录音里说的数字,是否一致?」
1.28 秒,回答:「不一致。」
我又换了个问法,让它先把两边的数字分别念出来:
图中营收:近六个月 38、42、40、55、61、68 万元…… 录音中营收:28,400 元 判断:不一致
两个错误叠在一起:它把柱状图的六个月数值当成了「图里的营收」(真正该比的是左上角 KPI 卡的 ¥128,400),同时把音频里的 128,400 吞成了 28,400。
再换第三次,只让它「分别取数」,不给判断任务:3.03 秒,两边的数字全对,还主动补了一句「128,400 元即 128.4 万元,与图中 KPI 一致」。
我的判断是:这种 2B 级全模态模型,做「多选题 / 取数」比做「判断题」稳。 一旦你要它下结论,它会自己挑一个参照物,而挑错了你很难发现——因为它给的是一句听起来很确定的话。工程上的做法是拆成两步:先让它把两边的原始数字结构化吐出来,再用代码比。这一步多花 2 秒,换来的是可验证。
六款怎么选
| 模型 | 输入 → 输出 | 我这台机器实测 | 适合谁 |
|---|---|---|---|
| LFM3-1.7B | 文本 → 文本 | 65–68 tok/s,256K 可加载 | 本地问答、工具调用、结构化抽取 |
| LFM3-VL-2B | 图/视频 → 文本 | 3.32s 读完中文看板,561 token | 截图理解、单据识别、图表读数 |
| LFM3-Audio-2B | 音频 → 文本/语音 | 1.18s 转写 10.9s 中文 | 录音问答、ASR(需指定语言) |
| LFM3-Omni-2B | 图+音+文 → 文本/语音 | 单模态准,组合判断易错 | 演示、原型,别做需要下结论的活 |
| d1-2B | 图+文 → 概率 | 1.69s 三问一次出 | 风控、路由、内容分级 |
| d1-omni-2B | 图+音+文 → 概率 | 未实测 | 同上,但要音频输入时 |
预算是关键:六个模型加起来不到 7 GB,但同时只能常驻一两个。16 GB 的机器跑一个 1.1 GB 权重 + mmproj,常驻 1.6–1.8 GB,剩下的留给系统和你的应用。想同时上 VL + Audio,内存没问题,但每次切换模型都要重新加载(1–6 秒),做交互式应用时这点延迟要算进去。
另外补一句站内对照:如果你要的是「非程序员在自己电脑上处理文件」,10 月 27 日那篇 Hob 那种成品桌面端更合适;LFM3 是给你自己拼应用的零件。
什么场景别用它
模型卡自己写得很直白:not recommended for agentic coding or complex multi-step reasoning。我赞同,再补三条来自实测的:
- 别拿 1.7B 当编码 agent 的主模型。 工具调用能通,但枚举值会飘、
</think>会漏,写代码这种要求严格格式的活儿,它扛不住。 - 别让 Omni 直接输出结论。 上面那个「不一致」就是例子,答案是错的,语气是确定的。
- 别把 Audio 当生产级转写。 llama.cpp 明说 experimental,我只有一条干净合成语音的样本,噪声和口音没测。
值得用的地方也很清楚:本地问答、截图取数、录音问答、以及决策模型那种「一次前向出概率」的轻量判断。这些活的共同点是——你不需要它写得漂亮,只需要它答得对、跑得快、数据不出机器。
上手路径
想看就跑最小闭环:编译 master → 下 1.7B 的 Q4_K_M → llama-server --jinja 起服务 → 用上面的 Python 片段关掉 thinking 问一句。跑通之后再决定要不要加 mmproj——视觉和音频各多 200–540 MB,加载时间多一到两秒,值不值得看你的场景。
官方资料在这里:Liquid AI 的 LFM3 发布博客、LFM3-1.7B 模型卡、llama.cpp 部署文档。模型卡里的跑分(WebArena-Verified 38.42、视觉 62 项基准均值 71.4)都是厂商自评,我这台机器上没有 GPU 集群,复现不了,看的时候留个折扣。