Luminal 拆解一个“用搜索代替启发式”的推理编译器
Luminal 想用 少量原语算子 + 非破坏式重写(e-graph) + 在真机上实测的搜索,代替“为每种模型 × 硬件 × 数据类型手写 kernel,再用一长串启发式 pass 去优化”的做法。下面拆开讲它在做什么、难在哪、怎么解,以及当前代码实际做到了哪一步。
- 它是什么:一个 Rust 写的 AOT(提前编译)推理编译器。前端接 PyTorch(
torch.compile(backend=…))或自己的 Rust 张量 API,后端目前有 CUDA(luminal_cuda_lite)、Metal 和一个 CPU 参考实现。 - 核心赌注:不写“什么时候该做什么优化”的规则,而是把所有等价的写法都塞进一个 e-graph,再在 GPU 上实测挑最快的。规则只需要回答“这样改对不对”,不用回答“这样改值不值”。
- 真正的难点不在想法,而在工程:e-graph 会爆炸、e-graph 里的“代价”不可加、每条重写都必须在所有形状 / dtype / 动态维度下严格正确、实测一个候选很贵。
- 当前状态:main 分支正在一次大重构(spec 里叫 “rejoin”)的中后期。新管线的正确性很扎实(与 PyTorch 对拍、形式化很重的 egglog 规则),但性能暂时远落后:H200 上 FP32 的 Llama3-8B 每 token 约 221 ms,没有注册融合 attention。README 里“~80% 理论峰值”“自动发现 FlashAttention”描述的是旧管线和公司的目标。
- 对你有用的地方:它把 cuBLASLt(旧管线还有 FlashInfer)当作搜索空间里的普通候选,这正好给出“手写 kernel 库在编译器里的位置”的一种答案;它的布局代数和 CuTe 思路相通。
01它要解决的问题
部署一个模型,要面对三个相乘的维度:模型结构(LLM、扩散、MoE、CNN……)、硬件(H100、B200、Apple GPU、AMD……)、数据类型(fp32、bf16、fp8、nvfp4……)。主流框架的做法是为高频组合手写 kernel(FlashAttention、FlashInfer、cuBLAS、CUTLASS 模板),再用编译器 pass 把模型“降”到这些 kernel 上。
Luminal 的 README 和 why.mdx 指出了两个问题:
- 组合爆炸:每加一个设备,就要把框架支持的每个算子都实现一遍;再乘上 dtype,代码量几乎呈指数增长。
- 破坏式重写的复杂度:XLA、TVM、torch.compile 的优化由大量“单向”改写组成。改写一旦执行,原图就没了,所以每条规则只能在确定有收益时才触发。判断“有没有收益”需要越来越多的特判和启发式,最后还是要靠 Triton / Pallas 这种 DSL 手工找回性能。
传统框架像一本越来越厚的菜谱:每道菜 × 每种灶具 × 每种口味都写一页,还得附上“什么时候该换哪一页”的说明。Luminal 只规定十几种基本食材和刀法(原语),每种灶具(后端)只说明“这几样刀法合起来,我有一招更快的做法”。具体怎么组合,让厨房试做几轮,用秒表决定。
02核心思路:三根支柱
① RISC 式的小原语集(HLIR)
README 说一切最终都归结为 15 个原语:Log2 Exp2 Sin Sqrt Recip、Add Mul Mod LessThan、SumReduce MaxReduce Iota Gather Scatter Cast。注意几个刻意的选择:只有以 2 为底的 Exp2/Log2(exp(x)=exp2(x·log2e)),没有减法和除法(a-b = a + b·(-1),a/b = a·recip(b)),矩阵乘也不是原语,而是“广播 → 逐元素乘 → 沿 k 求和”。
当前分支的逻辑层比 README 多:src/logical/op/ 下有约 36 个 Logical* 算子,增加了 Select、And/Or/Not、Floor/Ceil/Round/Trunc、TruncDiv/TruncRem、三种 scan 等。主要原因是对齐 PyTorch 的精确语义(整数取整方向、比特级 bool),见 associative-fold.md 里关于 prod = exp(sum(log x)) 在负数上出错的讨论。
② 非破坏式重写:e-graph(egglog)
所有优化都写成等价关系,而不是“替换”。e-graph 同时保存一个计算的所有等价写法,规则可以按任意顺序、反复触发,没有“先做哪个 pass”的问题。规则引擎是 egglog(Datalog + e-graph),Luminal 用自己的 fork:luminal-ai/egglog。
③ 以实测运行时间为代价函数的搜索
e-graph 里可能有天文数字种组合。Luminal 不写代价模型,而是采样候选、生成代码、在目标 GPU 上真跑,用遗传算法迭代。HN 上作者的话:“The best heuristic is no heuristic.”
另外两个配套原则:一切 AOT(整个网络是静态图,编译器有全局视野),以及原生的符号维度((s, 4096)、(b, h, w+3)),用分桶(bucket)为不同的动态取值范围分别搜索。
03整体管线
spec 里写着当前分支的管线是 recorder → egglog_core → runtime 自己负责的 extraction/search → bufferize → runtime。展开如下:
| 阶段 | 做什么 | 源码 |
|---|---|---|
| 前端 | PyTorch 走 torch.compile 导出 Core ATen IR,逐个 ATen 算子翻译成 Logical 原语;也可以直接用 Rust GraphTensor API 建图 | pytorch/…/translate/ · src/frontend/ |
| 逻辑 IR | 每个算子一个目录:构造器 + dtype / shape / 布局推导 + 代数重写,各是一个 .egg 文件 | logical/op/mod.rs · op/add/ |
| egglog 核心 | 整数表达式、形状、布局的“前导程序”(5000+ 行 egglog) | egglog_preamble.egg |
| 后端规则 | “逻辑模式 + 布局条件 ≡ 这个 CUDA kernel / 库调用” | luminal_cuda_lite/src/ops/ |
| 提取 | 给定 genome,遍历 e-graph 得到一张具体的数据流图 | extraction.rs · extract_with_genome |
| 搜索 | 采样、变异、实测、留下前几名作为后备 | cuda search.rs · search_implementations · search_support.rs |
| Bufferize | MLIR 风格的 one-shot bufferization,决定谁原地写、谁要新分配、哪里要拷贝 | bufferize.rs · bufferize |
| 代码生成 / 运行 | 把几何参数当字面量写进 CUDA 源码,用 NVRTC 编译;库调用走 host op;用 CUDA Graph 回放 | kernels.rs · KernelOp · cuda_graph.rs |
| 语义参考 | CPU 上的完整参考实现,所有后端都和它对拍 | luminal_reference/runtime.rs |
04深入:e-graph 与 egglog
为什么“非破坏式”很关键
经典例子(来自 egg 论文):表达式 (a×2)/2,有两条规则:强度削减 x×2 → x≪1,以及消去 (x×y)/y → x。用传统 pass 管线时,如果强度削减先跑,表达式变成 (a≪1)/2,消去规则就再也匹配不上了。这就是 phase ordering 问题:结果取决于 pass 的顺序,于是只能写启发式来决定顺序和触发条件。
破坏式 pass 像下棋时落子无悔:每一步都必须当场判断好坏,走错了回不去。e-graph 像把所有合法走法都画在一张棋谱树上(而且相同局面只画一次),最后再挑一条线。规则作者只需要保证每一步“合法”,不必判断“好不好”。
一条真实的规则:把“广播-乘-求和”识别为矩阵乘
矩阵乘不是原语,它是一个模式。下面这条规则把这个模式和一个辅助节点 LogicalHelperMatrixMultiply 合并到同一个 e-class,后端规则只需要匹配这一个节点:
; src/logical/helper/matrix_multiply/recognize.egg
(rule
((= ?out (LogicalReduceSum ?prod 0))
(= ?prod (LogicalMul ?a_b ?b_b))
(= ?a_b (LogicalHelperBroadcastAxis ?a 1 ?n))
(= ?b_b (LogicalHelperBroadcastAxis ?bT 2 ?m))
(= (shape-of ?prod) (ShapeLit [?m ?n ?k])) ; 形状把两条腿绑到同一个 [m,n,k]
(= (shape-of ?a) (ShapeLit [?m ?k]))
(= (shape-of ?bT) (ShapeLit [?n ?k])))
((union ?out (LogicalHelperMatrixMultiply ?a (LogicalHelperMatrixTranspose ?bT))))
:ruleset main_ruleset
:name "helper/matrix_multiply/recognize: reduce-sum of broadcast muls is a MatrixMultiply")
关键字是 union,不是“替换”。另外有一条方向相反的 expand.egg,把矩阵乘展开回原语链。两个方向同时存在,正是非破坏式的意思。博客里提到一个好处:模型作者没有显式写 matmul 的地方,只要出现这个模式,也会被识别出来。
可交互:一个 e-graph,三种 genome
下图是上面矩阵乘的 e-graph。虚线框是 e-class(一组等价计算),实线框是 e-node(一种写法)。搜索里的 genome 就是“每个 e-class 选哪个 e-node”。点按钮看不同 genome 选出的实现:
05深入:布局层(逻辑值 × 物理布局)
Luminal 把“算什么”和“数据在内存里怎么摆”分成两件事。逻辑 IR 里的张量没有物理布局(spec:只有输入输出有);在 e-graph 里,LayoutTensorLit(逻辑值, 布局) 把两者配对,同一个逻辑值可以有很多种布局写法,而且它们都在 e-graph 里互相等价。
后端规则只匹配硬件能直接用的那种写法。例如 CUDA 的 ReduceSum kernel 写的是 out[i],所以规则要求输出布局是行主序紧凑,并且映射是单射:
; crates/luminal_cuda_lite/src/ops/reduce_sum/match_functional.egg(节选)
(= ?out_layout (RightMajorContiguousElementLayoutLit ?out_rm_shape ?out_rm_bits))
(= ?out_logical (LogicalReduceSum ?in_logical ?reduction_dim))
(!= ?value_dtype (Int)) ; 整数单独一条规则:要处理回绕语义
(= (injectivity-of ?out_layout_tensor) (Injective))
; ⇒ union 成 LayoutTensorOpReduceSumGeneric(一个具体 CUDA kernel)
注释写道:“The e-graph joins equivalent spellings; the matcher matches the hardware's literal one.” 也就是说:等价性交给 e-graph,硬件约束交给匹配条件。转置、切片这类“视图”在这一层只是布局的改变,可以零拷贝;需要实体化时再插入 MaterializeLayoutCopy,是否插入同样由搜索决定。
逻辑值是书的内容,布局是排版:横排、竖排、双栏都是同一本书。e-graph 把所有排版都登记成“同一本书”;每台印刷机(kernel)只接受某一种排版的稿子。搜索要决定:直接用现成的排版送印,还是先花时间重排一遍(layout copy)再送去更快的印刷机。这和你在 G2 里学的 CuTe Layout(坐标 → 偏移的函数)是同一类思想。
06深入:搜索(遗传算法 + 真机实测)
为什么不像 egg 那样用代价函数做贪心提取?因为在 kernel 层面代价不可加:同一个 matmul,用 cuBLASLt 本身更快,但如果它要求的布局导致前后各多一次拷贝,整体可能更慢;融合、内存复用、launch 开销都是全局性质。Luminal 的选择是:不建模,直接测。
CompileOptions 的默认值:8 代 × 每代 8 个候选、每个后代 2 处突变、3 次计时、保留 4 个后备。被拒的候选(提取失败、超显存、超时)分门别类计入 RefusalBreakdown。几个细节值得注意:
- 无环保证:e-graph 本身可以有环(
x ≡ x+0),genome 的选择必须构成 DAG。采样和变异都维持这个不变式(mutate_genome里的flip_closes_cycle)。 - 相关采样:源码注释写道,独立地为每个位置抽选择,在大型重复图上“几乎永远抽不出一个连贯的实现”,因为每个候选里总会混进几条巨大的实体化路线。于是还有一种抽法:随机排一个“实现族”顺序,整个 genome 共用这个顺序(
sample_genome_correlated)。 - “描述”与“决定”分离:core 只负责“把 genome 变成图”和“怎么抽 genome”,不做任何选择;怎么给候选定价、循环怎么跑,归每个 runtime 自己(lib.rs 顶部注释)。
- 搜索不负责正确性:spec 明确规定,所有可提取的图都必须等价,搜索只比快慢,不允许“绕开”一个已知错误的候选。错了就去修规则。
// crates/luminal_cuda_lite/src/search.rs
impl Default for CompileOptions {
fn default() -> Self { Self {
generations: 8, generation_size: 8, mutations: 2, trials: 3,
seed: 0, keep_finalists: 4, candidate_timeout: None,
device_budget_bytes: None, /* 默认取可用 CUDA 显存 */ .. } }
}
代价模型像读汽车参数表:马力、扭矩都很漂亮,但上了真实路况(布局拷贝、launch 开销、缓存)不一定快。Luminal 每一代挑 8 辆车去同一条赛道各跑 3 圈,留下最快的几辆,把它们的零件(每个 e-class 的选择)随机换掉两处,再跑下一轮。缺点也一样:试驾很花时间。
07深入:厂商库也是搜索空间里的普通候选
“不写手写 kernel”不等于“不用手写 kernel”。cuBLASLt 调用在 Luminal 里就是一种 e-node,和生成的通用 kernel 平起平坐,由搜索决定用不用。难点在于:库调用的参数空间(转置、主序、leading dimension、batch stride、精度、epilogue)很大,而且每种组合在什么条件下合法,必须被严格证明。
当前做法分四步,每步一组 egglog 规则(cublaslt/README.md):
- 算术识别:认出
D = α·op(A)·op(B) + β·C加可选 epilogue(bias、ReLU、GELU)。这一步只是“命名”,不代表允许执行。 - 布局证明:证明某个存储张量确实按某个 vendor 描述符(7 个字段:dtype、主序、行、列、ld、batch 数、batch stride)寻址。
- 精度证明:只有作者显式写出的精确扩宽才允许直接读 BF16 / FP16;存储格式不能决定计算精度。
- 准入:上面这些事实再和白名单里的某一条完整配置对上,才生成可执行节点。白名单有 2,176 条完整配置,文件 approved_whitelist.egg 有 21,787 行。空白名单就什么都不生成。
认出“这是一箱电子产品”(识别)不等于可以入境。还要有装箱单(布局证明)、原产地证明(精度证明),而且商品编码必须在海关的准入清单上(白名单)。任何一张单据缺了,这箱货就留在仓库(不生成可执行节点),搜索就只能走通用 kernel 那条路。
和 FlashInfer 的关系:README 的“Where are we”里写着 “Many kernel libraries supported in the search space (FlashInfer, cuBLASLt, etc.)”,这是旧管线的状态。BRINGUP.md 把旧 crate 描述为一个“零件库”(cuBLASLt / FlashInfer / MoE、CUDA Graph),并说明新管线是按 CL-3 阶段逐个迁回来的。目前迁回来的只有 cuBLASLt。环境变量 LUMINAL_FLASHINFER_DIR 等仍留在 README 中。
08深入:内存规划与动态形状
DPS + one-shot bufferization
提取出来的图里,中间值还没有存储。Luminal 先把每个算子改写成“目标传递”风格(DPS:输出缓冲区作为参数传入),再用 MLIR 风格的 one-shot bufferization 决定原地复用还是新分配。冲突引擎检查三件事:可写性(不能写只读绑定)、写后读(所有读必须在 DAG 上可证明地先于覆盖写)、跨操作数共享许可(只有算子自己声明了 Sharing::May 才允许)。被拒的原地复用一律“修复”成新分配加必要的拷贝,所以拒绝永远不是错误。
每个中间张量是一位住客,入住和退房时间(生命周期)事先都知道。排房员(bufferize)尽量让新客人住进刚退的房间(原地复用),但有两条铁律:房间还有人要回来拿东西(后续还有读)时不能让给别人,标着“禁止入住”的房间(只读输入)不能用。排不下就另开一间(新分配),需要的话先把行李搬过去(拷贝)。
显存剪枝
搜索之前,CUDA 后端会对 e-graph 做一次强制的内存剪枝:在整个声明的动态维度范围内,物理容量超过显存预算的张量连同它们的生产者实现一起删掉,边界张量不能删(egraph_postpass.rs)。这样搜索根本不会抽到“需要 200 GB 中间结果”的路线。这属于“删掉不可行的候选”,不违反“不在 Rust 里做模式匹配和选择”的规定。
符号维度与分桶
形状是整数表达式(c + q、ceil(n/128)),egglog 前导程序里有一整套带值域的整数代数:截断除法与向上取整除法分成不同构造器,除法只在能证明除数非零时才参与合并。运行时给出 dyn_map(如 {q:1, c:812}),按所在 bucket 分发到对应编译结果。spec 要求:在代表点上测出来的最优计划,必须在整个声明范围内都合法。
09自动“发现” FlashAttention:说法与现状
这是 Luminal 最出名的一句话:不靠手写算子,靠搜索自动找到 FlashAttention 这类复杂优化。按作者在 Show HN 帖子中的解释,路径是:重写规则里既有循环 / 分块结构的改写,也有代数改写,其中关键一条是 softmax → online softmax。两者组合之后,搜索空间里就出现了“按 KV 分块、在块间重缩放、S 矩阵不落显存”的写法,也就是 FlashAttention 的结构。
这也揭示了这条路线的一个本质约束:搜索只能找到规则能到达的写法。SEARCH_DIAGNOSIS 原话:“Increasing generations/population explores combinations of existing producers; it does not extend the rewrite vocabulary.” 所以能力上限取决于规则库,而“该写哪些规则”本身仍然需要人的洞察。HN 讨论中也有人指出,等价保持的搜索很难找到 Strassen 这类“换算法”的优化。
10难点与解法总表
| 难点 | 为什么难 | Luminal 的做法 | 源码 / 证据 |
|---|---|---|---|
| e-graph 爆炸 | 规则越多,等价类和节点越多;有的规则(如结合律、交换律)会无限生成新项 | 规则全部放进命名 ruleset,并按阶段调度;整数代数有一整套“fence doctrine”控制哪些模式能命名哪些项,目的是让规则代价可控;旧 spec 里还有 loop rolling,把重复的层压成一个循环体再饱和 | spec.md · Rule conventions · preamble 的 DOCTRINE 注释 |
| 代价不可加 | kernel 的快慢取决于前后布局、融合与内存,传统的逐节点代价提取不准 | 不建模:genome 采样 + 遗传算法 + GPU 实测;相关采样提高抽到连贯候选的概率 | sample_genome_correlated |
| 每个候选都必须正确 | 搜索会挑出任何一个可提取的图,只要有一条规则在某个边角形状、dtype 或动态维度下不成立,就会出错 | 规则是“带前提的等价”:除法需要值域证明除数非零,整数有回绕规则,布局要证明单射;禁止在 Rust 里对提取后的图做后处理;CPU ReferenceRuntime 作为语义标准,PyTorch 对拍测试套件 | AGENTS.md · reference runtime |
| 库调用的合法性 | cuBLASLt 参数组合多,每种在什么布局 / 精度下合法很微妙 | 识别 → 布局证明 → 精度证明 → 白名单准入,分层证明,2,176 条配置 | cublaslt/egg/ |
| 布局与视图 | 转置、切片、广播要零拷贝,又要让 kernel 拿到它能用的布局 | 逻辑值与布局分离;布局阶梯自动发现;视图与实体化拷贝都是候选,由搜索决定 | layouts.rs |
| 显存 | 有些候选的中间结果在长上下文下会超出显存 | 搜索前按完整动态范围剪掉不可能的实体化;候选 arena 分配前再检查一次 | egraph_postpass.rs |
| 动态形状 | 静态编译与运行时变化的 batch / seq 长度矛盾 | 符号维度 + 分桶,每个 bucket 单独搜索,运行时按 dyn_map 分发 | bucketing.rs |
| 搜索很贵 | 每个候选都要编译 + 分配 + 预热 + 计时;作者在 HN 上说典型是 10 分钟到 10 小时 | NVRTC 模块缓存(编译时间不计入候选超时)、早停、保留后备;作者提到计划用 MCTS / RL 和按后端记忆化 | search-timing RESULTS |
11现状:仓库自带的实测数字
以下数据来自仓库中的验证报告(2026-09-21,NVIDIA H200,batch 1,FP32,128 输入 / 128 输出 token,3 次热请求取中位数):
| 模型 | TTFT (ms) | TPOT (ms/token) | 编译搜索 (s) | cuBLASLt 实例 | 通用 attention 乘积 |
|---|---|---|---|---|---|
| Qwen3-0.6B | 842.97 | 41.84 | 163.16 | 194 | 56 |
| Qwen3-4B | 3,524.11 | 144.41 | 189.69 | 254 | 72 |
| Llama3-8B-Instruct | 5,847.96 | 221.26 | 252.83 | 229 | 64 |
| Gemma3-4B-IT(文本) | 4,095.09 | 167.18 | 377.13 | 212 | 68 |
估算一下差距:Llama3-8B 的 FP32 权重约 8.03 B × 4 B ≈ 32 GB,H200 的 HBM 带宽约 4.8 TB/s,所以 decode 每 token 的带宽下限约 6.7 ms。实测 221 ms,约为下限的 33 倍,带宽利用率约 3%。这是我的粗略估算,不是仓库给出的数字,也忽略了 KV cache 读取,但量级足以说明:新管线目前关注的是正确性和架构,性能还没跟上。
搜索本身也很贵。仓库的一次计时分析里,Llama3-8B 的搜索用了 347 s,其中 60.5% 花在 pinned 主机内存的分配、清零和释放上,并不是在 GPU 上计时。下面是其中一个候选的耗时分解(按比例绘制):
正确性方面比较扎实:Metal 报告里,Llama3 / Qwen3 / Gemma3 / Qwen3-MoE 的小型实例与 ReferenceRuntime 的 logits 误差都小于 1e-4;CUDA 上四个模型 128 个 token 输出前后一致。README 中 “Q8 Llama 3 8B ~80% of theoretical max on H100”,以及 HN 帖子中 “M 系列 Mac 上 Q8 Llama 3 8B 15–25 tok/s”,对应的是旧管线(作者在 HN 上也承认当时还比不上 llama.cpp 这类手调实现)。
12与其他编译器 / 系统对比
| 系统 | 表示 | 怎么选实现 | 和 Luminal 的关系 |
|---|---|---|---|
| XLA / torch.compile (Inductor) | HLO / FX 图,破坏式 pass | 启发式规则;Inductor 生成 Triton,再对 Triton 配置做自动调优 | Luminal 直接批评的对象:规则必须确定有收益才触发 |
| TVM / Ansor | Tensor Expression + schedule | 代价模型 + 进化搜索,只针对单个算子的调度 | 作者说 Luminal 是“autotuning on steroids”:搜索的是整图的等价结构,不只是单个 kernel 的参数 |
| TASO / Tensat | 计算图替换;Tensat 用 e-graph | 代价模型 + ILP 提取 | 最接近的学术前身;Luminal 用实测代替代价模型,把重写下沉到布局和 kernel 层 |
| Mirage(OSDI ’25) | 多层 μGraph(kernel / block / thread) | 穷举超优化 + 概率等价验证 | 同属“搜索出融合 kernel”;Mirage 搜子图结构,Luminal 靠重写规则生成空间 |
| tinygrad | 极小原语集(UOps) | BEAM 搜索 kernel 优化参数 | README 点名同路人:小原语集、懒执行;tinygrad 没有 e-graph |
| FlashInfer / cuBLAS / CUTLASS | 手写 / 模板 kernel 库 | 人工 + 运行时 plan/选择 | 在 Luminal 里是搜索空间中的候选 e-node,而不是对手 |
13评价与借鉴
做得好的
- 问题拆分非常干净:规则只管“对不对”,搜索只管“快不快”
- 正确性纪律严格:带前提的等价、禁止后处理、参考实现对拍
- 厂商库作为候选,和生成的代码公平竞争
- 大量设计决策写在源码注释和验证报告里,连负面结果也记录
风险与局限
- 能力上限由规则库决定;attention / RoPE 的高阶乘积目前匹配不上
- 当前分支性能远低于带宽上限,FP32 为主
- 搜索时间以分钟到小时计,不适合频繁换形状、换模型的场景
- egglog 规则体系门槛高,注释里大量内部决议,外部贡献者不容易进入
对你有用的
- 把 online softmax / merge_state 写成“代数等价”的视角,可以用来讲解 FlashAttention
- 布局阶梯 + 单射证明,和 CuTe layout algebra 相通
- “库调用准入证明”的分层思路,可以借鉴到 FlashInfer 的 API 选择和测试
- 面试中谈编译器 vs 手写 kernel 时,这是一个很好的反方案例
手写 kernel 库是老司机记在脑子里的近路:快,但每个城市都要重新学。Luminal 想做的是导航软件:把所有合法道路都画进地图(e-graph),再实时测路况选最快路线(实测搜索)。导航能有多好,取决于地图画全了没有(规则库)和测路况花多少时间(搜索成本)。目前它的地图还缺几条高速公路(融合 attention),测路况也偏慢。
14源码阅读路线(约 3 小时)
- 想法(20 分钟):README → spec.md(注意它已过时,但契约部分仍然成立)→ AGENTS.md 的 Debugging 和 Rewrite Boundary 两节。
- 一个算子的完整生命周期(40 分钟):logical/op/add/ 的 10 个
.egg文件 → CUDA 侧 cuda_lite/ops/add/ → KernelOp 怎么生成源码。 - 模式识别(20 分钟):matrix_multiply/ 的识别与展开 → cublaslt/README.md。
- 提取与搜索(40 分钟):Genome → sample_genome / mutate_genome → search_implementations。
- 内存(30 分钟):bufferize.rs 文件头的长注释(冲突引擎讲得很清楚)。
- 诚实的现状(20 分钟):SEARCH_DIAGNOSIS.md 和 BRINGUP.md。
- 动手:
cargo test -p luminal --lib(不需要 GPU);在 5090 上可以试cargo run --release -p luminal_cuda_lite --example llama3 --features device,加LUMINAL_LOG=1看搜索过程,加LLIR_DUMP_DIR=…导出选中的计划。