一句话

在长期使用、反复 pip install 的全局 Python 环境里做深度学习,最容易撞上的不是算法问题而是二进制 ABI 冲突:旧 torch 配新 numpy、再被间接导入的 torchvision 触发,报错位置离根因很远。正确做法是为每个训练项目建独立虚拟环境,并且不装用不到的可视化依赖。

症状

import transformers 时报出与 transformers 毫无关系的栈:

A module that was compiled using NumPy 1.x cannot be run in NumPy 2.4.6
...
import transformers -> modeling_qwen3 -> modeling_layers -> processing_utils
  -> image_utils -> from torchvision.transforms import InterpolationMode
  -> torchvision/models/__init__.py -> from .convnext import *

看起来像 transformers 的 bug,实际上是 torchvision 用 NumPy 1.x 的 C API 编译,而环境里装的是 NumPy 2.4.6。

根因

三个层次叠加:

  1. NumPy 2 的 ABI 断代:用 NumPy 1.x 编译的 C 扩展在 NumPy 2 下不能直接加载,必须重编
  2. 间接导入:transformers 的 image_utils 会尝试导入 torchvision——即使任务与图像无关(这里只是给文本分类加载 Qwen3)。于是整个图像栈被拖了进来
  3. 全局环境漂移:全局环境里 torch 2.3.0+cpu、torchvision 旧版、NumPy 2.4.6 共存。这类环境通常由多次随手 pip install 累积而成,版本组合从未被验证过

环境实况对照

出问题的全局环境与新建虚拟环境的版本差异:

包全局(坏)venv(可用)
torch2.3.0+cpu2.14.0+cpu
numpy2.4.6与 torch 匹配
transformers4.57.65.17.0
peft0.7.10.21.0
trl0.13.01.13.0
torchvision旧版(元凶)未安装

处理步骤

# 1. 在项目目录建独立环境
python -m venv .venv

# 2. 先装 torch(CPU 用官方 CPU index,国内实测可达且快)
.venv\Scripts\python.exe -m pip install torch ^
    --index-url https://download.pytorch.org/whl/cpu

# 3. 再装训练栈,注意不装 torchvision
.venv\Scripts\python.exe -m pip install transformers peft trl datasets accelerate sentencepiece

# 4. 验证
.venv\Scripts\python.exe -c "import torch, transformers; print(torch.__version__, transformers.__version__)"

关键点:不安装 torchvision,transformers 的可用性检测会直接跳过图像分支,问题从根上消失,比"降级 numpy 到 1.x 去迁就旧 torchvision"干净得多。

怎么下次一眼认出来

  • 报错栈里出现与当前任务无关的模块(文本任务里出现 torchvision)→ 先怀疑间接导入
  • 报错提到 NumPy 1.x / 2.x 编译版本 → 直接判定 ABI 问题,不要去改业务代码
  • 全局环境里的包版本跨度大(torch 2.3 与 transformers 5.x 共存)→ 说明环境已经不可信,直接建 venv,不要试图逐个修复
  • 不要动用户的全局环境去"修好它":全局环境可能有别的项目依赖,隔离是唯一安全解

附:TRL 参数也会随版本漂移

同一个坑的另一种形态。TRL 1.x 的 SFTConfig 已经不接受 warmup_ratio(transformers 5 移除了它),直接报 Unexpected keyword argument。与其写死参数、每换一次版本修一次,不如按签名过滤:

import inspect
accepted = set(inspect.signature(SFTConfig.__init__).parameters)
cfg_args = {k: v for k, v in want.items() if k in accepted}
dropped = sorted(set(want) - set(cfg_args))
if dropped:
    print("[warn] SFTConfig 不接受这些参数,已丢弃: %s" % dropped)

并把 warmup_ratio 同时换算成 warmup_steps 提供,哪个被接受就用哪个。这样换版本只会告警,不会崩。

相关


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Python