Model Plane: vLLM + LiteLLM on EKS


title: “Model Plane: EKS 上的 vLLM + LiteLLM” weight: 100


在本模块中,我们将看到研讨会中的每个 agent 如何访问单一的模型端点,无论属于哪个方向。Qwen2.5-3B 在 AWS Inferentia 上通过 vLLM 运行,而 LiteLLM 位于其前面(也位于 Bedrock 前面),作为每个 agent 都会与之通信的唯一代理。这里无需我们安装任何东西。两者都已在运行。

为什么是两个组件,而不是一个?

vLLM 提供自托管模型(Inferentia 上的 Qwen2.5-3B)。LiteLLM 是一个轻量的 OpenAI 兼容代理,根据模型名称路由请求:

  • model: qwen2-5-3b-neuron → vLLM(自管理方向)
  • model: nova-lite → Amazon Bedrock(集成方向)

由于每个 agent 都调用 LiteLLM,而不是直接调用 vLLM 或 Bedrock,切换方向就变成了 agent 中的一行更改:将 model_id"qwen2-5-3b-neuron" 切换为 "nova-lite"。相同的 OpenAIModel 客户端,相同的 base URL,相同的代码路径。其余的都由代理处理,包括通过 LiteLLM pod 上的 Pod Identity 为 Bedrock 提供的 AWS IAM。

什么是 vLLM?

vLLM 是一个高吞吐量的 LLM 服务引擎,具备持续批处理、paged attention 以及标准的 chat-completions 风格 API。它运行在用于 Inferentia 和 Trainium 的 AWS Neuron SDK 上。

什么是 AWS Inferentia?

AWS Inferentia 是一款专为高性能、低成本推理而打造的 ML 加速器。Inferentia2(inf2)实例拥有多达 12 个 NeuronCores 和 384 GB 的 HBM。

什么是 LiteLLM?

LiteLLM 在前端使用 OpenAI chat-completions 格式进行通信,在后端将其转换为 100 多种后端中的任意一种(vLLM、Bedrock、OpenAI、Azure 等)。在本研讨会中,我们将它用作每个 agent 都会与之通信的模型平面(model plane)。它还会将自己的 span 转发到 Langfuse,因此稍后我们会在每个 agent 的 trace 旁边看到代理级别的 trace。

架构

Agent pod (any track)
    OpenAIModel(base_url=LITELLM_BASE_URL)
                │
                ▼
┌─── LiteLLM Proxy (litellm namespace) ────┐
│  qwen2-5-3b-neuron → vLLM                  │
│  nova-lite        → Bedrock (Pod Identity)│
└────┬────────────────────────────────┬────┘
     ▼                                ▼
  vLLM on Inferentia               Amazon Bedrock
  (Qwen2.5-3B)                       (us.amazon.nova-2-lite-v1:0)

预部署的基础设施

在研讨会设置期间部署:

  1. Inferentia NodePool:当 pod 请求 aws.amazon.com/neuroncore 时,Karpenter 会预置 inf2.xlarge
  2. vLLM Deployment:在 Neuron 上预编译的 Qwen2.5-3B 镜像,无运行时编译
  3. vLLM ClusterIP Serviceqwen2-5-3b-neuron.vllm.svc.cluster.local:8000
  4. LiteLLM Deployment:已配置两条模型路由的代理 pod
  5. LiteLLM ClusterIP Servicelitellm.litellm.svc.cluster.local:4000(每个 agent 都会与之通信的单一 URL)
  6. 用于 LiteLLM 的 Pod Identity:带有 bedrock:Converse* + bedrock:InvokeModel* 的 IAM 角色,附加到 litellm service account,因此只有代理(而非 agent)持有 Bedrock 权限

验证部署

两个 namespace 都应处于 Running 状态:

kubectl get pods -n vllm -l app=qwen2-5-3b-neuron
kubectl get pods -n litellm
如果 LiteLLM pod 不处于 `Running` 状态,请重启部署:
`kubectl rollout restart deploy/litellm -n litellm && kubectl rollout status deploy/litellm -n litellm --timeout=120s`

检查加载到代理中的 LiteLLM 模型路由表:

kubectl logs deploy/litellm -n litellm | grep -iE -B2 -A2 "qwen|nova-lite" 
Initialized Success Callbacks - ['langfuse']  
LiteLLM: Proxy initialized with Config, Set models: 
    qwen3-8b-neuron 
    nova-lite 
INFO:     10.0.6.254:51626 - "GET /health/readiness HTTP/1.1" 200 OK 
INFO:     10.0.6.254:51630 - "GET /health/readiness HTTP/1.1" 200 OK 

我们应该在启动日志中同时看到 qwen2-5-3b-neuronnova-lite

通过 LiteLLM 测试推理

获取 ingress URL 和生成的 master key。我们将同时用它们进行 curl 测试,稍后还会在下面的管理 UI 中再次使用:

LITELLM_URL="http://$(kubectl get ingress -n litellm litellm -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
LITELLM_KEY=$(kubectl get cm agent-config -n default -o jsonpath='{.data.LITELLM_API_KEY}')
echo "URL: $LITELLM_URL"
echo "Key: $LITELLM_KEY"

如果 $LITELLM_URL 打印为空字符串,则说明 ALB 仍在预置中。等待 60 秒后重新运行。master key 是 Terraform 为每次部署生成并写入 agent-config ConfigMap 的随机值。

使用自管理模型名称访问代理。该请求会发送到 Inferentia 上的 vLLM:

curl -s $LITELLM_URL/v1/chat/completions \
  -H "Authorization: Bearer $LITELLM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2-5-3b-neuron",
    "messages": [{"role": "user", "content": "What is 2+2?"}],
    "max_tokens": 100
  }'

现在切换模型名称。代理会将其改为路由到 Bedrock。agent 代码既不会知道也不会在意:

curl -s $LITELLM_URL/v1/chat/completions \
  -H "Authorization: Bearer $LITELLM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nova-lite",
    "messages": [{"role": "user", "content": "What is 2+2?"}],
    "max_tokens": 100
  }'

相同的端点,相同的 payload 结构,不同的后端。这正是关键所在。

探索 LiteLLM 管理 UI

LiteLLM 还在 /ui 提供了一个小型管理 UI。由同一个 ALB 提供服务。在浏览器中打开它并输入 LiteLLM key:

echo "$LITELLM_URL/ui"

echo "$LITELLM_KEY"

使用用户名 admin 和我们上面打印出来的 $LITELLM_KEY 值作为密码登录。可点击的内容有:

  • Models + Endpoints:配置文件中的两条路由,与我们用 curl 访问的列表相同
  • Playground:一个通过代理发送请求的聊天框。切换模型下拉菜单,无需触碰任何 Python 即可并排查看 Qwen 与 Nova
  • Logs:代理处理过的每个请求,附带延迟以及缓存与上游的状态。有助于发现那些反复询问同一件事的 agent
  • Virtual Keys:带有预算的作用域 API key。

关键配置(vLLM)

参数 描述
Model Qwen/Qwen2.5-3B-Instruct 30 亿参数模型,使用 optimum-neuron 为 Neuron 预编译
Instance inf2.xlarge 1 个 Neuron 设备,2 个 NeuronCores
Image vllm-neuron: qwen2.5-3b-optimum-neuron 预编译模型已烘焙进 Docker 镜像
tensor-parallel-size 2 将模型分布在 2 个 NeuronCores 上
max-model-len 8192 最大上下文长度
max-num-seqs 2 最大并发序列数

关键配置(LiteLLM)

设置 描述
Aliases qwen2-5-3b-neuronnova-lite agent 在 model_id 中使用的名称
vLLM upstream http://qwen2-5-3b-neuron.vllm.svc.cluster.local:8000/v1 集群内 Service
Bedrock upstream bedrock/us.amazon.nova-2-lite-v1:0 区域默认取自 pod 的 AWS 环境
Callbacks langfuse 将 LiteLLM 自己的 span 转发到研讨会的 Langfuse 项目
Master key $LITELLM_KEY 由 Terraform 生成,通过 agent-config ConfigMap 以 LITELLM_API_KEY 的形式暴露

端到端工作原理

  1. AgentOpenAIModel(base_url=LITELLM_BASE_URL, model_id="qwen2-5-3b-neuron")。本方向中每个实验的 Python 完全相同。
  2. LiteLLM:读取 model 字段,在其配置中查找路由,调用上游
  3. vLLM:将预编译的 Qwen2.5-3B 加载到 NeuronCores 上(冷启动约 2–3 分钟)并提供 OpenAI 格式的响应
  4. Bedrock(另一方向):LiteLLM 使用来自 Pod Identity 的 AWS 凭证,调用 Converse,并将响应转换回 OpenAI 格式
  5. Langfuse:来自 agent 和代理的 trace 都落在同一个项目中,因此我们可以看到时间花在了哪里