玩转 GPU 实例 – 我的 Linux 工具箱

发布于:2020 年 4 月 8 日 15:34

玩转GPU实例 – 我的Linux 工具箱

前言

显卡意味着什么?在不同的玩家心目中会有许多不同的答案。对我来说,开始着迷于显卡要从横空出世的 Voodoo 说起。那时候显卡市场的王者 3dfx 推出的一系列产品无论是游戏画质还是高分辨率下的流畅度都是其它产品望尘莫及的,市场的份额曾经高达 85%。只是那时候我的认识中显卡只是与游戏、视频输出效果这些场景有关,没有想到后来的所谓 GPU 居然有了今天的局面。至于后来 NVIDIA Force 的卧薪尝胆、ATI Radeon 的惊艳亮相以及 Matrox、S3 等刹那的辉煌……这些都让每一个玩家难以忘怀。

玩转GPU实例 – 我的Linux 工具箱

大约十年前,在饭桌上听同事说起他在读博期间参与的项目,提到了使用 CUDA 开发来以提高浮点运算的速度。那时候只是觉得所谓的高性能计算(HPC)距离我们还很远,Nvidia 的显卡用来加速运算听起来固然有趣,但更多的只是饭桌上的谈资。哪里预计得到今天的大红大紫。

至于 AWS 上的 GPU 实例最早要上溯到 2015 年。那一年发布的 EC2 G2 实例第一次为开发者提供了云计算上的 GPU 服务。四块 NVIDIA GRID GPUs 显卡提供的处理能力,让我们可以真正体会 GPU 的实力。而随着机器学习尤其是深度学习的快速发展,又进一步加速了这个领域的发展。2016 年 9 月,构建于 NVIDIA® Tesla® K80 之上的 EC2 P2 实例面世了。高达 16 块显卡的配置,不免让我们对于机器学习的发展有了更多的期望。

显然,硬件的发展还是没有及时跟上算法的进步与数据膨胀。看着明显老迈的 P2 实例难免让我心生焦虑。幸好,2017 年 10 月的 P3 实例在千呼万唤中飘然而至。8 个 NVIDIA Tesla V100 GPU、Intel Xeon E5 处理器的 64 个 vCPU、488GB RAM 以及采用 Elastic Network Adapter 技术、高达 25Gbps 的聚合网络带宽显然成为了 GPU 实例中的王者。当然仅有豪华的硬件配置是不可能解决全部的问题,还需要每个开发者充分利用好这一资源平台。在 P3 之后发布的 P3dn 以及接下来即将到来的 P4 实例,将会提供更强大的计算平台,但这都需要每个使用者的精巧构思,力求物尽其用方可尽显 GPU 的强大的能力。

去年底因为参加 NVIDIA GTC 大会的需要,又一次连接上了我的 GPU 实例,又不得不重复以往做过许多次的工作,安装、配置、编译、优化 … 这些繁琐的操作突然感觉自己在不断重复之中似乎缺少了点什么。于是心生念头,将曾经在 GPU 实例上的心得写成脚本以利于今后工作之用。这些脚本会涵盖曾经尝试过的一些内容 :

  • GPU 实例的创建与管理
  • GPU 实例的基础配置
  • 实例的系统优化
  • 实例的网络优化
  • Intel 软件的安装配置
  • OpenCV 编译安装
  • 开发工具篇
  • Nvidia 软件篇
  • Jupyter 的安装配置
  • OpenMPI 编译与配置
  • Horovod 配置
  • 深度学习框架篇(Tensorflow、PyTorch 以及 Mxnet)

在这个技术高速发展的年代,个人的努力是非常渺小的。我的这些心得与积累或有不足甚至谬误之处。非常希望听到更多的反馈与建议,也只有群智群力才能使得我们曾益其所不能。

第一部分 : GPU 实例的创建与管理

使用过 AWS 的用户都应该有过创建 EC2 实例的经验。我们常用的方法不外乎 AWS 控制台、AWS 命令行工具、CloudFormation 模版工具以及第三方的运维工具 (Terraform、puppet、ansibley 以及 chef 等)。从我的经验来看,AWS 命令行工具(awscli)应该是最麻烦的一个了。原因就在于我们需要熟练的掌握的参数实在是太多了,请看完整的 awscli 中创建实例的命令 run-instance 的完整参数 :

玩转GPU实例 – 我的Linux 工具箱

创建实例的脚本

相信对一个普通人来说这绝对是一个不能能完成的任务。但是,事情的另一面却是 AWS 命令行工具 (awscli ) 提供给我们的绝对是一个强大的、可以随心所欲进行定制的工具。用好这个工具的一个简单而有效的方法就是脚本。毫不夸张的说,一个好的脚本带给我们的价值的巨大的。它不仅可以节省我们大量的重复性的工作的时间,还可以以一种灵活的、程序化方式满足各种各样的运维的需求。而创建一个 EC2 的 GPU 实例就属于这一类的范畴。好了,我的创建实例的脚本就是这个样子的

复制代码
#!/bin/bash
instance_type="p3.16xlarge"
key_name=" 密钥名字 "
security_group_ids=" 安全组 ID"
subnet_id=" 子网 ID"
placement=" 置放群组 "
block_device_mappings=""
#http://169.254.169.254/latest/user-data/
user_data="ubuntu_userdata.txt"
ebs_mapping="ebs_mapping.json"
count=" 数量 "
region="AWS 区域 "
#ubuntu 18.0.4 LTS
image_id=$(aws ec2 describe-images --owners 099720109477 --filters \
'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server*' \
'Name=state,Values=available' 'Name=architecture,Values=x86_64' --query \
'reverse(sort_by(Images, &CreationDate))[:1].ImageId' --output text --region ${region})
owner=" 使用者 "
current_date_time="`date +%Y%m%d%H%M`";
project=" 项目名称 "
tags="ResourceType=instance,Tags=[{Key=owner,Value=$owner},{Key=date,Value=$current_date_time},{Key=project,Value=$project}] ResourceType=volume,Tags=[{Key=owner,Value=$owner},{Key=date,Value=$current_date_time},{Key=project,Value=$project}]"
# Using --dry-run to test
INSTANCE_ID=$(aws ec2 run-instances \
--image-id ${image_id} \
--count ${count} \
--instance-type ${instance_type} \
--key-name ${key_name} \
--security-group-ids ${security_group_ids} \
--subnet-id ${subnet_id} \
--ebs-optimized --associate-public-ip-address \
--block-device-mappings "file://${ebs_mapping}" \
--user-data "file://${user_data}" \
--region ${region} \
--tag-specifications ${tags} \
--output text --query 'Instances[*].InstanceId'
)
#aws ec2 wait instance-status-ok \
aws ec2 wait instance-running \
--instance-ids ${INSTANCE_ID} --region ${region}
IP_ADDRESS=$(aws ec2 describe-instances \
--instance-ids ${INSTANCE_ID} \
--query "Reservations[*].Instances[*].PublicIpAddress" \
--region ${region} \
--output=text)
echo "The instance is availiable now, access with : ssh ubuntu@${IP_ADDRESS}."
echo "Done."

脚本中的参数

这段脚本的内容并不复杂。理解其中的几个关键的变量就可以灵活的配置使用。其中需要提前准备几个重要的变量是 -

  • 实例的类型

以 P3 实例为例,P3 实例提供了三种实例大小:带有 1 个 GPU 的 2xlarge,带有 4 个 GPU 的 p3.8xlarge 以及带有 8 个 GPU 的 p3.16xlarge。脚本中可酌情选择,例如:
instance_type=”p3.16xlarge”

  • 密钥对的名字

可以通过控制台、命令行创建。也可以将自己创建的公有密钥上传到将要使用的 AWS 区域上。关于这部分内容可以参考 AWS 文档,例如:
key_name=”id_rsa”

  • 安全组的 ID

当我们创建实例时,以为该实例最多分配 5 个安全组。安全组是被用来控制到实例的入站数据流,以及另外一套单独规则以控制出站数据流。关于安全组的细节,请参考这里。在这里,我们的安全组设定需要开放 SSH 的端口以便于连接使用。例如:
security_group_ids=”sg-0f4bb098276d25df8″

  • 实例所属的的子网 ID

子网是 VPC 内的 IP 地址范围,每个实例都要归属到一个字网当中。例如:
subnet_id=”subnet-06c44af45fcd8512f”

  • 置放群组

设置置放群组。这里提到的置放群组是放置实例的一种方式。针对深度学习,尤其是多节点分布式模型训练的场景,我们选择的是集群置放群组。这意味着,通过将一个可用区中靠近的实例打包在一起。可以实现所需的低延迟网络性能,以满足分布式模型训练通常使用的紧密耦合的节点到节点通信的要求。关于置放群组,可以通过这里了解更多。例如:
placement=”GroupName = DL-pg”

  • 块设备映射项

这里不需要使用这项设置
例如:block_device_mappings=””

上述的这些项目中,密钥对、安全组、VPC 子网以及置放群组需要预先设置好。此外,还需要了解以下几个重要的参数:

  • 用户数据

这个参数是要提供给实例的用户数据。在实例启动的时候,用户数据会被自动执行,通常用来帮助我们完善实例的构建,例如安装 / 升级程序包等。需要注意的是,用户数据在被执行的过程中是不能够进行与用户的交互的。在官方的文档中,并没有设计 Ubuntu 的用户数据样例。因此构建一个没有交互的自动执行的用户数据是非常关键的一步。在我的实践中,这样的一个脚本是可以很好的被实例所执行。

复制代码
#!/bin/bash
set -e -x
export DEBIAN_FRONTEND=noninteractive
apt-get update &&
apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes &&
apt-get -o Dpkg::Options::="--force-confold" dist-upgrade -q -y --force-yes
apt-get -y autoremove
apt-get -y install awscli ec2-instance-connect git chrony screen
curl http://169.254.169.254/latest/user-data/ -o /home/ubuntu/userdata.sh
chmod +x /home/ubuntu/userdata.sh
mkdir -p /home/ubuntu/Projects
chown ubuntu:ubuntu /home/ubuntu/Projects
mkdir -p /home/ubuntu/Downloads
chown ubuntu:ubuntu /home/ubuntu/Downloads
echo "#---------------------¬" >> /home/ubuntu/.bashrc

这段脚本完成的任务有 通过 apt-get update、apt-get dist-upgrade 完成的系统与软件包的升级;常用软件的安装 apt-get -y install awscli ec2-instance-connect git chrony screen;以及创建我们后续将要使用到的一些目录。我们也可以按照自己的需要进行合理的增减。要注意的一点就是不要有任何需要交互的操作,否则这个 userdate 将不会被正确的执行。另外 userdata 的大小需要控制在 16K 之内。

  • 实例数量

创建的实例的数量,这对于需要同时创建多个同样实例的场景非常用用。例如分布式训练等。例如:
count=”5″

  • AWS 区域

这里所谓的区域都是一个单独的地理区域。“区域”对于理解 AWS 的基础设施是非常重要的一个概念。如果需要更多的了解,需要参考这里。对于每一个区域都有对应的代码。例如 中国(北京)区域 的代码为 cn-north-1;中国(宁夏)区域的代码为 cn-northwest-1。

关于 Ubuntu 18.0.4

事实上,我们在使用一个 GPU 实例的时候(例如 P3 实例)会有许多个 Linux 分发版本的选择,例如 Amazon Linux 2 、Centos 以及 Ubuntu 等等。但是不得不强调的就是 NVIDA 的 CUDA 对于众多 Linux 的分发版本来说支持最好的莫过于 Ubuntu。我曾经大费周折的试图在 Debian Stretch 上为我的 GTX 1070 安装最新版本的 CUDA 。但是屡经挫折之后不得不回到了 Ubuntu 之上。按照 Canonical (Ubuntu 的开发商)的声明,Ubuntu 的下个月即将发布的 Ubuntu 20.04 LTS 将会集成 NVIDIA 私有的显卡驱动,这无疑增强了我们继续选用的信心了。

在 AWS EC2 的实例上安装 Ubuntu 18.04 不是件困难的事情。在 AWS 推荐的快速启动的操作系统清单中就提供了 Ubuntu18.04 的选项。

玩转GPU实例 – 我的Linux 工具箱

但是,不好的地方在于每一个操作系统的镜像(AMI)都需要有一个 AMI 的 ID。例如美国俄勒冈区域的 Ubuntu 18.04 的 64 位 X86 的 AMI ID 为 ami-0d1cd67c26f5fca19。但是不同的 AWS 区域当中的 Ubuntu 18.04 的 AMI ID 确是完全不同的。这里有一个小的技巧可以帮助我们简单的获取每个区域的 Ubuntu 18.04,只需要提供区域的代码即可。

复制代码
image_id=$(aws ec2 describe-images --owners 099720109477 --filters \
'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server*' \
'Name=state,Values=available' 'Name=architecture,Values=x86_64' --query \
'reverse(sort_by(Images, &CreationDate))[:1].ImageId' --output text --region ${region})

这里的关键是使用了不同 AMI 提供着的 owner 这个参数以及不同的 AMI 的描述信息。同样的方法也适用于其它的操作系统,例如 Amazon Linux 2 等。

标签 (Tag) 的用法

对于 AWS 资源打标签 (tag) 绝对是一个非常有用但很容易被忽视的地方。那么什么是“标签”呢?标签是指为 使用的 AWS 资源分配的标记。每个标签都包含我们自行定义的一个键和一个值。标签可让我们灵活的按照各种标准 (例如项目、用途、所有者) 对 AWS 资源进行分类。这在具有大量相同类型的资源时将会很有用的功能 — 可以根据分配给资源的标签快速识别特定资源。例如,您可以为不同项目的 Amazon EC2 实例定义一组标签,以跟踪不同项目实例的使用情况以及成本的状况。简单的使用方法如下:

复制代码
owner=" 使用者名字 "
current_date_time="`date +%Y%m%d%H%M`";
project=" 项目名称 "
tags="ResourceType=instance,Tags=[{Key=owner,Value=$owner},{Key=date,Value=$current_date_time},{Key=project,Value=$project}] ResourceType=volume,Tags=[{Key=owner,Value=$owner},{Key=date,Value=$current_date_time},{Key=project,Value=$project}]"

这里标签的名称与标签完全是由我们自行定义的。在上面的这个例子中,就定义了实例的使用者、实例建立的日期、实例所属的项目的名称等。

网络存储(EBS)的设定

Amazon Elastic Block Store (EBS) 是 AWS 提供的一种数据块存储服务,通常与 EC2 一起使用。对于我们即将创建的实例这是一种非常适用的存储方式。设定实例中所使用的 EBS 的配置,需要在一个配置文件中声明。例如:

复制代码
[{
"DeviceName": "/dev/sda1",
"Ebs": {
"DeleteOnTermination": true,
"VolumeSize": 64,
"VolumeType": "gp2",
"Encrypted": false
}
}]

在这个配置中,“DeleteOnTermination”声明了当实例终止时需要删除该存储卷 ;
“VolumeSize”设置的是存储容量的大小,单位是 GB;“VolumeType”需要在高性能的 io1、通用型的 gp2、吞吐量优化的 st1 以及最低成本的 sc1 四种类型中进行选择。关于这四种类型的差异可以通过这张表来一窥究竟

玩转GPU实例 – 我的Linux 工具箱

最后的设置项是关于数据加密。“Encrypted”用来声明存储在 EBS 上数据是否以加密方式存储。无疑,选择加密存储会很好的保护我们的隐私数据。

创建实例与连接到实例

最后的一个步骤就是利用 aws ec2 run-instances 命令在我们准备好的参数与配置项之上创建我们需要的实例。并且为了后续操作的方面,会将实例绑定 Public IP 显示出来。

复制代码
IP_ADDRESS=$(aws ec2 describe-instances \
--instance-ids ${INSTANCE_ID} \
--query "Reservations[*].Instances[*].PublicIpAddress" \
--region ${region} \
--output=text)
echo "The instance is availiable now, access with : ssh ubuntu@${IP_ADDRESS}."
echo "Done."

这样我们就可以利用 ssh 命令登陆到这台实例上面。但是,对我们而言长时间的记住一组 IP 地址显然是不切实际的。我的工作习惯是准备一组关于 EC2 使用的 Linux 的别名(在我的.bashrc 文件中定义)。通过这些别名 / 命令来帮助我们找到实例,甚至是帮助我们关闭或者启动实例。

复制代码
alias ec2_list='aws ec2 describe-instances --output table –query '\''Reservations[*].Instances[*].[InstanceId,ImageId,State.Name,PublicIpAddress,Tags[*].Value | [0]]'\'
alias ec2_start='aws ec2 start-instances --instance-ids'
alias ec2_stop='aws ec2 stop-instances --instance-ids'
alias ec2_terminate='aws ec2 terminate-instances --instance-ids'

只需要在命令行输入 ec2_list –region cn-northwest-1 , 就能够看到在我的账户在中国(宁夏)区域上的全部 EC2 实例,包括该实例的 Public IP。

到这里我们的 GPU 实例应该已经创建完成。接下来我们要对这台实例进行细致的优化与配置,我将在该系列的下一篇继续这项工作。

本文转载自 AWS 技术博客。

原文链接: https://amazonaws-china.com/cn/blogs/china/play-with-gpu-instances-my-linux-toolbox/

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

阅读数:67 发布于:2020 年 4 月 8 日 15:34

评论

发布
暂无评论