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种方法


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,导致重复的内存开销。


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 检查能不能看到显卡信息


Stock Selection via Spatiotemporal Hypergraph Attention Network – A Learning to Rank Approach

论文原址:https://www.aaai.org/AAAI21Papers/AAAI-7907.SawhneyR.pdf

这几年研究把深度学习应用到量化交易和传统投资决策的论文并不少,但都有2个问题:

  1. They do not directly optimize the target of investment in terms of profit
  2. and treat each stock as independent from the others, ignoring the rich signals between related stocks’ temporal price movemen

为了解决这些问题,作者提出了一种叫 STHAN-SR 的超图神经网络。并通过对美3大交易所最近6年的数据,证明这个网络在当前所有股票预测算法里面是最优的

Introduction

美股市场,68万亿,19年

forecasting:预测

highly volatile and non-stationary nature – 股票市场具有很强的波动性

synergy 协同

当前主流的股票预测算法,最大的弊端就是不以投资收益为优化目标。因为这些算法,要么是基于回归来预测价格,要么基于分类来做交易决策,这种分类和回归的优化目标是为了提高股票价格走向的准确性或最小化预测股票收益的误差,而不一定是为了直接利润

(有什么不一样吗??一脸懵比)

0

第二个弊端就是不考虑股票之间的相关性,总是把这些股票作为独立的个体来分析,或者使用一个过于简化的股票市场模型,一个由单个股票之间的成对关系组成的图表,但在现实中,这与真正的市场是相反的


Software-defined far memory in warehouse-scale computers

论文重点

  • 采用软件zswap的方式进行cold memory数据压缩
  • google的业务有32%的memory数据是cold memory,其中20%的数据是可压缩的,压缩效率中位数为3倍,那么TCO有4.2%的收益
  • 该压缩策略保证了SLA不受影响:通过cold memory和promotion rate的模型保证
  • 压缩解压缩的开销不大,完全可以接受
  • 使用ML进行autotuner

1 introduction

问题:
  • 随着资源的scaling,一个资源瓶颈会影响整个scaling的TCO。
  • DRAM成为scaling的瓶颈,memory的TCO减少逐渐放缓
  • 随着大数据workload的普及,主流的in-memory computing的方式加速了DRAM的需求
方向:far memory(介于DRAM和DISK之间的存储)
far memroy需要解决的问题:
  • 不影响应用的性能
  • 应用数量多且种类多样,不能对每个应用单独优化(不现实),需要对业务透明且robust的机制,有效使用Far memory
  • 动态的cold数据行为:业务的数据冷热是会变化的,比如不同该时间段

 


Concurrency management in BPF

eBPF 对并发编程的支持是非常弱的

有一些讨论:

  1. https://lists.iovisor.org/g/iovisor-dev/topic/bpf_concurrency/74407447
  2. https://lwn.net/Articles/779120/

但是你甚至在 eBPF 的官方文档 https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md 里面几乎找不到任何关于并发的描述,除了 map.atomic_increment() 接口

但是这个借口也很奇怪。为了突出这个接口是原子的,线程安全的,难道其他接口就不是了吗?但好像又不是,因为 eBPF 里有大量的示例,并没有对 map 做特殊的并发控制,所以理论上可以认为,其他大部分 map 操作接口,也是线程安全的

线程安全验证

测试代码如下:

// calculate latency
int trace_run(struct pt_regs *ctx, struct task_struct *prev)
{
    u32 pid = bpf_get_current_pid_tgid();
    u64 *tsp;
    if (pid <= 1) {
        return 0;
    }
    
    pid = 10000; 
    u64 ts = bpf_ktime_get_ns();
    start.update(&pid, &ts);
   
#define __OP__ == 
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    /** 重复N次 **/
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    tsp = start.lookup(&pid); if (tsp __OP__ 0) { dist.increment(bpf_log2l(1)); }
    
    start.delete(&pid);
    return 0;
}

这段代码,修改自 bbc 工具集自带的 runqlat.py

这段代码实际上是在构造一个冲突场景,来验证 trace_run 到底是并行执行还是串行执行的。代码的逻辑是在一个超高频触发的钩子(调度器进行上下文切换)函数里,先在 map 里插入一个固定key(pid)的元素,然后查询 N 次,每次查询如果 key 不存在,计数器就 +1,最后把 key 从 map 里面删除掉,如果:

  1. trance_run 是串行的,那么技术器永远都是 0,因为在一个串行执行的逻辑里,插入元素后,不管查询多少次,元素必然是存在的
  2. 如果 trace_run 是并行的,那么就存在一个条件,一定有一个 cpu,在另外一个 cpu 查询 key 是否存在的时候,执行到了函数结束的地方,把 key 从 map 里面删掉了

事实证明,trace_run 是并行执行的,并且中间 lookup 的次数越多,冲突的概率就越大