之前的文章中浅析了大模型应用的技术栈,从用户到底层,每层中都有需要深究的东西。本文将专注介绍大模型部署层面的内容,从演进的视角来看大模型的部署过程中涉及到的工具和技术。
简介
模型训练好之后,最终一定是要对外提供服务的,那么就意味着需要将模型部署到服务端,并提供开放的接口来对外提供服务。接口层面一般遵循OpenAI的接口规范,模型服务一般需要提供OpenAI兼容的接口来让用户进行使用。那么,如何对训练好的模型进行部署呢?目前有哪些部署方式呢?本文将从部署演进的视角,来阐述大模型部署方式以及其中涉及到的推理优化过程。
模型部署
本地部署
Ollama
Ollama是一个开源的大型语言模型服务工具和框架,它简化了部署模型的流程,使训练好的大语言模型可以方便进行部署。 并且它支持不同的平台,也可以很灵活的进行扩展。对于端侧应用来说,是一个不错的选择。
ollama本身是CS架构,用户通过ollama client和启动的server通信,server端主要由两部分构成,api server和lamma.cpp构成。其中api server提供对外的api服务,lamma.cpp负责实际的模型推导。
ollama的好处是,它可以简单方便的部署到本地机器上,甚至不需要GPU,可以让模型在CPU上运行。这对于资源限制或者需要网络隔离的应用场景来说简直不要太好。但对于要求更高的网络服务来说,ollama的能力有点不够看了。
云端部署
K8s + ollama
有了ollama,那么在云端部署一个自然而然的想法是, 将ollama打包成docker镜像,然后通过k8s进行调度和部署,以满足云端部署及外部用户访问的需求。
这种方式在一些场景下是可以的(流量不大,用户不多),但既然我们想将它部署到云上,那么可能就得考虑下在云上工作的场景。此时的场景是面向成千上万的用户,有比较高的流量,此时面对的挑战是:
- 高吞吐量
- 低延迟
- 可扩展性
高吞吐要求能够同一时间处理更多的请求;低延迟要求处理速度尽可能的快,避免给用户带来体验上问题,扩展性则是要求能够随流量自动缩放资源,减少花费。一些实验结果表明:ollama在这些挑战上存在一定的限制。
ollama单卡性能测试,数据来源:参考文档1
OpenLLM
为了应对这些挑战,OpenLLM项目被提出。它是一个用于部署大语言模型的框架,它允许开发人员通过单个命令运行任何开源大语言模型(如 Llama 3.2、Qwen2.5、Phi3 以及更多)或自定义模型,使其作为与 OpenAI 兼容的 API。OpenLLM使用vllm作为其推理后端,提升其吞吐量并降低延迟,以应对上述云环境中面临的挑战;使用BentoML做为部署框架,以解决生产环境中部署所带来的一些问题,比如模型管理和版本控制,模型运行的可观察性等(通过打包成Bento以应对扩展性)。
使用OpenLLM和使用ollama的性能(单卡GPU) 对比如下:
openllm和ollama单卡性能对比,数据来源:参考文档1
推理优化
推理过程
目前的大模型的架构基本源于Transformer架构:
在这个架构之下, token是逐个生成的,当需要生成下一个token的时候,需要将之前生成的token信息作为上下文用于计算。很显然,这种处理方式效率很低,如果真的将其作为应用服务提供给用户使用,在大流量的场景下一定是满足不了上述挑战的。因此,通常需要对这个过程进行优化。
优化
连续批处理(Continuous batching)
为了实现高吞吐量,一个基本的想法是:批处理。当请求到达的时候,等待固定时间或者达到固定数量后,将这一批请求一次丢给推理后端进行处理。然而这种做法依旧存在问题:不同请求的处理速度是不一样的,那就意味着所有的请求需要等待最长请求的处理完成。这段时间里被占用的内存无法释放,处理完成的请求也无法及时回应。因此,更进一步的做法是:连续批处理。在连续批处理中,当当前批次处理中部分请求处理完成后,相应的内存会被释放,对应的请求处理结果会被返回;如果有新的请求过来,会被动态的加到该批次中。通过这种方式,提高了内存使用率和吞吐量。这种优化方式可以看作是从时间上进行优化,相对的,我们也可以从空间上进行优化。
批处理和连续批处理,图片参考来源:参考文档2。有加工
KVCache
KV cache可以看作是空间上的优化。KV cache主要发生在解码阶段(也就是是说只有存在解码器的模型才可以利用kv cache,像bert其实无法用上[4])。在Transformer解码的过程中,multi-head attention会包含Scaled dot-product attention操作,该操作会计算Q和K的矩阵相似度,得到注意力权重矩阵,然后和V进行加权计算得到最终的结果。在这个过程中,会重复计算之前已经生成的token的K和V, 为了避免重复计算,使用KV cache将其缓存下来,从而达到加速推理的效果。但这个过程中,由于无法预知请求需要提前分配的大小,会提前分配一块比较大的空间,而当该请求的任务结束之后,可能并不能立刻释放掉以为后续任务使用,由此可能产生一些内存碎片。可能需要更进一步的优化。
kv cache,图片参考来源:参考文档4,5。有加工
PageAttentioin
为了继续优化内存使用,首先需要了解运行时内存的分配情况。当实际运行的时候,内存中会包含三种类型的数据:静态数据、动态数据和其他数据。静态数据一般是模型的参数,这部分随模型加载而持续存放在内存中;动态数据则主要是跟随程序运行而逐步生成的动态数据,比如上面提到的kv cache;其他数据则是一些临时生成的数据。
对于kv cache这部分动态数据的优化,PageAttention技术将内存划分为block进行管理,每个block大小固定。在前面的场景中,当请求到来时,会根据请求的大小分配固定数量的block(如图左侧的block0~1),当生成过程中长度超出分配长度时,会分配新的block用于存储新的内容(block2),从而实现动态内存分配,减少内存碎片。同时,对于批量处理中的不同请求,当请求处理完成后,会及时释放掉该请求的block,从而使这部分内存可以被其他请求利用。另外,PageAttention中分配给请求的内存是逻辑内存,通过映射表映射到实际的物理内存。由于映射关系的存在,物理内存可以是不连续的。这也进一步减少碎片的可能。
PageAttention是推理优化中的一种重要技术,这里其实只提到最基本的一种内存管理过程,感兴趣的朋友可以继续深入了解。(比如其中涉及到的引用计数、Copy-on-Write等)
PageAttention是vllm的核心技术。通过PageAttention技术,高效的进行内存的管理,极大的提升了大模型服务的吞吐量和降低延迟。
总结
本文从大模型部署演进的视角出发,介绍了本地部署方案以及云端部署方案,并介绍了云端部署方案中面临的挑战以及如何使用技术应对这些挑战。文中涉及到的技术其实比较多,每一块其实都可以展开来单独再进行更细致的说明和研究。本文中沿推理优化的视角推进,仅描述技术中的相关部分,更侧重建立一个整体上的认知。
转载请注明出处~