认识 Quail:一条 SQL 里写中文让大模型逐行判断,为什么能快 14 倍?
先说一件让我自己都觉得离谱的事。
上个月有个朋友找我,他们有 10 万条客服工单,想筛出"用户其实是在抱怨物流而不是产品本身"的那些。这活儿关键词搞不定,规则写不出来,典型的"只有大模型能干"。
他很兴奋地给我看他的方案:SELECT * FROM tickets WHERE AI.IF("用户在抱怨物流"),一行 SQL,多优雅。
我多嘴问了一句:跑完多久?
他说:跑了一夜,没跑完。
这不是他代码写得烂,也不是模型选得差。这是一整类新 workload 撞上了一堵所有人都还没注意到的墙——我们用给聊天机器人设计的推理引擎,在跑数据库的活儿。
然后 2026 年 9 月 24 日,一个叫 Quail 的开源项目冒了出来,MIT 协议。它的官方说法很朴素:同一个最难的查询,精心调优过的 vLLM 要跑 6.84 小时,它跑 29.3 分钟。快 14.04 倍,成本从 27.03 美元降到 1.93 美元。
我第一反应是不信。看完它的做法之后,我觉得这个数字反而偏保守。
什么是 Quail
Quail 全称 QUery-Aware Inference Layer,是 Full Stack Data Lab(Shreya Shankar)与 Modal(Charles Frye)合作开源的 AI-SQL 推理引擎,MIT 协议,PyPI 包名 quail-engine,首发版本 0.1.0(要求 Python 3.12,锁定 vLLM 0.26.0)。代码在 github.com/fsdatalab/quail。它不是一个数据库,也不是 vLLM 那种通用推理服务器——它是一个"知道你整条 SQL 长什么样"的推理引擎,把查询计划和模型推理当成同一个优化问题来解。
要理解它为什么存在,得先说清楚 AI-SQL 是什么。
先搞懂一件事:AI-SQL 不是"让大模型写 SQL"
这是最多人搞混的地方,我必须放在最前面。
- Text-to-SQL:人说"给我上个月的销售额",模型生成一条 SQL。模型是翻译官。
- AI-SQL:人写一条 SQL,SQL 里某个条件是自然语言,由模型逐行判断真假。模型是数据库里的一个算子。
长这样:
-- 找热线索:每个客户画像 × 每个产品描述,让模型判断"这人会不会买这个"
SELECT customers.id, products.id
FROM customers JOIN products ON
AI.IF(
PROMPT("{customers.profile} might buy this: {products.description}")
)看出恐怖之处了吗?
AI.IF 是 WHERE 里的一个函数,它对每一行都要调一次模型。而 AI.IF 放在 JOIN 的 ON 条件里,就是对笛卡尔积的每一对调一次模型——1 万个客户 × 1 万个商品 = 一亿次模型调用,每次输入还好几百 token。
一条 SQL 能触发几百万次、每次几千 token 的推理请求。 这不是"推理服务",这是一次批量计算作业,却被当成"一堆独立的聊天请求"塞进了推理引擎。
这不是我瞎编的需求。Snowflake 的 Cortex AI、Google BigQuery、Databricks、MotherDuck 全都已经上线了这类 AI 函数,AI_FILTER / AI.IF / AI.GENERATE 各家拼写不同,本质一样。学术界更早就在做:Berkeley 的 DocETL、Stanford 的 LOTUS、MIT 的 Palimpzest、Cornell 的 ThalamusDB,都在琢磨怎么少调几次模型。
但几乎没人动"推理引擎本身"这一层。直到 Quail。
它有什么特点
第一,也是唯一的核心思路:把查询计划和推理一起优化
通用推理引擎(vLLM、SGLang)的处境是盲的——请求一个一个进来,它不知道下一个请求是什么,只能做通用优化。
Quail 的处境完全不同:你先把整条 SQL 交给它,它在执行前就知道全部请求长什么样。
这个信息差,让一堆原本做不了的优化突然能做了:
- 算子重排:两个
AI.IF过滤条件,先跑哪一个?当然先跑选择性高(能筛掉更多行)的那个——下游直接少算一大半。这是 Hellerstein 和 Stonebraker 几十年前的老手艺,只是现在成本模型里换成了 token 数。 - Join 顺序 + anchor 选择:用 System R 那套动态规划搜 Join 顺序,同时决定哪一侧放在 prompt 前面当"锚点"——因为前缀相同就能复用 KV 缓存。
- Speed-of-light 成本模型:先算出理论上最快能多快(只算 matmul 和 attention),拿它当标尺去衡量实际计划离极限还有多远。
以前是"数据库优化完 SQL,把一堆请求丢给推理引擎,各管各的"。 Quail 是"这俩本来就是一个问题"。 这也是为什么它最难的那个查询能快 14 倍,而平均只快 1.84 倍——Join 越复杂,联合优化的红利越大。
第二,KV 缓存的"回退"而不是"丢弃",这是最漂亮的一招
一条文档进来,跑完第一个过滤条件,它的 KV 缓存(这篇文档在 GPU 显存里的注意力状态)里包含了:文档本身 + 这个判断条件的 prompt。
vLLM 的做法是:这条请求结束,KV 缓存回收。下一算子还要判断同一篇文档?重新算一遍。
Quail 的做法是:
- 过滤判完 True/False,把只属于这个条件的那截后缀 KV 回退掉,文档本身的 KV 留在 HBM 里;
- 下游算子还需要这篇文档?直接接着用,零重算;
- 不需要了才释放;
- 显存不够要淘汰时,先淘汰短文档——因为注意力开销是长度平方级,长文档重算起来贵得多。
团队专门造了个指标叫 KV regret(多算的重复 token 数)。在最难的那个医疗查询上:
| Quail | vLLM(已调优) | |
|---|---|---|
| KV regret | 1800 万 token | 5030 万 token |
| 输入吞吐 | 1900 万 token/秒 | 136 万 token/秒 |
| 耗时 | 29.3 分钟 | 6.84 小时 |
| 单查询成本 | $1.93 | $27.03 |
那 5000 万 token 的差距,就是 vLLM 一遍又一遍重算同一批医疗报告的代价。
第三,Join 用"树注意力",锚点只算一次
AI Join 的场景是:文档 A 要跟 B1、B2、B3... 几百个候选分别比对。朴素做法是拼几百个 prompt,A 被重复编码几百次。
Quail 把共享同一个锚点的所有伙伴分组,注意力拆成两步:
- 所有伙伴的 query 对锚点的 KV 做一次交叉注意力(锚点只算一次);
- 伙伴各自对自己的后缀做因果注意力;
- 用 log-sum-exp(online-softmax 那套)把两边结果合并。
这和 FlashInfer 的 cascade attention、Hydragen、SpecInfer 是同一个思想家族。区别在于:那些技术得"猜"哪些前缀相同,Quail 提前就知道了,所以能省掉一大堆运行时复杂度。
第四,把 CPU 从关键路径上赶走
这是最容易被忽略、但在 QUAIL-B 全集上贡献最大的一块。
场景是:请求数百万级、模型很小(几十亿参数)、GPU 很大(H100)——这根本不在通用推理引擎的设计区间里。表现就是 GPU 在等 CPU 发请求,利用率上不去。
Quail 的解法:
- tokenizer 换成 Stanford 的 Gigatoken(vLLM 自带的 HF tokenizer 在这个量级上就是瓶颈);
- token ID 存进内存映射的 Arrow 文件;
- 算子间流式批处理,像 Apache DataFusion 那样 pull-based,CPU 准备下一批的同时 GPU 在跑当前批,不留空档。
用 Triton 手写了几个融合 kernel(add-RMSNorm 融合 FP8 量化、per-head QK-norm 融合 RoPE),核心 matmul 用 DeepSeek 的 DeepGEMM,注意力用 FlashAttention 3。
还有一个我觉得特别聪明的小优化:**输出头只算 TRUE 和 FALSE 两个 token 的 logits,不算整个词表。**反正你要的只是一个布尔值。
第五,架构是"拼"出来的,不是造出来的
这点值得单独说,因为它决定了这项目能不能活下来:
| 层 | 用什么 |
|---|---|
| SQL 解析 | sqlglot(支持 snowflake / bigquery 两种方言) |
| 查询计划 | 自研(核心工作量),用 Substrait 序列化 |
| 执行引擎 | 从 vLLM 的 forward 实现 fork 出来改,Triton 写融合 kernel |
| 存储 | pyarrow,列式 Arrow |
作者原话大意是:现在做数据库已经不难了(Stonebraker & Pavlo, 2024),关键组件都有可扩展的开源实现,加上 coding agent,拼装变得异常容易。
这句话我特别认同,而且它其实是整个项目的潜台词: 在推理和数据库交叉的地方,还有大量没被开垦的性能空间,等着人去捡。
场景:它到底在哪些活儿上值这个钱
先把丑话说在前面:**Quail 是 2026 年 9 月 24 日刚开源的研究型项目,版本 0.1.0,没有任何公开的商业化客户案例。**你要找"某某公司用它省了几百万"这种故事,现在没有。
但它公布的 QUAIL-B 基准里那 29 个查询,全都来自真实数据,而且把成本摆得很清楚。这比一个包装过的客户故事更有说服力。
场景一:医疗报告 × 不良反应术语的 Join(BIO-4)
5000 份医疗报告,跟 4144 个不良反应术语做 Join,术语集被用了两次。模型 Qwen3 4B FP8,单张 H100。
**29.3 分钟 vs 6.84 小时,$1.93 vs $27.03。**吞吐 1900 万 token/秒——这就是"单卡每分钟 10 亿 token"那个 headline 数字的来源。
放在 Modal 的 H100 定价($3.9492/GPU-hour)下折算,每十亿 token 不到 6 分钱。
场景二:10 万条 IMDB 影评,筛"剧透结尾的"(IMDB)
SELECT r.id FROM reviews r
WHERE AI_FILTER(PROMPT('Does this review discuss the ending?\n\n{0}', r.review))实测跑完的数据:10 万条影评,命中 16,057 条,实际消耗 3249.9 万 token,处理速度 360.5 文档/秒,总耗时(含 GPU 冷启动)335 秒,GPU 成本 $0.3675。
对照一下:同样两个过滤条件走 GPT-5 nano 的 API(含缓存价),大约 $1.75,是 Quail 的 4.8 倍。
场景三:Agent trace 分析(AGENT)——它输了的那一局
这部分我必须写出来,因为它比赢的那些更有信息量。
QUAIL-B 里有 2 个基于软件 Agent 运行轨迹的查询。在这两个查询上,Quail 输给了 vLLM:成本是对方的 2.32 倍。
原因很直白:Agent trace 里大量请求前缀是相同的(同一个系统提示词、同一段上下文),vLLM 的自动前缀缓存(automatic prefix caching)天生就吃这个场景,而 Quail 目前还不支持跨行的前缀复用——它只在同一条文档内部复用 KV。
作者没藏这个数据,直接写进了论文,还把它标成"未来工作"。
一个开源项目敢把自己输的那一局写进基准报告,并且专门设计了两个查询来暴露自己的短板—— 我对这种项目的信任度,比那些只放漂亮数字的,高一个数量级。
场景四:你已经在用 Snowflake / BigQuery 的 AI 函数
如果你公司已经在用 Snowflake Cortex AI 或 BigQuery 的 AI.IF 做批量标注、工单分类、合规筛查,那 Quail 给你的是一个开源的、可自托管的对照方案——同样一批活儿,自己拿开源小模型跑,成本可能差一个数量级。
它支持 Snowflake 和 BigQuery 两种方言的拼写(AI_FILTER 和 AI.IF 都能解析),所以你现有的 SQL 大概率不用改就能试。
初体验:怎么上手
最省事的方式是用 Modal 直接跑,不用自己有 GPU:
uvx modal run try_quail.pyimport modal
app = modal.App("try-quail")
image = (
modal.Image.from_registry("nvidia/cuda:13.0.1-devel-ubuntu24.04", add_python="3.12")
.entrypoint([])
.apt_install("git")
.uv_pip_install("quail-engine==0.1.0")
)
@app.function(gpu="H100!", image=image, timeout=600)
def run(sql=None, documents=None):
from datasets import load_dataset
import pyarrow as pa
import quail
if sql is None:
sql = """
SELECT r.id
FROM reviews r
WHERE AI_FILTER(PROMPT('Does this review discuss the ending?\n\n{0}', r.review))
"""
imdb = load_dataset("stanfordnlp/imdb")["train"]
documents = pa.table({
"id": pa.array(f"review-{i}" for i in range(len(imdb))),
"review": imdb.data.table.column("text"),
})
config = quail.EngineConfig(
gpus=1,
model="qwen3-4b-fp8",
backend="quail",
device="h100-sxm",
)
with quail.Session(config) as session:
session.register("reviews", quail.DocumentProvider.from_table(documents, id_col="id"))
result = session.sql(sql).run()
print(result.collect())
print(result.report)如果你自己有卡,Python API 就四步:注册数据(Arrow 表或 dataset)→ 写 SQL 或 Python 查询构造器→ session.sql(sql).run() → result.collect()。
几个上手时容易踩的点:
- 当前版本只支持 AI 过滤、AI Join、投影和 LIMIT。没有 GROUP BY,没有聚合,语义分组还在路线图上;
- 数据是注册进内存 Arrow 表的,官方假设你事先从 S3 或 Modal Volumes 拉下来,它自己不管存储;
- 你可以给算子显式传 selectivity(预期通过率)和 anchor(Join 锚点),不传的话它就按你写的顺序老实执行——想吃到优化红利,最好传 selectivity;
- 模型列表目前很窄,官方基准全用 Qwen3 4B FP8,KV 用 BF16。
进阶
如果这篇文章你只记住一件事,我希望是这个判断:
AI 的推理 workload 正在分裂,而我们还在用一套引擎打所有仗。
过去两年整个推理优化行业的目标函数高度一致:降低单请求延迟、提高单用户吞吐,因为服务对象是聊天和 Agent。但"给 10 万行数据每行做一次判断"这件事的目标函数完全不同——总吞吐和总成本才是唯一指标,延迟根本不重要,而且请求在执行前就全部已知。
用前者的引擎跑后者的活儿,就像用出租车送快递:能送到,但贵得离谱。Quail 的贡献不是发明了多少新 kernel,而是指出了这个 workload 分类,并给出了第一个开源参考实现。
冷水照例要泼,这次泼三盆,都很实在:
**第一,1.84 倍这个平均数,别当成你的收益。**拆开看:对比"朴素 vLLM"是 1.84 倍,但对比"已经做了算子间流水线的 vLLM",只有 1.12 倍。也就是说,全集上的收益大部分来自"消除 CPU 调度开销"这一项,而这一项你自己写点批处理程序也能捞回来一部分。真正拉开数量级的是 Join 密集场景(BIO 数据集上 8.72 倍的成本优势),如果你的活儿只是简单过滤,提升可能没你想象的大。
**第二,它离自己的理论极限还有 3.35 倍。**团队算了 speed-of-light 估计:整个 QUAIL-B 理论最快 491 秒,Quail 跑了 1644 秒。作者自己说"还有很大空间"。换句话说,现在的 Quail 是个不错的起点,不是终点。
**第三,功能面还窄,别急着往生产塞。**0.1.0 版本,无聚合、无跨行前缀缓存、模型支持少、只支持单机单卡到单机多卡。作者明确说了"我们还在 actively building"。
我的建议很具体:**如果你现在在用 Snowflake / BigQuery 的 AI 函数做批量任务,先把最贵的那条查询导出来,用 Quail 跑一遍同样的语义,对比账单。**这个实验半天就能做完,而结论可能直接决定你明年这笔预算怎么花。
再往深一层,我建议你去读两篇原文:Modal 那篇博客(从推理工程师视角讲 kernel 和 KV 管理),和 Full Stack Data Lab 那篇(从数据库工程师视角讲查询计划)。这两篇放在一起看,你会理解为什么"数据库的人"和"推理的人"过去两年几乎不说话——而最大的红利恰恰在他们中间那条缝里。
至于 AI-SQL 会不会变成数据库的标配能力(Snowflake、BigQuery、Databricks、MotherDuck 已经全上了,说明需求是真的),我的看法是:**已经是了。**问题只是这活儿最后由云厂商的闭源函数做,还是由你自己拿开源模型做——Quail 押注的是后者,而且押得挺聪明。
更多开源技术干货和学习资料,关注公众号「遇码」,领取专属福利。
