Pythonのプロジェクト管理ツール uv のv0.5.3までの便利な機能 - dependencies編
Python Advent Calendar 2024の7日目です。
この記事ではuvについて、v0.5.3までのアップデートで個人的に便利だった機能を依存関係に焦点を当ててまとめました。
uvは高速なPythonパッケージとプロジェクト管理ツールです。2024年8月20日にuvのバージョンがv0.3.0にアップデートされて以来、広く使われるようになりました。
以前に以下の記事をまとめています。これからuvを使いたい方などに参考になれば嬉しいです!また、公式のドキュメントがしっかりと整備されているのでhttps://docs.astral.sh/uv/getting-started/から使ってみることをオススメします。
現在のuvのバージョンがv0.5.3より前のバージョンであれば、以下のコマンドでuvをアップデートしましょう。
uv self update
index-url
を指定するパッケージのインストール
1. PyTorchを例にUsing uv with PyTorch - uvとして、uvでのPyTorchのインストール方法が公式ドキュメントがまとめられています。
この中で、そもそもPyTorchのインストールが特殊な理由として以下の2つが挙げています。
- 多くのPythonパッケージは、PyPI (Python Package Index)でホストされています。一方で、PyTorchの場合は専用のindexでホストされています(https://download.pytorch.org/whl/cpu)。PyTorchのインストールの際には、このindexを指定するようにする必要があります。
- PyTorchはアクセラレータごとに異なるwheelがビルドされて、そのwheelはローカルバージョンで指定されています(e.g., 2.5.1+cpu, 2.5.1+cu121)。各wheelは異なるindexで公開されて、異なるアクセラレータ環境ごとにindexを指定する必要があります(e.g., https://download.pytorch.org/whl/cu121)。
そのため通常のuv add torch
では、PyPIでホストされているPyTorchのパッケージがインストールされます。ここで、
In this case, PyTorch would be installed from PyPI, which hosts CPU-only wheels for Windows and macOS, and GPU-accelerated wheels on Linux (targeting CUDA 12.4)
とあることからtorch=2.5.1
では、PyPI上ではWindowsやmacOSではCPU、Linux環境ではCUDA 12.4でビルドされたwheelが公開されているらしいです。それ以外の組み合わせで行う場合を紹介されています。
tool.uv.index
を用いたPyPI以外のindexの指定
1.1 uvのv0.4.23から新しいindex指定が導入されて、pipスタイルの--index-url
や--extra-index-url
の設定オプションに相当する機能です。
最初にtool.uv.index
を用いて名前付きのindexを定義します。今回はCUDA 12.4のPyTorchのindexを指定します。このURLはPyTorchの場合はPrevious PyTorch Versionsを参照するか、uvのドキュメントを参照してください (Using a PyTorch index - uv)。なお、以下のnameは任意の文字列です。
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
tool.uv.sources
で、パッケージごとにどのindexからインストールするかを指定します。今回は上記で指定した名前付きindexを指定します。
[tool.uv.sources]
torch = { index = "pytorch-cu124" }
torchvision = { index = "pytorch-cu124" }
これによりtorchとtorchvisionは、https://download.pytorch.org/whl/cu124でホストされているパッケージからインストールされます。後はuv add torch torchvision
を実行することでパッケージをdependenciesに追加して、インストールすることが可能です。この操作はpipでの以下の操作と同じです。
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124
explicit = true
で[tool.uv.sources]
に指定されていないパッケージはデフォルトのindex(PyPI)を使用する
1.2 tool.uv.index
にはexplicit
という設定があります。これをtrue
に設定することにより、[tool.uv.sources]
で指定されていないパッケージはデフォルトのPyPIでホストされているパッケージをインストールします。PyTorchの場合はexplicit = true
が推奨されています。
そもそもなぜこの設定が必要なのかは、指定したindex urlにインストールしたいパッケージ以外もホストされている場合があり、余計な依存関係の競合が発生するおそれがあるためです。
explicit = true
でなければ、tool.uv.sources
に指定されていないパッケージもこのindex URLからインストールします。PyTorchのindex URLにはnumpyや、jinja2やnetworkxなどもホストされています。ただ、それらのパッケージは一部バージョンしかホストされていません。例えばnumpyであれば2.0.0以上のバージョンはホストされていないため、他のパッケージの要求と一致しない場合があります。そのためPyTorch関連パッケージ以外はPyPIを参照するよう、explicit = true
に設定することが望ましいです。
1.3 Environment markersでOSごとにindexの指定を変更する
tool.uv.sources
にはmarker
としてenvironment markersを使用することができます。この機能は、特にmacOSでCPU版のtorchを指定する際に便利です。またPyTorch以外のパッケージにももちろん使えるため、クロスプラットフォームでの開発時に便利な機能です。
Environment markersについては後述するとして、先にConfiguring accelerators with environment markers - uvのpyproject.tomlを見てみましょう。
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12.0"
dependencies = [
"torch>=2.5.1",
"torchvision>=0.20.1",
]
[tool.uv.sources]
torch = [
{ index = "pytorch-cpu", marker = "platform_system == 'Windows'" },
{ index = "pytorch-cu124", marker = "platform_system == 'Linux'" },
]
torchvision = [
{ index = "pytorch-cpu", marker = "platform_system == 'Windows'" },
{ index = "pytorch-cu124", marker = "platform_system == 'Linux'" },
]
[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
これはOSがWindowsであればpytorch-cpuで指定されているindex URLを使用し、Linuxであればpytorch-cu124で指定されているindex URLを使用するようになります。uv sync
のコマンドだけで柔軟にtorchのindexを変更してくれます。
では、enviromental makerについてです。そもそもuvはPEP 508に基づいたDependency specifiersの記述が可能で、その記述の1つにenviromental makerがあります。これにより、OSやCPUアーキテクチャなどシステムの環境に合わせて依存関係を記述することが可能になります。
markerとして、sys_platform (e.g., Linuxなら'linux'
, macOSなら'darwin'
)やplatform_machine (e.g., 'x86_64'
, 'aarch64'
, 'amd64'
)、前述したplatform_system (e.g., 'Linux'
, 'Darwin'
, 'Windows'
)などが使用できます。他のmarkerなど詳細については、Python Packaging User Guideの環境マーカを参照してください。
このmarkerはand
やor
、()
で組み合わせた条件式で記述することができます。これにより、OSはLinuxかつCPUアーキテクチャはx86_64のときはこのindexでインストールする、といったことが可能になります。
一例として以下の記事では、macOSまたはaarch64のLinuxではCPU版の、x86_64のLinuxではCUDA版のPyTorchを切り替えるを以下のように記述されています。
[tool.uv.sources]
torch = [
{ index = "torch-cuda", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'"},
{ index = "torch-cpu", marker = "sys_platform == 'darwin' or (sys_platform == 'linux' and platform_machine == 'aarch64')"},
]
2. optional-dependenciesを使う
特定のパッケージはextras_require
としてデフォルトの依存関係を減らし、明示的にインストールするパッケージを要求します。例えばpandasであれば、Excel parserが必要ならpip install pandas[excel]
、Excel parserとplot機能が必要であればpip install pandas[plot, excel]
のように明示的に追加します(pyproject.toml)。
uvではpyproject.toml内で[project.optional-dependencies]
に依存関係を記述します。
コマンドはuv add
時に--optional <extra名>
を指定することです。
uv add "numpy<2.0.0" --optional numpy-1
すると[project.optional-dependencies]
にnumpy-1
として追加されます。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = []
[project.optional-dependencies]
numpy-1 = [
"numpy<2.0.0",
]
このextrasに記述された依存パッケージをインストールするにはuv sync --all-extras
で実行可能で、特定のextrasだけインストールするにはuv sync --extra numpy-1
のように指定することが可能です。
conflicts
を宣言する
2.1 競合するoptional-dependenciesは
uvではoptional-dependenciesは相互に互換性があることが要求されます。そのため以下のように互換性のないパッケージのバージョンを指定するとエラーが発生します。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = []
[project.optional-dependencies]
numpy-1 = [
"numpy<2.0.0",
]
numpy-2 = [
"numpy>=2.0.0",
]
$ uv sync
× No solution found when resolving dependencies:
╰─▶ Because project[numpy-2] depends on numpy>=2.0.0 and project[numpy-1] depends on numpy<2.0.0, we can conclude that project[numpy-1] and project[numpy-2]
are incompatible.
And because your project requires project[numpy-1] and project[numpy-2], we can conclude that your projects's requirements are unsatisfiable.
ここでuvのv0.5.3からtool.uv.conflicts
追加されました。conflicts
を用いることで競合しているextrasを明示的に記載でき、このエラーを回避することができます。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = []
[project.optional-dependencies]
numpy-1 = [
"numpy<2.0.0",
]
numpy-2 = [
"numpy>=2.0.0",
]
[tool.uv]
conflicts = [
[
{ extra = "numpy-1" },
{ extra = "numpy-2" },
],
]
もちろんこの2つの依存関係を同時にインストールすることはできません。
$ uv sync --all-extras
Resolved 3 packages in 4ms
error: extra `numpy-1`, extra `numpy-2` are incompatible with the declared conflicts: {`project[numpy-1]`, `project[numpy-2]`}
uv sync --extra
でインストールするPyTorchを切り替える
2.2 Configuring accelerators with optional dependencies - uv
tool.uv.index
やoptional-dependenciesを用いることで、uv sync --extra cpu
やuv sync --extra cu124
で切り替えられます。
以下のようにpyproject.tomlを設定することで可能です。tool.uv.sources
において、extra
としてoptional-dependenciesを指定することが可能です。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = []
[project.optional-dependencies]
cpu = [
"torch>=2.5.1",
]
cu124 = [
"torch>=2.5.1",
]
[tool.uv]
conflicts = [
[
{ extra = "cpu" },
{ extra = "cu124" },
],
]
[tool.uv.sources]
torch = [
{ index = "pytorch-cpu", extra = "cpu" },
{ index = "pytorch-cu124", extra = "cu124" },
]
[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
3. 複数のdev-dependenciesを使い分ける
uvのv0.4.27からDependency groupsという機能が追加されました。これまでは単一だったdevelopment dependencies(開発用の依存関係; dev-dependencies)が、複数グループで設定できる機能です。
そもそもoptional-dependenciesではなく、dev-dependenciesはどのようなときに便利でしょうか。理由の1つとして、パッケージをビルドして公開するときにlinterやtest用のパッケージなどの余計な依存関係を含めずに済むことが挙げられます。
uv add --group
で[dependency-groups]
に追加する
3.1 コマンドはuv add
時に--gourp <グループ名>
を指定することです。
uv add ruff --group lint
uv add pytest --group test
するとdependency-groups
にグループごとにパッケージが追加されます。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = []
[dependency-groups]
lint = [
"ruff>=0.8.2",
]
test = [
"pytest>=8.3.4",
]
また、以前までのバージョンと同様にuv add --dev
も可能です。この場合はdev
というグループに追加されます。
このグループに記述された依存パッケージをインストールするにはuv sync --all-groups
で実行可能であり、特定のグループだけインストールするにはuv sync --group lint
のように指定することが可能です。
また、default-groups
としてuv sync
に含めることができます。以下の記述でuv sync
をするとdev
とlint
のグループ内の依存パッケージも加えてインストールします。
[tool.uv]
default-groups = ["dev", "lint"]
--no-build-isolation
が必要なパッケージのインストール
4.
近年の多くのパッケージではPEP 517に従い、隔離された仮想環境でビルドします。一方で、flash-attentionなど一部パッケージでは意図的に現在の仮想環境内でビルドすることを要求する場合があります。
pipであればビルドに必要なパッケージ(e.g., setuptools, packaging)を先に仮想環境に追加した後に、pip install
に--no-build-isolation
のオプションを追加することで実行できます。
uvの場合はどうすればいいか、実際にflash-attentionをインストールしてみましょう。
flash-attentionインストール
4.1まず最初にPyTorchをtool.uv.index
を指定して追加します。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = [
"torch>=2.5.1"
]
[tool.uv.sources]
torch = [
{ index = "pytorch-cu124" }
]
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
次にビルドに必要なパッケージをインストールします。flash-attnのInstallation and featuresには、packaging, ninja, psutilが必要なのでoptional-dependenciesに追加します。
uv add packaging ninja psutil --optional build
flash-attnを追加する前に便利な設定としてtool.uv.no-build-isolation-package
とtool.uv.dependency-metadata
を追記します。なお、この設定は行わなくてもインストールできます。
uvの場合はuv add
やuv sync
に--no-build-isolation
のオプションを追加することができて、現在の仮想環境内でビルドするようにできます。ただ毎回このオプションを追加するのが面倒であり、特定のパッケージを選択的に付けたいためtool.uv.no-build-isolation-package
を使います。以下のようにpyproject.tomlに記載します。
[tool.uv]
no-build-isolation-package = ["flash-attn"]
依存関係を解決する際に各パッケージのMETADATA(メタデータ)を用います。このメタデータはindexに静的ファイルとして用意されていることが多いですが、source distributionsのみ配布しているパッケージの場合はメタデータが存在しないことがあります。その場合はパッケージをビルドする必要があり、依存関係解決に時間が掛かる、正常にビルドできない、など問題が発生する可能性があります。
uvではtool.uv.dependency-metadata
としてメタデータを明示的に記述することができます。以下のようにpyproject.tomlに記載します。
[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]
name
はパッケージ名、version
はそのパッケージのバージョン、requires-dist
はパッケージの使用時に必要な依存パッケージを指定します。なおsetup.pyであればinstall_requires
に相当し、uvなどのpyproject.tomlであればdependencies
に相当するパッケージです。
dependency-metadata
の宣言はメタデータが存在しない場合を対象としていますが、no-build-isolation
が必要なパッケージに役にたつケースがあります。flash-attnはこの例の一つです。
いよいよflash-attnのインストールです。以下のコマンドを実行しましょう。バージョンは2.6.3に固定していますが、任意のバージョンで選択してください。
uv add flash-attn==2.6.3 --optional compile
インストールまで時間が掛かると思いますが、無事インストールできたら完了です。pyproject.tomlは以下のようになります。
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12.0"
dependencies = [
"torch>=2.5.1",
]
[project.optional-dependencies]
build = [
"ninja>=1.11.1.2",
"packaging>=24.2",
"psutil>=6.1.0",
]
compile = [
"flash-attn==2.6.3",
]
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch-cu124" }
[tool.uv]
no-build-isolation-package = ["flash-attn"]
[[tool.uv.dependency-metadata]]
name = "flash-attn"
version = "2.6.3"
requires-dist = ["torch", "einops"]
また、先にこのpyproject.tomlがある場合は2段階のuv sync
を実行します。
uv sync --extra build
uv sync --extra build --extra compile
以上で無事にflash-attnがインストールできるようになりました。
5. おわりに
この記事ではuvのv0.5.3までの便利な機能を、特にdependenciesに関係するところを紹介しました。uvはそれ以外にも自作パッケージビルドや、workspacesなど便利な機能があるので別の記事で紹介したいと思います。不明点などあればお気軽にコメントしていただけると嬉しいです!
Discussion