Nixとuvで磨くPython開発環境の再現性
LLM自作入門 という本の輪読会に参加しており、書籍のGitHubを用いて環境構築をしました。この記事ではLLM自作入門の第二章の環境再現を通じて、Nixとuvを利用した環境構築手法を紹介します。
本記事は requirements.txt
が提供されているケースを紹介しますが、ゼロから作る場合でもほぼ同じです。(uv add
が不要なだけ)
本記事の対象者はPythonの経験者です。Nixやuvの前提知識は問いません。
再現可能な環境を目指す
Python(特にCPython) はmacOSや一部のLinux Distributionに採用されているため、グローバルインストールすると思わぬ副作用が発生する可能性があります。また、ハッシュ無しのrequirements.txtのみで依存パッケージを管理している場合、再現に失敗することがあります。
再現性が担保されない環境では、実装者ごとに挙動が変わりかねません。また、実装してから月日が立った場合に挙動が変わる可能性もあります。実際、著者は数年前のPython本のコードが動かない経験をしたことがあります。
本記事では、Nixとuvを利用して「LLM自作入門の第二章の環境」の再現性を高めます。最終的にはNixとuvのロックファイル(プロジェクトが依存するすべてのパッケージおよび、そのバージョン・ハッシュ値を記録したファイル)が作成されます。
8月13日追記
また、requirements.txtはロックファイルのような機構を持たないため、完全な再現性が担保されません。
について誤りがあったため修正しました (@zhanpon 様ありがとうございました)
環境構築の流れ
次の流れで環境構築をします。
- 開発用シェルの作成
- Pythonプロジェクトの作成
開発用シェルの作成
Nixとnix developの紹介
Nix
は再現可能なビルドを実現するパッケージマネージャです。ビルドが、(ストアになければ) ホスト内のサンドボックスで行なわれるので、いわゆるDependency Hell (依存関係地獄) を回避できます。また、依存関係やビルド手順を宣言的に記述できることも特徴です。
開発中に便利なのが、Nixのコマンドである nix develop
です。flake.nix
などに定義された設定をもとに、一時的な開発用のシェルを立ち上げられます。開発用シェル内では宣言した依存パッケージが揃っており、ツールやライブラリが使えます。そしてセッションを終了すれば、その環境は破棄され、ホストシステムには変更が残りません。
Nixのインストール
公式の案内に従ってインストールしてください。有名どころのOSであれば1行実行するだけで入ります。
Nixでuv環境を作るソースコード
実際のコードを見てみましょう。
{
description = "Python Shell";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in {
devShell = pkgs.mkShell {
buildInputs = [
pkgs.python312
pkgs.uv
];
shellHook = ''
echo "uv version: $(uv --version)"
echo "python version: $(python --version)"
'';
};
});
}
※何やら難しそうなコード が出てきましたが、読む部分は以下の部分だけで十分です。
buildInputs = [
pkgs.python312
pkgs.uv
];
shellHook = ''
echo "uv version: $(uv --version)"
echo "python version: $(python --version)"
'';
-
buildInputs
にパッケージを記述します。今回はuvとPythonのバージョン3.12を入れています。パッケージ名称はNixOS Searchで検索すれば出てきます。 -
shellHook
にシェル起動時に動くコマンドを記述できます。今回はそれぞれのバージョンを確認します。
※ Nixの設定ファイルは関数型言語であるNix言語で記述します。本記事は関数型言語に馴染みの無い方を想定しているため、このような説明をしました。
開発用シェルを起動
nix develop
で起動すると次のようにシェルが立ち上がります。
$ nix develop
uv version: uv 0.8.0
python version: Python 3.12.11
NixでPythonが管理できているか確認してみましょう。
bash-5.2$ which python
/nix/store/a97d77dxagnddkn5484kfc2gyj3dcdnq-python3-3.12.11/bin/python
ホストOSのPythonを利用していないことが分かりました。
(nix developで起動したシェルは既存のツールや環境変数を引き継ぐため、記載していないツールはHost OSのパスになります)
Pythonプロジェクトの作成
uvの紹介
uvは、Pythonのパッケージ管理と実行環境を高速に構築できるツールです。ロックファイルを作成するのでバージョンのブレを防げます。また、依存関係解決・インストール速度が非常に速いです。ここ1年ぐらいで利用が大きく広まったので、名前を聞いたことがある方もいるかもしれません。
プロジェクトを初期化
nix develop
を実行したディレクトリで初期化します。毎回忘れるので --help
で確認しています。今回は uv init chapter_2 --app
としました。
bash-5.2$ uv init --help
Create a new project
Usage: uv init [OPTIONS] [PATH]
Options:
--name <NAME> The name of the project
--bare Only create a `pyproject.toml`
--package Set up the project to be built as a Python package
--no-package Do not set up the project to be built as a Python package
--app Create a project for an application
--lib Create a project for a library
--script Create a script
Pythonパッケージインストール
LLM自作入門のrequirements.txtを確認すると次のようになっています。今回はch02の環境構築をしたかったので、ch04移行の依存関係はコメントアウトしました。
torch >= 2.3.0 # all
jupyterlab >= 4.0 # all
tiktoken >= 0.5.1 # ch02; ch04; ch05
# matplotlib >= 3.7.1 # ch04; ch06; ch07
# tensorflow >= 2.18.0 # ch05; ch06; ch07
# tqdm >= 4.66.1 # ch05; ch07
# numpy >= 1.26, < 2.1 # dependency of several other libraries like torch and pandas
# pandas >= 2.2.1 # ch06
# psutil >= 5.9.5 # ch07; already installed automatically as dependency of torch
ではインストールしてみましょう。requirements.txt経由でインストールする場合は次のコマンドを実行します。
bash-5.2$ uv add --requirements requirements.txt
Using CPython 3.12.11 interpreter at: /nix/store/a97d77dxagnddkn5484kfc2gyj3dcdnq-python3-3.12.11/bin/python3.12
Creating virtual environment at: .venv
Resolved 122 packages in 659ms
Prepared 102 packages in 11.85s
Installed 102 packages in 407ms
...
+ jupyterlab==4.4.5
...
+ tiktoken==0.11.0
...
+ torch==2.8.0
...
インストールが終わったら pyproject.toml
を確認します。requirements.txt
で記述した依存関係が記載されていればOKです。
[project]
name = "chapter-2"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"jupyterlab>=4.0",
"tiktoken>=0.5.1",
"torch>=2.3.0",
]
おわりに
再現性の担保といわれると、大量の複雑なコードが必要なのかなと感じるかもしれませんが、意外と簡単だったと思います。この記事をきっかけにNix + uvの利用者が増えれば幸いです。
参考
Nix
Nixについては次のZenn本がおすすめです。
uv
uvは公式ドキュメントとGitHubのリリースノートを参考にしています。
補足
最新版のuvをインストールしたいという方もいると思います。その場合は cargo
を Nix
経由で取得しBuildすると良いでしょう。
cargo install --git https://github.com/astral-sh/uv uv
Discussion
Nixに入門したいと思っていたので、この記事を参考にはじめてみようと思います!
実はrequirements.txtにもロックファイル相当の機能があるので少し補足させてください。
python3 -m pip install --require-hashes --only-binary :all: -r requirements.txt
のようにすればrequirements.txtを検証してからインストールします参考資料:
@zhanpon
補足いただきありがとうございます!! 記事修正致します。
requirements.txt
側にもLockfile相当の機能があるのは目から鱗でした。