本地推理的默认假设是「一次一个用户」:llama-server、Ollama 这类服务按请求排队,谁先来谁先算。但现在的真实场景往往是反过来的——后台有三五个 agent 在总结邮件、整理文档、跑批处理,你临时想问一句,就得排在它们后面。Superfluid 想解决的就是这个调度问题:它把交互请求和批处理请求分开,交互请求在下一个调度点抢占,而不是等整批跑完。
需要先说明边界:Superfluid 是 2026 年 10 月才开源的新项目(Apache-2.0),GitHub 星数还很少,成熟度不能和生产级推理网关相比;官方给出的「30 倍更快响应」是在特定 Mac、特定负载下的对比,不是通用承诺。这篇教程的重点是让你在自己的机器上把这个数字验一遍,而不是照抄结论。
第一步:判断它是否适合你
- 适合:一台机器(Mac 或 Linux)同时跑多个本地 agent,且你还会在中间手动提问;
- 适合:需要会话在进程崩溃或服务重启后接着跑,而不是从头再来;
- 不适合:只在服务器上跑单一批处理任务,吞吐优先、交互延迟无所谓;
- 不适合:必须用 vLLM 的 PagedAttention、连续批处理或张量并行的生产集群。
Superfluid 支持三种运行时:llama.cpp(GGUF 模型)、MLX(Apple Silicon 的 MLX 目录)、以及自家的 BaseRT(.base 包)。对外暴露 OpenAI、Anthropic 和 Ollama 三套兼容 API,所以现有的客户端大多只改一个 base_url 就能接上。
第二步:安装
macOS(Apple Silicon)和 Linux(x86-64 / arm64)可以直接用安装脚本。macOS 和 Linux 上会默认装 BaseRT,llama.cpp 与 MLX 在需要时自动安装。
curl -fsSL https://superfluid.sh/install.sh | sh
# 或者用 Rust 工具链
cargo install superfluid安装完先确认版本和可用运行时:
superfluid --version
superfluid runtime list第三步:起一个模型
最省事的方式是直接从 Hugging Face 拉 GGUF,冒号后面指定量化档位。下面用 Qwen3.8-27B 的 UD-Q4_K_M 举例,模型大小和显存需求按你的机器调整:
superfluid serve unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M服务默认会监听本地端口,并在启动日志里打印 OpenAI 兼容的 base_url(通常是 http://127.0.0.1:8000/v1)。先发一个最小请求确认模型真的在跑:
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.8-27b","messages":[{"role":"user","content":"用一句话说明什么是 KV cache"}]}'第四步:把现有客户端接过来
大多数 SDK 只认 OpenAI 接口,改 base_url 和 api_key(本地服务随便填一个非空值)即可。Python 示例:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="local")
resp = client.chat.completions.create(
model="qwen3.8-27b",
messages=[{"role": "user", "content": "把这段会议记录压缩成三条待办"}],
)
print(resp.choices[0].message.content)如果你用的是 Anthropic SDK 或 Ollama 客户端,Superfluid 也提供对应兼容层,路径不同但模型名与参数含义一致。接之前先看一遍它支持的参数白名单,别把厂商特有的字段直接透传过去。
第五步:压测——8 个批任务加 1 个交互请求
官方说法是:8 个 agent 同时处理 7.5k token 的文档,第 2 秒插进来一个聊天请求,Superfluid 在 0.36 秒内给出回答,而 llama-server 要 10 秒以上。这个对比要在你自己的机器上复现,才有参考价值。下面是一个最小的并发脚本,用 8 个线程模拟批处理,主线程在 2 秒后发交互请求,记录首 token 延迟:
import threading, time
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="local")
long_doc = "这里放一段约 7500 token 的文档。" * 200
def batch(i):
client.chat.completions.create(
model="qwen3.8-27b",
messages=[{"role": "user", "content": f"总结第 {i} 份文档:{long_doc}"}],
)
threads = [threading.Thread(target=batch, args=(i,)) for i in range(8)]
for t in threads: t.start()
time.sleep(2)
t0 = time.time()
first = None
stream = client.chat.completions.create(
model="qwen3.8-27b",
messages=[{"role": "user", "content": "用一句话解释什么是抢占式调度"}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
first = time.time() - t0
break
print(f"交互请求首 token 延迟: {first:.2f}s")
for t in threads: t.join()记录三个数:交互请求的首 token 延迟、8 个批任务的总耗时、以及服务进程的峰值内存。然后把同样的脚本指向 llama-server 或你原来的服务,用同一模型、同一量化、同一上下文长度再跑一次。如果交互延迟没有明显改善,说明你的瓶颈不在调度,而在显存或模型本身,换服务解决不了。
第六步:验证会话能不能续上
Superfluid 的说法是「每个 token 在返回给你之前已经写进日志」,所以工作进程崩溃或服务重启后,会话能从断点继续。这个特性值得亲手验一次,因为「声称持久化」和「真的能续」是两件事。
- 发起一个长输出请求(例如让它写 2000 字),跑到一半时用 kill -9 直接杀掉服务进程;
- 重新执行 superfluid serve,使用同一个会话 ID 重新发起请求;
- 检查返回内容是否从上次中断的位置继续,而不是从头生成;
- 检查会话日志文件是否完整,有没有出现重复或丢失的 token。
如果你用不到会话恢复,这一步可以跳过,但要记住:日志会占磁盘,长会话跑多了要定期清理。
第七步:确认共享前缀只算一次 prefill
多 agent 场景里,最常见的是「同一个系统提示词 + 不同任务」。Superfluid 会把相同前缀的 prefill 结果复用:官方举例是 32 个请求共用同一段系统提示,只做一次 prefill。验证方法是构造 10 个只有最后一句不同的请求,观察服务日志里的 prefill 次数和首 token 延迟:
for i in $(seq 1 10); do
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d "{\"model\":\"qwen3.8-27b\",\"messages\":[{\"role\":\"system\",\"content\":\"你是一个严谨的技术编辑。\"},{\"role\":\"user\",\"content\":\"改写第 $i 段\"}]}" > /dev/null
done如果每个请求都重新 prefill,首 token 延迟会随系统提示长度线性上升;如果命中共享前缀,后面的请求应该明显更快。注意前缀必须完全一致,哪怕多一个空格都会失效。
第八步:多设备与收尾
Superfluid 支持把第二台机器加进同一个私有集群,命令会走加密链路。对家庭实验室或小团队,这比单独维护两套推理服务省事;但跨设备推理会引入网络延迟,交互场景要先把链路延迟测出来再决定。
最后给一份上线前的检查清单:
- 固定模型版本与量化档位,写进配置,不要用 latest 标签;
- 给交互请求和批处理设不同的超时与重试策略,别让批任务拖死交互;
- 监控首 token 延迟的 p50 与 p95,而不只是平均值;
- 定期清理会话日志,确认磁盘水位;
- 把服务放在本机回环地址或内网,默认不要暴露到公网。
本地推理服务的真正卖点不是「能跑」,而是「多个任务抢一台机器时,你还能插得进去」。先用你自己的负载把这个抢占效果测出来,再决定要不要换掉现有方案。