生产请求追踪#

SGLang 基于 OpenTelemetry Collector 导出请求追踪数据。你可以在启动服务器时通过添加 --enable-trace 来启用追踪,并使用 --otlp-traces-endpoint 配置 OpenTelemetry Collector 端点。

你可以在 https://github.com/sgl-project/sglang/issues/8965 查看可视化效果的示例截图。

设置指南#

本节介绍如何配置请求追踪并导出追踪数据。

  1. 安装所需的软件包和工具

    • 安装 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
    
  2. 启动 OpenTelemetry Collector 和 Jaeger

    docker compose -f examples/monitoring/tracing_compose.yaml up -d
    
  3. 在启用追踪的情况下启动 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
    
  4. 发起一些请求

  5. 观察追踪数据是否正在导出

    • 使用浏览器访问 Jaeger 的 16686 端口,以可视化请求追踪。

    • OpenTelemetry Collector 还会将 JSON 格式的追踪数据导出到 /tmp/otel_trace.json。在后续补丁中,我们将提供一个工具将此数据转换为 Perfetto 兼容格式,从而在 Perfetto UI 中可视化请求。

如何为你感兴趣的代码段添加追踪?#

我们已经在分词器(tokenizer)和调度器(scheduler)主线程中插入了检测点。如果你希望追踪额外的请求执行段或进行更细粒度的追踪,请按如下所述使用 tracing 包中的 API。

  1. 初始化

    在初始化阶段,涉及追踪的每个进程都应执行:

    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)”可以被视为线程的名称,用于在可视化视图中区分不同的线程。

  2. 标记请求的开始和结束

    trace_req_start(rid, bootstrap_room)
    trace_req_finish(rid)
    

    这两个 API 必须在同一个进程中调用,例如在分词器中。

  3. 为代码段添加追踪

    • 普通添加代码段追踪

      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)
      
  4. 当请求执行流转移到另一个线程时,需要显式传播追踪上下文。

    • 发送方:在通过 ZMQ 将请求发送到另一个线程之前执行以下代码

      trace_context = trace_get_proc_propagate_context(rid)
      req.trace_context = trace_context
      
    • 接收方:在通过 ZMQ 接收到请求后执行以下代码

      trace_set_proc_propagate_context(rid, req.trace_context)
      
  5. 当请求执行流转移到另一个节点(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 结构:SglangTraceReqContextSglangTraceThreadContext。它们的关系如下:

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_spanreq_root_spanthread_spanslice_span。其中,req_root_spanthread_span 分别对应 SglangTraceReqContextSglangTraceThreadContext,而 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