运行PAI-DLC任务时,您需要准备任务运行的资源,阿里云PAI-DLC支持使用阿里云公共资源组或自建专有资源组。使用公共资源组时,您可以选用更经济的竞价实例,降低任务运行成本。本文为您介绍使用竞价实例运行DLC任务的适用场景和操作实践。

背景信息

阿里云为所有阿里云用户提供了一定数量的竞价实例(抢占实例,即一系列的CPU、GPU实例资源组),供所有阿里云用户共同抢占使用,此类实例的详情请参见抢占式实例概述。运行PAI-DLC深度学习任务时,您也可以选用竞价实例,相比于普通的公共资源组实例(按量付费实例),竞价实例价格上通常有一定幅度的折扣,使用方法和计算能力与按量计费的实例无异,这可以帮助您以更低的成本获得AI计算能力,降低任务运行的资源成本。

由于竞价实例为所有阿里云用户共同抢占使用,抢占成功后有一段时间的保护期,超过保护期后阿里云会自动回收这些折扣售卖的实例,因此使用竞价实例运行DLC任务时,您需要关注任务运行时间,超过保护期后任务有可能因为资源被回收而运行失败。

应用场景

建议以下场景来使用竞价实例来降低成本:
  • 运行时间比较短的计算任务。
  • Debug状态的计算任务。
  • 能够容忍过程中失败的计算任务,如hyper-parameter search的子任务等。
  • 可中断和继续训练的计算任务,如经常做checkpoint并从checkpoint中继续的场景。

注意事项

由于竞价实例为阿里云所有用户共同抢占,不是稳定承诺可用的计算资源,因此使用竞价实例运行DLC任务时,需关注以下注意事项。

  • 资源申请:

    使用竞价实例的DLC任务提交后,PAI-DLC即开始为用户抢占实例资源,当阿里云的竞价实例资源库存不足时,有可能存在不能立即抢占到竞价实例资源的情况,此时PAI-DLC会持续为您申请抢占实例,在此阶段任务会表现为等待状态。

  • 资源回收:

    申请到竞价实例后,DLC任务即开始创建并运行,由于竞价实例超出保护期后会被回收,因此可能存在运DLC任务时长超过保护期后,任务资源由于超过保护期在无提示下被回收,此时任务表现为失败结束。

  • 资源计费:

    阿里云竞价实例的出价模式包含使用自动出价和设置您的最高价(SpotAsPriceGo)两种,PAI-DLC任务使用竞价实例时,使用设置您的最高价(SpotAsPriceGo)的出价模式,即实例价格随着当前市场价动态变化,因此即使提交两个一模一样的任务,使用一模一样的资源持续相同的时间,其账单价格也可能不一样。

操作步骤

以下以一个具体的实践案例,为您示意使用竞价实例创建提交任务的操作步骤。

本案例是一个使用竞价实例,在mnist模型上训练的PyTorch任务,任务代码文件存储在Git中,任务运行中无需数据存储,创建任务时可以使用社区或PAI平台的PyTorch镜像,整个过程在P100机器(ecs.gn5-c4g1.xlarge)上花费约15分钟的时间。

  1. 任务代码配置。
    1. 准备任务代码文件。
      将下列代码保存为名称mnist.py的代码文件。
      from __future__ import print_function
      import argparse
      import torch
      import torch.nn as nn
      import torch.nn.functional as F
      import torch.optim as optim
      from torchvision import datasets, transforms
      from torch.optim.lr_scheduler import StepLR
      
      
      class Net(nn.Module):
          def __init__(self):
              super(Net, self).__init__()
              self.conv1 = nn.Conv2d(1, 32, 3, 1)
              self.conv2 = nn.Conv2d(32, 64, 3, 1)
              self.dropout1 = nn.Dropout(0.25)
              self.dropout2 = nn.Dropout(0.5)
              self.fc1 = nn.Linear(9216, 128)
              self.fc2 = nn.Linear(128, 10)
      
          def forward(self, x):
              x = self.conv1(x)
              x = F.relu(x)
              x = self.conv2(x)
              x = F.relu(x)
              x = F.max_pool2d(x, 2)
              x = self.dropout1(x)
              x = torch.flatten(x, 1)
              x = self.fc1(x)
              x = F.relu(x)
              x = self.dropout2(x)
              x = self.fc2(x)
              output = F.log_softmax(x, dim=1)
              return output
      
      
      def train(args, model, device, train_loader, optimizer, epoch):
          model.train()
          for batch_idx, (data, target) in enumerate(train_loader):
              data, target = data.to(device), target.to(device)
              optimizer.zero_grad()
              output = model(data)
              loss = F.nll_loss(output, target)
              loss.backward()
              optimizer.step()
              if batch_idx % args.log_interval == 0:
                  print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                      epoch, batch_idx * len(data), len(train_loader.dataset),
                      100. * batch_idx / len(train_loader), loss.item()))
                  if args.dry_run:
                      break
      
      
      def test(model, device, test_loader):
          model.eval()
          test_loss = 0
          correct = 0
          with torch.no_grad():
              for data, target in test_loader:
                  data, target = data.to(device), target.to(device)
                  output = model(data)
                  test_loss += F.nll_loss(output, target, reduction='sum').item()  # sum up batch loss
                  pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability
                  correct += pred.eq(target.view_as(pred)).sum().item()
      
          test_loss /= len(test_loader.dataset)
      
          print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
              test_loss, correct, len(test_loader.dataset),
              100. * correct / len(test_loader.dataset)))
      
      
      def main():
          # Training settings
          parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
          parser.add_argument('--batch-size', type=int, default=64, metavar='N',
                              help='input batch size for training (default: 64)')
          parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
                              help='input batch size for testing (default: 1000)')
          parser.add_argument('--epochs', type=int, default=1, metavar='N',
                              help='number of epochs to train (default: 1)')
          parser.add_argument('--lr', type=float, default=1.0, metavar='LR',
                              help='learning rate (default: 1.0)')
          parser.add_argument('--gamma', type=float, default=0.7, metavar='M',
                              help='Learning rate step gamma (default: 0.7)')
          parser.add_argument('--no-cuda', action='store_true', default=False,
                              help='disables CUDA training')
          parser.add_argument('--dry-run', action='store_true', default=False,
                              help='quickly check a single pass')
          parser.add_argument('--seed', type=int, default=1, metavar='S',
                              help='random seed (default: 1)')
          parser.add_argument('--log-interval', type=int, default=10, metavar='N',
                              help='how many batches to wait before logging training status')
          parser.add_argument('--save-model', action='store_true', default=False,
                              help='For Saving the current Model')
          args = parser.parse_args()
          use_cuda = not args.no_cuda and torch.cuda.is_available()
      
          torch.manual_seed(args.seed)
      
          device = torch.device("cuda" if use_cuda else "cpu")
      
          train_kwargs = {'batch_size': args.batch_size}
          test_kwargs = {'batch_size': args.test_batch_size}
          if use_cuda:
              cuda_kwargs = {'num_workers': 1,
                             'pin_memory': True,
                             'shuffle': True}
              train_kwargs.update(cuda_kwargs)
              test_kwargs.update(cuda_kwargs)
      
          transform=transforms.Compose([
              transforms.ToTensor(),
              transforms.Normalize((0.1307,), (0.3081,))
              ])
          dataset1 = datasets.MNIST('../data', train=True, download=True,
                             transform=transform)
          dataset2 = datasets.MNIST('../data', train=False,
                             transform=transform)
          train_loader = torch.utils.data.DataLoader(dataset1,**train_kwargs)
          test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)
      
          model = Net().to(device)
          optimizer = optim.Adadelta(model.parameters(), lr=args.lr)
      
          scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)
          for epoch in range(1, args.epochs + 1):
              train(args, model, device, train_loader, optimizer, epoch)
              test(model, device, test_loader)
              scheduler.step()
      
          if args.save_model:
              torch.save(model.state_dict(), "mnist_cnn.pt")
      
      
      if __name__ == '__main__':
          main()
    2. 上传代码文件至代码仓中(Git代码文件存储目录),用于后续在任务配置中引用运行。
      您可以将上述代码文件上传至自己的代码仓库,或者上传至以下开源的代码仓库中。
      https://github.com/WencongXiao/pytorch-demo.git
  2. 在PAI-DLC上配置任务代码。
    将上述存有代码文件的代码仓信息配置到PAI-DLC的代码配置页中,便于后续创建任务时直接引用,操作详情请参见新建代码配置。使用上述开源代码仓时,请参见以下示意配置进行配置。代码配置
  3. 创建任务。
    在PAI-DLC的任务列表页面创建任务,操作详情请参见创建任务。本实践示例中的配置要点如下。
    1. 基本信息配置。
      基本信息
      • 节点镜像:本示例可选择社区镜像或PAI平台镜像中的PyTorch镜像。
      • 任务类型:本示例选择PyTorch
      • 代码配置:本示例选择上述步骤2中创建的任务代码配置。
      • 执行命令:本示例的执行命令可配置为:python /root/code/pytorch-demo/pytorch-demo/mnist.py
    2. 任务资源配置。
      资源配置
      • 节点配置:选择GPU类的节点,规格选择ecs.gn5-c4g1.xlarge,选用此规格的节点,本示例任务运行大概需要15分钟。
      • 使用竞价实例:开启使用竞价实例。
    3. 完成任务配置后,单击提交
      任务提交后,PAI-DLC即开始申请抢占竞价实例,创建并运行任务。如果抢占到竞价实例,此任务会显示为等待状态。

查看执行结果

成功创建运行任务后,本任务大约需要15分钟完成运行,您可在15分钟后进入任务列表页面,查看任务的运行结果及详情。任务详情如果任务运行过程中,任务运行时长超过竞价实例的保护期,则资源可能被回收,导致任务运行失败。运行失败

查看账单明细

任务执行成功后,您可以在任务执行后的第二天进入费用中心页面,查看使用竞价实例执行本示例任务时的费用明细,并对比当前ECS的按量计费价格,可见使用竞价实例的费用更低更经济。查看账单明细的操作请参见账单明细

以下以一个运行案例作为参考,对比竞价实例与普通资源的价格情况。
说明
  • 后付费账单以T+1的形式产生,您可以在任务运行成功后的第二天查看账单明细。
  • 以下价格为某个时刻的价格参考,不同时间不同区域的实例报价可能有差异。
实例类型 规格与型号 参考价格 成本预估
竞价实例
  • GPU 型号:NVIDIA P100
  • 规格:ecs.gn5-c4g1.xlarge * 2
5.7706元 使用竞价实例可节约成本4.99倍。
普通ECS实例 28.782元