AIFM – High-Performance, Application-Integrated Far Memory

AIFM 提出一种在用户态完成 swap 的方案,相比传统的内核态(虚拟内存)的方案,实现了:
  1. 更低的延迟:相比 fastswap,有6倍吞吐的提升
  2. 更高的灵活性:用户可以显式指定一块内存是local还是far(指向 remote memory)
0

1. 一些背景

1) OS swapping and far memory
之前对 swap 以及 far memory 相关的研究,大多都是在内核态的,由于内核态的 swap 都是 4k 粒度的,对于一些随机大量的小object场景,会带来很多性能问题
另一方面,如果你要依赖一些应用的信息(比如访存的行为、访存策略、内存热点)去做性能优化,这也是做不到的(madvise有一些近似的功能,但是不够)
为此,AIFM 提出了一种object级别的,可以访问远端内存的,类似于c++弱指针的一种新型指针
2)Disaggregated and distributed shared memory
当前大多数 Disaggregated memory 相关的实现,都要求数据中心具备高速互联的能力(但是现在很多数据中心其实还不具备这个能力),AFIM 提供了一种软件级的解决方案
另外一个问题是,传统的 far memory 系统里面,一份数据需要被多个 host 共享,这就要解决很复杂的数据一致性问题(同时带来了很大的性能开销)
AIFM 简化了这个问题, far memory 中的数据只属于一个 host


Distributed Shared Persistent Memory

NVM:Next-generation non-volatile memories,一种非易失性,按字节寻址,低延迟(接近DRAM的读写性能),新型设备
Hotpot 应该是第一个支持 PM 的 DSM 系统,传统的 DSM 都没有为 PM 做过真正意义上的支持。17年的Octopus也算是一个支持 PM 的 DSM 系统,但是它只有文件系统接口
Octopus: an RDMA-enabled DistributedPersistent Memory File System
因此,作者实现了一个支持 PM 的(所以叫DSPM区别于传统的DSM),内核态的DSM 系统,然后把一个 NoSQL 数据库部署到了 Hotpot 上,性能提升了3倍

1. 场景

作者发现 Tensorflow 和 PowerGraph 这类应用程序,在多个线程之间有大量的共享数据。
但是这个地方我没太理解,DSM 系统对同机多线程之间的数据共享是没有价值的,因为多线程本来就共享一个地址空间,数据只有一份
唯一的解析可能是,dsm 可以让这类系统,实现跨node级别的线性扩展

2. DSPM 的挑战

First)应该提供什么样的接口来同时支持直接内存访问,以及数据持久化?同时如果支持直接内存访问,会带来一个新的问题,怎么保证“指针”在不同机器上的一致性?
Second) 如何有效地组织DSPM中的数据以提供良好的应用程序性能?
Finally) 分布式缓存的数据一致性和可靠性怎么解决?


Mix Precision Training 混合精度训练

这是一篇百度和 nvidia 合作的论文
实际上在 MPT 之前,已经有一些论文在做减少精度训练相关的工作了,比如:
  1. Binaryconnect: Training deep neural networks withbinary weights during propagations @2015
  2. Binarized neural networks. InAdvances in Neural Information Processing Systems @2016

1. 背景

随着现在深度神经网络模型越来越大,万亿参数,百万亿参数,深度学习训练所需要的GPU内存越来越多,内存优化迫在眉睫
Year
Name
Param
From
2018
110M
OpenAI
2018
349M
Google
2020
175B
OpenAI
2022
540B
Google
传统的神经网络训练里面,都是用FP32来保存权重、梯度等数据。但是FP32在大模型场景下(万亿参数)内存开销太大了
为了解决这个问题,MPT 论文提出了一种使用 FP16 精度来训练神经网络,同时基本不影响训练效果的方法

2. 实现

使用FP16会有很多好处:
  1. 大幅减少内存消耗:50%?
  2. 运算加速,FP16肯定要比FP32要快
但是随之带来的问题是:精度损失
IEEE标准中的FP16格式如下:
0
取值范围是5.96× 10−8 ~ 65504,而FP32则是1.4×10-45 ~ 3.4×1038
精度损失在神经网络训练里面可是比较致命的,可能会训练出截然相反的结果
为了解决这个问题,论文提出了3种方法


阿里云 GPU Sharing in Kubernetes

文档:https://github.com/AliyunContainerService/gpushare-scheduler-extender/blob/master/docs/designs/designs.md

阿里云的 gpu sharing 只是实现了资源的按需分配和调度,并没有解决算力 & 显存隔离的问题

基于k8s原生的Scheduler Extender、Extended Resource、DevicePlugin机制来实现

提供2个接口:

  • aliyun.com/gpu-mem: 单位从  number of GPUs 变更为 amount of GPU memory in MiB,如果一个Node有多个GPU设备,这里计算的是总的GPU Memory
  • aliyun.com/gpu-count:对应于Node上的GPU 设备的数目

整体架构:


vm template 原理浅析

vm template 是一种加速虚机创建、节省vm内存的技术。

1. 背景

我们之所以说虚机是强隔离的,主要是因为虚机有独立的内核。不同vm之间的进程是完全运行在自己的内核空间里面的,相互不可见
引起虚机的启动,需要引导一个完整的内核,以及完整的os
这在传统的虚机场景下,问题不大,一个物理机上启动的vm数量可能就十几个
但是容器场景下,kata-containers,容器粒度要远小于虚机,一个物理机可能启动上百个kata容器,每个kata容器都有自己独立的内核,这样算来,开销就不小了
由于每个kata容器的内核,操作系统镜像,都是相同的。如果虚机之间的内核、操作系统镜像所占用的内存能够共享,这样就能省掉不少内存。
于是,vm template 技术出现了

2. vm template 原理

主要是利用了linux内核fork系统的cow原理(copy on write)
cow:fork一个新的进程,会把原进程的内存空间全部copy一份,但这个copy只是一个引用。只有新进程在写这块内存区域的时候,才会发生真正的copy操作
所以 vm template 的核心思路是:通过一个事先创建好的最小 factory-vm,包含公共的内核、操作系统镜像、以及kata-agent。创建kata容器的时候,从factory-vm fork一个vm出来,然后再通过热插拔的方式,调整vm的规格以符合kata容器的要求
但是这里有一个问题是,template vm 的规格是固定的,但是 Pod 的规格不是固定的,所以必须通过 Pod vm 的热插拔 & resize 能力来实现 vm 规格的调整


RunD – A Lightweight Secure Container Runtime

RunD – A Lightweight Secure Container Runtime for High-density Deployment and High-concurrency Startup in Serverless Computing
rund 是阿里提出的一种新的轻量级容器运行时技术。
不过目前从论文内容来看,更多是一些技术点的优化,而不是架构层面的创新

1. 设计目标

实现 serverless 场景下,pod的高密度部署、高频、高速启动
高密度部署:随着机型规格越来越大,比如 AMD milan 就有256核,可以部署数千个 pod
高频:faas 和 batch job 等负载,每天上百万的实例创建量,上亿次系统调用
高速:faas 场景的毫秒级启动,极致弹性

2. 问题分析

kata 容器的技术栈:
0
启动一个 kata 容器,首先需要通过qemu(或者其他hypervisor,比如fire cracker)拉起一个虚机,然后还需要再虚机内启动一个agent,来实现完整的oci语义
基本过程如下:
0

2.1 并发启动的开销

(1)在准备容器rootfs的可写层时有很长的耗时:同时启动200个kata container,准备rootfs需要耗时207ms,会产生4500iops和100MB/s的IO带宽,带来很高的cpu overhead
(2)同时启动多个kata containers时,涉及到host侧cgroup的创建及维护,在内核层面,凡涉及到cgroup 操作,需要持有全局粒度的自旋锁,导致cgroup 的创建及维护是一个串行过程

2.2 高密部署的瓶颈

(1)虚机(guest系统 + kata-agent + guest kernel)耗损
容器不是虚机,但是实现安全容器就必须依赖虚机。容器的规格一般都是很小的,比如内存100m 0.1vcpu,但是这个规格对虚机来说太小了,都起不来。所以为了能开一个100m的容器,你就得把虚机开到200m甚至更大,这就产生了 overhead
对于kata-qemu,一个内存规格为128MB的kata-containers,其内存overhead可以达到168MB;当部署密度从1提升到1500时,平均每个内存规格为128MB的kata-containers,其内存overhead 仍然会有145MB。
0
对于小内存规格的kata容器,guest kernel image所占内存占用了很大的比重。AWS数据:47% 的function computer的内存规格时128MB,Azure数据:90%的应用内存规格小于400MB。
(2)rootfs 内存耗损
rootfs基于块的主流解决方案在Host和Guest中生成相同的Page Cache,导致重复的内存开销。


kata 系统架构解读

https://github.com/kata-containers/kata-containers/tree/main/docs/design/architecture

https://github.com/kata-containers/kata-containers/tree/main/docs/design

1. 整体架构

如下图:

kata-containers 的核心 binary 就2个:

  1. containerd-shim-kata-v2,对应源代码目录 src/runtime
  2. kata-agent,对应源码目录 src/agent

containerd 实现了 cri 协议,可以天然直接无缝对接到 kublet 上,而 containerd-shim-kata-v2 实现了 containerd 的 shim-v2 协议,直接实现了对 kata-agent 以及 hypervisor 通讯的封装。


nr_dying_descendants 引发的在线业务性能抖动

最近发现一则比较奇怪的业务性能抖动问题

简单来说,就是离线作业频繁的触发oom,导致在线业务指标陡增。理论上来说,离线作业触发自身硬限,不应该导致其他容器出问题才是

先来说一下我们的 memory cgroup 层级结构,可以简单理解为如下:

/cgroups/v2/kubepods/

/cgroups/v2/kubepods/online/a -> a为在线服务

/cgroups/v2/kubepods/offline/job1 -> job1 为离线服务

通常来说,为了解决混部的内存争抢问题,我们会对整个 offline 大框设置一个内存上限,防止离线作业使用内存太多,导致在线服务出现内存压力。这看起来似乎没什么问题。

不过最近一个问题,发现离线 job1 频繁 oom,引发了在线的性能抖动

最后定位的结论如下:

  1. /cgroups/v2/kubepods/offline/cgroup.stat 下看到有大量的 nr_dying_descendants
  2. offline 大框频繁出发 oom
  3. oom 的回收过程有个效率不太好的地方,导致 oom 回收效率底下 -> 导致内核各种锁争抢严重 -> 进程sys高 -> cpu排队严重 -> 在线业务性能抖动

根因分析:nr_dying_descendants 的原因是由于容器被销毁时,容器内还有未完全回收干净的 page cache,所以内核不会立即释放这个 cgroup(用户态操作系统从 /cgroups 文件系统下已经看不到了,但是内核态的数据结构还在)

cat /cgroups/v2/kubepods/online/cgroup.stat

nr_descendants 10

nr_dying_descendants 4172

这就有一个问题,如果有大量的dying cgroups,内核在oom处理过程中:

  1. 如果是cgroup oom,会优先尝试从 dying cgroups 回收内存,但是最多只回收5个 -> 这个地方有效率问题
  2. 如果是整机回收,不处理dying cgroups内存

 


TMO – Transparent Memory Offloading in Datacenters

论文:https://www.pdl.cmu.edu/ftp/NVM/tmo_asplos22.pdf
内存是数据中心最大的瓶颈。百度也是如此,因为内存不足,导致大量机器cpu空闲但是利用率无法提升的主要原因
这篇论文的思路和 google 的 software-defined far memory 思路几乎是一样的,都是把 colder memory 卸载到 cheaper memory 上
google 论文是19年发表的,实际上项目2016年就已经上线了
facebook 论文是22年发表的,线上已经运行一年多。另外,论文里也隐约的提了,facebook就是在google的基础上做的,但是发现效果和 google 说的不一样,于是做了很多改进。如果我们要落地,也会有类似的问题。

1. INTRODUCTION

下文所有对比的地方,我简称:
  1. google的方案,g-swap
  2. facebook的方案,tmo
现状:
cpu的发展速度比memory要快
随着AI算法的发展,业务对内存的需求正在快速膨胀
硬件上,nvm和nvme ssd等技术的出现,Non-dram cheaper memory 开始越来越多的被使用
还有 CXL 这种 non-DDR memory bus 技术,可以接近 DDR 的原生性能
方向:内存分层技术,不管是 google 的 software-defined far memory 也有还是 facebook 的 TMO(透明内存卸载),本质上都是将低频访问的内存卸载到低速设备上
With memory tiering, less frequently accessed data is migrated to slower memory.
g-swap的一些问题:
  1. 核心是压缩
  2. 非常简单
    1. 好处:不用解决异构这些复杂的问题(感觉解决了?做了负载分类)
    2. 坏处:不利于最大化的 memory-cost saving
  3. promotion rate 的设定依赖离线分析
  4. 所有应用的 promotion rate 是一样的。没有考虑不同业务对内存的敏感性,以及不同分层设备的 performance(后者其实有简单的考虑,具体的 P% 的设备就取决于 far memory 到 near memory 的性能表现)
    1. g-swap的观点:为了保证应用程序的性能,memory offloading 应该控制在一个预先配置好的比例下面
    2. tmo的观点:facebook 对它们部署规模最大的程序,发现,在一些高速设备上,提高 memory offloading 比例,可以提高性能。 –> 反人类假设??
0
为了解决这些问题,facebook 提出了 tmo 方案,优点:
  1. 核心是卸载(但是卸载的backend 支持 compressed memory pool,也支持 NVMe SSDs)
  2. 不需要离线分析,依赖 PSI 的反馈,直接单机决定不同的 offloading 比例。不依赖app的任何先验知识(g-swap的方案就有一个冷启动问题,新app默认不回收任何内存)
  3. PSI accounts for application’s sensitivity to memory-access slowdown(这里其实是比较误导的,facebook认为psi观测出来的内存延迟一定是对业务敏感的,但是实际上不同的业务对延迟的敏感性是不一样的,简单来说这个是充分不必要条件)
收益空间:20-32% 收益,百万服务器,其中7-19%由应用贡献,13%由sidecar等基础服务贡献


How to install cuda and pytorch dev environment on thinkpad w530 & ubuntu 20.04

最近发现在笔记本上用 cpu 来训练模型效率还是太慢了,正好我笔记本上有一块 nvidia gpu,准备折腾一下把这块 gpu 用起来

由于在笔记本环境下安装 gpu 开发环境比较痛苦,虽然最终失败了,但是过程比较有意义,值得备忘一下

我的开发环境主要是 pytorch + cuda

硬件环境就是 thinkpad w530 + 32g + K1000M

由于笔记本的 gpu 通常型号比较老,并不是大部分的 pytorch 都支持

1. 安装 cuda

网上有很多资料,怎么安装 nvidia-cuda-toolkit 的,apt install nvidia-cuda-toolkit 就可以直接装

但是我发现装完了之后没法用,pytorch 没法检测到 cuda 环境。这两个东西不完全是一个东西,有挺多差异的。

python3 -c ‘import torch; print(torch.cuda.is_available())’ 必须返回 true

最后用官方的安装方法成功了

https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=deb_local

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600

wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda-repo-ubuntu2004-11-7-local_11.7.0-515.43.04-1_amd64.deb

sudo dpkg -i cuda-repo-ubuntu2004-11-7-local_11.7.0-515.43.04-1_amd64.deb
sudo cp /var/cuda-repo-ubuntu2004-11-7-local/cuda-*-keyring.gpg /usr/share/keyrings/
sudo apt-get update
sudo apt-get -y install cuda

2. 安装 nvidia-driver

ubuntu 默认源里面会有一些基本的 nvidia-driver

ubuntu-drivers devices 可以看到

# ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd00000FFCsv000017AAsd000021F5bc03sc00i00
vendor : NVIDIA Corporation
model : GK107GLM [Quadro K1000M]
manual_install: True
driver : nvidia-driver-390 - distro non-free
driver : nvidia-340 - distro non-free recommended
driver : nvidia-driver-418-server - distro non-free
driver : xserver-xorg-video-nouveau - distro free builtin

这里面会推荐你使用 nvidia-340

但是我的实际测试来看,nvidia-340是没法用的,安装完了之后显示器各种异常,最后安装到 nvidia-driver-390 解决了显示器异常的问题

但是nvidia-driver-390 版本太低,cuda 现在依赖的 nvidia-driver 版本都是470以上了,所以光有 nvidia-driver 是不行的,必须和 cuda 配套。

最终在安装 cuda 的时候会自动安装 nvidia-driver 配套驱动,其实不用自己搞

安装完,需要重启机器,重启后 nvidia-smi 检查能不能看到显卡信息