Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

233 lines
9.4 KiB
Python

"""Unit tests for OTel trace-context propagation across the diffusion disagg
JSON hop (encoder -> denoiser, denoiser -> decoder).
These exercise the serialization contract only (no GPUs, no server, no OTLP
collector required):
- ``extract_transfer_fields`` emits a JSON-safe ``_trace_state`` (W3C carrier)
when tracing is enabled, and omits it when tracing is disabled. It never
serializes the live ``TraceReqContext`` object itself.
- The ``_trace_state`` payload round-trips through ``codec.pack_tensors``
(the same ``json.dumps`` path the RDMA metadata frame uses).
- ``TraceReqContext.__setstate__`` reconstructs a live, ``is_copy=True``
context whose ``root_span_context`` is an OTel ``Context`` object.
- ``_build_disagg_req`` pops ``_trace_state`` and installs a rebuilt
``TraceReqContext`` on the receiver-side Req.
"""
from __future__ import annotations
import json
import unittest
from contextlib import contextmanager
from types import SimpleNamespace
import torch
from sglang.multimodal_gen.runtime.disaggregation.scheduler_mixin import (
SchedulerDisaggMixin,
extract_transfer_fields,
)
from sglang.multimodal_gen.runtime.disaggregation.transport.codec import pack_tensors
from sglang.multimodal_gen.runtime.pipelines_core import Req
from sglang.srt import server_args as srt_server_args_module
from sglang.srt.observability import trace as srt_trace
from sglang.srt.observability.trace import TraceNullContext, TraceReqContext
from sglang.srt.runtime_context import reset_context
from sglang.srt.server_args import set_global_server_args_for_scheduler
try:
from opentelemetry import propagate as otel_propagate
from opentelemetry import trace as otel_trace
from opentelemetry.sdk.trace import TracerProvider
_OTEL_AVAILABLE = True
except ImportError:
_OTEL_AVAILABLE = False
_OTEL_BOOTSTRAPPED = False
def _enable_minimal_otel() -> None:
"""Bootstrap just enough OTel state for TraceReqContext to produce real
spans. Idempotent — the TracerProvider can only be set once per process."""
global _OTEL_BOOTSTRAPPED
if not _OTEL_BOOTSTRAPPED:
otel_trace.set_tracer_provider(TracerProvider())
_OTEL_BOOTSTRAPPED = True
srt_trace.opentelemetry_initialized = True
srt_trace.tracer = otel_trace.get_tracer("test-diffusion-disagg")
srt_trace.trace_set_thread_info("TestThread")
@contextmanager
def _srt_trace_server_args():
try:
prev_server_args = srt_server_args_module.get_global_server_args()
except ValueError: # nothing published yet
prev_server_args = None
set_global_server_args_for_scheduler(SimpleNamespace(trace_modules="request"))
try:
yield
finally:
if prev_server_args is None:
reset_context()
else:
set_global_server_args_for_scheduler(prev_server_args)
def _traceparent_from(ctx) -> str | None:
"""Re-inject a W3C carrier from an OTel Context and return the traceparent.
Used to assert that a carrier round-trip preserves trace_id/span_id, which
is the actual correctness property (OTel's Context is a dict subclass, so
``isinstance(ctx, dict)`` isn't useful).
"""
carrier: dict = {}
otel_propagate.inject(carrier, ctx)
return carrier.get("traceparent")
def _roundtrip_scalar_fields(scalar_fields: dict) -> dict:
"""Run the actual RDMA metadata codec path: pack -> json bytes -> decode."""
metadata_bytes, _ = pack_tensors({}, scalar_fields)
decoded = json.loads(metadata_bytes.decode("utf-8"))
return decoded["scalar_fields"]
class TestDisaggTracePropagation(unittest.TestCase):
def test_transfer_keeps_seed_needed_to_rebuild_generator(self):
req = Req(request_id="test-seed", prompt="x")
req.generator = torch.Generator(device="cpu").manual_seed(req.seed)
_, scalar_fields = extract_transfer_fields(req)
self.assertEqual(scalar_fields["seed"], 42)
rebuilt = SchedulerDisaggMixin._build_disagg_req(None, dict(scalar_fields), {})
self.assertIsInstance(rebuilt.generator, torch.Generator)
self.assertEqual(rebuilt.seed, 42)
expected = torch.rand(
(), generator=torch.Generator(device="cpu").manual_seed(42)
)
actual = torch.rand((), generator=rebuilt.generator)
self.assertEqual(actual.item(), expected.item())
def test_build_disagg_req_rebuilds_generator_list(self):
scalar_fields = {
"request_id": "test-seed-list",
"prompt": "x",
"num_outputs_per_prompt": 2,
"seed": [11, 12],
}
rebuilt = SchedulerDisaggMixin._build_disagg_req(None, dict(scalar_fields), {})
self.assertEqual(rebuilt.seed, [11, 12])
self.assertEqual(len(rebuilt.generator), 2)
for seed, generator in zip(rebuilt.seed, rebuilt.generator):
expected = torch.rand(
(), generator=torch.Generator(device="cpu").manual_seed(seed)
)
actual = torch.rand((), generator=generator)
self.assertEqual(actual.item(), expected.item())
def test_tracing_disabled_omits_trace_state(self):
"""With a default TraceNullContext Req, no _trace_state is emitted and
the JSON codec does not encounter any live OTel objects."""
req = Req(request_id="test-off", prompt="x")
self.assertIsInstance(req.trace_ctx, TraceNullContext)
_, scalar_fields = extract_transfer_fields(req)
self.assertNotIn("_trace_state", scalar_fields)
# trace_ctx must never ride the JSON scalar path.
self.assertNotIn("trace_ctx", scalar_fields)
# json.dumps must succeed (this is the path that pre-fix crashed).
_roundtrip_scalar_fields(scalar_fields)
@unittest.skipUnless(_OTEL_AVAILABLE, "opentelemetry SDK not installed")
def test_tracing_enabled_state_roundtrip(self):
"""Sender emits a W3C-carrier _trace_state, it round-trips through
json encode/decode, and __setstate__ reconstructs a live is_copy=True
TraceReqContext with an OTel Context (not the raw dict)."""
_enable_minimal_otel()
with _srt_trace_server_args():
ctx = TraceReqContext(rid="test-on", role="server", module_name="request")
ctx.trace_req_start()
self.assertTrue(ctx.tracing_enable)
self.assertFalse(ctx.is_copy)
req = Req(request_id="test-on", prompt="x")
req.trace_ctx = ctx
_, scalar_fields = extract_transfer_fields(req)
self.assertNotIn("trace_ctx", scalar_fields)
self.assertIn("_trace_state", scalar_fields)
state = scalar_fields["_trace_state"]
self.assertTrue(state.get("tracing_enable"))
# W3C carrier must be present so downstream roles can nest spans.
self.assertIn("traceparent", state.get("root_span_context", {}))
decoded = _roundtrip_scalar_fields(scalar_fields)
self.assertEqual(decoded["_trace_state"], state)
rebuilt = object.__new__(TraceReqContext)
rebuilt.__setstate__(decoded["_trace_state"])
self.assertTrue(rebuilt.tracing_enable)
self.assertTrue(rebuilt.is_copy)
# The sender's traceparent must survive into the rebuilt Context so
# downstream role spans nest under the original trace_id.
self.assertEqual(
_traceparent_from(rebuilt.root_span_context),
state["root_span_context"]["traceparent"],
)
ctx.trace_req_finish()
@unittest.skipUnless(_OTEL_AVAILABLE, "opentelemetry SDK not installed")
def test_build_disagg_req_installs_rebuilt_ctx(self):
"""_build_disagg_req pops _trace_state from scalar_fields and installs
a live TraceReqContext on the rebuilt Req; the key does not leak onto
the Req as a stray attribute."""
_enable_minimal_otel()
with _srt_trace_server_args():
ctx = TraceReqContext(rid="test-brq", role="server", module_name="request")
ctx.trace_req_start()
req = Req(request_id="test-brq", prompt="x")
req.trace_ctx = ctx
_, scalar_fields = extract_transfer_fields(req)
self.assertIn("_trace_state", scalar_fields)
# _build_disagg_req is an instance method but its body does not
# touch ``self``; call via __func__ to avoid needing a real Scheduler.
rebuilt = SchedulerDisaggMixin._build_disagg_req(
None, dict(scalar_fields), {}
)
self.assertIsInstance(rebuilt.trace_ctx, TraceReqContext)
self.assertTrue(rebuilt.trace_ctx.tracing_enable)
self.assertTrue(rebuilt.trace_ctx.is_copy)
self.assertFalse(hasattr(rebuilt, "_trace_state"))
ctx.trace_req_finish()
@unittest.skipUnless(_OTEL_AVAILABLE, "opentelemetry SDK not installed")
def test_build_disagg_req_falls_back_when_tracing_off(self):
"""If the sender's context is a TraceNullContext, the receiver's Req
keeps its default TraceNullContext (no _trace_state to apply)."""
req = Req(request_id="test-brq-off", prompt="x")
self.assertIsInstance(req.trace_ctx, TraceNullContext)
_, scalar_fields = extract_transfer_fields(req)
self.assertNotIn("_trace_state", scalar_fields)
rebuilt = SchedulerDisaggMixin._build_disagg_req(None, dict(scalar_fields), {})
self.assertIsInstance(rebuilt.trace_ctx, TraceNullContext)
if __name__ == "__main__":
unittest.main()