こんにちは。Holy_KTQ(@u-10bei)と申します。
昨年に引き続き、松尾研のLLMコンペにTEAM RAMENの一員として参加させていただきました。
昨年のコンペで、自身の力不足を感じたこともあり、今年は、かなり予習をして臨みました。
そんな中から得た知見として、今回のように様々な技術レベルの方が集うチーム開発で、環境構築はどのようにしたら良いのか(良かったのか)について、共有してみたいと思います。
はじめに:「環境構築で丸一日」を終わらせる
「環境構築に丸一日かかった」「あの設定、どうやるんだっけ?」—私自身も昨年のコンペでこういった問題を経験しました。
チーム開発という視点でとらえると、この問題は士気や生産性を大きく削ぐ原因となります。特に、経験豊富な開発者と初心者の間で環境構築にかかる時間に大きな差が出るだけでなく、その複雑さから共有環境に触れることを諦め、挫折してしまうメンバーも少なくありません。これは、チームの成長にとって大きな壁となります。
私たちは、この技術レベルの差が引き起こす問題を乗り越え、 「設定ファイルの編集と数コマンドの投入で開発スタートできる」という現実的な目標を実現するために、何をしたのでしょうか?
この記事では、技術レベルの異なるメンバーが安心して開発に参加できるように作り上げた、環境構築の具体的なレシピを公開します。構築を自動化するための具体的なスクリプトの設計図を公開します。
1:環境構築の設計思想 — 3つの原則
様々な技術レベルのメンバーが開発に参加するチームでは、環境構築は「個人の作業」ではなく、「チーム共通の資産」として設計されるべきだと考えました。環境構築スクリプトを作成するにあたって、「設定ファイルの編集と数コマンドの投入で開発スタート」を実現するため、環境構築の設計において最も重視すべき3つの原則を考えていました。
-
原則1:再現性(どこでも動く)
常に同じ環境が立ち上がることは、バグの撲滅とスムーズな連携の土台です。
経験豊富なメンバーが「自分の環境では動く」という状態は、チームにとって最も危険な兆候の一つです。この原則は、OSや個人環境に依存せず、チームのどのメンバーが、いつ、どのPCでセットアップしても、完全に同じバージョンのミドルウェア、ライブラリ、設定で開発環境が構築されることを保証します。これにより、開発者が環境の違いによる「動かない」問題の調査に時間を浪費することを防ぎます。
-
原則2:ポータビリティ(持ち運びやすさ)
開発環境は、簡単に共有し、破棄し、再構築できる状態を維持すべきです。
環境が「持ち運びやすい」ことで、次のようなメリットが生まれます。
- オンボーディングの高速化:新メンバーへの環境共有が迅速に行えます。
- クリーンな状態の保証:長期間開発しているうちに生じる環境の「汚れ」を、環境を破棄して再構築することで簡単にリセットできます。
-
原則3:最小の手数(迷わない)
環境構築にかかる手順は、極限まで簡略化することが、初心者の心理的ハードルを下げる鍵です。
手順が多ければ多いほど、メンバーは途中で**「本当にこれで正しいのか?」**と迷い、ミスが発生する確率も高まります。この原則に基づき、私たちは環境構築の手順を「設定ファイルの編集 $\rightarrow$ 数コマンドの実行」というレベルまで自動化・簡略化することを目指しました。複雑な設定はドキュメントに頼るのではなく、自動化スクリプトやツールに任せる工夫が必要です。
2:具体的なスクリプトの設計にあたって
-
主な特徴
- モジュール化された設計: 設定とロジックが分離されており、各スクリプトが単一の責務を持つため、メンテナンスやカスタマイズが容易です。
- 柔軟なモード切替: pretrain、eval0、デフォルトの3つのモードを切り替えることで、用途に応じたライブラリセットを簡単に構築できます。
- 堅牢な実行方式: メインスクリプトから各サブスクリプトへConda環境のフルパスを渡すことで、HPC環境でも安定して動作します。
-
ディレクトリ構成
\~/team_suzuki/infra/HPC/ ├── config/ │ ├── env_config.sh \# デフォルト/pretrainモード用の設定ファイル │ ├── env_eval0.sh \# eval0モード用の設定ファイル │ ├── requirements.txt \# デフォルト/pretrainモード用のpip要件リスト │ └── requirements-eval0.txt \# eval0モード用のpip要件リスト │ ├── setup/ │ ├── install_conda_libs.sh \# Condaパッケージをインストール │ ├── install_pip_libs.sh \# Pipパッケージをインストール │ ├── build_utils.sh \# カスタムライブラリビルド用の共通関数 │ ├── build_apex.sh \# Apexのビルドスクリプト │ └── ... \# 他のビルドスクリプト │ ├── test/ │ ├── submit_run_test.sh \# テスト実行用のジョブスクリプト │ └── ... │ └── batch_submit.sh \# 全てのセットアップを開始する最上位のジョブスクリプト -
環境構築と使い方 githubにて公開予定
-
前提条件
- NVIDIA GPUとドライバ: 計算ノードにNVIDIA GPUと適切なドライバがインストールされていること。
-
HPCのモジュールシステム:
module loadコマンドが利用できる環境であること。
-
前処理
# リポジトリのクローン) git clone git@github.com:matsuolab-llmcompe2025-team-suzuki/team_suzuki.git -
設定ファイルの編集(必要な場合)
構築したい環境に応じて、
config/ディレクトリ内の設定ファイルを編集します。-
デフォルト/pretrainモード:
config/env_config.shを編集します。 -
eval0モード:
config/env_eval0.shを編集します。 -
主な設定項目:
-
CONDA_ENV_NAME: 作成するConda環境のベース名。 -
CONDA_ENV_FULL_PATH: 実際にConda環境が作成される場所。デフォルトでは$HOME/envs/${CONDA_ENV_NAME}に設定されています。 -
PYTHON_VERSION: Pythonのバージョン (例: 3.11)。 -
CUDA_TOOLKIT_VERSION,TE_COMMITなど、各種ライブラリのバージョン。
-
-
-
セットアップ実行
設定が完了したら、
batch_submit.shスクリプトでセットアップジョブを投入します。このコマンド一つで、全自動で環境が構築されます。batch_submit.shは、オプション引数pretrainまたはeval0を認識します。-
引数なし (デフォルトモード):
config/env_config.shに基づき、基本的なライブラリのみをインストールします。 -
pretrain(事前学習モード): デフォルトのライブラリに加え、Apex、TransformerEngine、FlashAttentionといった追加のカスタムライブラリをソースからビルド・インストールします。 -
eval0(評価モード): 評価に特化したライブラリ群をインストールします(ソースビルドは行いません)。デフォルトでconfig/env_eval0.shとconfig/requirements-eval0.txtが使用されます。
-
引数なし (デフォルトモード):
-
実行コマンド例
# 例1: デフォルト設定で最小限の環境を構築 cd ~/team_suzuki/infra/HPC/ sbatch batch_submit.sh # 例2: デフォルト設定で事前学習用の全ライブラリを構築 (pretrainモード) cd ~/team_suzuki/infra/HPC/ sbatch batch_submit.sh pretrain # 例3: 評価用の環境を構築 (eval0モード) cd ~/team_suzuki/infra/HPC/ sbatch batch_submit.sh eval0 # 例4: カスタム設定ファイルで最小限の環境を構築 cd ~/team_suzuki/infra/HPC/ # config/my_env.sh は事前に作成しておく sbatch batch_submit.sh config/my_env.sh # 例5: カスタム設定ファイルで事前学習用の全ライブラリを構築 cd ~/team_suzuki/infra/HPC/ sbatch batch_submit.sh pretrain config/my_env.shジョブの進捗は tail -f gpu_env_setup-(job_id).out でリアルタイムに確認できます。
-
作成した環境の有効化と利用
ジョブが完了すると、CONDA_ENV_FULL_PATHで指定したパスにConda環境が作成されています。
環境の有効化は、名前ではなくフルパスで行ってください。
# .bashrcを再読み込み (初回のみ) source ~/.bashrc # 悪い例 ❌ (カスタムパスにある環境は名前で見つけられない可能性がある) # conda activate compe_eval # 良い例 ✅ (設定ファイルで指定したフルパスで有効化する) # 例: export CONDA_ENV_FULL_PATH="$HOME/envs/compe_eval" と設定した場合 conda activate /home/your_username/envs/compe_eval
-
3:理想と現実(環境構築の進化)
こうして作成した環境構築スクリプトですが、当初の想定とは異なり、すべての方が使うものとはなりませんでした。概ね、以下のような経緯をたどったと思われます。
- Phase1前半(個人ディレクトリでの構築)
- すでに、プログラムスキルのある方が、初期環境構築に活用
- チーム内ハンズオンの開催などで、浸透を図る
- Phase1後半(共用ディレクトリで個人ごとに構築)
- モデル班、評価班がそれぞれに必要な環境を独自に構築
- Phase2以降(共用ディレクトリで構築した環境を共用)
- 評価班については、属人化を防ぐために一つの環境を共用することを決断。batchスクリプトの中に、activateコマンドを埋め込むことにした。
最終的に、誰でも環境構築を意識せずに、HPC環境に触れるまでには至りませんでしたが、このスクリプトは環境構築のハードルを下げる一定の役割は果たしたと思っています。
ただし、それでも残った課題について、列挙しておきます。
- 可読性の低下
誰でも動かせることのトレードオフとして、エラー時処理が膨大になった結果として、可読性が低下し、「誰でもメンテできる」という状態にまでは至りませんでした。 - 環境共用のリスク
今回は発生しませんでしたが、共用環境に他の人がライブラリインストールすると、所有者権限が複雑になり、再構築が大変になります。 - singurality対応
今回の環境では、Dockerは使用できませんでしたが、同様なコンテナ技術を持つ、singuralityは使用可能でした。採用すればさらにポータビリティの向上が見込めましたが、私自身の技術力不足があり、チーム内の共有までのコストを考え、導入を見送りました。
おわりに
折りたたみを開くと、バッチスクリプト部分を見ることができます。
#!/bin/bash
#
# Slurmジョブとして、開発環境の完全な自動構築を行います。
# `conda activate`方式で全体のライフサイクルを管理します。
# コマンドライン引数で設定ファイルを切り替え可能です。
#
# ##############################################################################
# 使用法:
# sbatch batch_submit.sh [mode] [設定ファイルへのパス]
#
# - `mode` (オプション): "pretrain" または "eval0" を指定します。
# - `pretrain`: apex, transformerengine 等を含む全てのライブラリをビルド・インストールします。
# - `eval0`: デフォルトで env_eval0.sh を読み込みます。
# - 未指定 (デフォルト): 最小限のライブラリをインストールします (apex等はスキップ)。
#
# - `設定ファイルへのパス` (オプション): カスタム設定ファイルを指定してデフォルトを上書きします。
# ##############################################################################
# --- Slurm ジョブ設定 ---
#SBATCH --job-name=gpu_env_setup
#SBATCH --partition=XXX
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=64
#SBATCH --gres=gpu:1
#SBATCH --time=02:00:00
#SBATCH --output=%x-%j.out
#SBATCH --error=%x-%j.err
#SBATCH --mem=256G
# --- 初期設定 ---
echo "--- Slurmジョブスクリプト開始 ---"
echo "ジョブ実行ホスト: $(hostname)"
echo "現在時刻: $(date)"
echo "作業ディレクトリ: $(pwd)"
echo "---------------------------------"
# --- モジュールのロード(HPC環境の構築に必要) ---
echo "HPC環境のモジュールをリセットします..."
if module reset; then
echo "モジュールのリセットに成功しました。必要なモジュールをロードします。"
module load nccl/2.22.3
module load hpcx/2.18.1-gcc-cuda12/hpcx-mt
else
echo "警告: 'module reset' に失敗しました。HPCモジュールのロードをスキップします。"
fi
# --- ロギングと設定ファイルの決定 ---
readonly DEFAULT_PROJECT_DIR="$HOME/team_suzuki/infra/HPC"
readonly LOGGING_UTILS_PATH="${DEFAULT_PROJECT_DIR}/setup/logging_utils.sh"
if [ ! -f "${LOGGING_UTILS_PATH}" ]; then
echo "FATAL: Logging utility not found at ${LOGGING_UTILS_PATH}" >&2
exit 1
fi
source "${LOGGING_UTILS_PATH}"
# エラーハンドリングとパイプの堅牢化
trap 'handle_error $? $LINENO "$BASH_COMMAND"' ERR
set -eo pipefail
# --- ヘルパー関数定義 ---
def_var() {
var_name="$1"
value="$2"
eval "$var_name=\"\$value\""
export "$var_name"
readonly "$var_name"
}
# --- 引数と設定ファイルの解析 ---
INSTALL_OPTION="default"
if [ "$1" == "pretrain" ] || [ "$1" == "eval0" ]; then
INSTALL_OPTION="$1"
log_info "インストールオプション '${INSTALL_OPTION}' が有効です。"
shift
else
log_info "デフォルトのインストールオプションで実行します。追加ライブラリのインストールはスキップされます。"
fi
# --- デフォルト設定ファイルの決定 ---
DEFAULT_CONFIG_FILE=""
if [ "${INSTALL_OPTION}" == "eval0" ]; then
DEFAULT_CONFIG_FILE="${DEFAULT_PROJECT_DIR}/config/env_eval0.sh"
else
# default と pretrain は同じデフォルト設定ファイルを使用
DEFAULT_CONFIG_FILE="${DEFAULT_PROJECT_DIR}/config/env_config.sh"
fi
# --- 使用する設定ファイルの最終決定 ---
CONFIG_FILE_ARG="$1"
CONFIG_FILE=""
if [ -n "${CONFIG_FILE_ARG}" ]; then
log_info "コマンドライン引数で設定ファイルが指定されました: ${CONFIG_FILE_ARG}"
CONFIG_FILE="${CONFIG_FILE_ARG}"
else
log_info "デフォルトの設定ファイルを使用します: ${DEFAULT_CONFIG_FILE}"
CONFIG_FILE="${DEFAULT_CONFIG_FILE}"
fi
if [ ! -f "${CONFIG_FILE}" ]; then
log_error "設定ファイルが見つかりません: ${CONFIG_FILE}"
exit 1
fi
# --- 設定ファイルの読み込み ---
log_info "設定ファイルを読み込みます: ${CONFIG_FILE}"
source "${CONFIG_FILE}"
# --- `env_config.sh` 読み込み後のディレクトリ定義 ---
readonly PROJECT_DIR="${TOOLS_DIR}/HPC"
readonly SETUP_DIR="${PROJECT_DIR}/setup"
readonly CONFIG_DIR="${PROJECT_DIR}/config"
# --- 使用するrequirementsファイルを決定 ---
REQUIREMENTS_FILE=""
# 設定ファイル内で `PIP_REQUIREMENTS_FILE` 変数が定義されていれば、それを使用する
if [ -n "${PIP_REQUIREMENTS_FILE}" ]; then
log_info "設定ファイルで指定されたrequirementsファイルを使用します: ${PIP_REQUIREMENTS_FILE}"
REQUIREMENTS_FILE="${PIP_REQUIREMENTS_FILE}"
else
# 未定義の場合は、デフォルトの `requirements.txt` を使用する
DEFAULT_REQUIREMENTS_FILE="${CONFIG_DIR}/requirements.txt"
log_info "設定ファイルでの指定がないため、デフォルトのrequirementsファイルを使用します: ${DEFAULT_REQUIREMENTS_FILE}"
REQUIREMENTS_FILE="${DEFAULT_REQUIREMENTS_FILE}"
fi
# --- 一時ディレクトリの作成 ---
export TMPDIR="/var/tmp/${USER}-${SLURM_JOB_ID}"
mkdir -p "$TMPDIR"
print_header "環境構築ジョブ開始"
log_info "ジョブID: ${SLURM_JOB_ID:-"N/A"}"
log_info "実行モード: ${INSTALL_OPTION}"
log_info "使用する設定ファイル: ${CONFIG_FILE}"
log_info "使用するPip要件定義ファイル: ${REQUIREMENTS_FILE}" # ログ出力も修正
nvidia-smi
# ==============================================================================
# --- グローバルなConda初期化 ---
# ==============================================================================
log_info "Condaのシェル機能を初期化します..."
source "${CONDA_ROOT_PATH}/etc/profile.d/conda.sh"
log_success "Condaの初期化が完了しました。"
# --- Condaキャッシュをクリーンアップ ---
print_header "Conda キャッシュのクリーンアップ"
conda clean --all -y
# --- Anaconda利用規約に同意 ---
print_header "Anaconda 利用規約への同意"
# 安定した運用のために自動更新を無効化
conda config --set auto_update_conda false
# エラーメッセージに表示されたコマンドで規約に同意
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
log_success "利用規約に同意しました。"
# --- Conda環境の作成 ---
source "${SETUP_DIR}/setup_env.sh"
# --- Condaライブラリのインストール ---
bash "${SETUP_DIR}/install_conda_libs.sh" "${CONDA_ENV_FULL_PATH}"
# ==============================================================================
# --- 環境を有効化 (Activate) ---
# ==============================================================================
print_header "Conda環境 '${CONDA_ENV_NAME}' を有効化"
conda activate "${CONDA_ENV_FULL_PATH}"
# --- Pipライブラリのインストール ---
print_header "Pipライブラリのインストール"
if [ ! -f "${REQUIREMENTS_FILE}" ]; then
log_error "requirementsファイルが見つかりません: ${REQUIREMENTS_FILE}"
exit 1
fi
bash "${SETUP_DIR}/install_pip_libs.sh" "${REQUIREMENTS_FILE}" "${INSTALL_OPTION}"
# --- PyTorch GPU認識テスト ---
python "${SETUP_DIR}/test_pytorch_gpu.py"
# ==============================================================================
# --- CUDA依存ライブラリのビルドと検証 (pretrain オプション時のみ) ---
# ==============================================================================
if [ "${INSTALL_OPTION}" == "pretrain" ]; then
print_header "CUDA依存ライブラリのビルドと検証を開始 (pretrain オプション)"
log_info "apex, transformerengine, flashattention などをビルドします..."
bash "${SETUP_DIR}/build_all_custom_libs.sh" "${CONDA_ENV_FULL_PATH}"
log_info "ビルドされたライブラリのインポートを検証します..."
bash "${SETUP_DIR}/verify_builds.sh"
log_success "追加ライブラリのビルドと検証が完了しました。"
else
print_header "CUDA依存ライブラリのビルドをスキップ"
log_info "pretrainオプションが指定されていないため、apex等のビルドは行いません。"
fi
# ==============================================================================
# --- 環境を無効化 (Deactivate) ---
# ==============================================================================
print_header "Conda環境を無効化"
conda deactivate
print_header "環境構築ジョブ正常終了"
log_success "すべてのセットアップが完了しました。"
# スクリプトの最後にトラップを解除
trap - ERR
最後まで読んでいただき、ありがとうございました。この記事が、チーム開発の土台を強くするためのヒントになれば幸いです。
本プロジェクトは、国立研究開発法人新エネルギー・産業技術総合開発機構(以下「NEDO」)の「日本語版医療特化型LLMの社会実装に向けた安全性検証・実証」における基盤モデルの開発プロジェクトの一環として行われます。