生产请求追踪#
SGLang 基于 OpenTelemetry Collector 导出请求追踪数据。你可以在启动服务器时通过添加 --enable-trace 来启用追踪,并使用 --otlp-traces-endpoint 配置 OpenTelemetry Collector 端点。
你可以在 https://github.com/sgl-project/sglang/issues/8965 查看可视化效果的示例截图。
设置指南#
本节介绍如何配置请求追踪并导出追踪数据。
安装所需的软件包和工具
安装 Docker 和 Docker Compose
安装依赖项
# enter the SGLang root directory pip install -e "python[tracing]" # or manually install the dependencies using pip pip install opentelemetry-sdk opentelemetry-api opentelemetry-exporter-otlp opentelemetry-exporter-otlp-proto-grpc
启动 OpenTelemetry Collector 和 Jaeger
docker compose -f examples/monitoring/tracing_compose.yaml up -d
在启用追踪的情况下启动 SGLang 服务器
# set env variables export SGLANG_OTLP_EXPORTER_SCHEDULE_DELAY_MILLIS=500 export SGLANG_OTLP_EXPORTER_MAX_EXPORT_BATCH_SIZE=64 # start the prefill and decode server python -m sglang.launch_server --enable-trace --otlp-traces-endpoint 0.0.0.0:4317 <other option> # start the mini lb python -m sglang_router.launch_router --enable-trace --otlp-traces-endpoint 0.0.0.0:4317 <other option>
将
0.0.0.0:4317替换为 OpenTelemetry Collector 的实际端点。如果你使用 tracing_compose.yaml 启动 OpenTelemetry Collector,默认接收端口为 4317。要使用 HTTP/protobuf span 导出器,请设置以下环境变量并指向 HTTP 端点,例如
http://0.0.0.0:4318/v1/traces。export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
发起一些请求
观察追踪数据是否正在导出
使用浏览器访问 Jaeger 的 16686 端口,以可视化请求追踪。
OpenTelemetry Collector 还会将 JSON 格式的追踪数据导出到 /tmp/otel_trace.json。在后续补丁中,我们将提供一个工具将此数据转换为 Perfetto 兼容格式,从而在 Perfetto UI 中可视化请求。
如何为你感兴趣的代码段添加追踪?#
我们已经在分词器(tokenizer)和调度器(scheduler)主线程中插入了检测点。如果你希望追踪额外的请求执行段或进行更细粒度的追踪,请按如下所述使用 tracing 包中的 API。
初始化
在初始化阶段,涉及追踪的每个进程都应执行:
process_tracing_init(otlp_traces_endpoint, server_name)
otlp_traces_endpoint 从参数中获取,你可以自由设置 server_name,但它在所有进程中应保持一致。
在初始化阶段,涉及追踪的每个线程都应执行:
trace_set_thread_info("thread label", tp_rank, dp_rank)
“线程标签(thread label)”可以被视为线程的名称,用于在可视化视图中区分不同的线程。
标记请求的开始和结束
trace_req_start(rid, bootstrap_room) trace_req_finish(rid)
这两个 API 必须在同一个进程中调用,例如在分词器中。
为代码段添加追踪
普通添加代码段追踪
trace_slice_start("slice A", rid) trace_slice_end("slice A", rid)
使用 “anonymous” 标志,在代码段开始时不指定名称,允许通过 trace_slice_end 确定代码段名称。
注意:匿名代码段不得嵌套。trace_slice_start("", rid, anonymous = True) trace_slice_end("slice A", rid)
在 trace_slice_end 中,使用 auto_next_anon 自动创建下一个匿名代码段,这可以减少所需的检测点数量。
trace_slice_start("", rid, anonymous = True) trace_slice_end("slice A", rid, auto_next_anon = True) trace_slice_end("slice B", rid, auto_next_anon = True) trace_slice_end("slice C", rid, auto_next_anon = True) trace_slice_end("slice D", rid)
线程中最后一个代码段的结束必须标记为 thread_finish_flag=True;否则,该线程的 span 将无法正确生成。
trace_slice_end("slice D", rid, thread_finish_flag = True)
当请求执行流转移到另一个线程时,需要显式传播追踪上下文。
发送方:在通过 ZMQ 将请求发送到另一个线程之前执行以下代码
trace_context = trace_get_proc_propagate_context(rid) req.trace_context = trace_context
接收方:在通过 ZMQ 接收到请求后执行以下代码
trace_set_proc_propagate_context(rid, req.trace_context)
当请求执行流转移到另一个节点(PD 分离)时,需要显式传播追踪上下文。
发送方:在通过 http 将请求发送到节点线程之前执行以下代码
trace_context = trace_get_remote_propagate_context(bootstrap_room_list) headers = {"trace_context": trace_context} session.post(url, headers=headers)
接收方:在通过 http 接收到请求后执行以下代码
trace_set_remote_propagate_context(request.headers['trace_context'])
如何扩展追踪框架以支持复杂的追踪场景#
目前提供的追踪包仍有进一步开发的潜力。如果你希望在其基础上构建更高级的功能,必须首先了解其现有的设计原则。
追踪框架实现的核心在于 span 结构和追踪上下文的设计。为了聚合分散的代码段并实现多个请求的并发跟踪,我们设计了两级追踪上下文结构和四级 span 结构:SglangTraceReqContext、SglangTraceThreadContext。它们的关系如下:
SglangTraceReqContext (req_id="req-123")
├── SglangTraceThreadContext(thread_label="scheduler", tp_rank=0)
|
└── SglangTraceThreadContext(thread_label="scheduler", tp_rank=1)
每个被追踪的请求都维护一个全局的 SglangTraceReqContext。对于处理该请求的每个线程,都会记录一个相应的 SglangTraceThreadContext 并组合在 SglangTraceReqContext 中。在每个线程内,当前追踪的每个代码段(可能是嵌套的)都存储在一个列表中。
除了上述层级结构外,每个代码段还通过 Span.add_link() 记录其前一个代码段,这可用于追踪执行流。
当请求执行流转移到新线程时,需要显式传播追踪上下文。在框架中,这由 SglangTracePropagateContext 表示,它包含请求 span 和前一个代码段 span 的上下文。
我们设计了四级 span 结构,包括 bootstrap_room_span、req_root_span、thread_span 和 slice_span。其中,req_root_span 和 thread_span 分别对应 SglangTraceReqContext 和 SglangTraceThreadContext,而 slice_span 存储在 SglangTraceThreadContext 中。bootstrap_room_span 旨在适应 PD 分离。在不同节点上,我们可能希望为 req_root_span 添加某些属性。但是,如果 req_root_span 在所有节点间共享,由于 OpenTelemetry 设计的限制,Prefill 和 Decode 节点将不被允许添加属性。
bootstrap room span
├── router req root span
| └── router thread span
| └── slice span
├── prefill req root span
| ├── tokenizer thread span
| | └── slice span
| └── scheduler thread span
| └── slice span
└── decode req root span
├── tokenizer thread span
| └── slice span
└── scheduler thread span
└── slice span