AI/ML Observability
Telegen provides comprehensive observability for AI/ML workloads: GPU monitoring, LLM API tracing via eBPF, inference metrics, and training observability.
Overview
AI/ML observability includes:
GPU monitoring - NVIDIA and AMD GPU metrics via NVML
LLM API tracing - eBPF-based tracing for OpenAI, Anthropic, Azure, and other LLM APIs (HTTP enrichment)
LLM inference - Token throughput, latency, TTFT, cost estimation
CUDA tracing - Kernel launches, memory operations as spans
Model serving - Batch size, queue depth, inference time
Training metrics - Loss, throughput, GPU utilization
GPU Monitoring
NVIDIA GPU Metrics
Telegen collects NVIDIA GPU metrics via NVML (NVIDIA Management Library):
Metric |
Description |
|---|---|
|
GPU compute utilization |
|
GPU memory used |
|
GPU memory total |
|
GPU memory free |
|
GPU temperature |
|
Current power draw |
|
Power limit |
|
Streaming multiprocessor clock |
|
Memory clock |
|
PCIe transmit throughput |
|
PCIe receive throughput |
|
Video encoder utilization |
|
Video decoder utilization |
Per-Process GPU Metrics
Track GPU usage per process:
Metric |
Description |
|---|---|
|
Memory used by process |
|
SM utilization by process |
Configuration
GPU collection is automatic. The GPU adapter is registered at startup and
activates whenever a supported device and its driver library are present, so
there is no gpu configuration section, no polling interval, and no per-metric
toggle. Adding those keys stops the agent from starting.
AMD GPU Metrics
For AMD GPUs, Telegen uses ROCm SMI:
Metric |
Description |
|---|---|
|
GPU utilization |
|
VRAM used |
|
VRAM total |
|
GPU temperature |
|
Power consumption |
|
Fan speed |
Configuration
AMD collection follows the same rule as NVIDIA: it is detected and enabled automatically, with no configuration section.
LLM Inference Metrics
Track LLM inference performance:
Key Metrics
Metric |
Description |
|---|---|
|
Total inference requests |
|
End-to-end request duration |
|
Time to first token (TTFT) |
|
Time between tokens |
|
Total tokens generated |
|
Token generation throughput |
|
Input prompt tokens |
|
Requests waiting in queue |
|
Current batch size |
|
KV cache memory usage |
Example Metrics
# Average time to first token
histogram_quantile(0.95,
sum(rate(llm_time_to_first_token_seconds_bucket[5m])) by (le, model)
)
# Token throughput
sum(rate(llm_tokens_generated_total[5m])) by (model)
# Request rate by model
sum(rate(llm_request_total[5m])) by (model)
# Queue depth
llm_queue_depth{model="llama-3-70b"}
Labels
Label |
Description |
|---|---|
|
Model name/version |
|
Server instance |
|
GPU device index |
LLM API Tracing (eBPF)
Telegen traces LLM API calls at the HTTP layer using eBPF uprobes and generic TCP tracing. This captures requests to OpenAI, Anthropic, Azure OpenAI, and other LLM providers without any SDK changes.
How It Works
HTTP enrichment —
internal/ebpf/common/http/genai_parsers.goparses LLM API request/response payloadsPartial JSON parsing —
internal/ebpf/common/http/partial_json.gohandles streaming JSON responses (SSE/streaming)Payload extraction —
internal/obiconfig/payload_extraction.goextracts token counts, model names, and latency from HTTP bodiesSpan generation — LLM API calls are converted to OTLP spans with GenAI semantic conventions
Captured Attributes
Attribute |
Description |
|---|---|
|
LLM provider (openai, anthropic, azure_openai, etc.) |
|
Model name (gpt-4, claude-3-opus, etc.) |
|
Sampling temperature |
|
Max tokens requested |
|
Response ID |
|
Model used (may differ from request) |
|
Prompt tokens |
|
Completion tokens |
|
Total tokens |
|
stop, length, content_filter, etc. |
|
chat, completion, embedding, etc. |
Sample Span
span:
name: "openai.chat.completions"
kind: CLIENT
duration_ms: 1250
attributes:
gen_ai.system: "openai"
gen_ai.request.model: "gpt-4"
gen_ai.request.temperature: 0.7
gen_ai.usage.input_tokens: 150
gen_ai.usage.output_tokens: 350
gen_ai.usage.total_tokens: 500
gen_ai.response.finish_reason: "stop"
http.request.method: "POST"
url.full: "https://api.openai.com/v1/chat/completions"
Cost Estimation
Telegen calculates estimated API costs per request:
span:
attributes:
gen_ai.usage.input_tokens: 150
gen_ai.usage.output_tokens: 350
gen_ai.cost.input_cost_usd: 0.0003 # $0.50/1M input tokens
gen_ai.cost.output_cost_usd: 0.0035 # $10.00/1M output tokens
gen_ai.cost.total_cost_usd: 0.0038
Configure cost rates in api/config.example.yaml:
aiml:
llm:
cost_rates:
"openai/gpt-4":
input_cost_per_1m: 0.50
output_cost_per_1m: 10.00
"openai/gpt-3.5-turbo":
input_cost_per_1m: 0.50
output_cost_per_1m: 1.50
"anthropic/claude-3-opus":
input_cost_per_1m: 15.00
output_cost_per_1m: 75.00
Streaming Support
TeleMgen traces streaming LLM responses (SSE - Server-Sent Events):
Time-to-first-token (TTFT) is captured from the first SSE chunk
Inter-token latency is calculated from chunk intervals
The span duration covers the entire streaming response
CUDA Kernel Tracing
Telegen traces CUDA operations as spans using eBPF (gpuevent tracer):
Captured Operations
Operation |
Span Name |
Attributes |
|---|---|---|
|
|
grid/block dimensions, shared memory |
|
|
direction (H2D, D2H, D2D), bytes copied |
|
|
bytes allocated |
|
|
bytes freed |
Sample CUDA Span
span:
name: "cuda.kernel.launch"
kind: INTERNAL
duration_ms: 0.05
attributes:
cuda.kernel.name: "matmul_kernel"
cuda.kernel.grid.x: 256
cuda.kernel.grid.y: 1
cuda.kernel.grid.z: 1
cuda.kernel.block.x: 32
cuda.kernel.block.y: 32
cuda.kernel.shared_memory: 4096
gpu.device.id: 0
gpu.device.name: "NVIDIA A100-SXM4-80GB"
OpenTelemetry GenAI Semantic Conventions
Telegen uses the OpenTelemetry GenAI semantic conventions for all LLM spans. This ensures compatibility with GenAI observability backends (LangSmith, Helicone, etc.).
Supported GenAI Attributes
Attribute |
Status |
Description |
|---|---|---|
|
✅ |
LLM provider |
|
✅ |
Model name |
|
✅ |
Sampling temperature |
|
✅ |
Max tokens |
|
✅ |
Response model |
|
✅ |
Input token count |
|
✅ |
Output token count |
|
✅ |
Total token count |
|
✅ |
Completion reason |
|
✅ |
Operation type |
|
🚧 |
Tool/function calls (planned) |
|
🚧 |
Prompt template (planned) |
Model Serving Frameworks
Supported Frameworks
Framework |
Auto-Instrumentation |
|---|---|
vLLM |
✅ Full metrics |
TGI (Text Generation Inference) |
✅ Full metrics |
NVIDIA Triton |
✅ Full metrics |
TensorFlow Serving |
✅ Basic metrics |
TorchServe |
✅ Basic metrics |
ONNX Runtime |
✅ Basic metrics |
vLLM Integration
vLLM is recognised from its serving process, and request duration, time to first
token, tokens per second, KV cache usage, and batch size are collected without
configuration. There is no aiml section to enable or tune it.
Triton Integration
Triton is likewise detected automatically. Its metrics endpoint is discovered from the running server rather than configured.
Training Observability
Monitor ML training jobs:
Metrics
Metric |
Description |
|---|---|
|
Current training loss |
|
Current training step |
|
Current epoch |
|
Current learning rate |
|
Training throughput |
|
GPU utilization during training |
|
Gradient norm |
Configuration
PyTorch, TensorFlow, and JAX training jobs are detected from the running process. Training metrics are exported through the normal metrics pipeline with no dedicated configuration section.
Multi-GPU Monitoring
GPU Labels
All GPU metrics include device labels:
gpu_utilization_percent{
device="0",
name="NVIDIA A100-SXM4-80GB",
uuid="GPU-abc123",
k8s_pod="llm-server-abc"
} 85.5
Multi-Node Training
Track distributed training across nodes:
# Total GPU utilization across all training nodes
sum(gpu_utilization_percent{job="distributed-training"})
# GPU memory per node
gpu_memory_used_bytes{job="distributed-training"} by (node)
# Communication overhead (NCCL)
rate(gpu_nccl_send_bytes_total[5m]) + rate(gpu_nccl_recv_bytes_total[5m])
Alerting Examples
GPU Alerts
groups:
- name: gpu
rules:
- alert: GPUHighTemperature
expr: gpu_temperature_celsius > 85
for: 5m
labels:
severity: warning
annotations:
summary: "GPU {{ $labels.device }} temperature is {{ $value }}°C"
- alert: GPUOutOfMemory
expr: gpu_memory_used_bytes / gpu_memory_total_bytes > 0.95
for: 5m
labels:
severity: warning
annotations:
summary: "GPU {{ $labels.device }} memory is {{ $value | humanizePercentage }}"
- alert: GPULowUtilization
expr: gpu_utilization_percent < 10
for: 30m
labels:
severity: info
annotations:
summary: "GPU {{ $labels.device }} underutilized"
LLM Alerts
groups:
- name: llm
rules:
- alert: LLMHighLatency
expr: histogram_quantile(0.95, rate(llm_request_duration_seconds_bucket[5m])) > 30
for: 5m
labels:
severity: warning
annotations:
summary: "LLM P95 latency is {{ $value | humanizeDuration }}"
- alert: LLMHighQueueDepth
expr: llm_queue_depth > 100
for: 5m
labels:
severity: warning
annotations:
summary: "LLM queue depth is {{ $value }}"
- alert: LLMSlowTTFT
expr: histogram_quantile(0.95, rate(llm_time_to_first_token_seconds_bucket[5m])) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "LLM time-to-first-token P95 is {{ $value | humanizeDuration }}"
Kubernetes GPU Support
NVIDIA GPU Operator
When using NVIDIA GPU Operator in Kubernetes:
# DaemonSet config
spec:
containers:
- name: telegen
resources:
limits:
nvidia.com/gpu: 0 # Don't request GPU, just monitor
volumeMounts:
# Mount NVML socket
- name: nvidia-mps
mountPath: /var/run/nvidia
volumes:
- name: nvidia-mps
hostPath:
path: /var/run/nvidia
MIG (Multi-Instance GPU) Support
Monitor MIG partitions:
gpu_utilization_percent{
device="0",
mig_device="mig-1g.5gb-0",
mig_profile="1g.5gb"
} 75.2
Dashboard Examples
GPU Overview
# GPU fleet summary
sum(gpu_utilization_percent) by (name) / count(gpu_utilization_percent) by (name)
# Memory pressure
sum(gpu_memory_used_bytes) / sum(gpu_memory_total_bytes) * 100
# Power efficiency (tokens per watt)
sum(rate(llm_tokens_generated_total[5m])) / sum(gpu_power_usage_watts)
LLM Performance
# Requests per second
sum(rate(llm_request_total[5m])) by (model)
# Token generation rate
sum(rate(llm_tokens_generated_total[5m])) by (model)
# Latency percentiles
histogram_quantile(0.50, sum(rate(llm_request_duration_seconds_bucket[5m])) by (le, model))
histogram_quantile(0.95, sum(rate(llm_request_duration_seconds_bucket[5m])) by (le, model))
histogram_quantile(0.99, sum(rate(llm_request_duration_seconds_bucket[5m])) by (le, model))
Best Practices
1. Use Per-Process Tracking
Per-process GPU attribution is always on. Use the gpu_process_* metrics to
identify which processes consume GPU resources.
2. Monitor KV Cache
KV cache is critical for LLM performance:
# Alert when KV cache is near capacity
llm_kv_cache_usage_bytes / llm_kv_cache_capacity_bytes > 0.9
3. Correlate with Traces
Inference metrics carry the trace context of the request that produced them, so
they join to spans on trace_id without any extra configuration.
Next Steps
Continuous Profiling - Profile GPU workloads
Agent Mode Configuration - GPU configuration
Monitoring Telegen - GPU dashboards