从旧项目迁移到 uv,配好国内 PyPI 镜像与 PyTorch CUDA 源,分清 --locked 和 --frozen,并把依赖管理接入 CI 与 Docker。
用 uv 替代 pip / Poetry:AI 项目的包管理与源配置
AI 项目的依赖问题,往往从“下载太慢”开始,最后却卡在另一处:同事装出了不同版本的 PyTorch,CI 多装了一组开发依赖,或者 vLLM 安装成功,启动时才报二进制兼容错误。
uv 把 Python 版本管理、虚拟环境、依赖解析和锁文件纳入同一套工作流。它值得采用的理由,除了安装效率,还有把依赖版本、包的来源和安装过程写进仓库,减少环境中的隐含条件。
本文以 Linux / Bash 为主要命令环境,覆盖新项目、已有 pip / Poetry 仓库以及 CI / Docker。示例固定 uv 0.12.17;GPU 配置明确限定平台和 CUDA wheel 来源。镜像地址与命令语义按更新日期核对,具体速度仍应在目标网络和机器上测量。
说明
先记住三个边界:uv.lock 锁定 Python 依赖,不锁定 NVIDIA 驱动或模型权重;PyPI 镜像与 CUDA wheel 源是不同配置;安装成功之后,还需要验证 GPU 工作负载。
一、先跑通一个小项目
安装 uv 本身不要求提前安装 Python,但这不等于 uv 二进制内置了 Python 解释器。运行项目时,uv 会使用已有解释器,或按配置下载解释器。企业网络可能需要为这条下载链路单独放行。安装说明 · Python 管理
# 安装本文使用的 uv 版本
curl -LsSf https://astral.sh/uv/0.12.17/install.sh -o uv-install.sh
sh uv-install.sh
# 如当前终端找不到 uv,按安装器提示更新 PATH,或重开终端
uv --version
# 安装 Python,并创建项目
uv python install 3.12
uv init --python 3.12 my-llm
cd my-llm
uv python pin 3.12
# 先用一个小依赖验证工作流
uv add httpx
uv run python main.py
uv run python -c "import httpx; print(httpx.__version__)"uv add 会更新依赖声明与锁文件,并同步项目环境;uv run 默认使用项目的 .venv,通常无需手动激活。新项目生成的 main.py 可以直接运行,后续再替换成自己的训练或服务入口。项目入门
提交代码时,一起提交 pyproject.toml、uv.lock 和 .python-version,把 .venv/ 放进 .gitignore。其他人在仓库中执行:
uv sync --locked
uv run --locked python main.py.python-version 写成 3.12 只固定到次版本。需要更严格的复现时,应固定经过测试的 Python 补丁版本,并记录操作系统或容器镜像版本。
二、AI 项目的源配置:按包指定来源
不要把所有下载都归为“换一个 pip 源”。一个 AI 项目至少可能访问四类来源:
先分清下载的是什么,再配置源:同一个 AI 项目,可能同时访问包索引、解释器分发站点与模型仓库。
图 1:包索引配置决定 Python 包的来源;解释器和模型权重需要分别配置。示意图不代表实测网络性能。
下面是一份独立 GPU 示例项目的完整 pyproject.toml。它限定 Linux x86_64、Python 3.12,选择官方提供的 PyTorch 2.11.0 / CUDA 12.8 wheel 组合。这个版本用于演示明确的来源绑定,不代表所有项目都应升级或降级到它。PyTorch 官方历史版本
[project]
name = "gpu-demo"
version = "0.1.0"
requires-python = ">=3.12,<3.13"
dependencies = [
"torch==2.11.0",
"transformers",
"accelerate",
]
[tool.uv]
environments = [
"sys_platform == 'linux' and platform_machine == 'x86_64'",
]
[[tool.uv.index]]
name = "tuna"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true
[[tool.uv.index]]
name = "pytorch-cu128"
url = "https://download.pytorch.org/whl/cu128"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch-cu128" }这里有三个关键点:
default = true用清华镜像替换默认 PyPI 索引,不意味着网络失败时自动切回 PyPI。explicit = true让 PyTorch 索引只用于显式绑定的包;普通依赖继续使用默认源。[tool.uv.sources]将torch绑定到 CUDA 12.8 索引。这项绑定不会自动扩展到所有传递依赖。
如果项目还需要 torchvision 或 torchaudio,应按 PyTorch 发布矩阵选择匹配版本,并分别声明依赖和来源,不能只加一条索引就假设所有 GPU 包都会自动匹配。uv 的 PyTorch 配置
在保存上述文件的独立目录中执行:
uv python pin 3.12
uv lock
uv sync --locked
# 在有 NVIDIA GPU 的目标节点验证版本、构建后端和设备可见性
uv run --locked python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available())"torch.version.cuda 表示这份 PyTorch 的 CUDA 构建版本;系统驱动还必须满足相应兼容要求。uv 不负责安装或升级 NVIDIA 驱动,也不会替项目补齐所有系统动态库。NVIDIA CUDA 兼容性说明
vLLM 应先选兼容组合,再生成锁文件
vLLM、PyTorch 与部分 CUDA 扩展存在二进制兼容关系。不要把任意一个 torch==... 和一个 vllm==... 拼在一起,再把解析成功当作可运行的证明。
建议先按目标 vLLM 版本的安装文档确认 Python、平台、GPU 和 PyTorch 要求,在独立环境验证。官方提供了使用 uv 自动选择 PyTorch 后端的安装方式:
# Linux 上的独立试验环境;此命令本身不会生成项目 uv.lock
uv venv --python 3.12 .venv-vllm
uv pip install --python .venv-vllm/bin/python vllm --torch-backend=auto--torch-backend=auto 当前属于 uv pip 工作流,不能直接视为 uv add / uv sync 的等价配置。试验通过后,将确认的版本约束与明确来源写入项目,再生成和提交锁文件。生产验证至少包括实际模型加载和一轮请求,而不只是 import vllm。vLLM GPU 安装文档 · uv 后端选择说明
三、项目、全局和临时配置,分别放在哪里
团队项目优先把源配置放入 pyproject.toml,让本地开发和 CI 使用同一份规则。个人默认值可以放在用户配置文件:
- Linux / macOS:通常为
~/.config/uv/uv.toml;设置了XDG_CONFIG_HOME时以该目录为准。 - Windows:
%APPDATA%\uv\uv.toml。
注意独立 uv.toml 不写 tool.uv 前缀:
[[index]]
name = "tuna"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true临时试用其他默认索引,可以使用环境变量,也可以直接传命令行参数:
# 仅对这次解析设置默认索引
UV_DEFAULT_INDEX=https://mirrors.aliyun.com/pypi/simple/ uv lock
# 或使用命令行参数
uv lock --default-index https://mirrors.aliyun.com/pypi/simple/两者都可能改变锁文件,执行后应检查差异。一般配置优先级是命令行 > 环境变量 > 项目 > 用户 > 系统;索引列表还涉及配置合并与索引顺序,不能只按一个字符串覆盖来理解。同目录存在 uv.toml 时,它优先于 pyproject.toml 中的 uv 配置。配置发现与优先级
旧变量 UV_INDEX_URL 已被标记为弃用,新配置使用 UV_DEFAULT_INDEX,不必同时设置两个变量。另外,uv 不读取 pip 的 pip.conf 和 PIP_INDEX_URL;[tool.uv.pip] 也只作用于 uv pip 子命令。环境变量参考 · pip 兼容性说明
多个索引不是自动测速与容灾
uv 默认采用 first-index 策略:按优先级寻找包,找到包含该包的第一个索引后,就在该索引中选择可用版本。这有助于减少依赖混淆风险,也意味着较高优先级源里的旧版本可能挡住其他源里的新版本。
因此,“多加几个源,总能挑到最快、最新的那个”并不成立。遇到缺包或同步延迟,应检查包的来源绑定与索引优先级,而不是直接放宽到跨所有索引任意选包。
uv.lock 还会记录索引和分发文件的信息。换一个默认源,不会让已锁定的下载地址自动变成新镜像;需要重新解析并审查锁文件差异。私有源的用户名和密码应通过 CI 密钥或认证机制注入,不要写进仓库。索引策略与认证
四、国内 PyPI 镜像:按部署网络选择
下面列出服务方文档中提供的公开地址。它们没有通用的速度排名;可用性应从实际开发机和 CI 节点验证,尤其要检查所需版本及对应 wheel 是否已经同步。
| 服务 | HTTPS 索引地址 | 服务方说明 |
|---|---|---|
| 清华 TUNA | https://pypi.tuna.tsinghua.edu.cn/simple | 使用说明 |
| 阿里云 | https://mirrors.aliyun.com/pypi/simple/ | 镜像说明 |
| 中科大 USTC | https://mirrors.ustc.edu.cn/pypi/simple | 使用说明 |
| 华为云 | https://repo.huaweicloud.com/repository/pypi/simple/ | 镜像说明 |
验证时不要只打开索引首页。至少用项目锁文件执行一次安装,检查具体包版本、平台 wheel、重定向后的下载地址以及失败重试情况。遇到证书错误,先检查系统时间、CA 和企业代理配置,不应把关闭证书校验当作默认方案。
另外,transformers、huggingface_hub、xformers 等 Python 包与模型权重是两类文件。PyPI 镜像可能提供这些包,但不会因此提供 Hugging Face 上的模型权重;HF_ENDPOINT 也不能替代 uv 的包索引配置。PyTorch 的特定 CUDA wheel、自定义 CUDA 扩展和夜间构建,则按各项目实际发布地址配置。
五、已有 pip / Poetry 项目怎样迁移
只有 requirements.txt:可以先保留仓库结构
uv venv --python 3.12
uv pip install -r requirements.txt
uv pip check这适合先替换安装工具。uv pip 提供熟悉的命令接口,但不保证与 pip 的全部行为一致,也不会自动创建 uv.lock。uv pip check 检查的是已安装包的依赖元数据关系,不能证明 CUDA 扩展在运行时兼容。
如果项目维护 requirements.in,可先解析完整依赖,再同步环境:
uv pip compile requirements.in --generate-hashes -o requirements.txt
uv pip sync --require-hashes requirements.txt与安装命令相比,uv pip sync 会清理环境中未列出的包,因此输入应当是包含传递依赖的完整解析结果。默认解析目标还受 Python 和平台影响,不能把一台机器生成的 requirements 文件直接当作所有平台通用的锁文件。依赖编译与同步
要迁移到项目模式,可在已有 pyproject.toml 项目中执行:
# requirements.in 记录直接依赖;旧 requirements.txt 用作版本约束
uv add -r requirements.in -c requirements.txt如果仓库还没有项目文件,先运行 uv init 并检查生成内容。不要直接把 pip freeze 的所有结果都当成项目直接依赖,否则容易把测试工具、临时工具和底层库一并固化。pip 到项目模式的迁移指南
Poetry:保留依赖意图,重新生成 uv.lock
uv 不会直接把 poetry.lock 当作自己的锁文件。迁移时需要核对:
- Python 范围与直接依赖,整理到标准
[project]元数据;Poetry 特有的版本写法需要转换。 - 开发和测试分组、可选依赖与 extras,确认它们在新命令中仍按预期安装。
- 私有源、Git 依赖、本地路径依赖、脚本入口与构建后端,逐项验证。
- 生成
uv.lock,对比关键版本,执行原有测试及 GPU 冒烟测试。
需要分阶段迁移时,可以先使用 Poetry 的官方导出插件导出 requirements,再用 uv pip 安装。这能暂时保留 Poetry 的依赖管理流程,但还不算完成项目模式迁移。Poetry 官方导出插件
六、锁文件到底保证什么
uv.lock 可以描述不同平台和 Python 版本下的依赖选择。**跨平台锁文件不等于每个依赖都有跨平台可用的二进制包。**本文 GPU 示例主动限制了 Linux x86_64;若同时支持 macOS 开发、CPU 测试和 CUDA 生产,应设计对应的环境标记或可选依赖,并分别验证。锁文件结构 · 平台范围配置
把依赖变更留在构建之前:开发时更新并审查,CI 检查一致性,运行时使用已经验证的环境。
几个常用选项需要分清:
| 命令或选项 | 行为 | 适用场景 |
|---|---|---|
uv sync | 必要时更新锁文件,并同步环境 | 本地开发 |
uv sync --locked | 检查项目与锁文件一致;需要更新锁文件时失败 | CI、发布构建 |
uv sync --frozen | 使用现有锁文件,跳过是否过期的检查 | 明确接受这一前提的专门流程 |
uv run --no-sync | 跳过环境同步,使用已有环境 | 已由前置步骤完成同步 |
--no-dev | 排除 dev 依赖组 | 只需排除 dev 时 |
--no-default-groups | 不自动安装默认依赖组 | 生产构建中明确控制分组 |
--frozen 并不比 --locked 更严格。生产流程还应明确是否启用 extras 或显式依赖组,并在构建各步骤中保持一致。uv run 默认会检查并同步环境,启动时如果再次调用它,也要考虑这些默认行为。锁定与同步语义
锁文件之外,GPU 服务至少还要记录驱动、GPU 型号、操作系统或镜像版本,以及模型权重的 revision。涉及源码构建时,还要记录编译工具链和构建参数。这些条件共同决定程序能否在另一台机器上运行。
七、CI / Docker:在构建时完成同步
CI 的核心是用 uv sync --locked 暴露“改了配置却没更新锁文件”的问题,再运行项目实际拥有的测试和入口。生产环境则按部署需要选择依赖组。
下面的 Dockerfile 对应前面带 main.py 的简单 Python 项目,展示分层缓存和依赖同步方式。它不是完整的 vLLM GPU 运行镜像:GPU 服务还需要选择合适的基础镜像、系统库和容器 GPU 运行配置。
# syntax=docker/dockerfile:1
FROM python:3.12-slim-bookworm
COPY --from=ghcr.io/astral-sh/uv:0.12.17 /uv /uvx /usr/local/bin/
WORKDIR /app
ENV UV_LINK_MODE=copy \
UV_PYTHON_DOWNLOADS=0 \
PATH="/app/.venv/bin:$PATH"
# 先装依赖;应用代码变动时可复用这一层
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-default-groups --no-install-project
# 再复制应用,完成项目安装
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-default-groups --no-editable
# 直接使用已经构建好的环境
CMD ["/app/.venv/bin/python", "main.py"]同时准备 .dockerignore,避免宿主机虚拟环境覆盖容器环境:
.venv/
.git/
__pycache__/
.pytest_cache/
.env该示例要求启用 BuildKit,且第一阶段适用于普通单项目;包含 workspace 成员或本地路径依赖时,需要按结构复制额外清单和源码。生产发布还应固定经过验证的基础镜像及 uv 镜像 digest,因为标签可能变化。uv 官方 Docker 指南
八、怎样证明迁移真的更快、更稳
“安装从 80 秒降到 8 秒”只有在测试条件明确时才有参考价值。AI 项目的安装耗时可能主要花在大体积 wheel 下载、源码编译或磁盘写入上,包管理器无法消除这些成本。
建议将测试分成三类,避免把不同操作混为一谈:
| 场景 | 应固定的条件 | 主要观察 |
|---|---|---|
| 冷安装 | 新环境、各工具空缓存、相同包版本与分发文件、相同网络 | 下载量、总耗时、失败情况 |
| 热安装 | 新环境、各工具已经预热的缓存、相同依赖集合 | 解包、链接或复制、环境创建成本 |
| 无变更同步 | 已安装完整依赖,配置与锁文件未变化 | 环境检查开销,不称为“重新安装” |
如果测的是解析速度,应单独计时;如果测的是安装速度,应提前准备等价的已解析依赖集合。不要将 pip 的冷安装与 uv 的缓存命中结果放在同一行比较。
测试记录应包含工具版本、Python 版本、依赖文件、操作系统、CPU / 磁盘、索引地址、缓存状态和重复次数,并报告中位数及失败情况。对于 GPU 项目,还应记录模型加载、推理或训练样例是否通过;更快地装出一个不可运行的环境没有实际收益。
实际迁移可以按四步推进:**先验证安装工具,再固定包来源,随后提交锁文件,最后把同一套规则接入 CI 和容器构建。**每一步都保留可回退的版本差异,并用项目自己的工作负载验证结果。
技术说明:本文命令与配置依据所链接的官方文档整理,配图为原创流程示意;未将示例描述为本文环境下的 GPU 或容器实测结果。用于生产前,请在目标硬件、驱动、网络与项目版本上完成验证。
最后更新于
