📢 转载信息
原文作者:Josh Longenecker and Mohammad Tahsin
随着强大且可通过 API 调用的 大型语言模型 (LLMs) 的兴起,将人工智能 (AI) 功能集成到应用程序中变得异常简单。然而,尽管这种便利性很高,但仍有相当数量的企业选择自托管自己的模型——他们接受了基础设施管理的复杂性、服务堆栈中 GPU 的成本,以及保持模型更新的挑战。选择自托管通常归结为 API 无法解决的两个关键因素。首先是数据主权:需要确保敏感信息不会离开基础设施,无论是由于监管要求、竞争考虑,还是与客户的合同义务。其次是模型定制:能够根据专有数据集对模型进行微调,以适应行业特定的术语和工作流程,或者创建通用 API 无法提供的专业化功能。
Amazon SageMaker AI 通过抽象化操作负担来解决自托管的基础设施复杂性。通过托管端点,SageMaker AI 负责预置、扩展和监控 GPU 资源,使团队能够专注于模型性能,而不是基础设施管理。该系统提供了具有流行框架(如 vLLM)的推理优化容器,这些容器已预先配置,以实现最大吞吐量和最小延迟。例如,大型模型推理 (LMI) v16 容器镜像使用了 vLLM v0.10.2,它使用 V1 引擎,并支持新的模型架构和新硬件,例如 Blackwell/SM100 一代。这种托管方法将通常需要专门的机器学习运维 (MLOps) 专业知识的部署过程,转变为只需几行代码即可完成的部署过程。
要使用这些托管容器实现最佳性能,仍然需要仔细的配置。像张量并行度、批处理大小、最大序列长度和并发限制等参数会极大地影响延迟和吞吐量——为您的特定工作负载和成本限制找到正确的平衡是一个迭代过程,可能非常耗时。
BentoML 的 LLM-Optimizer 通过启用跨不同参数配置的系统化基准测试来解决此挑战,用自动搜索过程取代了手动试错。该工具允许您定义约束,例如特定的延迟目标或吞吐量要求,从而可以轻松识别满足服务水平目标 (SLO) 的配置。您可以使用 LLM-Optimizer 在本地或开发环境中找到 vLLM 的最佳服务参数,然后将相同的配置直接应用于 SageMaker AI 端点,以无缝过渡到生产环境。本文通过在 Amazon SageMaker AI 端点上为 Qwen-3-4B 模型查找最佳部署来说明这一过程。
本文面向已经将模型部署在 Amazon SageMaker 或类似基础设施上的实践中的机器学习工程师、解决方案架构师和系统构建者。我们假设您熟悉 GPU 实例、端点和模型服务,重点关注实际的性能优化。推理指标的解释并非作为初学者教程,而是为了建立共同的直觉,特别是对于像批处理大小和张量并行这样的特定参数,以及它们如何直接影响生产中的成本和延迟。
解决方案概述
分步细则如下:
- 在 Jupyter Notebook 中定义约束:该过程始于 SageMaker AI Studio 内部,用户在此打开一个 Jupyter Notebook 来定义用例的部署目标和约束。这些约束可以包括目标延迟、所需的吞吐量和输出令牌数。
- 使用 BentoML LLM-Optimizer 运行理论和实证基准测试:LLM-Optimizer 首先运行理论 GPU 性能估算,以识别所选硬件(本例中为
ml.g6.12xlarge)的可行配置。它跨多个参数组合(如张量并行、批处理大小和序列长度)执行使用 vLLM 服务引擎的基准测试,以实证测量延迟和吞吐量。基于这些基准测试,优化器会自动确定满足所提供约束的最有效服务配置。 - 生成并部署 SageMaker 端点的优化配置:基准测试完成后,优化器会返回一个包含最佳参数值的 JSON 配置文件。此 JSON 从 Jupyter Notebook 传递到 SageMaker 端点配置,该配置使用最佳运行时参数在托管的 HTTP 端点中部署 LLM(本例中为使用基于 vLLM 的 LMI 容器的
Qwen/Qwen3-4B模型)。
下图是本文工作流程的概述。

在深入研究推理优化的理论基础之前,值得明确这些概念在真实部署中的重要性。当团队从基于 API 的模型转向自托管端点时,他们继承了调整性能参数的责任,这些参数直接影响成本和用户体验。从 GPU 架构和算术强度角度理解延迟和吞吐量的相互作用,使工程师能够有意识地做出权衡,而不是通过试错。
LLM 性能简要概述
在深入了解此工作流程的实际应用之前,我们介绍一些关键概念,以建立对为什么推理优化对 LLM 驱动的应用程序至关重要的直觉。以下入门并非学术性的,而是为了提供解释 LLM-Optimizer 输出并理解为什么某些配置能产生更好结果所需的思维模型。
关键性能指标
吞吐量(请求/秒):系统每秒完成的请求数。较高的吞吐量意味着可以同时服务更多用户。
延迟(秒):从请求到达直到返回完整响应的总时间。较低的延迟意味着更快的用户体验。
算术强度:执行的计算与移动数据之比。这决定了您的工作负载是:
内存受限:受数据移动速度限制(算术强度低)
计算受限:受原始 GPU 处理能力限制(算术强度高)
上限模型 (Roofline Model)
上限模型通过将吞吐量与算术强度作图来可视化性能。有关上限模型的更深入内容,请访问 AWS Neuron Batching 文档。该模型揭示了您的应用程序是受内存带宽还是计算能力限制。对于 LLM 推理,此模型有助于确定您是否受到以下限制:
- 内存带宽:GPU 内存与计算单元之间的数据传输(对于小批量大小通常如此)
- 计算能力:GPU 上可用的原始浮点运算 (FLOPS)(对于大批量大小通常如此)

吞吐量-延迟权衡
在实践中,优化 LLM 推理遵循一个基本权衡:随着吞吐量的增加,延迟也会上升。发生这种情况是因为:
- 更大的批处理大小 → 一起处理更多请求 → 吞吐量更高
- 更多并发请求 → 更长的队列等待时间 → 延迟更高
- 张量并行 → 将模型分布到 GPU 上 → 以不同方式影响两个指标
挑战在于跨多个相互依赖的参数找到最佳配置:
- 张量并行度(使用多少 GPU)
- 批处理大小(一起处理的最大令牌数)
- 并发限制(最大同时请求数)
- KV 缓存分配(注意力状态的内存)
每个参数对吞吐量和延迟的影响不同,同时还要遵守 GPU 内存和计算带宽等硬件约束。这个多维优化问题正是 LLM-Optimizer 有价值的原因——它系统地探索配置空间,而不是依赖于手动试错。

关于 LLM 推理的整体概述,BentoML 在其 LLM 推理手册中提供了有价值的资源。
实际应用:在 Amazon SageMaker AI 上查找 Qwen3-4B 的最佳部署
在接下来的部分中,我们将完成识别和应用 LLM 部署的最佳服务配置的动手示例。具体来说,我们:
- 使用 vLLM 在
ml.g6.12xlarge实例(每个 4x NVIDIA L4 GPU,24GB VRAM)上部署Qwen/Qwen3-4B模型。 - 定义合理的工作负载约束:
- 目标:每秒 10 个请求 (RPS)
- 输入长度:1,024 个令牌
- 输出长度:512 个令牌
- 探索多种服务参数组合:
- 张量并行度(1、2 或 4 个 GPU)
- 最大批处理令牌数(4K、8K、16K)
- 并发级别(32、64、128)
- 使用以下内容分析结果:
- 理论 GPU 内存计算
- 基准测试数据
- 吞吐量与延迟的权衡
最后,您将看到理论分析、实证基准测试和托管端点部署如何结合起来,以提供一种平衡延迟、吞吐量和成本的、可投入生产的 LLM 设置。
先决条件
运行此示例所需的先决条件如下:
- 可以访问 SageMaker Studio。这使得部署和推理变得简单,或者可以使用 PyCharm 或 Visual Studio Code 等交互式开发环境 (IDE)。
- 要对模型进行基准测试和部署,请根据模型大小检查推荐的实例类型是否可访问。要验证所需的 服务配额,请完成以下步骤:
- 在服务配额控制台中,在AWS 服务下,选择Amazon SageMaker。
- 验证所需实例类型用于“端点部署”(在正确的区域)的配额是否充足。
- 如有需要,请求配额增加/联系 AWS 寻求支持。
以下代码详细说明了如何安装必要的软件包:
pip install vllm
pip install git+https://github.com/bentoml/llm-optimizer.git
运行 LLM-Optimizer
要开始,必须根据目标工作负载定义示例约束。
示例约束:
- 输入令牌:1024
- 输出令牌:512
- 端到端延迟 (E2E):<= 60 秒
- 吞吐量:>= 5 RPS
运行估算
使用 llm-optimizer 的第一步是运行估算。运行估算会分析 Qwen/Qwen3-4b 模型在 4x L4 GPU 上的性能,并估算输入长度为 1024 令牌、输出为 512 令牌时的性能。运行后,系统会以数学方式计算并返回延迟和吞吐量的理论最佳值。返回的上限分析确定了工作负载的瓶颈,并返回了一系列服务器和客户端参数,供下一步运行实际基准测试时使用。
在底层,LLM-Optimizer 执行上限分析以估算 LLM 服务性能。它首先从 HuggingFace 获取模型架构,以提取隐藏维度、层数、注意力头数和总参数等参数。利用这些架构细节,它计算预填充(处理输入令牌)和解码(生成输出令牌)阶段所需的理论 FLOPs,同时考虑注意力操作、MLP 层和 KV 缓存访问模式。它将每个阶段的算术强度(每字节的 FLOPs)与 GPU 的硬件特性(特别是计算能力 (TFLOPs) 与内存带宽 (TB/s) 的比率)进行比较,以确定预填充和解码是受内存限制还是受计算限制。通过这种分析,该工具估算了不同并发级别下的 TTFT(首次令牌时间)、ITL(令牌间延迟)和端到端延迟。它还计算了三个理论并发限制:KV 缓存内存容量、预填充计算能力和解码吞吐量能力。最后,它生成用于经验基准测试的调整命令,这些命令会扫描不同的张量并行配置、批处理大小和并发级别,以验证理论预测。
以下代码详细说明了如何根据所选约束运行初始估算:
llm-optimizer estimate \
--model Qwen/Qwen3-4B \
--input-len 1024 \
--output-len 512 \
--gpu L40 \
--num-gpus 4
预期输出:
Auto-detected 4 GPU(s)
💡 Inferred precision from model config: bf16 === Configuration ===
Model: Qwen/Qwen3-4B
GPU: 4x L40
Precision: bf16
Input/Output: 1024/512 tokens
Target: throughput Fetching model configuration...
Model: 3668377600.0B parameters, 36 layers === Performance Analysis ===
Best Latency (concurrency=1):
TTFT: 16.8 ms
ITL: 1.4 ms
E2E: 0.72 s Best Throughput (concurrency=1024):
Output: 21601.0 tokens/s
Input: 61062.1 tokens/s
Requests: 24.71 req/s
Bottleneck: Memory === Roofline Analysis ===
Hardware Ops/Byte Ratio: 195.1 ops/byte
Prefill Arithmetic Intensity: 31846.2 ops/byte
Decode Arithmetic Intensity: 31.1 ops/byte
Prefill Phase: Compute Bound
Decode Phase: Memory Bound === Concurrency Analysis ===
KV Cache Memory Limit: 1258 concurrent requests
Prefill Compute Limit: 21 concurrent requests
Decode Capacity Limit: 25 concurrent requests
Theoretical Overall Limit: 21 concurrent requests
Empirical Optimal Concurrency: 16 concurrent requests === Tuning Commands === --- VLLM ---
Simple (concurrency + TP/DP):
llm-optimizer --framework vllm --model Qwen/Qwen3-4B --gpus 4 --host 127.0.0.1 --server-args "tensor_parallel_size*data_parallel_size=[(1, 4), (2, 2), (4, 1)]" --client-args "dataset_name=random;random_input_len=1024;random_output_len=512;random_range_ratio=0.95;num_prompts=3072;max_concurrency=[512, 1024, 1536]" --output-dir tuning_results --output-json tuning_results/config_1_vllm.json
Advanced (additional parameters):
llm-optimizer --framework vllm --model Qwen/Qwen3-4B --gpus 4 --host 127.0.0.1 --server-args "tensor_parallel_size*data_parallel_size=[(1, 4), (2, 2), (4, 1)];max_num_batched_tokens=[16384, 24576, 32768]" --client-args "dataset_name=random;random_input_len=1024;random_output_len=512;random_range_ratio=0.95;num_prompts=3072;max_concurrency=[512, 1024, 1536]" --output-dir tuning_results --output-json tuning_results/config_1_vllm.json
运行基准测试
在获得估算输出后,可以根据先前定义的约束对使用哪些参数进行基准测试做出明智的决定。在底层,LLM-Optimizer 从理论估算过渡到实证验证,它启动一个分布式基准测试循环,在目标硬件上评估真实的服务性能。对于服务器和客户端参数的每个排列,该工具会自动使用指定的张量并行、批处理大小和令牌限制启动一个 vLLM 实例,然后使用合成或基于数据集的请求生成器(例如 ShareGPT)驱动负载。每次运行都会捕获低级指标——首次令牌时间 (TTFT)、令牌间延迟 (ITL)、端到端延迟、每秒令牌数和 GPU 内存利用率——跨越并发请求模式。这些测量结果被聚合到一个帕累托前沿,使 LLM-Optimizer 能够在用户的约束范围内识别出最佳平衡延迟和吞吐量的配置。本质上,此步骤将先前的理论上限分析与真实性能数据相结合,生成直接影响部署调整的可重现指标。
以下代码使用估算中的信息来运行基准测试:
llm-optimizer \
--framework vllm \
--model Qwen/Qwen3-4B \
--server-args "tensor_parallel_size=[1,2,4];max_num_batched_tokens=[4096,8192,16384]" \
--client-args "max_concurrency=[32,64,128];num_prompts=1000;dataset_name=sharegpt" \
--output-json vllm_results.json
这将输出以下排列到 vLLM 引擎中进行测试。以下是对基准测试运行的不同客户端和服务器参数组合的简单计算:
- 3 个
tensor_parallel_sizex 3 个max_num_batched_tokens设置 = 9 - 3 个
max_concurrencyx 1 个num prompts= 3 - 9 * 3 = 27 种不同的测试
完成后,会生成三个工件:
- 包含结果帕累托仪表板的 HTML 文件:一个交互式可视化界面,突出显示在不同测试配置下的延迟和吞吐量权衡。
- 汇总基准测试结果的 JSON 文件:此紧凑的输出聚合了每个测试排列的关键性能指标(例如,延迟、吞吐量、GPU 利用率),用于程序化分析或下游自动化。
- 包含单个基准测试运行完整记录的 JSONL 文件:每行代表一个测试配置,带有详细的元数据,可以进行细粒度检查、过滤或自定义绘图。
示例基准测试记录输出:
{"config": {"client_args": {"max_concurrency": 32, "num_prompts": 1000, "dataset_name": "sharegpt"}, "server_args": {"tensor_parallel_size": 4, "max_num_batched_tokens": 8192}, "server_cmd_args": ["--tensor-parallel-size=4", "--max-num-batched-tokens=8192"]}, "results": {"backend": "vllm", "dataset_name": "sharegpt", "max_concurrency": 32, "duration": 178.69010206999883, "completed": 1000, "total_input_tokens": 302118, "total_output_tokens": 195775, "total_output_tokens_retokenized": 195764, "request_throughput": 5.5962808707125085, "input_throughput": 1690.7371840979215, "output_throughput": 1095.6118874637414, "mean_e2e_latency_ms": 5516.473195931989, "median_e2e_latency_ms": 3601.3218250000136, "std_e2e_latency_ms": 6086.249975393793, "p95_e2e_latency_ms": 17959.23558074991, "p99_e2e_latency_ms": 23288.202798799084, "mean_ttft_ms": 134.24923809297798, "median_ttft_ms": 75.87540699933015, "std_ttft_ms": 219.7887602629944, "p95_ttft_ms": 315.9690581494033, "p99_ttft_ms": 1222.5397153301492, "mean_tpot_ms": 28.140094508604655, "median_tpot_ms": 27.28665116875758, "std_tpot_ms": 7.497764233364623, "p95_tpot_ms": 36.30593537913286, "p99_tpot_ms": 48.05242155004177, "mean_itl_ms": 27.641122410215683, "median_itl_ms": 21.38108600047417, "std_itl_ms": 28.983685761892183, "p95_itl_ms": 64.98022639971161, "p99_itl_ms": 133.48110956045272, "concurrency": 30.871733420192484, "accept_length": null}, "cmd": "vllm serve Qwen/Qwen3-4B --host 127.0.0.1 --port 8000 --tensor-parallel-size=4 --max-num-batched-tokens=8192", "constraints": [], "metadata": {"gpu_type": "NVIDIA L4", "gpu_count": 4, "model_tag": "Qwen/Qwen3-4B", "input_tokens": -1, "output_tokens": -1}}
{"config": {"client_args": {"max_concurrency": 64, "num_prompts": 1000, "dataset_name": "sharegpt"}, "server_args": {"tensor_parallel_size": 4, "max_num_batched_tokens": 8192}, "server_cmd_args": ["--tensor-parallel-size=4", "--max-num-batched-tokens=8192"]}, "results": {"backend": "vllm", "dataset_name": "sharegpt", "max_concurrency": 64, "duration": 151.1696548789987, "completed": 1000, "total_input_tokens": 302118, "total_output_tokens": 195775, "total_output_tokens_retokenized": 195768, "request_throughput": 6.615084229704922, "input_throughput": 1998.5360173099916, "output_throughput": 1295.068115070481, "mean_e2e_latency_ms": 8939.159275709007, "median_e2e_latency_ms": 6008.622306500911, "std_e2e_latency_ms": 9605.635172303826, "p95_e2e_latency_ms": 27139.969452801306, "p99_e2e_latency_ms": 37183.75254391998, "mean_ttft_ms": 251.3472756509782, "median_ttft_ms": 116.74506849976751, "std_ttft_ms": 491.6096066277092, "p95_ttft_ms": 1224.981592999029, "p99_ttft_ms": 2902.0978502906837, "mean_tpot_ms": 48.65581712437634, "median_tpot_ms": 45.59879392866151, "std_tpot_ms": 31.47685312628492, "p95_tpot_ms": 65.96288688333136, "p99_tpot_ms": 130.59083745436504, "mean_itl_ms": 44.61668980280019, "median_itl_ms": 33.35350599991216, "std_itl_ms": 44.581804322583615, "p95_itl_ms": 111.47860099845275, "p99_itl_ms": 222.5829249997332, "concurrency": 59.133291551563126, "accept_length": null}, "cmd": "vllm serve Qwen/Qwen3-4B --host 127.0.0.1 --port 8000 --tensor-parallel-size=4 --max-num-batched-tokens=8192", "constraints": [], "metadata": {"gpu_type": "NVIDIA L4", "gpu_count": 4, "model_tag": "Qwen/Qwen3-4B", "input_tokens": -1, "output_tokens": -1}}
{"config": {"client_args": {"max_concurrency": 128, "num_prompts": 1000, "dataset_name": "sharegpt"}, "server_args": {"tensor_parallel_size": 4, "max_num_batched_tokens": 8192}, "server_cmd_args": ["--tensor-parallel-size=4", "--max-num-batched_tokens=8192"]}, "results": {"backend": "vllm", "dataset_name": "sharegpt", "max_concurrency": 128, "duration": 133.0894289429998, "completed": 1000, "total_input_tokens": 302118, "total_output_tokens": 195775, "total_output_tokens_retokenized": 195771, "request_throughput": 7.513744765020255, "input_throughput": 2270.0375409183894, "output_throughput": 1471.0033813718405, "mean_e2e_latency_ms": 14910.240386960006, "median_e2e_latency_ms": 10384.713371499856, "std_e2e_latency_ms": 15223.620712896502, "p95_e2e_latency_ms": 43486.963950149395, "p99_e2e_latency_ms": 61421.81745829036, "mean_ttft_ms": 663.0696945789732, "median_ttft_ms": 189.89979050093098, "std_ttft_ms": 1407.5295299267668, "p95_ttft_ms": 4652.777336598592, "p99_ttft_ms": 7000.883197711337, "mean_tpot_ms": 91.83800469031593, "median_tpot_ms": 77.46479336456856, "std_tpot_ms": 94.19538916493616, "p95_tpot_ms": 125.3206487750731, "p99_tpot_ms": 500.0748501195875, "mean_itl_ms": 73.16857466775902, "median_itl_ms": 49.85373300041829, "std_itl_ms": 72.57371615955182, "p95_itl_ms": 172.3669967985188, "p99_itl_ms": 328.1056552407972, "concurrency": 112.03174065271433, "accept_length": null}, "cmd": "vllm serve Qwen/Qwen3-4B --host 127.0.0.1 --port 8000 --tensor-parallel-size=4 --max-num-batched-tokens=8192", "constraints": [], "metadata": {"gpu_type": "NVIDIA L4", "gpu_count": 4, "model_tag": "Qwen/Qwen3-4B", "input_tokens": -1, "output_tokens": -1}}
解包基准测试结果后,我们可以利用不同并发级别下的 p99 端到端延迟和请求吞吐量指标来做出明智的决定。基准测试结果显示,跨可用 GPU 的张量并行配置 4 始终优于较低的并行度设置,最佳配置是 tensor_parallel_size=4、max_num_batched_tokens=8192 和 max_concurrency=128,实现了 7.51 请求/秒和 2,270 输入令牌/秒——与天真的单 GPU 基线 (2.74 req/s) 相比,吞吐量提高了 2.7 倍。虽然此配置在重负载下实现了峰值吞吐量,但伴随着较高的 p99 端到端延迟(61.4 秒);对于延迟敏感的工作负载,最佳点是 tensor_parallel_size=4 配合 max_num_batched_tokens=4096,在中等并发量 (32) 下,它仍能提供 5.63 req/s(是基线吞吐量的一倍多),同时保持低于 24 秒的 p99 延迟。数据显示,从天真的单 GPU 设置迁移到具有调整批处理大小的优化 4 路张量并行,可以释放出巨大的性能增益,具体配置选择取决于部署是优先考虑最大吞吐量还是延迟保证。
为了可视化结果,LLM-Optimizer 提供了一个方便的函数来在帕累托仪表板中查看输出图表。可以使用以下代码行显示帕累托仪表板:
llm-optimizer visualize --data-file vllm_results.json... [内容被截断]
🚀 想要体验更好更全面的AI调用?
欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。
评论区