Chapter 10. Distributed Inference with llm-d observability


You can use Observability to analyze your llm-d deployments, improving overall model management and monitoring on the platform.

Use Prometheus to collect metrics from Distributed Inference with llm-d deployments and build custom observability dashboards to monitor token throughput, cache use, routing decisions, and inference performance. On OpenShift, the controller automatically creates PodMonitor and ServiceMonitor resources to scrape metrics from vLLM engines and the inference scheduler. On other Kubernetes platforms, configure Prometheus scrape targets manually to collect metrics from the vLLM and inference scheduler endpoints.

Important

Distributed Inference with llm-d Observability is a Developer Preview feature only. Developer Preview features are not supported by Red Hat in any way and are not functionally complete or production-ready. Do not use Developer Preview features for production or business-critical workloads. Developer Preview features provide early access to upcoming product features in advance of their possible inclusion in a Red Hat product offering, enabling customers to test functionality and provide feedback during the development process. These features might not have any documentation, are subject to change or removal at any time, and testing is limited. Red Hat might provide ways to submit feedback on Developer Preview features without an associated SLA.

10.1. Metrics for llm-d inference deployments

Distributed Inference with llm-d deployments expose Prometheus metrics that you can use to monitor inference performance, track resource usage, and detect operational issues. On OpenShift, the controller automatically creates PodMonitor and ServiceMonitor resources to collect these metrics when you deploy an LLMInferenceService. On other Kubernetes platforms, you must configure Prometheus scrape targets manually to collect metrics from the vLLM engine and inference scheduler endpoints. You can build custom dashboards for SLO compliance and capacity planning.

llm-d components expose three distinct categories of Prometheus metrics:

vLLM engine metrics
The vLLM model servers expose metrics on port 8000 at the /metrics endpoint. These metrics cover token throughput, request latency distributions, KV cache utilization, request queue depth, and model loading status. vLLM metrics provide the foundation for understanding individual model server performance and resource consumption.
EPP and inference scheduler metrics
The Endpoint Picker (EPP) exposes metrics at the /metrics endpoint on the metrics service port. These metrics cover request routing decisions, endpoint scoring, scheduling latency, plugin processing times, and prefix cache indexing performance. EPP metrics help you understand how requests are distributed across model server replicas and how efficiently the scheduler routes traffic.
Inference objective metrics
The inference scheduler also exposes higher-level metrics that track end-to-end inference performance against service level objectives. These metrics include request error rates, time to first token (TTFT), time per output token (TPOT), SLO violation counts, and request duration distributions. Inference objective metrics provide the basis for SLO-based alerting and compliance monitoring.

These three metric categories work together to provide a complete picture of your llm-d deployment health. Use vLLM metrics to monitor individual model server capacity, EPP metrics to evaluate routing efficiency, and inference objective metrics to track overall service quality.

On OpenShift, the controller automatically creates PodMonitor and ServiceMonitor resources to scrape metrics from vLLM engines and the inference scheduler when you deploy an LLMInferenceService. On other Kubernetes platforms, the controller does not create these resources. You must configure Prometheus scrape targets manually to collect metrics.

The following scrape targets expose llm-d metrics:

vLLM engine metrics
Exposed on port 8000 at the /metrics endpoint on each model server pod.
EPP and inference scheduler metrics
Exposed at the /metrics endpoint on the metrics service port of the scheduler service.

Prerequisites

  • You have a deployed Distributed Inference with llm-d inference deployment on Red Hat OpenShift AI 3.5.
  • You have cluster administrator access.
  • You have a Prometheus-based monitoring stack configured in your cluster.

    On OpenShift, enable user workload monitoring. The Prometheus Operator is included automatically. See Configuring user workload monitoring.

    On other Kubernetes platforms, you must have the Prometheus Operator installed if you want to use PodMonitor and ServiceMonitor resources, or a standalone Prometheus deployment configured with static or service-discovery-based scrape targets.

Procedure

On OpenShift:

  1. Verify that the PodMonitor resource for vLLM engine metrics exists in your deployment namespace:

    $ kubectl get podmonitors -n <NAMESPACE> -l app.kubernetes.io/component=llm-monitoring

    Replace <NAMESPACE> with the namespace where your llm-d deployment runs.

    The output shows a PodMonitor resource with a name that begins with kserve-llm-isvc-vllm-engine. This resource scrapes vLLM metrics from port 8000 on all model server pods.

  2. Verify that the ServiceMonitor resource for inference scheduler metrics exists in your deployment namespace:

    $ kubectl get servicemonitors -n <NAMESPACE> -l app.kubernetes.io/component=llm-monitoring

    The output shows a ServiceMonitor resource with a name that begins with kserve-llm-isvc-scheduler. This resource scrapes EPP and inference scheduler metrics from the metrics port on the scheduler service.

On other Kubernetes platforms without the Prometheus Operator:

  1. Configure your Prometheus instance to scrape the following targets in your deployment namespace:

    Expand
    Table 10.1. llm-d scrape targets
    ComponentPortPathPod selector

    vLLM engine

    8000

    /metrics

    Pods with the label app: <POOL_NAME>, where <POOL_NAME> is the name of your InferencePool resource.

    EPP and inference scheduler

    9090

    /metrics

    The EPP service, selected by the label app: <EPP_NAME>, where <EPP_NAME> is the name of your EPP deployment.

On all platforms:

  1. Verify that Prometheus is scraping vLLM metrics by running the following PromQL query against your Prometheus instance:

    vllm:num_requests_running{namespace="<NAMESPACE>"}

    Replace <NAMESPACE> with your deployment namespace.

    On OpenShift, run this query from the web console by navigating to Observe > Metrics.

    On other Kubernetes platforms, run this query by using your Prometheus web UI or API.

    If the query returns results, Prometheus is successfully scraping vLLM metrics.

  2. Verify that Prometheus is scraping EPP and inference scheduler metrics by running the following PromQL query:

    llm_d_inference_scheduler_disagg_decision_total{namespace="<NAMESPACE>"}

    Replace <NAMESPACE> with your deployment namespace.

    If the query returns results, Prometheus is successfully scraping inference scheduler metrics.

Note

Metrics might take up to 60 seconds to appear after the LLMInferenceService is deployed. If metrics do not appear, verify that your monitoring stack is configured to scrape metrics from the deployment namespace. On OpenShift, verify that user workload monitoring is enabled and that PodMonitor and ServiceMonitor resources are present. On other platforms, verify that the Prometheus scrape configuration targets the correct namespace, ports, and label selectors.

10.3. PromQL queries for llm-d monitoring

You can use the following PromQL queries to monitor your Distributed Inference with llm-d deployments. The queries are organized into two groups: immediate failure and saturation indicators that drive operational alerts, and diagnostic drill-down queries that support deeper investigation.

Failure and saturation indicators

Use the following queries for dashboards and alerts that track service health and capacity limits.

Expand
Table 10.2. Failure and saturation PromQL queries
Operational concernPromQL query

Overall error rate

sum(rate(inference_objective_request_error_total[5m])) / sum(rate(inference_objective_request_total[5m]))

Per-model error rate

sum by(model_name) (rate(inference_objective_request_error_total[5m])) / sum by(model_name) (rate(inference_objective_request_total[5m]))

Error rate by error code

sum by(error_code) (rate(inference_objective_request_error_total[5m]))

Request rate by model

sum by(model_name, target_model_name) (rate(inference_objective_request_total[5m]))

Overall latency P99

histogram_quantile(0.99, sum by(le) (rate(inference_objective_request_duration_seconds_bucket[5m])))

Overall latency P90

histogram_quantile(0.90, sum by(le) (rate(inference_objective_request_duration_seconds_bucket[5m])))

Model-specific TTFT P99

histogram_quantile(0.99, sum by(le, model_name) (rate(vllm:time_to_first_token_seconds_bucket[5m])))

Model-specific TPOT P99

histogram_quantile(0.99, sum by(le, model_name) (rate(vllm:inter_token_latency_seconds_bucket[5m])))

EPP end-to-end latency P99

histogram_quantile(0.99, sum by(le) (rate(inference_extension_scheduler_e2e_duration_seconds_bucket[5m])))

Plugin processing latency P99

histogram_quantile(0.99, sum by(le, plugin_type) (rate(inference_extension_plugin_duration_seconds_bucket[5m])))

Scheduler health

avg_over_time(up{job=~".*epp.*"}[5m])

Request preemptions per vLLM instance

sum by(pod, instance) (rate(vllm:num_preemptions_total[5m]))

Diagnostic drill-down queries

Use the following queries for deeper investigation when failure and saturation indicators signal issues.

Expand
Table 10.3. Model serving and scaling queries
Operational concernPromQL query

KV cache utilization by pod

avg by(pod, model_name) (vllm:kv_cache_usage_perc)

Request queue length by pod

sum by(pod, model_name) (vllm:num_requests_waiting)

Model throughput in tokens per second

sum by(model_name, pod) (rate(vllm:prompt_tokens_total[5m]) + rate(vllm:generation_tokens_total[5m]))

Generation token rate by pod

sum by(model_name, pod) (rate(vllm:generation_tokens_total[5m]))

Active requests per pod

avg by(pod) (vllm:num_requests_running)

Expand
Table 10.4. Routing and load balancing queries
Operational concernPromQL query

Request distribution per instance

sum by(pod) (rate(inference_objective_request_total{target_model_name!=""}[5m]))

Token distribution across pods

sum by(pod) (rate(vllm:prompt_tokens_total[5m]) + rate(vllm:generation_tokens_total[5m]))

Routing decision latency P99

histogram_quantile(0.99, sum by(le) (rate(inference_extension_plugin_duration_seconds_bucket[5m])))

Expand
Table 10.5. Prefix caching queries
Operational concernPromQL query

Prefix cache hit rate

sum(rate(vllm:prefix_cache_hits_total[5m])) / sum(rate(vllm:prefix_cache_queries_total[5m]))

Per-instance prefix cache hit rate

sum by(pod) (rate(vllm:prefix_cache_hits_total[5m])) / sum by(pod) (rate(vllm:prefix_cache_queries_total[5m]))

KV cache utilization percentage

avg by(pod, model_name) (vllm:kv_cache_usage_perc * 100)

EPP prefix indexer size

inference_extension_prefix_indexer_size

EPP prefix indexer hit ratio P90

histogram_quantile(0.90, sum by(le) (rate(inference_extension_prefix_indexer_hit_ratio_bucket[5m])))

Expand
Table 10.6. Prefill/decode disaggregation queries
Operational concernPromQL query

Prefill worker utilization

avg by(pod) (vllm:num_requests_running{pod=~".*prefill.*"})

Decode worker KV cache utilization

avg by(pod) (vllm:kv_cache_usage_perc{pod=~".*decode.*"})

Prefill queue length

sum by(pod) (vllm:num_requests_waiting{pod=~".*prefill.*"})

Disaggregation decision rate by type

sum by(decision_type) (rate(llm_d_inference_scheduler_disagg_decision_total[5m]))

Disaggregation decision ratio

sum(rate(llm_d_inference_scheduler_disagg_decision_total{decision_type="prefill-decode"}[5m])) / sum(rate(llm_d_inference_scheduler_disagg_decision_total[5m]))

10.4. Import Grafana dashboards for llm-d

You can import community Grafana dashboards to get a baseline monitoring view of your Distributed Inference with llm-d deployments. The dashboards provide pre-built visualizations for vLLM performance, failure and saturation indicators, diagnostic drill-downs, KV cache performance, and prefill/decode disaggregation metrics.

Prerequisites

  • You have a Grafana instance deployed and connected to a Prometheus data source that collects llm-d metrics. Grafana is not included with OpenShift. You must deploy Grafana separately, for example, by using the Grafana community Operator.
  • Metrics collection is enabled for your Distributed Inference with llm-d deployment.

Procedure

  1. Download the community Grafana dashboard JSON files from the llm-d project.

    The following dashboards are available:

    llm-d vLLM Overview
    General vLLM metrics overview for monitoring llm-d inference servers. Download from llm-d-vllm-overview.json.
    llm-d Failure and Saturation Indicators
    Key failure and saturation indicators for identifying system issues and capacity constraints. Download from llm-d-failure-saturation-dashboard.json.
    llm-d Diagnostic Drill-Down
    Detailed diagnostic metrics for investigating performance issues. Download from llm-d-diagnostic-drilldown-dashboard.json.
    llm-d Performance Dashboard
    Performance metrics including KV cache utilization. Download from llm-d-performance-kv-cache.json.
    P/D Coordinator Metrics
    Prefill/decode disaggregation performance metrics, including vLLM end-to-end latency, prefill duration, decode duration, and phase breakdown. Download from llm-d-pd-coordinator-metrics.json.
  2. In the Grafana web interface, navigate to Dashboards > New > Import.
  3. Upload a dashboard JSON file or paste the JSON content into the import dialog.
  4. Select the Prometheus data source that collects metrics from your llm-d deployment.
  5. Click Import to create the dashboard.
  6. Repeat the import process for each dashboard JSON file.

Verification

  • Navigate to a newly imported dashboard and verify that the panels display data from your llm-d deployment. If a "No data" message is shown, verify that the Prometheus data source is correctly configured and that metrics collection is enabled for the llm-d namespace.
Note

In disconnected environments, download the dashboard JSON files from a connected network and transfer them to a workstation with access to the Grafana instance. The dashboards do not reference external data sources, so they function in disconnected environments after Prometheus and Grafana are available.

10.5. vLLM metrics for llm-d

The vLLM model servers in Distributed Inference with llm-d deployments expose Prometheus metrics on port 8000 at the /metrics endpoint. You can use these metrics to monitor token throughput, request latency, KV cache usage, and request queue depth for individual model server replicas.

Token throughput metrics

Expand
Table 10.7. Token throughput metrics
Metric nameTypeLabelsDescription

vllm:prompt_tokens_total

counter

model_name

The total number of prompt tokens processed by the model server.

vllm:generation_tokens_total

counter

model_name

The total number of generation tokens produced by the model server.

vllm:e2e_request_latency_seconds

histogram

model_name

The end-to-end request latency distribution in seconds, measured from request receipt to response completion.

Latency metrics

Expand
Table 10.8. Latency metrics
Metric nameTypeLabelsDescription

vllm:time_to_first_token_seconds

histogram

model_name

The time to first token (TTFT) distribution in seconds. This measures the latency from request submission to the first generated token.

vllm:inter_token_latency_seconds

histogram

model_name

The inter-token latency distribution in seconds. This measures the time between consecutive output tokens.

KV cache metrics

Expand
Table 10.9. KV cache metrics
Metric nameTypeLabelsDescription

vllm:kv_cache_usage_perc

gauge

model_name

The GPU KV cache utilization as a percentage between 0.0 and 1.0.

vllm:num_preemptions_total

counter

model_name

The total number of request preemptions caused by KV cache pressure. Preemptions occur when the cache is full and running requests must be evicted to make room for new requests.

Request queue metrics

Expand
Table 10.10. Request queue metrics
Metric nameTypeLabelsDescription

vllm:num_requests_running

gauge

model_name

The current number of requests being actively processed by the model server.

vllm:num_requests_waiting

gauge

model_name

The current number of requests waiting in the queue for processing.

vllm:request_success_total

counter

engine, model_name, finished_reason

The total number of successfully completed inference requests, broken down by completion reason.

Prefix cache metrics

Expand
Table 10.11. Prefix cache metrics
Metric nameTypeLabelsDescription

vllm:prefix_cache_hits_total

counter

model_name

The total number of prefix cache hits. A cache hit occurs when a request prompt matches cached key-value data, avoiding redundant computation.

vllm:prefix_cache_queries_total

counter

model_name

The total number of prefix cache queries. Use this metric with prefix_cache_hits_total to calculate the cache hit rate.

The Endpoint Picker (EPP) and inference scheduler in Distributed Inference with llm-d deployments expose Prometheus metrics at the /metrics endpoint on the metrics service port. You can use these metrics to monitor request routing decisions, scheduling latency, inference objective compliance, and prefix cache indexing performance.

Inference objective metrics

The inference objective metrics track end-to-end inference performance and SLO compliance. These metrics use the inference_objective_ prefix.

Expand
Table 10.12. Inference objective metrics
Metric nameTypeLabelsDescription

inference_objective_request_total

counter

model_name, target_model_name, priority

The total number of inference requests processed by the scheduler.

inference_objective_request_error_total

counter

model_name, target_model_name, error_code

The total number of inference request errors, broken down by error code.

inference_objective_request_duration_seconds

histogram

model_name, target_model_name

The end-to-end inference request duration distribution in seconds.

inference_objective_request_ttft_seconds

histogram

model_name, target_model_name

The time to first token (TTFT) distribution in seconds.

inference_objective_request_tpot_seconds

histogram

model_name, target_model_name

The time per output token (TPOT) distribution in seconds.

inference_objective_request_predicted_ttft_seconds

histogram

model_name, target_model_name

The predicted TTFT distribution in seconds, used by the scheduler for routing decisions.

inference_objective_request_predicted_tpot_seconds

histogram

model_name, target_model_name

The predicted TPOT distribution in seconds, used by the scheduler for routing decisions.

inference_objective_request_slo_violation_total

counter

model_name, target_model_name, type

The total number of SLO violations, broken down by violation type: tpot_slo_violation or ttft_slo_violation.

inference_objective_running_requests

gauge

model_name

The current number of active inference requests.

inference_objective_input_tokens

histogram

model_name, target_model_name

The input token count distribution per request.

inference_objective_output_tokens

histogram

model_name, target_model_name

The output token count distribution per request.

Scheduling and routing metrics

The scheduling and routing metrics track the internal performance of the Endpoint Picker. These metrics use the inference_extension_ prefix.

Expand
Table 10.13. Scheduling and routing metrics
Metric nameTypeLabelsDescription

inference_extension_scheduler_e2e_duration_seconds

histogram

None

The end-to-end scheduling duration in seconds, measured from request receipt to endpoint selection.

inference_extension_scheduler_attempts_total

counter

status

The total number of scheduling attempts, broken down by status: success or failure.

inference_extension_plugin_duration_seconds

histogram

extension_point, plugin_type, plugin_name

The plugin processing duration in seconds, broken down by extension point, plugin type, and plugin name. Use this metric to identify slow plugins that impact scheduling latency.

inference_extension_model_rewrite_decisions_total

counter

model_rewrite_name, model_name, target_model

The total number of model rewrite decisions made by the scheduler.

Prefix cache indexer metrics

The prefix cache indexer metrics track KV cache prefix matching performance in the scheduler. These metrics use the inference_extension_ prefix.

Expand
Table 10.14. Prefix cache indexer metrics
Metric nameTypeLabelsDescription

inference_extension_prefix_indexer_size

gauge

None

The current size of the prefix cache index maintained by the scheduler.

inference_extension_prefix_indexer_hit_ratio

histogram

None

The prefix cache hit ratio distribution. A higher hit ratio indicates that requests frequently match cached prefixes, reducing redundant computation.

inference_extension_prefix_indexer_hit_bytes

histogram

None

The prefix cache hit size distribution in bytes. Larger hit sizes indicate more effective prefix reuse.

Inference pool metrics

The inference pool metrics track aggregate statistics across all endpoints in a pool. These metrics use the inference_pool_ prefix.

Expand
Table 10.15. Inference pool metrics
Metric nameTypeLabelsDescription

inference_pool_average_kv_cache_utilization

gauge

name

The average KV cache utilization across all endpoints in the inference pool.

inference_pool_average_queue_size

gauge

name

The average request queue size across all endpoints in the inference pool.

inference_pool_average_running_requests

gauge

name

The average number of running requests across all endpoints in the inference pool.

inference_pool_ready_pods

gauge

name

The number of ready pods in the inference pool.

inference_pool_per_pod_queue_size

gauge

name, model_server_pod

The request queue size for a specific pod in the inference pool.

10.7. Monitor batch inference workloads

You can monitor batch inference workloads by collecting Prometheus metrics from the batch gateway components and configuring alerts for batch processing failures. The batch gateway API server, processor, and garbage collector expose metrics that you can use to track job throughput, queue depth, processing latency, and error rates.

Prerequisites

Procedure

  1. Verify that the ServiceMonitor resource for the batch gateway API server exists:

    $ oc get servicemonitors -n <batch_gateway_namespace> -l app.kubernetes.io/component=apiserver

    Replace <batch_gateway_namespace> with the namespace where the batch gateway is deployed.

    The ServiceMonitor scrapes API server metrics from port 8081. The Batch Gateway Operator creates this resource automatically during deployment. If the ServiceMonitor does not exist, verify that the Batch Gateway Operator deployment completed successfully.

  2. Verify that the PodMonitor resource for the batch processor exists:

    $ oc get podmonitors -n <batch_gateway_namespace> -l app.kubernetes.io/component=processor

    The PodMonitor scrapes processor metrics from port 9090. The Batch Gateway Operator creates this resource automatically during deployment. If the PodMonitor does not exist, verify that the Batch Gateway Operator deployment completed successfully.

  3. Verify that the PodMonitor resource for the garbage collector exists:

    $ oc get podmonitors -n <batch_gateway_namespace> -l app.kubernetes.io/component=gc

    The PodMonitor scrapes garbage collector metrics from port 9091. The Batch Gateway Operator creates this resource automatically during deployment. If the PodMonitor does not exist, verify that the Batch Gateway Operator deployment completed successfully.

    Note

    For PromQL queries and metric names, see the batch gateway metrics implementation in the repository. The metric definitions are in the apiserver/metrics/, processor/metrics/, and gc/metrics/ subdirectories.

  4. Optional: Import pre-built Grafana dashboards for batch gateway components.

    The batch gateway includes ConfigMap resources containing Grafana dashboard JSON definitions. Import these dashboards into your Grafana instance to visualize batch processing metrics.

    $ oc get configmaps -n <batch_gateway_namespace> -l grafana_dashboard=1

Verification

  • Verify that the ServiceMonitor and PodMonitor resources exist and that Prometheus is configured to scrape them.

Additional resources

Configure distributed tracing for LLM distributed inference (Distributed Inference with llm-d) deployments using the Red Hat build of Tempo. Distributed tracing provides end-to-end visibility into inference requests, enabling performance debugging, latency analysis, and system health monitoring across the inference scheduler, routing sidecar, and vLLM model server.

Distributed tracing for Distributed Inference with llm-d records request flows across the gateway, inference scheduler, routing sidecar, and vLLM model server to help you identify latency and troubleshoot inference workloads.

10.8.1.1. Four-component tracing pipeline

The distributed tracing pipeline consists of four components:

vLLM model server instrumentation
The vLLM model server emits trace data using the OpenTelemetry SDK.
Inference scheduler instrumentation
The inference scheduler emits trace data using the OpenTelemetry SDK. The scheduler creates spans for request routing decisions and forwards requests with trace context headers to enable parent-child span relationships across components.
Routing sidecar instrumentation
The routing sidecar emits traces for disaggregated inference operations, including prefill-decode coordination and KV cache transfers between pods. The sidecar propagates trace context to ensure end-to-end visibility across distributed inference stages.
Tempo storage with Jaeger UI
Tempo receives traces directly from the inference components, stores trace data with configurable retention policies, and exposes the Jaeger Query Frontend for searching, visualizing, and analyzing traces.

All instrumented components export traces directly to Tempo over gRPC using OTLP. == Data flow

Traces flow through the system in the following sequence:

  1. A client sends an inference request to the Gateway endpoint over HTTPS.
  2. The inference scheduler receives the request, creates a child span, selects a vLLM replica, and forwards the request with trace context headers.
  3. In disaggregated prefill and decode deployments, the routing sidecar coordinates request handling and KV cache transfer between the prefill and decode pods. Its spans record these operations for each inference request.
  4. The vLLM model server receives the request, creates child spans for tokenization and model inference operations, and returns the response.
  5. All components export their spans directly to Tempo over gRPC (port 4317) using OTLP.
  6. Tempo ingests the spans and stores them in the configured backend. You can use in-memory storage for development and testing or supported object storage for persistent production storage.
  7. Users access the Jaeger UI to search for traces by service name, time range, or trace ID.

10.8.1.2. Span hierarchy

A typical inference request generates the following span structure:

Root Span:
├── Child Span: inference-server-decode
│   └── Child Span: llm_request

Each span includes timing data, service name, and trace context. This hierarchy enables latency analysis at each stage of the inference pipeline.

Distributed tracing addresses key observability challenges in LLM inference deployments:

Latency diagnosis
Identifies which component contributes most to request latency across the gateway, scheduler, and vLLM model server.
Bottleneck identification
Reveals slow operations within vLLM (tokenization, model forward pass, tensor operations).
Request flow visualization
Shows the complete path of inference requests through distributed components.
Error correlation
Links errors across services by trace ID, simplifying troubleshooting.
Performance tuning
Provides data for optimizing scheduling decisions, model loading, and GPU utilization.

Install the Red Hat build of Tempo Operator required for distributed tracing support in Distributed Inference with llm-d deployments. The Tempo Operator manages the lifecycle of trace storage and query components.

Prerequisites

  • You have an Openshift Container Platform 4.19.9+ cluster.
  • You have cluster-admin access.
  • You have installed the OpenShift CLI (`oc`).
  • You have configured access to your cluster.

Procedure

  1. Install the Red Hat build of Tempo Operator:

    1. On OpenShift 4.20 and later, install from the software catalog

      • On OpenShift 4.19, install from the OperatorHub.
    2. Search for Red Hat build of Tempo.
    3. Select the Operator from the redhat-operators catalog.
    4. Click Install.
    5. On the Install Operator page, accept the default settings and click Install.
  2. Verify the Tempo Operator installation:

    $ oc get csv -n openshift-operators | grep tempo

Verification

  • The Tempo Operator CSV shows Succeeded status.
  • The Tempo Operator pod is running in the openshift-operators namespace:

    $ oc get pods -n openshift-operators | grep tempo

10.8.3. Deploy a Tempo instance for trace storage

You can store and inspect distributed traces from Distributed Inference with llm-d deployments by configuring a TempoMonolithic instance with a Jaeger user interface.

Prerequisites

  • You have installed the Red Hat build of Tempo Operator.
  • You have cluster-admin access.
  • You have installed the OpenShift CLI (`oc`).

Procedure

  1. Create a namespace for the Tempo deployment:

    $ oc new-project tempo-system
  2. Save the following YAML to a file named tempo-monolithic.yaml. Create a TempoMonolithic custom resource:

    apiVersion: tempo.grafana.com/v1alpha1
    kind: TempoMonolithic
    metadata:
      name: tempo-llmd
      namespace: tempo-system
    spec:
      storage:
        traces:
          backend: memory
      jaegerui:
        enabled: true
        route:
          enabled: true
      ingestion:
        otlp:
          grpc:
            enabled: true
          http:
            enabled: true

    where:

    storage.traces.backend
    Specifies the trace storage backend. The value memory provides temporary, in-memory storage for development and testing.
    jaegerui.enabled
    Enables the Jaeger Query Frontend for trace visualization.
    jaegerui.route.enabled
    Creates a Route for external access to the Jaeger user interface.
    ingestion.otlp.grpc.enabled
    Enables ingestion of telemetry data by using OTLP over gRPC.
    ingestion.otlp.http.enabled

    Enables ingestion of telemetry data by using OTLP over HTTP.

    Note

    The memory backend does not persist traces after the Tempo pod restarts. For production environments, configure a supported persistent storage backend.

  3. Apply the TempoMonolithic custom resource:

    $ oc apply -f tempo-monolithic.yaml
  4. Wait for the Tempo instance to be ready:

    $ oc get tempomonolithic tempo-llmd -n tempo-system -w

    Wait until the READY status shows True.

  5. Retrieve the Jaeger user interface route URL:

    $ oc get route -n tempo-system

    Example output

    NAME                         HOST/PORT
    tempo-llmd-jaegerui          tempo-llmd-jaegerui-tempo-system.apps.example.com

Verification

  1. Verify that the TempoMonolithic custom resource shows READY=True:

    $ oc get tempomonolithic tempo-llmd -n tempo-system

    The output shows READY status as True.

  2. Verify that Tempo pods are running in the tempo-system namespace:

    $ oc get pods -n tempo-system

    All pods show Running status.

  3. Verify that the Jaeger user interface route is accessible:

    $ oc get route tempo-llmd-jaegerui -n tempo-system -o jsonpath='{.spec.host}'

    The output shows the route hostname.

Enable distributed tracing for an LLMInferenceService by using the declarative spec.tracing API. Distributed tracing provides end-to-end visibility into inference request flows from the Gateway through the scheduler to the vLLM model server.

Prerequisites

  • You have deployed a Tempo instance for trace storage. For more information, see Deploy a Tempo instance for trace storage.
  • You have an LLMInferenceService resource deployed.
  • You have installed the OpenShift CLI (`oc`).
  • You have cluster administrator access.

Procedure

  1. Save the following YAML to a file named llminferenceservice-tracing.yaml. Create an LLMInferenceService custom resource with distributed tracing enabled:

    apiVersion: serving.kserve.io/v1alpha2
    kind: LLMInferenceService
    metadata:
      name: traced-llm-inference
      namespace: llm-inference
    spec:
      tracing:
        exporterEndpoint: "http://tempo-tracing:4317"
        sampler: "parentbased_traceidratio"
        samplerArg: "0.05"
        exporter: "otlp"
      model:
        uri: hf://Qwen/Qwen2.5-7B-Instruct
        name: Qwen/Qwen2.5-7B-Instruct
      replicas: 1
      router:
        scheduler: {}
        route: {}
        gateway: {}
      template:
        containers:
        - name: main
          image: registry.redhat.io/rhaii-early-access/vllm-cuda-rhel9:3.5.0-ea.1-1780065492
          imagePullPolicy: Always
          resources:
            limits:
              cpu: "4"
              memory: 32Gi
              nvidia.com/gpu: "1"
            requests:
              cpu: "2"
              memory: 16Gi
              nvidia.com/gpu: "1"

    where:

    spec.tracing.exporterEndpoint
    Specifies the Tempo endpoint for trace export. Use the cluster-internal service DNS name tempo-tracing:4317.
    spec.tracing.sampler
    Specifies the sampling strategy. Use parentbased_traceidratio for probabilistic sampling.
    spec.tracing.samplerArg
    Specifies the sampling rate as a ratio between 0 and 1. A value of 0.05 captures 5% of traces. For production deployments, use 1% to 5% to reduce trace volume and storage costs.
    spec.tracing.exporter
    Specifies the trace export protocol. Use otlp for OTLP over gRPC.
  2. Apply the LLMInferenceService custom resource:

    $ oc apply -f llminferenceservice-tracing.yaml
  3. Wait for the LLMInferenceService resource to be ready:

    $ oc get llmisvc traced-llm-inference -n llm-inference -w

    Wait until READY=True.

  4. Verify that the tracing configuration is applied:

    $ oc get llmisvc traced-llm-inference -n llm-inference -o jsonpath='{.spec.tracing}'

Verification

  1. Verify that the LLMInferenceService resource shows READY=True:

    $ oc get llmisvc traced-llm-inference -n llm-inference
  2. Verify that the scheduler and vLLM pods are running:

    $ oc get pods -n llm-inference

    All pods show Running status.

Note

The configuration uses parentbased_traceidratio sampling with a 5% sampling rate (samplerArg: "0.05"). This balances observability with trace volume and storage costs. For higher-traffic production deployments, consider reducing the sampling rate to 1-2%. For more information, see Configure distributed tracing sampling rates.

10.8.5. Distributed tracing sampling strategies

OpenTelemetry sampling strategies control what percentage of traces are captured and stored. Choosing the right sampling approach balances observability coverage with performance overhead and storage costs.

Understanding sampling strategies helps you configure appropriate trace capture rates for development and production environments.

10.8.5.1. Why sample traces

Sampling reduces the volume of trace data without completely disabling observability:

Performance impact
Capturing every trace adds CPU overhead for span creation, serialization, and network export. Sampling reduces this overhead proportionally.
Storage costs
Storing all traces in high-traffic environments requires significant object storage capacity. Sampling reduces storage requirements and costs.
Network overhead
Exporting traces to the OpenTelemetry Collector consumes network bandwidth. Sampling reduces outbound traffic from inference pods.
Query performance
Searching through millions of traces degrades Jaeger UI responsiveness. Sampling keeps trace volume within manageable limits.

10.8.5.2. Sampling strategies

OpenTelemetry supports multiple sampling strategies, each suited to different use cases.

10.8.5.2.1. Always-on sampling

Always-on sampling captures 100% of traces.

Use case
Development, debugging, and low-traffic environments where complete observability is required.
Performance impact
High storage and network overhead. All inference requests generate trace data. For low request volumes, the performance overhead might be acceptable.
Configuration
tracing:
  sampler: parentbased_always_on
10.8.5.2.2. Probabilistic sampling

Probabilistic sampling captures a configurable percentage of traces based on trace ID hash.

Use case
Production environments with moderate traffic where statistical sampling provides sufficient coverage.
Sampling rate
Configured as a decimal between 0.0 and 1.0. For example, 0.1 means 10% of traces are sampled.
Consistent sampling
The same trace ID always produces the same sampling decision, ensuring complete traces (not partial).
Configuration
tracing:
  sampler: "parentbased_traceidratio"
  samplerArg: "0.1"
10.8.5.2.3. Parent-based sampling

Parent-based samplers make sampling decisions based on whether the parent span was sampled:

parentbased_always_on
If the parent span was sampled, this span is sampled. If there is no parent span, the trace is always sampled.
parentbased_always_off
If the parent span was sampled, this span is sampled. If there is no parent span, the trace is not sampled.
parentbased_traceidratio
If the parent span was sampled, this span is sampled. If there is no parent span, probabilistic sampling is used based on trace ID.

This ensures trace completeness across distributed components.

10.8.5.3. Coordinating sampling across components

Both the inference scheduler and vLLM should use the same sampling strategy and rate to ensure trace completeness:

spec:
  tracing:
    sampler: "parentbased_traceidratio"
    samplerArg: "0.05"

Mismatched sampling rates can result in incomplete traces where some spans are missing.

Configure OpenTelemetry trace sampling rates to balance observability needs with performance overhead and storage costs. Probabilistic sampling reduces trace volume in production deployments while maintaining statistical coverage.

Prerequisites

  • You understand distributed tracing sampling strategies. For more information, see Distributed tracing sampling strategies.
  • You have deployed an LLMInferenceService resource with distributed tracing enabled.
  • You have installed the OpenShift CLI (`oc`).
  • You have cluster administrator access.
Important

Configure the scheduler and vLLM components with the same sampling settings. Different settings can produce incomplete traces with missing spans.

Procedure

  1. Choose a sampling strategy for your environment:

    • Development and testing: Use parentbased_always_on to sample all traces.
    • Production: Use parentbased_traceidratio with an initial sampling rate of 1% to 5%.
  2. Save the following YAML to a file named llminferenceservice-tracing.yaml. Update the LLMInferenceService resource to use probabilistic sampling. The following example configures a 5% sampling rate:

    apiVersion: serving.kserve.io/v1alpha2
    kind: LLMInferenceService
    metadata:
      name: traced-llm-inference
      namespace: llm-inference
    spec:
      tracing:
        exporterEndpoint: "http://tempo-tracing:4317"
        sampler: "parentbased_traceidratio"
        samplerArg: "0.05"
        exporter: "otlp"
  3. Apply the configuration:

    $ oc apply -f llminferenceservice-tracing.yaml
  4. Monitor trace volume and adjust the sampling rate as needed:

    1. Check Tempo storage usage:

      $ oc exec -n tempo-system <tempo_pod> -- df -h

      where:

      __<tempo_pod>__
      Specifies the name of the Tempo pod.
    2. In the Jaeger UI, review the number and distribution of sampled traces.
    3. Adjust the samplerArg value based on observed trace volume.

Verification

  1. Confirm that the sampling configuration is present in the LLMInferenceService resource:

    $ oc get llminferenceservice traced-llm-inference -n llm-inference -o yaml
  2. Verify that the spec.tracing.sampler field is set to parentbased_traceidratio and that the spec.tracing.samplerArg field is set to "0.05".
  3. Generate inference requests and confirm that traces appear in the tracing interface at approximately the configured sampling rate.
Note

Each sampled trace adds approximately 1-2 KB of data and minimal CPU overhead for trace context propagation. Monitor vLLM and scheduler CPU and memory usage when enabling tracing.

10.8.7. Verify distributed tracing deployment

Verify that distributed tracing is working correctly by generating test inference traffic and viewing traces in Jaeger UI. Successful verification confirms that traces flow end-to-end from the inference components to Tempo storage.

Prerequisites

  • You have deployed distributed tracing infrastructure
  • The LLMInferenceService with tracing enabled shows READY=True
  • Jaeger UI is accessible

Procedure

  1. Send a test inference request to generate trace data:

    $ SERVICE_URL=$(oc get llmisvc traced-llm-inference -n llm-inference \
        -o jsonpath='{.status.url}')
    
    $ curl -X POST "${SERVICE_URL}/v1/chat/completions" \
        -H "Content-Type: application/json" \
        -d '{
          "model": "Qwen/Qwen2.5-7B-Instruct",
          "messages": [{"role": "user", "content": "What is Kubernetes?"}],
          "max_tokens": 100
        }'
    Note

    If you configured trace sampling, send multiple requests to ensure at least one trace is captured. The number of requests needed is 1 / sample_rate. For example, with a 5% sample rate, send at least 20 requests to see a trace in Jaeger UI.

  2. Access the Jaeger UI by using port forwarding:

    $ oc port-forward -n tempo-system svc/tempo-llmd-query-frontend 16686:16686
  3. Open a web browser to http://localhost:16686.
  4. Search for traces:

    1. In the Service dropdown, select inference-server-decode or gateway-api-inference-extension.
    2. Set the Lookback time to Last 15 minutes.
    3. Click Find Traces.
  5. Examine a trace to verify end-to-end spans:

    1. Click a trace from the search results.
    2. Verify the trace has the following spans:

      • Scheduler span showing request routing
      • vLLM span showing model inference
    3. Check that all spans share the same Trace ID.
    4. Verify span attributes include service names.
  6. Verify trace context propagation:

    1. Expand each span in the trace view.
    2. Verify parent-child relationships between spans.
    3. Confirm span timing data is accurate.

Diagnose and resolve common issues with distributed tracing for Distributed Inference with llm-d deployments. This reference covers the most frequently encountered problems and their solutions.

10.8.8.1. No traces appearing in Jaeger UI

Symptoms:

  • Inference requests succeed but no traces appear in Jaeger UI
  • Jaeger UI shows no services or traces

Diagnosis:

  1. Check Tempo logs for receiver errors:

    $ oc logs -n tempo-system -l app.kubernetes.io/name=tempo-monolithic | grep -i error
  2. Verify the spec.tracing configuration:

    $ oc get llmisvc traced-llm-inference -n llm-inference -o yaml | grep -A 4 tracing:
  3. Check network connectivity from vLLM to Tempo:

    $ oc exec -n llm-inference <vllm-pod> -- \
        curl -v http://tempo-tracing:4317

Resolution:

  • If the spec.tracing configuration is missing, update the LLMInferenceService custom resource with the correct tracing parameters.
  • If network connectivity fails, check NetworkPolicy resources and ensure Tempo is running.

10.8.9. Distributed tracing sampling parameters

OpenTelemetry sampling parameters control which traces an LLMInferenceService resource records and exports, enabling you to manage trace volume across Distributed Inference with llm-d components.

10.8.9.1. sampler field

Expand
Sampler valueDescriptionUse case

always_on

Samples 100% of traces.

Development, debugging, low-traffic environments.

always_off

Does not sample any traces.

Temporarily stop trace collection without removing the tracing configuration.

traceidratio

Samples based on trace ID hash (probabilistic).

Production environments requiring consistent sampling.

parentbased_always_on

Always samples if parent span is sampled, otherwise always samples.

Ensures complete traces when distributed tracing spans services.

parentbased_traceidratio

Always samples if parent span is sampled, otherwise uses traceidratio.

Production environments with consistent sampling across services.

Use parentbased_traceidratio for production Distributed Inference with llm-d deployments to ensure consistent sampling decisions across scheduler and vLLM components.

10.8.9.2. samplerArg field

For traceidratio and parentbased_traceidratio samplers:

Expand
ValueSampling rateDescription

1.0

100%

Samples all traces.

0.5

50%

Samples approximately 1 in 2 traces.

0.1

10%

Samples approximately 1 in 10 traces.

0.01

1%

Samples approximately 1 in 100 traces.

0.001

0.1%

Samples approximately 1 in 1,000 traces.

0.0

0%

Does not sample any traces.

Format: Decimal value between 0.0 and 1.0 (inclusive).

10.8.9.3. Configuration example

The following example configures a 5% parent-based sampling rate in the spec.tracing section of an LLMInferenceService custom resource:

spec:
  tracing:
    sampler: "parentbased_traceidratio"
    samplerArg: "0.05"
Red Hat logoGithubredditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat Documentation

Legal Notice

Theme

© 2026 Red Hat
Back to top