从今天开始,我会连载三篇关于 GPU 共享的思考,分别从三个角度和大家共同讨论有关于 GPU 共享的问题。
这篇是三者中的开山之作,从需求的角度来探讨我们是否真的需要 GPU 共享。
GPU 共享似乎是近来相当热门的词汇。在为数不多的和客户交流的场合中,都听到了 GPU 共享的需求。
那什么是 GPU 共享呢?简而言之,就是把一块 GPU 让多个任务“同时”占用。
这里的“同时”加了引号,这是因为当我们直接将一块 GPU 指派给两个或多个任务时,GPU 并不是真正的同时处理来自这两个或多个任务发来的 GPU 指令的。(当然我们这里谈论的都是 Nvidia GPU。)每个任务需要拥有一个 GPU context 才能向 GPU 发出指令。而面对不同的 contexts, GPU 会采取时间切片的方式在不同的 contexts 之间做切换。而甲任务发出的 GPU 指令只有当 GPU 切换到甲 context 时才能被执行。不用担心,GPU 指令(这里指 GPU kernel function)是有 timeout 的,不会霸占着 GPU 不让切换 context。而需要注意的是,切换 context 是有成本的,而且成本并不廉价。
以上内容,以客户的水平基本是不会知道的。所以一些似懂非懂的客户在 GPU 共享之后可能会拿着 benchmark 找你逼逼,“为什么原来 10 分钟跑完的训练共享之后要跑 12 分钟了?!”。想必看过上面一段,你是可以回答这位客户的了。
那么客户是怎么想的呢?A)老黄卖的 GPU 真贵,而且还不能买游戏卡做训练(仅限正经客户,不正经的客户有的是用 1080 Ti 的);B)小刘写的代码真不行,每次跑起来只能用掉 2G 显存,剩下的 8 G显存全浪费了。
不得不承认,人家说的还是有道理的。可是客户很少会让小刘把代码优化优化,更不要提跟老黄去讨价还价了。客户最直接的办法,就是甩给你一个单子:“中央已经决定了,让你来做 GPU 共享”。
可惜不像有的人,嘴上说着“另请高明”。钱够就做。
当灰姑娘的姐姐们硬要把自己的猪蹄塞进水晶鞋的时候,作为旁观者一定是忍俊的。
各位客户的猪蹄大致包含了以下几种类型:
- 神经网络的训练
- 神经网络的推理
- 其他 GPU 应用
通常一块 GPU 的资源可以从两个相对独立的维度来描述:GPU 显存和 GPU 算力。一个任务,其对 GPU 显存的占用和其对 GPU 算力的使用并不一定呈现线性关系。显然是存在占用 GPU 显存很多但算力很少的应用,亦或是只占用少量显存但频繁计算的应用。
然而在深度学习领域,我们一般可以认为真实显存的占用和 GPU 算力的使用是正相关的。为什么要说“真实显存占用”呢?因为存在 TensorFlow 这样比较“自以为是”的应用,觉得自己管理显存更在行,于是在程序运行之初便申请全部显存后自己管理。显然其占用的(全部)显存并不全部被用作训练或推理。好在我们可以改变这种行为,在这里不再赘述。
有了对 GPU 资源的基本认识,我们先来看看第二种:神经网络的推理。
推理服务相对训练,其对 GPU 显存的占用是明显下降的。大量激活函数由于其非光滑的特性,必须保留中间变量以便日后求导。而这些变量在推理服务中即可舍去。
与此同时,没有了反向传播的需求,对于计算量的需求大幅下降。加之一般推理请求的特性就是 request-after-request,往往不像训练时那样组成 batch,导致单次推理操作对 GPU 算力的使用也很低。
这样一来,我们可以总结,推理服务属于那种显存和算力使用都较小的 GPU 应用。可以想象,如果把一块 GPU 单独指定给一个推理服务,那必然会造成资源的极大浪费。这样看来,GPU 共享这双水晶鞋确实找到了灰姑娘的那双脚。
模型训练与推理相比,不仅因为反向传播增添了对 GPU 显存和算力的需求,更关键的是调参员可以通过 Batch Size 来调整对 GPU 显存以及算力的使用。
下次如果灰姑娘的姐姐拿着 Batch Size 为 8 的分布式训练 case 找来的时候,我希望她可以明白:“命里有时终须有,命里无时莫强求”。
有人会问,那如果是一块 GPU 分给几个用户同时 debug 模型呢?首先,如果是为了跑通代码,没有必要用 GPU。如果要验证模型的初始效果,用半块 GPU 在那里测试的体验远不如提交一个任务查看任务的收敛。所以对于这种需求,与其期待 GPU 共享为用户带来更好的体验,不如考虑直接从训练脚本提交任务并直接查看训练结果来得更好。
那么其他的 GPU 应用,如果依然遵循显存与算力正相关的规律的话,可以依照“小显存,小算力”和“大显存,大算力”来划分。
举个栗子,Nvidia MPS 提供的案例即为 N-Body 模拟。至于这个应用是否属于“小显存,小算力”,我觉得还是交给大家自己去验证来得更好。
读到这里,你是否也觉得,嗯,模型推理服务确实是适合 GPU 共享的应用呢?
“真的吗?我不信。” —— 鲈鱼
我们是否真的要把多个模型服务的应用调度到同个 GPU 上呢?
在刚才的分析中,推理服务 request-after-request 的特性,即无法 batching 的特性是导致“小显存,小算力”的原因。那我们是否可以改变这一特点呢?当然不是改变用户习惯,而是采取暂存的方式,将一段时间内的 requests 打包成一个 batch 送入 GPU。
退一步讲,假设 batching 效果不佳,我们是否要就真的要在一块 GPU 上起多个模型服务,并且忍受 context 切换带来的 overhead 呢?
感兴趣的朋友可以去翻看翻看 Nvidia Triton Inference Server 如何实现同一个 context 下利用多 stream 的方式实现多个模型同时实现前向推理功能的。结合更灵活且智能的 model scheduling,相信可以实现更好的效果。
最后来谈谈我自己对 GPU 共享的“热情”。为什么要加引号?因为我对 GPU 共享没有热情。
通篇看下来,你可以感觉到,那些追求 GPU 共享的人,基本上就是既不懂 GPU 也不懂 DL 的人。
我还是希望做真正被 DL 所需求的 GPU 功能。但是既然当时中央已经决定了,我也只能念两句诗了。
那么接下来的两篇中,我们就来谈谈如何实现 GPU 的共享吧。