来源:oldpan
token 吞吐量(Token Throughput) 首 token 响应时间(Time-to-First-Token, TTFT) 每 token 输出时间(Time-per-Output-Token, TPOT)。
本文将首先探讨关键采样技术:Top-K、Top-P 和重复惩罚。然后,我们将在 TensorRT-LLM 和 vLLM 框架下评估这些技术在不同配置下的性能开销。
理解采样方法
贪心采样
贪心采样在每次迭代中简单地选择概率最高的 token(上图)。
这种方法提供了可预测的输出,非常适用于开发或调试。然而,它往往会导致输出重复(即“ degeneration”)且缺乏多样性。例如,以下是 Llama-3–8B 生成的一个例子,该例子过度使用了“conscious”一词。
输入: “Hey, are you conscious?”
使用贪心采样生成的输出:
“I mean, are you really conscious? I mean, are you really conscious of your consciousness? I mean, are you really conscious of your consciousness of your consciousness”
Top-K 采样
为了提高输出多样性,引入了基于采样的方法,例如 Top-K 采样。Top-K 采样允许概率较高但非最高的 token 也有机会被选择。正如图 2 所示,LLM 根据 token 的概率进行排名,仅保留概率最高的 K 个 token。然后对这些 K 个 token 的概率进行归一化,以确保总和为 1,并根据归一化后的概率随机选择下一个 token。
这种方法引入了受控的随机性,有助于生成更具多样性的输出,同时仍能避免选择概率极低的 token。
输入: “Hey, are you conscious?”
使用贪心采样生成的输出:
“I mean, are you really conscious? I mean, are you really conscious of your consciousness? I mean, are you really conscious of your consciousness of your consciousness”
使用 Top-K(K=50)采样生成的输出(两组不同随机种子):
输出 1: “Are you aware of the fact that there are only 8 hours left in the year 2018? The year 2018 is going to end in a”
输出 2: “You have to be conscious to be able to do the things you need to do in life. Consciousness is a state of being that enables you to be”
上述例子展示了使用贪心采样和 Top-K 采样(K=50)生成的句子对比。相比于确定性解码,Top-K 采样减少了重复性,并生成了更具多样性的句子。
Top-P(核采样)
Top-P 采样,又称核采样,与 Top-K 采样类似,但在候选 token 集的选择方式上有所不同。Top-P 采样不是限制为固定数量的 token(K),而是动态选择概率累积值超过预设阈值 P(例如 0.9)的 token 集。
这种动态方法提供了更大的灵活性,因为候选 token 的数量可以根据生成的上下文而变化。通过调整阈值 P,模型可以控制每一步中被考虑的 token 数量,从而在生成输出的多样性和连贯性之间取得平衡。
注意,Top-K 和 Top-P 采样可以结合使用,如以下示例所示。当结合使用时,token 集首先被限制为 K 个候选,然后进一步缩小到满足概率累积阈值 P 的 token。
输入: “Hey, are you conscious?”
使用贪心采样生成的输出:
“I mean, are you really conscious? I mean, are you really conscious of your consciousness? I mean, are you really conscious of your consciousness of your consciousness”
使用 Top-P(P=0.9)生成的输出:
“That’s a good question to ask yourself, because it is a question that we all need to ask ourselves from time to time. Consciousness is the ability to”
使用 Top-K(K=50)和 Top-P(P=0.9)生成的输出:
“It’s time to get up.\nI’m sure you’re still sleeping. You’ve been sleeping for 30 years. You’ve been dreaming of a better life”
Top-K 和 Top-P 采样都高度依赖于 token 的概率分布,而 温度(Temperature, T) 是另一个有用的参数,可以让用户控制概率分布。正如图 4 所示,降低温度会使概率分布更加集中,从而使模型更有可能选择最高概率的 token。这减少了输出的随机性,生成更连贯、可预测的句子(图 4b)。
相反,提高温度会使概率分布更平滑,从而使模型更有可能选择概率较低的 token。这可以生成更具创意和多样化的文本,但也增加了生成不连贯结果的风险(图 4c)。根据具体应用场景,合适的温度设置可以在连贯性和多样性之间取得平衡。
重复惩罚(Repetition Penalty)
重复惩罚是另一种重要的技术,用于减少模型生成重复性输出的概率。这种方法对在之前步骤中已经被选择的 token 进行惩罚,从而降低它们的概率,减少它们再次被选中的可能性。例如,设置重复惩罚为 1.1 时,Llama-3–8B 模型生成以下输出:
输入: “Hey, are you conscious?”
使用贪心采样生成的输出:
“I mean, are you really conscious? I mean, are you really conscious of your consciousness? I mean, are you really conscious of your consciousness of your consciousness”
使用重复惩罚(1.1)生成的输出:
“I mean really conscious?\nI’m not talking about being aware of your surroundings or the people around you. I’m talking about being aware of yourself and what’s”
结合 Top-K(K=50)、Top-P(P=0.9)和重复惩罚(1.1)生成的输出:
“I’m just asking. You know what I mean: Are you really awake and aware of your life?\nI have a friend who is always saying she’s “”
通过调整重复惩罚参数,用户可以减少退化现象,同时保持句子的连贯性。然而,较高的惩罚可能导致输出的连贯性下降,因为它可能过度惩罚那些对句子结构至关重要的 token。
此外,还有其他方法可以控制重复性输出:频率惩罚(Frequency Penalty) 和 存在性惩罚(Presence Penalty)。两者通过从 logits 中减去一定数值来施加惩罚,而重复惩罚则通过缩放 logits 实现(详见以下代码)。此外,频率惩罚根据重复次数施加惩罚,而其他方法仅基于 token 是否存在进行惩罚。通过同时调整这些参数,可以更精细地控制不同服务环境中的重复性输出。在本文中,为了简化这里仅使用重复惩罚,其他方法的计算开销也差不多。
repetition_penalties[~(prompt_mask | output_mask)] = 1.0logits = torch.where(logits > 0, logits / repetition_penalties, logits * repetition_penalties)logits -= frequency_penalties.unsqueeze_(dim=1) * output_bin_countslogits -= presence_penalties.unsqueeze_(dim=1) * output_mask
需要注意的是,这些惩罚方法可以与 Top-P 和/或 Top-K 采样并行使用。结合采样方法后,LLM 能够生成更具多样性和创意的句子。
实验
基准数据集
为了评估不同采样方法的影响,我们使用了真实数据集 ShareGPT,而非此前文章中的随机固定数据集。具体来说,我们从 ShareGPT_Vicuna_unfiltered 数据集中抽取了 1,000 个样本。
输入提示的平均长度约为 225 个 token,而输出长度固定为 1,024 个 token,以创建偏重解码的场景。这种偏重解码的任务让我们能够更好地评估采样对性能的影响,因为这些方法主要影响解码阶段,而非预填充阶段(预填充阶段的输出较短)。
模型和硬件规格
• 模型: Llama-3–8B(BF16)
• 硬件: Intel Xeon(R) 2.20GHz(12 核),1 NVIDIA A100-SXM 80G GPU,128GB RAM
框架版本
由于 TensorRT-LLM 的 C++ API 基准工具最初不支持采样选项,我们采用了 vLLM 基准中的测量方法。我们使用 Triton 推理服务器(Triton Inference Server)上的 Llama-3–8B(BF16),并通过 vLLM 源代码中的 benchmarks/benchmark_serving.py 脚本测量采样句子的吞吐量、TTFT 和 TPOT。
• vLLM: v0.6.2
• TensorRT-LLM: 0.12.0.dev24080600(Triton Inference Server Release 24.08)
实验结果
不同请求速率下的结果
我们首先评估了将所有前述采样技术结合使用与贪心采样之间的性能差异。我们将请求速率从 1 调整到 8,分析了采样对吞吐量、TTFT 和 TPOT 的影响。对于每种采样方法,参数设置如下:
Top-P(P=0.9),Top-K(K=50),温度(T=4),重复惩罚(1.1)
如图 5 所示,当请求速率大于 2 时,采样方法导致吞吐量显著下降。当请求速率为 8 时,应用采样技术后,vLLM 的吞吐量下降了 15.4%,TensorRT-LLM 的吞吐量下降了 7.1%。
与此同时,当请求速率较低时,采样几乎没有导致性能下降。这是因为请求速率增加时,工作负载变得更偏向计算受限。较高的请求速率导致更大的 *运行批量大小(running batch size),以及更高的操作密度。
在计算受限的情况下,采样技术的额外计算开销更加显著,而在较低请求速率(内存受限)条件下,开销可忽略不计。TTFT 和 TPOT 也受到了类似影响,其中 vLLM 表现出更显著的 TTFT 降低。而在 TPOT 中,vLLM 的下降幅度(20.6%)高于 TensorRT-LLM(9.2%)。
两个框架之间性能下降的差异可能源于它们在采样过程实现上的不同。vLLM 依赖于基于 Python 的采样实现(链接[1]),而 TensorRT-LLM 使用了自定义的 CUDA 核函数和低级 GPU 优化来最小化开销(链接[2])。随着 vLLM 的不断发展,以及采用专用 CUDA 核函数的努力,这种差距可能在未来缩小。
不同批量大小下的结果
如前所述,采样开销在计算受限场景中更为显著。为了进一步验证这一点,我们通过调整批量大小进行了额外实验。正如上一篇文章中讨论的,调整 最大批量大小(max batch size) 参数可以使工作负载更加偏向计算受限场景。我们在请求速率为 8 的条件下,针对每个框架调整 最大批量大小 参数,并测量吞吐量、TTFT 和 TPOT 的变化。
如图 6 所示,当 最大批量大小 为 256(默认值)时,两个框架均出现了最严重的性能下降。此外,随着最大批量大小的减少,贪心采样与其他采样方法之间的差距逐渐缩小,因为工作负载更加偏向内存受限场景。
采样方法的消融研究
为了更深入地了解每种采样技术的影响,我们进一步对三种采样方法进行了消融研究。实验在请求速率为 8 和最大批量大小为 256(采样开销最显著)时进行。
对比单独应用每种采样方法的结果,我们发现 Top-K 的开销最大,其次是 Top-P,而重复惩罚的开销最小。值得注意的是,与 Top-K 和 Top-P 采样相比,重复惩罚的开销较低,因为前者需要排序算法。在 TensorRT-LLM 的情况下,重复惩罚几乎没有引入额外开销。总体而言,vLLM 的采样开销比 TensorRT-LLM 高 2-3 倍,尤其是当所有采样方法同时使用时,vLLM 的 TPOT 性能下降超过 20%。
代码改动细节
与之前的文章不同,本次实验需要支持采样功能,因此我们采用了 TensorRT-LLM 的 Triton 推理服务器。然而,在开始基准测试时,我们发现吞吐量显著下降——几乎比预期慢了三倍。
当使用不带采样的 C-API 基准工具进行测试时,吞吐量约为 3,127 tokens/s。但在使用 Triton 推理服务器并启用采样后,吞吐量下降到 313 tokens/s,即 慢了 10 倍。这一性能下降远远超出了采样开销本身的合理范围。
经过调查,我们发现问题出在 TensorRT-LLM 和 Triton 推理服务器的后处理机制上。具体来说,transformers 库中的 AutoTokenizer 处理词汇表(vocabulary)的方式对性能产生了显著影响。目前,Triton 推理服务器与 TensorRT-LLM 后端的后处理实现依赖于调用 len(self.tokenizer.vocab) 来计算词汇表大小(链接[3])。这导致词汇表字典对象在每次调用时都被重复创建,造成明显的性能下降。
我们通过一个简单的fix解决了这个问题。在初始化阶段预先计算词汇表大小,并在后处理过程中重复使用,而不是每次都调用 len(self.tokenizer.vocab)(链接[4])。应用这一补丁后,我们成功消除了意外开销,使采样方法的性能达到合理水平。需要注意的是,目前所有发布版本的 TensorRT-LLM 后端中都存在此问题,因此用户需引起注意。
最终总结
在本文中,我们探讨了各种采样技术及其对 vLLM 和 TensorRT-LLM 性能的影响。实验结果表明,采样开销在计算受限场景中更加显著,例如高请求速率、解码任务较重的数据集或较大的批量大小。在此类情况下,需谨慎考虑采样开销。
我们观察到,vLLM 的采样开销比 TensorRT-LLM 更为显著,这可能是由于实现上的差异。在 vLLM 中,需要排序的采样方法(例如 Top-P 和 Top-K)表现出比重复惩罚更大的开销。
在未来的文章中,我们将继续探索面向真实场景的大型语言模型部署的各种高级功能,例如结构化输出、前缀缓存、多 LoRA 支持等。我们希望这些洞察能够为从业者提供有用的信息,从而在 LLM 部署策略中做出明智选择。