九章智算云
用 uv 替代 pip / Poetry:AI 项目的包管理与源配置

从旧项目迁移到 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 项目,可能同时访问包索引、解释器分发站点与模型仓库。

项目配置pyproject.toml默认索引:普通 Python 包命名索引:指定构建版本uv.lock 记录解析结果变更来源后检查锁文件差异默认 PyPI 镜像普通依赖,例如 transformers / acceleratedefault = true · 替换默认 PyPI 索引PyTorch 官方 CUDA 索引torch 通过 tool.uv.sources 显式绑定explicit = true · 不自动接管所有传递依赖另外两条下载链路Python 解释器使用已有解释器,或由 uv 单独下载安装 uv 本身不等于已经安装 Python模型权重由模型客户端访问 Hugging Face 等仓库HF_ENDPOINT 与 uv 包索引分别配置图 1:包索引配置决定 Python 包的来源,解释器和模型权重需分别配置;配置关系示意,不代表下载速度或镜像可用性。

图 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 索引地址服务方说明
清华 TUNAhttps://pypi.tuna.tsinghua.edu.cn/simple使用说明
阿里云https://mirrors.aliyun.com/pypi/simple/镜像说明
中科大 USTChttps://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 当作自己的锁文件。迁移时需要核对:

  1. Python 范围与直接依赖,整理到标准 [project] 元数据;Poetry 特有的版本写法需要转换。
  2. 开发和测试分组、可选依赖与 extras,确认它们在新命令中仍按预期安装。
  3. 私有源、Git 依赖、本地路径依赖、脚本入口与构建后端,逐项验证。
  4. 生成 uv.lock,对比关键版本,执行原有测试及 GPU 冒烟测试。

需要分阶段迁移时,可以先使用 Poetry 的官方导出插件导出 requirements,再用 uv pip 安装。这能暂时保留 Poetry 的依赖管理流程,但还不算完成项目模式迁移。Poetry 官方导出插件

六、锁文件到底保证什么

uv.lock 可以描述不同平台和 Python 版本下的依赖选择。**跨平台锁文件不等于每个依赖都有跨平台可用的二进制包。**本文 GPU 示例主动限制了 Linux x86_64;若同时支持 macOS 开发、CPU 测试和 CUDA 生产,应设计对应的环境标记或可选依赖,并分别验证。锁文件结构 · 平台范围配置

把依赖变更留在构建之前:开发时更新并审查,CI 检查一致性,运行时使用已经验证的环境。

01声明与解析修改 pyproject.tomluv lock审查版本与来源差异02提交仓库pyproject.tomluv.lock同时记录 Python 版本03CI / 镜像构建uv sync --locked运行项目测试按需选择依赖组04运行与验证使用已构建环境加载模型并执行请求记录镜像与模型版本三个选项,控制三件不同的事--locked检查项目与锁文件一致需要更新锁文件时失败--frozen跳过锁文件是否过期的检查不代表更严格的一致性校验--no-sync跳过环境同步需要前置步骤已准备好环境锁文件之外仍需记录GPU 型号与驱动 / 操作系统与系统库 / 模型 revision / 源码构建工具链图 2:把依赖变更留在开发与构建阶段,运行时使用已验证的环境;跨平台锁文件不保证可用 wheel,也不锁定 GPU 驱动。

几个常用选项需要分清:

命令或选项行为适用场景
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 或容器实测结果。用于生产前,请在目标硬件、驱动、网络与项目版本上完成验证。

最后更新于

这篇文档对你有帮助吗?

目录