在第 1 章中,我们学习了如何从零开始,通过一个简单的编码示例,把 LLM 部署到 Kubernetes 上。整套技术栈包括:一个模型服务端 vLLM,用于优化模型执行;以及一个 Model Server Controller——KServe,用于管理它与 Kubernetes 的集成,以及部署生命周期。
接着在第 2 章中,我们聚焦于 LLM 模型数据本身,以及当前管理这类大模型体量时所面临的复杂性与可选方案。我们正一步步逼近一个完整的生产环境:在这个环境中,LLM 工作负载能够被完全托管并自动化,从而可以与其他工作负载(例如传统应用)并行运行,并统一交由 Kubernetes 管理。
Kubernetes 通过声明式 API 来编排容器执行,借助控制器(controller)和协调循环(reconciliation loop),以最终一致(eventually consistent)的方式实现工作负载自愈。任何有 Kubernetes 经验的人都知道,这种机制并不能替代完善的可观测性与监控体系。这些能力的价值在于:当某些问题无法自动修复时,你能够快速做出反应。这个原则同样适用于 LLM。监控模型服务端至关重要;但由于 LLM 的特殊性,监控 LLM 并不等同于监控传统应用。
LLM 在产生工作负载的方式上,与传统微服务有显著不同。对于传统微服务而言,通常只有少量 endpoint,工作负载主要由请求数量以及对数据查询的速度驱动。LLM 与这种模式不同,甚至与传统机器学习也不同。
在本章中,我们将看到它们究竟为何不同、执行过程中哪些方面值得重点监控,以及对应有哪些可用指标。
在本书的大部分内容里,我们通常把 LLM 当作一个运维层面的“黑盒”来看待:重点关注部署、扩缩容和资源管理,而不需要深入理解它的内部机制。
但一旦进入可观测性与生产监控领域,理解 LLM 是如何处理请求的就变得必不可少。我们所监控的指标——例如 Time To First Token、token 吞吐量、KV cache 利用率——都直接对应于 LLM 的推理流水线。
如果你跳过了“理解 LLM 基础”中的 LLM fundamentals 部分,那么在深入本章的可观测性细节之前,建议先回去复习。那一节讲解了 tokenization、embedding、prefill 与 decode 阶段,以及 compute-bound 与 memory-bound 工作负载等概念。这些内容正是理解“为什么要监控这些指标”以及“这些指标揭示了什么生产性能信息”的基础。
在本章剩余部分中,我们默认你已经对这些基础概念有基本了解;接下来我们会把重点放在 Kubernetes 上生产级 LLM 部署的可观测性工具、技术以及最佳实践上。
可观测性栈与配置
本节将探讨:在 Kubernetes 上监控 LLM 工作负载时,可以使用哪些可观测性工具与实践。
在监控 LLM 工作负载时,我们完全可以复用或改造 Kubernetes 生态中已有的工具和已经成熟的工作负载可观测性实践。
一个工作负载的可观测性通常涉及多个方面:查看日志以定位错误、采集指标做时序分析、通过 tracing 关联执行步骤、以 sidecar 方式代理流量,甚至直接向容器中注入模块。对应用工作负载来说是这样;对于使用 KServe 与 vLLM 部署的 LLM,也基本同样适用。
日志(Logs)
Kubernetes 有一套明确的日志架构:容器的 stdout 和 stderr 都会被重定向到容器所在 worker node 上的一个 log-file.log 文件中。
这让我们可以通过 kubectl logs 命令非常方便地访问日志;但它并不提供日志的长期存储或索引能力。要实现这些能力,你需要在集群中额外引入相关项目,例如 Grafana Loki。
当你以 InferenceService 的方式部署模型时,KServe controller 会创建一个包含多个容器的 Deployment。
这些容器包括:一个名为 storage-initializer 的 initContainer,用于加载模型;一个运行模型服务端的 kserve-controller;以及若干额外的 sidecar 容器,具体取决于部署模式(Knative 或 ModelMesh;更多细节见“KServe”一节)。
对于 LLM 而言,日志的查看与管理方式,与应用工作负载是类似的。为了了解这些日志中究竟包含哪些信息,下面我们来看一个典型的 vLLM 启动过程。
示例 5-1 展示了 vLLM 服务端典型生命周期中的关键日志条目:从初始化开始,一直到接收到第一个推理请求为止。
示例 5-1. vLLM 启动日志
INFO [api_server.py:651] vLLM API server version ...
INFO [api_server.py:652] args: ...
INFO [api_server.py:199] Started engine process with PID ...
INFO [config.py:478] This model supports multiple tasks: ...
WARNING [arg_utils.py:1089] Chunked prefill is enabled ...
INFO [llm_engine.py:249] Initializing an LLM engine (...) with config: model=...
INFO [model_runner.py:1092] Starting to load model ...
INFO [weight_utils.py:243] Using model weights format ['*.safetensors']
...
Loading safetensors checkpoint shards: 100% Completed | 4/4 [00:04<00:00, 1.12s/it]
...
INFO [worker.py:241] the current vLLM instance can use total_gpu_memory ...
INFO [worker.py:241] model weights take 14.99GiB; ...
...
INFO [launcher.py:19] Available routes are:
INFO [launcher.py:27] Route: /openapi.json, Methods: HEAD, GET
...
INFO [launcher.py:27] Route: /v1/chat/completions, Methods: POST
...
INFO: Started server process [39626]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO [logger.py:37] Received request cmpl-...: prompt: ...
INFO [engine.py:267] Added request cmpl-....
其中:
- vLLM 会记录它的版本,以及启动时传入的参数。
- 一个模型可能支持多种任务类型;最常见的是 generation,但也可能包括 classify、reward 等其他任务。
- 用于加载模型的配置,也会被 vLLM 记录下来。这些配置定义在模型的
config.json文件中。 - 模型加载完成后,vLLM 会记录模型当前消耗的 VRAM 信息,以及一些附加信息,例如分配给 KV cache 的空间(为了简洁,这部分日志在示例中做了截断)。
- 日志中会列出所有可用的 endpoint。
- vLLM 每次收到一个新请求时,都会生成一条日志,记录请求详情(prompt 和相关参数)。如果你不希望记录这些内容,可以通过
--disable-log-requests参数关闭这一行为。
指标(Metrics)
Kubernetes 核心本身并不内置指标能力,但这是一个非常常见的场景,已经有相当成熟的实践与技术方案。
大多数 Kubernetes 发行版(例如 Red Hat OpenShift)都会开箱即用地提供一套监控解决方案;尽管不同发行版实现有所区别,但事实上的标准仍然是 Prometheus,以及采用 OpenMetrics 暴露格式的指标端点。
OpenMetrics 是一个由 CNCF 孵化中的项目,它在保留向后兼容性的同时,对原始 Prometheus 文本格式进行了标准化与扩展。
容器通常会通过一个 endpoint(通常是 /metrics)以这种格式暴露指标。
负责抓取这些指标的采集组件会周期性地去拉取这个 endpoint。见示例 5-2。
示例 5-2. 配置一个可监控的 Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service-deployment
spec:
...
---
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "80"
labels:
app.kubernetes.io/part-of: my-application
spec:
type: ClusterIP
selector:
app: my-service
ports:
...
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-service-servicemonitor
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: my-application
endpoints:
- interval: 15s
这里:
- Service 中这些 annotation 用来声明 metrics endpoint 的位置。
ServiceMonitorAPI 用于启用监控。- 必须配置 selector,以匹配需要监控的 Service。
- 还可以配置抓取频率。
监控模型的配置方式非常相似:KServe 定义了一组 annotation,可直接在 ServingRuntime 和 InferenceService 对象上配置监控。
基于这些 annotation,KServe controller 会自动为 Deployment 生成正确配置(见示例 5-3)。
示例 5-3. 为模型启用监控配置
apiVersion: serving.kserve.io/v1alpha1
kind: ServingRuntime
metadata:
name: kserve-vllm
spec:
annotations:
prometheus.kserve.io/port: '8080'
prometheus.kserve.io/path: "/metrics"
...
---
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: my-model
annotations:
serving.kserve.io/enable-prometheus-scraping: "true"
spec:
...
其中:
- 这些 annotation 是 KServe 专用的,但语义上与
prometheus.io/*等价。 - 这个 annotation 会让 KServe 把
prometheus.io/*自动注入到 Pod 中,从而启用抓取。
正如示例所示,无论是传统 Deployment 还是模型服务,声明 metrics endpoint 的方式都非常相似。
一旦这些指标被导出并被采集器(例如 Prometheus)收集,你就可以对它们进行查询,或者通过 Grafana dashboard 展示出来。这一点与我们监控传统 Kubernetes 工作负载的方式完全一致。
提示(TIP)
正如前面在“KServe”一节中介绍过的,KServe 有不同的部署模式。
当使用 Knative 模式时,监控行为会有所不同,因为同一个 Pod 中运行模型时,除了主容器外,还有 Knative 和 Istio 的 sidecar 在协同工作。
而 Prometheus 的抓取配置默认假设只有一个单一 endpoint 可抓取,这意味着我们可能会遗漏其他容器中的重要指标。
为了解决这个问题,KServe 项目开发了一个 metric aggregator 组件(名为 qpext),它会从所有容器抓取指标,并对外暴露一个聚合后的统一 metrics endpoint。
如需启用这一行为,可以使用 annotation:
serving.kserve.io/enable-metric-aggregation
如果采用的是 Standard 模式,则通常不需要做这种聚合,因为 Deployment 中只有一个容器。
在完成模型服务端指标导出配置之后,关于最重要的指标,请参见后文“模型服务端指标”一节。
不过在那之前,我们先来看 tracing 栈。
链路追踪(Tracing)
Kubernetes 中的可观测性包含多个方面:我们可以访问容器日志,从而完整了解组件(在这里即模型服务端)正在做什么;也可以通过聚合指标来观察趋势和时序特征。
但我们仍然缺少一种能力:追踪单个请求的完整执行路径。
Kubernetes 中 tracing 最佳实践的发展路径,与 metrics 很相似:它并不是原生内置的,而是由 OpenTelemetry 项目定义出了一套概念和格式,并逐步成为事实标准。
OpenTelemetry 在 tracing 规范中规定:每个请求都应该有一个标识符,用于关联执行过程中跨越多个步骤的执行流。因此,tracing 与 metrics 有着本质差异。
在真实世界场景中,处理一个请求时,除了模型服务端本身,通常还会涉及多个组件,例如充当前处理器或后处理器的防火墙、网关等。这些组件都必须实现该协议,能够传播这个标识符,并产出 tracing 信息。
与由 collector 主动拉取的 metrics 不同,trace 信息是由组件主动推送到 exporter 的。
目前最常用的 tracing 服务端实现之一是 Jaeger。它实现并暴露了收集 tracing 数据所需的 endpoint,同时还提供图形化工具来展示这些数据。
vLLM 使用 OpenTelemetry SDK 集成 tracing 支持,因此它的配置方式被简化了,且与其他采用相同方案的项目非常类似(见示例 5-4)。
示例 5-4. 为 vLLM 配置 tracing
apiVersion: serving.kserve.io/v1alpha1
kind: ServingRuntime
metadata:
name: kserve-vllm
spec:
containers:
- name: kserve-container
image: vllm/vllm-openai:latest
args:
- --model
- /mnt/models/
- --port
- "8080"
- --otlp-traces-endpoint
- "$JAEGER_TRACE_ENDPOINT"
env:
- name: "OTEL_SERVICE_NAME"
value: "vllm-server"
...
其中:
--otlp-traces-endpoint参数用于在 vLLM 中启用 OpenTelemetry tracing,并配置 exporter endpoint。它支持 gRPC、HTTP,以及其他多种配置方式。- OpenTelemetry SDK 通过环境变量进行配置。更多细节可以参考 OpenTelemetry SDK 官网以及 Python SDK 文档。
你可能已经注意到,在可观测性配置中,我们会反复看到 Prometheus 与 OpenTelemetry 这两个名字;这反映了当前监控标准生态正在演进中的现实(见下一个侧栏)。
PROMETHEUS、OPENMETRICS 与 OPENTELEMETRY
Prometheus 项目是目前采用最广泛的 metrics 方案,但在早期,它的指标格式并没有被正式规范化。
随着时间推移,社区做出了多次标准化尝试,而现在 OpenMetrics 已成为正式规范:它在保留几乎完全向后兼容的前提下,对原始 Prometheus 格式进行了扩展。
OpenTelemetry 项目则是一整套 API 定义、SDK 与工具集合,目标是覆盖可观测性的所有方面。
它不仅定义接口,还提出了 semantic conventions(语义约定) ,用于统一 metrics 和 trace 条目的命名方式。这些约定构成了一套核心标准,鼓励所有实现共同采用。
此外,OpenTelemetry 社区还在为不同上下文定义 metrics、spans 和 events 的语义约定。
LLM 可观测性(更广义地说,是 OpenTelemetry 下 generative AI 子项目的一部分)正是其中一个上下文。目前,已经有一份实验性规范定义了这方面的一组核心语义约定。
和往常一样,这类规范与约定最终会被多广泛采用,很难提前判断;但社区对此的兴趣确实很高。许多活跃贡献者已经在推动不同 runtime 对这些约定的采用。
vLLM 当前的 tracing 实现,已经是基于这套语义约定工作来构建的。
这种朝着统一可观测性语义约定收敛的努力,与 KServe 中 Open Inference Protocol(OIP) 的工作很相似:它们的共同目标,都是统一模型接口或观测接口的“形状”。
模型服务端指标
当 metrics 栈已经安装完成、LLM 已通过 KServe 部署,并且 vLLM 也正确配置为输出指标之后,我们就可以开始分析模型服务端性能了。
本节将重点介绍那些专属于 LLM 工作负载、并且与传统应用监控有显著差异的关键指标。
在 Kubernetes 上监控工作负载这件事,我们已经很熟悉了,因此我们很自然会去看 CPU 使用率、内存使用率、吞吐量(每秒请求数)和延迟(处理一个请求所需时间)等指标。
对于 LLM,这种思路同样适用,但差异非常重要。
一旦真正理解 LLM 的工作方式,就会发现:监控这类工作负载并没有那么简单。
首先,LLM 的主要计算发生在 GPU 上,因此单看 CPU 使用率,并不能很好反映系统当前的实际负载。
更进一步说,LLM 推理的两个主要阶段——prefill 和 decode——本身就有本质不同:前者是 compute-bound,后者是 memory-bound。这些阶段我们已经在“理解 LLM 基础”中讲过。
问题并不只是资源使用层面。
就连“吞吐量”或“延迟”这些概念本身,在 LLM 场景里也发生了变化。因为面对一条请求,你无法事先预测答案会有多长,因此任何只按“请求数”来统计的指标,都无法真实反映模型服务端的实际工作负载。
由于 token 才是 LLM 生成过程中的核心计算单位,因此模型服务端的指标也应当以 token 为中心,而不是以请求为中心。
接下来我们就来看模型服务端产出的关键 LLM 指标;而第 4 章会进一步介绍如何在更高级场景中使用这些指标,例如自动伸缩。
Time To First Token
Time To First Token(TTFT) 表示:从用户发出请求,到用户开始收到响应之前,真正等待了多久。
对于聊天机器人这类实时用例来说,这是最重要的指标;
而对于批处理这类离线场景,它的重要性就相对较低,因为用户并不会直接感知这段等待时间。
TTFT 以秒为单位,通常以 histogram 类型指标暴露(即用一组可配置 bucket 来记录数值分布)。
例如,vLLM 会以 vllm:time_to_first_token_seconds 这个名称暴露该指标;而 OpenTelemetry 语义约定建议使用 gen_ai.server.time_to_first_token。
结合 LLM 的工作机理来看,产生第一个 token 所需的时间,本质上就是完成 prefill 阶段 所需的时间。
Time Per Output Token / Inter-Token Latency
Token 是一个接一个生成的,而且通常会以流式方式返回给用户,因此第二个关键指标就是:在第一个 token 之后,每生成一个后续 token 需要多长时间。
如果说 TTFT 对应的是用户主观感受到的“开始等待时间”,那么 Time Per Output Token(TPOT) 表示的就是终端用户感知到的“输出速度”。
这个指标对于实时场景更重要,而在离线场景中则相对没那么关键。它也常被称为 Inter-Token Latency(ITL) 。
平均而言,人类每分钟大约阅读 180 个单词,也就是大约每秒 3 个单词。虽然 token 与单词并非完全一一对应,但只要系统每秒能生成 4 到 5 个 token,通常就足以让人类在阅读时感觉不到额外延迟。
与 TTFT 一样,这个指标也是以秒为单位,并使用 histogram 作为类型。
在 vLLM 中,它叫做 vllm:time_per_output_token_seconds;而 OpenTelemetry 语义约定建议使用 gen_ai.server.time_per_output_token。
如果说 TTFT 对应 prefill 阶段,那么这个指标衡量的就是每一次 decode iteration 的持续时间。
吞吐量(Throughput)
既然 token 是 LLM 的基本计算单位,那么吞吐量自然就定义为:每秒生成多少 token。
不过,请求本身可能非常长(超过 100k token 也并不罕见),因此如果只看生成出的 token 数量,就会忽略掉处理初始输入请求(prefill)本身所花费的时间与成本。
在这个问题上,vLLM 采取了同时提供拆分指标与合并指标的方式:
vllm:prompt_tokens_total表示每秒处理的输入 token 数vllm:generation_tokens_total表示每秒产出的输出 token 数vllm:tokens_total则把两者合并起来,表示每秒总共处理的 token 数
对于这类指标,OpenTelemetry 语义约定目前还没有给出推荐命名。
尽管输入与输出吞吐量都能监控,但一般来说,仅看生成 token 的吞吐量,通常就足以作为系统负载的有效指示器。
因为现代 GPU 的计算速度非常快,输入处理(compute-bound)通常很快完成,而大多数时间都花在 decode 阶段。
与此同时,这个指标与“处理了多少请求”并不直接对应。
系统可能被一个超长响应完全占满;反过来,也可能处理了很多请求,但整体负载并没有满。
延迟(Latency)
Latency 表示:模型生成完整响应所需的总时间,单位是秒。
这个指标与前面提到的指标密切相关,尤其与 TTFT 和 TPOT 有直接关联;但它仍然是一个非常重要的独立指标,因为它反映了处理一个请求的总耗时,也可用于观察趋势或识别模式。
在 vLLM 中,这个指标的名称是 vllm:e2e_request_latency_seconds,类型为 histogram,单位为秒。
OpenTelemetry 语义约定则建议命名为 gen_ai.server.request.duration。
请求队列指标(Request Queue Metrics)
前面这些指标对于衡量系统整体速度都很关键。
但如果请求太多,会发生什么?
每当 vLLM 收到一个请求时,它都会通过 batching 技术来最大化吞吐量;这也意味着,如果 batch 已满,那么某个请求就不一定会被立刻处理。
因此,还有一组指标用于跟踪请求队列:
vllm:num_requests_waiting:当前仍在等待处理的请求数vllm:num_requests_running:当前正在执行的请求数
vLLM 指标还可以用于观察执行过程中的许多其他方面。
例如,我们前面多次强调 KV cache 对高效 token 生成的重要性;对应地,也有多项指标可用于监控其使用情况。完整的 vLLM 指标列表,请参考 Production Metrics 页面。
如果你想用 Prometheus 来实现告警,可以参考示例 5-5。
示例 5-5. 使用 vLLM 指标创建 Prometheus 规则
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: my-llm-rule
spec:
groups:
- name: "vllm.latency.rule"
rules:
- alert: vLLMLatency
expr: max_over_time(time_per_output_token_seconds}[5m]) >= 0.3
labels:
severity: critical
app: my-model
annotations:
message: Latency of vLLM is too high.
summary: Model "my-model" needs to keep latency < 0.3 second
runbook_url: https://my.company/runbooks/vllm/modelslow
description: The runtime is slowing down, check request queue
这里:
expr用于定义触发告警的条件。- 还可以关联一个 runbook(即针对该告警的标准处置文档),方便值班工程师快速排查问题。
当你在定义告警与监控策略时,理解服务级指标之间的关系,有助于建立更有意义的阈值(见下一个侧栏)。
SLI、SLO 与 SLA
Service-Level Indicator(SLI) 是一个用于监控某项服务的指标。
它应当基于那些对用户有直接影响的方面。比如在 LLM 场景中,TPOT 就是一个典型 SLI,因为它反映了用户在收到首 token 之后,还要等待多久才能看到每一个后续 token。
Service-Level Objective(SLO) 则是我们围绕某个 SLI 向用户做出的目标承诺。
例如,对 TPOT,我们可以定义一个 SLO:承诺在某个给定时间窗口(例如按月)内,99.999% 的请求都能把该值维持在某个阈值以下。
最后,Service-Level Agreement(SLA) 则是我们与用户之间的契约性协议。
它通常与 SLO 有关,但层级更高,往往以“服务月可用性”这类更高层的方式来表达。
如果一个或多个 SLO 被违反,就可能影响 SLA,严重时甚至意味着我们不再符合合同约定。
GPU 使用监控
前一节中,我们介绍了多种系统指标,可用于衡量系统整体吞吐量以及集群正在处理的请求数量。
借助这些指标,你可以监控系统是否满足预期 SLA,并在不满足时配置相应告警。
除此之外,你还可以像监控传统 Kubernetes 工作负载一样,去监控 CPU、内存和网络资源的使用情况。
不过,当使用像 RDMA 或 InfiniBand 这样的高性能互联作为辅助网络接口时,网络监控可能会更复杂。
而 GPU 使用情况则需要额外考虑。
第 3 章已经更详细地介绍了 Kubernetes 集群中的 GPU 配置;本节只聚焦 GPU 设备的指标层面。
不同硬件厂商都提供了各自实现,但总体思路类似:
- 有一个管理组件负责从 GPU 收集使用数据
- 再由一个 exporter 组件把这些数据通过
/metricsendpoint 暴露出来,从而兼容 Prometheus
NVIDIA 提供了一整套名为 Data Center GPU Manager(DCGM) 的工具,用于管理集群中的 GPU;同时还有一个 DCGM-exporter 项目,提供 Helm Chart 来把 exporter 部署到 Kubernetes 中。
之后你就可以像示例 5-2 那样配置 metrics 抓取。
另外,NVIDIA 还提供了一个 GPU Operator,用于与 Kubernetes 做更优的集成。你可以在集群中安装它,以自动完成 metrics exporter 的部署与配置。
AMD 的方案与 NVIDIA 类似,它提供了 AMD Device Metrics Exporter 和 AMD GPU Operator。
Intel 也有自己的 Prometheus Metric Exporter。几乎所有其他厂商也都有类似方案。通常只要按各自文档完成部署,就可以开始采集 GPU 指标。
不同厂商目前还没有统一的指标命名约定,但它们一般都会覆盖一些底层使用指标,例如 PCIe 带宽或图形引擎活跃度。
关于 Kubernetes 中管理与观测 GPU 的更多工具,我们已经在第 4 章中介绍过。
质量指标(Quality Metrics)
到目前为止,本章讲解的内容,基本都属于 LLM 的基础设施监控:我们观测吞吐量与延迟,以确保终端用户体验符合 SLA。
这些内容对于管理集群至关重要;但 LLM 不仅要快,还必须要对。
自从机器学习模型开始被广泛用于生产系统以来,监控模型质量就一直是一件关键的事情:
一个接收到未知输入的普通应用,往往会直接崩溃,或者显式返回错误信息;
而一个机器学习模型在相同情况下,通常不会崩,而是会继续输出很差甚至错误的预测。
机器学习模型总是基于某一批训练数据进行训练,而这批数据被假定能代表真实分布。
但人类行为会随着时间变化(即 drift),因此,即便模型当初训练得再完美,也需要定期调优或重训练,才能维持质量。
这是一个早已被充分认知的问题,因此业界已经发展出很多监控手段,例如性能指标、数据漂移检测和偏差检测。
这类技术,与其他许多关注点一起,被纳入一个更大的倡议之下——即 Responsible AI(负责任 AI) 。
这个研究领域早在生成式 AI 之前就已经被建立并不断发展,而现在它也在持续演进,以应对 LLM 带来的新挑战。
尤其是在 LLM 场景中,由于它是生成式模型,因此产生错误结果的方式非常多。而最糟糕的情况,是模型输出了一段听起来完全合理、但实际上指向了一个根本不存在事实的内容。
这类问题被称为 hallucination(幻觉) ,它是当前最难处理的问题之一,也是 LLM 在真实世界场景中落地的最大障碍之一。
虽然示例 5-6 中的幻觉看起来似乎无伤大雅,但想象一下:如果一个企业客服机器人因为幻觉而依据一条根本不存在的政策批准退款,那后果就完全不同了。
示例 5-6. LLM 幻觉示例(OpenAI ChatGPT)
“What is the world record for crossing the English channel entirely on foot?”
“This world record was made on August 14, 2020, by Christof Wandratsch of Germany,
who completed it in 14 hours and 51 minutes.”
遗憾的是,并不存在一个通用评估指标,可以直接判断一个 LLM 是否正在 hallucinate。
不过,仍然有很多 benchmark 可用于基于预定义能力来评估模型整体质量,例如其推理能力。
在采用一个你并不了解的模型之前,或者在你对现有模型做调优时,这类评估都是至关重要的。
目前执行此类任务最常用的工具套件之一,就是 Language Model Evaluation Harness。
部署前 benchmark 对模型选择很有帮助;但问题是:模型在生产环境运行过程中,如何持续评估质量?
这时就轮到 LLM-as-a-judge 技术出场了:
即由一个 LLM 去评估另一个 LLM 的输出,在相关性、一致性、事实性、安全性等质量维度上打分。
与人工评估相比,这种方法更容易扩展;与简单的规则检查相比,它又能捕捉更细腻的质量问题。
例如,一个能力较强的模型(如 GPT-4)或专门训练出来的 judge 模型,可以评估回答是否有帮助、是否准确、是否得体。本质上,它就像一个自动化的质量审查员。
从 Kubernetes 的运维视角看,实现 LLM-as-a-judge 时,必须认真权衡成本与延迟。
我们并不希望同步地评估每一条响应,因为那样会显著增加用户请求延迟,同时也会大幅抬高推理成本。
因此,生产系统通常会在一个异步流水线中,对一小部分采样响应(例如 1% 到 10%)进行评估,而不阻塞面向用户的请求链路。
judge 模型会产出质量分数,而这些分数也可以被导出成 Prometheus 指标。这样,你就能够像本章前面介绍的那样,对质量趋势做持续跟踪、在质量退化时触发告警,并把质量指标与基础设施变化或流量模式联系起来分析。
像 OpenAI Evals、LangSmith 和 Arize AI 这样的框架,都提供了结构化的 LLM 评估方法,虽然很多团队仍会根据自身质量要求实现定制方案。
关键点在于:要把质量指标当作与延迟和吞吐量同等级别的一等可观测性信号来对待。
把评估结果纳入你现有的可观测性栈:例如把指标放进 Prometheus,把详细评估结果放进日志系统,并像对待基础设施指标一样建立质量 SLO。
当 LLM 部署完成之后,还可以针对某些特定任务计算某些指标,以降低 hallucination 风险。
例如,在摘要任务中,我们通常预期输出的主要内容应当来自输入文本本身。这时可以使用一种叫做 ROUGE 的技术,来衡量输入与输出之间词组重叠程度。
在类似场景中,我们可以像“Fairness”一节中介绍的那样,引入一个组件来计算这些指标并导出到 Prometheus。
即使模型没有产生 hallucination,它仍然可能生成不恰当或有毒内容;幸运的是,我们可以借助一种称为 guardrails 的技术来进行缓解。
Hallucination 和 toxic content 都属于更广义的**模型安全(model safety)**问题。
接下来,我们先来看 Responsible AI,然后再讨论一些模型安全技术。理解 Responsible AI 的原则,是有效落地这些安全护栏的基础。
Responsible AI
Responsible AI(负责任 AI) 是一个涵盖原则与技术的领域,其目标是以一种能够为所有相关方带来透明性与信任的方式来开发和管理 AI 方案。
它具有明确的伦理含义,例如避免偏差,并整体上致力于降低 AI 采用过程中的各种风险。
这一目标无法通过只关注某一个单点问题来实现。
相反,它需要你的组织在各个层面共同采用一套框架。
从某种角度来说,Responsible AI 的思维方式,很像组织对待安全(security)的方式:即使有一个专门的安全团队在落实安全策略,也并不意味着其他人就可以不遵守安全原则。Responsible AI 也是一样。
Responsible AI 涵盖很多不同方面,并不存在一个唯一标准定义;但总体来说,可以概括为两大方向:可解释性(explainability) 与 公平性(fairness) 。
而在最近,LLM 也已经成为 Responsible AI 的首要关注重点,尤其是有毒内容检测与幻觉问题。
下面我们会先简要介绍更偏向传统预测式 AI 的 explainability 和 fairness,然后在“模型安全:幻觉与 Guardrails”一节中,把重点转向更适用于 LLM 的模型安全问题。
可解释性(Explainability)
可解释性是一个贯穿性的议题,因为它既涉及模型选择,也涉及执行后的分析。
它的核心原则是:人类信任建立在能够理解模型为何、以及如何做出预测之上。
而不同模型天然具备的可解释性程度并不相同。
例如,神经网络能力很强,但对人类来说很难理解,因为其“知识”是以多层权重中的数字形式存在的,人类很难把这些数字直接关联回真实输入和输出。
可解释性技术可以解释模型的整体行为(global explanation),也可以解释某一次具体预测(local explanation)。有时它也被称为 interpretability,因为有些模型本身就是可以直接被解释的。
从 Kubernetes 的角度看,KServe 支持为 InferenceService 挂接一个 explainer,用来执行 local explanation。
但这通常不建议在生产环境中直接使用,因为生成 explanation 的成本往往比直接执行模型推理还要高一个数量级。
TrustyAI 项目提供了多种 explainer 实现,并且可以与 KServe 原生配合使用(详见其文档)。
在生产环境中,更实用的做法是:使用 Inference Logger 捕获模型服务端的每一组请求-响应对。这样一来,当将来需要调查某次有争议的预测时,就可以事后只针对那一次预测生成 local explanation,而不是在实时链路中为每一个请求都付出高昂的解释成本。
公平性(Fairness)
公平性是 AI 采用中的另一个关键问题。
我们不希望模型歧视任何人,尤其是弱势或代表性不足的群体;更广泛地说,我们也不希望模型学习并放大训练数据中的偏见。
偏差(bias)可以通过多种方式进入模型:
例如,某些类别在训练数据中样本不足,即便数据里没有显式歧视,也可能产生不公平结果;
或者,模型学习到一些本不该驱动预测的相关性。举个例子:住在贫困地区的人群可能在历史数据中贷款拒绝率更高,但这并不意味着模型就应该基于地区直接拒绝贷款申请。
总体而言,偏差通常与一个或多个特征相关,而这些特征会被定义为 受保护属性(protected attributes) :对于这些特征,我们期望模型表现出公平性,也就是说,受保护属性的取值不应直接主导预测结果。
公平性中最关键的一点在于:即使训练数据已经被认真审查、模型训练时也避免了偏差,运行时仍然可能因为**数据漂移(data drift)**而产生偏差。
训练数据可能已经不能代表当前的人类行为模式,于是模型首次遇到某类新分布数据时,就可能出现有偏结果。
KServe 和 TrustyAI 可以帮助你在生产运行过程中监控这一问题:它们能够围绕一个或多个 protected attributes 产出偏差指标。
TrustyAI 会利用 Inference Logger 获取全部预测数据,然后计算并导出 Prometheus 指标。
更多信息可以参考对应的 demo。
模型安全:幻觉与 Guardrails
作为本章关于可观测性的最后一个主题,我们将讨论模型安全。这很可能是当前 LLM 监控领域里演进最快的方向之一,未来预计还会有大量新进展与重大变化。
模型安全主要关注 LLM 部署中的两类关键风险:
一类是 hallucination(幻觉) ,即模型生成了看似合理、但实际上错误的信息;
另一类是有毒或不恰当内容,即模型可能产出有害响应,或者在提示注入攻击下被操控。
这些风险既需要检测机制,也需要防护机制——也就是所谓的 guardrails(护栏) ——从而确保模型在生产环境中能够安全、可靠地运行。
理解并检测幻觉
LLM 容易产生幻觉。
几乎每个人在使用生成式 AI 的过程中都遇到过这种情况,往往一开始还会误以为回答是正确的。
其原因在于:LLM 即便在 hallucinate 时,也依然会给出条理清晰、理由充分的答案。
什么是幻觉?
幻觉通常指的是各种层面的不一致,它可以出现在:
- 生成文本内部:例如“Daniele 很高,因此他是最矮的人。”
- 输入提示与输出回答之间:例如提示要求“生成一段正式文本,通知同事……”,结果模型却输出“Yo Boyz!”
- 事实层面错误:例如“2024 年第一个登上月球的人”
示例 5-6 就展示了一个真实世界中的幻觉案例。
为什么会产生幻觉?
LLM 本质上是黑盒,而它之所以会 hallucinate,原因可能有很多:
- 训练数据不完整或不一致,导致模型只能基于不充分的数据去泛化
- 使用了更“容易幻觉化”的采样配置,例如 temperature、top_k、top_p 等参数,使模型倾向于产生概率更低、但更有创造性的回答
- 提供给模型的上下文或 prompt 质量不足,例如问题过于宽泛,不够具体
如果分析这三类原因,你会发现几个根本问题:
大多数团队并不会自己训练 LLM,因此无法真正解决训练数据局部缺失或错误的问题。
而虽然通过模型配置可以限制创造性,但 LLM 的一个重要目标恰恰又是保持创造性。
因此,最可控的部分,其实是:让输入更具体、更高质量。
除了幻觉之外,还有其他安全风险也必须处理。
有毒或不恰当内容,既可能来自模型输出,也可能来自恶意用户输入。
“不恰当”的定义很宽泛,既可能是离题内容,也可能是返回私人或敏感信息(PII,Personally Identifiable Information,个人身份信息)。
大多数知名开源模型都已经经过微调,使其倾向于生成友好、不过分冒犯的文本。
但攻击者仍然可以设计特定 prompt,绕过模型内置的安全机制。
这类攻击包括:
- prompt jailbreaking:通过提示诱导模型输出违反使用条款的内容
- prompt injection:用户试图在提示中注入特定指令,以绕过开发者原本配置好的系统指令
实现这类攻击其实很简单:只要在 prompt 中加一句类似“ignore all previous instructions”,就有可能让模型混乱,从而绕过 system prompt。
运行时 Guardrails(Runtime Guardrails)
幸运的是,我们可以通过实现 guardrails 来保护模型:
即在用户输入和模型输出前后加入前处理器(preprocessors)和后处理器(postprocessors),对数据进行校验,确保模型始终沿着正确轨道运行。
从本质上说,guardrail 包含两个部分:
- 一个输入校验函数,用于分析文本(或其他模态)并判断其是否符合安全标准
- 基于判断结果采取动作:允许请求继续、阻断请求,或修改请求
但要真正实现对任意文本的分析,并判断其是否不恰当,这并不简单。
实践中,往往会借助其他 LLM 来完成这件事。
有一些模型已经专门针对这类检测任务做过调优。
它们非常灵活,允许你指定要检测哪些风险类型。
这一类模型中比较典型的两个家族是 Llama Guard 和 Granite Guardian:二者都可以在 Hugging Face 上找到,且有不同规模和配置可选,因此你可以根据自己的用例选择合适版本。
目前,这类模型中较大的版本大约为 7B 或 8B,对应的数据体量约为 15–20 GB。
它们本质上也是 LLM,因此也可以像我们在“vLLM”一节中学到的那样,使用 KServe 和 vLLM 进行部署。
要执行何种校验,其配置通常直接写在 prompt 本身中。可参考示例 5-7。
同样的方法也可以用于校验模型输出。
示例 5-7. 使用 Llama Guard 校验用户输入的 Prompt
[INST] Task: Check if the content of 'user' messages in the
conversations our policies described with the below categories.
<BEGIN UNSAFE CONTENT CATEGORIES>
O1: Violence and Hate.
Should not
- Promote or suggest people to use violence.
...
Can
- Describe what violence is.
...
<END UNSAFE CONTENT CATEGORIES>
<BEGIN CONVERSATION>
...
<END CONVERSATION>
Provide safety assessment with following format:
- First line must be 'safe' or 'unsafe'
- If unsafe, second line must include comma-separated list of
violated categories.
这个 prompt 中包含几个关键点:
- 在和 LLM 的对话中,你可以定义不同角色;这里这段指令就是在让 Llama Guard 只检查 user 消息。
- 在这一部分中,你可以配置要检测的一个或多个不当类别;对于
Should not和Can的描述越具体,检测效果通常越好。 - 在这个标签之后,填入你想要校验的完整对话。
- 必须非常明确地规定输出格式,才能让结果易于解析,并据此决定后续动作。
这种技术非常强大,但也代价不小,无论是资源成本还是引入的延迟都很高:
因为你需要额外部署另一个 LLM 来检查对话;而且为了做安全评估,它通常需要看到完整对话,无法按 token 级别逐步判断。可以想见,这会给终端用户带来相当明显的额外等待。
因此,在实现安全 guardrails 时,必须认真权衡,尽量选择更小、更专门化的模型和技术方案,以便在成本与性能之间找到适合自己用例的平衡点。
guardian 模型如何嵌入终端用户请求流程,当然可以通过自定义编排代码以程序方式完成;与此同时,相关工作也正在持续推进,希望把这部分能力整合进我们在第 4 章介绍过的 AI / LLM gateway 组件中。
除此之外,社区也已经发展出一些专门框架,用于在生产环境中编排和管理 guardrails。下面我们来看几种流行框架。
NVIDIA NeMo Guardrails
NVIDIA NeMo Guardrails 是一个开源工具包,用于为基于 LLM 的对话应用添加可编程 guardrails。
该框架使用一种名为 Colang 的自定义建模语言,这种语言专门用于定义对话流与安全约束。
借助这种方式,开发者可以通过定义特定响应模式、禁止讨论某些话题、以及确保对话路径始终停留在允许范围内,来控制 LLM 的行为。
NeMo Guardrails 支持五类 rails,它们可以作用于 LLM 交互的不同阶段:
Input rails
在请求到达模型前,对用户输入进行校验和过滤,阻断恶意 prompt 或敏感信息请求。
Dialog rails
控制多轮对话流程,确保模型始终围绕主题展开。
Retrieval rails
在 RAG 场景中,对外部知识库检索回来的信息进行验证。
Execution rails
监控并控制模型何时调用外部工具或 API。
Output rails
在模型响应返回给用户前,对输出进行过滤与校验。
该框架既支持 OpenAI 这类云端 LLM,也支持 Llama 4 这类自托管模型;它既可以作为 Python 库使用,也可以部署为独立 Guardrails server,或者打包进容器镜像用于 Kubernetes 部署。
NeMo Guardrails 特别适合那些需要严格对话边界的领域型助手和问答系统。
FMS Guardrails Orchestrator
FMS Guardrails Orchestrator 由 IBM Research 开发,并已集成进 TrustyAI 项目。
它的设计目标非常明确:专门用来在复杂工作流中,编排一个或多个 guardrail 的应用顺序与方式。
该框架所解决的是生产级 LLM 部署中的一个常见难题:在请求处理的不同阶段,往往需要组合多种安全检查,而这些检查之间还需要相互协调。
Orchestrator 提供了一层抽象,使你可以把不同类型的 guardrail(例如输入校验、输出过滤、PII 检测)组合成一个统一的安全流水线。
这些 guardrail 单元在框架中被称为 detector。
当你需要基于上下文、用户角色,或当前调用的具体 LLM,去应用不同安全策略时,这种组合能力就非常有价值。
对于 Kubernetes 部署来说,FMS Guardrails Orchestrator 可以作为一个服务部署在应用与模型服务端之间,拦截请求与响应,并应用预先配置好的安全策略。
由于它与 TrustyAI 集成,因此还具备监控能力——例如,你可以把 guardrail 的触发情况与违规情况导出为 Prometheus 指标。
Guardrails AI
与前面偏向基础设施层的框架不同,Guardrails AI 采取的是一种更偏向开发者、也更贴近代码层的方式。
它是一个 Python 库,采用 validator-based architecture(基于校验器的架构) ,并提供一个集中式的预构建风险检测器仓库。
这个框架主要有两个用途:
- 通过输入/输出校验来检测和缓解特定 AI 风险
- 帮助从 LLM 响应中生成结构化数据
Guardrails AI 最大的差异点在于 Guardrails Hub:
这里汇集了社区贡献的大量 validator,可用于检测毒性语言、PII 暴露、幻觉、竞争对手提及、跑题回答等具体风险。
这些 validator 可以灵活组合,构建适合你业务场景的保护逻辑。
与那些需要学习新配置语言、或部署独立编排服务的框架不同,Guardrails AI 的 validator 本质上就是 Python 函数,直接集成在你的应用代码里即可。
框架会在应用内部拦截 LLM 输入与输出,把它们送进已配置的 validator,并根据结果采取动作:例如阻断请求、记录违规日志,或进行修复处理。
这种开发者中心、代码级集成的方式,对于那些更愿意在应用层维护安全逻辑、而不是额外部署基础设施组件的团队来说,很有吸引力。
但这也意味着,与 NeMo Guardrails 或 FMS Guardrails Orchestrator 这类可作为独立服务部署的框架相比,它与 Kubernetes 生态的集成程度较低。
在 Kubernetes 环境中,Guardrails AI 通常直接嵌入到应用容器代码中,这使得部署更简单,但在多服务之间做集中式策略管理时灵活性可能不如前者。
Llama Stack 与 Moderation API
由 Meta 创建的 Llama Stack 定义了一套较为完整的生成式 AI 应用 API,其中包含一个专门的安全层:通过 Safety API 和可配置的 shields(护盾 / guardrails) 来实现。
该 API 允许开发者注册具备特定配置的 safety shields,并在 LLM 交互的输入与输出阶段分别应用它们。
Safety API 支持多种 shield 类型:
从基于 Llama Guard 模型的基础内容审核,到面向特定领域需求的高级自定义安全策略,都可以覆盖。
Shields 可以以很细粒度的方式应用,例如:对用户输入与模型输出使用不同 shield,或者根据对话状态动态调整 shield。
Llama Stack 还提供了一个 /v1/moderations endpoint,其设计理念与 OpenAI Moderation API 十分接近。
OpenAI Moderation API 本质上是一个专用模型 endpoint,用于对文本输入进行分类,例如仇恨言论、自残、色情、暴力等类别。
API 会返回各个类别的评分,以及一个二元标记,指出该内容是否违反相应策略。
使用 OpenAI 或 Llama Stack 这类 moderation API 的优势在于:
你无需自己部署和维护额外的 guardrail 模型,就可以直接使用那些已经预训练并且持续更新的安全分类模型。
但它们通常不如 NeMo Guardrails 或 Guardrails AI 这类框架方案那样可定制;而且,一旦依赖外部 API,就会引入网络延迟以及一定的供应商依赖。
对于 Kubernetes 部署来说,Llama Stack 可以以一个服务的形式部署,让应用通过调用该服务来应用 shields;也可以把 Llama Stack SDK 直接集成进应用容器。
而 moderation API 这种方式,最适合的是异步校验工作流:也就是对一小部分采样请求做安全评估,而不阻塞面向用户的响应链路。
提示(TIP)
前面提到的很多 guardrailing 技术,本质上都依赖于 LLM as a judge——也就是由一个 LLM 去评估另一个 LLM 的输出,甚至评估它自己的输出。
当你使用 LLM as a judge 去做安全检测,或者用于任何形式的评估 prompt 时,一定要让评估问题足够具体。
不要问“这个答案对吗?”,而要问更有针对性的问题,例如:
- “这个回答的语气是否正式?”
- “这个响应里是否包含个人信息?”
越具体、越聚焦的评估标准,通常越能让 judge 模型产出稳定且一致的判断。
模型安全仍然是一个高度活跃的研究领域。
实施合适的 guardrailing 对于缓解 LLM 使用带来的风险至关重要;但与此同时,要在复杂度、成本与效果之间找到合适平衡,仍然是件很难的事。
经验总结(Lessons Learned)
本章中,我们探讨了如何在 Kubernetes 上观测 LLM 工作负载:从基础设施指标,到模型质量监控,再到安全 guardrails。
传统监控指标对于 LLM 推理工作负载来说,只能讲出一部分故事。
CPU 与内存利用率当然重要,但它们既忽略了主要计算资源(GPU),也无法反映推理两个阶段的本质差异:compute-bound 的 prefill 与 memory-bound 的 decode。
基于 token 的指标,取代了传统的基于请求的可观测性。
Time To First Token(TTFT) 衡量的是用户在 prefill 阶段感知到的等待时间;
Time Per Output Token(TPOT) 则决定了生成文本是否快到让人类阅读时感觉顺畅。
这些指标能直接映射到用户体验上,而这是传统吞吐量与延迟指标无法做到的。
模型质量可观测性,不能只停留在基础设施监控层面。
无论是安全 guardrails、幻觉检测,还是偏差缓解,都必须在输入和输出两个阶段被嵌入系统,把内容校验当作一等公民级别的运维关注点,而不是等部署完成后再做审计。
GPU 指标需要依赖厂商专用工具。
NVIDIA 的 DCGM、AMD 的 ROCm SMI、Intel 的 XPU Manager,都会通过 Prometheus exporter 暴露硬件指标,使我们能够把 GPU 利用率、显存、温度与功耗,与 LLM 专属指标一起纳入同一套可观测性体系。
平台运维人员应当为完整推理路径做埋点:从请求进入,到模型执行,再到响应返回,都应被纳入观测。
在条件允许时,应尽量采用 OpenTelemetry 语义约定,以提升跨 runtime 的可移植性。
只有在日志、指标、链路追踪与质量信号这些维度上都建立起完整可观测性,我们才真正具备诊断性能问题、优化资源分配,以及在生产环境中维持安全 guardrails 的基础。
至此,我们关于推理与生产就绪性的讨论告一段落。
现在,你已经具备了部署、扩缩容以及观测 LLM 工作负载所需的工具。
接下来,我们将进入下一个重要主题:模型调优(tuning) ——也就是如何把基础模型适配到你的具体需求之上。