本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
用于推理的分解预填和解码 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 个以上的输出标记。更多的输出代币意味着稳定的每代币延迟带来更多的累积收益。
如果您的工作负载输入较短、并发性较低或使用小型模型,则标准托管部署会更简单且性能良好。
先决条件
在部署使用分解预填和解码的推理端点之前,您需要在本地开发环境中设置以下组件:
-
通过 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.48xlarge、ml.p5e.48xlarge、ml.p5en.48xlarge、ml.p6-b200.48xlarge、ml.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-level
worker.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 指标可用:
| 指标 | 说明 |
|---|---|
| E2E TTFT | 获得第一个代币的总时间(预填+KV 转账+路由) |
| 预先填充 TTFT | Prefiller-only 延迟 |
| 预填队列 | 等待预填的请求数 |
| 解码队列 | 在解码器上等待的请求数 |
| 预填时间 | 花在预填计算上的时间 |
| 解码延迟 | Per-token 输出延迟 (TPOT) |
| KV 传输时间 | 是时候将 KV 缓存从预填器传输到解码器了 |
| 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用于在多个预填器副本之间均匀分配负载。使用prefixaware或kvaware与单个前缀一起使用,或者当提示共享通用前缀(系统提示、聊天记录)时,可以最大限度地提高缓存命中率。 |
使用您的实际工作负载和输入长度分布进行测试。
要应用配置更改,请编辑您的部署 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) 部署问题。