🥃

Rye × uvでPython環境と機械学習環境を整える

2024/07/12に公開

チューリングのE2E自動運転チームの岩政(@colum2131)です。

最近、チーム内でPythonを使った開発はRyeとuvを使うことが多くなり、特に機械学習環境もRyeとuvで問題なく開発できるようになりました。社内でのオンボーディング資料としてRyeとuvの操作を整備しようと思い、このテックブログで紹介します。

[2024-08-25追記]
2024年8月20日にuvのマイナーバージョンがアップデートされました。変更点もいくつかあり、以下のページにまとめました。

https://zenn.dev/turing_motors/articles/594fbef42a36ee

1. Rye × uvとは?

RyeはPythonの包括的なプロジェクトおよびパッケージ管理のツールです。これまでもPoetryなど管理ツールはありましたが、pyenvなどPythonのバージョン管理ツールが必要でした。Ryeは、Pythonのバージョン管理からパッケージ管理を行えて、Poetry同様にpyproject.tomlの設定ファイルを使用したプロジェクト管理も可能です。

uvは非常に高速なパッケージインストーラおよびリゾルバーで、一般的なpipおよびpip-toolsの代替としてデザインされています。2024年2月からRyeからuvを使えるようになり、パッケージインストールの高速さから絶賛の声が相次ぎました。

一方で、RyeはPythonのパッケージ管理の最終的なソリューションになりそうにないとRye作者のArmin Ronacherブログに綴っています。現在、Ryeの管理はuvの開発を行なっているAstralに移行されています。将来的には、uvがRyeに代替されるよう、現在も日々多くの機能がuvサポートされるようになっています。そのため、2024年7月現在でも一部OSSでRyeではなくuvだけが採用されているケースも見られます。

本ブログでは、現時点でのRyeおよびuv単体の基本的な操作や、便利な機能、CUDA依存のパッケージをインストールする方法を紹介します。

1.1 Ryeのインストール

Ryeの公式ドキュメントはRyeで整備されています。

今回はLinuxベースのOSでインストールします。それ以外のOSであれば公式のInstallation - Rye参照してインストールしてください。

Ryeのインストールは以下のコマンドで実行できます。途中、パッケージインストーラーをuvに選択して、RyeのPATHを.profileに追加するに選択してください。

curl -sSf https://rye.astral.sh/get | bash

もし.profileを参照しない環境であれば.bashrcなどに別途PATHを追加してください。

echo 'source "$HOME/.rye/env"' >> ~/.bashrc

今回使用するRyeのバージョンは以下になります。

rye --version
> rye 0.36.0
> commit: 0.36.0 (12c024c7c 2024-07-07)
> platform: linux (x86_64)
> self-python: cpython@3.12.3
> symlink support: true
> uv enabled: true

1.2 uv単体のインストール

uvのドキュメントはGitHubのREADME上に整備されています。

インストール方法はGetting Startedに記載されています。今回はpipxでインストールします。

pipx install uv

~/.bashrc. "$HOME/.cargo/env"が追加されているのを確認し、設定を反映させます。

source ~/.bashrc

今回使用するuvのバージョンは以下になります。

uv version
> uv 0.2.24

2. Ryeの基本操作

公式のBasics - Ryeに従いましょう。以下、特によく使う基本操作です。

2.1 Ryeの新規プロジェクトの初期化

以下のコマンドで新規プロジェクトを作成することが可能です。my-projectは任意のプロジェクト名です。

 rye init my-project
 cd my-project

すると以下のディレクトリ構造が作成されます。

.
├── .git
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src
    └── my_project
        └── __init__.py

既に作成したいプロジェクト下にいる場合はrye initで作成できます。

2.2 RyeによるPythonバージョン変更

初期化時点で.python-versionファイルがデフォルトのPythonバージョンで書き込まれています。このプロジェクト内のPythonバージョンを変える場合はrye pinで変更することが可能です。以下、Python3.11にバージョンを変更します。

rye pin 3.11

2.3 Ryeによるパッケージの同期

pyproject.tomlをもとにパッケージをインストールします。以下のコマンドを実行できます。

rye sync

この処理の際、仮想環境として.venvが作成されます。また、requirements.lockrequirements-dev.lockのlockfileも作成されて、Ryeの環境以外でもrequirements.lockなどを用いて必要なパッケージをインストールすることができます。

2.4 仮想環境のアクティベート

以下のコマンドで仮想環境を有効化できます。

. .venv/bin/activate

無効化する場合は以下を実行します。

deactivate

2.5 RyeによるPythonパッケージの追加

Ryeではrye addで新しくパッケージを追加することが可能です。高速なPythonのリンターおよびコードフォーマッターであるruffを追加してみます。

rye add ruff

この際、pyproject.tomldependenciesが更新されるのが確認できます。

dependencies = [
    "ruff>=0.5.1",
]

追加しているパッケージはrye listから確認することも可能です。

rye list
> -e file:///workspaces/my-project
> ruff==0.5.1

パッケージの追加はバージョンを指定することも可能です。

rye add "numpy<=1.26.4"

また、rye removeでパッケージを取り除くことも可能です。

rye remove numpy

パッケージの追加はrye add --devで開発用として追加することも可能です。

rye add numpy --dev

[tool.rye]に追加されるのが確認でき、lockfileはrequirements-dev.lockのみ更新されます。

[tool.rye]
managed = true
dev-dependencies = [
    "numpy>=2.0.0",
]

2.6 Ryeによる実行

rye runで実行ファイルを実行することができます。例えば、ruffによるリンターの実行であれば以下で実行します。

rye run ruff check

同様に仮想環境を有効化せずともrye run pythonで特定のPythonスクリプトを実行することも可能です。

3. uvの基本操作

uvの基本的な操作はREADMEに書かれていますが、全ての操作が書かれているわけではなさそうです。もし整備されたドキュメントがあれば教えてもらえると助かります。

3.1 uvで仮想環境を作成してパッケージをインストールする

uvであれば、仮想環境は以下のコマンドで作成できます。

uv venv

uvでPythonのバージョンを変更する場合はuv pythonで操作が行えます。uv python pinでPythonバージョンを指定した後にuv python installでPythonをインストールできます。

uv python pin 3.11
uv python install

仮想環境の有効化や無効化は前述通りの操作です。

. .venv/bin/activate
deactivate

uvで新しくパッケージをインストールする処理はuv pip installで実行できます。以下でruffをインストールします。

uv pip install ruff

インストールしたパッケージはuv pip listから確認することができます。

uv pip list

3.2 pyproject.tomlを用いてuvを操作する

uvもpyproject.tomlに対応しており、Pythonプロジェクトの構成情報をまとめて記述できて利便性が非常に高いです。現在は、pyproject.tomlを生成するコマンドはuvに存在しないためプロジェクト下に以下のファイルを作成します。

[project]
name = "my-package"
version = "0.1.0"
description = "My package description"
authors = []
requires-python = ">=3.10"

pyproject.tomlが存在する場合はuv addでパッケージを追加することが可能です。以下でruffをインストールします。

uv add ruff

するとpyproject.tomldependenciesが追加されます。

[project]
name = "my-package"
version = "0.1.0"
description = "My package description"
authors = []
requires-python = ">=3.10"
dependencies = [
    "ruff",
]

バージョンを指定することも可能です。例えばnumpyのversion1.26.4をインストールする場合は以下になり、また開発用としてインストールすることができます。

uv add "numpy==1.26.4" --dev

dependenciesは以下のようになります。

[project]
name = "my-package"
version = "0.1.0"
description = "My package description"
authors = []
requires-python = ">=3.10"
dependencies = [
    "ruff",
]

[tool.uv]
dev-dependencies = [
    "numpy==1.26.4",
]

また、このpyproject.tomldependenciesが存在すればuv syncで同期することができます。

uv sync

特定のパッケージを取り除く場合はuv removeを使います。開発用としてインストールしたパッケージは--devをつけます。

uv remove numpy --dev

4. Ryeの便利な機能

4.1 [tool.rye.scripts]によるタスクランナー

pyproject.toml内の[tool.rye.scripts]で、rye runを介して扱えるカスタムスクリプトを登録することができます。これを設定することで、例えばrye run lintを設定して、複数のリンターをこのコマンドのみで実行することができます。

最初に必要なパッケージを追加します。

rye add ruff mypy pytest pytest-cov

pyproject.tomlに以下を追加します。chainを使うことで複数のスクリプトを1回で実行することが可能になります。

[tool.rye.scripts]
lint = { chain = ["lint:ruff", "lint:ruff_format", "lint:mypy" ] }
"lint:ruff" = "ruff check ./ --diff"
"lint:ruff_format" = "ruff format --check --diff"
"lint:mypy" = "mypy ./ --explicit-package-bases"

以上を追記後、以下を実行するとruffとmypyが実行されます。

rye run lint

同様にruffによるフォーマッターも[tool.rye.scripts]に以下を追加します

format = { chain = [ "format:ruff", "format:ruff_check" ] }
"format:ruff" = "ruff format ./"
"format:ruff_check" = "ruff check ./ --fix"

以下を実行するとフォーマッターが実行されます。

rye run format

また、chainを使わない場合は{ cmd = "..." }やそのまま文字列として書くことも可能です。

test = { cmd = "pytest ./tests --cov=./src --cov-report term-missing --durations 5" }

実際にpytestをテストしてみる場合はtests/test_init.pyに以下のコードを書いて、

from my_project import hello

def test_hello() -> None:
    assert hello() == "Hello from my-project!"

pyproject.toml[tool.pytest.ini_options]を新しく追加してください。テスト対象のプロジェクトのpythonpathやテストコードが存在するパスを設定できます。

[tool.pytest.ini_options]
pythonpath = "src"
testpaths = ["tests"]

以上を追記後、以下を実行するとpytestが実行されます。

rye run test

以上が最終的な[tool.rye.scripts]です。GitHub Actionsで実行する用にlint_githubを追加しています。

[tool.rye.scripts]
lint = { chain = ["lint:ruff", "lint:ruff_format", "lint:mypy" ] }
"lint:ruff" = "ruff check ./ --diff"
"lint:ruff_format" = "ruff format --check --diff"
"lint:mypy" = "mypy ./ --explicit-package-bases"
format = { chain = [ "format:ruff", "format:ruff_check" ] }
"format:ruff" = "ruff format ./"
"format:ruff_check" = "ruff check ./ --fix"
test = { cmd = "pytest ./tests --cov=./src --cov-report term-missing --durations 5" }
lint_github = { chain = [ "lint_github:ruff", "lint:ruff_format", "lint:mypy" ] }
"lint_github:ruff" = "ruff check ./ --diff --output-format=github"

4.2 ruffやmypyの設定

2024年7月現在、PythonのCIにおいてruffの流行りを感じます。ruffは非常に高速なリンターおよびフォーマッターであり、Flake8やBlack、isort、pydocstyle、pyupgrade、 autoflakeなどを置き換えることが可能なツールになっています。また、mypyはPythonで実行可能な静的型チェッカーであり、型注釈によるコードの可読性の向上などコードの品質を高めることができます。

これらの設定はpyproject.tomlに記載することができます。コミュニティやプロジェクトごとに依存している設定があると思いますが一例を紹介します。

ruffの場合は@hppさんこちらのポストを参考に設定しています。

[tool.ruff]
exclude = [".git", ".mypy_cache", ".ruff_cache", ".venv", "third_party"]
line-length = 160
target-version = "py311"

[tool.ruff.lint]
fixable = ["ALL"]
unfixable = []
select = [
    "A",  # flake8-builtin
    "B",  # flake8-bugbear
    "E",  # pycodestyle error
    "F",  # Pyflakes
    "I",  # isort
    "N",  # pep8-naming
    "W",  # pycodestyle warning
    "PL",  # Pylint
    "UP",  # pyupgrade
]
ignore = [
    "B905",  # Zip-without-explicit-strict
    "E501",  # Line too long
    "F403",  # Unable to detect undefined names
    "N812",  # Lowercase imported as non-lowercase
    "N999",  # Invalid module name
    "PLR0912",  # Too many branches
    "PLR0913",  # Too many arguments in function definition
    "PLR2004",  # Magic value used in comparison
]

[tool.ruff.format]
quote-style = "double"
line-ending = "auto"

mypyの場合はCADDiのテックブログのmypy 設定ファイルの読み合わせと修正を実施しましたを参考に設定しています。

[tool.mypy]
allow_redefinition = true
allow_untyped_globals = false
check_untyped_defs = true
color_output = true
disallow_incomplete_defs = true
disallow_subclassing_any = false
disallow_untyped_calls = false
disallow_untyped_decorators = false
disallow_untyped_defs = true
error_summary = true
ignore_missing_imports = true
implicit_reexport = true
namespace_packages = true
no_implicit_optional = true
pretty = true
show_column_numbers = true
show_error_codes = true
show_error_context = true
show_traceback = true
strict = true
warn_no_return = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = false

この記事ではruffやmypyの詳細は紹介しませんが、ruffであれば公式ドキュメントのRules - ruffを参照すると良いです。

4.3 [tool.hatch.build.targets.wheel]によるモジュールアクセス

Poetryであれば[tool.poetry]packagesと近いことができる設定です(本来の機能と異なるかもしれませんが…)。以下のように設定することで、ディレクトリ構造を気にすることなくモジュールをimportすることが可能になります。

[tool.hatch.build.targets.wheel]
packages = ["src/my_project", "src"]

4.4 GitHub Actions上でRyeをインストールする

GitHub Actions上でCIを行うときに、Ryeのインストールおよびタスクランナーでリンターを実行することができます。Ryeのインストール後にecho "$HOME/.rye/shims" >> $GITHUB_PATHでパスを追加することが必要です。

name: Run Linters
on:
  pull_request:
    types: [ opened, synchronize ]
    paths:
      - 'src/**'
      - 'tests/**'
      - 'pyproject.toml'
      - 'requirements-dev.lock'
      - 'requirements.lock'
      - '.github/workflows/linter.yml'

jobs:
  linters:
    runs-on: ubuntu-22.04

    strategy:
      matrix:
        python-version: [3.11]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install rye
        run: |
          curl -sSf https://rye.astral.sh/get | RYE_INSTALL_OPTION="--yes" bash
          echo "$HOME/.rye/shims" >> $GITHUB_PATH

      - name: Install dependencies
        run: rye sync

      - name: Lint
        run: rye run lint_github

5. RyeでCUDA依存のパッケージをインストールする

[[tool.rye.sources]]によってPyPI以外のindexからパッケージを追加することができます。特に機械学習ライブラリは独自のインストールインデックスが指定されるものが多く、こちらを活用してインストールすることが多いです。

以下にtorch、mmcv、cupy、RAPIDS系(cudf、cuml)の追加方法を記載します。その他のCUDA依存のパッケージの追加ができたら追記していきます。

5.1 torchの場合

PyTorchのprevious-versionsを参照し、インストールするtorchのversionとその--index-urlを調べます。

今回はversion2.1.2でCUDAは12.1依存のPyTorchをインストールします。最初に以下をpyproject.tomlに追加します。

[[tool.rye.sources]]
name = "torch"
url = "https://download.pytorch.org/whl/cu121"
type = "index"

そして以下のコマンドでCUDA依存のtorchをインストールできます。

rye add "torch==2.1.2+cu121"

同様にtorchvisionもtorchaudioもインストールできます。

 rye add "torchvision==0.16.2+cu121"
 rye add "torchaudio==2.1.2+cu121"

5.2 wheelからインストール

CUDAのバージョンを合わせるために自前でビルドした場合など、localのwheelを追加したい場合はrye add コマンドの path オプションで追加可能です。

ただしフルパスを記載する必要があるため、コンテナ環境でパスを固定するなどを併用する必要があります。
Can't add relative path dependencies in virtual project

rye add --path /path/to/xxx.whl

5.3 mmcvの場合

INSTALLATION - mmcvを参照して、インストールします。Install with pipを参照のもとmmcvの設定を作成します。

OSはLinuxで、cuda12.1で、torch 2.1.x系でmmcv 2.20をインストールする場合は以下のpipコマンドになり、

pip install mmcv==2.2.0 -f https://download.openmmlab.com/mmcv/dist/cu121/torch2.1/index.html

この設定から以下の設定を追記します。

[[tool.rye.sources]]
name = "mmcv"
url = "https://download.openmmlab.com/mmcv/dist/cu121/torch2.1/index.html"
type = "find-links"

そして以下のコマンドでmmcvをインストールできます。

 rye add "mmcv==2.2.0"

5.3 cupyの場合

cupyの場合はそのままrye addすることができます。CUDA11系の場合はcupy-cuda11x、CUDA12系の場合はcupy-cuda12xを指定します。

rye add cupy-cuda12x

5.4 RAPIDSの場合

RAPIDS Installation Guideをもとにpipコマンドを調べます。CUDA12系であれば以下であり、

pip install \
    --extra-index-url=https://pypi.nvidia.com \
    cudf-cu12==24.6.* dask-cudf-cu12==24.6.* cuml-cu12==24.6.* \
    cugraph-cu12==24.6.* cuspatial-cu12==24.6.* cuproj-cu12==24.6.* \
    cuxfilter-cu12==24.6.* cucim-cu12==24.6.* pylibraft-cu12==24.6.* \
    raft-dask-cu12==24.6.* cuvs-cu12==24.6.*

この設定から以下の設定を追記します。

[[tool.rye.sources]]
name = "RAPIDUS"
url = "https://pypi.nvidia.com"
type = "index"

そして以下のコマンドでcudfとcumlをインストールできます。他のパッケージも同様にインストールできます。

rye add "cudf-cu12==24.6"
rye add "cuml-cu12==24.6"

6. uvでCUDA依存のパッケージをインストールする

pyproject.tomlでの書き方は変わりますが、Rye同様にインストールすることが可能です。

6.1 torchの場合

以下をpyproject.tomlに追記します。

[tool.uv.pip]
index-url = "https://download.pytorch.org/whl/cu121"

そして以下のコマンドでtorchのversion2.1.2をインストールできます。

uv add "torch==2.1.2"

6.2 追加でindex-urlを追加する場合

index-urlは単一のURLしか指定できません。複数指定する場合は、extra-index-urlに記載します。RAPIDUSのcuDFを追加する場合は、pyproject.tomlに以下を追記します。

[tool.uv.pip]
index-url = "https://download.pytorch.org/whl/cu121"
extra-index-url = ["https://pypi.nvidia.com"]

そして以下コマンドでcuDFをインストールできます。

uv add "cudf-cu12==24.6"

7. 終わりに

Ryeとuvの他に便利な機能があれば追記していきます!快適なRye × uvライフを!

Tech Blog - Turing

Discussion