AWSのPhysical AI Scaffolding Kit (PASK)を試す① — SageMaker HyperPodでSlurmクラスタを構築する
連載「PASKを一つずつ試す」の第1回です。AWSジャパンの「フィジカルAI開発支援プログラム」で公開されているサンプルリポジトリPhysical AI Scaffolding Kit (PASK)を、実際に自分のAWSアカウントへデプロイしながら理解していきます。第1回は土台となるAmazon SageMaker HyperPodのSlurmクラスタ構築です。
はじめに
ロボット学習(Physical AI)のモデル開発では、大量のGPUリソースで効率よく学習を回す環境が欠かせません。PASKは、その環境をAWS上にCDKとして提供してくれるサンプル集です。
PASKは大きく2層構成です。
環境構築コンポーネント(CDK)
| ディレクトリ | 役割 |
|---|---|
hyperpod/ |
標準的なSlurmクラスタをHyperPodで構築(今回) |
physai/ |
パイプラインSDK + physai CLI(HyperPodの上位版) |
isaacsim-workstation/ |
NVIDIA Isaac Sim開発機をEC2に構築 |
学習サンプル(HyperPod上で実行)
- π0 / openpi(LoRAファインチューニング)
- NVIDIA Isaac GR00T(VLAモデルのファインチューニング)
- Isaac Lab Newton RL(四足歩行ロボットの強化学習)
学習サンプルはいずれも「HyperPodクラスタが立っている」ことが前提なので、まずは土台のhyperpod/から始めます。
この記事のゴール
hyperpod/のCDKでHyperPodのSlurmクラスタを構築し、ログインノードにSSH接続して、共有ストレージ(FSx for Lustre)がS3と連携することを確認するところまでを行います。
今回構築するアーキテクチャ
VPC (10.0.0.0/16)
├── Public Subnet + NAT Gateway
└── Private Subnet
├── HyperPod クラスタ
│ ├── controller (ml.c5.large) … Slurm スケジューラ
│ └── login (ml.c5.large) … SSH 接続の入口
└── FSx for Lustre (/fsx) ⇔ S3 バケット(双方向同期)
ポイントは、初回はGPUワーカーを作らないことです。hyperpod/cdk.jsonのWorkerGroupはデフォルトで空になっており、これは公式手順でも「GPUインスタンスの確保失敗でデプロイ全体が落ちるのを防ぐため、初回はworker groupを入れない」と推奨されています。GPUワーカーは次回追加します。
事前準備
AWSアカウントとプロファイル
検証用のAWSアカウントに、CLI用のプロファイルを用意します(本記事ではプロファイル名pask、リージョンus-west-2)。今回は検証用途・短期・破棄前提だったため、IAMユーザーのアクセスキーを発行して使いました。
アクセスキーは長期認証情報のため、本番運用ではIAM Identity Center(SSO)やロールの利用が推奨されます。検証で使う場合も、終わったらキーを削除する運用にしておくと安全です。
aws configure --profile pask
# AWS Access Key ID / Secret Access Key / region(us-west-2) / output(json)
# 疎通確認
aws --profile pask sts get-caller-identity
ローカルツール
- Node.js / AWS CDK(
cdk) - Docker
- AWS CLI v2
- session-manager-plugin(ログインノードへのSSHに必須)
session-manager-pluginはmacOSならHomebrewで入ります。
brew install session-manager-plugin
session-manager-plugin --version
私は普段
npmではなくpnpmを使っているため、本記事の依存インストールはpnpm installで行っています。cdkはグローバル導入済みのものを使います。
クォータの確認(GPUを使う前に)
HyperPodのクラスタ用インスタンスは、サービスクォータ(<インスタンスタイプ> for cluster usage)の枠が必要です。新規アカウントだとGPU系は0のことが多いので、事前確認しておきます。
aws service-quotas list-service-quotas --service-code sagemaker \
--profile pask --region us-west-2 \
--query "Quotas[?contains(QuotaName,'cluster usage')].{Name:QuotaName,Applied:Value}" \
--output table
筆者の環境では、controller/login用のml.c5.largeは30、GPUでは**ml.g6e(L40S)/ ml.g6(L4)系は全部0でしたが、ml.g5(A10G)系は枠がありました(ml.g5.12xlarge=10など)。**初回はGPUを使わないので関係なしですが、次回GPUワーカーを足すときはg5系を選べば申請なしで進められます。
デプロイ
1.リポジトリと依存
git clone https://github.com/aws-samples/sample-physical-ai-scaffolding-kit.git
cd sample-physical-ai-scaffolding-kit/hyperpod
pnpm install
2. cdk bootstrap
cdk bootstrapは、CDKがデプロイで使う「足場」(アセット用S3バケット・ECR・IAMロールをまとめたCDKToolkitスタック)を、アカウント × リージョンに一度だけ作るコマンドです。継続課金されるサーバー類は作らないので、ほぼ無料です。
export AWS_PROFILE=pask
cdk bootstrap
3. cdk deploy(ここから課金開始)
cdk.jsonはデフォルト(WorkerGroup: [])のまま、デプロイします。
cdk deploy
# Do you wish to deploy these changes? (y/n) → y
筆者の環境では約16分で完了し、以下のようなOutputsが表示されました(一部マスキング)。
PASK.HyperPodClusterId538E5BBE = pask-cluster
PASK.HyperPodLoginGroupNameC7597C92 = login-group
PASK.BucketDataBucketName8A747711 = pask-bucketdata-xxxxxxxx
PASK.ClusterClusterAvailabilityZone995C3B25 = us-west-2c
PASK.Region = us-west-2
このcdk deployで、VPC・NAT・FSx for Lustre・HyperPodクラスタ(ml.c5.large×2)が実際に立ち上がります。ここから時間課金が始まります(後述)。
クラスタへの接続(SSM経由SSH)
HyperPodのログインノードはプライベートサブネットにあり、パブリックIPを持ちません。そのため、SSHをAWS Systems Manager (SSM) Session Managerのトンネルに通して接続します。セットアップには公式のeasy-ssh.shを使います。
# wget が無い環境なので curl で取得
curl -fsSL https://raw.githubusercontent.com/awslabs/awsome-distributed-training/refs/heads/main/1.architectures/5.sagemaker-hyperpod/easy-ssh.sh -o easy-ssh.sh
chmod +x easy-ssh.sh
export AWS_PROFILE=pask
./easy-ssh.sh -c login-group pask-cluster
実行すると~/.ssh/configに以下のような設定が追記されます。
Host pask-cluster
User ubuntu
IdentityFile /Users/<user>/.ssh/id_ed25519
ProxyCommand sh -c "aws ssm start-session --target sagemaker-cluster:<cluster-id>_login-group-i-<instance-id> --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
ProxyCommandの中でaws ssm start-sessionを呼ぶことで、SSH通信がSSMトンネルの中を流れます。SSHなのにAWS認証(AWS_PROFILE=pask)が要るのは、入口がSSMだからです。
ハマりどころ:公開鍵の自動登録に失敗する
easy-ssh.shはubuntuユーザーのauthorized_keysへの公開鍵登録も行いますが、クラスタ作成直後はタイミングによって以下のように失敗することがありました。
Error: Failed to add SSH public key to the cluster. The key was not found after writing.
You may need to manually add your public key to /fsx/ubuntu/.ssh/authorized_keys on the cluster.
この場合、いったんrootで接続できる状態にはなっているので(easy-ssh.sh実行直後の#プロンプト、または再実行)、そのrootセッションで手動登録すれば解決します。
# root セッション内で(公開鍵はローカルの ~/.ssh/id_ed25519.pub の中身)
echo "ssh-ed25519 AAAA...(自分の公開鍵)... user@example.com" >> /fsx/ubuntu/.ssh/authorized_keys
chown ubuntu:ubuntu /fsx/ubuntu/.ssh/authorized_keys
chmod 600 /fsx/ubuntu/.ssh/authorized_keys
その後、rootセッションを抜けて、改めてubuntuで接続します。
exit
ssh pask-cluster
成功すると、ログインノードにubuntuユーザーで入れます。
You're on the login
Controller Node IP: 10.0.199.199
Login Node IP: 10.0.225.142
Instance Type: ml.c5.large
ubuntu@ip-10-0-225-142:~$
動作確認
Slurmの状態
sinfo
# PARTITION AVAIL TIMELIMIT NODES STATE NODELIST
# dev* up infinite 0 n/a
devパーティションがup(稼働中)になっており、Slurmスケジューラ自体は正常です。NODES = 0は、まだGPUワーカーを入れていないためで、これは想定通りです(次回追加すると、ここにノードが現れます)。
FSx for Lustre ⇔ S3の連携
クラスタの共有ストレージ/fsxのうち、/fsx/s3linkがS3バケットとリンクしています。テストファイルを置いて、S3に同期されるか確認します。
# クラスタ内(ubuntu)
sudo chmod -R 777 /fsx/s3link
touch /fsx/s3link/test.txt
# 手元から S3 を確認
aws s3 ls s3://pask-bucketdata-xxxxxxxx/ --recursive --profile pask --region us-west-2
# 2026-06-16 17:08:23 0 test.txt
/fsx/s3linkに置いたファイルが、S3バケットに自動で現れました。「クラスタの共有ストレージに置いたデータがS3と連携する」ことが確認できました。
課金と後片付け
このクラスタ(ml.c5.large×2 + FSx for Lustre + NAT Gateway)は、アイドル状態でも稼働し続ける限り課金されます(us-west-2オンデマンドでおよそ月$355相当 ≒ 1日$11〜12)。HyperPodはアイドルのノードを自動停止しないため、使わないときは破棄するのが基本です。
# 後片付け(hyperpod/docs の CLEANUP 手順を参照)
cdk destroy
cdk destroyするとクラスタ・VPC・FSxは消えますが、リポジトリは手元に残るため、再度cdk deployすれば約20分で同じ環境を再構築できます。また、S3データバケットは保持設定(RETAIN)なので、destroyしてもデータは残ります(学習データやチェックポイントを置いておけば再構築後も使えます)。
まとめ
第1回として、PASKのhyperpod/でHyperPodのSlurmクラスタを構築し、
cdk bootstrap→cdk deployでクラスタ一式を構築- SSM経由SSHでログインノードへ接続(公開鍵登録のつまずき対処も)
sinfoでSlurm稼働確認、FSx ⇔ S3連携を確認
までを行いました。「箱(クラスタ+スケジューラ)はできたが、中で計算するGPUマシンはまだ」という状態です。
次回は、GPUワーカー(ml.g5系)を追加し、実際に学習サンプル(GR00Tなど)を動かしていきます。




