news 2026/9/23 15:30:19

Kornia 修复 pinned-torch CI 中 triton 孤儿依赖导致的 Dynamo/ONNX 导出崩溃

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kornia 修复 pinned-torch CI 中 triton 孤儿依赖导致的 Dynamo/ONNX 导出崩溃

Kornia 修复 pinned-torch CI 中 triton 孤儿依赖导致的 Dynamo/ONNX 导出崩溃

【免费下载链接】kornia🐍 Geometric Computer Vision Library for Spatial AI项目地址: https://gitcode.com/gh_mirrors/ko/kornia

导读

本文剖析 Kornia 仓库中一次典型的"构建环境漂移"问题修复(对应 changelog.d/+migration-096.fixed.md):在 pinned-torch CI 矩阵中,uv sync依据 lockfile 安装的是 PyPI 版 torch,而矩阵各腿随后将 torch 单独替换为+cpu构建——该构建的 wheel 完全不声明triton,导致 lockfile 里的 triton 变成"孤儿",最终 torch 2.6.0 腿在 32 个 Dynamo / ONNX 导出测试的导入阶段集体崩溃。文章将完整还原问题机理、安装步骤的修复方案、环境校验脚本check_torch_env.py的实现细节,以及测试侧external_data=False的兼容性处理,读者读完可以掌握这类"按依赖元数据精确换装 torch"的 CI 加固套路,并可直接复用其中的环境一致性校验思路。

一、问题现场:2.6.0 腿在导入阶段集体阵亡

1.1 两个安装阶段的叠加效应

Kornia 的 tests.yml 构建 CI 环境分两步:

  1. uv sync安装 lockfile:此时安装的 torch 来自 PyPI,其Requires-Dist中在 Linux x86_64 + Python < 3.13 条件下声明了triton (==3.2.0)这类 sidecar 依赖(参见 test_check_torch_env.py 中的PYPI_WHEEL_REQUIRES);
  2. 矩阵腿单独换装 torchpinned通道执行uv pip install --reinstall-package torch --index-url https://download.pytorch.org/whl/cpu "torch==$PYTORCH_VERSION"(tests.yml),仅替换 torch 本身。

问题在于:CPU wheel 索引的+cpu构建声明了比 PyPI wheel 更窄的依赖集——完全不声明triton。changelog 明确注明这一点在 2.5.1、2.6.0、2.9.1 和 2.14.0 上均已核实(见 check_torch_env.py 的模块 docstring)。测试代码中CPU_WHEEL_REQUIRES变量给出了 torch-2.6.0+cpu wheel 的完整Requires-Dist列表,确实只有 filelock、typing-extensions、networkx、jinja2、fsspec、setuptools、sympy、opt-einsum、optree 等,没有 triton(test_check_torch_env.py)。

1.2 孤儿 triton 为什么会被 torch 捡起来

uv sync为 lockfile 的 torch(例如 2.14.0)装的 triton(3.8.0)在换装后仍然残留在虚拟环境中。更关键的是 torch 的分发机制:torch 对 inductor triton 路径的判断依据是"导入是否可用",而非版本是否匹配_is_triton_available()只测试import triton是否成功)。于是:

  • torch 2.6.0 换上后,发现环境中存在可导入的 triton,便走 inductor 的 triton 路径;
  • 但该 triton 是 3.8.0,早已移除了 torch 2.6.0 需要的from triton.backends.compiler import AttrsDescriptor这个名称;
  • 结果 32 个 dynamo 和 export 测试在导入阶段直接抛 ImportError——且这个腿从未绿过

这正是一个典型的"孤儿依赖被静默拾取"问题:安装元数据(wheel 声明)与运行时分发逻辑(按 import 可用性)产生了错位。

二、修复方案:安装步骤同步"瘦身",验证步骤整体断言

2.1 安装步骤:丢弃未声明的 sidecar 包

修复后,torch 换装步骤在完成--reinstall-package torch之后立即追加一次"对账"(tests.yml):

report=$(pixi run -e ... uv run --no-sync \ python .github/scripts/check_torch_env.py --list-undeclared-sidecars | tr -d '\r') # 结果行必须存在,否则视为解析失败而非环境干净 if ! printf '%s\n' "$report" | grep -q '^undeclared-sidecars:'; then echo "the sidecar check reported no result; refusing to assume the venv is clean" >&2 exit 1 fi stale=$(printf '%s\n' "$report" | sed -n 's/^undeclared-sidecars:[[:space:]]*//p' | tail -n 1) if [[ -n "$stale" ]]; then echo "Removing packages the installed torch does not declare: $stale" pixi run -e ... uv run --no-sync uv pip uninstall $stale fi

这段逻辑的关键点有三:

  1. 对账发生在换装步骤内部,而不是单独一个步骤。工作流注释明确解释:如果把对账放在独立的步骤里,它可能被重排序到换装之前——那时环境里还是 lockfile 的 torch(它声明triton),检查结果为空,问题就会在"全绿"的运行中幸存下来(tests.yml)。把对账放进换装自己的步骤里,顺序就"不可重排"了。
  2. 空捕获等于失败。脚本总是会打印undeclared-sidecars:前缀;若 grep 不到该前缀,说明解析坏了,工作流会拒绝假设环境是干净的。这个约定同时被 test_check_torch_env.py 的TestWorkflowWiring测试用grep -q '^undeclared-sidecars:'断言钉死。
  3. 允许名单是精心收缩的SIDECAR_PACKAGES = ("triton", "pytorch-triton")(check_torch_env.py)。脚本刻意不做"已安装但不在已装 torch 闭包内"的通用 diff——因为虚拟环境里绝大部分包(kornia 自身的依赖、测试工具链)本来就合法地不在 torch 的闭包内,且安装元数据不记录来源,diff 无法区分孤儿与根依赖。换装后幸存的其他包(lockfile 的nvidia-*cuda-*wheel)只占磁盘、在+cputorch 上不会被加载,因此无害。未来若出现新的"按可用性分发"的 sidecar,再往名单里加。

2.2 验证步骤:对"整个环境"断言,而非只查torch.__version__

原先只读torch.__version__的验证方式,无法发现"其余包仍属于另一个 torch"的 venv。修复后的 "Verify PyTorch version" 步骤调用:

resolved=$(pixi run -e ... uv run --no-sync \ python .github/scripts/check_torch_env.py \ --channel "$TORCH_CHANNEL" --expected "$EXPECTED_PYTORCH_VERSION" \ | tr -d '\r' | sed -n 's/^torch-version: //p' | tail -n 1) if [[ -z "$resolved" ]]; then echo "the environment check did not report a torch version" >&2 exit 1 fi

(tests.yml)。其中有两个被测试钉死的契约:

  • 只有通过的检查才打印torch-version:。若在失败路径也打印,误配的 venv 仍会给工作流一个"看起来有效"的答案,shell 的-z守卫就会变成死代码(test_check_torch_env.py);
  • 通道语义:pinned要求版本精确等于矩阵值(忽略+cpu本地段);stable把矩阵值当作地板,因为上游探测装的是 stable 索引当天解析出的版本(check_torch_env.py)。

验证步骤同时把解析出的实际版本写入GITHUB_OUTPUT,供后续按版本命名的产物工件使用。

三、check_torch_env.py:一个可复用的环境一致性校验器

check_torch_env.py 是本修复的"唯一事实来源",工作流的注释都指向它。其核心函数如下。

3.1runtime_requirements:按 marker 求值当前依赖

读取已装 torch 的Requires-Dist,用packaging.requirements.Requirement解析,并把extramarker 置空后求值——只保留"无 extra、纯运行时"条件下成立的依赖(check_torch_env.py)。测试test_drops_extras_and_false_markers验证了 setuptools(python_version >= "3.12")和 opt-einsum / optree(仅 extra)都会被正确过滤(test_check_torch_env.py)。

3.2scan_installed:按首个 sys.path 条目优先 + 同位置冲突检测

distributions()sys.path顺序遍历、首个匹配胜出——这与importlib.metadata.version和普通import的解析结果一致,所以"first-wins"是诚实答案。但同一分发位置内出现两个name-version.dist-info目录则没有先后可言,那正是"半成品 venv"(例如被中断的uv pip install --reinstall-package torch的残留),会被单独报告出来,而不是由文件系统顺序"随机裁决"(check_torch_env.py)。测试用临时目录构造冲突 dist-info 验证该行为(test_check_torch_env.py)。

3.3undeclared_sidecars+check_environment:四类问题的完整诊断

for name in undeclared_sidecars(requires, installed, environment): problems.append( f"{name} {installed[name]} is installed, but torch {torch_version} declares no dependency on it: " "torch dispatches onto an importable sidecar without checking its version (#4199)" )

check_environment汇总四类问题(check_torch_env.py):

  1. 版本不符:不满足通道约束;
  2. 缺失依赖:torch 声明了但环境里没有;
  3. 版本滞留:依赖还停在 lockfile 版本,未随换装移动——测试test_flags_a_requirement_left_at_the_locked_versionsympy 1.14.0复现了这一点;
  4. 孤儿 sidecar:装了但当前 torch 未声明(#4199 主故障);
  5. 同包多版本:一个名字出现多个冲突版本,即半成品 venv。

失败时,诊断信息会附带一张name version需求表(缩进两空格打印到 stderr),且只在确实有错时才打印——健康腿不会被约 15 行的噪声刷屏;成功时打印torch-version: x.y.z并返回 0(check_torch_env.py)。

四、测试侧修复:ONNX 导出改为external_data=False

4.1 被 import 错误掩盖的五个用例

triton 崩溃发生在 32 个 dynamo / export 测试的导入阶段,把tests/core/test_small_linalg.py里 5 个 ONNX 用例的真实问题也一并掩盖了。环境修好后,这些用例暴露出 torch 2.6 dynamo 导出器的一个兼容性差异。

4.2 BytesIO 目标下的导出器差异

test_small_linalg.py 的_export_kwargs展示了完整的分层兼容逻辑:

kwargs = {"opset_version": 18} if torch_version_ge(2, 5, 0): kwargs["dynamo"] = use_dynamo_exporter if use_dynamo_exporter and torch_version_ge(2, 6, 0): kwargs["external_data"] = False return kwargs

原因链非常清晰(源码注释逐条给出):

  • 这些用例把图导出到BytesIO
  • dynamo 导出器默认external_data=True,会把手柄直接交给 onnxscript 的save_model_with_external_data——它需要的是文件系统路径,于是在 torch 2.6 上翻译成功后才抛出TypeError: expected str, bytes or os.PathLike object, not BytesIO
  • 2.9.1 和 2.14.0 通过更新的 onnxscript framework API 能接受 buffer;
  • 这些图只有少量节点、没有 initializer,任何版本下都没有外部数据要写,所以external_data=False无副作用;
  • 该参数必须用torch_version_ge(2, 6, 0)门控,因为external_data=在 2.6 之前不存在——而_export_kwargs承诺对 kornia 支持的所有 torch 版本都返回合法 kwargs。

4.3 "导出成功本身就是门槛"的测试哲学

该文件的另一个设计要点(test_small_linalg.py):缺失 lowering 会直接抛异常(在 2.9.1 上实测:legacy 导出器抛UnsupportedOperatorError,dynamo 导出器抛ConversionError),所以"导出调用成功"本身就是最硬的门槛。_assert_basic_arithmetic_only正则(_LINALG_OP_RE)则补上另一种情况:防止未来某个分解把 linalg 运算"成功导出"成重操作——它特意用子串匹配aten_linalg_inv这种 dynamo 导出的 fallback 节点命名,并用锚定规则避免qr误吞Sqrtlu误吞Relu/Selu/Elu/Celu/PRelu/LeakyRelu/ThresholdedRelu全家(test_small_linalg.py)。

五、测试对脚本与工作流契约的双向锁定

本修复的可维护性很大程度上来自 test_check_torch_env.py 对"脚本与工作流两端契约"的联合断言:

  • TestWorkflowWiring直接读取 tests.yml 文本,断言换装步骤内--reinstall-package torch--list-undeclared-sidecars(顺序是修复的全部意义)、uv pip uninstall $stale存在、验证步骤含--channel "$TORCH_CHANNEL" --expected "$EXPECTED_PYTORCH_VERSION"-z "$resolved"守卫、以及工作流解析的前缀与脚本打印的前缀一致(test_check_torch_env.py)。注释特别说明:任一侧改名前缀,另一侧会静默退化为 no-op。
  • TestMain强调main是工作流 grep 并据此门控的表面:stdout 前缀是tests.yml解析的对象,且只有通过的检查才打印版本——破坏任一约定,工作流步骤会退化成"保持绿色但什么都不做"的 no-op(test_check_torch_env.py)。
  • 测试代码里还保留着MIS_PROVISIONED这个"罪证":2.6.0+cpu torch + 3.8.0 triton + 2.14.0 闭包——正是 #4199 描述的误配环境的字面快照(test_check_torch_env.py)。

六、经验总结:可迁移的三条 CI 加固原则

  1. 依赖换装必须连带对账:当你把某个包从 lockfile 的版本换成另一个通道/构建(如 PyPI →+cpuwheel)时,不要只换包本身,还要按已装包声明的依赖闭包清理幸存者——尤其警惕按 import 可用性分发的 sidecar。
  2. 验证要断言整个环境,而不是单点版本:只查torch.__version__发现不了"其余包仍属于另一个 torch"的 venv。把"所有运行时依赖齐备、无未声明 sidecar、无重复版本"作为验证契约,让误配在 setup 阶段就失败,而不是变成一堵"看起来毫不相关"的测试失败墙。
  3. 用测试锁定工作流与脚本的双向契约:把"脚本打印什么前缀、工作流 grep 什么前缀、步骤顺序如何"写成读取工作流文件本身的测试,任何一侧被破坏都会在 PR 阶段暴露,而不是在 CI 上静默退化。

【免费下载链接】kornia🐍 Geometric Computer Vision Library for Spatial AI项目地址: https://gitcode.com/gh_mirrors/ko/kornia

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 15:29:49

SpringBoot+协同过滤算法构建大学生兼职平台实践

1. 项目概述作为一名有多年全栈开发经验的工程师&#xff0c;我最近完成了一个基于SpringBoot的大学生兼职平台项目。这个项目源于我观察到当前大学生兼职市场存在的信息不对称、匹配效率低下等问题。传统兼职平台往往缺乏有效的审核机制和智能推荐功能&#xff0c;导致学生和企…

作者头像 李华
网站建设 2026/9/23 15:26:06

基于BP神经网络与近红外光谱的汽油辛烷值预测MATLAB实现

简介&#xff1a;面向数据分析与机器学习初学者&#xff0c;围绕使用BP神经网络预测汽油辛烷值这一具体任务&#xff0c;切实解决工业场景中辛烷值难以快速准确测量的痛点&#xff0c;提供从数据理解、网络结构搭建、隐藏层设计、参数设置到训练评估的完整实践案例。压缩包共2个…

作者头像 李华
网站建设 2026/9/23 15:25:55

政务大模型落地实践:私有化部署、AI网关与RAG知识库全解析

简介&#xff1a;全省一体化政务平台接入AI大模型应用方案是一份面向政务平台管理人员、信息技术人员及政策制定者的完整设计文档&#xff0c;重点解决政务服务智能化升级、用户体验优化与数据安全等核心问题&#xff0c;适合有一定技术背景的读者作为规划参考。资源共1个docx文…

作者头像 李华
网站建设 2026/9/23 15:24:36

可视化报表速成指南:五招套出高质量看板

做可视化报表这事儿&#xff0c;我见得太多了。很多人打开Excel&#xff0c;面对着上万行的数据&#xff0c;第一反应就是“先插入个图表再说”&#xff0c;结果插出来自己都看不下去&#xff0c;老板也皱眉头。还有人是Excel、Power BI、Tableau装了一堆&#xff0c;真到做报表…

作者头像 李华
网站建设 2026/9/23 15:23:51

企业官网数字化转型:核心能力与实战策略

1. 企业数字化浪潮下的官网价值重塑2026年的商业环境中&#xff0c;企业官网正经历着从"线上名片"到"战略枢纽"的质变。最近在为某跨国消费品集团做数字化咨询时&#xff0c;他们的CMO向我展示了一组数据&#xff1a;新版官网上线半年后&#xff0c;官网直…

作者头像 李华
网站建设 2026/9/23 15:22:53

猫狗目标检测实战:1000图三格式标签+YOLO11跨平台训练

简介&#xff1a;本资源是一套面向目标检测初学者与实战开发者的猫狗检测专用数据集及配套训练方案&#xff0c;适用于监控场景下的动物识别项目开发、YOLO系列算法入门实践及多平台模型训练验证。数据集包含1000张真实场景高质量图像&#xff0c;覆盖奔跑、睡觉、散步、坐卧、…

作者头像 李华