本文介绍了在推理场景中面临的挑战及解决方案,重点阐述了如何使用Tair数据库解决限流、负载均衡、异步处理、数据管理和索引增强等问题的过程。
详细说明了如何利用Tair数据库的消息队列、半结构化数据存储和向量检索能力来解决推理服务中遇到的问题,如限流、负载均衡等。
对比了Tair、社区Redis及Kafka等数据库和中间件产品在效率、可扩展性、灵活性和数据持久化等方面的差异,强调了Tair的优势。
介绍了Tair在实际应用中的挑战,包括连接数超限、内存超限和带宽超限等问题,以及相应的优化措施。
一个典型的推理场景面临的问题可以概括为限流、负载均衡、异步化、数据管理、索引增强 5 个场景。通过云数据库 Tair 丰富的数据结构可以支撑这些场景,解决相关问题,本文我们会针对每个场景逐一说明。
目前 AI 热度极高,各种大模型满天飞,催生出很多 AI 推理的服务。通常我们自己部署一个实验性质的推理服务需要部署推理引擎并加载大模型,就能直接通过 curl 来访问,最多再部署一个 webui 就可以通过图形化界面来发起请求。
而如果是要做一个面向公众的推理服务产品则会复杂很多,要面临更多产品化的问题,需要保证产品的稳定、高效以及高质量的结果,这需要在推理引擎外围做很多工作,一个典型的推理服务会遇到下面几方面问题。
-
用户请求速率和推理服务计算速率不匹配,过多的请求堆积在推理引擎上导致服务器过载,推理延时增加,最终大量请求出现超时和失败。
-
推理服务器间负载不均,一些服务器超载,而另一些服务器资源空闲,整体吞吐较低。
-
推理请求处理时间长、耗时不确定,导致接入服务需要一直等待 http 请求返回,超时时间很难设置,存在连接异常断开导致请求失败的问题。
-
-
需要管理用户信息,根据用户需求定制 prompt,让问答结果更符合用户期望。
-
-
大模型训练数据有限,无法接触到私域数据,通过 RAG 模式可以以较低的成本获得更准确的结果,需要引入一个可扩展的向量数据库。
上面提到的推理场景问题可以概括为限流、负载均衡、异步化、数据管理、索引增强 5 个场景,通过云数据库 Tair 丰富的数据结构可以支撑这些场景,解决相关问题,下面我们针对每个场景来逐一说明。
要解决前后端速率不匹配,避免推理服务过载,保证推理服务的吞吐和 SLO,最好的办法是对接入请求进行限流。在传统服务中我们可以使用 Tair Incr 接口+过期时间来完成固定窗口的限流功能,伪代码如下。
ret = jedis.incr(KEY);
if (ret == 1) {
jedis.expire(KEY, EXPIRE_TIME);
}
if (ret <= LIMIT_COUNT) {
return true;
} else {
return false;
}
这是一种比较简单的限流实现,能限制一个用户在固定EXPIRE_TIME时间内的请求数量为LIMIT_COUNT,缺点是限流不够平滑,可能用户在限流窗口的第一秒内就把流量额度用完了,剩余时间内都无法请求。另一个缺点是这种方式需要用户不断地重试,可以想象一下一个人机对话机器人需要用户不停地重试请求,但服务端一直返回“系统忙,请重试”的体验是多么差。
稍微改进一下,使用令牌桶的限流逻辑可以解决限流不平滑,需要用户不断重试的问题,伪代码如下。
jedis.blpop(TIMEOUT, KEY);
if (jedis.llen(KEY) < MAX_COUNT) {
jedis.rpush(KEY, value, ...);
}
这段逻辑让请求端等待 TIMEOUT 时间,如果还是无法获取令牌则返回失败,避免了用户反复重试。如果投放令牌的时间间隔设置较小,限流也可以变大很平滑。
但这种限流方式没有考虑每个请求间的差异,不同的推理请求对服务器资源消耗差异很大,通常以生成的 TOKEN 数来评估资源的消耗比较合理,业务还可能有其他自定义的限流逻辑。为了实现复杂的限流逻辑,通过简单的 Tair 接口很难满足业务灵活的限流需求,所以业务会引入单独的限流服务,通过 Tair 来解耦接入服务和限流服务,用 Tair 完成消息通知。
简化后的推理服务框架如下图,用户请求进入无状态的接入服务,接入服务通过 Tair 将限流请求转发到限流服务上,限流服务通过判断 uid 信息、推理服务器负载,令牌状态等信息,结合业务逻辑和策略,让请求直接返回失败或进入排队队列,在排队成功或超时后将限流结果告知接入服务,接入服务通过限流的结果决定是否将请求转发到推理服务。
这里通过独立的限流服务可以根据产品需求来定制限流策略,完成任意复杂的限流逻辑。将 Tair 作为消息队列解耦了接入服务和推理服务,并完成了限流结果的实时推送。
为了满足逐步扩大的业务量,后台会部署很多服务器来提供请求,需要有一个策略来路由用户的请求到服务器上。常见场景下会使用 LVS、Nginx 等 4 层、7 层负载均衡服务来分发请求。
负载均衡的调用通常采用轮训调度(Round Robin)的方式或加权轮训的方式来分发请求,这些调度算法没有考虑请求的差异,也没有动态考虑服务器负载的差异。但在传统应用场景下,因为每个用户请求对服务器的资源消耗相差不大,而且请求执行速度快,消耗资源少,在请求数量足够的情况下,每个服务器的负载会相近,整体负载呈现均衡状态。
但在推理场景下,不同的请求对推理服务器的资源消耗差异很大,请求执行时间长,消耗资源高,如果采用轮询调度的方案必然会导致部分推理服务器过载、部分推理服务器资源闲置,无法使整体吞吐最大,也没法保证 SLO。
传统的负载均衡服务也有基于应用服务器连接数、负载等指标来动态调度的算法,但在推理场景这些算法的效果也不好。因为应用服务器的连接数不能代表负载,而负载指标是由负载均衡服务定期刷新的,没法保存实时更新。其次多个负载均衡服务器的状态同步也需要时间,多个负载均衡服务器将请求同时调度到一个负载很低的推理服务器上就可能导致这台机器超载。
总体而言,因为单个推理请求的资源消耗太大,个体差异太大,少量的调度失衡就可能导致负载相差很大,所以传统的负载均衡服务无法满足推理请求的调度要求。
-
一种是基于代价估算的方案,由一个调度器来评估每条推理请求的代价,这里通过一个小模型来快速估算该请求推理结果的 TOKEN 数量,然后在全局维度根据估算的代价来分发请求。该方案的优点是在分发请求时能考虑 kvcache 的亲和性,例如将多轮对话分发到同一台推理服务器上,复用之前的 kvcache,减少重复计算。但缺点是调度逻辑比较复杂,调度器需要考虑服务器负载、请求估算代价、kvcache 亲和性等指标,而且全局的调度器是一个孤点,可能存在性能瓶颈,也需要考虑高可用相关的问题。当估算模型出现偏差时还会导致负载不均。
-
另一种方案是基于拉取的模型,如下图,接入服务并不直接将请求转发给推理服务,而是将请求放入 Tair 的 stream 结构中,当推理服务空闲时,主动去 Tair 拉取请求,这保证了所有的推理服务都能在最佳负载运行。该方案的优点是实现起来比较简单,能够保证推理服务器在最佳负载下运行,同时推理服务器间负载均衡,整体吞吐很高。缺点是很难保持 kvcache 的亲和性,后续优化手段有限。
在复杂的模型推理场景中,由于推理耗时较长,推理时间无法确定等原因,接入服务同步等待结果会导致 HTTP 连接过多,连接异常断开导致请求失败等问题,使整体性能下降、失败率提升。
另一方面同步请求方式由用户发起提问,服务端推理完毕后返回答案,交互形式固定为一问一答模式。如果服务端能够合理判断用户的需求,主动发起对话,能够提升交互的体验。例如当用户询问北京景点后,他可能是想去北京旅游,那么他可能也关心北京酒店的情况。异步化让架构更灵活,简化后期扩展新业务的逻辑。
此时可以引入 Tair 作为消息队列,将推理结果写入 Tair stream 结构中,接入服务在请求提交成功后就能返回用户请求提交成功的状态,并流式返回推理结果,避免用户长时间等待。另一方面解耦了接入服务和推理服务的强绑定关系,其他服务组件也能向 Tair stream 结构写入数据,完成对用户的主动交互。
-
流式返回结果给用户,提升体验。
-
解耦组件间的直接关联,方便后续业务逻辑的扩展。
-
基于用户注册信息、用户历史对话信息能够为用户生成标签,这些标签作为用户请求时的系统 prompt 一起提交到推理引擎能够返回定制化的结果,做到千人千面,让结果更符合用户预期。
使用 Tair hash 结构能够以树的格式来存储用户的属性,支持点查和批量查询。
在多轮对话场景,需要存储之前的对话信息并作为 prompt 来影响新的请求,这让对话保持连续性。产品化上也需要保存一段时间内的历史对话内容,让用户能够查询之前的对话。
对话信息的保存非常灵活,可以有多种实现。例如通过 zset 结构保存历史对话,使用对话的时间戳作为 score,方便对话信息的排序,最早对话的删除以及按时间来查看历史对话。
如果服务本身已经通过 stream 结构完成了异步化改造,那么推理的请求和结果已经全部存储在 stream 中了,不需要再额外存储,stream 支持设置队列的大小,能够自动完成最早对话消息的删除。
不同的对话有不同的上下文信息,可以使用 Tair hash 来存储 session 信息,进行快速切换。
使用RAG(Retrieval-Augmented Generation,检索增强生成)能够有效解决传统大型语言模型(LLM)在实际应用中的局限性,包括:
相比于Fine-tuning(微调),RAG 可以有更低的成本和更高的数据时效性。
Tair 向量引擎是Tair自研的扩展数据结构,提供高性能、实时,集存储、检索于一体的向量数据库服务。支持HNSW 算法和暴力搜索,具备横向扩容和分布式全局索引的能力。能够解决 RAG 中数据索引的问题。
例如需要实现一个 Tair 接入 FAQ 的问答机器人,可以采用如下方案。
使用 Tair 作为 RAG 方案的向量数据库有以下好处
-
推理服务本身已经依赖了 Tair,不需要再额外引入一个单独的向量数据库组件。
-
Tair 向量数据库支持横向扩容,满足不断增长的业务规模。