前回の記事で作成したAPV-MCTSのセルフプレイによる強化学習のAWS Batch環境をGPU化して動かしてみました。
今回のコード
下記、タグv0.0.2
になります。
環境の概要
全体構成は下記です。
前回の構成から下記の変更を行いました。
- nvidia-docker2に対応したAMIの作成を行うCodeBuildを追加
- セルフプレイの新モデル作成時のトレーニングをGPUに対応
- AWS Batchのコンピューティング環境で使うAMIとインスタンスを変更
GPU化の実装
具体的な実装内容です。ちなみに環境作成手順はbashからマークダウン形式に変更しdocs/setup_batch.mdに移動しました。
nvidia-docker2に対応したAMIの作成を行うCodeBuildを追加
Packerを使ってDeep Learning AMI(ami-08a7740ff4d3fd90f
)をベースにnvidia-docker2に対応したAMI
を作成するCodeBuildを追加しました。
CodeBuildでPackerを使ってのAMIの作成方法は下記を参考にしました。
nvidia-docker2に対応したAMI
の作成方法は下記を参考にしました。
ただ、Deep Learning AMI
の/usr/local/cuda/version.txt
が複数行になっていたのでconfigure-gpu.sh
のGet CUDA version
はcat
をhead -n 1
に変更しました。
以下、コードです。
CodeBuildの作成手順は下記です。Packerで必要になるAMI作成場所のサブネットID*1などを環境変数で渡すように指定しています。
cat <<EOF > Source.json { "type": "CODECOMMIT", "location": "https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/heta-reversi", "buildspec": "buildspec_ami.yml" } EOF cat <<EOF > Artifacts.json { "type": "NO_ARTIFACTS" } EOF cat <<EOF > Environment.json { "type": "LINUX_CONTAINER", "image": "aws/codebuild/docker:18.09.0", "computeType": "BUILD_GENERAL1_SMALL", "environmentVariables": [ { "name": "AWS_REGION", "value": "ap-northeast-1", "type": "PLAINTEXT" }, { "name": "AWS_SUBNET_ID", "value": "${SUBNET_ID}", "type": "PLAINTEXT" } ] } EOF aws codebuild create-project --name test-batch-ami \ --source file://Source.json \ --artifacts file://Artifacts.json \ --environment file://Environment.json \ --service-role ${ROLE_AMI_BUILD_ARN} CODEBUILD_ID=$(aws codebuild start-build --project-name test-batch-ami --source-version ${BRANCH} | tr -d "\n" | jq -r '.build.id') echo "started.. id is ${CODEBUILD_ID}" while true do sleep 10s STATUS=$(aws codebuild batch-get-builds --ids "${CODEBUILD_ID}" | tr -d "\n" | jq -r '.builds[].buildStatus') echo "..status is ${STATUS}." if [ "${STATUS}" != "IN_PROGRESS" ]; then if [ "${STATUS}" != "SUCCEEDED" ]; then echo "faild." fi echo "done." break fi done AMI_IDS=$(aws ec2 describe-images \ --owner self \ --filters "Name=tag:USE,Values=heta-reversi" | jq -r ".Images[].ImageId") AMI_ID="${AMI_IDS[0]}"
CodeBuildで実施するAMI作成処理は下記です。今回はAMIが1つしかない前提でのAMI ID取得*2になっているためAMI作成前に過去に作成済みのAMIを削除するようにしてあります。*3
version: 0.2 phases: pre_build: commands: - curl -qL -o packer.zip https://releases.hashicorp.com/packer/1.3.3/packer_1.3.3_linux_amd64.zip && unzip packer.zip - ./packer validate amazon-linux_packer-template.json build: commands: - ./delete_amis.sh - ./packer build amazon-linux_packer-template.json post_build: commands: - echo "done $(date)"
過去のAMIを削除する処理は下記です。今回の作成AMIにはタグUSE
にheta-reversi
を付与するようにしてあるため削除の目印にしてあります。
#!/bin/bash aws ec2 describe-images \ --owner self \ --filters "Name=tag:USE,Values=heta-reversi" | jq -r ".Images[].ImageId" | while read -r AMI_ID do aws ec2 deregister-image --image-id ${AMI_ID} done
AMIを作成するPackerのTemplateは下記です。インスタンスタイプはGPUインスタンスのp2.xlarge
を使うようにしました。
amazon-linux_packer-template.json
{ "variables": { "aws_region": "{{env `AWS_REGION`}}", "aws_subnet_id": "{{env `AWS_SUBNET_ID`}}", "aws_ami_name": "amazon-linux_heta-reversi" }, "builders": [{ "type": "amazon-ebs", "region": "{{user `aws_region`}}", "instance_type": "p2.xlarge", "subnet_id": "{{user `aws_subnet_id`}}", "ssh_username": "ec2-user", "ami_name": "{{user `aws_ami_name`}}", "ami_description": "heta-reversi's Amazon Linux", "source_ami": "ami-08a7740ff4d3fd90f", "ssh_pty": true, "tags": { "USE": "heta-reversi" } }], "provisioners": [ { "type": "file", "source": "configure-gpu.sh", "destination": "/home/ec2-user/configure-gpu.sh" }, { "type": "shell", "inline": [ "bash ./configure-gpu.sh", "sudo rm -rf /var/lib/ecs/data/ecs_agent_data.json" ] } ] }
PackerのTemplateのprovisionersで実行するnvidia-docker2の設定処理は下記です。dockerのdefault-runtimeにnvidia
を指定してnvidia-docker2が有効になるようにしています。
#!/bin/bash # Install ecs-init, start docker, and install nvidia-docker 2 sudo yum install -y ecs-init sudo service docker start DOCKER_VERSION=$(docker -v | awk '{ print $3 }' | cut -f1 -d"-") DISTRIBUTION=$(. /etc/os-release;echo ${ID$VERSION_ID}) curl -s -L https://nvidia.github.io/nvidia-docker/${DISTRIBUTION}/nvidia-docker.repo | \ sudo tee /etc/yum.repos.d/nvidia-docker.repo PACKAGES=$(sudo yum search -y --showduplicates nvidia-docker2 nvidia-container-runtime | grep ${DOCKER_VERSION} | awk '{ print $1 }') sudo yum install -y ${PACKAGES} sudo pkill -SIGHUP dockerd # Get CUDA version CUDA_VERSION=$(head -n 1 /usr/local/cuda/version.txt | awk '{ print $3 }' | cut -f1-2 -d".") # Run test container to verify installation sudo docker run --privileged --runtime=nvidia --rm nvidia/cuda:${CUDA_VERSION}-base nvidia-smi # Update Docker daemon.json to user nvidia-container-runtime by default sudo tee /etc/docker/daemon.json <<EOF { "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } }, "default-runtime": "nvidia" } EOF sudo service docker restart
セルフプレイの新モデル作成時のトレーニングをGPUに対応
現在のモデルをベースにセルフプレイで生成した棋譜データを使って学習している部分をGPUで行うようにしました。変更を行なったらCodeBuildを実行してECRのdocker imageを更新します。
以下、コードです。
まずGPUを使う箇所を局所化するためにgpu_device
をグローバル定義しました。*4この値を目印にGPU処理にするか判断します。
gpu_device = -1
学習を行う_create_new_model
をGPU化します。学習の実施前後でモデルをto_gpu
でGPUモードにしてto_cpu
でCPUモードに戻しています。
def _create_new_model(self, steps_list, epoch_num = None, batch_size = 2048): epoch_num = default_params['dual_net_trainer_create_new_model_epoch_num'] if epoch_num is None else epoch_num model = DualNet() model.load(self.model_filename) if gpu_device >= 0: cuda.get_device(gpu_device).use() model.to_gpu(gpu_device) optimizer = chainer.optimizers.Adam() optimizer.setup(model) for i in range(epoch_num): x_train, y_train_policy, y_train_value = self._get_train_batch(steps_list, batch_size) y_policy, y_value = model(x_train) model.cleargrads() loss = F.mean_squared_error(y_policy, y_train_policy) + F.mean_squared_error(y_value, y_train_value) loss.backward() optimizer.update() print("[new nodel] epoch: {} / {}, loss: {}".format(i + 1, epoch_num, loss)) if gpu_device >= 0: model.to_cpu() return model
棋譜データから学習データを取り出す_get_train_batch
です。GPUモードの場合はnumpy
では無くcuda.cupy
を使う必要があります。
def _get_train_batch(self, steps_list, batch_size): batch_x, batch_y_policy, batch_y_value = [], [], [] for _ in range(batch_size): x, y_policy, y_value = self._get_train_random(steps_list) batch_x.append(x) batch_y_policy.append(y_policy) batch_y_value.append(y_value) xp = np if gpu_device >= 0: xp = cuda.cupy x_train = Variable(xp.array(batch_x)).reshape(-1, 4, 8, 8) y_train_policy = Variable(xp.array(batch_y_policy)).reshape(-1, 64) y_train_value = Variable(xp.array(batch_y_value)).reshape(-1, 1) return x_train, y_train_policy, y_train_value
最後にcreate-model-batch
で動かす場合にGPUモードにしています。
elif len(args) > 2 and args[1] == 'create-model-batch': ### ...(省略)... gpu_device = 0 ### ...(省略)...
AWS Batchのコンピューティング環境で使うAMIとインスタンスを変更
AWS Batchで今回のAMIをGPUインスタンスを使うようにしました。
以下、コードです。
コンピューティング環境の定義のimageId
に今回のAMIをinstanceTypes
にGPUインスタンスを指定します。今回はp2.xlarge
にしました。
cat << EOF > compute-environment.spec.json { "computeEnvironmentName": "test-compute-environment", "type": "MANAGED", "state": "ENABLED", "computeResources": { "type": "EC2", "minvCpus": 0, "maxvCpus": 4, "desiredvCpus": 0, "instanceTypes": [ "p2.xlarge" ], "imageId": "${AMI_ID}", "subnets": ["${SUBNET_ID}"], "securityGroupIds": ["${DEFAULT_SECURITY_GROUP_ID}"], "instanceRole": "${INSTANCE_ROLE_ARN}" }, "serviceRole": "${ROLE_SERVICE_ARN}" } EOF COMPUTE_ENV=$(aws batch create-compute-environment --cli-input-json file://compute-environment.spec.json)
動かした結果
結論は前回と同じで4時間かかり学習時間が短縮することはありませんでした。
GPUが有効に動いていないことが原因ではないかと今回のAMI/インスタンス/docker imageでMNISTのサンプルを動かしてみましたがきちんと動作しているようです。GPUは有効だと思われます。
chainer/examples/mnist at v5 · chainer/chainer · GitHub
バグがある可能性や今回のケースではあまりGPUの効果がない可能性、バッチサイズを増やしたりパラメータを変更したら効果がある可能性、など疑っていますが調査中です... 🤔
まとめ
今回はAPV-MCTSのセルフプレイによる強化学習のAWS Batch環境をGPU化してみました。残念ながら学習時間は前回と同じで短縮できませんでしたがPackerの使い方を知ることができてGPU環境も作ることができました。引き続き調査と流石にそろそろコードが限界なのでリファクタリングをしたいです。