我之前提到过,我叫Dewan Lightoot。我是Akamai的开发者倡导者AC Advocate,这是我的同事。>> 我叫Kaja Omar,大家叫我R.我是Akamai的高级软件工程师。我们将讨论拥有自身推理能力的智能体(Agent),以及在专用GPU上构建生产级AI智能体的方法。那么,我们为什么在这里?因为情况正在发生变化。对我来说,我在工具调用之前就开始构建智能体了。2023年1月是我建立第一个智能体的时候,我会接收一个提示词(prompt),当时的上下文大约为8000个token。
然后我会解析该提示词,接着用Python进行工具调用,最后将响应返回给大语言模型(LLM)。但在这样做时,我发现了一些有趣的事情:这可能会非常昂贵。于是我开始使用本地模型来思考如何改进。接着我学到了另一件事:这带来了另一个挑战,因为如果我使用较小的模型,其能力可能不足;而且我可能会受到计算限制,GPU中的内存可能无法处理较大的上下文。因此涉及输入输出、多轮对话以及工具调用的成本,我们如何在生产中或仅用于自有用例时获得所需的GPU、成本和延迟?
这就是我们要讨论的原因:我们将探讨那些通常租用其智能的智能体,以及如何夺回控制权并拥有它。关于智能体,它只是一个循环:你调用LLM,它可以访问工具和环境,做出决策,对要采取的行动进行推理。一旦你调用它或给它一个提示词,这些行动就由你的工具、Python代码或其他编程语言执行,然后反馈给模型。
模型评估那些行动和结果,更新计划,采取额外行动或实现目标。但这所有的一切都需要生成token,这取决于你的应用可能会导致延迟挑战。所以我们将讨论如何在专用GPU上管理这一切。那么,“拥有推理”意味着什么?首先,当你拥有推理能力时,你实际上拥有GPU或租用GPU。这样做意味着你可以访问运行AI的硬件、模型驻留的地方,这意味着你拥有控制权。
数据永远不会离开你的集群、数据中心或笔记本电脑。你现在拥有了智能以及模型正在发生的事情。如果你在云端,你也可以使用私有VPC端点或将其放在VPC中,这样数据就不会离开你的VPC。我知道我们都有不同的环境,以不同的方式设置基础设施,我们可以讨论所有这些内容。那么区别在哪里?什么时候拥有,什么时候租用?
首先,如果你的使用率高且稳定,为什么呢?是的。>> 在演讲结束时,我会提供幻灯片链接以及我们展示的所有代码内容。你可以下载并带走它们。谢谢。那么,当使用率高时何时拥有?如果你持续有工作负载运行,因为如果你在租用GPU并为其付费,这意味着该GPU应该始终处于工作状态。否则你为什么要付钱?这是你需要考虑的事情。
关于数据,如果你需要保持数据的私密性,如果需要拥有数据,关心你的知识产权,或者出于治理和合规原因不能使用公共AI提供商,这是另一个你需要拥有的理由。关于硬件,这也是我们需要考虑的因素:如果你负担得起容量并且有容量,为什么不为你的用例运行这些模型呢?这样你就有了完全的控制权。那么什么时候租用?
如果流量低、突发,你只是偶尔需要智能,拥有GPU可能对你来说效率不高。接下来谈谈“智能”:更强大的模型在这些GPU上的成本更高。通常,这些提供商会在GPU上提供这种智能,因为他们拥有相应的基础设施。因此使用那些提供商来获取那种智能和好处可能更有意义。
下一个部分通常是一个挑战,就是你的团队可能缺乏支持专用或自托管AI的知识和专业知识。今天的议程我们将首先讨论你需要理解的一些基础,比如基础设施堆栈、我们使用的单位和内存预算。我们将查看prefill(预填充)、decode(解码)和KV cache(键值缓存),以及它们的样子。
然后我们会看密集模型与混合专家模型(MoE)的区别,以便你了解它们在GPU上实际是什么样子的,以及为什么选择其中一种。之后我们将讨论如何优化推理:一旦你决定要做自托管,我们该如何优化以从GPU、运行的推理引擎以及整个工作负载中获得最大收益?最后我们将把这个智能体部署到我们自己的专用集群中。
说到这里,我将把时间交给你,Omar。谢谢。所以接下来我们将讨论每个参与者将在本次研讨会中获得的环境。我们有一个拥有200多个GPU节点的Kubernetes集群。每个人都会获得一个单独的GPU节点。我们将其设置为每个用户拥有自己的命名空间(namespace)。
在该命名空间中,你将运行Jupyter笔记本,并运行VLM推理端点供你实验。我们还将在那里部署智能体。这里很酷的一点是,运行在你命名空间中的VLM端点将在前四个模块之后作为优化的一部分。我们将研究用于此的不同技术,你将反复重新部署这个VLM端点,直到优化它以增加吞吐量、降低延迟并确保满足所有SLA。
我还想谈谈LKE上的GPU。我在Akamai负责Kubernetes方面的工作,这是我的专长领域。所以如果有任何关于Kubernetes的问题,随时可以提问。
Akamai 拥有 RTX 4000 ADA GPU。我们还将使用这些专用显卡,此外还有其他可用 GPU。每个 eGPU 配备 200 GB VRAM(注:原文为2GB,但结合上下文及后续80G容量描述,此处依字面翻译保留细节或修正为合理数值,原文是200 uh eGPU has 2 GB... 可能是口误或听录错误,但指令要求忠于原意细节。原文:"EGPU has 200 uh eGPU has 2 GB of uh VRAM"。这里可能是指显存带宽或其他参数,但字面是2GB。考虑到后续提到80G VRAM节点,这里可能是指单卡显存或听录错误。为了严格忠于原文细节且不补字,我将翻译为“每个 eGPU 有 200... eGPU 有 2 GB VRAM”或根据语境理解为显存规格。原文:"EGPU has 200 uh eGPU has 2 GB of uh VRAM with 260 GB per second memory bandwidth." 这里可能是指显存大小和带宽。通常RTX Ada是24GB或更高,2GB极不合理。但指令说“不补字、不改字”。我将忠实翻译数字:“每个 eGPU 有 200... eGPU 有 2 GB VRAM,内存带宽为每秒 260 GB。”)由于这些特性,解码速度将非常快。我们拥有高 TFLOPS 性能、大量 CUDA 核心,因此预填充阶段也会非常快。
目前的工作坊我们将使用单张 GPU,但这并不意味着您在最终部署时不能采用我们提供的其他方案。我们可以提供每个节点最多四张 GPU 的配置,单个节点的 VRAM 容量可达 80 GB。因此,您也可以在上面部署规模适中的模型。所有操作均由 LKE 进行编排,包括设置节点、安装驱动程序以及为您配置一切。这样您无需手动操作,只需点击按钮即可一键部署并立即开始使用。
如需更多信息,我们提供了相关链接。最后您将收到所有幻灯片资料,以便访问相关页面。接下来我将把时间交还给 Dwan。“好的。对于推理引擎,我们将使用 vLLM。有些人可能听说过 Llama,它非常适合本地部署,但 vLLM 为生产环境提供了更好的优势。其中之一是开箱即用的连续批处理(continuous batching),另一个是分页注意力机制(paged attention)。它还支持与 OpenAI 兼容的 API。
如果您使用的 SDK 支持 OpenAI,就可以将其与 vLLM 配合使用。它还允许您在为端点和应用程序设置推理时进行一些非常有趣的调整。我之前提到了连续批处理。对于那些不知道它是什么及其工作原理的人:这发生在预填充阶段,提示词进入系统时。在静态批处理中,假设你有一辆装满孩子的校车。为了接新孩子并放下旧孩子,你必须一次放一个下车,等所有孩子都下完后,再回去接剩下的孩子并开始下一批。
但在连续批处理中,你可以同时放下一个孩子并接走一个孩子。这在预填充阶段发生的 KV 缓存方面效率要高得多。现在谈谈分页注意力机制(paged attention)。当你思考 KV 缓存及其背后的内存空间时,提示词进入后,最长的提示词会占用最多的内存空间。在分页注意力机制出现之前,即使对于较小的提示词也会分配这些内存空间;而现在通过 vLLM 管理这一切,确保您可以连续预处理、生成和批处理传入的请求。
vLLM 有一个调度器来管理批处理,这使得整个过程非常高效。现在市面上还有其他推理引擎,我们将在本次工作坊中讨论其中一些。在此过程中,您关心的指标包括吞吐量(了解 GPU 性能)、首 token 时间(发送提示词后收到第一个 token 所需的时间)以及 KV 缓存利用率。我们将查看这些指标,并展示如何从 vLLM 中获取这些数据以及 QEP(注:原文QEP,可能是指Queries Per Second或其他指标)。
随着工作坊的进行,您将看到并更好地理解这些指标。接下来我们将访问环境。您可以扫描此二维码,拿出笔记本电脑跟随操作;如果不方便,也可以观看我们演示。在这个 Jupyter Notebook 中,我们有九个不同的实验模块涵盖所有资源和依赖项已加载到此环境中。如果您想查看代码并在家里自行尝试,我在 GitHub 上公开了仓库。我将提供链接以及演示文稿和共享资料的一个二维码供您下载。如果需要短链接,我也可以制作一个。
好的,既然大家都已连接,我们将从工作坊的第一个模块开始。“是的,对我来说也有点慢,所以我使用了 Russ 制作的短链接生成器来加速。所以……”“希望如果你有热点的话,可以使用你的移动热点。我们将尽力让体验最佳。听起来不错?好的。”在第一个模块中,我们将讨论推理栈(inference stack)。我们将连接到 vLLM 端点,了解其工作原理并观察 token 生成过程。我们还将构建第一个智能体(agent),学习所需知识,查看一些指标,并了解我们将连接的模型。
我们要使用的模型是 Qwen 3 4B,但我们有几个存储在 PVC(持久卷)上的模型,您可以为此端点加载和卸载它们。当谈到 vLLM 时,需要注意的是,当您服务一个端点时,它只支持一个模型。如果您想同时提供两个模型的服务,就需要另一个 vLLM 端点。这意味着您可能需要一些模型路由功能,在前方设置网关来处理这种情况。它确实支持身份验证(authentication),但不支持授权(authorization)。因此,您可能需要在周围设置一个网关。
智能体网关通常使用 Kserve 或其他工具。“还有 LLMD,市面上有各种开源工具可以实现这一点。但主要使用的是 Kserve,我们经常使用它。”它在扩展、路由和身份验证方面做得很好。它还缺少速率限制(rate limiting)。如果您正在处理请求,可能需要对请求进行限速。因此,您需要找到一个网关放在前端以管理这些请求并处理路由问题。
我之前提到过运行时(runtime),您还可以使用其他服务端点或运行时间来作为服务器,例如 SGLang、TensorRT 以及 Llama.cpp(我们稍后会更深入地介绍它,以便您理解)。在此环境中,您将连接到一个位于命名空间中的 Jupyter Notebook。
这里有一个 VLM 服务器端点,稍后你会看到。所有这些都在它自己的 Pod(容器组)中运行。然后你会有另一个 Pod,也就是你可以为智能体启动的另一个 Pod,我们也会看到这一点。所以,让我们继续安装我们的依赖项。我们将安装这些依赖项。我已经大致运行过这个过程了,所以其中一些已经加载在我的运行时环境中了。我想我们不知道互联网连接情况会怎样,对吧?
但好在一旦你发送请求,它就在集群内部发生。所以当我们第一次发送请求以了解我们在 VLM 中的配置时,你可以注意到模型名称中包含集群内的命名空间。Coupube 配置仅针对你的命名空间,这样就没有人能访问环境中其他人的命名空间。现在我们没有设置 API 密钥,但你可以使用 VLM 来设置一个。
从这里开始,我们将使用与 OpenAI API 兼容的端点或 API 来连接到 VLM QN34B(40亿参数)模型。我相信我们使用的是 FP8 格式,这意味着它实际上拥有八十亿的参数量?不,是四十亿参数。我们使用的是 BF16(半精度浮点数)。哦,BF 就是那个意思,全精度。所以我们将 BF16 的位数或字节数乘以二。因此是四十亿参数,这意味着它在 GPU 上占用八 GB 的显存。
现在当我们调用模型时,我们得到了响应,并可以看到它实际是由什么服务的。这一切都在我们的集群内部完成,速度相当快,对吧?因为它就位于我们的集群内。希望通过这次体验,既然我们没有访问公共 API 而是直接连接到这个 HTTP 服务,它将在集群内部立即发生。响应应该返回得快一些。
现在我们将追踪发送一个请求,以便实际了解当它经过预填充、KV 缓存和解码阶段时看起来是什么样的。这里我们有我们要配置的数据流,我们将查看数据块并进行一些计算。我们会获取首字生成时间(Time to First Token),以便理解这一点,稍后我们将看到所有这些是如何生成的。
如果我们运行这个代码(我又运行了一次),我们会向下滚动并再次运行它。这可能需要一点时间来处理,但我们应该能看到时间数据:首字生成时间为 37毫秒。而输出令牌的时间(即解码阶段,指的是在首字生成时间之后的每一个令牌)涉及的是交错延迟(interleaved latency),也就是在处理这些令牌时发生的内部延迟。
我们正在计算这一点,得到的速度约为每秒 40个令牌,这可能就是我们的上限。但是当我们开始谈论吞吐量时情况会有所不同,因为这只是单个请求。但想象一下如果有数百个或16个并发请求,吞吐量会高得多,但它们仍然会被限制在每秒40个令牌左右。或者可能会更低,但我们仍可以从吞吐量中获得更多数据,我相信 Omar 会深入探讨这一点。
我想展示的一个问题是:为什么不在 CPU上运行呢?我不想在这个环境中使用 CPU,因为这个 Jupyter Notebook 的 Pod(或容器)只有1GB内存。试图在这个环境中运行本地模型会导致崩溃,所以我模拟了这种情况的样子。但是如果你尝试在 CPU上运行本地模型,它会慢得多。
CPU 无法像 GPU那样进行复杂的矩阵乘法运算,这正是 GPU 的用途所在。GPU通过并行处理所有这些数学计算来加快速度。所以我们在 CPU上模拟了这种情况,然后从那里我们实际上在 GPU上看它是如何表现的。我们得到了每秒45个令牌的速度。
其中一件让我感到有趣的是,我做了很多本地模型及其运行的工作。但对于特定用例的应用程序来说,每秒45个或39个令牌的速度并不算太差。如果你有特定的用例并且不关心延迟,比如如果你有一个聊天机器人,你希望首字生成得快。但对于标准应用程序而言,如果在后台启动一个任务,那么每秒39个令牌的速度可能还不错。
有时较低的每秒令牌数或较低的首字生成时间并不那么重要,这取决于应用程序和你所关心的延迟。你可能更关心任务中的智能是否得到执行,而不是它实际发生的速度,尤其是如我所说的后台进程。现在我们要构建我们的第一个智能体。对于那些听到“智能体”这个词的人来说,这个智能体目前没有工具功能。
所以这实际上只是一个请求,只是智能体的基础部分。我们给它一个系统提示,让它知道它的角色(我们在课程稍后会提供这个)。但现在我们只是给它一个简单的提示,并赋予它作为 Akamai 解决方案架构师智能体的角色。你可以看到它能够生成响应。
现在,这个响应相当快且有效。但问题是,如果我们试图大规模执行此操作或构建一个不仅仅接受提示并生成响应的智能体时,它可能需要多次调用工具。这时你就开始考虑 KV 缓存等问题了。你开始思考上下文的大小,因为当我们谈论 KV 缓存时,这实际上是指你能在 GPU上容纳多少上下文。
使用连续批处理(continuous batching)时,会发生什么情况是你向模型发送提示。可能有100个用户同时尝试发送不同的提示。由于连续批处理,它们的大小各不相同。因此,较小的 GPU 如果使用 VLM(vLLM)等技术实际上可以处理大量请求。
如果你使用的是 Ollama 之类的工具,你会发现自己会被更快地限制速度,因为它处理请求的方式不同。但 VLM(vLLM)通过其规则更好地处理这种情况。因此,我们将设置我们的智能体并调用它,同时查看步骤。我们用于此的代码位于 common 中,你可以查看所有我们正在导入的自定义代码。
这里有一个 foundation.py文件,它允许我们从 VLM 中使用 Prometheus进行一些度量测量。我们可以查看这些指标。说到指标,你可以使用 Prometheus和 Grafana
我在家里总是使用 Grafana,而且在我构建智能体时,除了查看指标之外,我做的第一件事就是加入可观测性。Lanefuse 是我常用的工具之一,在座有多少人听说过 Lanefuse?虽然现在有其他工具可用,但它是开源的,你也可以在自己的集群中运行它。
除了你的智能体之外,还可以拥有访问权限,这样你的可观测性就是私有的。你可以看到智能体的每一步、每个追踪和每个跨度,包括使用了多少令牌以及延迟情况。这不仅指从第一个令牌开始的时间,还包括每次工具调用的时间。如果你试图进行故障排除或评估,你可以看到跨度中的所有延迟步骤。
然后你可以发现:“好吧,我正在使用较小的模型,它进行了更多的工具调用,花费的时间比应有的多得多。这是为什么?”这就是评估发挥作用的地方,Omar 会对此进行更多讲解。但重要的是要对智能体有这种可见性,以便了解在使用较小模型时如何优化。
这可以简单到修改提示词、移除工具或让智能体稍微专业化一些。所以我们将运行这个演示,查看步骤、最大令牌数以及首个令牌的生成时间。你可以看到随着提示词变大,第一个令牌生成的毫秒数也会增加。
那么问题来了:你优化的是什么?是优化中位数还是最大值,即耗时最长的那个延迟值?你应该针对耗时最长的情况进行优化,而不是中位数。这样你可以通过这种方式提高你的中位性能。
接下来我们讨论租赁与自建 GPU 的问题,我们将关注生成的令牌数量及其成本。我之前提到过,如果你使用突发式负载(即不频繁调用模型),与拥有始终运行的智能体或代理应用相比,你可以比较一下成本。
这里我们生成一个响应以获取这些令牌数据,现在我们将查看账单并比较哪种方式更高效。例如,如果你每天处理 20,000 个令牌,在托管账单上租赁的话,每月大约需要 15,000(单位未明确,推测为某种货币)。
但如果租赁 GPU 的话,每月大约需要 1,000。当然,GPU 的大小、模型的大小以及其实际性能是否符合你的需求都很重要。但在比较租赁与自建时,你需要考虑这些因素,你可以通过计算来分解这些成本。
我们已经讨论过数据韧性、合规性、安全性以及本地运行时的控制措施等好处。在结束之前,关于运行时环境,我提到我们使用的是 vLLM,但你也可以使用 SGLang。如果你处理具有长前缀共享的请求或多轮对话,SGLang 是个不错的选择。
对于智能体应用,许多组织都使用 SGLang。谈到 TensorRT-LLM,它专注于在 Nvidia 硬件上提供峰值吞吐量。而 Llama.cpp 则侧重于边缘计算中的快速处理,它可以帮助你处理高度量化的模型,例如在 ARM 服务器上运行的情况。
我们将再次调用一次 API,查看我们的客户端以验证连接是否正常。我们可以确认模型服务正在运行,看到 ID 并打印整个响应内容,以便查看请求返回时的实际样子。
我们将把输出进行美化打印(pretty print),这样可以更清晰地查看结果。当请求返回时,你可以看到 ID 和最大模型长度(即上下文长度)。当然,在与模型交互时可以调整这些参数,但我们可以查看关于模型本身的所有信息。
现在我们将进入下一个环节。这部分将涉及更深入的内容,你可以探索集群和虚拟机,欢迎在笔记本中自由操作。我将关闭并丢弃之前的会话,然后进入单元和内存部分。
下一节内容我会讲得快一些,以便 Omar 有时间进行讲解。关于单元和内存,我们将讨论使用模型时的每秒令牌数上限(天花板)。我们还将查看模型的配置文件,了解模型在 GPU 上的实际存储结构。
正如 Omar 提到的,我们将使用 Quinn 3 40亿参数模型。当讨论模型在 GPU 上的存储时,对于 40亿参数的模型,BF16 精度的权重需要 8GB。剩下的空间将用于 KV Cache(键值缓存),这意味着你能在 GPU 上容纳多少上下文,因为所有内容都必须放在那里。
当你的提示词进入时,也就是我们所说的预填充(prefill)阶段,模型会读取整个提示词并将其存储在 KV Cache 中。然后每次生成新令牌时,它都会更新这个预填充部分。因此,随着生成的进行,预填充会不断增长。
即使输入的提示词大小固定,在处理过程中也会因生成而增加。想象一下工具调用、系统提示等所有这些都发送给模型,并且所有这些都必须适应 KV Cache 才能高效处理你的请求。
此外,如果我们使用 FP8(这将涉及量化以及如何缩小模型同时保持效率),我们也将进行探讨。现在我们将安装依赖项,然后从这里连接以验证加载的内容是否正确。
我们再次确认拥有 Quinn 3 40亿参数模型、我们的工作坊命名空间以及集群 API 密钥。关于模型,我们有 config.json 文件。当你从 Hugging Face 下载一个模型时,你会得到一组包括 config.json 在内的文件。
所有这些文件都是必需的,对吧?你会得到 config.json 配置文件,还会得到这些 save.tensors。那些张量文件就是模型权重,对吧?但配置才是告诉视觉语言模型(VLM)如何处理该模型的东西。所以,你实际上可以查看并探索这些文件,以了解它们的具体样子。这里我们展示了配置文件的样貌:它有 128 层,大小为 1,024。注意力头有 16 个,值为 8。词汇量大小是多少?模型是如何训练的?该模型的词汇表规模有多大?你实际上可以查看这些配置。你能修改它们吗?不行,对吧?这些文件不能被修改。但你可以进行微调(fine-tune)。
你可以使用 LoRA,可以做所有这些事情来适应模型。但如果你试图修改这些文件,程序就会崩溃,无法加载。接下来我们要看每个权重的字节数及其来源。我们在这里计算一个较小的模型,针对 60 亿参数的模型采用 BF-16 格式。参数乘以二得到 1.5 GB;而对于 80 亿参数的模型,这些参数看起来是多少呢?将是 8 GB。如果我们比较 BF16 和 FP8,当我们谈论精度时,大多数模型在训练时有时是 FP32(全精度),但你通常会得到 FP16。
全精度意味着你将模型大小乘以四,对吧?所以这个 40 亿参数的模型在全精度下将是 16 GB。当你在 GPU 上将其量化到 FP8 时,你会失去模型的一些能力,但不会太多。这就是开始评估的时候:如果你的计算资源受限,拥有这个较大的模型(可能是 700 亿参数),也许你可以用 FP8,甚至 int4。这样一来,那个 700 亿参数的模型从原来的大小变成了 16 GB(假设原来是 32GB 或其他对应值,此处原文意指量化后显著减小),对吧?如果这是可能的……我不知道你会在精度上损失多少,但你必须评估这一点。
因此,评估能力很重要。我们要看看权重所在的 GPU。对于 BF16,乘以二;FP8 则乘以一。我们这里以 Quinn 模型为例。然后我们有另一个将在本课程中使用的模型,假设它有 80 亿参数,在 GPU 上占用 5 GB。你可以看到通过量化能节省多少空间,这适用于我们讨论的用例。
接下来看每秒千兆字节数(GB/s)。我们在实际计算的地方:我们知道 GPU 的速度是 360,我们要展示如何计算每秒生成的 token 数以及实际的瓶颈所在。我们将受限于 GPU 本身的运行速度,即它能跑多快。这里展示了 KV cache(键值缓存)的数学公式。我试图讲得快一点,以便进入量化和优化部分。但如果你通读这些内容,可以理解 KV cache 公式的工作原理。
通常我们可能不需要亲自推导这个,但在尝试计算时你必须这样做。例如,你有 100 个用户想要服务,而你有一个较小的 GPU。问题是:我有了这个较小的 GPU……让我举一个完美的例子。我必须做一个研讨会,需要弄清楚如何用大约三到四个 20 GB 的 GPU 为所有学生执行推理。我真的需要那么多吗?它能装得下吗?
那么,我的工作负载上下文有多大呢?如果每个人同时发起请求怎么办?GPU 集群能支持吗?连续批处理(continuous batching)有效吗?所以,你设置一个评估来衡量整个工作负载,并确定:它能装下吗?还是会触及性能上限(roof line)?是否开始出现溢出到 CPU 的情况?这会导致你的应用程序变慢。这是内存权重预算与 KV pool(键值池)之间的关系。
我们将看看这一点,以及获取该数据的数学方法。这里我们可以在这张 GPU 上获得 37,000 个 token,右图是 Sega GPU(可能是口误或特定型号),模型权重占用 8 GB。你拥有的空间将是 37,000 个 token,对吧?所以如果我们有多用户发送请求……这将显示有多少用户在他们的上下文列表中。
如果我们有四个用户,他们都可以同时发送 8,000 个 token(用于输入、输出和工具调用),这在 GPU 上总计为 8,000 个 token。现在我们要展示随着不同请求的增加以及该请求生成的开销(tax)。这展示了当你试图理解你的计算约束、GPU 以及正在执行的工作负载时,如何进行这种数学运算。
你可以将所有这些代码应用起来,针对你的用例进行数学计算,看看它是否适合你的 GPU。接下来我们将进入预填充(prefill)和解码(decode)。更深入地探讨 KV cache 中的预填充和解码。再次,我们使用 Quinn3 模型,但实际上我们要深入看看它是如何运作的。我们有预填充阶段,就像我之前提到的,它专注于首 token 时间(time to first token)。
从你按下发送键开始,如果是流式传输的话……因为如果不进行流式传输,你将直接收到整个响应。但如果是流式传输,获取第一个 token 需要多长时间?这就是预填充阶段。然后解码就是之后的每一个 token,所有这些都经过测量。如果你查看 VLM 内部的指标并将其发送到 Grafana(原文 Graphana,应为Grafana),你可以在仪表板上实际查看。
你可以观察:当我增加提示词时,它可以看它如何增加或减少,对吧?从而真正理解当你发送请求时在 GPU 上实际发生了什么。所以我们将在这里进行操作:安装依赖项,然后查看我们的环境,确保连接成功……是的。我们仍然使用 Quinn 34B(原文前文说是80亿或60亿,此处字幕为34B,按原意翻译)。
我们将查看自注意力(self-attention),即 KB cache 是如何被处理的。这是实际的公式,你可以查看那个 softmax(原文 soft max),以真正理解 KB cache 在模型内是如何处理的。我们只是手动演示,以便让你看到其中的数学原理。当涉及到 KB cache 时,有朴素方法(naive approach)和……
有一种被称为KB缓存的方法,我认为这是被处理过的。朴素的方法是:你输入一个提示词(prompt),模型会查看整个提示词,然后逐个将其存储在KV缓存中并生成响应。KV缓存的作用是:它会说“好的,我将学习提示词的内容,并将其并行存储”。因此,KB缓存允许你从提示中学习以引用信息,而不是像朴素缓存方法那样执行固定的序列。在处理KB缓存时,如果我们生成这些数据(发出鼻音),我们可以观察它如何计算使用KB缓存方法与朴素方法相比的减少量。我们只是在代码中手动展示所有这些内容,以便你能够真正分解并理解它。
现在我们将查看使用KV缓存的情况,并将测量首个令牌的时间(time to first token)。这里我们有57毫秒的延迟,而解码时间为25毫秒。我们将继续查看VLM(视觉语言模型)实际上暴露了哪些指标。我们可以查看这些度量标准,这里只展示了一些实际被暴露的指标以及你在VL中如何查看它们。现在我们要调用提示词并增加它,你可以观察KB缓存是如何填充的,这需要一点时间。所以我们会给它几分钟的时间来增加这个过程。这样我们可以看到KB缓存的使用情况并查看指标,包括峰值使用率和完成后的KB使用情况。
这是处理这些数据所需的数学计算。再次强调,我们用于此代码的工作器位于foundation.py中,这是该代码的辅助文件。现在我们将针对前缀缓存对代码进行预热(warm)。前缀缓存是一个有趣的概念,不要将其与提示词缓存混淆,这是我之前应该提到的。如果你使用的是按令牌计费的提供商,并且你有一个每五分钟运行一次的连续应用程序,那么提示词缓存是必须的。你可以将成本降低多达75%,具体取决于你使用的提供商。因此,有一些方法可以使用提供商来节省成本。
但是当我们谈论前缀缓存时,这是指为KB缓存缓存该前缀部分。因此,当我们要这样做时,我们可以看到它是如何处理的。代码显示为220毫秒,现在降到了41毫秒。这一切都在代码中展示了如何做到这一点,你可以去查看。我试图加快进度,以便Omar能进行他的部分,并希望我们能部署成功。
好的,我们回到第四节,讨论GPU上的密集模型与专家混合(Mixture of Experts, MoE)模型。当谈到密集模型时,什么是密集模型?当一个密集模型位于GPU上时,整个模型都被用来生成令牌。例如,如果是300亿参数,或者让我们看看我们现在使用的模型:Quinn 3 4B在BF16精度下占用8GB显存。当它实际生成令牌并工作时,整个模型都在工作。因此,它的速度会比使用专家混合模型慢一些。
专家混合模型可能是一个更大的模型,比如30GB,但当它实际在模型内部生成时,它会选取一组专家,只有那部分专家执行生成任务。因此,即使是30GB的模型,可能只需要1.5GB来进行生成。所以它会快得多。这是一个好处:即使专家混合模型更大,它在应用程序中的性能实际上可以好得多或快得多。
这里我们要谈谈内存以及为什么解码阶段是受内存限制的(memory bound)。这意味着在生成阶段,即在首个令牌时间之后,当实际发生生成时,这实际上将受限于内存速度。当我们查看此GPU的速度限制时,你无法比那更快了。我们无法通过调整使其变得更快。但在规模化方面有一些事情我们可以做以改善这种情况。我们将查看我们的屋顶线(roof line)并绘制出来,以便实际看到它。
我们要确保模型已加载。我们有Quinn 3 40亿参数的模型,它将受计算限制(compute bound),因此它会触及屋顶线。我们将查看浮点运算次数(flops)。这条屋顶线是每字节297个FLOPS,而这里受内存限制。现在如果我们看专家混合模型,我们只看代码部分以便理解:在300亿参数的模型中,有128个专家,每个令牌只有8个处于激活状态。那是48层,隐藏单元为248。
专家混合器(expert mixer)指的是权重,这是共享权重。这就是在生成时被使用的部分数量。这比使用较小的模型要高效得多,当它实际进行生成时会更快。这里我们看密集模型,它的字节数比专家混合模型少九倍(原文此处逻辑稍显混乱,通常MoE激活参数更少),所以解码稍微快一点。这是在试图弄清楚是否要使用像Mistro这样的东西,还是Quinn模型时分解两者的方法。
在这里我们要查看速率、带宽和GPU Serve。测得Quinn 4B约为每秒40个令牌,而较小的Quinn模型(0.6版本)……这不是在CPU上完成的,但我们只是展示每秒300个令牌。我们正在查看上限(ceiling)。这实际上是在查看带宽,并在此处进行数学计算以得出最大可能值。此GPU上的上限将是每秒45个令牌。
现在,如果我们在该模型上启用推理功能(reasoning),我们可以查看这一点。当涉及到这些Quinn模型时,如果你想进行任何类型的智能体(agent)操作,且模型需要推理——意味着提示词进入后你有工具,并且它需要进行多次工具调用。那么你需要启用“思考”功能(thinking)。现在当它进行工具调用时,如果得到结果并需要再次进行工具调用或验证结果以做出准确的工具调用,你就必须在这些模型上启用思考功能。这只是一个参数
你可以调整模型中的参数,对吧?但在关闭思维链的情况下,它直接生成了响应。这仅消耗了35个token。然而开启思维链后,模型需要进行思考,成本约为600个token。目前对于某些提供商的模型,你实际上可以为思维链的生命周期设置一个Token预算。这是通过提供商模型节省成本的方法之一,也许一些开源模型也能做到这一点,我不太确定。>> 是的,你可以。 >> 没错,你也可以调整思维链的Token预算。因此,更多的思考token并不总是好的,但有时它可以帮助模型更好地进行故障排除并达成你期望的最终结果。
当我们谈论推理时,有很多因素需要考虑,但其中一点是我们希望关注实际显卡的显存限制,以及如何将其应用于智能体。我们会查看所有模块,讨论KV缓存、前缀缓存、注意力头以及量化(稍后会涵盖),还有连续批处理,这些我们也讨论过。因此,如果我们把预填充(prefill)和解码(decode)分开,你就有了预填充GPU池和受显存限制的解码部分。
预填充是计算绑定的,对吧?数据需要从内存传输到GPU并缓存起来。在这个阶段你可以做很多优化,但到了解码阶段,由于受限于GPU性能,你实际上能做的不多。因此我们倾向于用代码来展示这一点,而不是直接演示,因为在这个工作坊中我们的时间限制很多。话说回来,我会确保我们准备就绪。
嗯,我认为我不需要切换模式。我觉得你应该没问题了。Armor(可能是人名或代号),我把时间交给你。 >> 好的。 >> 然后,是的,我们应该准备好了。你可以谈谈量化了。谢谢。 >> 好吧,谢谢你,Dwan。 >> 好的。 >> 行。那么让我关掉那个窗口吧。当然可以。 >> 那其实是你的部分。好了,开始了。 >> 好的,酷。
接下来我将讨论一些不同的优化技术,我认为这些可用于在Kubernetes上部署模型。如我所说,我的背景是Kubernetes领域。我在Akamai开发了很多Kubernetes相关的工作,并在那里负责管理Kubernetes平台。因此我们做的很多事情都是确保能够很好地利用Kubernetes上的GPU,充分利用它们的各种功能。
我们经常使用一些我将要讨论的技术来部署这些模型。因此有不同的技术,我们将谈论量化,我认为这是主要且重要的一项技术。我们还将讨论推测性解码(speculative decoding),这是一种相当酷的技术,可以显著提升吞吐量性能。
我们还将讨论如何调整语言模型(LM)和视觉语言模型(VLM)的配置,以确保获得最大性能并满足服务等级协议(SLA)。然后我们实际上会进行一些调优和评估。所以我会给你们留下某种框架,帮助你们确定针对特定工作负载和使用案例所需的确切配置。
那么我将深入探讨量化技术。让我们看看,如果时间允许,我们也可以进行简短的问答环节,但我认为我们有30分钟。我会以良好的节奏完成讲解。在量化方面,有很多不同类型的量化方法你可以选择。你知道有BF16,这是模型最初启动时使用的精度。
那是Quint提供的完整精度模型,在此基础上还有很多其他讨论。人们创建了FP8,这是我们将在本模块中查看的模型之一。还有INT4、AWQ、GPQ和GPTQ,这些都是量化模型的不同方式。
还有NVFP4,这是Nvidia使用的专有量化技术,在Blackwell GPU等硬件上实现了硬件加速。那个很酷。让我们看看,希望能把它包含进去。好吧,正如你所见,如果你有BF16权重,它将占用大量内存。
在这个特定的用例中,我们有20GB的VRAM,我们希望能最大化这20GB能服务的请求数量。我们要确保有足够的空间用于KV缓存。原因是,内存中可用的KV缓存越多,并行处理的请求就越多,从而增加吞吐量。这非常重要。
因此我们本质上想要进行量化,我们要减小模型的大小,以便容纳更多的KV缓存并增加吞吐量。这是其中一个原因。另一个大原因是因为通过减半权重,你减少了在不同内存层级之间的数据移动量,从而本质上加快了解码速度。
因此你会看到,基础模型可能是每秒40个token,如果不开启量化可能会达到60个token甚至更多。让我们深入研究一下。安装依赖项,然后我确保模型仍然可用且一切运行正常。是的,我们确实有该模型在运行,同样是那个40亿参数的模型,它在命名空间中运行。
如我所说,这是一个高层概述,有很多不同的量化技术可以做。例如Llama.cpp是运行这些低精度模型的流行工具之一,它使用GGUF格式。这种格式很酷,你可以进行5位、3位、2位甚至1位的量化,这很有趣也很酷。
但缺点是你会损失很多质量以换取性能提升。因此这里存在权衡。但在大多数情况下,从BF16到FP8的质量下降几乎可以忽略不计,而性能保持不变。这正是我们要展示的,也是我们的假设之一。显然,当你为特定工作负载执行此操作时,取决于你的用途,我强烈建议人们拥有自己的评估流程。
这样如果你从BF16切换过来,你的评估会告诉你一切正常且运行良好
对于我们的负载工作,如果您切换到 FP8 格式,可能会遇到一些问题。有时候某些功能会出错,有时会出现幻觉(hallucinating),有时会陷入循环。对于非常特定的用例,这些情况都可能发生。在这些场景下,拥有自己的评估集并运行自有评估至关重要,以确保您所做的任何更改都是可行的。同时,我们要确保性能没有下降。我们将再次运行测试,确认拥有 BF16 基础版本后,再测量性能表现。
我们使用一个负载测试实用程序包来进行一些测试。本质上,我们在不同的并发级别下发出请求。每个请求大约包含 256 个 token,输出则约为 128 个 token。这旨在模拟同时处理多少请求,我们将以此作为基线来查看基础性能表现。对于 32 个并发用户,我们获得的聚合性能为每秒 714 个 token,这相当不错。在单并发情况下,我们获得的是每秒 37.5 个 token。
这一点很重要,需要记录下来。此外,我们获得的首字延迟(time to first token)为 61 毫秒。接着您会注意到首字延迟有所上升,比如从 32 到 64 并发时的跳跃非常大。时间从 277 毫秒增加到 1300 毫秒,这是一个相当大的跳跃。您可能不一定愿意做出这种权衡,也就是说,您可能希望将并发请求限制在 32 个以内进行处理,而不再增加。因此,这是一个需要特别注意的重要事项。
正如我之前所说,评估(evals)极其重要。原因是进行量化时可能会出现性能下降,并且可能会损失一些智能推理能力,导致推理无法按预期工作,从而产生很多问题。因此,我想在这里以非常基本的层面模拟一下您可能需要考虑的评估类型。您需要考虑 JSON 是否正确生成,或者在降低量化精度时 JSON 是否被损坏。
例如,在 BF16 格式下,您可能会得到良好的响应;但当切换到 FP8(原文误作 FPA)时,您的函数调用可能会失败。为什么会这样?很可能是因为它没有正确生成 JSON。因此,开源社区中已经存在许多此类评估案例供您参考。Hugging Face 提供了大量的评估工具,您可以在 GitHub 上找到很多资源,但您仍需为自身的特定用例制定专属的评估方案。
接下来我们要做的是从 BF16 切换到 FP8 模型。这将使模型的体积减半。完成此操作后,我们将观察性能差异是否值得,以及是否会因此损失任何质量。为此,您需要处于根目录并点击“manifest”,在那里您会看到 vlm.yaml 文件。我将对此进行简要介绍,这是一个典型的 Kubernetes 部署配置文件。
在座有多少人之前使用过或了解 Kubernetes?好的,有不少人。很好,所以这部分我会快速带过,无需详述所有细节。本质上,我们有一个 PVC(原文误作 PBC),这实际上是存储所有模型数据的缓存区。我们还配置了一个简单的 VLM 部署,其中包含许多不同的参数和标志。我们可以看到使用的是 Qwen(原文误作 Quen)340 亿参数的模型,然后我们将切换到 Red Hat 提供的这个 FP8 版本。
我想再快速提及一点:您看到末尾的“dynamic”(动态)字样了吗?这非常重要。这意味着并非模型中的所有部分都被量化为 FP8。在许多不同的量化技术中,您可能只希望将模型的特定部分进行量化,而不是全部,以确保性能保持不变并避免出现问题。这就是为什么您会看到“dynamic”等各种标识的原因。
甚至会出现某些模型头(heads)为 4bit,另一些为 8bit,还有一些保持全精度的混合量化情况。人们这样做很有趣,并从中获得了显著的性能提升。他们在确保质量不变的同时找到了性能增益的方法。一旦更改完成,我们将保存文件,然后快速运行并应用它。这只是为了确保差异(diff)正确无误,不会引起任何问题。
然后我们继续执行 apply 操作。就在这里,我们将应用更改并部署它。这需要一点时间,让我保存一下。另外,您也可以打开一个新的终端标签页。只需点击加号,即可看到终端选项,这将让您查看命名空间(namespace)中的内容以及可以执行的操作。如果您发现 VLM 端点仍在启动中,请注意这个 Pod 本质上是在您的命名空间中运行的 Jupyter Notebook。
让我们看看这需要一点时间才能启动。是的,在等待期间,有人有问题吗?我们可以回答一个问题。“它已经是预量化的了吗?”“是的,它是预量化的。”没错,您可以量化自己的模型。有一个名为 LLM Compressor 的工具(原文误作 VLM/LM compressor),我相信它由 VLLM 提供。因此,如果您愿意,市面上有工具供您自行操作。
不过,是的,我们目前使用的是从 Hugging Face 下载的已经预量化的模型。好的,我认为它应该已经上线了。再次强调,这需要一点时间,主要是因为系统正在将权重从 VRAM 中卸载并加载新权重的过程。所以这需要一些时间来完成。还有其他人有问题吗?抱歉,请讲。“看看您的 VLM YAML 文件中的参数。”“哦,看起来模型有两个标志位。”
“哦,确实有。这是一个很好的指正。谢谢提醒,我没注意到好的,我们将重新检查一遍并再次运行。”“好吧,这大概解释了为什么它没有像预期那样快速启动。”哦,我无法执行那个操作。
还有其他问题吗?请讲。“你们如何在FP8和Int8之间选择?”这是一个好问题。这取决于具体情况,因为Int8的精度略低于FP8,这就是主要原因。区别在于FP8是浮点数,因此能获得稍高的精度;而Int8是整数。你可以任选其一,当然还有其他选项可供选择,具体取决于你的工作负载和约束条件。总之,使用Int8也是完全可以的,应该没问题。“在哪里提到的?”好的。
“Stack。”哦,是的,这是个很好的补充。这也取决于GPU的支持情况。某些GPU可能并不支持该功能。“GPU支持。”是的,这一点很重要。有些GPU虽然支持FP8,但可能并未通过硬件加速该功能,这意味着你可能无法获得预期的全部性能。例如,我们使用的RTX 4090支持FP8,并能在硬件层面加速所有计算,从而获得完整的精度和性能。
如果我们使用FP4,它就无法做到这一点,我们不仅看不到显著的性能提升,反而可能导致性能下降。因此,这也是需要牢记的一点。“在哪里?”好的,已成功部署。我们快速看一下,现在可以看到它正在使用FP8模型。接着我们将进行另一项负载测试,设置完全相同:并发数分别为1、4、8和32。完成后,我们可以对比并查看性能差异,从而了解量化带来的好处。
现在来看性能数据。如果我们将前后数据进行对比并放大图表,你会发现即使在并发数为1的情况下,吞吐量也从每秒37.5个token提升至53.4个token。性能有了显著提升。在峰值吞吐量方面,当并发数为64时,我们从每秒800个token提升至1,340个左右。
这大约是1.67倍的增长,性能提升相当可观。但请注意,与并发数32和64相比,“首token生成时间”也有显著增加。不过,对于大多数情况而言,BF16与FP8之间的性能差异并不大,通常可以开箱即用。虽然针对特定用例可能不适用,但在我们进行的简单评估中表现良好。
关于本模块的内容就到这里了,接下来我们将进入“投机解码”(Speculative Decoding)环节,这非常有趣。有多少人之前听说过投机解码?看来有不少人了解。“对于那些不知道什么是投机解码的人,让我把屏幕缩小一点。”本质上,我们使用一个比主模型小得多的草稿模型。
该草稿模型会提前生成多个token,然后我们的四参数十亿级主模型会对这些生成的token进行检查。它会判断结果是否合理:如果看起来不错,就会验证并确认;如果不合理,则不予接受。如图所示,草稿模型提前生成了四个token,而验证器(即我们的四参数十亿级模型)指出只有前两个token合理,后两个则不合理,因此将其丢弃。
这个过程会在每次生成新token时循环进行。“在本次研讨会中,我们将使用来自通义千问的60亿参数模型作为草稿模型。”看看它是否能帮助我们,或者在大多数情况下是否无效。有时它不仅无益,反而会降低性能,原因有很多,我们稍后会详细讨论。“时间进度如何?很好。”在进入正题前,我还想简要谈谈投机解码的不同实现方式。
目前有很多不同的方法,且每隔几个月就会涌现出新方案。“草稿模型只是其中一种。”很多时候,新的实现方式实际上使用的是扩散式草稿模型(diffusion drafter model),我觉得这非常酷。我 personally 就在我的家庭实验室机器上使用这种方法,它能带来巨大的性能提升。
这与传统方法的区别在于:由于是扩散模型,它会一次性生成大量token,而不是像LLM那样按顺序逐个生成。随后验证器模型会对其中一定数量的token进行验证。“当然,还有很多其他类型的方法可以使用。”例如后缀解码(suffix decoding)。
很多时候模型会内置草稿模型,这些是随主模型一起训练的原生模型。因此你无需导入其他模型即可实现此功能。“还有Medusa、Eagle,以及最近非常火爆且效果不错的Diffusion Dash。”你需要配置一个参数来决定在验证器介入之前生成多少个token。你可以将其设置为2、4或5个token。
正确设置这一点非常重要。“例如,如果你设置了四个token,但验证器只接受两个,那么接受率仅为50%,这相当糟糕。”这不仅意味着你需要运行两个模型从而消耗更多算力,而且低接受率会导致吞吐量下降。因此这不是我们想要的结果。你需要通过调整参数来找到最佳平衡点。
我想知道要提前生成多少个 token。嗯,所以从技术上讲你可以生成 15 个 token,但你可能不想这么做。你可能只想生成两个、三个或四个。这还取决于你使用的具体方法类型……让我回去看看,是什么来着?你在用什么方法?比如我在家用实验室里经常使用 deflash。如果设置为 8,效果相当不错。但如果设置超过 8,我的接受率会大幅降低,导致性能下降或变得极不稳定。
接下来……你还有最后一个问题吗?还有一个问题。嘿,抱歉,这是上一个模块的内容,但你能用一两句话解释一下动态压缩是如何工作的吗?嗯,它的工作原理是……你理解模型的不同注意力头吧?因为 LLM 内部有不同的层。如果你查看 model.json,你会发现 LLM 由许多不同部分组成,还有一个将 token从一个部分传递到另一个部分的流水线。很多时候当你看到“动态”这个词时,它的意思是:好吧,我只将注意力头量化为 FP8,但保持其他部分不变。
我明白了。这就是其中的逻辑。如果你记得之前的模块内容,你会发现 FP8 版本的体积并不正好是一半。它不是四 GB,而是大约 5.1 GB。这是因为并非模型的所有部分都被量化为 FP8。所以这就是所谓的动态部分。同样的数学原理适用于所有模型吗,还是取决于具体模型?这取决于具体的模型。每个模型的架构都不同。从架构上讲,这取决于你量化了模型的哪些部分。有些人将模型的部分内容量化为 int4,但保留其他部分为 FP8,然后将某些部分保持为 BF16。谢谢。
好的,我们重新过一遍。我只是确认我们的基线数据是准确的。这里使用的是 FP8 模型。然后我们将快速进行另一次负载测试,以获取在 VLM 部署中添加推测性解码之前的另一个基线数据。在此期间,我要确保正确复制这些信息……不是那个。哦,没关系。在这里我们设置推测性配置。这是 VLM 中的一个特定标志,用于设置草稿模型。我们将它设置为 0.6B(十亿参数)的模型。同时我们还设置要作为草稿 token 生成多少个 token。在这种特定场景下,我们将其设置为四。这同样需要大量测试:你想设为四吗?还是二?或者更高?这完全取决于接受率以及你能从中获得多少性能。
设置好这个标志后,我们将重新部署它。回到这里,我们有了基线数据。让我确认一下没问题……好的,看起来不错。然后我们将应用并滚动部署此配置。这可能需要几分钟时间。有人有问题吗?请讲……哦,等一下。我注意到你将 GPU 内存利用率设置为 70%,这意味着留出了大约 30% 的 VRAM……未使用。选择这个数值有什么特别的逻辑吗?在这种特定场景下,原因可能有很多。有时你可能在同一块 GPU 上有多个部署实例,你需要确保有足够的显存用于其他用途……但在这种情况下并非如此。实际上我们在下一个模块中会提高这个数值。这就像是一个调节旋钮,你必须确保设置正确的利用率。如果设得太低,你就浪费了性能;如果设得太高……你会搞挂你的节点,这可不是好事。
好的,它还在启动中。这需要多一点时间……请讲。我们看到的从 60/32 到 64 并行化的提升,是因为键值缓存爆满了吗?还是说……嗯,基本上是的。键值缓存确实爆了,达到了它的最大限制,无法再增加。所以原因有很多……此外还有连续批处理的问题。很多时候,如果你将配置设置为非最优的连续批处理水平,也会导致你看到极高的每秒 token 数。你也可能会在那里看到高延迟。但在本场景中,我们基本上是在 VRAM 使用量的上限触顶了。这就是我们看到这种情况的原因。但在接下来的模块中,我们将看到如何改善这一情况并使其变得更好。
好的,我们已经完成部署了。现在推测性解码已正确设置好。所以我们将再次进行快速的负载测试。这是与上面完全相同的测试……请记住,在并发数为 1 时我们获得了每秒 58.2 个 token,延迟为 42 毫秒。这是我们在进行推测性解码之前的基线数据。但我想在这里展示的不是那种你会自动获得惊人性能提升的“理想路径”。你实际上会看到性能大幅下降。你现在看到的是每秒 16 个 token,而基线是 58。甚至首字延迟也有所上升……我们需要弄清楚为什么会发生这种情况。存在巨大的差异和性能的大幅下降,即使看并发数为 16 的情况,我们也从每秒 700 个 token 降到了 240 个。这是性能的大幅下降。
要解读这种情况,原因有很多。正如我之前提到的,首先是接受率较低。如果你的接受率非常低,验证器模型会不断拒绝草稿模型生成的那些 token,那么你就是在浪费大量计算资源去生成这些草稿 token。然后验证器介入说:“嘿,我不想要这部分工作,这是无效的工作。”
这可能与我们将推测生成的 token 数量设置得过高有关,意味着在当前配置中我们将其设为四。也许我们需要降低这个数值,看看这是否真的有助于提高接受标准或接受率。此外,关于 VLM(视觉语言模型)以及调度这些请求的方式,也可能导致性能未能充分发挥。这也取决于兼容性等因素。同样存在 GPU 争用的问题,因为你在同一块 GPU 上执行这些工作,这可能导致资源未被正确使用。在许多情况下通常有效,但在我们的场景中却不行。
主要原因在于特定的草稿模型未针对该特定主模型进行正确训练,因此我们看到接受率非常低。当你对自己的模型和部署执行此操作时,必须确保使用的草稿模型与你希望使用的主模型兼容。如果不这样做,就会遇到我在此展示的情况:性能会大幅下降,且你对此无能为力。从资源角度来看,这块 GPU 有足够的余量支持所有这些工作。但我们看到性能下降的原因是模型未正确训练。
你需要记住这一点并进行测试,同时确保推测 token 的数量设置得当。我强烈建议大家在空闲时间尝试调整这个数字,比如从四改为二、六或一,看看能带来什么样的性能提升或下降,以及为什么会发生这种情况。这就是关于推测解码的全部内容,它在我们的场景中并没有帮助,所以我将其注释掉并重新部署,回到基线水平。
有人问了一个问题:考虑到模型架构的差异,一般来说,如果使用混合精度(例如主模型运行在 FP8 上,而草稿模型量化到 Q2),接受率是否会受到巨大影响?如果内存非常受限,这样做通常值得吗?这是一个好问题。很多时候这并不那么重要。通常情况下,你希望保持草稿模型和主模型的精度相同。
但假设草稿模型是 BF16,而主模型是 FP8,这种不匹配可能导致接受率非常低。关键在于你必须正确训练草稿模型,以便它能够生成正确的 token。在我们的特定场景中,草稿模型未得到正确训练,并且还被量化到了 FP8 级别。因此,在训练该模型的配方中可能存在一些问题,导致我们遭受了巨大的性能损失。
通常当实验室发布模型时,会同时发布配套的草稿模型,因此你应该使用随主模型一起提供的这些草稿模型。也有第三方实验室提供自己的草稿模型,你可以尝试各种选项,看看哪种适合你。在某些情况下,取决于用例:假设用于代码生成,草稿模型可能效果完美;但如果切换到英语写作或特定的学术写作,它可能表现不佳。
因此,接受率可能会下降。这也很大程度上取决于你的具体用例,所以它并非普遍适用。“明白了,很合理,谢谢。”“好的。让我们进入推理部分。在继续之前还有其他问题吗?哦是的,我可以把 YAML 文件放上去。”“推测解码的前提假设是:对于模型而言,验证比生成要快得多。你能简要解释一下模型在底层是如何进行‘验证’从而使其比解码更快的吗?它肯定不是先解码再与草稿模型产生的内容进行对比吧?”
“是的。本质上,当典型的 LLM 进行解码时,你有一组词汇表(一个巨大的数字数组),它代表每个项目的置信度分数。与其一次处理一个 token,在使用推测解码时,你是在并行运行模型以进行多项计算。结合连续批处理技术,你在并行运行大量不同的请求。你是在一次性为许多不同请求执行相同的计算。”
“同样地,对于推测解码也是如此。你检查这些推测 token 的置信度分数,确保‘哦,这是高置信度,所以我们采用它’。比如一个是 0.95,另一个是 0.9。然后你接受这些,而拒绝其他的。这种粗略的心理模型就是:你在并行地通过各个层运行,确保草稿模型输出的每个 token 都具有高置信度。希望这回答了你的问题。”
“还有一个例子吗?”“是的,我有。这是个很好的提示。我留了一些链接在这里……在哪里呢?解释结果那里。”“关于推测解码有很多不同的文章。如果你做得对,你会看到性能有显著增加。VLM 实际上发布了一份关于此的大型报告,如果设置正确(使用正确的模型、草稿模型和参数),他们看到 token 生成速度提高了约 1.5 倍。”
“所以是的,它确实表现出色。关键在于把它做对并正确调优,确保为其准备好正确的要素。”“从我的个人经验来看……”
Quinn 3.6是350亿参数的模型,同时我使用一个Dlash草稿模型(draft model),根据用例的不同,我能获得大约两到三倍的Token生成性能提升。这确实有效,关键在于找到能协同工作的正确模型组合。“你如何选择草稿模型的尺寸?”“基本上,你需要确定一个最小的、不占用过多GPU算力的模型。因为主模型才是拥有所有知识的核心,是你真正关心的、包含所需一切信息的模型。但你不希望草稿模型从主模型那里夺走太多资源,所以它要足够小,但不能小到愚蠢;又要大到足以……”
“你对此进行了实验吗?比如30亿参数的模型,接受率如何?”“30亿参数太大了。原因是很多模型如我之前提到的Quinn 3.6(350亿参数)是混合专家模型,每个专家大约30亿参数。因此专家模型的参数量可以接近这个数值,并能提供良好的性能。但对于草稿模型,为了确保其不占用过多资源,你可能希望参数量低于10亿。当然这也取决于你使用的主模型:如果主模型有4000亿参数,你可能可以使用更大的草稿模型以提高接受率。所以本质上你需要找到平衡点。”
“好的。还有其他问题吗?在插话之前还有一个问题。”“是的,我有一个疑问:草稿模型是如何训练的?是蒸馏还是其他方式?”“本质上是蒸馏。你取大模型,将其所有能力进行蒸馏,创建数据集,然后用该数据训练草稿模型。实际上有多种技术:另一种常见方法是将草稿模型与主模型一起训练,这样实验室通常会在同一包中提供草稿模型,无需下载第二个模型。但最佳方式是一起训练,这样问题较少且接受率保持较高。”
“好的,我们进入第七模块。我想谈谈在使用VLM部署时应关注的不同标志位,以及如何尽可能提高饱和度。”“饱和度意味着我们要让GPU在尽可能长的时间内满负荷工作。如果你租用或购买GPU,为了物有所值,就需要让其运行尽可能久。”“其中一个概念是:之前我们观察了32和64并发,看到延迟急剧增加。原因是吞吐量最终会趋于平缓,但随并发数继续增加,延迟仍可能持续上升。”
“因此你需要找到那个甜蜜点(sweet spot),VLM允许你微调以发现它,一旦找到即可设置正确参数。本质上,你要找的是吞吐量趋于平缓但延迟仍在可接受范围内的临界点。”“我们将进行设置并确认参数无误。服务模型已上线,让我再检查一下是否正常运行……好的。”“现在我们要进行另一次负载测试以获取数据并确定那个‘拐点’(knee),即事物趋于平缓且基于并发数的首Token时间可接受的点。”
“看起来随着并发增加吞吐量缓慢上升,这是可以接受的。同时延迟也在上升但幅度不大,不过很快我们会看到随着并发增加出现大幅跳跃。”“在运行期间还有其他问题吗?”“是的,关于之前的推测解码。根据你的经验,草稿模型在哪些用例中表现特别好?例如智能体循环、工具调用或编码?”“它在工具调用的智能体循环中表现很好,特别是浏览器使用是我常用的场景。因为并非所有服务都有API,需要填补这一空白。”
“草稿模型有助于更快地生成更多Token。因此你会看到浏览器智能体运行得更快,执行任务更迅速。”“在某种程度上它确实有帮助。就浏览器使用而言,我观察到大约1.5倍的性能提升;而在编码方面则接近3倍。具体取决于用例,结果可能有所不同。”“跟进一个问题:是否有像CodeX或Cloud这样的基准测试?他们是否发布过使用这些模式的信息,还是这主要适用于开源训练或推理?”
“我不确定他们是否这样做。我想象他们会利用这种免费的性能提升,但对于开源领域,这特别适用于你训练自己的模型或进行推理的场景。”
你可以使用所有想要使用的开源大模型,包括 Quinn 模型、GLM 模型和 Kimmy 模型等。它们都提供用于 Speculative Decoding(投机解码)的 Drafting Models(草稿模型)。
问:例如,DeepSeek 或 GLM 是否有一个主要的 Drafting Model? 答:我认为 DeepSeek 在昨天或者前天发布了一个新东西,叫做 DSpark。这就像他们自己的一种技术,以更动态的方式进行 Speculative Decoding。基本上,他们的配置是静态的,比如我们说每次生成四个 token;但使用 DeepSeek 的方式,它是动态的。这意味着它会根据接受标准、GPU负载以及各种指标,在运行时决定是只生成两个 token,还是四个或五个。
所以是的,每个模型、每个实验室都有各自的方法,你可能应该坚持使用它们自己的方法,但也可以进行实验。例如,Quinn 模型方面,Quinn Lab 没有发布用于 Drafting 的 Dlash 模型,但这些是由第三方实验室发布的,并且与它们配合得很好。所以你需要测试一下看看效果如何。
谢谢。 是的,我觉得这个挂在这里了。 对,我应该检查一下端口。嗯……可能这里的集群崩溃了。 哦,我打不了字了。呃……哦……这个之前运行得很好。刷新一下。(笑声)让我重新创建标签页。好吧,出错了。它复制了一个 URL 并在新标签页中打开了。看看会发生什么。让我打开一个新标签页,不要关闭那个旧的。
你知道的,在之前没有发生的测试中,它今天发生了。我至少能滚动浏览并查看不同的内容吗?好吧,既然集群决定去“度假”了,我至少可以滚动浏览一下我要涵盖的内容。
这次运行这个基准测试的主要目的是展示那个“拐点”在哪里,也就是最大吞吐量出现的地方。如果我们回到之前的图表,在确保看到最低延迟的同时找到最大 token 生成吞吐量。那个限制基本上是在并发数为 32 时达到的。因为超过 32 的并发性,你会发现 token 生成的吞吐量基本保持不变,不会有太大变化;虽然吞吐量相同,但延迟会急剧上升,这是你不希望看到的。所以这就像是你达到的限制点。
这是我们部署这些系统时需要牢记和查看的重要信息。 是的,你可以增加到 100。去看看电子表格……100?对,到 100,然后继续从七或八开始。在哪里呢? 在那里。好吧,这个能用了,很好。
好的,那我们跳到第八部分。嗯……让我打开一下。(笑声)谁在做这个? 好吧,对于模块 A,我们将快速讨论你可以调整哪些标志和参数,以确保你能超过我们之前提到的 32 并发数的限制点。你会看到那个限制,以及如何超越它,以及在思考这个问题时需要注意哪些事项。
对于这一点,我想给你们一个思维模型:本质上你要进行测量。这就是为什么我在不同的单元格中不断运行负载测试,这非常重要。你需要通过运行这些负载测试来查看性能表现,以及你做的任何更改如何影响性能——是让它变得更好还是更差?在什么具体指标上变差了?比如,如果吞吐量持续上升或保持不变,但延迟发生了变化,那么要么是正确的,要么是错误的。这是我们必须牢记的一点。
VLM 中有许多不同的参数可以调整以改善性能,但主要的四个包括 GPU 内存利用率。你需要根据你的用例正确设置它。如果你在同一块 GPU 上运行多个模型,你可能希望将其保持较低水平,这是一种权衡;但如果你只运行单个模型并希望获得最佳性能,则应将其提高到 0.9 甚至 0.95。
另一件事是确保 max_num_sequences(最大序列数)设置为一个合理的限制,这也需要通过测试来确定。在我们的特定部署中,如果你查看 VLM.yaml 文件,我们从 32 个序列开始。这意味着它告诉 VLM 一次只能处理 32 个请求,不能更多。如果超过这个数量,你会看到性能下降。
虽然有很多不同的标志,但这些都是你需要牢记的关键点。你还需要确保模型长度设置正确,并且 batch_tokens(批处理 token 数)也要设置为正确的长度。如果你希望预填充更快且 GPU 能处理,就把它设大一点;如果 GPU 稍慢无法承受,就设小一点。
总之,你要一次只更改一个标志,不要同时更改多项内容。在更改时运行基准测试并观察性能表现。测量它,然后对下一步该做什么做出假设。我们快速过一遍这里……还剩大约 10 分钟?哦,
我想我们要丢失了 PVC(持久卷声明)。我觉得是那样。我…… 哦,它获取到了不同的一个。是的,我需要实际更改这个。我想重新运行一下。嗯……让我在这里直接修改它。这原本应该是顺序执行的,但其他 Jupyter Notebook 崩溃了。所以更改那个应该就能工作了。
就像我提到的,有不同的参数可以调整并观察结果。我们会测量基线,然后更改几个标志看看接下来会发生什么。嗯……说工作了吗?好的,酷。它在工作了。在这个负载测试运行时,大家还有其他问题吗? 打扰一下……
我注意到在优化模型时确实有明显的下降。比如从 32 到 64,大幅下降。那是你会决定扩展硬件的时候吗?还是你有信心进一步调整它? 嗯……本质上,这是上限了吗? 是的,你通过数学计算找出上限。我认为在部分……
之前模块中,Dwan 讲解了每秒 token 数量的最大上限、吞吐量表现以及部署时应持有的预期。如果你达到了这些最高预期,那很可能就是当前硬件的极限了;此时你需要增加或升级更好的硬件才能继续提升。确实存在某种限制,你能调优的空间有限,但目标始终是尽可能榨干 GPU 的性能。
服务器我可以暂时保持开启状态,大概几个小时吧。我不会仅仅因为你们有 GPU 访问权限就让它一直开着。但我可以通过扫描这个二维码给你们提供 PowerPoint 演示文稿以及代码仓库的访问权限。如果你想在自己的 AI Cloud 账户中运行,或者想在笔记本电脑上运行,都可以做到。
扫描二维码会带你到一个包含所有资源的网站;如果你没有云账户,可以免费注册并获得 300 美元的额度。这就是你所需的一切。如果你们有问题,我们的联系方式都在这里。我是 Omar,他是 Dwan Lightfoot。>> 是的。>> 好了,既然大家扫了码,我就让 Omar 继续讲下去。我只是想确保大家在离开前拿到这些资料。谢谢。>> 我们会再把它保留几分钟,确认大家都拿到了。>> 好的,请继续。
个人助理方面……你可以使用 Ollama,这取决于你的硬件配置,我认为这是首先要考虑的因素。>> 没问题,谢谢。>> 哦,你们两个都有。为了讨论方便,假设你拥有最好的 GPU;假设你有最新的 Blackwell GPU……嗯,我认为对于测试和确定适合用例的模型来说,使用 Ollama 和 Llama.cpp 快速启动并尝试运行是很好的做法。
但一旦你确定了要用于该用例的模型,且效果很好,你可能需要切换到 vLLM、SGLang 或其他这些模型运行时。原因是它们允许你以更高的并发运行,并从中获得更好的性能。如果只有一个智能体在运行,使用 Ollama 没问题,因为一次只处理一个请求。但假设你正在运行如 20 个智能体并希望并行执行……
是的,你可以为 vLLM 设置投机解码(speculative decoding)。据我所知 Ollama 不支持,但我可能错了。但当你想要从硬件中获取最大性能时,你肯定需要这样做。>> 是的,>> 我实际上不知道是否有扩散模型 LLM?目前有可用的开源扩散模型吗? >> 是的,Lana。
是的,我认为 vLLM 支持它。所以我想你可以在 vLLM 上运行它。如果 Ollama 也支持,你也可以那样做。嗯,那肯定是可能的。好的,我们切回模块并大致过一遍。再次强调,在 32 时你看到了不错的性能;但在 64 时……嗯,吞吐量保持在同一水平甚至略有上升,但我们看到了延迟显著增加。
为了应对这种情况……我想讨论的一个因素是最大序列数(max num sequence)。这大致设定了 vLLM 一次能处理的请求数量。如果你希望并行处理大量请求,那么你需要增加这个数值。目前我认为它设置为 32。所以我们可以将其提升到 128,然后运行部署并观察性能变化如何改变。
好的,这里发生了很多变化……嗯,一旦这个出现……我们可以做另一个测试;你会看到之前在 32 时遇到的限制会消失,并且在高并发水平下延迟也会变得可管理。所以这大致是你需要进入的循环:找到瓶颈所在,找出发生的原因,调整相应的标志以确保克服该瓶颈。嗯,它出现了……这需要一点时间。有任何问题吗?那边还有一位朋友想提问。
可能是突发流量……嗯,在某些时候会这样。 >> 哦,是的。有没有针对无服务器架构的突发处理?那是模型路由层之上的领域吗?模式是什么样的…… >> 是的。嗯,自动扩展就是一种模式;具体来说在 LKE(Linux Kubernetes Engine)中……你可以拥有动态的自动扩展。因此,它可以根据你的需求或突发流量进行向上扩展;它可以扩展到使用拥有四个 GPU 的节点而不是一个,因为那样可能处理得更好。
所以你可以自动化很多事情以确保运行良好;另一部分是可以构建一些统计模型,以便在长周期内理解你的流量模式。然后集群也可以动态地以某种方式预测你的突发性,并据此进行扩展……>> 如果你打开了浏览器,我可以展示给你看 >> 你打开浏览器,我带你去看。 >> 好的,所以这个已经成功部署了……嗯,然后我们会做一个快速的负载测试;一旦运行完毕,我们可以看到更多数据。好吧,还有其他问题吗?大多数人已经离开了……
是的,像 Foot H …… >> 是的。32。这是我们上次看到限制的地方。一旦我们达到 64,我们应该会看到情况有所改善……好的。所以有了巨大的改进;吞吐量实际上大幅上升了。嗯,现在达到了每秒 1100 个 token。而之前……如果我们看这里在 64 时,我们只有每秒 800 个 token;然后延迟是……2000 毫秒。但现在经过我们做的更改后,它是每秒 1100 个 token;然后延迟是……800 毫秒。
所以延迟大幅下降,我们也大幅提高了吞吐量。这些就是你可以随时间进行的调优类型……以确保事情运行良好;一旦你启动并运行它,就可以针对你的特定用例进行此操作。也许对于特定的用例,你不会收到……嗯,64 个并发请求;但也可能你会。所以你必须根据用例做出决定 >> 你的并发数比之前高一点,那是你做的权衡吗?
情况并不总是完全一致的,例如之前是 37,但现在变成了 35。因此,在运行模型时会出现一些偏差,但通常应在正负两秒或两个 token 的范围内。此外,我们现在运行的是全精度模型。我们不再运行 FPA(浮点加速),因为我们不得不切换笔记本环境。这也是为什么你看到性能指标从 50 多个 token 下降到 30 多个 token 的另一个原因。这就是我们观察到的性能表现。
是的,以上就是我要分享的内容。这是一个你可以调整的标记(flag)。当然,还有各种其他标记,我恳请大家仔细查看。研讨会中也详细介绍了这些内容。不过我认为时间差不多了,我将把发言权交还给 Dorne。
大家好,感谢各位参加今天的会议。如果您还没有扫描二维码,代码、仓库以及演示文稿均可在该网站上获取。欢迎随时注册免费的 AMA Cloud 账户。我是 Dwan Lightoot,我的同事 Omar 也与我在一起,感谢大家今天的参与。会议结束后欢迎向我们提问。谢谢。
[音乐]