AI 编译缓存:命中同一张图之前,先确认输入形状稳定

AI 编译缓存:命中同一张图之前,先确认输入形状稳定 AI 编译缓存命中同一张图之前先确认输入形状稳定一、编译缓存能省时间也能缓存错误假设AI 编译器会把计算图优化成更适合目标硬件的执行计划。编译过程昂贵所以服务端常加编译缓存。相同模型、相同图、相同形状直接复用 plan。问题在于动态图和可变 batch 很容易让缓存 key 失真。如果 key 只包含模型版本不包含输入形状、dtype、目标后端和优化开关就可能复用不兼容的 plan。轻则性能异常重则输出错误。编译缓存的第一原则不是命中率而是正确性。二、缓存 key 要表达所有影响 plan 的变量一个稳定 key 至少包含图哈希、输入形状签名、dtype、后端版本、优化级别和算子实现版本。flowchart TD A[计算图] -- F[Compile Key] B[输入形状] -- F C[dtype] -- F D[后端版本] -- F E[优化开关] -- F F -- G{缓存命中} G --|是| H[复用执行计划] G --|否| I[重新编译] I -- J[写入缓存]动态维度可以归一化但必须有明确规则。比如只允许 batch 动态序列长度分桶。不能把所有-1都当成同一种形状。三、Rust 中用结构化 key 避免字符串拼接错误缓存 key 不要靠手写字符串拼接。结构化后再序列化和哈希更容易审计。#[derive(Debug, serde::Serialize)] pub struct CompileKeya { pub graph_hash: a str, pub shape_signature: a str, pub dtype: a str, pub backend: a str, pub opt_level: u8, } pub fn cache_key(key: CompileKey_) - anyhow::ResultString { let bytes serde_json::to_vec(key)?; let digest blake3::hash(bytes); Ok(format!(compile:{}, digest.to_hex())) }这样新增字段时代码评审能清楚看到 key 变化。字符串拼接很容易漏字段。结构化 key 的另一个好处是支持部分匹配策略。在实际部署中同一计算图可能因不同优化级别存在多个 plan 变体opt_level0的 plan 在opt_level2查询中不应命中但 LRU 驱逐可以按graph_hash做分组对同图的所有变体使用共享容量配额避免某模型因多次形状变化吃光全部缓存。blake3 的选择也值得说明相比 SHA256blake3 在 x86 上有 SIMD 加速、单次计算微秒级适合频繁 key 生成相比 XXH3它是密码学哈希、不需要担心恶意碰撞攻击。但对千万级 key 规模文件系统层碰撞仍需考虑——建议在 key 之外保留 raw 字段副本在get_or_insert时做二次确认避免哈希碰撞返回错误的执行计划。四、缓存失效要跟发布流程绑定后端编译器升级、算子实现替换、优化 pass 调整都应该触发失效。不能只靠 TTL。否则旧 plan 会在新运行时里继续被使用问题很难定位。还要记录编译失败原因。某些形状不支持应该回退解释执行或拒绝请求而不是无限尝试编译。失败缓存同样有价值可以避免同一个非法形状反复打爆编译线程。最后缓存命中要分桶看。总体命中率高可能掩盖长尾形状持续编译。服务端要监控编译耗时、失败率和 key 基数。key 基数失控说明输入形状没有被治理。缓存容量也要有限制。编译 plan 往往不小长尾形状如果无限写入会把内存或磁盘打满。可以按模型版本和后端分区设置 LRU并给高频形状预热。预热失败也要阻断发布否则流量进来后会集中触发编译。多进程部署还要考虑缓存一致性。每个进程各自编译启动时会造成编译风暴。可以使用共享磁盘缓存或编译服务但写入必须原子化。半写入的 plan 被另一个进程读取比未命中更危险。五、总结AI 编译缓存要先保证 key 正确再追求命中率。图哈希、形状、dtype、后端版本和优化开关都应进入结构化 key。缓存失效要绑定编译器发布流程失败也要被记录。编译缓存不是简单的性能层它是执行正确性的一部分。