百亿KV的压力
推荐上一篇文章说到所谓TB级模型主要的问题在于百亿离散特征,反映在物理层面就是由百亿KV对构成的Embedding表。很多人会直观的认为这里需要一个redis集群,或者类redis的大型KV存储,其实非也。Embedding在使用上存在一个显著的特点:批量发布、批量查询、尺寸种类很少(一个模型通常只使用不超过10种维度的Embedding)。与统一写入KV存储相比,更合理的做法是将这百亿KV对打成若干个数据包来提供服务。由于存在CHD之类的算法可以非常高效地打包5-10亿的KV对,百亿特征实际上也就10-30个包,存储引擎需要管理元数据的量级不是百亿而是几十。
分布式Embedding服务
虽说拥有TB级内存的服务器已经不是什么稀罕物了,但是在云原生时代为一种服务准备专用机器不是什么好主意,一般来说还是考虑分布式服务。由于Embedding查询落点往往接近幂律分布,这就意味着用一组较小的的本地数据就可以覆盖相当可观比例的请求。另一方面,除了基于实体ID的特征外,其他特征在同一个batch内通常也有可观的重复率。建立在这两个特点基础上,我们可以做针对性优化。
从上一篇文章我们知道某种离散特征是否非常稀疏,跟它的来源有密切关系。有了这种先验知识就可以很简单地划分本地Embedding和远程Embedding,查询时可以使用一种算法(以下称为“着陆表”)一次性完成去重和路由。典型的一次排序模型请求,可能有10万项Embedding查询,去重后剩3万多项,远程部分2万项。以16维半精度Embedding计算,2万项请求数据回传是0.64MB,一个25G网卡的机器能带4K多QPS。由于这2万项通常是要路由到多个分片服务的,每个分片的请求batch一般就几千,处理时间1ms左右,算上同机房网络延迟,远程访问的平均延迟可以优化到5ms以内。本地查询那部分显然是快于5ms的,该过程和远程部分可以异步进行,不会额外增加延迟。单线程计算的话,着陆表加上数据回填的平均耗时也在5ms,满打满算Embedding查询环节平均延迟可以做到10ms左右。考虑到对于大模型,神经网络部分的计算延迟通常也要到10ms量级,引入分布式Embedding服务并不会大幅降低服务响应速度。
在有些项目上,模型服务有独立的机器学习团队在维护。由于缺乏特征来源方面的先验知识,可能会倾向于基于训练期频次统计等后验知识来确定本地Embedding和远程Embedding的划分,并使用串行逻辑来处理本地和远程查询。串行会慢一些,不过实际上也并不致命。
真正的软肋
说到TB级模型上线的困难,一般人很容易想起费内存和访问慢。从前文分析可知,访问其实并没有非常慢。考虑到整个服务链路上重计算的环节还不少,从集群层面看可能会有不少闲置内存,费内存不一定是大问题。个人认为TB级模型上线真正的软肋在于更新难,模型更新与业务需求以及基础存储条件密切相关,本文暂不展开讨论,日后有机会再填坑。