分布式AI系统-六-

分布式AI系统(六)

原文:Distributed AI Systems

译者:飞龙

协议:CC BY-NC-SA 4.0

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_457_0.png

图 9.4 展示了一个 8 卡 A100 节点的示例输出(完整输出见 code/k3d/kubectl_describe_node.txt)。在输出中,Capacity 表示检测到的 GPU 总数,Allocatable 显示可用于 Pod 调度的数量,而 Allocated 则以请求/限制的形式显示当前使用情况(零表示目前还没有 Pod 使用 GPU)。由于 k3d 会将宿主机上的所有 GPU 传递给每个容器,因此每个节点报告的 GPU 数量相同——这是本地开发的预期行为。

关于集群自定义选项(如挂载模型目录或选择特定 GPU),请参阅 code/k3d/README.md。

在 k3d 上部署 vLLM

GPU 集群运行起来后,我们现在可以部署 vLLM 来提供 LLM 推理服务。code/k3d/vllm/ 目录包含多个模型的即用型 Kubernetes 清单,因此你无需从头编写 YAML。

Hugging Face 上的许多流行模型都是“受限”的,也就是说你需要接受其许可条款并进行身份验证后才能下载。Llama 系列模型就属于这一类。如果你要部署受限模型,请先使用我们先前设置的 HF_TOKEN 环境变量创建一个 Kubernetes Secret:

kubectl create secret generic hf-token-secret --from-literal=token="$HF_TOKEN"

现在你可以部署模型了。在生产环境中,模型选择既取决于你可用的 GPU 内存,也取决于延迟、吞吐量和输出质量等业务需求。在本演示中,我们使用较小的模型。Phi-tiny-MoE 是一个轻量级选项,非常适合在较小的 GPU 上进行测试,而 Llama-3.2-1B 质量更好,但大约需要 8GB GPU 内存:

cd code/k3d/vllm
kubectl apply -f llama-3.2-1b.yaml
# or ./deploy-phi-tiny-moe.sh for smaller GPUs

这些部署清单处理了你原本需要手动配置的细节:GPU 资源请求(让 Kubernetes 将 Pod 调度到有可用 GPU 的节点上)、为模型加载设置合适超时的健康探针、用于缓存已下载权重的卷挂载,以及用于网络访问的 Kubernetes 服务。使用以下命令监控部署进度:

kubectl get pods -l app=vllm -w

# NAME READY STATUS RESTARTS AGE
# vllm-llama-32-1b-pod-xxx 0/1 ContainerCreating 0 2m40s

Pod 最初会显示 ContainerCreating,因为 Kubernetes 正在拉取 vLLM 容器镜像。这个过程可能需要几分钟,具体取决于你的网络速度。当状态变为 Running 后,你可以查看日志:

kubectl logs -l app=vllm --follow

图 9.5:vLLM Pod 日志

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_458_0.png

图 9.5 展示了模型启动时的典型日志输出。如果模型需要从 Hugging Face 下载,日志会显示下载进度,随后是模型被加载到 GPU 内存的过程。模型加载通常需要 2-5 分钟,具体取决于模型大小以及权重是否已在本地缓存。当日志显示“Application startup complete.”时,服务器就准备好接受请求了。

要测试 API,请将服务端口转发到本地机器并发送请求:

kubectl port-forward svc/vllm-llama-32-1b-service 8000:8000 &
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
  "model": "meta-llama/Llama-3.2-1B-Instruct",
  "messages": [{"role": "user", "content": "Hello!"}],
  "max_tokens": 50
}'

图 9.6:来自 Kubernetes 部署的 vLLM 响应

图 9.6 展示了运行在 Kubernetes 中的 vLLM 服务器返回的成功响应。该 JSON 响应遵循 OpenAI 聊天补全格式,包含模型名称、生成的内容以及 token 使用统计信息。

查看部署清单(code/k3d/vllm/llama-3.2-1b.yaml 和 code/k3d/vllm/phi-tiny-moe.yaml),你会发现几个值得理解的配置模式。

--gpu-memory-utilization 0.2 标志告诉 vLLM 仅保留 20% 的 GPU 内存——远低于默认的约 0.85。这种保守设置适用于共享 GPU 或多模型集群;如果你希望单个模型获得最大吞吐量,请改用 0.8–0.9。

健康探针值得特别关注。Kubernetes 使用存活探针和就绪探针来判断 Pod 是否健康,但 LLM 模型加载到 GPU 内存需要相当长的时间——对于较大的模型,通常需要几分钟。清单将 initialDelaySeconds 设置为 120-180 秒,给模型留出加载时间;如果没有该延迟,Kubernetes 会无限循环地重启 Pod。请允许连续多次失败(failureThreshold 为 3 或更高),这样垃圾回收期间某次缓慢的探针不会被认为是崩溃。

/models 处的卷挂载会在 Pod 重启后保留已下载的模型权重。这一点很重要,因为从 Hugging Face 下载 7B 参数模型需要大量的时间和带宽。一旦缓存,后续 Pod 重启时会直接从磁盘加载模型。

/dev/shm 处的共享内存很容易被忽视。Docker 的默认段只有 64MB,然而张量并行的 vLLM worker 会通过它路由 NCCL 和 IPC 缓冲区——空间太小会导致集合通信挂起,且几乎没有诊断输出。清单会挂载一个更大的卷(使用 medium: Memory 的 emptyDir,或容器上等效的 --shm-size),其大小与你的张量并行度相匹配。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_459_0.png

对于无法在单个 GPU 上容纳的更大模型,你可以通过将 --tensor-parallel-size 设置为 GPU 数量并相应更新资源限制来对模型进行分片。code/k3d/README.md 文件提供了多 GPU 配置和排查常见问题的详细指南。

清理

实验完成后,清理集群以释放资源:

k3d cluster delete mycluster-gpu
docker rmi k3s-cuda:<your-tag> # optional, removes the custom image

多模型与多引擎服务

在单个 vLLM 部署成功运行后,我们现在可以探索更复杂的服务模式。生产环境中的部署很少只服务一个模型——你可能会针对不同任务使用不同的模型(小型模型处理简单查询,大型模型处理复杂推理),或者你可能希望对不同的模型或推理引擎进行 A/B 测试。

code/k3d/ 目录提供了两个自动化这些部署模式的管理脚本:

  • manage-cluster-multi-models.sh: 使用单一引擎(vLLM)部署多个模型(Llama-3.2-1B + Phi-tiny-MoE)

  • manage-cluster-multi-engines.sh: 在多个引擎(vLLM + SGLang)上部署同一模型(Llama-3.2-1B)

多模型路由

关键的洞察在于,API 网关可以根据 OpenAI 兼容请求体中的 model 字段来路由请求。当客户端发送指定 "model": "meta-llama/Llama-3.2-1B-Instruct" 的请求时,网关会查找哪个 Kubernetes 服务托管该模型,并相应地转发请求。这样就创建了一个统一的端点,客户端无需知道哪个后端服务器处理哪个模型。

图 9.7 展示了这种架构。客户端应用向 API 网关发送请求,网关解析 model 字段并将请求转发到相应的 vLLM 服务。每个模型运行在自己的 Pod 中,并配有专用的 Kubernetes 服务。

图 9.7:多模型路由架构

让我们部署这种架构。管理脚本处理了所有复杂性——创建命名空间、部署两个模型以及设置网关:

cd code/k3d
./manage-cluster-multi-models.sh start

我们可以通过以下命令检查部署状态:

./manage-cluster-multi-models.sh status

状态命令会显示正在运行的内容。你应该会看到两个 vLLM Pod(每个模型一个)及其对应的服务:

==========================================
k3d Cluster Status: mycluster-gpu
==========================================

📊 集群列表:

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_461_0.png

| 名称 | 服务器 | 代理 | 负载均衡器 |

|------|---------|--------|--------------|

| mycluster-gpu | 1/1 | 1/1 | true |

Switched to context "k3d-mycluster-gpu".

📊 Kubernetes 节点:

| 名称 | 状态 | 角色 | 存在时间 | 版本 |

|------|--------|-------|-----|---------|

| k3d-mycluster-gpu-agent-0 | Ready | <none> | 5h32m | v1.35.1+k3s1 |

| k3d-mycluster-gpu-server-0 | Ready | control-plane | 5h32m | v1.35.1+k3s1 |

📊 命名空间:

| 名称 | 状态 | 存在时间 |

|------|--------|-----|

| multi-models | Active | 20m |

📊 multi-models 命名空间中的 Pod:

| 名称 | 就绪 | 状态 | 重启次数 | 存在时间 |

|------|-------|--------|----------|-----|

| vllm-llama-32-1b-pod-76895c5cfb-vkz7w | 1/1 | Running | 0 | 20m |

| vllm-phi-tiny-moe-pod-55b8c8c959-nvxjh | 1/1 | Running | 0 | 2m33s |

📊 multi-models 命名空间中的服务:

| 名称 | 类型 | 集群 IP | 外部 IP | 端口 | 存在时间 |

|------|------|------------|-------------|---------|-----|

| vllm-llama-32-1b-service | ClusterIP | 10.43.178.154 | <none> | 8000/TCP | 20m |

| vllm-phi-tiny-moe-service | ClusterIP | 10.43.55.228 | <none> | 8000/TCP | 20m |

该脚本会创建一个 multi-models 命名空间,部署两个 vLLM 模型(Llama-3.2-1B 和 Phi-tiny-MoE),并配置 API 网关。

路由配置(gateway/routing-config.yaml)将模型名称映射到 Kubernetes 服务:

routing:
- model: "meta-llama/Llama-3.2-1B-Instruct"
  service_name: "vllm-llama-32-1b-service.multi-models.svc.cluster.local"
- model: "microsoft/Phi-tiny-MoE-instruct"
  service_name: "vllm-phi-tiny-moe-service.multi-models.svc.cluster.local"

当网关收到带有 "model": "meta-llama/Llama-3.2-1B-Instruct" 的请求时,它会在该表中查找服务名——这里是 vllm-llama-32-1b-service.multi-models.svc.cluster.local,即 <service>.<namespace>.svc.cluster.local——并将请求转发出去,不会向客户端暴露后端布局。

现在让我们测试一下。首先,设置端口转发,以便从你的本地机器访问网关:

kubectl port-forward svc/vllm-api-gateway 8080:8000 &

然后发送请求并获取 Llama 的响应:

$ curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "meta-llama/Llama-3.2-1B-Instruct", 
       "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-8d9100935563642c","object":"chat.completion","created":...,
"model":"meta-llama/Llama-3.2-1B-Instruct","choices":[{"index":0,"message":{
"role":"assistant","content":"Hello! How can I assist you today?"...
"prompt_logprobs":null,"prompt_token_ids":null,"kv_transfer_params":null}

现在向 Phi 模型发送请求——同一个端点,不同的 model 字段:

$ curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "microsoft/Phi-tiny-MoE-instruct", 
       "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-a59b963764eb8f41","object":"chat.completion","created":...,
"model":"microsoft/Phi-tiny-MoE-instruct","choices":[{"index":0,"message":{
"role":"assistant","content":" Hello! How can I assist you today?"...
"prompt_logprobs":null,"prompt_token_ids":null,"kv_transfer_params":null}

请注意,两个请求都发送到 localhost:8080,但网关会根据 model 字段将它们路由到不同的后端。这就是多模型路由的强大之处:客户端只与一个端点交互,而由基础设施来处理模型放置。

在进入多引擎路由之前,清理此部署以释放 GPU 资源:

./manage-cluster-multi-models.sh stop

该命令会停止端口转发进程,并删除 multi-models 命名空间中的所有资源。

多引擎路由

更进一步,我们可以在不同的推理引擎(vLLM 和 SGLang)上部署同一模型,并根据 inference_server 字段进行路由。这对于对引擎进行基准测试或在它们之间逐步迁移非常有用。

如图9.8所示,网关解析model和inference_server字段以确定路由。使用多引擎管理脚本:

cd code/k3d
./manage-cluster-multi-engines.sh start

这将创建一个multi-engines命名空间,并在 vLLM 和 SGLang 上部署 Llama-3.2-1B。

我们可以通过运行以下命令来检查部署状态:

$ ./manage-cluster-multi-engines.sh status
==========================================
k3d Cluster Status: mycluster-gpu
==========================================

📊 集群列表:

| 名称 | 服务器 | 代理 | 负载均衡器 |

|------|---------|--------|--------------|

| mycluster-gpu | 1/1 | 1/1 | true |

已切换到上下文 "k3d-mycluster-gpu"。

📊 Kubernetes 节点:

| 名称 | 状态 | 角色 | 运行时间 | 版本 |

|------|--------|-------|-----|---------|

| k3d-mycluster-gpu-agent-0 | 就绪 | <none> | 6h16m | v1.35.1+k3s1 |

| k3d-mycluster-gpu-server-0 | 就绪 | 控制平面 | 6h16m | v1.35.1+k3s1 |

📊 命名空间:

| 名称 | 状态 | 运行时间 |

|------|--------|-----|

| multi-engines | 活动 | 21m |

| multi-models | 活动 | 64m |

📊 命名空间 multi-engines 中的 Pod:

| 名称 | 就绪 | 状态 | 重启次数 | 运行时间 |

|------|-------|--------|----------|-----|

| sglang-llama-32-1b-pod-7dc599696d-67f9r | 1/1 | 运行中 | 0 | 21m |

| vllm-llama-32-1b-pod-76895c5cfb-m9bbw | 1/1 | 运行中 | 0 | 21m |

📊 命名空间 multi-engines 中的服务:

| 名称 | 类型 | 集群 IP | 外部 IP | 端口 | 运行时间 |

|------|------|------------|-------------|---------|-----|

| sglang-llama-32-1b-service | ClusterIP | 10.43.249.196 | <none> | 8000/TCP | 21m |

| vllm-llama-32-1b-service | ClusterIP | 10.43.89.85 | <none> | 8000/TCP | 21m |

图9.8:多引擎路由架构

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_465_0.png

路由配置(code/k3d/gateway/routing-config.yaml)包含特定于引擎的路由,允许客户端选择其偏好的推理引擎:

routing:
  - model: "meta-llama/Llama-3.2-1B-Instruct"
    inference_server: "vllm"
    service_name: "vllm-llama-32-1b-service.multi-engines.svc.cluster.local"
  - model: "meta-llama/Llama-3.2-1B-Instruct"
    inference_server: "sglang"
    service_name: "sglang-llama-32-1b-service.multi-engines.svc.cluster.local"
  - model: "meta-llama/Llama-3.2-1B-Instruct"
    inference_server: null
    # Default to vLLM
    service_name: "vllm-llama-32-1b-service.multi-engines.svc.cluster.local"

第三个条目中的 inference_server: null 用作回退——未指定引擎的请求默认使用 vLLM。

设置端口转发并测试两个引擎:

kubectl port-forward svc/vllm-api-gateway 8080:8000 &

首先,发送一个未指定引擎的请求(默认路由到 vLLM):

$ curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "meta-llama/Llama-3.2-1B-Instruct",
       "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-9583dfa06c9b9669","object":"chat.completion","created":...,
"model":"meta-llama/Llama-3.2-1B-Instruct","choices":[{"index":0,"message":{
"role":"assistant","content":"Hello! How can I assist you today?"...
"prompt_logprobs":null,"prompt_token_ids":null,"kv_transfer_params":null}

现在,通过添加inference_server字段显式路由到 SGLang:

$ curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "meta-llama/Llama-3.2-1B-Instruct",
       "inference_server": "sglang",
       "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"e02f545aca204f...3b20","object":"chat.completion","created":...,
"model":"meta-llama/Llama-3.2-1B-Instruct","choices":[{"index":0,"message":{
"role":"assistant","content":"Hello! How can I assist you today?"...
"usage":{"prompt_tokens":37,"total_tokens":47,"completion_tokens":10...}

网关还会聚合 /v1/models 端点,返回所有后端中所有可用模型的合并列表。对于生产部署,你需要添加身份验证(API 密钥或 OAuth 令牌)、速率限制(每客户端请求配额)和可观测性(请求日志、延迟指标、错误跟踪)。code/k3d/gateway/api-gateway.py 包含了针对这些问题的示例中间件。

使用 llm-d 的 Kubernetes 部署

在上一节中,我们构建了一个本地 k3d 集群来试验 vLLM 部署。虽然这种方法提供了灵活性,并能让你深入了解底层组件,但大规模生产部署通常受益于能够处理分布式推理运维复杂性的标准化解决方案。

目前存在多个 Kubernetes 原生的 LLM 服务框架。KServe [1] 提供企业级模型服务,支持流量治理(金丝雀发布、A/B 测试)和多模型托管,但需要 Istio 或 Knative 作为依赖。KubeAI [2] 提供轻量级 Operator,支持从零伸缩和前缀感知负载均衡,无需外部依赖——非常适合较简单的部署。vLLM production-stack [3] 是官方 vLLM 部署解决方案,集成了 LMCache,支持跨实例的 KV 缓存共享。本章重点介绍 llm-d [4],因为它提供了一个“开箱即用”的部署框架,将经过充分测试的组件(vLLM、Envoy、NIXL)整合为一个内聚系统,提供配置极少且可生产就绪的 Helm charts,并支持分离式预填充/解码以及通过 InfiniBand RDMA 进行高速数据传输等高级功能。这些框架的选择取决于你的具体需求:KServe 适用于企业级流量治理,KubeAI 适用于轻量级零依赖部署,vLLM production-stack 适用于需要 KV 缓存共享的官方 vLLM 支持,而 llm-d 适用于需要分离式推理或高速互连的部署。表 9.2 提供了手动 k3d 方式与 llm-d 之间的详细对比。

什么是 llm-d?

可以把 llm-d 看作一个“开箱即用”的部署框架,它将一流的开源组件整合为一个内聚系统。其核心是,llm-d 使用 vLLM [5] 作为模型服务器——这与我们在 k3d 部分手动部署的引擎相同。但 llm-d 在此之上增加了多个智能层:推理网关(IGW)[6] 充当智能请求调度器,Envoy 代理 [7] 负责负载均衡和路由,NIXL [8] 则支持通过 InfiniBand RDMA 或 TPU ICI 等高速互连进行高速数据传输。Kubernetes 编排所有这些组件,确保它们能够优雅地伸缩和恢复。

主要特性

智能推理调度。 llm-d 最有价值的能力之一是智能请求路由。传统负载均衡器以轮询或基于连接数等简单指标来分发请求。但 LLM 推理具有独特特征:一个包含 10,000 个 token 的提示与一个包含 100 个 token 的提示行为截然不同。llm-d 的 IGW 理解这一点。它可以预测请求延迟并据此进行路由,确保长请求不会阻塞短请求。它还实现了前缀缓存感知路由——如果某个 vLLM 实例已经拥有特定系统提示的 KV 缓存,后续具有相同前缀的请求会被路由到该实例,从而显著缩短首 token 时间。对于企业部署,SLA 感知调度可确保高价值客户优先获得计算资源,同时负载感知均衡会根据每个实例的当前容量来分配工作,而不仅仅是统计连接数。

预填充/解码分离。 llm-d 最具创新性的特性可能是其对分离式推理的支持。在传统的 LLM 服务中,单个 GPU 同时处理预填充阶段(处理输入提示)和解码阶段(逐 token 生成输出)。这两个阶段的计算特征截然不同:预填充是计算密集型且可并行的,而解码则是内存带宽密集型且顺序执行的。通过将它们分离到不同的服务器池,llm-d 可以独立优化每个阶段。预填充服务器可以使用更大的批次大小和更高的 GPU 利用率,而解码服务器则可以针对低延迟进行调整。挑战在于如何在两者之间传输 KV 缓存——这正是 NIXL 的闪光点,它利用 RDMA 在毫秒级移动数 GB 的缓存数据。一个 sidecar 容器协调这些传输,确保解码服务器在需要时准确收到 KV 缓存。

分离式前缀缓存。 基于 vLLM 的 KVConnector 抽象,llm-d 实现了精细的缓存层级。独立缓存(有时称为 N/S,即南北向)将 KV 缓存卸载到本地内存和 NVMe 存储,使单个实例能够比其 GPU 内存通常允许的并发请求数服务更多请求。共享缓存(E/W,即东西向)支持实例之间的 KV 缓存传输,因此如果某个服务器已经计算了常见系统提示的缓存,其他服务器可以直接获取而无需重新计算。对于要求最高的部署,全局索引提供整个集群范围内已缓存前缀的视图,以额外的协调开销为代价实现最优路由决策。

变体自动伸缩。 传统的 Kubernetes 自动伸缩(HPA)基于 CPU 或内存利用率进行伸缩,但 LLM 工作负载需要更智能的伸缩方式。llm-d 的变体自动伸缩器会测量每个模型服务器实例的实际容量——在给定当前内存压力下每秒能生成多少 token。然后分析最近的流量模式:请求大小的组合、服务质量要求和到达速率。基于此分析,它计算出预填充服务器、解码服务器以及为延迟容忍型批量请求预留的实例的最优组合。这实现了真正的 SLO 级效率,在延迟恶化之前而非之后进行扩容。

硬件支持: llm-d 的优势之一是其广泛的硬件兼容性。该项目直接在 NVIDIA GPU(A100、L4 及更新型号)、AMD GPU(MI250 及更新型号)、Google TPU(v5e 及更新型号)以及 Intel 数据中心 GPU Max 系列(Ponte Vecchio)上测试和验证部署。这种多供应商支持正变得越来越重要,因为各组织希望避免锁定并优化不同云提供商的成本。

部署架构

在介绍了llm-d的关键特性——智能调度、预填充/解码分离、分布式缓存和变体自动扩缩容——之后,让我们看看这些组件如何组合在一起形成生产部署。

图9.9展示了完整的架构。为了理解其工作原理,让我们跟踪一个请求在系统中的流转过程。

每个生产级API都需要一个“前门”——一个处理互联网流量各种现实问题的组件:TLS加密、身份验证、限流以及优雅地处理不守规矩的客户端。在llm-d中,这一角色由Envoy Proxy[7]承担,它是一种在云原生部署中广泛使用的高性能边缘代理。你的基础设施中可能已经有Envoy(它为Istio[9]服务网格和许多API网关提供支持);llm-d只是将其作为所有推理请求的入口点。

图9.9:Kubernetes上的llm-d部署架构

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_469_0.png

请求从Envoy流向推理网关(IGW)[6]——它是llm-d的“大脑”。Envoy处理通用的HTTP问题,而IGW理解LLM推理。它检查每个请求的特征:提示词有多长?请求的是哪个模型?任何后端是否已经缓存了相关前缀?基于这些分析,IGW将请求路由到最优的模型服务器。这与传统负载均衡器(基于轮询或连接数分发请求)有根本区别——IGW根据每个请求的实际计算成本做出路由决策。

在IGW后面是执行推理的模型服务器。在标准模式下,每个服务器处理完整的推理流水线。但llm-d也支持分离模式,即服务器专门化:预填充服务器处理输入提示(计算密集型、可并行化),而解码服务器生成输出token(内存带宽受限、对延迟敏感)。当预填充服务器完成处理一个提示时,NIXL[8]边车使用RDMA[10]将KV缓存传输到指定的解码服务器,即使对于较大的缓存,也常常在毫秒内完成。这种分离允许每种服务器类型独立优化——预填充服务器可以积极批处理以提高吞吐量,而解码服务器则针对最小延迟进行调优。

对于服务许多共享前缀请求的部署(常见的系统提示、少样本示例),llm-d可以选择性地将KV缓存存储在共享NVMe[11]存储上。这实现了集群级前缀缓存:当一个服务器为流行前缀计算缓存时,其他服务器可以直接获取,而不是重新计算,从而大幅减少整个集群中的冗余工作。

开始使用llm-d

部署llm-d需要一个生产级Kubernetes集群,版本为1.29或更高。与我们本地的k3d实验不同,llm-d是为拥有高性能硬件的环境设计的:你需要能够运行大型模型的加速器(对于70B+参数模型,需考虑A100或更新型号),并且理想情况下节点内使用NVLink等快速互连,节点间使用InfiniBand或RoCE RDMA。对于Google Cloud部署,TPU ICI和DCN提供类似的高带宽连接。

安装过程利用Helm,即Kubernetes的包管理器。添加llm-d仓库后,一条helm install命令即可部署整个栈——vLLM模型服务器、推理网关、Envoy代理以及所有支撑基础设施。Helm的优雅之处在于,复杂的配置变成了简单的键值对:启用推理网关、指定要服务的模型、设置多GPU推理的张量并行度。

配置通过values.yaml文件完成,该文件读起来几乎像一份规格说明文档。你声明你需要什么——两个具有缓存感知路由的IGW副本,vLLM在四块GPU上以90%内存利用率服务Llama 3.1 70B,独立的预填充和解码服务器池及其各自的副本数和GPU分配——而Helm将其转换为实现这一切所需的数十种Kubernetes资源。自动扩缩容部分尤其优雅:它不是基于CPU利用率(对GPU工作负载而言意义不大)进行扩缩容,而是指定目标每秒查询数,其余由llm-d的变体自动扩缩器处理。

附带的代码目录(code/llmd/)包含完整的、经过测试的部署配置。llm-d-multi-engine/子目录演示了使用vLLM和SGLang后端部署同一模型(Qwen2.5-0.5B-Instruct),展示了llm-d的引擎无关路由。llm-d-multi-model/子目录展示了使用不同Llama变体的多模型部署。每个目录都包含部署脚本、Helm values文件和故障排除指南——你需要在自身环境中复现这些设置所需的一切。

成熟路径

llm-d项目使用“成熟路径”一词来描述经过充分测试和基准测试的部署模式。这些路径不是让用户通过试错来找出最佳配置,而是代表了常见场景下久经考验的配方。

智能推理调度

智能推理调度是大多数部署的起点。通过将vLLM放在推理网关后面,你可以立即获得比轮询更智能的负载均衡。IGW根据提示词长度预测请求延迟并进行相应路由,防止长请求阻塞短请求。它还跟踪哪些vLLM实例缓存了哪些前缀,将重复请求路由到能够更快处理它们的实例。对于刚刚开始生产LLM之旅的团队来说,这条路径在简单性和性能提升之间提供了最佳平衡。

预填充/解码分离

当服务具有长提示的大型模型时,预填充/解码分离变得很有价值。考虑一个70B参数模型处理10,000个token的文档:预填充阶段(计算所有输入token上的注意力)是计算密集型且可并行化的,而解码阶段(逐个生成输出token)是内存带宽受限且串行的。通过将它们分离到不同的服务器池中,每个池都可以独立优化。预填充服务器可以积极批处理以提高吞吐量;解码服务器可以针对最小延迟进行调优。结果是缩短了首token时间(TTFT),并且每输出token时间(TPOT)更可预测。缺点在于KV缓存必须在服务器之间传输,这需要快速互连——这条路径在InfiniBand或NVLink下表现出色,但在较慢的网络上可能不值得增加这种复杂性。

宽专家并行

宽专家并行针对混合专家(MoE)模型,如Mixtral或DeepSeek。这些模型每个token只激活一部分参数,这使得它们在推理时高效但部署起来具有挑战性。专家并行将不同的专家分布到不同的GPU上,而数据并行同时处理多个请求。llm-d协调这一复杂过程,将token路由到正确的专家,同时最大化加速器利用率。对于大规模部署MoE模型的组织来说,这条路径可以显著降低延迟并提高吞吐量。

监控与可观测性

生产级LLM服务需要深入了解系统行为。llm-d与标准Kubernetes监控栈集成——Prometheus用于指标收集,Grafana用于可视化。除了CPU和内存等通用指标外,llm-d还提供LLM特定的遥测数据:请求延迟分布、每秒token吞吐量、整个集群的GPU利用率,以及关键的KV缓存命中率。最后一个指标尤其说明问题:高命中率表明前缀感知路由正在有效工作,而低命中率则表明你可能需要缓存预热策略或调整路由策略。

选择你的路径

对于刚接触生产级LLM服务的团队来说,智能推理调度路径提供了最平缓的学习曲线,并能立即带来好处。你可以通过最少的配置更改从基本的vLLM设置部署它,并通过更智能的路由获得有意义的延迟和吞吐量改进。

随着你的部署逐渐成熟,遇到特定的瓶颈,其他路径就会变得相关。如果用户抱怨长文档的首token时间缓慢,预填充/解码分离可以提供帮助——但前提是你有足够的网络带宽来高效传输KV缓存。如果你正在部署MoE模型并看到次优的GPU利用率,专家并行可能是答案。

关键是要监控你的指标,并让它们指导你的演进。观察KV缓存命中率以评估路由有效性。分别跟踪TTFT和TPOT以了解延迟来源。监控GPU利用率以识别未充分利用的容量。并根据实际流量模式而不是理论估计来调整自动扩缩容参数——将最小副本数设置为足够高以处理基线负载而不会冷启动,最大副本数以适应峰值,并根据每个实例的观察容量设置目标QPS。

多模型服务

正如我们使用k3d部署多个模型一样,llm-d也支持使用生产级基础设施进行多模型服务。code/llmd/llm-d-multi-model/目录包含在vLLM上服务Llama-3.2-1B和Qwen2.5-0.5B的部署配置。

图9.10展示了llm-d的多模型架构。每个模型都有自己的ModelService,InferencePool会自动发现它们,并根据model字段路由请求——无需手动服务映射。这是与我们的k3d设置的关键区别:不是手动配置路由规则,而是llm-d自动发现ModelService实例并构建路由表。

图 9.10:llm-d 多模型服务架构

llm-d-multi-model/ 目录提供了一个管理脚本,用于处理所有复杂性。使用以下命令部署两个模型:

cd code/llmd/llm-d-multi-model
./manage-cluster-multi-models.sh start

该脚本将 vLLM 固定为 v0.14.1,以匹配 llm-d v0.5.0——通常比独立的 code/basic/ 栈(上述安装命令中的 0.15.1)落后一两个版本,因为 llm-d 是针对自己的镜像矩阵进行测试的。它还需要自定义的 k3s-cuda 镜像(默认的 k3s 缺少 NVIDIA 容器工具包支持)。请为你的 CUDA 版本构建一个——先运行 nvidia-smi,然后执行 cd code/k3d && ./build.sh。vLLM 和 llm-d 迭代很快;请根据需要更新部署文件中的镜像标签(参见 README)。使用 ./manage-cluster-multi-models.sh status 检查集群状态。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_473_0.png

该脚本会创建集群、安装 NVIDIA 设备插件、设置 llm-d,并部署两个模型。检查部署状态:

$ kubectl get pods
NAME                 READY   STATUS    AGE
vllm-llama-32-1b     1/1     Running   5m
vllm-qwen2-5-0-5b    1/1     Running   3m

每个模型都通过 Helm values 文件(llama-3.2-1b-values.yaml、qwen2.5-0.5b-values.yaml)进行配置,该文件指定了模型工件的位置、GPU 要求和副本数量。关键的配置是 modelArtifacts 部分,它告诉 llm-d 从哪里获取模型:

modelArtifacts:
  uri: "hf://meta-llama/Llama-3.2-1B-Instruct"
  name: "meta-llama/Llama-3.2-1B-Instruct"

通过端口转发到每个服务来测试部署:

kubectl port-forward svc/vllm-llama-32-1b 8001:8000 &
kubectl port-forward svc/vllm-qwen2-5-0-5b 8002:8000 &

# 请求 Llama 模型
$ curl http://localhost:8001/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "meta-llama/Llama-3.2-1B-Instruct", "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-...","object":"chat.completion","created":...,"model":"meta-llama/Llama-3.2-1B-Instruct","choices":[{"index":0,"message":{"role":"assistant","content":"Hello! How can I help you today?"}...

# 请求 Qwen 模型
$ curl http://localhost:8002/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "Qwen/Qwen2.5-0.5B-Instruct", "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-...","object":"chat.completion","created":...,"model":"Qwen/Qwen2.5-0.5B-Instruct","choices":[{"index":0,"message":{"role":"assistant","content":"Hello! How can I assist you?"}...

上面的示例使用最简单的设置——直接访问服务,不经过 Inference Gateway。这需要为每个模型单独进行端口转发,类似于访问原始 vLLM Pod。对于生产部署,llm-d 提供了 Inference Gateway,它根据请求体中的 model 字段将请求路由到正确的模型。让我们来添加它:

./manage-cluster-multi-models.sh start --with-gateway

这会部署 Gateway API CRD 和基于 Envoy 的网关,该网关根据请求体中的 model 字段路由请求。现在你可以通过单个端点访问两个模型:

$ kubectl port-forward svc/llm-gateway 8000:8000 &
Forwarding from 127.0.0.1:8000 -> 8000

# 请求 Llama 模型
$ curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "meta-llama/Llama-3.2-1B-Instruct", "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-...","model":"meta-llama/Llama-3.2-1B-Instruct","choices":[{"message":{"role":"assistant","content":"Hello! How can I assist you today?"}...

# 请求 Qwen 模型(同一端口,不同模型)
$ curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "Qwen/Qwen2.5-0.5B-Instruct", "messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-...","model":"Qwen/Qwen2.5-0.5B-Instruct","choices":[{"message":{"role":"assistant","content":"Hello! How can I assist you today?"}...

网关从请求体中提取模型名称,并将其路由到相应的 vLLM 服务。完整的示例日志(展示部署和测试过程)可以在 code/llmd/llm-d-multi-model/example.log 中找到。

与我们手动搭建 k3d 时构建自定义 API 网关不同,llm-d 的 Inference Gateway 使用 Kubernetes Gateway API 的 Inference Extension 自动处理路由。它还增加了智能功能:跟踪跨实例的前缀缓存状态,并将重复请求路由到能够更快处理这些请求的服务器。

比较 k3d 和 llm-d 两种方法

表 9.2 总结了我们手动 k3d 设置与 llm-d 生产栈之间的关键差异。最显著的差异在于路由和负载均衡:我们的 k3d 设置使用自定义 API 网关,采用基本的轮询分发;而 llm-d 的 Inference Gateway 是 Kubernetes 原生的,并且感知前缀缓存——它会将请求路由到已经拥有相关 KV 缓存条目的 Pod,从而减少重复计算。对于多模型部署,k3d 需要在网关配置中手动映射服务,而 llm-d 会自动发现 ModelService 实例并构建路由表。监控方面也类似:k3d 需要手动设置 Prometheus/Grafana,而 llm-d 开箱即用地提供了预配置的仪表盘。

| 功能 | k3d(手动) | llm-d(生产) |

|---|---|---|

| 设置 | 手动 YAML 文件 | Helm charts(自动化) |

| 路由 | 自定义 API 网关 | Inference Gateway(K8s 原生) |

| 负载均衡 | 基本轮询 | 智能(感知前缀缓存) |

| 监控 | 手动设置 | 内置 Prometheus/Grafana |

| 伸缩 | 手动管理 Pod | 支持 HPA,自动伸缩 |

| 多模型 | 手动服务映射 | 自动发现 |

| 生产特性 | 有限 | 完整生产栈 |

表 9.2:k3d 与 llm-d 部署方法对比

llm-d 遵循“vLLM 优先”的设计理念。Inference Gateway 的智能特性——感知前缀缓存的路由、基于 NIXL 的 KV 缓存传输以及推理调度器——都与 vLLM 的内部机制紧密集成。SGLang 支持正在积极开发中(见 GitHub issue #403[12],但截至撰写本文时,llm-d 的原生路由尚不支持引擎选择。对于对多引擎部署感兴趣的读者,code/llmd/llm-d-multi-engine/ 目录提供了一种使用自定义 API 网关层的变通方案——详情请参阅 README。

注意

我们前面探索的 k3d 方法对于学习和本地开发很有价值——因为你亲手构建了每个组件,所以你能确切理解每个组件的用途。但对于处理真实流量的生产部署,llm-d 经过实战检验的配置和智能路由提供了更坚实的基础。过渡很简单:概念完全相同,只是实现细节有所不同。

总结

在生产环境中服务 LLM,远不止加载权重和接受请求这么简单。我们探索了推理内部机制——分词、注意力、KV 缓存——以理解为什么像 vLLM 和 SGLang 这样的框架能为我们处理批处理和内存管理的复杂编排。

我们用 k3d 从零构建了一个多模型栈,深入了解了服务、路由和 API 网关。随后我们将其与 llm-d 进行了对比,在 llm-d 中,同样的概念——InferencePool、模型感知路由、健康检查——被打包成生产就绪的 Helm charts,并支持自动模型发现。

生产系统对正确性的要求更高:需要可观测性用于调试、金丝雀部署用于安全发布,以及通过自动伸缩实现成本优化。从笔记本电脑到数百万请求的旅程是漫长的,但模式保持一致:了解你的工作负载,度量一切,并尽可能自动化。

有了服务基础设施,一个自然的问题出现了:它的性能如何?下一章将直接解决这个问题——使用 genai-bench 和 PyTorch profiler 等工具对吞吐量、延迟和扩展效率进行基准测试。

代码总结

  • fastapi.responses.StreamingResponse:通过 SSE 将生成的 token 流式传输给客户端

  • fastapi.FastAPI:推理网关和模型运行器的 HTTP 层

  • uvicorn:ASGI 服务器,用于启动网关、模型运行器和分词器服务

  • httpx.AsyncClient:从 API 网关到模型运行器的异步转发

  • pydantic.BaseModel:推理 API 的经过验证的请求和响应模式

  • transformers.AutoTokenizer:用于计费、验证和自定义后端的独立分词

  • prometheus_client:TTFT、延迟百分位数和吞吐量指标

  • opentelemetry.trace:跨服务组件的分布式追踪跨度

  • kubectl:在 Kubernetes 上部署、检查和调试 vLLM 和 SGLang 工作负载

实用链接

LLM 服务框架

  • vLLM GitHub:https://github.com/vllm-project/vllm

  • vLLM Production Stack:https://github.com/vllm-project/production-stack

  • SGLang GitHub:https://github.com/sgl-project/sglang

Kubernetes 与 llm-d

  • llm-d GitHub:https://github.com/llm-d/llm-d

  • llm-d 文档:https://www.llm-d.ai/

  • 推理网关:https://github.com/kserve/inference-gateway

  • k3d(Docker 中的 k3s):https://k3d.io/

  • 适用于 Kubernetes 的 NVIDIA 设备插件:https://github.com/NVIDIA/k8s-device-plugin

可观测性与监控

  • OpenTelemetry:https://opentelemetry.io/

  • Prometheus:https://prometheus.io/

  • Grafana:https://grafana.com/

API 网关与路由

  • Envoy 代理:https://www.envoyproxy.io/

  • FastAPI:https://fastapi.tiangolo.com/

  • Kubernetes 网关 API:https://gateway-api.sigs.k8s.io/

教程与指南

  • vLLM Kubernetes 部署:https://docs.vllm.ai/en/stable/deployment/k8s/

  • NVIDIA 容器工具包:https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/

参考资料

  • KServe:https://kserve.github.io/website/

  • KubeAI:https://www.kubeai.org/

  • vLLM production-stack:https://docs.vllm.ai/en/stable/deployment/integrations/production-stack.html

  • llm-d 项目:https://github.com/llm-d/llm-d

  • vLLM 推理引擎:https://github.com/vllm-project/vllm

  • 推理网关(IGW):https://github.com/kubernetes-sigs/gateway-api-inference-extension

  • Envoy 代理:https://www.envoyproxy.io/

  • NIXL(NVIDIA 推理传输库):https://github.com/ai-dynamo/nixl

  • Istio 服务网格:https://istio.io/

远程直接内存访问(RDMA)允许计算机之间直接进行内存访问,而无需 CPU 或操作系统的参与。

非易失性内存标准(NVMe)是一种为 SSD 设计的高速存储接口协议。

https://github.com/llm-d/llm-d/issues/403

练习

构建带模型路由的 API 网关

实现一个 API 网关,根据请求体中的 model 字段将请求路由到不同的模型后端。

要求:

函数签名:

class ModelRouter:
    def __init__(self, model_endpoints: dict[str, str]):
        """Initialize with mapping of model names to backend URLs."""
        pass

    async def route_request(self, request: dict) -> dict:
        """Route request to appropriate backend based on model field."""
        pass
  • 从传入请求中解析 model 字段。

  • 将请求转发到正确的后端 URL。

  • 处理请求的模型不可用的情况(返回 404)。

  • 为后端添加基本健康检查。

  • 同时支持 /v1/chat/completions 和 /v1/completions 端点。

测试你的实现:

import asyncio
import httpx

# Initialize router with model endpoints
router = ModelRouter({
    "llama-3.2-1b": "http://localhost:8001",
    "qwen-0.5b": "http://localhost:8002",
})

# Test routing
request = {
    "model": "llama-3.2-1b",
    "messages": [{"role": "user", "content": "Hello!"}],
    "max_tokens": 50
}
response = asyncio.run(router.route_request(request))
print(f"Response from {request['model']}: {response}")

# Test unknown model
unknown_request = {"model": "unknown-model", "messages": []}
try:
    asyncio.run(router.route_request(unknown_request))
except Exception as e:
    print(f"Expected error: {e}")

实现速率限制中间件

创建一个速率限制中间件,对每个客户端强制执行请求配额。

要求:

类签名:

class RateLimiter:
    def __init__(self, requests_per_minute: int, burst_size: int = 5):
        """Initialize rate limiter with limits."""
        pass

    async def check_rate_limit(self, client_id: str) -> bool:
        """Return True if request is allowed, False if rate limited."""
        pass

    def get_remaining_requests(self, client_id: str) -> int:
        """Return number of remaining requests for client."""
        pass
  • 使用令牌桶算法进行速率限制。

  • 按客户端(通过 API 密钥或 IP 识别)跟踪请求。

  • 支持突发容量以应对流量峰值。

  • 返回相应的头部(X-RateLimit-Remaining、X-RateLimit-Reset)。

  • 清理过期的客户端条目,防止内存泄漏。

测试你的实现:

import asyncio
import time

limiter = RateLimiter(requests_per_minute=10, burst_size=5)

async def test_rate_limiting():
    client_id = "test-client-123"
    # Should allow burst of requests
    for i in range(5):
        allowed = await limiter.check_rate_limit(client_id)
        remaining = limiter.get_remaining_requests(client_id)
        print(f"Request {i+1}: allowed={allowed}, remaining={remaining}")

    # Should start rate limiting
    for i in range(10):
        allowed = await limiter.check_rate_limit(client_id)
        if not allowed:
            print(f"Rate limited after {i+5} requests")
            break

asyncio.run(test_rate_limiting())

实现支持自动回滚的金丝雀部署

创建一个金丝雀部署系统,逐步切换流量并在出错时自动回滚。

要求:

类签名:

class CanaryDeployment:
    def __init__(self, stable_endpoint: str, canary_endpoint: str):
        """Initialize with stable and canary endpoints."""
        pass

    def set_canary_percentage(self, percentage: float):
        """Set percentage of traffic to route to canary (0-100)."""
        pass

    @property
    def canary_percentage(self) -> float:
        """Current canary traffic percentage (0-100)."""
        pass

    async def route_request(self, request: dict) -> dict:
        """Route request to stable or canary based on percentage."""
        pass

    def record_result(self, is_canary: bool, success: bool, latency_ms: float):
        """Record request result for monitoring."""
        pass

    def should_rollback(self, error_threshold: float = 0.05) -> bool:
        """Return True if canary error rate exceeds threshold."""
        pass
  • 根据配置的百分比路由流量。

  • 跟踪稳定版和金丝雀版的成功/失败率。

  • 计算金丝雀版与稳定版之间的错误率差异。

  • 当金丝雀版错误率超过阈值时触发回滚。

  • 支持逐步增加流量(10% -> 25% -> 50% -> 100%)。

测试你的实现:

import asyncio
import random

canary = CanaryDeployment(
    stable_endpoint="http://localhost:8001",
    canary_endpoint="http://localhost:8002"
)

# Start with 10% canary traffic
canary.set_canary_percentage(10)

# Simulate requests
for i in range(100):
    request = {"model": "test", "messages": []}
    # Simulate response (canary has higher error rate)
    is_canary = random.random() < 0.1
    success = random.random() > (0.1 if is_canary else 0.02)
    latency = random.uniform(50, 200)
    canary.record_result(is_canary, success, latency)

    if canary.should_rollback(error_threshold=0.05):
        print(f"Rollback triggered after {i+1} requests!")
        canary.set_canary_percentage(0)
        break

print(f"Final canary percentage: {canary.canary_percentage}%")

使用 OpenTelemetry 添加分布式追踪

使用 OpenTelemetry 对多服务 LLM 服务系统进行埋点,实现端到端的请求追踪。

要求:

为请求处理的每个阶段创建 span:

  • gateway.receive:网关收到请求

  • gateway.route:路由决策

  • tokenizer.encode:词元化

  • model.inference:模型推理

  • tokenizer.decode:去词元化

  • gateway.respond:响应已发送

为跨度添加属性:

  • model.name:模型的名称

  • request.tokens:输入词元数量

  • response.tokens:输出词元数量

  • latency.ttft_ms:首词元耗时

  • latency.total_ms:总延迟

  • 跨服务边界传播追踪上下文

  • 将追踪导出到控制台或 Jaeger

测试你的实现:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor

# Setup tracing
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("llm-serving")

async def process_request(request: dict):
    with tracer.start_as_current_span("gateway.receive") as span:
        span.set_attribute("model.name", request.get("model", "unknown"))
    with tracer.start_as_current_span("tokenizer.encode"):
        tokens = tokenize(request["messages"])
        span.set_attribute("request.tokens", len(tokens))
    with tracer.start_as_current_span("model.inference"):
        output = await model.generate(tokens)
    with tracer.start_as_current_span("tokenizer.decode"):
        response = detokenize(output)
        span.set_attribute("response.tokens", len(output))
    return response

# Test with sample request
request = {
    "model": "llama-3.2-1b",
    "messages": [{"role": "user", "content": "What is machine learning?"}]
}
response = asyncio.run(process_request(request))

在 k3d 上部署多模型服务

在支持 GPU 的本地 k3d 集群上部署一个多模型 LLM 服务系统。

要求

  • 创建启用 GPU 透传的 k3d 集群:

    k3d cluster create llm-serving \
        --gpus=all \
        --volume /path/to/models:/models \
        --port "8080:80@loadbalancer"
    
  • 部署两个提供不同模型的 vLLM 实例:

    • 模型 1:Qwen/Qwen2.5-0.5B-Instruct,端口 8001

    • 模型 2:meta-llama/Llama-3.2-1B-Instruct,端口 8002

  • 创建一个按模型名称进行路由的 API 网关部署

  • 验证部署:

    # List available models
    curl http://localhost:8080/v1/models
    
    # Test Model 1
    curl http://localhost:8080/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{"model": "Qwen/Qwen2.5-0.5B-Instruct", "messages": [{"role": "user", "content": "Hello!"}]}'
    
    # Test Model 2
    curl http://localhost:8080/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{"model": "meta-llama/Llama-3.2-1B-Instruct", "messages": [{"role": "user", "content": "Hello!"}]}'
    

交付物

  • 用于 vLLM 部署的 Kubernetes YAML 文件

  • API 网关部署和服务 YAML

  • 用于模型到服务路由的 ConfigMap

  • 用于部署和测试系统的 Shell 脚本

预期学习成果

完成这些练习后,你应该能够:

  • 构建将请求路由到多个模型后端的 API 网关

  • 实现速率限制以保护服务免受过载影响

  • 部署具备自动回滚能力的金丝雀发布

  • 使用 OpenTelemetry 对分布式系统进行埋点以实现可观测性

  • 在 Kubernetes 上部署和管理 LLM 服务基础设施

  • 设计具备适当监控和容错能力的生产级服务系统

获取本书 PDF 版本及更多内容

扫描二维码(或访问 packtpub.com/unlock)。按名称搜索本书,确认版本,然后按照页面上的步骤操作。

注意:请备好你的发票。直接从 Packt 购买则无需发票。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_486_0.pnghttps://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_486_1.png

10 分布式基准测试与性能优化

衡量并优化分布式 AI 系统的性能

如果你无法衡量它,就无法改进它。

彼得·德鲁克(Peter Drucker),管理顾问兼作家

分布式系统中的性能差距

你已经构建了分布式 AI 系统。DDP 在 8-GPU 集群上同步梯度。FSDP 跨节点对 70B 参数模型进行分片。vLLM 通过连续批处理来服务推理请求。第 9 章中的生产栈将流量路由到正确的模型实例。一切都能工作——但它是否工作得好?

这个问题区分了功能正常的系统与经过优化的系统。一次能跑完的训练任务,并不等同于一次能高效利用你价值 10 万美元 GPU 的训练任务。一个能返回响应的推理端点,也不等同于一个满足 200ms P99 延迟 SLA 的端点。“能工作”与“工作得好”之间的差别,可能意味着数天被浪费的训练时间、违反服务协议以及不必要的云成本。

考虑这样一个场景:你的团队在 8 个节点的 64 块 GPU 上训练一个模型。训练完成,模型在基准测试中表现出色,所有人都为此庆祝。但日志中隐藏着一种令人不安的模式——GPU 利用率徘徊在 45% 左右,从 8 块扩展到 64 块 GPU 的扩展效率只有 52%。你为 64 块 GPU 付了钱,却只获得相当于 33 块 GPU 的有效算力。在为期两周的运行中(每块 GPU 336 小时),整个机群闲置的 48% 大约相当于 31 块 GPU 等效时长的浪费——按典型的云费率(每 GPU 小时 4–5 美元)计算,约合 50,000 美元。

本章将教你发现并修复这些隐藏的低效问题。我们将涵盖完整的基准测试生命周期:对分布式系统真正重要的指标、能揭示时间都花在哪里的性能剖析工具、确保优化不会降低模型质量的精度评估、针对通信瓶颈的网络诊断,以及帮助你理解系统极限的扩展性分析。到本章结束时,你将掌握系统性地识别性能瓶颈、做出数据驱动的优化决策,并验证分布式系统是否以峰值效率运行的技能。

为什么基准测试很重要

对单 GPU 训练循环进行基准测试很简单:对前向传播、反向传播和优化器步骤计时即可。这些数字可复现,方法简单,结果具有可操作性。然而,分布式系统则引入了根本不同的挑战。

当你从 1 块 GPU 扩展到 8 块时,你并不是简单地运行同一工作负载的 8 份副本。这 8 块 GPU 必须在每次反向传播之后同步梯度、协调内存分配以避免碎片化,并通过带宽和延迟各不相同的网络进行通信。这些协调点中的每一个都会引入单设备训练中不存在的开销。梯度同步中 10% 的低效在单个训练步骤中似乎微不足道,但在为期两周的训练运行中,它会累积成数天被浪费的 GPU 时间。

更复杂的是,分布式系统的性能取决于那些简单的计时测量无法察觉的因素。网络拓扑影响 AllReduce 性能。内存碎片化影响批大小。数据加载吞吐量影响 GPU 利用率。如果没有将这些因素考虑在内的系统性基准测试,优化工作就变成了盲目猜测。

基准测试不足的后果以多种方式显现:

  • 次优的技术选型:一个团队使用固定长度提示词的基准测试来评估 vLLM 和 SGLang。vLLM 以微弱优势胜出。然而在生产环境中,请求长度差异巨大——有些提示词只有 50 个词元,另一些则有 5,000 个。对于这种工作负载,SGLang 的基数注意力(radix attention)提供了高出 40% 的吞吐量。该基准测试方法未能反映生产实际。

  • 资源过度配置:如果没有准确的扩展效率测量,容量规划就只能依赖保守估算。团队会配置两倍

他们为了“安全”所需要的硬件,使基础设施成本翻倍,却没有提升性能。

未知瓶颈: 一个训练任务在 64 块 GPU 上运行了两周。事后分析显示,GPU 利用率平均仅为 45%——瓶颈在于数据加载,而非计算。一块 50 美元的 SSD 升级本可以节省 10,000 美元的 GPU 时间浪费。

生产 SLA 违规: 一个推理服务通过了负载测试,测试中使用平均 100 个 token 的合成提示词。但在生产环境中,真实用户查询包含长文档,会触发不同的代码路径。P99 延迟飙升至 500ms——是承诺 SLA 的 2.5 倍。

本章将建立系统化、可复现的测量实践来应对这些挑战。我们首先介绍对分布式系统至关重要的指标,然后依次讲解性能分析工具、准确性评估、网络诊断和扩展性分析。

基准测试基础

在深入具体工具之前,让我们先确立适用于所有分布式 AI 基准测试(无论是训练还是推理)的基础概念。

理解百分位延迟

对于任何性能测量,百分位延迟比平均值更重要。P50(中位数)代表典型行为,P95 通常是 SLA 目标,而 P99 则捕捉影响用户体验的最差情况性能。一个平均延迟 80ms 但 P99 为 500ms 的系统会让用户感到沮丧——那些慢请求才是关键。

效率指标

原始性能数字只能说明部分问题。一个每秒处理 10,000 个样本的系统听起来很厉害——直到你发现它需要 64 块 GPU 才能达到这个吞吐量。效率指标弥合了这一差距。

注意:为什么 P99 比平均值更重要

平均延迟可能具有误导性。考虑两个系统:系统 A 的平均延迟为 100ms,所有请求都在 90-110ms 之间。系统 B 的平均延迟为 80ms,但 5% 的请求需要 500ms。系统 B 的平均延迟更好,但用户体验更差——那 5% 的慢请求会使用户感到沮丧,并可能导致超时。在报告平均值时,务必同时报告 P95 和 P99。

  • 扩展效率回答了一个基本问题:当你将硬件翻倍时,性能是否也翻倍?如果 8 块 GPU 只提供了 6.5 倍而不是 8 倍的吞吐量,你的扩展效率就是 81.25%——意味着你额外硬件投入中近 20% 都损失在了协调成本上。

  • 内存效率衡量你有效利用 GPU 内存的程度。内存碎片可能导致你虽有 20GB “可用”空间,却无法分配一个 10GB 的张量,因为这些空闲空间分散在不连续的区域中。

  • 每 Token/样本成本将技术指标转化为业务现实。该指标将性能测量与实际基础设施成本(云实例定价、电力消耗、散热需求)相结合,以回答最终重要的问题:每个单位的有用工作需要花费多少成本?

基准测试方法论

严谨的方法论能将有意义的基准测试与噪声区分开来。以下三个关键实践同时适用于训练和推理:

  • 测量前预热。 CUDA 操作是惰性编译的——首次执行会触发 JIT 编译、内存分配和缓存填充。运行足够的预热迭代使计时趋于稳定:标准的 eager 模式 PyTorch 大约需要 5–10 次,而当 torch.compile 或 CUDA graph 在早期步骤中进行捕获和编译时则需要 20–50 次。只有在预热完成后、并且在记录开始时间前执行完 torch.cuda.synchronize(),才能启动计时器。

  • 测量多次迭代并统计。 单次测量几乎说明不了任何问题。性能会因热降频、后台进程、内存碎片和网络拥塞而波动。至少运行 100 次迭代,并报告均值、标准差和百分位数。

  • 隔离你要测量的内容。 预先加载数据,在启动计时器之前调用 torch.cuda.synchronize(),在停止之前再次调用。同步确保所有 GPU 操作都已完成,而不仅仅是已排队。

参见 code/benchmark_warmup.py 获取完整实现,其中演示了正确的预热、同步和统计报告方法。

训练基准测试

训练分布式模型涉及一个复杂的流水线:数据加载、前向传播、反向传播、梯度同步和优化器更新。每个组件都会影响整体训练时间,瓶颈可能隐藏在任何一环。有效的训练基准测试需要分别测量每个阶段,以确定优化工作应聚焦在哪里。

关键训练指标

  • 每秒样本数是你主要的训练吞吐量指标——所有 GPU 每秒处理的训练样本数量。这直接决定了训练时长:如果你每秒处理 1,000 个样本,每个 epoch 有 100 万个样本,那么每个 epoch 大约需要 17 分钟。

  • GPU 利用率衡量 GPU 在积极计算与等待之间各自所占的时间百分比。低利用率(低于 80%)通常表明瓶颈在其他地方——数据加载过慢、通信阻塞计算,或 CPU 预处理造成停滞。

  • 通信开销量化了分布式训练的隐性成本。每次梯度同步都会消耗本可用于计算的时间。在优化良好的系统中,通信与反向传播计算重叠,从而掩盖了大部分成本。在配置不当的系统中,GPU 只能空闲等待 AllReduce 操作完成。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_491_0.png

图 10.1:按阶段和 GPU 数量划分的训练迭代时间分解

上图展示了分布式训练中的一种常见模式。左侧,随着我们增加 GPU,绝对迭代时间不断下降——从单块 GPU 的 155ms 降至 16 块 GPU 的 33ms。但右侧面板揭示了隐性成本:通信开销从 0%(单块 GPU 无需同步任何内容)增长到 16 块 GPU 时的 55%。此时,每次迭代中超过一半的时间花在梯度同步而非实际计算上。这就是扩展效率下降的原因——你为 16 块 GPU 付费,但通信开销意味着你并没有获得 16 倍的吞吐量。理解这种分解是优化的第一步:如果通信占主导地位,你可以考虑梯度压缩、增大批量大小以分摊同步成本,或将通信与计算重叠。

PyTorch 性能分析器

PyTorch 内置的性能分析器是您理解训练性能的第一工具。它能够捕获 CPU 和 CUDA 操作、内存分配,并可导出 trace 以便可视化。

基本用法

PyTorch 性能分析器同时捕获 CPU 和 CUDA 操作,让您完整了解时间的去向。关键在于使用 record_function 标记代码区域,以便按阶段识别瓶颈。关键配置选项包括:

  • activities:指定 CPU 和 CUDA 以同时捕获主机和设备操作

  • record_shapes:记录张量形状,有助于理解内存模式

  • profile_memory:跟踪内存分配和释放

  • with_stack:捕获 Python 调用堆栈以便更深入调试

输出表格按总 CUDA 时间排序显示操作。寻找消耗时间不成比例的操作——这些就是您的优化目标。导出的 trace.json 可以在 chrome://tracing 中查看,以进行详细的时间线分析。

使用 Schedule 的高级性能分析

对于多迭代分析,请使用能够自动处理预热的性能分析 schedule。schedule 参数控制性能分析的生命周期:wait 跳过冷启动迭代,warmup 运行不记录的迭代,active 对指定数量的迭代进行性能分析,repeat 多次循环此模式。tensorboard_trace_handler 会自动保存 trace,以便使用 TensorBoard 进行可视化。

参见 code/pytorch_profiler.py 获取基本性能分析和计划性能分析的完整实现。

NVIDIA Nsight Systems

对于更深入的分析(尤其是 CUDA 内核和 NCCL 通信),NVIDIA Nsight Systems 提供了 PyTorch 性能分析器无法比拟的系统级性能分析能力。

命令行用法

# Profile training script
nsys profile --trace=cuda,nvtx,osrt \
    --output=training_profile.nsys-rep \

python train.py

# Generate report

nsys stats --report gputrace training_profile.nsys-rep

Nsight 能够捕获 GPU 内核执行时间、内存传输时间(H2D、D2H)、CUDA API 调用、同步点以及 NCCL 通信操作。这种详细程度对于诊断微妙性能问题(如内核启动开销或次优的内存访问模式)至关重要。

自定义训练基准

要对各种配置进行系统化基准测试(idx_d3d0cac3),自定义基准类比临时分析提供了更多控制。该类分别测量每个训练阶段——数据加载、前向传播、反向传播和优化器步骤——使你能够精确定位时间花在哪里。

为什么分阶段计时很重要: 如果你的反向传播耗时是前向传播的 3 倍,可能存在梯度计算效率低下或内存碎片问题。如果数据加载占主导地位,你需要更多的 DataLoader 工作进程或更快的存储。没有分阶段的细分,你就是在盲目优化。

解读结果: 返回的 stats 字典包含每个阶段的平均值、标准差和百分位数。如果 data_loading 超过总时间的 10%,请增加 DataLoader 工作进程。如果 backward 是 forward 的 2 倍以上,请检查梯度检查点机会或内存碎片。高方差(平均值与 P99 之间差距大)表明系统不稳定——请调查热节流或竞争进程。

有关 idx_343b9960 完整的 TrainingBenchmark 类实现,请参阅 code/training_benchmark.py。

何时使用 Nsight 与 PyTorch Profiler

先从 PyTorch Profiler 开始进行高层分析——它更易于使用,并能与 TensorBoard 集成。当需要了解 CUDA 内核行为、NCCL 通信模式或系统级交互时,再转向 Nsight Systems。在分析自定义 CUDA 内核或调试 PyTorch 视图中不出现的性能问题时,Nsight 也是必不可少的。

注意

衡量扩展效率

扩展效率量化了你的 idx_99c42a46 系统利用额外资源的程度。完美的线性扩展(100% 效率)意味着 GPU 数量加倍则吞吐量加倍,但通信开销使得这在实践中不可能实现。衡量扩展效率有助于你判断何时增加更多 GPU 具有成本效益,以及何时已经遇到收益递减。

图 10.2:扩展效率:理想与实际的吞吐量和效率百分比

解读扩展效率:

  • >90%: 优秀扩展——你的系统优化得很好

  • 70-90%: 良好扩展——典型于调优得当的分布式训练

  • 50-70%: 中等扩展——通信开销显著,请调查网络

  • <50%: 较差扩展——存在主要瓶颈,可能是通信或数据加载

了解你的扩展效率有助于进行容量规划。如果你在 8 个 GPU 上拥有 81% 的效率,你可以预测 16 个 GPU 将提供大约 13 倍吞吐量(而非 16 倍),从而帮助你做出明智的硬件决策。

请参阅 code/scaling_efficiency.py 中的函数,以计算和基准测试扩展效率。

验证分布式训练的准确性

性能基准测试(idx_05759d2c)衡量速度,而准确性基准测试衡量正确性——两者都很重要。分布式训练的一个关键问题是:你的分布式设置是否产生与单 GPU 训练相同的模型质量?

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_494_0.png

这并不是理论上的担忧。梯度同步中的错误、不同的有效批次大小或数值精度问题可能导致分布式训练收敛到更差的解。损失曲线可能看起来相似,但下游任务准确性可能低 8%。如果没有系统性的准确性比较,你永远不会知道。

验证方法很简单:在单 GPU 和分布式设置上使用相同的超参数训练相同的模型,然后在保留的测试集上比较准确性。使用统计显著性检验——p=0.3 时 0.5% 的准确性下降可能只是噪声,但 p=0.001 时 0.5% 的下降则表明存在需要调查的真实问题。

请参阅 code/accuracy_benchmark.py 中的 idx_a2a06f8e 函数,以比较集中式与分布式训练的准确性并检验统计显著性。

网络与通信分析

在分布式训练中,网络(idx_24f045b7)往往是瓶颈。节点之间的一个慢速链路就可能限制整个系统的性能。理解通信模式并诊断网络问题对于高效扩展至关重要。

图 10.3:Ring AllReduce 模式与多节点带宽层次结构

带宽层次结构极其重要:同一节点内 GPU 之间的 NVLink 提供约 600 GB/s,节点之间的 InfiniBand 提供约 200 GB/s,而以太网仅提供约 12.5 GB/s(100 Gbps)。跨越这些边界的通信模式会付出显著的延迟代价。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_495_0.png

在深入进行特定于 NCCL 的分析之前,请使用标准网络工具建立基线连通性。iftop 显示每个连接的实时流量,nload 显示带宽图表,iperf3 测量节点间的原始 TCP 带宽。如果 iperf3 显示 100 Gbps,但你的训练仅达到 20 Gbps 的有效带宽,那么瓶颈在于你的通信模式,而不是网络硬件。

AllReduce 是分布式训练中主要的通信操作(idx_a0bef4c5、idx_ef2a2dab)——它在所有 GPU 之间同步梯度。将 AllReduce 带宽与训练分开测试,有助于将网络问题与计算问题隔离开来。小消息具有高开销(受延迟限制),而大消息接近峰值带宽(受带宽限制)。如果你的大消息带宽明显低于硬件规格,请检查拓扑问题或 NCCL 配置问题。

请参阅 code/network_diagnostics.py 中的 AllReduce 带宽测试和通信开销分析函数。

扩展瓶颈分析

一旦你知道你的扩展效率(idx_d2b17392)不佳,下一步就是找出原因。阿姆达尔定律提供了理论极限:即使有无限多的处理器,加速比也受限于工作负载中的串行部分。如果有 10% 的串行工作,即使 GPU 无限多,也只能提供 10 倍加速。

常见的瓶颈模式包括:

  • 数据加载无法扩展: 你的 DataLoader 无法跟上多个 GPU。增加 num_workers 或使用更快的存储。

  • 通信开销高: 网络是瓶颈。考虑梯度压缩、更大的批次大小或更好的互连。

  • GPU 利用率低: GPU 在等待某些东西——通常是数据或同步。

一旦确定,就应用相应的修复措施。DDP 使用梯度分桶将反向计算与梯度同步重叠——从 25MB 桶开始,并根据分析进行调整。对于数据加载,将每个 GPU 的 num_workers 设置为 CPU 核心数的 2-4 倍,并设置 pin_memory=True。如果通信开销较高,梯度累积可通过在多个微批次上累积来降低同步频率。

请参阅 code/scaling_bottlenecks.py 中的阿姆达尔定律计算、瓶颈分析函数和优化策略实现。

推理基准测试

推理基准测试(idx_0ef5a2c9)带来了与训练不同的挑战(idx_198185ad)。请求模式多变,缓存效果很重要,尾部延迟要求严格。训练作业慢 10% 令人恼火;违反 P99 SLA 的推理端点会失去客户。

关键推理指标

LLM 推理拥有自己的一套指标词汇表(idx_e797a3c7),这些指标捕捉了自回归生成的独特特征。理解这些指标及其相互关系对于有效的基准测试至关重要。

图 10.4:LLM 推理性能指标概览

优化多节点训练集群

要提高扩展效率(例如从 52% 提升到 80%),请遵循系统化的诊断过程:(1)分析通信与计算时间,(2)检查数据加载吞吐量,(3)测量 GPU 利用率,(4)测试节点间的网络带宽。常见修复措施对应特定的瓶颈:通信瓶颈 → 启用梯度压缩并优化桶大小;数据加载瓶颈 → 增加 num_workers 并使用 pin_memory=True;计算瓶颈 → 检查使执行串行化的 CPU-GPU 同步点。

注意

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_497_0.png

第10章:分布式基准测试与性能优化

每秒 Token 数(Tokens per Second,TPS) 衡量生成吞吐量。系统总 TPS 涵盖了所有并发请求——随着并发度增加,总 TPS 会上升,直到 GPU 计算资源饱和,之后可能因内存压力而下降。

图 10.5:显示并发请求吞吐量的 TPS 时间线

每秒请求数(Requests per Second,RPS) 捕获完整的请求生命周期,包括排队和路由。与 TPS 不同,该指标反映的是实际的 API 容量。

端到端请求延迟(e2e_latency) 是指从提交查询到收到完整响应的总时间,涵盖分词(tokenization)、预填充(prefill)、所有 token 生成以及反分词(detokenization)。

图 10.6:端到端请求延迟流水线

首个 Token 时间(Time to First Token,TTFT) 衡量直到第一个 token 出现所需的时间。用户将其感知为“响应时间”。TTFT 包括分词、预填充阶段(处理整个输入提示)、生成第一个 token 以及反分词。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_498_0.png

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_498_1.png

图 10.7:从输入到首个输出 token 的 TTFT 流水线

Token 间延迟(Inter-token Latency,ITL),也称为每个输出 Token 的时间(Time per Output Token,TPOT),是连续 token 之间的平均时间。这决定了流式响应被感知到的“打字速度”:

图 10.8:显示输出 token 生成的 ITL 流水线

这些指标之间的关系决定了你应该优化什么:

e2e_latency = TTFT + (output_tokens - 1) × ITL

对于短输出(10-20 个 token),TTFT 占主导——优化预填充和 KV 缓存初始化最为关键。对于长输出(数百个 token),ITL 占主导——内存带宽和解码效率变得至关重要。对于流式应用,用户将 TTFT 感知为“响应时间”,将 ITL 感知为“打字速度”,因此两者都需要关注。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_499_0.png

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_499_1.png

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_499_2.png

理解延迟的分布对于设定现实的 SLA 至关重要。图 10.9 展示了一个典型的延迟分布,同时包含直方图和 CDF。CDF 使得直接读取百分位值变得容易:P50 位于 50% 交叉点,P95 位于 95%,P99 位于 99%。

图 10.9:带百分位标记的延迟分布直方图和 CDF

基准测试工具:genai-bench

对推理进行基准测试的最简单方法——在循环中发送相同请求——忽略了真实流量的复杂性。生产环境用户发送的提示词从 10 个 token 到 10,000 个 token 不等。负载从只有单个请求的安静时段,到数百个并发用户的突发流量。只测试固定长度提示词和恒定并发度的基准测试,几乎无法揭示你的系统在最关键时刻的表现。

genai-bench[1] 通过支持可配置的流量模式来弥补这一不足,这些模式镜像了生产工作负载。它不会发送相同请求,而是生成符合真实分布的提示词长度。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_500_0.png

性能基准测试与准确性基准测试

本节介绍使用 genai-bench 等工具进行的性能基准测试,它衡量工程指标(吞吐量、延迟、扩展性)。这与用于文本到视觉评估的 GenAI-Bench 等准确性基准测试工具(https://linzhiqiu.github.io/papers/genai_bench/)有所不同,后者衡量模型输出质量。我们将在下一节介绍准确性基准测试。

注意 该工具使用流量场景来定义请求分布。D(100,100) 发送确定性请求,输入和输出 token 恰好各为 100 个——适用于受控对比。D(512,512) 测试更长的上下文。要进行真实可靠的基准测试,你需要测试一组场景矩阵:低并发 + 短上下文可建立无批处理效应时的基线延迟;高并发 + 短上下文可揭示系统批处理请求的能力;任何并发 + 长上下文都会暴露内存压力和 KV 缓存行为。当延迟随上下文长度增加而非线性增长时,说明你遇到了内存带宽瓶颈。

安装很简单:

pip install genai-bench

关于以编程方式运行 genai-bench 的完整示例,包括单个基准测试、测试矩阵和结果分析,请参阅 code/genai_bench_example.py。

自定义推理基准测试

对于 genai-bench 不适用的场景——自定义模型、非标准 API 或特定于应用的指标——构建自定义基准测试能提供你所需的控制力。关键是在遵循同样严谨方法论(预热、多次迭代、正确同步和统计报告)的同时,衡量对用例真正重要的指标。

自定义基准测试对于 CI/CD 集成尤其有价值。你可以定义性能门禁,当延迟回退时使构建失败,或者随时间跟踪指标,在渐进式性能劣化影响用户之前提前发现。

冷启动与热性能对比

加载模型后的第一个请求与后续请求的行为截然不同。CUDA 内核必须进行 JIT 编译,内存必须分配,缓存必须填充。对于小模型(1–3B),冷启动延迟通常是热解码的 3–5 倍;对于内核编译和缓存填充占主导的中型 LLM,约为 16 倍;对于 70B+ 的检查点,从磁盘加载需要数分钟,冷启动延迟可能达到 50 倍或更多。

这对自动扩缩容非常重要。如果冷启动需要 30 秒,而热请求在 100 毫秒内完成,激进的缩容策略会变成一个陷阱:你通过终止空闲实例来节省成本,但当流量回归时,用户会在新实例预热期间经历 30 秒的延迟。解决方案是同时对冷性能和热性能进行基准测试,然后利用这些数据配置自动扩缩容的最小实例数。保持足够的热实例运行,以处理基线流量而不会触发冷启动。

推理与多步骤模型

传统基准测试衡量的是单个请求-响应周期,但推理模型——思维链系统、工具增强型 LLM、多步骤智能体——需要不同的方法。一次用户交互可能涉及多次模型调用、外部 API 请求、数据库查询和检索操作。仅仅衡量模型推理时间会遗漏大部分情况。

对推理系统进行有效的基准测试,需要将延迟分解为各个组成部分。每步延迟(每个推理步骤的 TTFT 和 ITL)可以识别哪些步骤缓慢。端到端会话延迟捕获用户感知到的总延迟。最重要的是,将本地生成时间与外部调用分离,可以揭示优化工作的重点应该放在哪里。

在智能体工作流中,一个令人惊讶的模式经常出现:总延迟的 60% 或更多来自外部调用——工具调用、API 请求、数据库查询——而非模型推理。当用户实际上在等待一个缓慢的 API 响应时,优化模型带来的收益微乎其微。基准测试数据会告诉你,是应该优化模型、并行化工具调用,还是缓存外部结果。

有关自定义推理基准测试、冷启动测量和推理会话分析的完整实现,请参阅 code/inference_benchmark.py。

验证推理准确性

如果输出是错误的,速度就毫无意义。推理优化——量化、不同的服务引擎、批处理策略——可能会以不那么显而易见的方式轻微降低输出质量,而如果没有系统化评估,这些问题很难被发现。

以量化为例:INT8 量化可能将吞吐量提升 2 倍,但如果数学问题的准确率下降了 15%,这就是一笔糟糕的交易。由于采样或数值精度实现上的差异,不同的服务引擎对同一提示词可能产生不同的输出。其中一个可能是正确的,而另一个存在 bug。没有准确性基准测试,这些性能回退就会在未被发现的情况下进入生产环境。

标准方法使用成熟的基准测试和指标。对于文本生成,BLEU 和 ROUGE 衡量与参考文本的 n-gram 重叠度,而 BERTScore 捕获语义相似性。对于任务特定评估,可使用分类准确率、F1 分数、精确匹配(用于问答)或 Pass@k(用于代码生成)。GLUE/SuperGLUE、MMLU、HumanEval 和 HELM 等标准基准测试可在不同模型和配置间提供一致的评估。

在将量化模型与其全精度基线进行比较,或比较不同服务引擎的输出时,统计显著性非常重要。准确率下降 0.5% 且 p=0.3,可能只是噪声;而准确率下降 0.5% 且 p=0.001,则表明存在需要关注的真实性能回退。

有关用于评估量化影响和检验统计显著性的 idx_b1d0dc28 函数,请参阅 code/accuracy_benchmark.py。

摘要

本章为你提供了系统化基准测试和优化分布式 AI 系统的工具与技术。核心要点如下:

  • 方法论至关重要: 适当的预热、多次运行和方差分析能将有意义的基准测试与噪声区分开来。

  • 选择合适的工具: 训练用 PyTorch Profiler 和 Nsight;推理用 genai-bench;准确度用 GLUE、MMLU 和 HumanEval。

  • 双重基准测试: 性能(速度、吞吐量)和准确度(质量、正确性)都必须测量——损害准确度的优化不是优化。

  • 网络往往是瓶颈: 通信开销随规模增长。对它进行分析、理解并优化。

  • 扩展存在极限: 阿姆达尔定律设定了理论上限。测量你的扩展效率,以了解自己所处的位置。

  • 可复现性推动进步: 记录一切。对基准测试脚本进行版本控制。固定随机种子。

你掌握的技能

  • 设计具有适当预热和测量的可复现基准测试实验。

  • 使用 PyTorch Profiler 和 Nsight Systems 分析训练工作负载。

  • 使用 genai-bench 结合真实流量模式对推理系统进行基准测试。

  • 使用标准基准评估模型准确度。

  • 使用通信剖析诊断网络瓶颈。

  • 计算并解读扩展效率。

比较推理引擎

在选择推理引擎(vLLM、SGLang、TensorRT-LLM)时,先定义你的指标:吞吐量、延迟百分位数(P50/P95/P99)和内存占用。使用生产请求模式创建真实工作负载,用 genai-bench 运行基准测试以保证一致性,然后分析权衡。更高的吞吐量往往伴随更高的内存占用;更低的延迟可能牺牲批处理效率。“最佳”引擎取决于你的具体约束。

有效的基准测试是性能优化的基础。没有准确的测量,优化工作就是盲目猜测。本章介绍的技术提供了所需的可见性,以便你对分布式 AI 系统做出数据驱动的决策。

纵观本书,我们涵盖了分布式 AI 的当前状态:用于训练的 DDP 和 FSDP,用于推理的 vLLM 和 SGLang,用于作业调度的 Slurm,以及生产级服务技术栈。但该领域正在快速发展。最后一章探讨新兴趋势和未来方向:MoE 扩展、混合边缘-云架构、高级并行策略和成本优化技术。了解该领域的发展方向将帮助你为下一波分布式 AI 创新做好准备。

代码摘要

  • torch.profiler:用于性能分析的 PyTorch Profiler

  • torch.utils.benchmark:PyTorch 基准测试工具

  • nvidia-ml-py:用于 GPU 监控的 Python 库

  • psutil:用于资源监控的系统和进程工具

  • mlperf_logging:用于标准化基准测试的 MLPerf 日志工具

  • tensorboard:用于训练指标可视化的 TensorBoard

  • wandb:用于实验跟踪的 Weights & Biases

  • py-spy:用于 Python 应用程序的采样分析器

  • nsys:用于系统级剖析的 NVIDIA Nsight Systems

  • ncu:用于内核级剖析的 NVIDIA Nsight Compute

实用链接

性能基准测试工具

  • genai-bench:https://github.com/sgl-project/genai-bench

  • PyTorch Profiler:https://pytorch.org/tutorials/recipes/recipes/profiler_recipe.html

  • NVIDIA Nsight Systems:https://developer.nvidia.com/nsight-systems

  • NVIDIA GenAI-Perf:https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/perf_analyzer/genai-perf/README.html

  • NVIDIA NIM Benchmarking:https://docs.nvidia.com/nim/benchmarking/llm/latest/index.html

  • IBM FMWork:https://github.com/IBM/fmwork

准确度基准测试

  • GLUE Benchmark:https://gluebenchmark.com/

  • MMLU Benchmark:https://github.com/hendrycks/test

  • HumanEval(代码生成):https://github.com/openai/human-eval

  • HELM(全面评估):https://crfm.stanford.edu/helm/

  • Hugging Face Evaluate:https://huggingface.co/docs/evaluate/

  • GenAI-Bench(文生视觉):https://linzhiqiu.github.io/papers/genai_bench/

  • NVIDIA NeMo Evaluator:https://github.com/NVIDIA-NeMo/Evaluator

论文与标准

  • MLPerf Inference Benchmark:https://arxiv.org/abs/1911.02549

  • LLM-Inference-Bench:https://arxiv.org/abs/2411.00136

  • MLCommons:https://mlcommons.org/

参考

genai-bench:来自 SGLang 项目的 LLM 推理基准测试,支持可配置的输入/输出长度分布。https://github.com/sgl-project/genai-bench

练习

多项选择题

  1. 在基准测试中,预热迭代的主要目的是什么?

    • A) 增加基准测试的总运行时间

    • B) 消除冷启动效应,如 JIT 编译和缓存预热

    • C) 减少 GPU 内存占用

    • D) 测试错误处理代码路径

  2. 测量首 token 时间(TTFT)时,包括哪些阶段?

    • A) 仅模型前向传播

    • B) 分词、预填充计算和首个 token 生成

    • C) 包括最终 token 在内的所有 token 生成

    • D) 仅网络延迟

  3. 一个分布式训练任务在 8 块 GPU 上表现出 85% 的扩展效率。这意味着什么?

    • A) 使用了 85% 的 GPU 内存

    • B) 该任务达到了理想线性加速的 85%(6.8 倍而非 8 倍)

    • C) 85% 的时间用于计算

    • D) 15% 的梯度被丢弃

  4. 哪个指标最能反映流式 LLM 应用中用户感知的响应速度?

    • A) 每秒 token 数(TPS)

    • B) 端到端延迟(E2E)

    • C) 首 token 时间(TTFT)

    • D) GPU 利用率

  5. 在 PyTorch Profiler 输出中,哪些操作表示通信开销?

    • A) aten::matmul 和 aten::linear

    • B) nccl:all_reduce 和 nccl:all_gather

    • C) aten::relu 和 aten::gelu

    • D) 仅 cuda::memcpy

  6. 阿姆达尔定律对扩展效率有什么预测?

    • A) 增加 GPU 总能实现线性扩展

    • B) 无论并行化程度如何,代码的串行部分限制了最大加速

    • C) 通信开销随 GPU 数量增加而减少

    • D) 内存占用随 GPU 数量线性增长

  7. 在对推理吞吐量进行基准测试时,为什么需要在不同并发级别下测试?

    • A) 为了找到最大批处理大小

    • B) 为了识别饱和点,即增加更多请求不再提高吞吐量

    • C) 为了测量冷启动延迟

    • D) 为了测试模型准确度

  8. 哪种方法能在 DDP 训练中正确地将通信时间与计算时间分开测量?

    • A) 测量总训练时间并除以 GPU 数量

    • B) 在梯度同步前后使用 torch.cuda.synchronize()

    • C) 统计模型中的参数数量

    • D) 仅测量前向传播时间

  9. Inter-token Latency(ITL)与 Time Per Output Token(TPOT)之间是什么关系?

    • A) ITL 测量首个 token,TPOT 测量后续 token

    • B) 它们衡量的是同一件事——连续输出 token 之间的时间

    • C) ITL 用于训练,TPOT 用于推理

    • D) TPOT 包含网络延迟,ITL 不包含

  10. 在比较分布式训练与单 GPU 训练的模型准确度时,什么问题表明出现了异常?

    • A) 损失曲线完全相同

    • B) 出现统计上显著且超出预期方差的准确度下降

    • C) 更多 GPU 带来更快收敛

    • D) 每个 GPU 的内存占用更低

  11. 在生产推理中,哪个百分位指标对 SLA 合规性最重要?

    • A) P50(中位数)延迟

    • B) P95 或 P99 延迟

    • C) 平均延迟

    • D) 最小延迟

  12. 扩展效率的正确公式是什么?

    • A) efficiency = throughput_N / throughput_1

    • B) efficiency = (throughput_N × N) / throughput_1

    • C) efficiency = throughput_N / (throughput_1 × N)

    • D) efficiency = N / throughput_N

  13. 在分析网络通信时,nvidia-smi topo -m 显示什么?

    • A) GPU 内存使用情况

    • B) GPU 互连拓扑(NVLink、PCIe 连接)

    • C) 网络带宽使用情况

    • D) CUDA 内核执行时间

  14. 为什么基准测试应多次运行并进行统计分析?

    • A) 为了增加总运行时间

    • B) 为了考虑方差并确保可重复、具有统计显著性的结果

    • C) 为了预热 GPU

    • D) 为了测试不同的模型配置

简答题

  • 解释为什么在分布式训练中,扩展到更多 GPU 时通信开销通常会增加。

  • 一个基准测试显示 P50 延迟为 50ms,P99 延迟为 500ms。这种分布表明系统的行为有何特点?

  • 描述两种在不改变模型架构的情况下减少分布式训练中通信开销的方法。

  • 为什么量化模型(INT8)即使在相同权重下,在不同推理引擎(vLLM 与 SGLang)上可能表现出不同的准确率?

  • 训练基准中的“每秒样本数”与推理基准中的“每秒令牌数”有什么区别?

答案

  1. B - 预热消除了冷启动效应

  2. B - TTFT 涵盖从分词到生成第一个令牌的过程

  3. B - 理想线性加速的 85%

  4. C - TTFT 反映初始响应能力

  5. B - NCCL 操作表示通信

  6. B - 串行代码限制了最大加速比

  7. B - 找到吞吐量饱和点

  8. B - 使用同步屏障进行精确计时

  9. B - 两者都衡量连续令牌之间的时间

  10. B - 具有统计显著性的准确率下降

  11. B - P95/P99 用于 SLA 合规性

  12. C - efficiency = throughput_N / (throughput_1 × N)

  13. B - GPU 互连拓扑

  14. B - 统计显著性和可重复性

简答题指南

  1. 更多的 GPU 意味着跨更多节点进行更多的梯度同步,增加了 AllReduce 通信量,并可能经过较慢的互连(PCIe 与 NVLink)。

  2. 较高的 P99/P50 比率(10 倍)表明存在尾延迟问题——大多数请求很快,但有些请求会经历显著延迟,这可能是由于垃圾回收、资源争用或缓存冷启动效应所致。

  3. 梯度累积(减少同步点)、梯度压缩(减少数据量)、通信与计算重叠、使用更快的互连。

  4. 不同的量化实现、内核优化和数值精度处理方式,即使权重相同,也可能导致轻微的准确率差异。

  5. 样本/秒衡量的是已处理的完整训练样本数;令牌/秒衡量的是单个令牌的生成速率,该速率随序列长度和批处理而变化。

预期学习成果

完成这些练习后,你应该能够:

  • 理解关键基准测试概念及其实际含义

  • 正确解读基准测试指标(TTFT、ITL、TPS、扩展效率)

  • 识别分布式系统基准测试中的常见陷阱

  • 分析性能分析输出以诊断性能瓶颈

  • 将统计思维应用于基准测试结果

11 分布式人工智能的演变格局

探索分布式人工智能的新兴技术与未来方向

预测未来的最好方式是创造未来。

艾伦·凯,计算机科学家

分布式人工智能的演变

在整本书中,我们探索了分布式人工智能的基础:从 DDP 的梯度同步到 FSDP 的内存优化,从 vLLM 的 PagedAttention 到 SGLang 的 RadixAttention。这些技术使得训练和服务几年前难以想象的模型成为可能。但这一领域并未止步——其格局仍在以连经验丰富的从业者都感到惊讶的方式不断变化。

我们如今所处的位置

数字讲述了一个非凡的故事。训练基础设施已扩展到单集群超过 100,000 个 GPU,这得益于 NCCLX 和 Torchcomms 等新型通信框架。SGLang 如今为全球超过 400,000 个 GPU 提供算力[1],运行 xAI 的 Grok 3 和微软 Azure 的生产工作负载。最引人注目的或许是经济账:DeepSeek-V3 是一个拥有 6710 亿参数的 MoE 模型,每个令牌有 370 亿激活参数,仅在 2,048 个 H800 GPU 上以 550 万美元的成本完成训练——这个成本在几年前简直低得令人难以置信。

端侧 AI 已跨越一个关键门槛。像 Gemma 3n 这样的模型通过使用逐层嵌入(Per-Layer Embeddings)等技术,可以在 2-3GB 内存中流畅运行,使 LLM 在智能手机上变得切实可行。曾经看似实验性的 4 比特量化方法如今已成为标准做法。

但也许最重要的转变是优先事项的变化。多年来,AI 行业痴迷于训练:建设更大的集群、训练更大的模型、不断推进前沿。那个时代尚未结束,但它已不再是主导叙事。推理工作负载如今消耗了 AI 基础设施支出的 55% 以上,并且这一比例还在持续增长。对未来几年全球推理市场的预测从数千亿美元到远超万亿美元不等——这种差异反映了分析师们是否将芯片、云服务或打包应用计算在内。

细想之下,这种转变是有道理的。训练只发生一次;推理则发生数百万次。随着模型日趋成熟和部署规模扩大,经济性不可避免地偏向推理优化。过去几年基础设施成本大幅下降,使得在以前不切实际的场景中部署 AI 在经济上变得可行。

新的瓶颈

伴随这一增长而来的是新的约束。有趣的是,首要瓶颈不再是芯片的可用性,而是电力和散热。数据中心正在围绕热限制而非机架空间进行设计。组织如今将散热改造纳入总体拥有成本的计算,并提前数月协商云容量承诺。

其余的技术挑战同样有趣。高效扩展到超过 10 万个 GPU 需要新的通信模式和容错机制。在生产推理中平衡延迟和吞吐量既是一门科学,也是一门艺术。而跨混合云-边缘架构的成本优化正在成为一门独立的学科。

新兴趋势概览

在深入探讨每个领域之前,这里有一份正在重塑分布式 AI 的路线图:

  • 先进 MoE 架构:LatentMoE 带来了硬件-软件协同设计,以实现每个 FLOP 的最佳准确率,已被英伟达的 Nemotron-3 采用。MoSE(可精简专家混合)支持可变宽度专家执行,从而实现准确率与计算量的持续权衡。弹性 MoE 将推理时的专家数量扩展到训练值的 2-3 倍。ReMoE 引入了使用 ReLU 替代 TopK+Softmax 的完全可微路由。

  • 端侧与边缘 AI:低于十亿参数的模型如今能有效处理实际任务。Gemma 3n 的逐层嵌入减少了内存需求——5B/8B 模型以 2B/4B 的内存占用运行。限制边缘推理的是内存带宽,而非 FLOPs;当草稿模型能很好地跟踪目标模型时,推测解码会有所帮助。

下一代通信:Torchcomms 是 PyTorch 的新 API,专为支持异构硬件的 100K+ GPU 规模而设计。NCCLX/RCCLX(Meta 的增强后端)在 AllReduce 操作上可带来 10–50% 的加速。异步检查点现在借助缓存计划和更少的 GIL 争用,速度快了 6 倍。

推理引擎演进:SGLang 在 H100 上实现 16,215 tok/s 的吞吐,通过 RadixAttention 实现自动前缀复用,并通过流水线并行支持百万 token 上下文。vLLM 的 PagedAttention 将 KV 缓存浪费从 60–80% 降至 4% 以下。两者现在都支持 AMD MI355/MI300、Intel Xeon、Google TPU 和 Ascend NPU。

混合专家:主流架构

我们在第 5 章(用于训练的专家并行,见图 5.8)和第 6 章(使用 vLLM 的 MoE 推理)中介绍了 MoE 的基础知识。这里我们重点关注推动 MoE 能力进一步发展的研究前沿。

研究持续推动 MoE 的能力超越我们在前面章节中介绍的范围:

  • LatentMoE[2] 利用软硬件协同设计,在不同推理场景中优化每 FLOP 的准确率。该方案被 Nvidia 的 Nemotron-3 模型采用,证明恰当的协同设计可以在不牺牲能力的前提下显著提升效率。

  • MoSE(Mixture of Slimmable Experts)[3] 支持可变宽度的专家执行。与固定大小的专家不同,MoSE 可以在推理时动态调整专家宽度,实现精度与算力之间的连续权衡。这对于延迟要求各不相同的部署场景尤其有价值。

  • Elastic MoE[4] 将活跃专家的数量扩展到训练时的数值之外。使用 top-2 路由训练的模型可以在推理时使用 top-4 或 top-6,在提升性能的同时达到训练时 2–3 倍的专家数量。这实现了训练与推理配置的解耦。

  • ReMoE[5] 用完全可微的基于 ReLU 的路由取代了不可微的 TopK+Softmax 路由。这使得动态计算分配更加高效,并简化了训练动态。

code/moe_layer.py 中的代码提供了扩展第 5 章概念的实现,包括采用无辅助损失方法的负载均衡路由。

边缘-云连续体

分布式 AI 中最深刻的转变之一是云与边缘之间界限的模糊。多年来,人们的假设很简单:在云端训练,也许在云端提供服务,而边缘设备仅用于消费。这种模式正在瓦解。

为什么边缘 AI 现在很重要

边缘 AI 的优势令人信服。消除网络往返后,延迟从数百毫秒降至个位数。当数据从不离开设备时,隐私性得到显著提升。当你不必为云端推理按 token 付费时,成本得以降低。而离线能力则开启了全新的用例。

是什么变化让这一切变得切实可行?三股力量汇聚在一起。首先,模型架构变得更高效。参数量低于十亿的模型现在可以处理以前需要 7B+ 模型才能完成的实际任务。其次,量化技术成熟了。从 16 位降到 4 位可将存储和内存流量都减少 4 倍,使用 GPTQ[6] 和 AWQ[7] 等方法时质量损失极小。第三,硬件进步了。移动端 NPU 现在已足够强大,能够以可接受的速度运行这些量化模型。

但这里有一个常被忽略的微妙之处。移动设备的瓶颈不是算力,而是内存带宽。手机的带宽约为 50–90 GB/s;而 H100 级别的数据中心 GPU 每秒可处理数 TB 数据——通常比较下来差距为 30–50 倍,与 H200 相比差距更大,与 V100 等较老的显卡相比差距则较小。量化之所以重要,是因为它减少了每次前向传播中经过该总线的字节数,而不仅仅是磁盘上存储的权重。

实用的端侧模型

具备边缘能力的模型格局已经迅速成熟:

| 模型 | 参数 | 内存 | 用例 |

|---|---|---|---|

| Gemma 3n | 5B/8B | 2–3GB | 通用助手 |

| Llama 3.2 | 1B/3B | 1–2GB | 文本任务 |

| Phi-4 mini | 3.8B | 2GB | 推理 |

| Qwen2.5 | 0.5B–1.5B | <1GB | 轻量级任务 |

Google 的 Gemma 3n 使用 Per-Layer Embeddings(逐层嵌入)运行 5B 和 8B 参数模型,内存占用分别相当于 2B 和 4B 模型——总计约 2–3GB。Meta 的 Llama 3.2 提供专为边缘部署设计的 1B 和 3B 版本。Microsoft 的 Phi-4 mini 将强大的推理能力塞进 3.8B 参数。Alibaba 的 Qwen2.5 提供 0.5B 到 1.5B 的多种尺寸,满足最轻量级的任务需求。

关键的洞察在于,在小规模下,架构和训练质量比原始尺寸更重要。一个训练良好的 1B 模型在许多任务上可以胜过训练不佳的 3B 模型。

边缘-云协同

最有趣的系统不会把边缘和云视为两个独立的世界——它们会设法让两者协同工作。还记得第 7 章中的推测解码吗?我们用一个小型草稿模型提议 token,再由一个更大的模型进行验证,从而在单台服务器上加速推理。同样的思路在边缘-云边界上也能出色地发挥作用。

想象一下,你的手机运行着一个小巧但快速的模型。它生成一串候选 token——比如 "The cat sat on the"——然后将其发送给一个强大的云端模型。云端不生成任何内容,只检查每个 token 是否与它本会生成的 token 一致。验证成本很低:云端模型可以在一次前向传播中评估全部五个 token,而逐个生成则需要五次。当草稿大部分正确时(对于可预测的文本,情况往往如此),你就能以边缘般的速度获得云端质量的输出。收益取决于任务:代码或 JSON 等结构化输出通常能实现 3–4 倍的端到端加速,而开放式创意文本可能只会提升 1.2–1.5 倍,因为拒绝的情况很常见[8]。

图 11.1:边缘-云推测解码工作流

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_513_0.png

图 11.1 展示了这一协作过程。边缘设备提议 token(黄色圆圈),将其发送到云端,验证器将每个 token 标记为接受(绿色)或拒绝(红色)。在这个例子中,"on" 被拒绝——也许云端模型更倾向于使用不同的介词——因此边缘设备需要从该位置重新生成。但五个 token 中有四个顺利通过,显著节省了延迟。

智能路由将这种协同推向了新的高度。并非每个请求都需要云端参与。一个设计良好的系统会评估每个请求的难度,检查边缘模型的置信度,观察网络状况,然后决定:在本地处理还是发送到云端?简单查询——"东京现在几点了?"——留在设备上处理。复杂的推理任务则交给云端。路由逻辑甚至可以从过去的决策中学习,在预测哪些请求可以在本地成功处理方面变得越来越准确。

code/edge_cloud.py 文件实现了这些模式:EdgeCloudSpeculativeDecoding 用于草稿-验证循环,IntelligentOffloading 用于基于复杂度的路由,以及随时间不断改进的 AdaptiveRouter。

大规模并行

我们之前介绍的并行策略——数据并行(第 3 章的 DDP、第 4 章的 FSDP)、张量并行和流水线并行(第 5 章)——仍然是基础。但在 100K+ GPU 规模下,新的挑战不断涌现,需要新的解决方案。

通信革命

PyTorch 引入 Torchcomms 标志着分布式训练基础设施的重大演进。之前的通信栈虽然可用,但在大规模场景下已显老态。Torchcomms 从头开始为 100K+ GPU 部署设计,并带来了几项关键创新。

首先,它将通信原语与 PyTorch 核心解耦,使研究人员能够独立迭代新的集合通信和后端。其次,它使用即时初始化和模型特定提示,在大规模场景下优化通信器和资源分配。第三,它支持异构硬件——在单个训练任务中混合部署多个厂商和不同代际的 GPU。第四,它内置了以往事后才考虑的容错机制。

Meta 随 Torchcomms 一起发布的 NCCLX 后端,以及面向 AMD 平台的 RCCLX,通过 Direct Data Access(DDA)等技术为 AllReduce 操作带来 10–50% 的加速。这些不是渐进式的改进——它们可能决定一次训练运行在经济上是否可行。

分层并行

在大规模场景下,你不是选择一种并行策略,而是将它们组合起来。一次典型的大型训练可能使用跨节点的数据并行、节点内的张量并行,以及跨模型阶段的流水线并行。计算很简单:如果你有 world_size 个 GPU,那么 dp_size × tp_size × pp_size = world_size。

精妙之处在于选择正确的组合。张量并行在节点内通信开销低(NVLink 速度快),但跨节点开销高。流水线并行可以隐藏通信延迟,但会引入气泡开销。数据并行扩展性好

但这需要梯度同步。最佳配置取决于你的模型架构、硬件拓扑和批大小约束。

code/parallelism.py 中的 HierarchicalParallelism 类负责管理所有三个维度的进程组创建,确保每个 GPU 都清楚自己在每种并行类型中属于哪些组。

面向长上下文的序列并行

随着上下文长度不断增长,序列并行变得至关重要。其核心思想是将序列维度拆分到多个 GPU 上。每个 GPU 对自己处理的序列分块计算注意力,但注意力机制需要看到完整的键(Key)和值(Value)张量。这意味着需要从所有序列并行 rank 上全量收集(all-gather)K 和 V。

SGLang 的流水线并行在长提示词场景下表现出色:DeepSeek-V3.1 上预填充吞吐量提升 3.31 倍,百万 token 上下文下 TTFT(首 token 延迟)最高降低 81%,扩展效率达 82.8%。这些技术解决了内存和延迟问题;而检索质量是另一个独立的问题——许多模型在处理远超其标称上下文长度的长文档时,准确率会明显下降。

环形注意力

我们在第 5 章作为上下文并行的一部分引入了环形注意力(见图 5.6)。这里我们重新审视它,探讨其在扩展到百万 token 序列时的作用。

环形注意力[9]为标准的序列并行提供了一种内存高效的替代方案。它不需要全量收集完整的 K 和 V 张量(这要求每个 GPU 占用 O(序列长度) 的内存),而是在 GPU 环上传递 K 和 V 分块,并在每一步计算部分注意力分数。每个 GPU 从自己的本地 Q、K、V 分块开始。在每一轮中,GPU 计算本地 Q 与当前 K、V 之间的注意力,然后将 K 和 V 传递给下一个 GPU。经过 N−1 轮后,每个 rank 都已对所有键和值完成注意力计算。

这种方案用通信轮数换取了内存效率——当你受内存限制但有富余的通信带宽时,它会很有用。在百万 token 规模下,这种权衡变得越来越有吸引力:避免全量收集 K、V 所节省的内存,可能正是任务能否成功运行与内存溢出之间的分水岭。code/parallelism.py 中的 RingAttention 类实现了这一模式。

当问题出现时:容错机制

假设每块 GPU 的日可靠度为 99.9%,那么一个拥有 10 万块 GPU 的集群在每 24 小时内预计会出现大约 100 次故障。所有设备当天都正常存活的概率是 0.999^(100,000) ≈ 3.7×10^(-44)——实际上等于零。在这种规模下,故障是常态运行条件,而不是例外。

检查点带来的挑战

传统的检查点机制是同步的:暂停训练,将状态保存到磁盘,然后恢复。当检查点只需几秒、训练任务只需几小时时,这种方式可以接受。但在大规模场景下,检查点可能需要几分钟,训练任务则持续数周。开销变得相当可观。

PyTorch 的分布式检查点(DCP)引入了多项优化,使检查点处理速度提高了大约 6 倍:

  • 缓存保存计划:跨检查点复用张量元数据——形状和数据类型不会变化,何必重新计算?

  • 后台保存:将实际的 I/O 操作移到独立线程,减少 GIL 竞争

  • 增量保存:只写入自上次检查点以来发生变化的张量

code/fault_tolerance.py 中的 AsyncCheckpointer 实现了这些思路。它维护一个后台线程,从队列中处理保存请求,从而在写入检查点的同时继续训练。它还负责检查点的保留策略,自动清理旧检查点以节省磁盘空间。

故障检测与处理

故障检测听起来很简单——只需检查工作节点是否响应——但细节很重要。你在宣告一个工作节点死亡之前会等待多久?等待太短,网络抖动会造成误报;等待太长,你又会浪费时间等待一个确实已经死掉的工作节点。

FailureDetector 类采用心跳机制。工作节点会定期发送心跳;心跳缺失会触发故障回调。系统随后可以决定如何响应:用剩余的工作节点继续,重启失败的工作节点,或者保存检查点并中止。

关键设计决策包括:

  • 心跳间隔:通常为 1-5 秒,在响应速度与开销之间取得平衡

  • 超时阈值:通常连续 3-5 次心跳缺失才宣告故障

  • 故障回调:采取的具体行动——记录日志、报警、保存检查点或中止

弹性训练

最复杂的方法是弹性训练,即系统在不停止的情况下适应工作节点的变化。在训练过程中,工作节点可以随时加入或离开。当一个工作节点失败时,系统会保存检查点,在剩余工作节点间重新分配任务,然后继续。当新工作节点加入时,它会分到一部分任务。

这需要细致的协调:

  • 梯度平均必须考虑工作节点数量的变化

  • 数据加载必须动态重新分配数据分片

  • 学习率调度可能需要针对不同的工作节点数量进行调整

  • 检查点机制必须在状态转换期间处理部分状态

code/fault_tolerance.py 中的 ElasticTrainer 类处理了这些问题,在故障发生时自动保存检查点并优雅降级。

超越文本:多模态分布式训练

如今最受关注的模型并非纯文本模型——而是多模态模型。能够看和读的视觉-语言模型(VLM)、能处理视频的模型、能同时理解音频和文本的系统。高效地分布式训练这些模型需要新的模式。

跨模态挑战

一个典型的 VLM 通常包含一个视觉编码器(通常是基于 ViT 的),用于生成图像特征,以及一个语言模型,它在处理文本的同时会关注这些图像特征。跨模态注意力——即文本 token 关注视觉特征的部分——正是两种模态交汇之处。

这种跨模态注意力可以使用张量并行进行并行化,将注意力头分散到多个 GPU 上。但有一个微妙之处:视觉编码器和语言模型可能有各自不同的最优并行策略。视觉编码器处理固定尺寸的图像,更受益于与语言模型不同的批大小,因为语言模型要处理的是可变长度的文本。

code/multimodal.py 文件提供了这些组件的实现:

  • VisionEncoder:ViT 风格的图像编码器,包含 patch 嵌入和 transformer 块

  • CrossModalAttention:支持张量并行的文本到视觉注意力

  • VisionLanguageModel:结合视觉和语言组件的完整 VLM

处理多种模态

不同模态具有不同的特性:

| 模态 | 尺寸 | 特性 |

| --- | --- | --- |

| 图像 | 预处理后固定 | 适合批处理,内存可预测 |

| 文本 | 长度可变 | 需要填充,内存动态变化 |

| 视频 | 多帧 | 内存需求高,具有时间结构 |

| 音频 | 时长可变 | 具有时间结构,流式处理 |

MultimodalDataParallel 负责将批次分散到各工作节点,同时保持模态对齐。这比听起来要复杂得多——你需要确保与文本提示对应的图像最终落在同一个工作节点上,即使不同模态的批大小不同。

联邦学习:另一种范式

贯穿本书,我们一直假设所有 GPU 都能访问共享数据集——或者至少能访问存储在共同数据中心中的数据集分片。但如果数据无法移动呢?如果隐私法规、竞争考量或纯粹的物流因素使得集中化不可行呢?这就是联邦学习发挥作用的地方。

联邦范式

联邦学习扭转了传统思路:它不是将数据带给模型,而是将模型带给数据。每个参与者——无论是智能手机、医院还是银行——都在本地数据上训练,并且只共享模型更新,绝不共享原始数据。中央服务器聚合这些更新,生成一个全局模型。这种范式自 2016 年就已存在,如今已经发展成为隐私敏感领域的实用解决方案。

图 11.2 展示了这种架构。顶部是一台中央服务器,负责协调训练并持有全局模型。底部有四个客户端——一家拥有患者记录的医院、一家拥有交易数据的银行、一个拥有用户行为的移动应用,以及一个拥有传感器数据的物联网设备集群——每个客户端都在各自的私有数据上进行本地训练。蓝色箭头表示服务器分发当前模型;绿色箭头表示客户端发回更新。底部强调的关键一点是:数据永远不会离开客户端。只有梯度和权重在网络中传输。

图 11.2:联邦学习:数据留在客户端

典型算法是 FedAvg[10]。每一轮,服务器将当前模型发送给一部分客户端。客户端在本地训练若干 epoch,然后发回更新后的权重。服务器对这些权重求平均,生成新的全局模型。尽管听起来很简单,但这一协议已被证明非常有效——并且该领域在其基础上构建了许多改进。

联邦学习蓬勃发展的领域

联邦学习在特定垂直领域得到了广泛应用:

  • 医疗健康:医院可以在不共享患者病历的情况下协同训练诊断模型。NVIDIA FLARE[11] 等项目支持多机构医学影像研究,同时保持 HIPAA 合规性。在 20 家医院联合训练的模型所看到的病理多样性,远超任何单一机构所能提供。

  • 移动键盘:谷歌的 Gboard 使用联邦学习来改进下一个词预测,而无需上传用户输入内容。模型从数百万台设备中学习,同时将文本保留在设备端。这是联邦学习最早的规模化生产部署之一。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_519_0.png

  • 金融服务:银行可以在不共享交易数据的情况下协作构建欺诈检测模型。每家机构贡献其客户群体中的模式;组合后的模型能够捕捉单一银行数据无法揭示的欺诈行为。

  • 边缘物联网:工业传感器、自动驾驶车辆和智能设备产生的数据,集中起来成本高昂或不切实际。联邦学习可以在无需大规模数据传输的情况下改进模型。

异构性带来的挑战

联邦学习面临着传统分布式训练中不存在的挑战:

  • 非独立同分布(Non-IID)数据:在数据中心,你可以打乱数据以确保每个 GPU 看到具有代表性的样本。而在联邦学习中,每个客户端的数据反映其本地分布。在日本训练的键盘模型看到的文本与在巴西训练的不同。这种统计异构性可能导致模型发散和收敛缓慢。

  • 系统异构性:客户端的计算能力差异巨大。旗舰智能手机和三年前的廉价手机无法以相同速度训练。有些客户端可能因电池限制或网络问题在训练中途退出。系统必须对掉队者和部分参与具有鲁棒性。

  • 通信约束:与通过 NVLink 连接的 GPU 不同,联邦客户端通过移动网络或互联网通信。带宽有限且昂贵。梯度压缩、量化和低频同步等技术变得至关重要——不是为了性能,而是为了可行性。

现代联邦技术

研究通过日益复杂的方法解决了这些挑战:

  • FedProx[12] 在本地目标中添加了一个近端项,防止客户端偏离全局模型过远。当数据高度非 IID 时,这能稳定训练。

  • Scaffold[13] 使用控制变量来修正客户端漂移,在异构数据上比 FedAvg 收敛更快。

  • 差分隐私可以叠加在联邦学习之上,提供正式的隐私保证。通过对更新添加校准噪声,即使聚合后的模型也只能揭示关于任何个人数据的有限信息。这在医疗和金融领域尤为重要。

  • 安全聚合[14] 确保服务器只能看到客户端更新的总和,而非单个贡献。即使服务器被攻破,也无法提取任何单个客户端的模型更新。

面向大语言模型的联邦学习

将联邦学习应用于大语言模型面临独特的挑战。7B 参数的模型无法放入智能手机,即使是微调也需要大量内存。近期研究探索了几种方法:

  • 基于 LoRA 的联邦微调:客户端不更新所有参数,而是训练低秩适配器。这减少了通信量(只交换适配器权重)和内存占用(适配器很小)。FedIT[15] 及类似方法使联邦大模型微调变得可行。

  • 拆分学习:模型在客户端和服务器之间拆分。客户端在本地数据上运行浅层,并将激活值(而非原始数据)发送给服务器,由服务器完成前向传播。这使得训练大型模型时客户端无需持有完整模型。

  • 设备端个性化:不是训练单一的全局模型,而是每台设备维护一个个性化版本。全局模型提供起点;本地微调适应用户个体。这对于应体现用户偏好的助手尤其重要。

Flower 框架

Flower[16] 已成为领先的开源联邦学习框架。它提供:

  • 用于定义联邦训练循环的简单 API

  • 支持多种聚合策略(FedAvg、FedProx 等)

  • 与 PyTorch、TensorFlow 和 JAX 集成

  • 用于开发和研究的仿真能力

  • 生产部署工具

该框架抽象掉了大部分复杂性,让研究人员专注于算法而非基础设施。一个基本的联邦训练设置只需几十行代码。

何时考虑使用联邦学习

联邦学习并非传统分布式训练的替代品——它是针对特定场景的工具:

| 在以下情况考虑联邦学习 | 在以下情况坚持传统训练 |

| --- | --- |

| 数据不能离开其来源(隐私、监管) | 数据可以集中 |

| 数据天然分布(移动设备、边缘) | 你掌控基础设施 |

| 参与者互不信任 | 单组织训练 |

| 通信成本高昂 | 可用的高带宽互联 |

联邦学习的开销——收敛更慢、通信约束、异构性挑战——意味着在你可以直接集中数据时,它不是正确的选择。但当隐私或现实条件使集中化不可能时,联邦学习实现了否则无法发生的协作。

智能体 AI:下一个前沿

也许近期最令人兴奋的发展是智能体 AI 的兴起——这类系统不仅响应提示,还能规划、使用工具并采取行动。这些系统将分布式推理推向新的方向。

为什么智能体需要分布式

考虑当智能体决定调用工具时会发生什么。工具可能是代码解释器、网页浏览器、数据库查询或 API 调用。每个工具都有不同的资源需求和延迟特征。有些工具有状态,需要在特定的工作节点上运行;有些可以轻松并行;另一些则是顺序瓶颈。

多智能体系统又增加了一个层次。多个智能体可能协作完成一项任务,各自具备不同能力。它们需要沟通、协调,有时也会产生分歧。这种通信可以发生在单个节点内,也可以跨越分布式系统。

长推理链——类似 o1 和 DeepSeek-R1[17] 等模型产生的推理链——需要在多个步骤上进行持续推理。每一步都可能涉及工具调用、内存查找或与其他智能体的协调。推理不是一次简单的前向传播,而是一种可能运行数分钟的扩展计算。

多智能体模式

你如何编排多个智能体协同工作?有三种模式已成为多智能体系统的中坚力量,每种模式适用于不同的问题结构。

图 11.3:三种常见的多智能体协调模式

图 11.3 展示了这些模式。在顺序处理中,智能体形成一条流水线:一个智能体提取信息,下一个对其推理,第三个格式化响应。每个智能体的输出成为下一个智能体的输入。当任务能自然地分解为具有明确交接点的阶段时,这种方法效果很好。

并行处理采用不同的方法——多个智能体同时处理同一任务。也许你想要不同的视角:一个智能体可能找到创造性解决方案,而另一个找到安全方案。或者你让多种策略竞速,选用先完成的那一个。结果流向一个聚合器,由它合并或从中选择。

层次化处理反映了人类团队常见的协作方式。协调者接收任务,将其分解为子任务,并委派给专门的工作节点。工作节点汇报结果(图中的绿色虚线箭头),协调者将他们的结果综合为最终输出。这种方法在处理自然分解的复杂任务时表现出色——协调者负责策略,工作节点负责执行。

code/agentic_inference.py 文件实现了全部三种模式,以及根据任务特征在它们之间进行选择的路由逻辑。

分布式工具执行

当智能体决定调用工具时,该工具实际运行在哪里?并非所有工具都是平等的。代码解释器需要具备适当安全隔离的沙盒环境——你

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_523_0.png

不能在任何地方随意运行代码。网页浏览器可能需要 GPU 加速来进行渲染。而一个简单的计算器则可以在任何有空闲计算周期的节点上运行。

code/agentic_inference.py 中的 DistributedToolExecutor 负责这种路由。工具在注册时带有可选的节点约束:“我需要沙箱隔离”,“我需要 GPU”,或者“我是无状态的,可以任意运行”。当智能体请求某个工具时,执行器会找到合适的节点,分发调用,并返回结果。

这带来了复杂的放置策略。常用工具会在多个节点间复制以实现负载均衡。有状态工具——比如数据库连接或浏览器会话——会被固定在特定节点上,以便在多次调用之间保持状态。资源密集型工具会被隔离到专用节点上,以免它们挤占其他操作的资源。执行器会跟踪执行统计信息,随着时间推移学习哪些放置方式效果最好。

推理智能体

最先进的智能体不只是响应——它们会思考。面对复杂问题时,它们会分解问题、尝试不同方法、观察结果并做出调整。这正是 o1 和 DeepSeek-R1 这类推理智能体的领域,其推理过程不是一次简单的前向传播,而是可能跨越数十步的扩展性深思。

code/agentic_inference.py 中的 ReasoningAgent 捕获了这一模式。它维护一个推理轨迹——即思考过程、工具调用和观察结果的持续记录。在每一步,智能体都面临一个选择:更深入地思考当前状态、调用工具收集信息,或者给出最终答案。当它调用工具时,结果会成为一个新的观察,并进入下一步思考。这个循环会一直持续,直到智能体有足够信心回答问题或达到步数上限。

这是生产级推理系统所做工作的简化版本,但它抓住了核心动态:思考与行动之间、内部深思与外部观察之间的相互作用,使得智能体能够解决那些单次推理无法解决的复杂问题。

规模经济

随着分布式 AI 日趋成熟,经济考量变得越来越重要。“多堆 GPU 就行”的时代已经结束;效率和成本优化现在已成为头等大事。

工作负载分布

企业工作负载分布中,没有任何单一的主导类别:

| 工作负载类型 | 占比 |

|---|---|

| 大规模推理 | 34.6% |

| 基础模型训练 | 24.9% |

| 领域特定训练 | 23.3% |

| 微调 | 17.2% |

这种多样性意味着基础设施必须具备灵活性——如果只为某一种工作负载优化,就无法满足全部需求。

各类组织正在采用混合策略:

  • 公有云:处理需求不可预测的弹性、可变工作负载

  • 本地部署:服务于成本可预测性至关重要的大规模生产推理

  • 近边缘数据中心:处理对延迟敏感、面向用户的 AI 服务

资源管理

当工作负载变化时,动态资源分配至关重要。code/resource_management.py 中的 AdaptiveGPUAllocator 会基于以下条件估算 GPU 需求:

  • 模型大小(内存需求)

  • 批量大小(吞吐量需求)

  • 作业优先级(业务重要性)

它会为无法立即调度的作业维护一个队列,并在资源可用时处理该队列。

多租户 GPU 共享

MultiTenantGPUScheduler 实现了时间片 GPU 共享:

  • 优先权加权的轮询调度

  • 租户间的公平分配

  • 待处理工作的队列管理

  • 高优先级作业的抢占

这允许多个团队或服务公平地共享 GPU 资源,优先级体现了业务重要性。

梯度压缩

在规模较大时,通信开销可能主导计算时间。code/resource_management.py 中的 GradientCompression 可以降低这种开销:

  • Top-k 稀疏化[18]:仅保留最大的 k 个梯度值。当 k 为总梯度的 1% 时,可实现 100 倍压缩。误差反馈——将丢弃的梯度累积到下一迭代——保持了收敛性。

  • 量化:将精度降低到 8 位。这可在对训练动态影响最小的情况下实现 4 倍压缩。可与稀疏化结合以得到更高压缩率。

关键的洞见在于梯度具有高度可压缩性。大多数梯度值很小,对学习的贡献微乎其微。通过将通信集中在最大的值上并累积其余部分,可以在不牺牲收敛性的前提下大幅降低带宽需求。

为未来做好准备

需培养的技能

  • MoE 与稀疏架构:随着 MoE 成为默认架构,理解专家路由策略(TopK、基于 ReLU 的)、负载均衡机制以及推理时的弹性专家扩展正变得越来越重要。

  • 边缘-云协同:推测解码实现、面向移动端的内存带宽优化,以及量化技术(GPTQ、AWQ)打开了直到最近才切实可行的新部署选项。

  • 大规模通信:Torchcomms API 与 NCCLX/RCCLX 后端、异步检查点策略以及异构硬件部署,支持更大规模的训练任务和更高效的基础设施利用。

  • 推理优化:RadixAttention 和 PagedAttention 内部机制、长上下文的流水线并行以及多租户 GPU 调度,可提高服务效率并降低成本。

  • 智能体系统:多智能体编排模式、分布式工具执行和推理链优化,代表了分布式 AI 与复杂问题求解相遇的下一个应用前沿。

保持跟进

这个领域发展很快——今天看似前沿的技术,六个月后可能就成了标准做法。保持跟进需要在几个关键信息源上养成习惯。

对于日常学习,arXiv 不可或缺。cs.DC 和 cs.LG 每天都有新论文发布,而且许多最佳成果往往会提前几个月出现在这里,之后才在会议上发表。为“分布式训练”“推理优化”和“混合专家模型”等关键词设置提醒。开源项目同样重要:vLLM、SGLang、DeepSpeed 和 Megatron-LM 是理论与实践交汇的地方。关注它们的发布说明和 GitHub 讨论——在那里你能了解到哪些方法真的能在大规模下起作用。来自 xAI、Anthropic、Google 和 Meta 团队的行业博客会定期发布技术深度文章,揭示论文中从未出现的实用见解。也不要忽视社区:Hugging Face 论坛、PyTorch Discuss 和 SGLang Discord 是实践者分享经验教训、共同排查棘手问题的地方。

想要深入钻研,会议仍然必不可少。NeurIPS、ICML 和 ICLR 等研究会议是新算法和新架构的首发之地——要特别关注关于高效机器学习和大型系统的研讨会。MLSys、OSDI 和 SOSP 等系统会议则聚焦基础设施、通信优化和生产部署;如果你关心如何让系统变快,这些都是必读内容。GTC 和 PyTorch Conference 等行业活动是厂商发布新硬件和框架的场合,可以让你提前了解未来 12–18 个月的趋势。

本章末尾的“参考文献”部分提供了文中提及的关键论文和项目的 URL。

总结

本章探讨了分布式 AI 的现状以及塑造其未来的趋势:

  1. 向推理的重大转变:推理工作负载如今消耗了超过 55% 的 AI 基础设施支出,而训练在总算力中所占比例逐渐变小。这重塑了基础设施的优先事项和优化目标。

  2. MoE 主导地位:像 DeepSeek-V3 这样的架构证明,671B 参数的模型只需 550 万美元即可完成训练,每个 token 仅激活 37B 参数。理解专家路由、负载均衡和并行性至关重要。

  3. 端侧 AI 已切实可行:十亿参数以下的模型、4-bit 量化以及逐层嵌入等技术使得大语言模型能够运行在移动设备上。瓶颈是内存带宽,而非算力。

分布式AI不断演变的格局

10万+ GPU规模:新的通信API(Torchcomms、NCCLX/RCCLX)和异步检查点技术使前所未有的规模训练成为可能。异构硬件支持日益重要。

容错能力至关重要:在超大规模下,故障是常态——在10万GPU集群中每天约发生100次故障。弹性训练和异步检查点不再是可选项,而是必选项。

多模态与智能体:VLM和多智能体系统需要新的分布式模式来支持跨模态注意力与工具执行。这些代表了下一个应用前沿。

联邦学习作为替代范式:当数据因隐私或监管限制无法集中时,联邦学习提供了一种成熟的替代方案。FedProx和安全聚合等技术解决了非独立同分布数据和不可信参与者带来的独特挑战。

推理引擎演进:SGLang借助RadixAttention实现了16,215 tok/s;vLLM的PagedAttention将KV缓存浪费降低到4%以下。两者都支持多种硬件平台。

本书涵盖的技术——DDP、FSDP、DeepSpeed、Megatron-LM、vLLM、SGLang——仍然是基础,但技术格局在持续快速演变。现在的首要瓶颈是功耗与散热约束,而非硅片供应。

如果你从第1章一路读到这里,你就是从大多数从业者的起点出发:一个超出单GPU容量的模型,以及一系列关于硬件、并行、训练框架、推理引擎和生产部署的问题。本章将这些内容放在未来的背景下——大规模MoE、端侧推理、容错,以及一个推理而非训练主导大部分基础设施支出的世界。工具会不断变化,但你一路建立的系统思维不会过时。

分布式AI的未来正在被书写,由那些突破边界的研究者和大规模部署的实践者共同创造。读完本书后,你已更有能力去跟进这些工作——并参与开创这些工作。

代码总结

  • torch.distributed.checkpoint.save:使用DCP进行异步分布式检查点

  • torch.distributed.checkpoint.load:跨rank加载分片检查点

  • torch.distributed.elastic.multiprocessing:带容错能力的弹性训练

  • torch.ao.quantization.quantize_dynamic:用于模型压缩的动态量化

  • flwr.client.NumPyClient:Flower联邦学习客户端接口

  • flwr.server.strategy.FedAvg:联邦平均聚合策略

  • megatron.core.transformer.moe.router.TopKRouter:Megatron MoE top-k路由

  • vllm.LLM:带PagedAttention的vLLM推理引擎

  • sglang.Engine:带RadixAttention的SGLang运行时

实用链接

MoE架构

  • DeepSeek-V3技术报告:https://arxiv.org/abs/2412.19437

  • LatentMoE:实现每FLOP最优精度:https://arxiv.org/abs/2601.18089

  • MoSE:可精简专家混合:https://arxiv.org/abs/2602.06154

  • Elastic MoE:推理时专家扩展:https://arxiv.org/abs/2501.03140

  • ReMoE:使用ReLU路由的全可微MoE:https://arxiv.org/abs/2412.14711

通信与规模

  • NVIDIA NCCL:https://developer.nvidia.com/nccl

  • Torchcomms API:https://pytorch.org/blog/torchcomms/

  • 面向AMD平台的RCCLX:https://engineering.fb.com/2026/02/24/data-center-engineering/rrcclx-innovating-gpu-communications-amd-platforms-meta/

  • PyTorch分布式检查点:https://pytorch.org/docs/stable/distributed.checkpoint.html

  • 异步检查点改进:https://pytorch.org/blog/6x-faster-async-checkpointing/

  • 深度梯度压缩:https://arxiv.org/abs/1712.01887

推理引擎

  • SGLang(RadixAttention):https://arxiv.org/abs/2312.07104

  • SGLang流水线并行:https://lmsys.org/blog/2026-01-15-chunked-pipeline/

  • SGLang文档:https://sgl-project.github.io/

  • vLLM(PagedAttention):https://arxiv.org/abs/2309.06180

  • vLLM文档:https://docs.vllm.ai/

  • 推测解码:https://arxiv.org/abs/2211.17192

内存高效注意力

  • 多头潜在注意力(DeepSeek-V2):https://arxiv.org/abs/2405.04434

  • Ring Attention:https://arxiv.org/abs/2310.01889

  • FlashAttention-2:https://arxiv.org/abs/2307.08691

端侧AI与量化

  • GPTQ:训练后量化:https://arxiv.org/abs/2210.17323

  • AWQ:激活感知权重量化:https://arxiv.org/abs/2306.00978

  • KV缓存压缩:https://arxiv.org/abs/2405.12981

  • Google LiteRT:https://ai.google.dev/edge/litert

  • Gemma 3n:https://ai.google.dev/gemma/docs/gemma-3n

推理与智能体AI

  • DeepSeek-R1:https://arxiv.org/abs/2501.12948

  • ReAct:语言模型中的推理与行动:https://arxiv.org/abs/2210.03629

容错与弹性训练

  • PyTorch Elastic:https://pytorch.org/docs/stable/elastic/run.html

  • TorchSnapshot:https://pytorch.org/torchsnapshot/

联邦学习

  • FedAvg:来自分散数据的通信高效学习:https://arxiv.org/abs/1602.05629

  • FedProx:异构网络中的联邦优化:https://arxiv.org/abs/1812.06127

  • SCAFFOLD:随机控制平均:https://arxiv.org/abs/1910.06378

  • 用于隐私保护机器学习的安全聚合:https://eprint.iacr.org/2017/281

  • Flower框架:https://flower.ai/

  • NVIDIA FLARE:https://github.com/NVIDIA/NVFlare

  • FedIT:LLM的联邦指令微调:https://arxiv.org/abs/2409.12568

参考文献

  1. LMSYS/SGLang项目更新:https://github.com/sgl-project/sglang,https://lmsys.org/blog/

  2. LatentMoE:实现每FLOP最优精度:https://arxiv.org/abs/2601.18089

  3. MoSE:可精简专家混合:https://arxiv.org/abs/2602.06154

  4. Elastic MoE:推理时专家扩展:https://arxiv.org/abs/2501.03140

  5. ReMoE:使用ReLU路由的全可微专家混合:https://arxiv.org/abs/2412.14711

  6. GPTQ:生成式预训练Transformer的精准训练后量化:https://arxiv.org/abs/2210.17323

  7. AWQ:用于LLM压缩与加速的激活感知权重量化:https://arxiv.org/abs/2306.00978

  8. 通过推测解码实现Transformer快速推理:https://arxiv.org/abs/2211.17192

  9. 使用分块Transformer实现近无限上下文的Ring Attention:https://arxiv.org/abs/2310.01889

  10. 从分散数据中进行深度网络的通信高效学习:https://arxiv.org/abs/1602.05629

  11. NVIDIA FLARE:联邦学习应用运行时环境:https://github.com/NVIDIA/NVFlare

  12. 异构网络中的联邦优化:https://arxiv.org/abs/1812.06127

  13. SCAFFOLD:用于联邦学习的随机控制平均:https://arxiv.org/abs/1910.06378

  14. 用于隐私保护机器学习的安全聚合实践:https://eprint.iacr.org/2017/281

  15. 结合领域覆盖增强的LLM联邦指令微调:https://arxiv.org/abs/2409.12568

  16. Flower:友好的联邦学习框架:https://flower.ai/

  17. DeepSeek-R1:通过强化学习激发LLM的推理能力:https://arxiv.org/abs/2501.12948

  18. 深度梯度压缩:降低分布式训练的通信带宽:https://arxiv.org/abs/1712.01887

练习

选择题

  1. MoE(专家混合)架构的主要优势是什么?

    a) 更快的训练收敛

    b) 在不按比例增加计算量的情况下扩展模型容量

    c) 消除分布式训练需求

    d) 减小模型尺寸

  2. 在一个包含64个专家并使用top-2路由的典型MoE层中,每个token由多少个专家处理?

    a) 1

    b) 2

    c) 32

    d) 64

  3. 移动设备上端侧LLM推理的主要瓶颈是什么?

    a) CPU计算

    b) 存储空间

    c) 内存带宽

    d) 电池容量

  4. 推测解码在边缘-云场景中实现了什么?

    a) 减小模型尺寸

    b) 使用一个小型草稿模型生成候选token,再由更大的模型验证

    c) 消除网络延迟

    d) 压缩梯度

  5. 在10万GPU规模且单GPU可靠性为99.9%的情况下,每天大约会发生多少次故障?

    a) 1

    b) 10

    c) 100

    d) 1000

  6. MoE训练中负载均衡损失的作用是什么?

    a) 减少内存使用

    b) 鼓励专家均匀利用

    c) 加速推理

    d) 压缩模型权重

  7. 在专家并行中,哪种通信模式代价最高?

    a) 广播(Broadcast)

    b) 归约(Reduce)

    c) 全对全(All-to-all)

    d) 收集(Gather)

  8. Ring Attention为降低内存使用带来了什么权衡?

    a) 精度

    b) 更多通信轮次

    c) 更大的批大小

    d) 更少的专家

简答题

  1. 解释为什么推理工作负载现在消耗超过55%的AI基础设施支出,而仅仅几年前训练还占主导地位。

  2. 描述同步和异步检查点之间的关键区别,以及为什么异步检查点在超大规模下至关重要。

VLM(视觉-语言模型)使用跨模态注意力。解释这意味着什么,以及在分布式部署它时面临的一个挑战。

比较用于梯度压缩的top-k梯度稀疏化与量化,二者有哪些权衡取舍?

动手练习

实现基本的MoE路由

创建一个简单的MoE路由器,它可以:

  • 接收输入token并将其路由到top-k专家

  • 计算负载均衡损失

  • 返回加权后的专家输出

请参考code/moe_layer.py获取指导。使用4个专家和top-2路由进行测试,并在一批输入中衡量专家利用率。

获取本书的PDF版本及更多内容

扫描二维码(或访问packtpub.com/unlock)。按名称搜索本书,确认版本,然后按照页面上的步骤操作。

注意:请将您的购买凭证放在手边。直接从Packt购买时无需提供凭证。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_533_0.pnghttps://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_533_1.png

解锁您的专属福利

您所拥有的这本书包含以下专属福利:

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_535_0.png

通过3个简单步骤解锁本书的免费福利

步骤1

请为步骤3准备好您的购买凭证。如果您有实体书,请用手机扫描并保存为PDF、JPG或PNG格式。

如需进一步了解如何查找购买凭证,请访问https://www.packtpub.com/en-us/unlock?step=1。

步骤2

扫描二维码或访问packtpub.com/unlock。

在打开的页面(桌面端类似于图12.1)上,按名称搜索本书并选择正确的版本。

如果您直接从Packt购买本书,则无需购买凭证。完成步骤2后,您就可以立即访问专属内容。

注意

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_536_0.png

图12.1:Packt桌面端解锁登录页面

步骤3

选择您的书籍后,登录您的Packt账户或免费创建一个账户。然后上传您的购买凭证(PDF、PNG或JPG格式,最大10 MB)。按照屏幕上的说明完成流程。

需要帮助

如果您遇到困难需要帮助,请访问https://www.packtpub.com/unlock-benefits/help,查看关于如何查找购买凭证等问题的详细常见问题解答。此二维码将带您进入帮助页面。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_537_0.pnghttps://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_537_1.png

如果问题仍然存在,请联系customercare@packt.com。

注意

附录

OpenAI兼容API端点

vLLM和SGLang都提供OpenAI兼容的API。

下表列出了常用端点及其用法:

| 端点 | 方法 | 描述 | 用法 |

|---|---|---|---|

| /v1/models | GET | 列出可用模型 | curl http://localhost:8000/v1/models |

| /v1/completions | POST | 用于基础模型的文本补全 | 与prompt参数一起用于基础模型 |

| /v1/chat/completions | POST | 用于指令微调模型的聊天补全 | 与messages参数一起用于聊天模型 |

| /v1/embeddings | POST | 从文本生成嵌入 | 与input参数一起用于嵌入模型 |

| /v1/rerank | POST | 按与查询的相关性对文档重新排序 | 与query和documents参数一起使用(仅限SGLang) |

| /v1/classify | POST | 对文本输入进行分类 | 与input参数一起用于分类模型 |

| /generate | POST | 使用扩散模型生成文本 | 与text参数一起用于扩散语言模型(仅限SGLang) |

| /health | GET | 健康检查端点 | curl http://localhost:8000/health |

| /metrics | GET | Prometheus指标 | curl http://localhost:8000/metrics |

| /docs | GET | API文档(Swagger UI) | 在浏览器中打开:http://localhost:8000/docs |

注意: vLLM的默认端口是8000,SGLang的默认端口是30000。请相应调整上述示例中的端口。有关详细的API文档和特定模型的要求,请参阅各自的文档:

  • vLLM API参考

  • SGLang文档

2026

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/distributed-ai-systems/img/522d0e44234972210ef66d7489ddbd04_540_0.png

packtpub.com

订阅我们的在线数字图书馆,即可完整访问超过7,000本图书和视频,以及行业领先的工具,帮助您规划个人发展并推进职业生涯。

欲了解更多信息,请访问我们的网站。

为什么订阅?

  • 借助来自4,000多位行业专业人士的实用电子书和视频,减少学习时间,增加编码时间

  • 通过专为您打造的学习计划提升学习效果

posted @ 2026-09-25 20:43  绝不原创的飞龙  阅读(15)  评论(0)    收藏  举报