延伸預先建置的容器 - Amazon SageMaker AI

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

延伸預先建置的容器

如果預先建置的 SageMaker AI 容器不符合您的所有需求,您可以擴展現有映像以滿足您的需求。即使為您的環境或架構提供直接支援,您可能想要新增其他功能,或以不同方式設定容器環境。透過延伸預先建置的映像,您可以利用內含的深度學習程式庫和設定,無需從頭開始建立映像。您可以延伸容器,以新增程式庫、修改設定和安裝其他相依性。

下列教學課程示範如何擴展預先建置的 SageMaker AI 映像並將其發佈至 Amazon ECR。

延伸預先建置容器的需求

若要延伸預先建置的 SageMaker AI 映像,您需要在 Dockerfile 中設定下列環境變數。如需使用 SageMaker AI 容器的環境變數詳細資訊,請參閱 SageMaker Training Toolkit GitHub 儲存庫。

  • SAGEMAKER_SUBMIT_DIRECTORY:容器中用於訓練的 Python 指令碼所在的目錄。

  • SAGEMAKER_PROGRAM:應調用並用作訓練進入點的 Python 指令碼。

您還可以透過在 Dockerfile 中包含下列內容來安裝其他程式庫:

RUN pip install <library>

下列教學課程將介紹如何使用這些環境變數。

擴展 SageMaker AI 容器以執行 Python 指令碼

在本教學課程中,您將了解如何使用使用 CIFAR-10 資料集的 Python 檔案來擴展 SageMaker AI PyTorch 容器。透過擴充 SageMaker AI PyTorch 容器,您可以利用現有的訓練解決方案來使用 SageMaker AI。本教學課程延伸訓練映像,但也可以採取相同步驟來延伸推論映像。有關可用映像的完整清單,請參閱可用的深度學習容器映像

若要使用 SageMaker AI 容器執行您自己的訓練模型,請透過 SageMaker Notebook 執行個體建置 Docker 容器。

步驟 1:建立一個 SageMaker 筆記本執行個體

  1. 開啟 SageMaker AI 主控台

  2. 從左邊導覽窗格中,選擇筆記本,選擇筆記本執行個體,然後選擇建立筆記本執行個體

  3. 建立筆記本執行個體頁面上,提供下列資訊:

    1. 對於筆記本執行個體名稱,輸入 RunScriptNotebookInstance

    2. 對於筆記本執行個體類型,選擇 ml.t2.medium

    3. 許可與加密區段內執行下列動作:

      1. 對於 IAM 角色,選擇建立新角色

      2. 建立 IAM 角色頁面上,選擇特定的 S3 儲存貯體,指定名為 sagemaker-run-script 的 Amazon S3 儲存貯體,然後選擇建立角色

        SageMaker AI 會建立名為 的 IAM 角色AmazonSageMaker-ExecutionRole-YYYYMMDDTHHmmSS,例如 AmazonSageMaker-ExecutionRole-20190429T110788。請注意,執行角色命名慣例會使用角色建立時的日期和時間,並以 T 分隔。

    4. 對於根存取,選擇已啟用

    5. 選擇建立筆記本執行個體

  4. 筆記本執行個體頁面上,狀態待定。Amazon SageMaker AI 可能需要幾分鐘的時間來啟動機器學習運算執行個體,在這種情況下,它會啟動筆記本執行個體,並將 ML 儲存磁碟區連接至它。筆記本執行個體具備預先設定的 Jupyter 筆記本伺服器和一組 Anaconda 程式庫。如需詳細資訊,請參閱 CreateNotebookInstance

  5. 許可和加密區段中,複製 IAM 角色 ARN 編號,然後將其貼到記事本檔案中以暫時儲存。稍後您可以使用此 IAM 角色 ARN 編號,在筆記本執行個體中設定本機訓練估算器。IAM 角色 ARN 編號如下所示:'arn:aws:iam::111122223333:role/service-role/AmazonSageMaker-ExecutionRole-20190429T110788'

  6. 筆記本執行個體的狀態變更為 InService 後,請選擇開啟 JupyterLab

步驟 2:建立並上傳 Dockerfile 和 Python 訓練指令碼

  1. 開啟 JupyterLab 後,在 JupyterLab 主目錄內建立一個新資料夾。在左上角選擇新增資料夾圖示,然後輸入資料夾名稱 docker_test_folder

  2. docker_test_folder 目錄中,建立一個 Dockerfile 文字檔案。

    1. 選擇左上角的新增啟動器圖示 (+)。

    2. 其他區段下右邊的窗格中,選擇文字檔案

    3. 將下列 Dockerfile 範例程式碼貼到您的文字檔中。

      # SageMaker PyTorch image FROM 763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-training:1.5.1-cpu-py36-ubuntu16.04 ENV PATH="/opt/ml/code:${PATH}" # this environment variable is used by the SageMaker PyTorch container to determine our user code directory. ENV SAGEMAKER_SUBMIT_DIRECTORY /opt/ml/code # /opt/ml and all subdirectories are utilized by SageMaker, use the /code subdirectory to store your user code. COPY cifar10.py /opt/ml/code/cifar10.py # Defines cifar10.py as script entrypoint ENV SAGEMAKER_PROGRAM cifar10.py

      Dockerfile 指令碼會執行以下任務:

      • FROM 763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-training:1.5.1-cpu-py36-ubuntu16.04 – 下載 SageMaker AI PyTorch 基礎映像。您可以使用任何要用來建置容器的 SageMaker AI 基礎映像來取代此項目。

      • ENV SAGEMAKER_SUBMIT_DIRECTORY /opt/ml/code – 將 /opt/ml/code 設定為訓練指令碼目錄。

      • COPY cifar10.py /opt/ml/code/cifar10.py – 將指令碼複製到 SageMaker AI 預期容器內的位置。此指令碼必須位於此資料夾。

      • ENV SAGEMAKER_PROGRAM cifar10.py – 將您的 cifar10.py 訓練指令碼設定為進入點指令碼。

    4. 在左側目錄導覽窗格中,文字檔案名稱可能自動命名為 untitled.txt。若要重新命名檔案,請在該檔案上按一下滑鼠右鍵,選擇重新命名,將檔案重新命名為 Dockerfile (不包含 .txt 副檔名),然後按下 Ctrl+sCommand+s 以儲存檔案。

  3. docker_test_folder 中建立或上傳訓練指令碼 cifar10.py。您可以使用下列範例指令碼進行這個練習。

    import ast import argparse import logging import os import torch import torch.distributed as dist import torch.nn as nn import torch.nn.parallel import torch.optim import torch.utils.data import torch.utils.data.distributed import torchvision import torchvision.models import torchvision.transforms as transforms import torch.nn.functional as F logger=logging.getLogger(__name__) logger.setLevel(logging.DEBUG) classes=('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') # https://github.com/pytorch/tutorials/blob/master/beginner_source/blitz/cifar10_tutorial.py#L118 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1=nn.Conv2d(3, 6, 5) self.pool=nn.MaxPool2d(2, 2) self.conv2=nn.Conv2d(6, 16, 5) self.fc1=nn.Linear(16 * 5 * 5, 120) self.fc2=nn.Linear(120, 84) self.fc3=nn.Linear(84, 10) def forward(self, x): x=self.pool(F.relu(self.conv1(x))) x=self.pool(F.relu(self.conv2(x))) x=x.view(-1, 16 * 5 * 5) x=F.relu(self.fc1(x)) x=F.relu(self.fc2(x)) x=self.fc3(x) return x def _train(args): is_distributed=len(args.hosts) > 1 and args.dist_backend is not None logger.debug("Distributed training - {}".format(is_distributed)) if is_distributed: # Initialize the distributed environment. world_size=len(args.hosts) os.environ['WORLD_SIZE']=str(world_size) host_rank=args.hosts.index(args.current_host) dist.init_process_group(backend=args.dist_backend, rank=host_rank, world_size=world_size) logger.info( 'Initialized the distributed environment: \'{}\' backend on {} nodes. '.format( args.dist_backend, dist.get_world_size()) + 'Current host rank is {}. Using cuda: {}. Number of gpus: {}'.format( dist.get_rank(), torch.cuda.is_available(), args.num_gpus)) device='cuda' if torch.cuda.is_available() else 'cpu' logger.info("Device Type: {}".format(device)) logger.info("Loading Cifar10 dataset") transform=transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset=torchvision.datasets.CIFAR10(root=args.data_dir, train=True, download=False, transform=transform) train_loader=torch.utils.data.DataLoader(trainset, batch_size=args.batch_size, shuffle=True, num_workers=args.workers) testset=torchvision.datasets.CIFAR10(root=args.data_dir, train=False, download=False, transform=transform) test_loader=torch.utils.data.DataLoader(testset, batch_size=args.batch_size, shuffle=False, num_workers=args.workers) logger.info("Model loaded") model=Net() if torch.cuda.device_count() > 1: logger.info("Gpu count: {}".format(torch.cuda.device_count())) model=nn.DataParallel(model) model=model.to(device) criterion=nn.CrossEntropyLoss().to(device) optimizer=torch.optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) for epoch in range(0, args.epochs): running_loss=0.0 for i, data in enumerate(train_loader): # get the inputs inputs, labels=data inputs, labels=inputs.to(device), labels.to(device) # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs=model(inputs) loss=criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss=0.0 print('Finished Training') return _save_model(model, args.model_dir) def _save_model(model, model_dir): logger.info("Saving the model.") path=os.path.join(model_dir, 'model.pth') # recommended way from http://pytorch.org/docs/master/notes/serialization.html torch.save(model.cpu().state_dict(), path) def model_fn(model_dir): logger.info('model_fn') device="cuda" if torch.cuda.is_available() else "cpu" model=Net() if torch.cuda.device_count() > 1: logger.info("Gpu count: {}".format(torch.cuda.device_count())) model=nn.DataParallel(model) with open(os.path.join(model_dir, 'model.pth'), 'rb') as f: model.load_state_dict(torch.load(f)) return model.to(device) if __name__ == '__main__': parser=argparse.ArgumentParser() parser.add_argument('--workers', type=int, default=2, metavar='W', help='number of data loading workers (default: 2)') parser.add_argument('--epochs', type=int, default=2, metavar='E', help='number of total epochs to run (default: 2)') parser.add_argument('--batch-size', type=int, default=4, metavar='BS', help='batch size (default: 4)') parser.add_argument('--lr', type=float, default=0.001, metavar='LR', help='initial learning rate (default: 0.001)') parser.add_argument('--momentum', type=float, default=0.9, metavar='M', help='momentum (default: 0.9)') parser.add_argument('--dist-backend', type=str, default='gloo', help='distributed backend (default: gloo)') # The parameters below retrieve their default values from SageMaker environment variables, which are # instantiated by the SageMaker containers framework. # https://github.com/aws/sagemaker-containers#how-a-script-is-executed-inside-the-container parser.add_argument('--hosts', type=str, default=ast.literal_eval(os.environ['SM_HOSTS'])) parser.add_argument('--current-host', type=str, default=os.environ['SM_CURRENT_HOST']) parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) parser.add_argument('--data-dir', type=str, default=os.environ['SM_CHANNEL_TRAINING']) parser.add_argument('--num-gpus', type=int, default=os.environ['SM_NUM_GPUS']) _train(parser.parse_args())

步驟 3:建置容器

  1. 在 JupyterLab 主目錄中,開啟 Jupyter 筆記本。若要開啟新筆記本,請選擇新的啟動圖示,然後在筆記本區段中選擇 conda_pytorch_p39

  2. 在第一個筆記本儲存格中執行下列命令,以切換至 docker_test_folder 目錄:

    % cd ~/SageMaker/docker_test_folder

    這樣會返回目前的目錄,如下所示:

    ! pwd

    output: /home/ec2-user/SageMaker/docker_test_folder

  3. 登入 Docker 以存取基礎容器:

    ! aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 763104351884.dkr.ecr.us-east-1.amazonaws.com
  4. 若要建置 Docker 容器,請執行下列 Docker 建置命令,包含在結尾處有句點的空格:

    ! docker build -t pytorch-extended-container-test .

    必須從您建立的 Docker 目錄中執行 Docker build 命令,在此案例中為 docker_test_folder

    注意

    如果您收到以下錯誤訊息,表示 Docker 找不到 Dockerfile,請確認 Dockerfile 的名稱正確,且已存入目錄。

    unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /home/ec2-user/SageMaker/docker/Dockerfile: no such file or directory

    請記住,docker 會在當前目錄中查找名稱為 Dockerfile 且不含任何副檔名的檔案。如果您將其命名為其他名稱,則可以使用 -f 標記手動輸入檔案名稱。例如,如果您將 Docerfile 命名為 Dockerfile-text.txt,請執行下列命令:

    ! docker build -t tf-custom-container-test -f Dockerfile-text.txt .

步驟 4:測試容器

  1. 若要在筆記本執行個體內本機測試容器,請開啟 Jupyter 筆記本。選擇新啟動器,然後在 conda_pytorch_p39 架構中選擇筆記本。其餘的程式碼片段必須從 Jupyter 筆記本執行個體中執行。

  2. 下載 CIFAR-10 資料集。

    import torch import torchvision import torchvision.transforms as transforms def _get_transform(): return transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) def get_train_data_loader(data_dir='/tmp/pytorch/cifar-10-data'): transform=_get_transform() trainset=torchvision.datasets.CIFAR10(root=data_dir, train=True, download=True, transform=transform) return torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) def get_test_data_loader(data_dir='/tmp/pytorch/cifar-10-data'): transform=_get_transform() testset=torchvision.datasets.CIFAR10(root=data_dir, train=False, download=True, transform=transform) return torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) trainloader=get_train_data_loader('/tmp/pytorch-example/cifar-10-data') testloader=get_test_data_loader('/tmp/pytorch-example/cifar-10-data')
  3. role 設定為用於建立 Jupyter 筆記本的角色。這用於設定 SageMaker AI 估算器。

    from sagemaker import get_execution_role role=get_execution_role()
  4. 將下列範例指令碼貼入筆記本程式碼儲存格,以使用擴充容器設定 SageMaker AI 估算器。

    from sagemaker.estimator import Estimator hyperparameters={'epochs': 1} estimator=Estimator( image_uri='pytorch-extended-container-test', role=role, instance_count=1, instance_type='local', hyperparameters=hyperparameters ) estimator.fit('file:///tmp/pytorch-example/cifar-10-data')
  5. 執行程式碼儲存格。此測試會輸出訓練環境組態、用於環境變數的值、資料的來源,以及訓練期間獲得的損失和準確率。

步驟 5:將容器推送至 Amazon Elastic Container Registry (Amazon ECR)

  1. 成功執行本機模式測試之後,您可以將 Docker 容器推送至 Amazon ECR,並使用它執行訓練工作。

    在筆記本儲存格中執行以下命令列。

    %%sh # Specify an algorithm name algorithm_name=pytorch-extended-container-test account=$(aws sts get-caller-identity --query Account --output text) # Get the region defined in the current configuration (default to us-west-2 if none defined) region=$(aws configure get region) fullname="${account}.dkr.ecr.${region}.amazonaws.com/${algorithm_name}:latest" # If the repository doesn't exist in ECR, create it. aws ecr describe-repositories --repository-names "${algorithm_name}" > /dev/null 2>&1 if [ $? -ne 0 ] then aws ecr create-repository --repository-name "${algorithm_name}" > /dev/null fi # Log into Docker aws ecr get-login-password --region ${region}|docker login --username AWS --password-stdin ${fullname} # Build the docker image locally with the image name and then push it to ECR # with the full name. docker build -t ${algorithm_name} . docker tag ${algorithm_name} ${fullname} docker push ${fullname}
  2. 推送容器後,您可以從 SageMaker AI 環境中的任何位置呼叫 Amazon ECR 映像。在下一個筆記本儲存格中執行下列程式碼範例。

    如果您想要將此訓練容器與 SageMaker Studio 搭配使用,以使用其視覺化特徵,您也可以在 Studio 筆記本儲存格中執行下列程式碼,以呼叫訓練容器的 Amazon ECR 映像。

    import boto3 client=boto3.client('sts') account=client.get_caller_identity()['Account'] my_session=boto3.session.Session() region=my_session.region_name algorithm_name="pytorch-extended-container-test" ecr_image='{}.dkr.ecr.{}.amazonaws.com/{}:latest'.format(account, region, algorithm_name) ecr_image # This should return something like # 12-digits-of-your-account.dkr.ecr.us-east-2.amazonaws.com/tf-2.2-test:latest
  3. 使用從上一個步驟ecr_image擷取的 來設定 SageMaker AI 估算器物件。下列程式碼範例會設定 SageMaker AI PyTorch 估算器。

    import sagemaker from sagemaker import get_execution_role from sagemaker.estimator import Estimator estimator=Estimator( image_uri=ecr_image, role=get_execution_role(), base_job_name='pytorch-extended-container-test', instance_count=1, instance_type='ml.p2.xlarge' ) # start training estimator.fit() # deploy the trained model predictor=estimator.deploy(1, instance_type)

步驟 6:清除資源

若要在入門範例使用完畢後清除資源
  1. 開啟 SageMaker AI 主控台,選擇筆記本執行個體 RunScriptNotebookInstance,選擇動作,然後選擇停止。停止執行個體可能需要幾分鐘。

  2. 執行個體狀態變更為已停止後,選擇動作,選擇刪除,然後在對話方塊中選擇刪除。刪除執行個體可能需要幾分鐘。當筆記本執行個體被刪除,會從表格中消失。

  3. 開啟 Amazon S3 主控台,刪除您為了儲存模型成品和訓練資料集而建立的儲存貯體。

  4. 開啟 IAM 主控台並刪除該 IAM 角色。如果已建立許可政策,也可一併刪除。

    注意

    Docker 容器執行之後會自動關閉。您不需要刪除它。