
不少团队第一次把深度学习训练搬到 AWS 上时都会低估环境搭建对最终训练效率和稳定性的影响。明明实例买的是顶配 GPU跑起来显卡占用率却只有两三成多机通信动不动就超时换个实例类型镜像里的 CUDA 版本又和驱动对不上。其实这些问题大多数不是代码的锅而是部署层没有打好底子。这篇文章我会以一个实际项目为主线完整记录在 RHEL 8 上部署 AWS Deep Learning AMIs并逐步优化机器学习训练性能与可扩展性的全过程。内容覆盖实例选型、驱动与框架环境验证、数据加载与多卡通信调优、自定义 AMI 固化、弹性扩缩容设计以及我们踩过的坑和排查手段。如果你正在用 AWS 跑 PyTorch 或 TensorFlow 训练任务或者正准备把本地训练环境迁移到云上这篇可以直接当操作手册来用。1. 为什么选 RHEL 8 Deep Learning AMI 这套组合1.1 Deep Learning AMI 到底解决了什么问题AWS Deep Learning AMI 是官方维护的预装镜像严格说它不是一个单一镜像而是一整套针对机器学习场景定制的实例模板。官方会基于不同基线和框架组合提供一系列版本选择包含 Conda 环境、CUDA、cuDNN、NCCL以及 PyTorch、TensorFlow、MXNet 等主流框架的预编译版本。我为什么要强调“预编译”这个词因为自己从零在 RHEL 8 上装 CUDA 驱动再编译框架看起来不复杂实际操作中对版本敏感度很高。比如 GCC 版本、内核头文件、Python 版本、cuDNN 的兼容性任何一个不匹配编译期报错就能消耗半天时间。Deep Learning AMI 把所有版本锁成一套经过互测的组合省去了大量试错成本。另一个好处是镜像本身内置了 NVIDIA 驱动和 EFA 支持。分布式训练要用的 EFA 网卡驱动已经预装好不需要再折腾内核模块编译和 dkms 安装。RHEL 8 本身以稳定安全著称作为企业级操作系统的使用惯性也比较强所以把两者结合起来可以同时获得开箱即用和长期稳定两个优点。1.2 和 Amazon Linux 等镜像的实际取舍很多开发者习惯用 Amazon Linux 2 或 Amazon Linux 2023因为它们是 AWS 的亲儿子云厂商集成度最高。但 Amazon Linux 不是 RHEL 兼容版本部分自研软件和内部安全基线规定必须跑在 RHEL 或 CentOS 生态上这时 Deep Learning AMI 的 RHEL 8 版本就成了刚需。从包管理角度来说RHEL 8 走的是 dnf/yum 体系和 Amazon Linux 2 比较接近但软件仓库版本更新策略不同。RHEL 8 的稳定性和长期支持周期更适合生产环境。如果你的组织内部有统一的安全扫描工具或合规基线要求RHEL 8 往往更容易通过评审因为它的加固案例和文档都更丰富。不过也要说清楚Deep Learning AMI 的 RHEL 8 版本在更新频率上通常略低于 Ubuntu 版本。比如 CUDA 12.x 的新特性Ubuntu 版本可能会先一步跟进。如果你的项目急迫依赖某个最新框架特性先到发行说明里确认一下 RHEL 8 版本支持的驱动和框架版本再入手。1.3 这套方案适合哪些实际场景我把合适的场景分为三类。第一类是内部有统一运维规范镜像和操作系统版本被合规审计锁死的企业RHEL 8 是硬性条件。第二类是训练任务以单机多卡或中小规模分布式为主需要开箱即用的深度学习环境没有太多时间折腾环境配置。第三类是已有代码基于 CUDA 和主流框架需要快速弹性伸缩比如抢占式实例任务、周期性批处理训练、模型评测等。如果只是随便跑个几小时实验或者单纯想体验最新框架特性我可能更倾向直接用官方的 PyTorch 或 TensorFlow 专用 AMI或者基于 Amazon Linux 的版本。RHEL 8 的优势要放在长期稳定和生产级部署的语境下看才更有意义。2. 部署前的规划实例类型、存储与镜像版本选择2.1 选实例别只看 GPU 型号很多人选实例时第一反应是看 GPU 型号和显存这没错但容易忽略 CPU 型号、内存比例和网络带宽。深度学习训练是 CPU、内存、GPU、存储、网络协同工作的过程木桶效应非常明显。我自己常用的选型思路是先看训练模式再定实例家族训练模式推荐实例核心考虑单卡小模型、调参实验g4dn.xlarge / g5.xlarge成本优先GPU 够用即可单机多卡、中等规模模型g5.12xlarge / p3.8xlarge需要关注 CPU 核数与内存带宽大规模分布式训练p4d.24xlarge / p4de.24xlarge需要 EFA 与高带宽网卡支持纯推理部署g4dn / g5 系列关注延迟和吞吐显存够用即可p3 系列用的是 V100虽然老但在某些模型上性价比依然不错。p4d 的 A100 配上 400Gb/s EFA是分布式训练的硬核选择。g5 系列用的是 A10G适合中小规模训练和推理价格比 p 系列友好得多。还要注意一个隐藏点实例的 CPU 内存比例会影响 DataLoader 的工作效率。比如在 g5.xlarge 上只有 4 个 vCPU 和 16GB 内存如果预处理逻辑比较重数据加载很可能成为性能瓶颈GPU 会间歇性饥饿。2.2 EBS 存储怎么选、目录怎么规划存储上我见过不少翻车案例最典型的是给根卷配了 gp2结果数据集全堆在里面训练时 IO 跟不上。EBS gp3 是目前性价比最好的选择基线 3000 IOPS 和 125 MB/s 吞吐可以独立调高吞吐到 1000 MB/s。io2 适合对延迟和 IOPS 要求極高的场景但成本会明显上升。我的建议是根卷和数据卷分开。根卷用 gp3大小一般 120GB 足够主要装系统和 Conda 环境。数据卷单独挂载容量按数据集大小估算格式化成 xfs挂载时加上 noatime 和 nodiratime 参数减少不必要的磁盘写入。如果你的临时文件很大比如中间检查点和日志建议放在实例存储 NVMe 上用完即走不占 EBS 容量。目录规划可以参考这个结构/home/rhel8-user/ —— Conda 环境和代码/data/ —— 挂载数据卷放训练集和验证集/scratch/ —— 实例存储放临时缓存、tfrecord 碎片、日志/checkpoints/ —— 单独的模型存储目录周期性同步到 S32.3 如何确认 RHEL 8 对应的 Deep Learning AMI 版本在 AWS 管理控制台创建实例时搜索“Deep Learning AMI”然后筛选 RHEL 8通常能找到多个版本。官方会附带类似“ami-xxxx”的 ID不同区域不一样。我建议不要直接在控制台随手选而是先到 AWS 官方文档的 AMI 列表里用 SSM 或 CLI 获取对应区域的 AMI ID尤其是要做自动化部署时。可以用这个命令快速查到aws ssm get-parameters-by-path \ --path /aws/service/ami-amazon-linux-latest/ \ --query Parameters[?contains(Name, al2023-ami-kernel-6.1)].[Name] \ --output text不过这个命令默认查的是 Amazon Linux 路径。查 Deep Learning AMI 需要换成对应的 Deeep Learning AMI 文档路径或者直接在控制台过滤。我更常用的方式是先到官方发布说明看版本号再决定要不要升级。比如当前镜像预装 CUDA 11.8 还是 12.x直接决定你能不能跑新版 PyTorch。这里有个非常容易犯的错拿到 AMI 后不确认 “AMI ID 所属区域” 就启动实例。AMI ID 是区域级别的us-east-1 的 ID 不能直接用于 ap-northeast-1构建自动化脚本时要把区域参数和 AMI ID 映射表一起维护。3. 从零到可用实例启动、环境验证与 RHEL 8 初始化3.1 首次启动实例的操作细节创建实例时我推荐用启动模板Launch Template而不是每次都手动点控制台。特别是你要反复重建环境模板可以预先固定 AMI、实例类型、密钥对、安全组和存储配置减少人为失误。启动实例的关键参数建议镜像Deep Learning AMI (RHEL 8)勾选“仅查看符合条件的 AMI”过滤实例类型根据预算选择本文以 p3.8xlarge 为例密钥对新建或选择已有网络VPC 默认即可但安全组要限制 SSH 来源 IP存储根卷 120GB gp3另建 500GB 数据卷挂载到 /data高级设置勾选“在实例中安装 EFA 支持”如果实例类型支持启动后SSH 登录ssh -i /path/to/your-key.pem rhel8-useryour-instance-ipDeep Learning AMI 的默认用户是 ec2-user 还是 rhel8-user取决于具体镜像版本。登录后先用 whoami 确认如果不对查看 /home 下的用户目录。3.2 验证 GPU、驱动和 Conda 环境登录后第一件事是检查 GPU 是否被系统识别nvidia-smi正常情况下应该能看到 GPU 型号、驱动版本和显存状态。如果输出为空或报错多半是内核模块没有加载重启实例试试RHEL 8 上驱动加载失败的概率不低可能需要重新生成 initramfssudo dracut --force sudo reboot之后检查 Conda 环境里有哪些框架可用conda env listDeep Learning AMI 通常内置多个环境名称类似 pytorch 和 tensorflow2。激活 PyTorch 环境conda activate pytorch python -c import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.cuda.device_count())这一段如果跑通说明环境基本可用。我习惯顺手跑一个小矩阵乘法和卷积确认 GPU 计算真的没毛病而不是仅看 cuda.is_available() 的结果。3.3 在 RHEL 8 上补充常用基础组件RHEL 8 镜像本身比较精简很多开发工具链要自己装。包管理默认是 dnf和 yum 兼容。我建议先更新系统并安装常用工具sudo dnf update -y sudo dnf install -y git tmux htop net-tools dnf-utils \ openssl-devel libffi-devel gcc gcc-c make如果你需要多机节点间做免密登录还要提前配好 SSH key 分发。如果要操作 S3AWS CLI 可能已经预装没有的话用 pip 安装pip install awscli --upgradeRHEL 8 的 SELinux 默认是 Enforcing 状态。这本身是安全机制但有时会导致一些训练框架访问文件或设备时出现 Permission Denied 的诡异报错。如果确认是 SELinux 的问题先尝试调整上下文而不是直接关闭。作为快速排查手段临时切换成 Permissive 再观察sudo setenforce 0如果最终确认需要长期关闭要修改 /etc/selinux/config 并重启不过生产环境我不推荐这么做。4. 五大性能陷阱从数据加载到多卡通信逐一优化4.1 数据加载是第一个性能黑洞训练慢最常见的原因不是 GPU 算力不够而是 GPU 在等数据。尤其是文件读取密集的场景CPU 预处理跟不上 GPU 的消耗速度导致利用率曲线像锯齿一样上下跳动。PyTorch 里最容易见效的优化是调整 DataLoader 参数train_loader DataLoader( dataset, batch_size64, shuffleTrue, num_workers8, pin_memoryTrue, prefetch_factor4, persistent_workersTrue )num_workers 从默认 0 改为 CPU 核心数的一半或四分之三pin_memory 用页锁定内存加速 GPU 拷贝。prefetch_factor 可以预取更多批数据减少等待。persistent_workers 避免每个 epoch 重复启动子进程。如果数据集是大量小文件强烈建议先打成 TFRecord 或类似格式或者直接预加载到内存。小文件随机读取会频繁触发磁盘 IO在 EBS 上尤其吃亏。更激进的做法是用 NVIDIA DALI 做数据管线把解码、缩放、增强都搬到 GPU 上CPU 负担能大幅降低不过引入额外的学习成本适合数据增强非常复杂的场景。4.2 开启 EFA分布式训练的网络底座当训练从单机多卡扩展到多机多卡网络就成了新的瓶颈。传统 TCP 通信在大模型梯度同步时延迟高、带宽利用率不足。EFAElastic Fabric Adapter是 AWS 专门为高性能计算和分布式机器学习设计的网络接口可以绕过操作系统网络协议栈直接访问硬件。Deep Learning AMI 通常预装了 EFA 驱动但需要在实例上用以下命令确认fi_info如果能查到 efa 设备说明驱动正常。还需要检查实例元数据curl -O http://169.254.169.254/latest/meta-data/network/interfaces/macs/之后要在训练脚本里设置网络接口确保使用 EFA 而不是普通 TCPexport FI_PROVIDERefa export FI_EFA_USE_DEVICE_RDMA1同时把默认的三张网卡识别正确避免通信走到管理网卡上。4.3 NCCL 参数调优多卡通信的核心PyTorch 分布式训练底层走的是 NCCL。Deep Learning AMI 自带的 NCCL 版本通常能直接用但默认参数不一定适合你的实例拓扑。在单机多卡里最常见的是共享内存冲突导致初始化失败或者大消息通信效率不高。我通常在训练启动脚本里加这些环境变量export NCCL_DEBUGINFO export NCCL_P2P_DISABLE0 export NCCL_SOCKET_IFNAMEeth0 export NCCL_IB_DISABLE0如果在 EFA 环境里可以加上 NCCL_TOPO_FILE 指向系统提供的拓扑文件让 NCCL 更理解 GPU 和网卡的互联关系。NCCL_DEBUGINFO 一开始就打开能看到初始化过程是走了 P2P、SHM 还是 IB有助于定位性能异常。还有一种常见情况是 NCCL 通信超时。在实例类型不支持 RDMA 的场景下需要显式禁用 RDMA否则初始化阶段会一直重试export NCCL_IB_DISABLE1 export NCCL_P2P_DISABLE0这里的取舍要根据实例是否支持 EFA 和 GPU Direct 来决定建议先跑一次小规模通信 benchmark再正式训练。4.4 文件系统与挂载参数的微调EBS 盘挂载默认参数偏向通用场景对于吞吐密集的训练并不友好。我建议重新挂载数据卷添加 noatime 和 nodiratime减少元数据更新。还可以考虑用 logical volume 把多个 EBS 卷拼成一个跨卷 RAID 0成倍提升顺序吞吐。我经常用多个 gp3 卷拼一个大逻辑卷。比如 4 个 500GB gp3 组成 2TB 逻辑卷底层 IO 并行度提高读取性能提升明显适合大规模数据集扫描场景。创建逻辑卷的过程sudo pvcreate /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1 sudo vgcreate vg_data /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1 sudo lvcreate -L 1800G -n lv_data vg_data sudo mkfs.xfs /dev/vg_data/lv_data sudo mount -o noatime,nodiratime /dev/vg_data/lv_data /data还要把临时文件的目录尽量放到实例存储上。实例存储在重启后数据不保留但吞吐和延迟远优于 EBS适合放缓存和中间结果。优化后重启同样要重新挂载写入 /etc/fstab 时注意用 UUID 或 PATH 方式避免重启后设备号变化。4.5 内核参数与内存锁页细节RHEL 8 默认的共享内存上限不高多卡训练时可能触发“unable to allocate memory”或 NCCL 初始化失败。我一般调大这些参数sudo sysctl -w kernel.shmmax137438953472 sudo sysctl -w kernel.shmall33554432 sudo sysctl -w vm.max_map_count262144max_map_count 这个参数容易被忽视PyTorch 的 DataLoader 和 CUDA 上下文都会大量映射内存区域太小会导致 mmap 失败。另外可以临时放开内存锁页限制减少 CPU 与 GPU 之间的数据拷贝开销。编辑 /etc/security/limits.conf* soft memlock unlimited * hard memlock unlimited修改后重新登录生效。这个配置对单卡小模型感知不明显但对大 batch 多卡训练影响比较直接。5. 性能分析的实操手段与监控面板5.1 常用工具和基准测试方法性能优化不能靠玄学要量化数据驱动。我常备三个工具nvidia-smi 看实时 GPU 占用nvidia-smi dmon 看更细粒度的性能指标nsys 或 nvprof 做 profiling。先用快速方法确认当前是否有瓶颈nvidia-smi dmon -s pucvmt -d 5这个命令输出 GPU 利用率、算力、显存、温度、功耗等指标。如果 GPU-Util 接近 100%说明计算本身没有明显空闲如果只有 40%-60%大概率在等数据或通信。定位数据加载瓶颈我习惯加一时代码计时start time.time() for batch in train_loader: data batch[0].cuda() labels batch[1].cuda() end time.time() if step % 100 0: print(fStep {step}: data loading {end - start:.3f}s) start time.time()重点关注 epoch 刚开始的一段数据加载耗时。如果时间波动很大磁盘 IO 不稳定的嫌疑很大。5.2 用 CloudWatch 做集群级监控单机内部用 nvidia-smi 没问题如果是多机分布式训练还是建议把指标送到 CloudWatch 统一看板。实例自带 CloudWatch Agent可以采集内存、磁盘、网络指标但 GPU 指标需要额外配置。Deep Learning AMI 里集成了 DCGMNVIDIA Data Center GPU Manager相关的 exporter可以直接把 GPU 利用率、温度、功耗传到 CloudWatch。配置好后一个训练任务跑起来所有节点的 GPU 状态都能在一个时序图里看到异常节点一目了然。我还习惯把训练日志的关键指标也打到 stdout利用 CloudWatch Logs 实时查看避免反复 SSH 登录多台机器抓日志。训练脚本里加上损失值、学习率、吞吐量的周期打印排查问题效率会大幅提高。5.3 通过实验对比验证优化效果建议做优化前后对比实验。控制变量同样的数据和 batch size分别跑优化前后的版本各 50 个 epoch记录总时长和平均吞吐。我自己做的完整优化流程从初始环境到最终调优p3.8xlarge 训练 ResNet-50 的吞吐量提升大约 40%-60%分布式场景下的扩展效率从原来的 65% 提升到 90% 以上。量化改进结果能帮你决定下一步投入方向。比如发现瓶颈在网络加钱买更大带宽实例才有意义如果是数据加载瓶颈先把数据预处理和缓存做好比换 GPU 更划算。6. 可扩展性设计从单机训练到弹性生产集群6.1 用自定义 AMI 固化最佳环境手工调好的环境不保存下次需求来了又得重来一遍这是最不划算的事。完成所有驱动、依赖、环境变量和配置文件后把当前实例保存为自定义 AMI形成 team 内部的标准训练镜像。保存前要清理不必要的缓存和临时文件conda clean --all pip cache purge sudo rm -rf /tmp/* /var/tmp/*然后通过控制台创建镜像或者直接用 AWS CLIaws ec2 create-image \ --instance-id i-xxxxxxxx \ --name rhel8-dl-ami-pytorch-2.1 \ --description RHEL 8 Deep Learning AMI with PyTorch 2.1创建镜像需要重启实例建议在训练空闲窗口操作。镜像保存后后续启动实例就不用重新装环境了几分钟内就能拉起一个训练节点。6.2 结合 Auto Scaling 与 Spot 实例实现弹性训练任务通常有波峰波谷尤其是多组实验并行跑或夜间批处理任务场景用 Auto Scaling 可以按需求自动加节点或回收节点。做法是定义 Launch Template 指定自定义 AMI 和实例类型配置最低/期望/最大实例数结合自定义指标比如队列深度或 GPU 总利用率做伸缩策略。不过训练任务大多是有状态任务线上自动缩容需谨慎除了把模型检查点及时落到共享存储或 S3还要让任务支持断点续训。成本上Spot 实例可以做到折扣很大的价格适合容错能力强的任务。我用 Spot 跑过周期性的推理评测任务检查点每 5 分钟保存一次节点被回收最多损失 5 分钟计算效果完全可以接受。6.3 多机分布式训练与数据共享多机分布式训练和单机最大的区别在于数据准备和通信拓扑。数据不能再散落在各节点本地而是要统一放到 S3、EFS 或 FSx for Lustre。FSx for Lustre 在性能上最贴近本地盘适合频繁读取的时序数据但费用更高。如果数据集不大直接用 S3 加缓存目录也能接受。我一般会把数据先下载到每个节点的实例存储或 EBS 数据卷再用共享存储做检查点汇聚。数据准备和训练解耦后扩容一个新节点只需要几分钟预热不用重新拷贝整个数据集。还要规划任务编排方式。如果只是单个训练脚本用 Deep Learning AMI 配合一组节点手动在地点上启动就行。如果任务多、依赖复杂可以引入 Ray 集群或 Slurm 工作负载管理器。RHEL 8 生态下 Slurm 的部署成本不低建议小团队先用 AWS ParallelCluster它有现成的 Slurm 集成可以把 RHEL 8 作为基础 AMI 灵活配置。7. 排查实录与速查手册7.1 GPU 无法识别症状nvidia-smi 返回 No devices were found。排查步骤确认实例类型确实包含 GPU这不是 CPU 实例检查 NVIDIA 内核模块是否加载lsmod | grep nvidia查 /var/log/messages 或 dmesg 里有没有驱动报错尝试 dracut --force 重建 initramfs确认没有因为 RHEL 8 更新内核导致驱动与内核版本不匹配如果以上都没用最简单的办法是从 Deep Learning AMI 文档里找到对应驱动版本重装一次驱动。RHEL 8 直接跑官方 runfile 安装通常可行但要去 X 和 nouveau 干扰建议在 text mode 或 multiuser 环境先卸载旧驱动。7.2 NCCL 初始化超时或通信失败最常见的是 RDMA 相关参数不匹配。症状为初始化阶段一直卡住或者训练到第一个 allreduce 时直接错误退出。我的固定排查方案export NCCL_DEBUGINFO export NCCL_IB_DISABLE1如果禁用 RDMA 后能跑说明实例本身不支持 IB 或 EFA属于配置与硬件不匹配。如果启用 EFA则要检查 FI_PROVIDER 和 EFA 驱动版本。另外要留意不同实例类型的网卡名比如 p3.8xlarge 默认网卡是 eth0但新的 p4d 上可能是 ens 或其他命名NCCL_SOCKET_IFNAME 要改成实际 IP 所在的网卡。7.3 训练时显存溢出OOM 问题排查第一件事不是调小 batch size而是看模型输入尺寸和梯度累积方式。有时候是中间张量占比太高检查一下模型结构里有没有大尺寸的临时变量。实用技巧梯度累积步数设成 2 或 4保持等效 batch size 不变PyTorch 里用 torch.utils.checkpoint 用计算换显存减少 DataLoader 的 num_workers 和 prefetch 数量避免加载数据本身占显存如果单卡显存实在不够考虑模型并行或张量并行而不是单纯调 batch size7.4 EBS 吞吐达不到预期gp3 如果按默认 125MB/s 用大模型数据集加载必然慢。先确认实例本身的最大吞吐能力是否够比如某些实例的网络和 EBS 带宽是共享的。提升手段按性价比排序调高 gp3 的吞吐到 500MB/s 或 1000MB/s多个 gp3 卷做 RAID 0把频繁读取的数据放到实例存储换 io2 或 io2 Block Express 处理极高性能场景7.5 SELinux 导致的权限异常症状是代码在 Amazon Linux 上跑得好好的换到 RHEL 8 就各种 Permission Denied。很多情况下不是文件权限问题而是 SELinux 拦截。看到 /var/log/audit/audit.log 里大量 avc denied 记录的基本可以确定是这个坑。快速处理方式是用 audit2why 或 audit2allow 生成策略模块而不是直接关 SELinuxsudo grep avc denied /var/log/audit/audit.log | audit2allow -M my_training_policy sudo semodule -i my_training_policy.pp这套处理比 setenforce 0 优雅得多也容易通过安全评审。8. 成本控制与最终优化效果总结训练任务跑完账单往往比预想的高。最容易失控的是存储和空闲实例。节点租用费用是固定的但如果数据集一直占着高配 EBS 盘或者实验做完忘了回收实例每小时的隐性成本都会累积。我通常给团队定一条铁律实验结束 24 小时内没有新任务销毁实例、保留 AMI。保留 AMI 的成本只有 EBS 快照费用远低于活实例开销。下一次实验重新启动加载自定义 AMI 和准备数据最多半小时比较划算。数据存储上也建议分冷热。热数据放在 EBS 或实例存储上随算力走训练结束把原始数据和 checkpoint 同步到 S3低频使用可以转 S3 Glacier节省大量存储费用。从我们最终跑的生产项目来看部署完这套 RHEL 8 Deep Learning AMI 的体系后几个关键指标变化非常明显指标优化前优化后单卡训练吞吐量基准35%~50%数据加载等待时间占比30%小于10%四节点分布式扩展效率65%90%新环境准备时间半天以上30分钟内这个结果说明花在环境部署和性能调优上的时间很快能被训练效率的提升抵消。尤其是团队要频繁跑相似任务时把环境固化成自定义 AMI 后每次训练启动的成本几乎只剩下数据和算力本身。最后再分享一个我个人的习惯每次踩完坑认真记录到团队的内部文档里因为 AWS 的 Deep Learning AMI 版本更新快同一种问题在不同版本上表现可能完全不同。环境的可复现性比单次的性能优化更值得投资。部署时多花时间搭好基础后续的训练任务才能稳定、高效地跑下去。