본문으로 건너뛰기

[SGLang] Observability: 추적, 메트릭, 프로파일링 인프라

들어가며

프로덕션 LLM 서빙에서는 성능 병목, 지연 원인, 리소스 사용량을 실시간으로 파악해야 한다. SGLang은 observability/ 패키지를 통해 OpenTelemetry 추적, Prometheus 메트릭, 함수 레이턴시 측정, CPU 모니터링 등의 관측 도구를 제공한다.

구조도

observability/
├── trace.py                    ── OpenTelemetry 분산 추적
├── metrics_collector.py        ── Prometheus 메트릭 수집
├── func_timer.py               ── 함수 레이턴시 히스토그램
├── cpu_monitor.py              ── CPU 사용량 모니터링
├── req_time_stats.py           ── 요청별 시간 통계
├── request_metrics_exporter.py ── 메트릭 파일 내보내기
├── scheduler_metrics_mixin.py  ── 스케줄러 메트릭 믹스인
├── startup_func_log_and_timer.py ── 시작 시 로깅/타이머
├── label_transform.py          ── 메트릭 레이블 변환
└── utils.py                    ── 유틸리티 (버킷 생성 등)

핵심 코드 분석

OpenTelemetry 추적 (trace.py)

trace.py는 OpenTelemetry를 사용한 분산 추적을 구현한다. gRPC와 HTTP 양쪽 프로토콜을 지원한다.

try:
    from opentelemetry import context, propagate, trace
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
        OTLPSpanExporter as GRPCSpanExporter)
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
        OTLPSpanExporter as HTTPSpanExporter)
    from opentelemetry.sdk.trace import TracerProvider, id_generator
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    opentelemetry_imported = True
except ImportError:
    logger.debug("opentelemetry package is not installed, tracing disabled")

추적 컨텍스트는 W3C TraceContext 형식(traceparent, tracestate)을 따른다.

TRACE_HEADERS = ["traceparent", "tracestate"]

def extract_trace_headers(headers: Mapping[str, str]) -> Optional[Dict]:
    return {h: headers[h] for h in TRACE_HEADERS if h in headers}

추적 이벤트와 슬라이스를 구조화한 데이터클래스도 제공한다.

@dataclass
class TraceSliceContext:
    slice_name: str
    start_time_ns: int
    end_time_ns: Optional[int] = None
    span: Optional[trace.span.Span] = None

Prometheus 메트릭 수집 (metrics_collector.py)

SchedulerStats는 스케줄러의 핵심 지표를 수집한다. 실행 중 요청 수, 토큰 사용량, 캐시 히트율, 처리량 등을 추적한다.

@dataclass
class SchedulerStats:
    num_running_reqs: QueueCount = field(default_factory=QueueCount)
    num_used_tokens: int = 0
    token_usage: float = 0.0
    gen_throughput: float = 0.0
    num_queue_reqs: QueueCount = field(default_factory=QueueCount)
    cache_hit_rate: float = 0.0
    max_total_num_tokens: int = 0
    spec_accept_length: float = 0.0
    spec_accept_rate: float = 0.0

우선순위 스케줄링이 활성화된 경우 우선순위별 요청 수도 추적한다.

@dataclass
class QueueCount:
    total: int = 0
    by_priority: Optional[Dict[int, int]] = None

    @classmethod
    def from_reqs(cls, reqs, enable_priority_scheduling=False):
        by_priority = (
            dict(Counter(req.priority for req in reqs))
            if enable_priority_scheduling else None)
        return cls(total=len(reqs), by_priority=by_priority)

함수 레이턴시 타이머 (func_timer.py)

time_func_latency 데코레이터는 동기/비동기 함수의 실행 시간을 Prometheus Histogram으로 기록한다.

def enable_func_timer():
    global enable_metrics, FUNC_LATENCY
    enable_metrics = True
    FUNC_LATENCY = Histogram(
        "sglang:func_latency_seconds",
        "Function latency in seconds",
        buckets=exponential_buckets(start=0.05, width=1.5, length=18),
        labelnames=["name"],
    )

동기와 비동기 함수를 모두 지원한다.

def time_func_latency(func=None, name=None):
    def measure(func):
        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            if not enable_metrics:
                return await func(*args, **kwargs)
            start = time.monotonic()
            ret = await func(*args, **kwargs)
            FUNC_LATENCY.labels(name=name).observe(time.monotonic() - start)
            return ret

        @wraps(func)
        def sync_wrapper(*args, **kwargs):
            if not enable_metrics:
                return func(*args, **kwargs)
            start = time.monotonic()
            ret = func(*args, **kwargs)
            FUNC_LATENCY.labels(name=name).observe(time.monotonic() - start)
            return ret

메트릭이 비활성화되면 오버헤드 없이 원래 함수를 직접 호출한다.

CPU 모니터링 (cpu_monitor.py)

별도 데몬 스레드에서 psutil을 사용하여 CPU 시간을 주기적으로 측정한다.

def start_cpu_monitor_thread(component: str, interval: float = 5.0):
    cpu_seconds_total = Counter(
        name="sglang:process_cpu_seconds_total",
        documentation="Total CPU time consumed by this process",
        labelnames=["component"],
    )
    def monitor():
        process = psutil.Process()
        last_times = process.cpu_times()
        while True:
            time.sleep(interval)
            curr_times = process.cpu_times()
            delta = (curr_times.user - last_times.user) + \
                    (curr_times.system - last_times.system)
            cpu_seconds_total.labels(component=component).inc(delta)
            last_times = curr_times
    t = threading.Thread(target=monitor, daemon=True)
    t.start()

활성화 방법

기능 서버 인자 설명
Prometheus 메트릭 --enable-metrics 스케줄러/요청 통계
OpenTelemetry 추적 --enable-trace 분산 추적
OTLP 엔드포인트 --otlp-traces-endpoint 기본: localhost:4317
메트릭 HTTP 포트 --metrics-http-port Prometheus scrape 포트
파일 내보내기 --export-metrics-to-file 메트릭을 파일로 저장

관련 포스트

  • Server Args: 300+ 서버 인자 완전 가이드
  • Debug Utils: 텐서 비교, 스케줄 시뮬레이터

참고

댓글

관련 포스트

SGLang 의 다른글