TorchServe 已不再维护,继续使用它意味着团队需要自行承担推理服务、GPU 运行时、依赖版本和故障排查的全部责任。对于已经运行在 Amazon EKS 上的团队,AWS Ray Serve Deep Learning Container(DLC)提供了一条更稳妥的迁移路径:把框架、GPU 驱动相关运行时和服务层预先组装在受支持、经过测试的容器中,再用 Ray Serve 部署视觉语言模型。
本文以单个 GPU 节点为假设,展示一套可以改造成实际项目的最小部署方式。示例中的 DLC 镜像地址和模型名称需要替换为你所在区域、账号权限和目标模型对应的值。
为什么不只是“换一个启动命令”
TorchServe 停止维护后,问题不只在于启动脚本过时。GPU 推理栈通常同时包含以下几层:
- CUDA、GPU 驱动和 PyTorch 的兼容性;
- Transformers、图像处理库以及模型权重的加载方式;
- HTTP 服务、并发控制、批处理和健康检查;
- Kubernetes 的 GPU 调度、滚动升级和故障恢复。
这些组件任意一层出现版本漂移,都可能导致“镜像能启动,但模型在 GPU 上运行失败”。Ray Serve DLC 的价值在于提供一个支持和预测试的基础容器,把常见的框架与服务运行时组合起来。团队仍然要负责模型代码、输入协议、资源配置和可观测性,但不必从零维护整套底层推理环境。
这并不代表 DLC 会自动解决所有问题。镜像版本、GPU 节点驱动、模型显存需求和区域可用性仍然需要在目标 EKS 集群中验证。
用一个轻量 Ray Serve 部署封装视觉语言模型
下面的示例使用 Transformers 的 image-to-text pipeline,接收图片二进制内容,并通过 X-Prompt 请求头传入提示词。它适合作为验证 DLC、GPU 调度和 HTTP 链路的最小样例;生产环境中通常需要根据具体视觉语言模型改写 processor 和生成参数。
把下面内容保存为 app.py。运行前,将 MODEL_ID 改成实际使用的模型,并确认基础镜像已经包含 ray[serve]、transformers、torch 和 Pillow。
import io
import os
from fastapi import Request
from PIL import Image
from ray import serve
from transformers import pipeline
MODEL_ID = os.getenv("MODEL_ID", "Salesforce/blip-image-captioning-base")
@serve.deployment(
num_replicas=1,
ray_actor_options={"num_gpus": 1},
)
class VisionLanguageModel:
def __init__(self):
self.generator = pipeline(
task="image-to-text",
model=MODEL_ID,
device=0,
)
async def __call__(self, request: Request):
image_bytes = await request.body()
if not image_bytes:
return {"error": "request body must contain image bytes"}
prompt = request.headers.get("X-Prompt", "")
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
result = self.generator(
image,
prompt=prompt or None,
max_new_tokens=128,
)
return {"model": MODEL_ID, "result": result}
app = VisionLanguageModel.bind()
num_gpus: 1 是关键约束:Ray 会为这个 deployment 申请一张 GPU。对于单 GPU 节点,这能避免多个副本意外争抢同一张卡。模型如果需要更大的显存,应改用更大规格的节点,或者调整模型量化、并行和副本策略,而不是简单增加 HTTP worker 数量。
构建镜像并部署到 EKS
DLC 通常作为基础镜像使用。由于不同区域、版本和访问权限对应的镜像地址可能不同,下面用环境变量表示镜像地址,不硬编码一个未经验证的 URI。
创建 Dockerfile:
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
WORKDIR /app
COPY app.py /app/app.py
# 如果基础 DLC 已经包含这些依赖,这一行可以删除。
# 不要在生产构建中无条件升级 torch 或 CUDA 相关包。
RUN python -m pip install --no-cache-dir fastapi pillow transformers
ENV MODEL_ID=Salesforce/blip-image-captioning-base
CMD ["serve", "run", "app:app", "--host", "0.0.0.0", "--port", "8000"]
构建时,把 BASE_IMAGE 替换成 AWS 提供、且与你的 CUDA/PyTorch 需求匹配的 Ray Serve DLC:
export BASE_IMAGE="<AWS_RAY_SERVE_DLC_IMAGE>"
export ECR_REPO="<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/ray-vlm"
aws ecr get-login-password --region "<REGION>" \
| docker login --username AWS --password-stdin "<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com"
docker build \
--build-arg BASE_IMAGE="${BASE_IMAGE}" \
-t "${ECR_REPO}:v1" .
docker push "${ECR_REPO}:v1"
如果 DLC 已经带有完整的 Transformers 和图像处理依赖,建议移除 Dockerfile 中的 pip install,减少构建时的依赖漂移。尤其不要在一个已经验证过 CUDA 和 PyTorch 的镜像中随意升级 torch。
下面是一个最小 Kubernetes Deployment 和 Service。它假设集群已经安装 NVIDIA device plugin,并且 GPU 节点带有 nvidia.com/gpu 资源。将 ${IMAGE} 替换为刚刚推送的镜像后再应用:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ray-vlm
spec:
replicas: 1
selector:
matchLabels:
app: ray-vlm
template:
metadata:
labels:
app: ray-vlm
spec:
nodeSelector:
eks.amazonaws.com/compute-type: ec2
containers:
- name: model
image: ${IMAGE}
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
env:
- name: MODEL_ID
value: "${MODEL_ID}"
resources:
requests:
cpu: "2"
memory: "8Gi"
nvidia.com/gpu: "1"
limits:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: "1"
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ray-vlm
spec:
selector:
app: ray-vlm
ports:
- name: http
port: 8000
targetPort: http
type: ClusterIP
实际部署:
export IMAGE="<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/ray-vlm:v1"
export MODEL_ID="Salesforce/blip-image-captioning-base"
envsubst < ray-vlm.yaml | kubectl apply -f -
kubectl rollout status deployment/ray-vlm
kubectl get pod -l app=ray-vlm -o wide
kubectl logs deployment/ray-vlm
这里的 readinessProbe 只是最小示例。模型加载期间容器可能需要较长时间,生产部署应增加合适的启动保护,或者提供一个明确的 /healthz 端点,避免把“进程已启动”误判为“模型已经可以接收请求”。
验证 GPU 和 HTTP 推理链路
先将 Service 转发到本地,再发送一张图片。请求体是图片二进制,提示词通过请求头传递:
kubectl port-forward service/ray-vlm 8000:8000
curl -X POST http://127.0.0.1:8000 \
-H "Content-Type: image/jpeg" \
-H "X-Prompt: describe this image" \
--data-binary @sample.jpg
出现模型结果后,再检查 Pod 是否真的获得 GPU,而不是退回 CPU:
kubectl describe pod -l app=ray-vlm | grep -A5 -i "nvidia.com/gpu"
kubectl exec deploy/ray-vlm -- nvidia-smi
如果 Pod 一直处于 Pending,优先检查节点是否有可分配 GPU、NVIDIA device plugin 是否正常,以及 Deployment 的资源请求是否与节点标签匹配。如果容器启动后在模型加载阶段崩溃,则应查看显存、模型权重下载权限和 CUDA/PyTorch 兼容性,而不是先调大 HTTP 超时时间。
从单 GPU 验证走向生产的检查清单
单 GPU 部署适合验证镜像、模型和请求协议,但不能直接代表生产吞吐。继续推进时可以按下面的顺序收敛风险:
- 固定版本:锁定 DLC 标签、模型版本和 Python 依赖,不使用不可追踪的
latest。 - 验证显存:记录模型加载峰值和推理峰值,给 Kubernetes 留出余量。
- 控制并发:先测单请求延迟,再逐步增加并发;不要因为 Ray Serve 能扩展副本,就忽略 GPU 显存限制。
- 完善探针:区分进程存活、模型加载完成和依赖服务可用这几种状态。
- 处理模型缓存:规划权重下载权限、缓存目录和重启后的恢复时间。
- 加入观测:至少记录请求延迟、错误率、模型加载时间、GPU 利用率和显存使用量。
- 再决定扩容策略:如果单模型无法在一张卡上稳定运行,应评估量化、模型并行或更大 GPU 节点,而不是盲目增加副本。
Ray Serve DLC 的核心价值不是替团队隐藏所有复杂性,而是把最容易发生兼容性问题的基础推理栈放进一个受支持的起点。对于 TorchServe 之后的迁移,先在单 GPU EKS 节点上跑通“模型加载—GPU 调度—HTTP 请求—滚动更新”这一条链路,再逐步加入批处理、监控和高可用,通常比一次性重写完整平台更容易控制风险。