跳转至正文

针对 llm-d 进行推理

本文档将向您展示如何与模型服务器和推理调度器进行交互。

提示

若要对 llm-d 栈进行性能测试,请查看我们的基准测试文档

先决条件

假设您已根据指南部署了 llm-d 推理栈(使用模型服务 Helm chart),或遵循了 llm-d 关于部署推理调度器和模型服务器的规范。

暴露您的网关

首先,我们需要选择暴露网关或与网关交互的策略。需要注意的是,这会受到您在按照特定指南安装 llm-d-infra chart 时所使用的值的影响。请选择与您的环境匹配的选项卡。

注意: 如果您不确定使用哪种方式,请从端口转发 (port-forward) 开始,因为它是最可靠且最简单的方法。对于任何需要共享的场景,请使用 Ingress/Route。如果您的云服务商支持且您只需要原始 L4 访问,请使用 LoadBalancer。

端口转发 (集群内部)

对于安装在集群内的网关提供商,您可以直接对网关部署进行端口转发。

GATEWAY_SVC=$(kubectl get svc -n "${NAMESPACE}" -o yaml | yq '.items[] | select(.metadata.name | test(".*-inference-gateway(-.*)?$")).metadata.name' | head -n1)

注意: 此命令假设您在指定的 ${NAMESPACE} 中只有一个网关,即使有多个,它也只会获取按字母顺序排列的第一个网关服务的名称。如果您在单个命名空间中运行多个网关部署,则必须显式将 $GATEWAY_SVC 设置为相应的网关端点。

k get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gaie-inference-scheduling-epp ClusterIP 10.16.3.250 <none> 9002/TCP,9090/TCP 18s
gaie-inference-scheduling-ip-18c12339 ClusterIP None <none> 54321/TCP 12s
gaie-sim-epp ClusterIP 10.16.1.220 <none> 9002/TCP,9090/TCP 80m
infra-inference-scheduling-inference-gateway-istio LoadBalancer 10.16.3.226 10.16.4.3 15021:34529/TCP,80:35734/TCP 22s
infra-sim-inference-gateway LoadBalancer 10.16.1.62 10.16.4.2 80:38348/TCP 81
export GATEWAY_SVC="infra-inference-scheduling-inference-gateway-istio"

获取网关服务名称后,我们就可以对其进行端口转发

export ENDPOINT="https://:8000"
kubectl port-forward -n ${NAMESPACE} service/${GATEWAY_SVC} 8000:80

注意: 端口 8000 是我们指南中的默认网关服务端口。您可以通过更改 llm-d-infra helm chart 的值并相应地更新端口转发命令来修改它。

注意: 您还可以使用其他特定于平台的网络选项,例如 Openshift Routes。在通过 OCP routes 对 pd-disaggregation 示例进行基准测试时,我们注意到 Openshift Networking 会对网关请求强制执行超时,在重负载下这会影响我们的测试结果。如果您希望在基准测试设置中使用平台相关的选项,请务必检查您的平台文档。

上述每条路径都应导出 ${ENDPOINT} 环境变量,我们可以向该变量发送请求。

发送请求

/v1/models 端点

我们可以访问的第一条路径是 /v1/models。该端点仅依赖于您的 InferencePool 中运行的 vllm 服务器,因此即使您遵循宽泛的专家并行 (expert-parallelism) 指南,并且只有一个解码 (decode) 或预填充 (prefill) 实例启动,您也应该能获得响应(这也取决于 EPP 使用的配置)。这通常是最安全的请求方式,因为尽管不同指南中的模型各异,但 /v1/models 始终可用。

  1. 尝试使用 curl 访问 /v1/models 端点
curl -s ${ENDPOINT}/v1/models \
-H "Content-Type: application/json" | jq

预期输出

`{
"data": [
{
"created": 1752727169,
"id": "random",
"object": "model",
"owned_by": "vllm",
"parent": null,
"root": "random"
}`,
`{
"created": 1752727169,
"id": "",
"object": "model",
"owned_by": "vllm",
"parent": "random",
"root": ""
}`
],
"object": "list"
}

/v1/completions

现在让我们尝试访问 /v1/completions 端点(这取决于模型,请确保您的模型与服务器针对 v1/models curl 返回的结果匹配)。

curl -X POST ${ENDPOINT}/v1/completions \
-H 'Content-Type: application/json' \
-d ``{
"model": "random",
"prompt": "How are you today?"
}`` | jq

预期输出

`{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "Today is a nice sunny day.",
"role": "assistant"
}`
}
],
"created": 1752727735,
"id": "chatcmpl-af42e9e3-dab0-420f-872b-d23353d982da",
"model": "random"
}

您可以在 /v1/completions 中使用的一些其他有用技巧包括控制 max_tokens 和传递 seed

max_tokens 非常有用,因为它可以限制服务器返回的 token 数量。通过将其设置为 1,您可以模拟 TTFT(首个 token 响应时间)测试。您可以按照以下方式操作:


curl -s http://${ENDPOINT}/v1/completions \
-H "Content-Type: application/json" \
-d ``{
"model": "deepseek-ai/DeepSeek-R1-0528",
"prompt": "Hi, how are you?",
"max_tokens": "1"
}`` | jq

seed 非常有用,因为它允许推理服务器将每个请求视为唯一的。需要注意的是,这不会影响任何关于前缀缓存或 inference-scheduler 层面的内容,因此请求仍会被路由到相同位置,但它不会在 vllm 服务器层面进行任何缓存。

curl -s http://${ENDPOINT}/v1/completions \
-H "Content-Type: application/json" \
-d ``{
"model": "deepseek-ai/DeepSeek-R1-0528",
"prompt": "Hi, how are you?",
"seed": "'$(date +%M%H%M%S)'"
}`` | jq

有关 /v1/completions 端点可设置选项的更多信息,请参阅 openAI API server /v1/completions 文档

/v1/chat/completions (即将推出)

虽然 /v1/completions 是旧版 API,但目前仍受支持。我们正在将其转换为 /v1/chat/completions,这项工作正在多个地方进行跟踪。

kv-cache-manager 包的初始实现已作为 v0.2 冲刺的一部分落地。

llm-d-inference-scheduler 镜像中的支持正在进行中,属于 v0.3 路线图的一部分。此外,对上游 epp 镜像的进一步支持也在进行中,并在此处进行跟踪

跟随请求日志

我们推荐使用 stern 工具来跟随请求,因为它允许您同时跟随多个 pod 日志。这在 llm-d 的上下文中特别有用,因为大多数部署都有多个 decode pod。有关更多信息,请参阅我们的可选工具文档

要获取命名空间中的所有 decode pod,我们可以执行以下操作

DECODE_PODS=$(kubectl get pods --no-headers -n ${NAMESPACE} -l "llm-d.ai/role=decode" -o custom-columns=":metadata.name")

然后您可以使用 stern 同时查看这些日志

stern -n ${NAMESPACE} "$(echo "$DECODE_PODS" | paste -sd'|' -)"

就像使用 kubectl logs 一样,您可以在 pod 名称后指定容器名称,以仅获取特定容器的日志(stern 默认会打印 pod 中所有容器的日志)。操作如下:

stern -n ${NAMESPACE} "$(echo "$DECODE_PODS" | paste -sd'|' -)" -c routing-proxy # for routing sidecar logs
stern -n ${NAMESPACE} "$(echo "$DECODE_PODS" | paste -sd'|' -)" -c vllm # for vllm logs

要获取 prefill pod,您可以重复使用之前的命令,只需更换角色标签即可

PREFILL_PODS=$(kubectl get pods --no-headers -n ${NAMESPACE} -l "llm-d.ai/role=prefill" -o custom-columns=":metadata.name")

通常这没有那么必要,因为 prefill 在较低并行度下表现更好,目前我们所有的指南都使用 1 个 prefill 实例。

从日志中 grep 掉干扰信息

我们提供了一些有用的 grep -v 命令,可以帮助您清除 vllmrouting-proxy 容器中的干扰信息。

routing-proxy 容器干扰信息

从 K8s 的角度来看,一旦 vllm 容器上线(当其 status 变为 ready 时),sidecar 就会开始尝试连接并与其通信。然而,在 vllm pod 启动后,vllm API 服务器仍需要启动才能响应请求。在此时间差内,sidecar 中会产生一些不美观的日志,如下所示:

ms-inference-scheduling-llm-d-modelservice-decode-8ff7fd5bjvpbm routing-proxy E0824 16:49:51.115884       1 proxy.go:268] "waiting for vLLM to be ready" err="dial tcp [::1]:8200: connect: connection refused" logger="proxy server"
...

为了避免这种情况,请考虑在日志命令中添加以下内容

stern ... | grep -v "waiting for vLLM to be ready"

vllm 容器干扰信息

vllm 容器会记录对其指标 (metrics) 端点的任何访问,该端点应该会被持续轮询。您可以使用以下命令将它们过滤掉:

stern ... | grep -v "GET /metrics HTTP/1.1"

在某些情况下,您可能还想忽略 vllm 经常记录的使用情况信息,以便隔离每个请求的日志。vllm 的示例使用情况日志可能如下所示:

ms-inference-scheduling-llm-d-modelservice-decode-8ff7fd5bxf7lt vllm DEBUG 08-24 18:09:51 [loggers.py:122] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 0.0 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 0.0%

要针对所有这些使用情况日志,您可以直接过滤掉它们的共同前缀

stern ... | grep -v "Avg prompt throughput"

不过,您也可以自定义此 grep -v 命令以包含 0 值,从而仅在服务器开始接收流量后查看使用指标。您还可以使用 \| 分隔符将多个术语链接在一起进行 grep 过滤,例如:grep -v "日志中不显示 a\|日志中也不显示 b"

内容来源

此内容由 llm-d/llm-d 仓库 main 分支下的 docs/getting-started-inferencing.md 自动同步。

📝 若要建议更改,请编辑源文件创建 issue