본문으로 건너뛰기

[sglang] [SGLang] LingBot 실시간 서빙 최적화: 카메라 컨디셔닝 캐싱과 전송 프로토콜 개선

PR 링크: sgl-project/sglang#27297 상태: Merged | 변경: +462 / -119

들어가며

실시간 비디오 생성 및 제어 모델인 LingBot과 같은 시스템에서 '지연시간(Latency)'은 사용자 경험을 결정짓는 가장 중요한 요소입니다. 특히 Diffusion 모델 기반의 실시간 서빙에서는 매 디노이징 스텝(Denoising Step)마다 발생하는 반복적인 연산과 생성된 고해상도 프레임을 클라이언트에 전송하는 과정이 주요 병목 지점이 됩니다.

최근 SGLang 프로젝트에 반영된 이 PR은 LingBot의 실시간 서빙 성능을 개선하기 위해 두 가지 핵심 영역을 최적화했습니다. 첫째는 모델 내부의 Camera Conditioning 연산 캐싱이고, 둘째는 데이터 전송 프로토콜의 효율화입니다. 이 글에서는 실제 코드 변경 사항을 통해 시니어 엔지니어의 관점에서 어떤 고민이 담겼는지 분석해 보겠습니다.


코드 분석: 핵심 변경 사항

1. Camera Conditioner Scale/Shift 캐싱 (lingbot_world.py)

LingBot은 카메라의 위치 정보(c2ws)를 입력받아 모델의 특징 맵(Feature Map)에 주입합니다. 기존에는 매 디노이징 스텝마다 동일한 카메라 정보에 대해 Linear Layer 연산을 반복했습니다.

Before

def forward(
    self, hidden_states: torch.Tensor, c2ws_plucker_emb: torch.Tensor | None
) -> torch.Tensor:
    # ... 생략 ...
    c2ws_hidden_states = self.cam_injector(c2ws_plucker_emb)
    c2ws_hidden_states = c2ws_hidden_states + c2ws_plucker_emb
    cam_scale = self.cam_scale_layer(c2ws_hidden_states)
    cam_shift = self.cam_shift_layer(c2ws_hidden_states)
    return (1.0 + cam_scale) * hidden_states + cam_shift

After

def _cam_conditioner_scale_shift(
    self,
    c2ws_plucker_emb: torch.Tensor | None,
) -> tuple[torch.Tensor, torch.Tensor] | None:
    # ... 캐시 확인 로직 ...
    source_key = (
        c2ws_plucker_emb.data_ptr(),
        tuple(c2ws_plucker_emb.shape),
        tuple(c2ws_plucker_emb.stride()),
        c2ws_plucker_emb.dtype,
        c2ws_plucker_emb.device.type,
        c2ws_plucker_emb.device.index,
        c2ws_plucker_emb._version,
    )
    if cache.get("source_key") != source_key:
        cache.clear()
        cache["source_key"] = source_key
        cache["scale_shift"] = self.cam_conditioner.compute_scale_shift(c2ws_plucker_emb)
    return cache["scale_shift"]

왜 좋은 최적화인가? 실시간 제어 시나리오에서 카메라 파라미터는 한 프레임을 생성하는 여러 디노이징 스텝 동안 변하지 않습니다. 엔지니어는 data_ptr()_version을 조합한 source_key를 사용하여 텐서의 내용이 변경되지 않았음을 보장하면서, 무거운 Linear 연산 결과를 재사용하도록 설계했습니다. 이는 불필요한 GPU 연산을 줄여 scheduler forward 시간을 단축시킵니다.

2. 전송 레이어: Delta-Gzip에서 Lossless Raw 전송으로의 전환 (realtime_output_adapter.py)

기존에는 대역폭을 아끼기 위해 이전 프레임과의 차이점만 추출하여 Gzip으로 압축하는 delta-gzip 방식을 사용했습니다. 하지만 이는 CPU 부하를 가중시키는 원인이 되었습니다.

Before

elif content_type == RAW_RGB_CONTENT_TYPE and transport_frames:
    raw_payload = build_delta_gzip_raw_rgb_payload(
        transport_frames,
        reference_frame=reference_frame,
    )
    payload_content_type = RAW_RGB_DELTA_GZIP_CONTENT_TYPE
    # ...

After

elif content_type == RAW_RGB_CONTENT_TYPE and transport_frames:
    raw_payload = b"".join(transport_frames)
    payload_metadata = {
        "raw_size": len(raw_payload),
        "encoding": RAW_LOSSLESS_OUTPUT_FORMAT,
    }

왜 좋은 최적화인가? 벤치마크 결과에 따르면 raw payload build 시간이 기존 20~22ms에서 1ms로 급감했습니다. 실시간 시스템에서는 네트워크 대역폭보다 CPU 연산에 의한 지연이 더 큰 병목이 될 수 있음을 보여주는 사례입니다. 특히 H200과 같은 고성능 인프라에서는 네트워크 속도가 충분히 빠르기 때문에, 복잡한 압축 알고리즘을 제거하는 것이 전체 지연시간 단축에 훨씬 유리합니다.

3. 대용량 페이로드의 효율적 전송 (Header/Payload 분리)

대용량 바이너리 데이터를 msgpack으로 인코딩할 때 발생하는 메모리 복사 오버헤드를 줄이기 위해, 헤더와 데이터를 분리하여 전송하는 방식을 도입했습니다.

After

if len(transport_payload.payload) >= FRAME_BATCH_PACK_OFFLOAD_BYTES:
    header_payload = _pack_frame_batch_header(header)
    await ws.send_bytes(header_payload) # 헤더 먼저 전송
    await ws.send_bytes(transport_payload.payload) # 실제 데이터 전송
else:
    message_payload = _pack_frame_batch_message(header, transport_payload.payload)
    await ws.send_bytes(message_payload)

이 방식은 msgpack 라이브러리가 거대한 바이너리 데이터를 처리하며 발생하는 직렬화 오버헤드를 피하고, WebSocket 수준에서 효율적으로 데이터를 스트리밍할 수 있게 합니다.


왜 이게 좋은가: 성능 수치와 교훈

벤치마크 결과 (4xH200 환경)

  • Steady chunk total: 2360 ms → 2114 ms (10.3% 개선)
  • Raw payload build: ~21 ms → ~1 ms
  • Scheduler forward: 2252 ms → 2092 ms (7.1% 개선)

일반적인 교훈

  1. 변하지 않는 컨디셔닝은 캐싱하라: Diffusion 모델에서 텍스트 임베딩이나 카메라 파라미터처럼 스텝 간 고정된 값은 반드시 캐싱하여 중복 연산을 막아야 합니다.
  2. 압축의 트레이드오프를 고려하라: 대역폭 절약보다 CPU 연산 시간이 더 비싼 환경(예: 고속 로컬 네트워크 또는 데이터센터 내부)에서는 단순한 Raw 데이터 전송이 더 빠를 수 있습니다.
  3. Zero-copy 지향: 대용량 데이터를 다룰 때는 전체를 하나의 객체로 묶어 직렬화하기보다, 메타데이터(Header)와 본문(Body)을 분리하여 전송함으로써 메모리 복사와 인코딩 비용을 최소화해야 합니다.

이 PR은 단순히 코드를 깔끔하게 만드는 것을 넘어, 실제 서빙 환경에서의 병목을 정확히 짚어내고 데이터 기반으로 최적화를 수행한 훌륭한 사례입니다.

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글