图与运行时
CPU 后端那一部分讲的是「给我张量和一个引擎,我马上算」。这一部分讲 traced 主线的中段:TracedTensor 上的运算什么也不算,只记图;GraphCompiler 把图编成一个程序;Runtime::prepare 针对一组引擎目标把程序准备好,Runtime::run 在调用方借出的引擎上执行。求导(grad / vjp / jvp)是在图上做的变换,放在自动微分部分;这里只关心图怎么来、怎么编、怎么跑。
代码在 tenferro-runtime/src/:traced.rs(traced 张量)、graph/(GraphCompiler)、program/(语义程序)、compiler/(降级与优化 pass)、segment.rs(分段)、runtime/(引擎目标、注册、快照、准备、缓存、调度、执行)。图的数据结构来自单独的仓库 computegraph-rs(crate computegraph 0.1.0),算子词汇表来自 tenferro-core-ops 和 tenferro-internal-ops。按 tag 数,tenferro-runtime/src 去掉 tests/ 目录和 tests.rs、*_tests.rs 文件后约 5.0 万行,computegraph 的 src 约 1000 行。
- computegraph:外部 crate 只管「图」:
GraphOperation只描述输入输出个数,值的身份是结构性的ValueKey,resolve→materialize_merge→compile顺手做了公共子表达式消除;它不懂求导,只给 AD 留了一个OperationRole标签;tenferro 用宏生成的StdTensorOp接进来,Operand是(),从不调用它的eval。 - TracedTensor:一个 traced 张量是「某张小图里的一个值」,每个运算新建一张只含一个节点的图,把操作数的图挂成父图;运算返回
Result;同一个越界的切片,形状已知时建图就报错,形状由输入说明给出时编译才报错,符号形状的叶子拖到运行时。 - 编译:
GraphCompiler::compile不碰后端,只产出语义程序和默认输入绑定;降级成执行 IR、跑优化 pass、折叠元数据视图、分段和规划逐元素区都在Runtime::prepare里;做了公共子表达式消除和死代码消除,没有常量折叠。 - 执行与缓存:
Runtime只保存配置快照和准备缓存,不持有引擎;prepare每次都先降级再查缓存,键里有语义指纹、运行时纪元、引擎和输入特化;run不查缓存,绑定引擎后按计划执行;实测一个 41 条命令的程序,prepare命中缓存约 88 µs,run约 22 µs。
flowchart TB tt["TracedTensor 运算"] --> g["computegraph Graph"] g --> gc["GraphCompiler compile"] gc --> cg["CompiledGraph"] rb["Runtime builder"] --> rt["Runtime 快照"] cg --> prep["prepare 降级 查缓存"] rt --> prep tgt["engine target"] --> prep prep --> pd["Prepared"] pd --> run["run 借用引擎"] eng["CpuEngine"] --> run run --> out["Output"] ad["grad vjp jvp"] -.->|变换图| tt
第一页是图的底座,第二页是往图里加节点的那一侧,后两页是把图变成结果的那一侧。图被求导变换之后再走一遍同样的编译和执行,见 traced AD;整条主线串起来走一遍见一次 grad。引擎本身(CpuEngine、线程池、缓冲池)见 CPU 引擎。
💬 关联讨论
发起讨论加载中...