跳到主要内容

九亿个变体,一张查表

原石
原石

· 阅读约 6 分钟

一 PB,只读,每行一个键。

我看到 AlphaGenome Atlas 的第一反应不是"基因组学",是"这是一张查表"。九亿个变体,每个的预测结果提前算好,落盘,一 PB 停在那儿不动。这里没有在线推理,没有请求队列,没有 GPU 池的自动扩缩容。就一张表。

如果让我给这张表写个键,大概长这样:

struct variant_key {
    uint8_t  chr;   /* 染色体,最多二十几个,8 位够 */
    uint32_t pos;   /* 位点,三十亿用 32 位塞得下 */
    uint8_t  alt;   /* 换成哪个碱基,去掉自己还剩三种 */
};

十几个字节一个键。人类基因组三十亿个碱基对,每个位置三种可能的单字母替换,乘出来就是九亿——这个空间是有限的、可枚举的、今天就能完完整整列出来的。没有新变体会在明天冒出来,也没有哪个查询能落在空间之外。

这才是重点。不是"AI 预测基因",是"这个空间是闭的"。

一旦确认查询空间是闭的、有限的、不随时间增长,整个架构问题就塌缩了。你不需要一个服务,你需要一次批处理加一张表。算一遍要花很多机器很多天,但算完之后每一次查询都是 O(1) 的寻址。慢的那部分只发生一次,快的那部分发生一百万次。

在线推理那套,是给谁准备的

反过来想。不上这张表,改成开一个推理端点,谁要查哪个变体就现算。

那你要什么?要一个队列,因为请求会突发。要一个缓存,因为热门位点会被反复查。要一个限流器,因为总有人的脚本把整条染色体循环一遍。要一个调度器,把批任务和交互请求分开。要一套监控,因为你半夜不知道那几张卡是闲着还是挂了。要一套重试和回退,因为推理跑到一半会 OOM。

一层一层加上去,最后你得到一个分布式系统,它唯一的功能是查一个九亿分之一的数。

我不是说在线推理没用。这段我没完全想明白的,是边界在哪:如果查询空间不闭呢?如果每个查询还得带一长串上下文,组合爆炸,预计算根本不成立——那你就只能在线算,那一整套东西也就配得上它的复杂度了。

所以顺序是先问那个问题。空间闭吗。闭的,别上服务。不闭的,再说。

更狠的一手是那个分数

九亿行预测结果,每行不是一个数,是一组数——编码区的、非编码区的、各个调控方向上的预测。做实验的人拿到这张表也没用,他不可能一次读一万个数据点然后自己排个序。

所以他们给了个 AVI score,把一个变体横跨编码区和非编码区的预测压成一个标量。

这一步才是真正让这张表变得可用的那一步。前面那九亿行是数据,这个分数是接口。

我完全支持这个做法,但我想说清楚它是什么:它是一个排序键,不是真相。

/* 排序键 */
int cmp(const void *a, const void *b) {
    return score_of(b) - score_of(a);
}

把多个维度压成一个维度,一定得选一组权重。这组权重是别人定的,是他们认为"影响大"的那个定义。拿它来排序候选、挑前 1%、决定先做哪个湿实验——完全正确,排序键就该干这个。拿它当"这个变体致病"的判决——那就把一个别人的先验当成了事实。

再说一遍,我没有要批评这个选择。做排序键的时候不选权重,才是回避问题。

Broad Institute 那边用它挑出了 DNM1 上的一个变体,预测说它造出了一个错误的剪接位点,这条证据帮他们把一桩悬着的罕见病案子结了。Hawkes 那边拿着五万四千多个 UK Biobank 样本,按预测的分子效应把变体分组,多找出 22% 的非编码关联;只看影响最大的前 1%,定位到 19 个跟 BMI 相关的区域。

这些数字的价值不在于"AI 准"。在于一个排序键可以让做实验的人从列表顶部开始做。实验室的产能是瓶颈,不是算力。你不可能把一万个候选都做一遍,你只能做五个。所以系统的真正输出不是预测结果,是"先做这五个"。

回头看,那一 PB 其实是中间产物。用户要的不是九亿行,是排好序的前五行。为了拿到那五行,你造了一张一 PB 的表,听起来极不合理。但如果这张表只算一次、之后十年都是它,那它就是最便宜的那个方案。

那 98%,本质是个没文档的系统

人类基因组里,编码蛋白的那 2% 研究得比较透,剩下 98% 基本靠猜。

从工程角度读这句话:这不是"科学未解之谜",这是一个 98% 没写文档的系统,你手里只有一盒探针。

对付没文档的系统,不上理论,做扰动,看输出怎么变。这是 fuzzing 的古老套路——把每个字节都改成别的值,跑一遍,看哪个字节改了会崩。

九亿个变体,就是那份穷举的 fuzz 语料。全部算一遍存下来,没有任何取巧。这跟我在自己那个玩具存储引擎上做的事一样:我不猜哪条分支慢,我把每个点都打一遍。费的是机器时间,买的是不用推理。

这样也许能把 98% 砍到 20%,也许砍不动。但至少你不再猜。

那个网页

一个细节我挺喜欢:它有个网页入口,不用写代码。没有 SDK,没有 API key,没有"请先安装 Python 客户端"。

这个年代 API-first 是个反射动作。什么东西都想先给个接口,再让用户自己写脚本去调。但真实用户是临床研究员和生物学家,他们一辈子不写代码,他们要的是把变体编号粘进去、看它返回什么。给他们一套客户端库是浪费,给他们一个表单才是对的。接口应该长成用户已有的形状,不是长成你实现起来最省事的形状。

收束

一条规则:凡是查询空间是闭的、有限的、不随时间增长的,先别上在线服务,先算一遍存下来。

预计算的成本是一次性的、可预测的、你算得出来的。在线服务的成本是持续的、会呼吸的、会长出队列、缓存、调度、重试、监控那一大堆的。

九亿个变体听着吓人。但一张只读的表,比一个服务简单得多。

原石
原石

把代码当文章写的系统工程师,以源码立论、单线程式拒绝复杂度。

查看主页 →