合作机构:阿里云 / 腾讯云 / 亚马逊云 / DreamHost / NameSilo / INWX / GODADDY / 百度统计
*本文数据具有即时性,不代表实时数据。
快手的模型场景主要是实时的大模型。实时主要体现在社交上。每天都有新用户上传 1500 万以上的视频,每天有亿级以上的直播活跃用户,并且上传数每年都在同比上涨。
大主要体现在流量规模。快手现在的日活达到了 3.87 亿,有千亿级别的日均曝光,百亿级别的日均播放,模型量级非常大,还要保证实时。并且快手的核心价值观是平等普惠,即千万级的用户同时在线时,个性化请求时会推荐不同的内容。
总结起来,数据处理的特点是既大,又要实时。
一般的推荐业务架构如上图所示,在视频池里(比如有几千万的视频)会经过固定的四个阶段:
快手的业务类型比较多样,主要可以分成大型业务和中小型业务。
大型业务的样本量级很大,像主站推荐一天的样本可能有千亿,存储能达到 p 的级别。迭代主要用流式迭代,即在线迭代特征和模型,速度会非常快。如果选用批式迭代的话,回溯样本要 30 天,需要的资源是流式迭代的几十倍,快手大场景下的流量分配又比较多,所以倾向于做在线的流式迭代实验,速度快,消耗资源量相对也少很多。
中小业务,一天的样本大约在百亿级别,存储大概几十 T。选择流式迭代会需要频繁上线迭代,而且流量分配也不够。这种情况下一般尽量选用批式迭代,此时需要很大量级的计算样本,比如要回溯至少 60 天以上,回溯样本能达到 p 级别。因为对于大模型来说,如果数据量不够,模型训练不充分,效果就会相应地下降。所以在这种小的业务场景里,还是倾向于批式迭代,回溯更多天的样本,以使模型达到一个更稳定的状态。在这种场景下面,会倾向于批次迭代实验。
这里是之前在快手发布的一个万亿级别模型文章里的截图,快手是个性化模型,所以参数量非常大。从图中对比来看,OpenAI 的 GPT3 参数量是 175B,但快手参数量 1900B,已经到万亿级别了。主要是因为快手选用的是 SIM 长序列模型,需要用户长期的兴趣,然后把该序列输入到模型。快手有亿级用户,life-long 兴趣需 10 万以上序列,再加上千亿级的样本的叠加,因此参数量非常大,能达到 1.9 万亿。虽然这 1.9 万亿参数跟 OpenAI 的 GPT 3 模型的参数类型不一样,计算量也不太一样。但从参数量级上来看,快手推荐是非常大的。
推荐模型跟语言模型紧密相关,一般新模型都会在语言模型上去做迭代,成功之后就会引入推荐模型,比如 DN、RNN、Transformer。上图是亚马逊 3 月份时发布的一个图,主要介绍了语言模型的一些进展。
可以看到,17 年之前主要是 RNN 模型,RNN 模型是按次序去顺序遍历数据后训练,该模型对并行算力要求并不高,但模型收敛比较复杂,因为可能会存在梯度消失的问题。2017 年出现 Transformer 之后,语言模型突破了原有的限制,可以做并发迭代,所以其算力大规模增长。
图中的树分为三个部分:(1)红线部分是 encoder-only 技术,最早是 Bert 模型;(2)绿线是 encoder-decoder 类型,Google 主要选择这一类型;(3)蓝线主要是 open API 里 ChatGPT 选用的类型,这一类模型发展得最好,因为它足够简单,只需要考虑 decoder,运算量小,而且模型效果也会很好。
快手对数据时效性要求很高,用户看到视频后会反馈到快手的 log 收集系统,该用户的行为会实时地拼接推荐日志(推荐日志就是推荐服务落下来的特征),特征流加上行为流成为样本流进入后面的特征处理,然后进入模型训练。模型训练完成后实时更新到在线预估,在线预估会根据模型的更新推荐出最符合用户需求的一些视频。该链路要求延迟必须要在一秒内,需要将用户行为尽快反馈到模型里,所以对于大数据处理的时效性要求是非常高的。
快手有千万级用户在线,不考虑行为多样性的情况下,QPS 至少是千万级的,如果区分到行为的多样性,这个组合数量就更爆炸了,高峰期大概每秒需要处理 30T 左右的状态。
业界方案主要是采用 Flink 流式框架,但如果直接用 Flink 引入 state join,在并发几千的情况下会造成大量的慢节点。因为 30T 状态如果 1000 并发的话,需要存 30G 的状态,如果 1 万并发也得存 3G。3G 在 1 万并发下的慢节点的概率会非常大。在这种情况下如果出现慢节点,需要几个小时恢复,这对于推荐系统肯定是不能忍受的。
所以快手选择了一个折中方案,把状态下沉至高性能存储上,然后采用无状态 hash join 的方式来做一个实时 join 的状态,只要用户的行为和特征都到齐,就立即触发样本的下发,这样就可以保证行为能够及时地反馈到模型。虽然特征和行为来的顺序不一样,但通过外部的状态,再加上 Flink 流式框架并行的操作,就能实现大规模高性能的 join。
在上述处理完成之后,是特征计算场景,主要有两种计算,标量计算和向量计算。标量计算类似于特征处理,比如要把某些值求和、求平均。在向量计算里,会对一批样本同一列进行一个同样的操作,放在 GPU 通过 cuda 计算。这样,通过使用 GPU 和 CPU 协同的方式实现高性能计算,一些标量操作在 CPU 上计算,内存访问也会在 CPU 上进行,然后传输到 GPU 上去做高性能的 GPU 计算。
为了保证算法迭代的灵活性,采用了 DSL 抽象。因为 SQL 不能完全描述所有的特征处理场景。比如有一些在时间窗口的操作,如果通过 SQL 去做需要写一些自定义的 UDF,这样很不利于迭代。所以我们的 DSL 是用 Python 描述的,用户可以通过 Python 直接调用下层的高效执行算子。第一步先写计算层,使用 C++ 实现一些高效的 operator,包括 cuda 和 CPU 相关的计算也都是通过 C++ 库去做的。在 runtime 下面采用 Flink 的分布式框架加上 GNI 的方式去调用 C++ 的这些算子,以达到高性能、高吞吐的处理。
推荐场景下有两个特点,一个是批流一体,另一个是潮汐。
批式调研和在线实验这两种场景会需要有批流一体,因为在批场景里调研特征或调研模型结构完成之后,需要到在线去做上线,因此需要有一个批流一体的统一描述语言加上统一的执行引擎。用户在批式上调研,会使用 DSL、Hadoop 和 Spark 把所有的数据计算出来,做模型迭代。模型迭代成功之后做特征上线,上线到流式通用特征处理框架上,或是上线到流式特征框架特化的一个处理框架上。这里之所以会分出两个节点,主要是因为有一些特征是所有模型公用的,所以可能在通用的框架下面,这样只需要计算一次。而在特化的算子下面则是一些模型所特有的特征,因此分开处理。但这两个计算引擎和语言描述其实是一样的。同样地,这些通用处理的数据需要落盘到批场景下。批场景下有很多是基于 base 的特征去迭代,会加入它自己的性价特征,所以在批次场景下面计算的也是 Delta。
上线完之后就会到在线服务,这里会有一个高性能的存储和计算库去承接,这一点在后文中还会讲到。在流式场景下,注重的是高吞吐、低延迟和高可用。在批场景下,主要关注高吞吐、高可靠。
另外一个特点就是请求潮汐。上图是请求潮汐的示意图(并不是快手的真实流量)。从图中可以看到,有早高峰和晚高峰两个高峰。在高峰期需要给足在线的算力,在低峰期则要把冗余的算力利用起来。
在这种情况下,快手的大数据处理框架以及在线所有的模块需要针对潮汐的特点,去做云原生架构的一些改造,比如快速恢复、自动伸缩、快速伸缩。快速伸缩主要是因为在自动伸缩的时候并不能保证是高效的,比如一次自动伸缩需要耗一小时或者几个小时之久,那么在线的请求在这几个小时之间会有比较大的损失。
另外,还需要把在线服务的资源池和大数据处理的资源池统一起来,这样所有资源在低峰期时可以把冗余算力给批式场景、大模型预训练场景或者大模型批量预估的场景,使资源得以利用。快手现在所有的架构都在向云原生架构演进。
大规模数据存储的第一个特点就是超低延迟,因为存储节点存储的都是状态,一些计算节点需要很多的状态信息才能去计算,所以存储节点大部分时间都是在叶子节点,而且推荐的在线实验有上千个模块,每一个模块只能给十毫秒以内或者最多几十毫秒的超时时间,因此要保证所有存储节点都是低延迟、高吞吐并且高可用的。
推荐实验和推荐服务 base 之间有一个互相切换的过程。一般并行的实验数量非常多,实验完成之后会去切换成一个在线的 base,这样它承担的流量就会非常大。比如在训练服务 base 里会有召回的 base、粗排的 base和精排的 base,各个 base 都需要去承担千万级的 QPS,而且要提供超高的可靠性。所以在线存储部分,大量选用的是全内存架构。
其次,快手有超大存储的需求。前文中提到,快手大模型有 1.9 万亿的参数量,如果换成普通八维的 float,需要的存储也要有 64T,而且还有一个全用户的行为序列,有 180T 左右的状态信息。如果要采用全内存的存储,将会需要 2000 多台机器。而且所有的状态需要在 30 分钟内恢复,因为推荐系统如果超过 30 分钟不恢复,会对线上产生非常大的影响,用户体验会很差。
针对上述需求,我们的方案主要有以下几个:
存储方案是 NVM Table,分成异构存储的三层:物理层提供底层存储的 API,包括 NVM 存储和 memory 存储;中间 memory pool 封装统一的管理功能,把 NVM 和 memory 的模块都管理起来;上层业务通过 memory pool 的一个 API 去调用下层的 NVM 和 memory,提供统一的查询逻辑。
在数据结构布局方面,memory pool 采用的是 block 接口抽象。将 NVM 和 memory 分成若干不同的、可通过全局统一地址来访问的 block,这样就可以实现 zero copy 的访问自由化。对于一些频繁访问的 key,会放到 mem-key 上。不常访问的 key 会放在到 NVM 上。一些索引的 key 会频繁访问,但查找到 key 之后,其 value 在最后要返回给上游的时候才会用到,并且量级较大,所以将 value 放到持久化的存储。Key 查询比较多,同时也比较小,所以放在内存,这样就实现了内存和 NVM 的零拷贝技术。这里的哈希表采用了业界领先的无锁技术,以减少临界区的竞争,完成高效存储。
从 NVM Table 的一个场景测试数据可以看出,其网络的极限吞吐与 JIRA 是相当的。跨网络访问一般是网络达到极限,所以 NVM 带宽可以完全覆盖网络带宽,瓶颈主要在网络上,这样就能保证 NVM 既有成本上的收益,也有大存储和高吞吐的收益。另一方面,恢复时间也下降了 120 倍。最开始恢复 T 的数据需要两个小时,采用 NVM 之后只需要2分钟。
存储方面,还有强一致性的需求,主要是因为在推荐场景里也有一些广告和电商的推荐,需要存储的副本特别多。因为当一些新的短视频或者新物料进来时,下游所有模块会有一个并发分发,需要保证这些视频在 10 秒内到达所有的推荐服务,且所有推荐服务里的状态需要保证一致。否则对于模型的效果影响很大。
我们采用了 Raft 协议加 BT 的模式。Raft 协议主要负责选组和同步数据,BT 的模式主要是改造 BT 同步的模式,比如在几千上万台机器规模下的同步,如果同时用主从同步的话,主节点的出口带宽可能会是从节点的千倍以上,带宽就会成为瓶颈,下发的状态就会非常少,高吞吐和数据同步会受到影响。
我们的方案是分布式的平衡树分发,构造一个平衡二叉树,把所有主从节点进行组织,每个节点只管有限个从节点,从而保证从主节点同步到叶子节点所需要的带宽不变,但是单节点的带宽限制为小于等于 2,这样在全局下既能做到一次性,也能做到高效地同步,10 秒内即可将所有视频状态分发到每个节点。
推荐模型的发展跟语言模型是相关的,从 DNN 模型到 Wide&Deep,到 Transformer,再到 SIM 长序列及生成式模型,模型增长了很多倍。除了模型的增长,算力增长也会随视频的增长和用户的增长,呈现出指数级的上升。从统计数据来看,最近两年推荐模型的算力增长接近 10 倍,我们的方案主要是优化工程架构和新的硬件技术。
生成式模型会带来计算量的爆炸,因为它是一个 token-based 的推荐,每次推荐需要之前所有的 token 作为 context,在这种情况下生成的效果才会最好。如果没有 token-based,那么与算力不会呈指数级增长。因此,推荐的压力,将主要来自状态存储的大规模提升,因为目前的推荐模型主要是 pointwise 的推荐,对于长序列推荐模型算力也是有限的。如果全部采用深层次模型推荐,其状态存储还将再增长 10 倍,挑战会非常大。因此我们需要通过一些新硬件,比如 CXL、NVM 以及新推出的 Grace 架构,再加上工程上的优化,比如状态做差分、传输计算等等,来应对未来的挑战。
TOP