生产环境请求追踪#

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

您可以在 https://github.com/sgl-project/sglang/issues/8965 中找到可视化的示例截图。

设置指南#

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

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

    • 安装 Docker 和 Docker Compose

    • 安装依赖项

    # 进入 SGLang 根目录
    pip install -e "python[tracing]"
    
    # 或手动使用 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 服务器

    # 设置环境变量
    export SGLANG_OTLP_EXPORTER_SCHEDULE_DELAY_MILLIS=500
    export SGLANG_OTLP_EXPORTER_MAX_EXPORT_BATCH_SIZE=64
    # 启动 prefill 和 decode 服务器
    python -m sglang.launch_server --enable-trace --otlp-traces-endpoint 0.0.0.0:4317 <other option>
    # 启动 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. 观察是否正在导出追踪数据

    • 使用 Web 浏览器访问 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 必须在同一进程中调用,例如在 tokenizer 中。

  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。对于处理请求的每个线程,在 SglangTraceReqContext 中记录并组合一个对应的 SglangTraceThreadContext。在每个线程中,每个当前被追踪的片段(可能是嵌套的)存储在一个列表中。

除了上述层次结构外,每个片段还通过 Span.add_link() 记录其前一个片段,这可用于追踪执行流程。

当请求执行流程转移到新线程时,需要显式传播追踪上下文。在框架中,这由 SglangTracePropagateContext 表示,它包含请求 span 的上下文和前一个 slice span 的上下文。

我们设计了四级 span 结构,包括 bootstrap_room_spanreq_root_spanthread_spanslice_span。其中,req_root_spanthread_span 分别对应 SglangTraceReqContextSglangTraceThreadContextslice_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