View a markdown version of this page

用于推理的分解预填和解码 HyperPod - 亚马逊 SageMaker AI
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

用于推理的分解预填和解码 HyperPod

分解预填和解码 (DPD) 将 LLM 推理的两个阶段(预填和解码)分离到专用 GPU 池中,并使用远程直接内存访问 (RDMA) 通过 Elastic Fabric Adapter (EFA) Fabric Adapter 在它们之间传输键值 (KV) 缓存。 GPU-Direct

当预填和解码在同一 GPU(同置)上运行时,单个长上下文请求可能会阻碍其他客户端的动态令牌流,从而增加负载下每个令牌的延迟。DPD 通过在一组 GPU 上运行计算绑定预填充,在另一组 GPU 上运行内存带宽限制的解码来消除这种干扰,从而在混合流量下产生更可预测的延迟,并允许您独立扩展每个阶段。

推理操作员负责编排,包括配置路由器、通过 LmCache 和 NIXL 将预填和解码 pod 连接在一起,以及与可观察性集成。 HyperPod 您可以通过在已用于推理端点的InferenceEndpointConfig资源中添加一个pdSpec部分来启用 DPD。

当 DPD 提供帮助时

当满足以下所有条件时,DPD 可提供最大的收益:

  • 大型密集模型 — 70B 以上的参数(例如 Llama 3.3 70B)。

  • 长输入 — 4,000 多个输入标记。 Inter-token 延迟 (ITL) 的改善随输入长度而变化,因为较长的预填充在一起时会导致更多的解码干扰。

  • 持续并发 — 每秒 2 个以上的请求。如果没有并发请求争夺同一 GPU,就没有什么可分解的。

  • 中等或长输出 — 256 个以上的输出标记。更多的输出代币意味着稳定的每代币延迟带来更多的累积收益。

如果您的工作负载输入较短、并发性较低或使用小型模型,则标准托管部署会更简单且性能良好。

先决条件

在部署使用分解预填和解码的推理端点之前,您需要在本地开发环境中设置以下组件:

  • Amazon 命令行界面 (Amazon CLI)

  • 通过 kubectl 访问您的 HyperPod 亚马逊 E K S 集群

  • Huggin g Face 令牌,允许读取相应模型检查点。如果模型检查点已位于 Amazon S3 存储桶中,则无需执行此操作。

  • 包括 vlLM、lmCache、NVIDIA NIXL 和 EFA libfabric 提供商的工作映像。支持以下图像选项:

    • DLC:public.ecr.aws/deep-learning-containers/vllm:server-hyperpod-cuda-v1.1

    • lmCache:lmcache/vllm-openai:v0.4.3

    这两张图片都包括 lmCache 0.4.3、vlLM 0.19.0 和 NIXL 1.0.0。

  • HyperPod 已安装推理运算符 3.2 或更高版本。早期版本不支持 DPD。默认情况下,操作员安装在新创建的 HyperPod Amazon EKS 集群中。如果您打算使用现有集群,请按照中的安装说明进行操作设置 HyperPod 集群以进行模型部署。验证您的版本:

    kubectl get deployment hyperpod-inference-operator-controller-manager \ -n hyperpod-inference-system \ -o jsonpath='{.spec.template.spec.containers[?(@.name=="manager")].image}{"\n"}'
重要

分解预填和解码需要支持 GPU-Direct RDMA 的 EFA-capable 实例。支持以下实例类型:ml.p5.48xlargeml.p5e.48xlargeml.p5en.48xlargeml.p6-b200.48xlargeml.p6-b300.48xlarge。DPD 不支持其他实例类型。

部署 DPD 终端节点

大多数InferenceEndpointConfig字段都与非 DPD 端点共享并记录在中。部署基础模型和自定义经过微调的模型要启用 DPD,请在清单中添加以下部分。

Prefill-Decode 规格:p d Spec

声明 prefill/decode 拓扑并指定参数。此字段的存在是端点分解的原因:操作员为预填和解码创建单独的部署,并通过路由器和 LmCache PD 后端将它们连接在一起。

pdSpec: prefillSpec: replicas: 1 resources: limits: nvidia.com/gpu: ${GPUS_PER_NODE} requests: nvidia.com/gpu: ${GPUS_PER_NODE} args: - "--gpu-memory-utilization" - "0.75" decodingSpec: replicas: 1 resources: limits: nvidia.com/gpu: ${GPUS_PER_NODE} requests: nvidia.com/gpu: ${GPUS_PER_NODE} routingThreshold: 4096
replicas

独立缩放预填和解码。

resources

应用于角色的 pod 规范。 Top-levelworker.resources对于 DPD 窗格,会被忽略;每个角色的值会被覆盖。

routingThreshold

将请求路由到分解路径的令牌长度阈值。不符合此阈值的请求会绕过预填器,直接进入解码器。

args

特定于该角色的 vlLM 标志。启动worker.args时合并到:已worker.args存在的标志将替换为每个角色的值;附加不存在的标志。

DPD 环境变量:环境变量

这些环境变量以相同的方式应用于预填器和解码器容器;没有每个角色的 env-var 字段。对于每个角色的行为,请pdSpec.{prefillSpec,decodingSpec}.args改用。

environmentVariables: - name: PD_BUFFER_SIZE value: "8589934592" - name: LMCACHE_SAVE_DECODE_CACHE value: "False" - name: PYTHONHASHSEED value: "0"
PD_BUFFER_SIZE(8 GiB)

解码器上预留的 GPU 缓冲区用于传入的 KV 缓存传输,按等级大小调整。对于 TP=8 的 Llama 70B,每个令牌的 KV 缓存约为每个等级 40 KB,因此 6000 个令牌的提示每个等级占用大约 0.23 GB,8 GiB 可容纳大约 35 次此类飞行中传输。当缓冲区超出容量时,解码器会记录日志Failed to allocate memory object, retrying...,客户端会看到延迟峰值。decodingSpec.replicas如果需要,可以增加到 16/32 GiB 或缩放。

LMCACHE_SAVE_DECODE_CACHE: "False"

禁用解码器上的冗余 L1 缓存。预填器是缓存命中率的真实来源。

PYTHONHASHSEED: "0"

LmCache 使用 Python 的内置功能hash()来计算提示令牌缓存密钥。默认情况下,Python 会将每个进程的哈希种子随机化,因此相同的提示会在预填器和解码器上生成不同的按键,查找会失败。固定种子会使各吊舱的密钥一致。

配置路由策略

intelligentRoutingSpec部分设置了 DPD 路由器用来为每个请求选择预填器的路由策略。路由器存在时pdSpec会自动创建;此部分是可选的,默认为prefixaware

intelligentRoutingSpec: enabled: true routingStrategy: prefixaware

DPD 还可以与智能路由和 KV 缓存集成。有关更多信息,请参阅 配置 KV 缓存和智能路由

使用单个预填副本,所有策略都会路由到该副本。只有在以下情况下,该选择才会影响行为prefillSpec.replicas > 1

  • 对于单个预填副本,当提示共享系统提示或聊天记录等常见前缀时,使用prefixaware(默认)最大限度地提高 KV 缓存命中率。

  • 对于多个预填副本,请使用在副本之间均匀分配负载,避免roundrobin对单个预填充器进行热点。

完整示例

以下清单在两个 ml.p5.48xlarge 实例(一个预填器,一个解码器)上部署 Llama 3.3 70B:

apiVersion: inference.sagemaker.aws.amazon.com/v1 kind: InferenceEndpointConfig metadata: name: dpd-test namespace: default spec: endpointName: dpd-test instanceType: ml.p5.48xlarge invocationEndpoint: v1/chat/completions modelName: Llama-3.3-70B-Instruct modelSourceConfig: modelSourceType: s3 modelLocation: Llama-3.3-70B-Instruct s3Storage: bucketName: <YOUR_BUCKET> region: <YOUR_REGION> loadBalancer: healthCheckPath: /health metrics: enabled: true kvCacheSpec: enableL1Cache: true intelligentRoutingSpec: enabled: true routingStrategy: prefixaware pdSpec: prefillSpec: replicas: 1 resources: requests: nvidia.com/gpu: "8" limits: nvidia.com/gpu: "8" decodingSpec: replicas: 1 resources: requests: nvidia.com/gpu: "8" limits: nvidia.com/gpu: "8" routingThreshold: 4096 worker: image: public.ecr.aws/deep-learning-containers/vllm:server-hyperpod-cuda-v1.1 args: - "--model" - "/opt/ml/model" - "--host" - "0.0.0.0" - "--port" - "8000" - "--tensor-parallel-size" - "8" - "--max-model-len" - "16384" - "--gpu-memory-utilization" - "0.75" modelInvocationPort: name: http containerPort: 8000 modelVolumeMount: name: model-weights mountPath: /opt/ml/model resources: requests: cpu: "96" memory: 1024Gi nvidia.com/gpu: "8" limits: cpu: "96" memory: 1024Gi nvidia.com/gpu: "8" environmentVariables: - name: HF_HOME value: /tmp/hf_home - name: PD_BUFFER_SIZE value: "8589934592" - name: LMCACHE_SAVE_DECODE_CACHE value: "False" - name: PYTHONHASHSEED value: "0"

应用清单:

kubectl apply -f inference_endpoint_dpd_config.yaml

验证部署

图像拉取和模型加载需要几分钟。监控 pod 状态:

kubectl get pods -A \ | grep -E "prefill-|decode-|router"

健康的部署显示:

NAMESPACE NAME READY STATUS RESTARTS AGE default prefill-dpd-test-XXXX 3/3 Running 0 7m default decode-dpd-test-XXXX 3/3 Running 0 7m hyperpod-inference-system dpd-test-router-XXXX 2/2 Running 0 7m

每个模型 pod 有 3 个容器(vlLM 工作器、Nginx 反向代理、收集器)。 OpenTelemetry路由器 pod 有 2 个容器(路由器、 OpenTelemetry 收集器)。检查InferenceEndpointConfig状态:

kubectl get inferenceendpointconfig dpd-test -n default \ -o jsonpath='{.status.conditions[0].message}{"\n"}'

预期输出:DPD prefill and decode deployments are ready

验证 DPD 角色

确认预填报告sender和解码器报告。receiver这是最具辨别力的启动信号——如果两个 pod 都报告相同的角色或者都没有打印线路,则操作员没有正确接线 DPD。

PREFILL_POD=$(kubectl get pod -n ${NAMESPACE} \ -l 'inference.sagemaker.aws.amazon.com/dpd-role=prefill' \ -o jsonpath='{.items[0].metadata.name}') DECODE_POD=$(kubectl get pod -n ${NAMESPACE} \ -l 'inference.sagemaker.aws.amazon.com/dpd-role=decode' \ -o jsonpath='{.items[0].metadata.name}') kubectl logs $PREFILL_POD -n ${NAMESPACE} -c prefill-${DEPLOYMENT_NAME} \ | grep -oE "'pd_role': '[a-z]+'" | sort -u kubectl logs $DECODE_POD -n ${NAMESPACE} -c decode-${DEPLOYMENT_NAME} \ | grep -oE "'pd_role': '[a-z]+'" | sort -u

预期输出:

'pd_role': 'sender' 'pd_role': 'receiver'

调用端点

端点准备就绪后,发送简短和长长的提示以使用两条路由路径,然后检查日志以确认通过 EFA 传输 KV。

PREFILL_POD=$(kubectl get pod -n ${NAMESPACE} \ -l 'inference.sagemaker.aws.amazon.com/dpd-role=prefill' \ -o jsonpath='{.items[0].metadata.name}') DECODE_POD=$(kubectl get pod -n ${NAMESPACE} \ -l 'inference.sagemaker.aws.amazon.com/dpd-role=decode' \ -o jsonpath='{.items[0].metadata.name}') ROUTER_POD=$(kubectl get pods -n hyperpod-inference-system -o name \ | grep -- "${DEPLOYMENT_NAME}-${NAMESPACE}-router" | head -1) ROUTER_URL=http://${DEPLOYMENT_NAME}-${NAMESPACE}-routing-service.hyperpod-inference-system.svc.cluster.local:443/v1/chat/completions

短提示(低于阈值,直接进入解码器)

令牌数少于routingThreshold绕过预填器并直接进入解码器的请求:

kubectl run curl-short --rm -it --image=curlimages/curl --restart=Never -- \ curl -s -k -X POST "$ROUTER_URL" \ -H "Content-Type: application/json" \ -d '{ "model": "/opt/ml/model", "messages": [{"role": "user", "content": "What is disaggregated prefill-decode in one sentence?"}], "max_tokens": 80, "temperature": 0.0 }'

长提示(超过阈值,DPD 路径)

超过阈值的请求通过预填器进行KV缓存计算,然后路由到解码器以生成令牌:

kubectl run curl-long --rm -it --image=curlimages/curl --restart=Never -- sh -c ' LONG="" i=0; while [ $i -lt 600 ]; do LONG="${LONG}The quick brown fox jumps over the lazy dog. "; i=$((i+1)); done curl -s -k -X POST "'"$ROUTER_URL"'" \ -H "Content-Type: application/json" \ -d "{\"model\":\"/opt/ml/model\",\"messages\":[{\"role\":\"user\",\"content\":\"${LONG}\"}],\"max_tokens\":30,\"temperature\":0.0}" '

验证 KV 传输

发送长提示后,通过检查解码器日志来确认 KV 缓存已传输:

kubectl logs $DECODE_POD -n ${NAMESPACE} -c decode-${DEPLOYMENT_NAME} \ | grep -E "Retrieved.*tokens.*throughput" | tail -2

预期输出(每个 TP 等级各占一行):

[Worker_TP5] [LMCache INFO] [req_id=cmpl-...] Retrieved 6035 out of 6035 required tokens (from 6035 total tokens). size: 0.2344 gb, cost 1.3304 ms, throughput: 176.1686 GB/s

Retrieved N out of N required tokens如果设置为 N > 0,则确认 KV 缓存成功穿过 NIXL 通道。如果你看到Retrieved 0 out of N,解码器又回到了本地重新计算——请参阅。分解的预填和解码 (DPD) 部署问题

您还可以在路由器日志中验证路由决定:

kubectl logs $ROUTER_POD -n hyperpod-inference-system -c router-container --tail=20 \ | grep -E "Conditional routing"

对于长提示,你应该看到:

[INFO] Conditional routing: estimated_tokens=6750, threshold=4096, disaggregate=True

对于简短的提示:

[INFO] Conditional routing: estimated_tokens=12, threshold=4096, disaggregate=False
注意

要通过 SageMaker AI AI 终端节点进行调用,请在您的endpointName中进行设置InferenceEndpointConfig。如果未设置,endpointName则不会创建 SageMaker AI AI 终端节点,并且只能直接调用 ALB。

可观测性

通过metrics.enabled: true在您的中设置来启用指标InferenceEndpointConfig。DPD 指标可在 HyperPod 推理仪表板中找到。有关更多信息,请参阅 在集群上实现推理可观测性 HyperPod

以下 DPD-specific 指标可用:

DPD-specific 指标
指标 说明
E2E TTFT 获得第一个代币的总时间(预填+KV 转账+路由)
预先填充 TTFT Prefiller-only 延迟
预填队列 等待预填的请求数
解码队列 在解码器上等待的请求数
预填时间 花在预填计算上的时间
解码延迟 Per-token 输出延迟 (TPOT)
KV 传输时间 是时候将 KV 缓存从预填器传输到解码器了
DPD 路由计数 分解请求与后备请求(低于阈值)

调整您的 DPD 部署

下表提供了根据您在指标控制面板中观察到的症状调整 DPD 的快速参考。

DPD 调整参考
Config 作用 默认 何时调音
pdSpec.routingThreshold 通过预填器路由的最小输入令牌。低于此阈值的请求会直接进入解码器。 4096 默认设置适用于大多数工作负载。将其设置得太低会增加 TTFT,因为在短提示时会进行不必要的 KV 传输,而将其设置得太高会限制 TPOT 的改进,因为通过 DPD 路径的请求较少。
pdSpec.prefillSpec.replicas 预填充舱的数量。 1 如果预填队列深度较高,则扩大规模,以改善预填充的 TTFT。
PD_BUFFER_SIZE 用于传入 KV 传输的解码器 GPU 缓冲区(按等级)。在 TP=8 时,8 GiB 可容纳大约 35 次机上 6 次K-token 转移,价格为 70B。 "8589934592"(8 GiB) 增加以处理更多并发 KV 传输。如果发现内存问题,请减小。增加时,您可能需要降低--gpu-memory-utilization解码器的容量,以便为较大的缓冲区释放 GPU 内存。
--gpu-memory-utilization vLLM 用于权重、激活和 KV 缓存的 GPU 内存的一部分。 0.75 增加长输入时可获得更多 KV 缓存余量。风险:预填充器 OOM,因为预填还需要内存才能激活。使用您的实际输入长度分布进行测试。
--max-num-seqs 每个工作批次的最大并发序列数。 16(预填充器)、32(解码器) 在负载下提高批处理效果更好。如果击中预填充器上的 OOM,则降低。通过pdSpec.{prefillSpec,decodingSpec}.args按角色设置。
intelligentRoutingSpec.routingStrategy 当存在多个副本时,路由器如何选择预填器。 prefixaware roundrobin用于在多个预填器副本之间均匀分配负载。使用prefixawarekvaware与单个前缀一起使用,或者当提示共享通用前缀(系统提示、聊天记录)时,可以最大限度地提高缓存命中率。

使用您的实际工作负载和输入长度分布进行测试。

要应用配置更改,请编辑您的部署 YAML 并重新应用:

kubectl apply -f inference_endpoint_dpd_config.yaml

已知限制条件

  • 对于具有 70B 或更多参数的密集模型,建议使用 DPD。较小的模型和 Mixture-of-Experts 模型通常无法从分解中受益。

  • 当前版本支持每个端点进行单一解码部署。计划在 future 版本中支持多种解码部署。

  • 使用 Llama 3.3 70B 在 ml.p5.48xlarge 上验证了多达 64 个并发请求的性能。

  • 要从 DPD 部署恢复到标准托管部署,请应用不带托管的新InferenceEndpointConfig部署。pdSpec

要对 DPD 部署进行故障排除,请参阅分解的预填和解码 (DPD) 部署问题