 并测得实打实的加速)
kornia 开发者的 compile-first 优化工作流让算子真正兼容 torch.compile(fullgraphTrue) 并测得实打实的加速【免费下载链接】kornia Geometric Computer Vision Library for Spatial AI项目地址: https://gitcode.com/GitHub_Trending/ko/kornia本篇技术指南以 kornia 仓库中面向开发者contributor的《kornia-developercompile-first optimization》技能规范为骨架完整讲解如何在 kornia 中把一个算子op改造成真正torch.compile(fullgraphTrue)兼容、且在 eager 与编译两种路径下输出逐字节一致的函数。文章覆盖了图中断graph break的六类分类与对应「单路径」修复方案、禁止is_compiling()兜底的根本原则、强制的前后性能基准benchmark实验规范、跨库对比矩阵与「10× vs albumentations」目标以及把成果用test_dynamo与 PR 时间dynamoCI 任务锁死的落地流程。读完你可以直接照此工作流处理 kornia或同类 PyTorch 库中的任意编译失败并产出可进入 PR 的完整证据链。为什么需要一条「刚性」的 compile-first 工作流kornia 的数值核心filters、color、geometry transforms、losses、morphology、metrics大部分已经可以编译通过但「能编译」与「编译后真正等价且更快」是两回事。该技能文档把目标定义得很明确让一个 kornia 函数真正torch.compile(fullgraphTrue)兼容并且可测量地更快。这是一条「刚性rigid」流程——每一步都必须执行因为它建立在一条首要原则上先有地基再有绿灯测试foundation, not green tests。一个通过的测试只是证据永远不是目标。流程中每一步的存在都是为了让你声称的事实为真如果你发现自己只是在调测试让它变绿却没有理解代码为什么现在能工作请停下——你正在沙子上盖楼。这条原则的由来是仓库踩过的三个具体陷阱SKILL.md 中逐一列出测试可以通过却从未真正检验所声称的事实。用预生成的参数调用op(x, paramspregenerated_params)会跳过forward_parameters于是一个「fullgraph」的 dynamo 测试即使通过也从未 trace 过参数生成路径。必须测试真实用户路径op(x)而不是绕过所声称修复代码的捷径。这一点在kornia/augmentation/base.py中体现得很清楚forward_parameters第 293-303 行内部调用__batch_prob_generator__再调用generate_parameters参数生成本身就是一个蕴含torch.rand、torch.where的图节点不编译它等于没有编译完整的 op。「在我机器上能跑」不等于「能跑」。int(tensor)在某些 torch 版本如 2.10下会在 dynamo 中被常量折叠constant-fold在另一些版本如 2.9.1下则降级为打断图的.item()。只在本地 torch 上验证过的 fullgraph 声明如果 CI 跑的是另一个版本就毫无价值。结论以 CI torch 版本PR 的 dynamo job为准并且优先采用构造上与版本无关的修复——用张量构造张量如torch.stack拼接标量0/1绝不使用int(tensor)或torch.tensor([[... python int ...]])。删除死分支前先理解它为什么是死的。tensor torch.Size(...)不会广播而是退化为恒等静默地永远是False。代码可能在某个从不运行的分支下「正常工作」。要诊断机制本身而不是套模板式地修。铁律禁止is_compiling()兜底torch.compiler.is_compiling()守卫被明令禁止作为修复手段。原因在于它让编译路径跳过数据相关代码而不是让它可 trace于是 eager 与 compile 运行的是不同路径编译路径下的检查被静默丢弃。这是「靠排除法实现 fullgraphfullgraph by exclusion」不是真正的 fullgraph——而且它让 op 只被部分编译直接杀死加速效果。修复必须让 eager 与 compile 运行同一条代码路径。六类图中断的分类与「单路径」修复表遇到 graph break 时首先torch.compile(fn, fullgraphTrue)(x)精确定位断点必须是kornia/…内的 traceback 帧然后对照下表分类处理断点形态修复方案单一路径if (t 0).any(): raise——取值校验torch._assert_async((t 0).all(), msg)—— 检查保留且不产生断点bits0/8风格的逻辑分支分支条件依赖张量无分支的torch.where(cond, a, b)—— 两个分支都计算再选择if not mask.any(): break—— 定界循环里的提前退出直接删除尾部迭代必须可证明是 no-opif mask.all(): return None—— 返回不同类型的提前返回始终返回该值让调用方对该值的消费变成 no-opif batch_prob.sum()1—— 对 {0,1} 张量的门控无分支乘法gate * valuePythonif p 1其中p是Pythonfloat/bool保留原样 —— trace 期就已解析不算断点如果六类都不适用无界while、由.item()驱动的 shape、随机置换分发、动态 shape 的nonzero/unique说明它需要重新设计或维护者决策——不要强行打补丁记录下来并停下。对照源码可以看到这些方案在 kornia 中的落地形态torch._assert_async替代数据相关校验kornia/enhance/adjust.py、kornia/enhance/equalization.py、kornia/augmentation/_2d/intensity/equalize.py、kornia/augmentation/_2d/intensity/clahe.py均使用它。例如equalize.py的注释明确说明该函数内部调用kornia.enhance.equalize期望输入在[0, 1]对无法均衡化的值抛出RuntimeError该检查正是torch._assert_async它从 torch 2.13 起在 MPS 上有内核而在更老的 MPS 版本上条件会被拿到 host 端读取——每次调用多一次设备同步且在torch.compile下被跳过。这说明选型时不仅要考虑编译还要考虑设备CUDA/MPS与版本差异。无分支门控gate * valuekornia/augmentation/base.py的__batch_prob_generator__第 210-244 行正是文档表格中「对 {0,1} 张量的门控」一行的实例。旧代码中if batch_prob.sum() 1是数据相关的分支现在被替换为无分支组合batch_prob batch_prob * elem_prob——batch_prob是 shape(1,)、取值{0, 1}的张量选中批次得到elem_prob未选中得到全零与旧分支结果完全一致却没有图中断。函数内的p 1、p 0、same_on_batch分支因为是 Python 值trace 期即解析不构成断点予以保留。这个单点修复解除了约 13 个增强算子的编译阻塞是「共享修复」杠杆价值的典型代表。删除数据相关的提前返回kornia/losses/_utils.py的mask_ignore_pixels第 25-59 行是「返回不同类型的提前返回」一行的实例。旧实现可能对target_mask全True没有像素被忽略时提前返回新实现去掉数据相关的 early-out始终返回 mask——没有像素被忽略时 mask 全True调用方的 masking 是 no-opwhere保持target不变与旧返回结果一致但可 fullgraph 编译。ignore_index is None时直接返回(target, None)的检查保留因为ignore_index是 Python 的Optional[int]。这一处修复解除了 dice/focal/tversky 等损失的编译阻塞。验证契约fullgraph eager且 eager 逐字节不变图中断修复后要做两个相互独立的检查SKILL.md 第 3 步编译与 eager 在同一条路径上一致torch.allclose(torch.compile(fn, fullgraphTrue)(x), fn(x), atol1e-5)。修复不得改变 eager 输出哪怕一个字节在同一 seed 下分别保存main分支与你的分支上fn(x)的输出用torch.equal(old, new)断言逐字节相同——不是allclose。这是契约编译重构只有在现有用户拿到完全相同的数字时才可接受如果无法做到逐字节一致例如真正新增的 opt-in 模式必须把改动门控在新的参数后面让默认路径保持逐字节一致。# on main: torch.manual_seed(0); torch.save(fn(x), old.pt) # on branch: torch.manual_seed(0); assert torch.equal(fn(x), torch.load(old.pt, weights_onlyTrue))对边缘逻辑的无分支改写要穷尽边界输入验证例如bits的每一个取值 0..8而不是只抽一个样本。凡是碰 RNG 的地方augmentation base、generator一次抽样的顺序变化都会破坏逐字节一致性——此时要以整个模块的测试套件原样通过作为佐证见第 6 步。基准测试是实验不是感觉每一个被改动的函数都必须做前后基准测试SKILL.md 第 4 步。一个没加速任何东西或拖慢 eager的编译修复必须给出理由。基准测试要用实验标准来对待import torch.utils.benchmark as bench def us(f, *a): return bench.Timer(stmtf(*a), globals{f: f, a: a}).blocked_autorange(min_run_time1.0).median * 1e6 eager us(fn, *args) c torch.compile(fn, fullgraphTrue) c(*args) # warmup —— 首次调用包含编译 allocator cudnn autotune comp us(c, *args)让数字可信的方法论偏离即等于在比噪声计时前必须 warm up首次调用付出编译/autotune/懒初始化成本。绝不给单次调用计时——用blocked_autorange统计性的多次中位数你报告的是信号而不是调度器抖动。前后对比必须在同一台机器、同一进程、背靠背进行。永远不要拿一台机器的数字去对比另一台。torch.utils.benchmark会处理 CUDA 同步手写的time.time()包住 CUDA 调用测的是发射延迟不是计算时间。使用真实形状与 batch增强算子建议(32,3,256,256)——(1,3,8,8)的玩具输入会放大 Python 开销、掩盖 kernel 成本。记录数字时要同时记录硬件、torch 版本、commit、日期Jetson 这类边缘芯片的数字只具方向性GPU 领先性的论断需要数据中心级 GPU。确认改写没有拖慢 eager无分支「两个分支都算」与删除提前退出都会给 eager 增加工作量——要测出来。把前后对比表放进 PR。跨库对比孤立数字毫无意义kornia 的数字不能脱离其他库单独解读。使用仓库现成的基准脚本不要手搓benchmarks/augmentation/flagship.py—— 旗舰套件通过每个库的随机变换类 API包含参数采样对比 korniaeager compiledvs torchvision v2、albumentations、OpenCV、PIL支持--json导出。benchmarks/augmentation/cross_library.py—— 聚焦的三方逐算子对比。benchmarks/augmentation/pipeline.py—— 端到端多算子流水线吞吐训练循环的真实形态支持--compile与--halffp16/AMP。benchmarks/filters/flagship.py、benchmarks/geometry/flagship.py—— 函数式算子的对应套件。动手前先读benchmarks/augmentation/README.md——它记录了各性能区间的划分与常设的改进清单也是持久结果与诚实解读的存放处当你移动一个数字时要更新它。运行示例该 README 给出python benchmarks/augmentation/flagship.py --batches 1,8,32 --size 256 --threads 4 --device cpu --compile --json aug_cpu.json python benchmarks/augmentation/flagship.py --batches 1,8,32 --size 256 --threads 4 --device cuda --compile --json aug_cuda.json python benchmarks/augmentation/pipeline.py --batch 32 --size 224 --device cuda --compile每个脚本都会按benchmarks/README.md的方法论契约打印 git commit、平台、CUDA 下设备名未安装的可选库会被报告为 skip 行而不是让运行失败。诚实地阅读列性能区间不是同一种竞赛benchmarks/augmentation/README.md中的「regimes」表说明这些库解决的不是同一个问题单读某一列就断言「谁赢了」是误导库数据批量设备可微分korniafloatBCHW张量✅ 批量CPU或 GPU✅ 是torchvision v2floatBCHW张量✅ 批量CPU 或 GPU❌ 否albumentationsuint8HWCnumpy❌ 逐图循环仅 CPU❌ 否OpenCVcv2uint8HWCnumpy❌ 逐图循环仅 CPU❌ 否PILPillowuint8HWC图像❌ 逐图循环仅 CPU❌ 否kornia-rsuint8HWCnumpy❌ 逐图仅 CPU原生 Rust❌ 否由此得到两个泾渭分明的竞赛CPU /uint8/ 单图——albumentations、OpenCV、PIL 与 kornia-rs 的领地是经典数据加载器区间SIMD/原生后端在此取胜。kornia 的float路径不是为此设计的不应期望领先。GPU /float/ 批量 / 可微分——kornia 的主场。torchvision v2 在原始吞吐上可以竞争但不可微分其他库根本跑不了这里。这是 kornia 设计上要领先的区间也是torch.compile融合能兑现收益的地方。按此诚实口径陈述所在区间OpenCV / kornia-rs 赢 CPU/uint8/单图torchvision v2 赢原始 float 张量吞吐kornia 的区间是 GPU 批量 可微分 编译——也是唯一存在可微分、设备内on-device增强的区间。10× vs albumentations 的登月目标增强包的性能目标是对 albumentations 的 10×——每次性能修复都要以此为目标框架。现状是 kornia 在 CPU 上落后能缩小乃至反转差距的杠杆有三条(a)torch.compile即你刚做的修复约 2–3×(b)uint8快速路径albumentations 的全部优势就是 uint8 OpenCVkornia 会升到 float32(c) 把热点算子下沉到kornia-rsalbumentations 无法匹敌的 Rust 后端。一个只做到持平的编译修复只是通往 10× 的一步不是终点——PR 里要说明还有哪条杠杆在桌上。从benchmarks/augmentation/README.md记录的实测可见这些杠杆的现实佐证在 2026-09-18 的 Intel i7-14700K RTX 4090 快照batch 32img/s中CUDA 编译使 RandomGaussianBlur 提升 4.78×、ColorJiggle 提升 3.15×但编译也会输——CPU 上 GaussianBlur 为 eager 的 0.93×、水平翻转 0.75×、仿射 0.45×。这正说明「编译不是所有工作负载的最优解」任何修复都必须在真实区间里衡量。同一文档还记录了 Jetson Orin 上torch.compile的✗是 Jetson wheel 的 CUDA 驱动限制、Resize的nan是 Orin wheel 的cusolver缺陷——这类数字必须附上设备 caveat 再引用。持久、可引用的数字存放在benchmarks/results/渲染在docs/get-started/performance新增结果后用python docs/generate_benchmarks.py --refresh-llms刷新 LLM 摘要。锁定成果test_dynamo 与 PR 时间的 dynamo CI第 5 步是把修复锁进回归防线。为被改动的 op 添加/确认test_dynamo且必须锻炼真实路径——编译完整前向torch.compile(op, fullgraphTrue)(x)不传预生成的params而不是只编译传入参数的apply_transform这样参数生成也被覆盖。这一点在tests/augmentation/test_augmentation.py中有直接的测试形态torch.compile(RandomHorizontalFlip(p1.0), fullgraphTrue)(input).shape input.shape的断言之外还显式torch._dynamo.reset()后再编译完整前向注释写明「同时编译完整前向内部生成参数使参数生成路径也是 fullgraph而不只是 apply_transform」。PR 时间PR-time的dynamoCI 任务.github/workflows/pr_test_cpu.yml现在会在CI 的 torch 版本上用inductor后端跑一遍编译干净的核——主测试矩阵默认会跳过这些用例KORNIA_TEST_OPTIMIZER时被 deselected因此本地运行必须显式设置KORNIA_TEST_OPTIMIZERinductor否则这些测试不会真正执行如果你把算子加进了 scoped-core 目录CI 任务会自动覆盖它——要在那里确认通过而不是只在你本地 torch 上通过。该 CI 任务的pytest-extra明确为-k dynamo or compile覆盖 tests/augmentation、tests/enhance、tests/feature、tests/filters、tests/color、tests/geometry、tests/losses、tests/morphology、tests/models 等目录。验证整个套件未变然后按规范提交 PR第 6 步用--dtypefloat32跑该 op 的完整测试文件。对于共享 helper 或 augmentation base要跑整个模块的测试套件——无分支改写可能改变 RNG 的消耗顺序例如原本条件式的抽样现在总是执行套件原样通过才是「没有 seed 化测试依赖它」的证明。这一步与第 3 步的逐字节断言互为印证逐字节保证单个 op 的输出模块套件保证 RNG 顺序变化没有渗透进任何已 seed 的测试。第 7 步一个 PR 只放一个 op或一个共享 helper。PR 正文必须包含断点the break、真正的修复the genuine fix、fullgraph eager 的证据、以及基准对比表。最后用.pre-commit-config.yaml中锁定的版本跑ruff check与ruff format --check保持零告警。杠杆优先共享修复胜过逐算子修补收益最大的永远是共享 helper / 基类——一处真正的修复能解锁许多算子。SKILL.md 给出的两个实例都在源码中得到了印证kornia/augmentation/base.py的__batch_prob_generator__门控 → 解锁约 13 个增强算子该函数由forward_parameters调用处于每个随机增强算子的必经路径上kornia/losses/_utils.py的mask_ignore_pixels→ 解锁 dice/focal/tversky 损失。因此当一个算子编译失败时先 trace 到最深的kornia/帧——断点往往在某个共享工具里在那里修杠杆远高于逐算子修。动手之前先扫描已有的编译干净核SKILL.md 最后提醒kornia 的数值核心大部分已经可编译filters、color、geometry transforms、losses、morphology、metrics。在假设存在断点之前先扫描确认——把精力投在真正需要修复的地方而不是重复解决已解决的问题。小结一份可直接执行的编译修复清单复现断点torch.compile(fn, fullgraphTrue)(x)捕获精确的 graph-break 位置kornia/…内的 traceback 帧绝不猜测。分类并修复对照六类断点表应用单路径修复is_compiling()一律禁用不适用任何一类时重新设计或请示维护者不要强打补丁。双验证编译与 eager 同路径allcloseeager 输出与main逐字节torch.equalRNG 相关改动以模块套件通过佐证。基准前后对比warm up blocked_autorange 同机背靠背 真实 shape记录硬件/版本/commit/日期用benchmarks/现成 harness 做跨库对比按区间诚实解读对照 10× 目标说明还有哪条杠杆未动。锁定test_dynamo必须覆盖完整前向含参数生成本地以KORNIA_TEST_OPTIMIZERinductor运行确认 PR 的dynamoCI 在 CI torch 版本上通过。套件回归op 测试文件以--dtypefloat32全绿共享 helper 跑整个模块套件。提交 PR一 op 一 PR正文含断点、修复、等价证据、基准表ruff双检查干净。【免费下载链接】kornia Geometric Computer Vision Library for Spatial AI项目地址: https://gitcode.com/GitHub_Trending/ko/kornia创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考