当ID是知识而非地址时会发生什么
地址什么都不知道
要在数据库中找到李舜臣,你需要一个ID。
在Wikidata中,李舜臣的ID是 Q8492。
这个数字指向李舜臣。
但字符串 Q8492 本身什么都不知道。
它不知道这是一个人还是一座建筑。 它不知道这是韩国人还是法国公民。 它不知道这是16世纪的人物还是21世纪的人物。 它不知道此人是生是死。
Q8492 是一个地址。
送信的邮递员完全不知道信封里写了什么。
他只是看信封上的地址然后投递。
UUID也是如此。550e8400-e29b-41d4-a716-446655440000。
128位随机数。唯一性只是为了避免碰撞——
它不告诉你任何关于所指对象的信息。
过去五十年来,数据库ID都是这样工作的。 ID是地址,要了解任何信息,你必须跟着地址去读取数据。
必须跟过去才能知道
为什么这是个问题?
假设你想找"19世纪出生的德国男性哲学家"。
在传统数据库中,流程是这样的:
1. 从persons表筛选 gender = 'male'
2. JOIN nationalities表并筛选 country = 'Germany'
3. JOIN birth_dates表并筛选 year BETWEEN 1800 AND 1899
4. JOIN occupations表并筛选 occupation = 'philosopher'
四次JOIN操作。 每次JOIN比较两个表的行。 如果表很大,需要遍历索引;如果没有索引,就执行全表扫描。 十亿条记录的情况下,这个过程需要数秒到数十秒。
为什么如此复杂?
因为ID什么都不知道。
看着 Q8492,你无法判断这是德国人还是韩国人,
所以必须去另一个表检索该信息。
每个问题都必须跟着ID指向的地方去查。 这就是数据库五十年来一直在支付的成本。
如果ID本身就知道呢?
让我们翻转前提。
如果ID本身就包含关键信息呢?
如果只需看ID, 就能判断它指的是人类、来自哪个国家、 属于哪个时代、如何分类呢?
要找"19世纪德国男性哲学家", JOIN变得不必要了。
扫描十亿个ID, 通过检查其比特就能立即判断每个是否匹配。
这就是语义对齐索引的核心思想。
将意义对齐到ID中
SIDX(语义对齐索引)是一个64位标识符。
这64位不是随机数。 每个比特的位置都被赋予了意义。
高位存放最重要的信息。 这是什么类型的实体?人、地点、事件还是概念?
接下来的比特存放分类信息。 如果是人,属于哪个时代?哪个地区?
低位携带越来越具体的信息。
关键原则是这样的:
比特的顺序就是信息重要性的顺序。
最基本的分类在最上面, 最细粒度的区分在最下面。
这不仅仅是排序。 这是一种设计哲学。
从十亿到一万,一次扫描
SIDX的实际威力体现在数字中。
WMS持有十亿个实体。 每个实体的SIDX是64位。 总大小:10亿 x 8字节 = 8 GB。
这8 GB完全放得进内存。
你想找"属于人类且来源于东亚的实体"。 高位包含"人类"标志和"东亚"编码, 因此可以用单个位掩码进行过滤。
mask = 0xFF00_0000_0000_0000 (高8位:类型 + 地区)
target = 0x8100_0000_0000_0000 (人类 + 东亚)
for each sidx in 1_billion:
if (sidx & mask) == target:
add to candidates
这个操作可以用SIMD并行化。 使用AVX-512,单条指令同时比较8个SIDX。 扫描10亿条记录:约12毫秒。
在GPU上?不到1毫秒。
十亿条记录缩减到一万条。 详细过滤剩余的一万条是瞬间完成的。
零JOIN。 零索引树遍历。 只需一次按位AND。
为什么64位就够了
一开始,我以为需要更大的空间。
32字节(256位)。一个32维FP16向量。 我试图把实体的每个关键属性都塞进ID中。 是否是人、性别、国籍、时代、职业、地区、存活状态、分类路径……
但后来我意识到了。
ID不需要知道所有事情。
它只需要将十亿条记录缩减到一万条。 WMS处理其余的。
把它想象成检查站。 在高速公路收费站, 从车牌判断"这辆车正前往京畿道", 不需要知道后备箱里装了什么。
64位就够了。 用高位捕获类型和粗略分类, 用低位进行更细的分类。 64位足以将十亿条记录缩减到一万条。
而且64位 = 四个16位字。 它们在流中自然流动。 32字节的ID会使流变得沉重, 但64位SIDX轻巧且快速。
优雅降级:比特截断后意义依然存在
语义对齐的另一个优势是其降级特性。
因为SIDX的比特按从重要到次要的顺序排列, 即使低位损坏或被截断, 高位中的核心信息依然被保留。
完整64位: "李舜臣,16世纪朝鲜海军将领"
48位: "16世纪朝鲜军事将领"
32位: "16世纪东亚人类"
16位: "人类"
8位: "物理实体"
随着信息被截断,具体性丧失, 但最基本的分类保留到了最后。
这是"优雅降级"原则在比特层面的实现。
即使网络中断只传递了部分数据, 系统知道"我不确定这是谁,但至少这是一个关于人类的故事" 并可以继续推理。
模糊的轮廓胜过完全的沉默。 部分理解胜过完全失败。
查询变成ID
语义对齐索引开启的最引人注目的可能性是: 自然语言查询可以被转换为临时SIDX。
用户问:“壬辰战争中击败日本海军的将军是谁?”
编码器分析这个问题。 人类。东亚。16世纪。军事相关。 将这些属性组装成比特,就产生一个临时SIDX。
这个临时SIDX在WMS中扫描十亿个SIDX。 比特模式最相似的实体浮现为候选。 李舜臣、元均、权栗、李亿祺……
将候选者的详细信息进行交叉比对,得出最终答案。
这将搜索和实体链接统一为单一机制。 不需要单独的搜索引擎。 不需要单独的NER(命名实体识别)流水线。 一次SIDX比较就够了。
为什么不用B树?
传统数据库使用B树索引。
B树擅长在排序数据中以O(log n)找到特定值。 对于"查找Q8492",它们是最优的。
但对于"查找所有属于人类且来源于东亚的实体",它们很弱。 复合条件搜索需要交叉多个索引, 交叉的成本随数据规模急剧增长。
SIDX + SIMD穷举扫描采用了根本不同的方法。
如果B树是快速回答"谁住在这个地址"的电话簿, SIDX扫描就是快速回答"谁具有这些特征"的画像分析。
问题的性质不同,最优的数据结构也不同。
| 查询类型 | B树 | SIDX扫描 |
|---|---|---|
| 按特定ID查找 | O(log n),最优 | 不必要(用哈希) |
| 复合条件过滤 | 需要JOIN,慢 | 一次按位AND,快 |
| 相似实体搜索 | 不可能 | 可通过向量相似度实现 |
| 插入 | O(log n),需要再平衡 | O(1),追加 |
| 实现复杂度 | 高 | 低 |
WMS不使用B树。 它将十亿个SIDX加载到内存中 并使用SIMD位掩码进行穷举扫描。
简单。暴力。快速。
霍夫曼的智慧
SIDX的比特分配结构遵循霍夫曼编码的原则。
在霍夫曼编码中,出现频率高的符号获得较短的编码, 出现频率低的符号获得较长的编码。
在SIDX中,最常需要的分类信息占据高位, 很少需要的细节占据低位。
同样的原则也支配着这种语言的数据包类型前缀。 出现频率最高的Tiny Verb Edge获得最短的前缀。 出现频率低的Event6 Edge获得较长的前缀。
霍夫曼的智慧贯穿于这个设计的每一层。 没有一个比特被浪费。 最重要的事情付出最低的成本。
总结
传统ID是地址。地址什么都不知道。
- 当ID不携带意义时,每次都必须跟着它去数据那里。那就是JOIN。
- 对十亿条记录进行四次JOIN很慢。
- SIDX通过语义对齐将意义直接编码到ID中。
- 一次位掩码AND将十亿条记录缩减到一万条。零JOIN。
- 64位就够了。ID不需要知道所有事情——它只需要缩小候选范围。
- 因为最重要的信息占据高位,即使比特被截断,核心意义依然存在。
- 将自然语言查询转换为临时SIDX,搜索就变成了向量运算。
当ID不再是地址而成为知识的那一刻, 数据库的规则就改变了。