RL 시스템을 위한 SGLang

RL 시스템을 위한 SGLang (SGLang for RL Systems)

이 문서는 SGLang을 RL·포스트트레이닝 시스템에 통합하는 인프라 팀을 위한 실용 가이드입니다. 루프 안의 운영상 병목 지점(롤아웃, 평가, 훈련, 가중치 동기화)을 구체적인 SGLang API·플래그·통합 패턴으로 연결해 줍니다. 롤아웃 효율·정확도·안정성을 최대화하면서 롤아웃-서빙 동작을 프로덕션 환경에서 일관되게 유지하는 데 초점을 둡니다.

출처: 문서

본문

왜 RL 수명주기에 SGLang인가? (Why SGLang for RL Lifecycle?)

초기 DeepMind의 RL 엔지니어링에서 나온 지침 원칙을 받아들입시다:

라이브러리가 되어라, 프레임워크가 되지 마라. (Be a library, not a framework.)

이 철학은 SGLang을 경직된 구조가 아닌 유연한 도구로 제공해 혁신을 가능하게 합니다. RL 수명주기에 SGLang을 사용해야 하는 다섯 가지 이유:

  • 세밀한 엔진 수면·기상 (Fine-Grained Engine Sleep and Wake Up): 최대 전력의 롤아웃·훈련을 지원
  • 개방형 리핏(refit) 기능 (Open-To-Use Refit Functionality): 공동 배치 또는 분리(disaggregation)를 위한 다양한 방법
  • 생성을 쉽게 연기 (Easy To Postpone Generation): 부분 롤아웃과 전용 롤아웃 제어를 가능하게 함
  • 결정적 추론 (Deterministic Inference): 훈련-추론 불일치를 없애기 위한 결정적 추론
  • 로드 밸런싱 라우터 (Load Balancing Router): 고처리량 롤아웃을 위한 캐시 인지 로드 밸런싱

다음 섹션에서 이 측면들을 자세히 다룹니다.

세밀한 엔진 수면·기상 (Fine-Grained Engine Sleep and Wake Up)

롤아웃과 훈련은 모두 메모리 집약적이며, 같은 GPU에 공동 배치하면 메모리 압박과 느린 핸드오프가 발생하기 쉽습니다. SGLang은 서버 프로세스를 유지한 채 KV 캐시와 가중치를 해제하는 메모리 인지 수면·기상 메커니즘을 제공하고, 전체 재시작 없이 롤아웃을 위해 이를 재개합니다. 이렇게 하면 각 RL 단계에서 반복적인 디스크 I/O와 CUDA 그래프 재캡처를 피할 수 있습니다.

내부적으로 RL 팀은 torch_memory_saver를 통한 CUDA-그래프 인지 가중치 오프로드를 사용해 그래프 재생을 위한 가상 메모리 주소를 보존합니다. 자세한 내용은 Efficient RL Training - Optimizing Memory Usage in verl을 참고하세요.

서버 플래그 (Server flag)

서버 시작 시 메모리 세이버 지원을 활성화합니다:

--enable-memory-saver

메모리 해제 (Release Memory)

엔드포인트: POST /release_memory_occupation

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
tags 해제할 메모리 영역. 생략하면 전부 해제. None 타입: list[str], 값: kv_cache, weights

동작 참고:

  • 이 호출은 진행 중인 요청이 없음을 전제합니다. 호출 전에 엔진이 유휴 상태인지 확인하세요.
  • kv_cache가 해제되면 SGLang은 캐시를 플러시하고, 이후 요청은 필요에 따라 KV 캐시를 재구축합니다.

메모리 재개 (Resume Memory)

엔드포인트: POST /resume_memory_occupation

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
tags 재개할 메모리 영역. 생략하면 전부 재개. None 타입: list[str], 값: kv_cache, weights

개방형 리핏 기능 (Open-To-Use Refit Functionality)

각 단계의 훈련이 끝나면 롤아웃 엔진을 새 가중치로 리핏해야 합니다. SGLang은 세 가지 리핏 전략을 지원하므로 인프라 스타일(공동 배치 vs 분리)과 확장 요구에 맞출 수 있습니다. 각 전략은 명확한 요청 스키마를 가진 구체적인 API에 매핑됩니다. SGLang의 가중치 업데이트 유틸리티에 대한 자세한 내용은 RL System Deep Thinking: Weight Update Mechanisms을 참고하세요.

선택 방법:

  • 디스크에서 (From disk) 가장 단순하며 탄력적 롤아웃 확장과 체크포인트에 적합.
  • 텐서에서 (From tensor) 인메모리 텐서를 전달할 수 있는 공동 배치 훈련/롤아웃에 적합.
  • 분산에서 (From distributed) 전용 통신 그룹(NCCL/IB)을 사용하는 분리 훈련/롤아웃에 적합.

디스크에서 가중치 업데이트 (Update Weights from Disk)

사용 시점:

  • 체크포인트를 디스크에 저장하고 디스크에서 가중치를 업데이트
  • 동적 확장(새 롤아웃 인스턴스가 같은 체크포인트에서 로드 가능)

잘 동작하는 이유:

이 경로는 일부 I/O 오버헤드를 단순성·유연성과 맞바꿉니다. 체크포인팅과 자연스럽게 통합되며 새 롤아웃 엔진을 추가하는 것이 간단해집니다. 같은 체크포인트를 가리키고 API를 호출하기만 하면 됩니다. 체크포인트 자체가 소스 오브 트루스이므로 고가용성에 가장 안전한 옵션이기도 합니다.

엔드포인트: POST /update_weights_from_disk

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
model_path 새 가중치가 있는 모델 경로. 필수 타입: str
load_format 가중치를 로드할 형식. None 타입: str
abort_all_requests 업데이트 전에 실행 중인 모든 요청을 중단. False 타입: bool
weight_version 서버가 추적하는 선택적 가중치 버전 라벨. None 타입: str
is_async 가중치 로드를 비동기로 수행. False 타입: bool
torch_empty_cache torch 캐시 비우기. False 타입: bool
keep_pause 업데이트 후 스케줄러를 일시정지 상태로 유지. False 타입: bool
recapture_cuda_graph 업데이트 후 CUDA 그래프 재캡처. False 타입: bool
token_step 롤아웃 부기용 트레이너 단계 id. 0 타입: int
flush_cache 업데이트 후 KV 캐시 플러시. True 타입: bool

응답 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
success 업데이트 성공 여부. - 타입: bool
message 상태/오류 메시지. - 타입: str
num_paused_requests 업데이트 중 일시정지된 요청 수. 0 타입: int

Python 엔진 API: engine.update_weights_from_disk(model_path, load_format=None)

디퓨전 엔진 (SGLang-Diffusion): 디퓨전 엔진은 동일한 POST /update_weights_from_disk 엔드포인트를 다음 동작으로 노출합니다:

  • 전부-또는-무-롤백 (All-or-nothing with rollback): 모듈 하나라도 로드에 실패하면 이전에 업데이트된 모든 모듈이 원래 모델 경로에서 다시 로드되어 원래 가중치로 롤백됩니다. 부분 업데이트가 남지 않습니다. 롤백 자체가 실패하면 예외가 전파되어 호출자가 모델이 불일치 상태임을 알 수 있습니다.
  • 오프로드 인지 (Offload-aware): 레이어별 오프로드(--dit-layerwise-offload)가 활성화되면 디퓨전 오프로드 매니저는 실제 가중치가 통합된 고정 CPU 버퍼에 있는 동안 GPU 파라미터를 작은 torch.empty((1,)) 자리표시자로 대체합니다. 순진한 param.data.copy_()는 형태 불일치로 실패합니다. 대신 업데이터가 활성 오프로드 매니저를 동적으로 감지해 자리표시자를 완전히 우회하고 새 가중치를 CPU 버퍼에 직접 기록합니다. 업데이트 시점에 우연히 GPU에 프리페치된 레이어는 라이브 GPU 텐서도 업데이트되어 변경이 즉시 적용됩니다. 이는 추가 GPU 메모리를 요구하지 않으며 오프로드 상태를 방해하지 않습니다.
  • DTensor 인지 (DTensor-aware): torch.distributed.tensor(텐서 병렬)로 분산된 파라미터는 distribute_tensor를 통해 업데이트되어 각 샤드가 올바른 디바이스 메시에 배치됩니다.

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
model_path 새 가중치가 있는 모델 경로. 필수 타입: str
flush_cache 업데이트 후 TeaCache 상태 플러시. True 타입: bool
target_modules 업데이트할 모듈 이름 목록(예: ["transformer"]). 생략하면 모든 nn.Module 컴포넌트를 업데이트. None 타입: list[str]

응답 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
success 업데이트 성공 여부. - 타입: bool
message 상태/오류 메시지. - 타입: str

참고: 디퓨전 엔진(SGLang-Diffusion)은 현재 핫 리핏(추론 진행 중 가중치 업데이트)을 지원하지 않습니다. 디퓨전 스케줄러는 한 번에 하나의 요청을 처리하고 다음 요청을 처리하기 전에 전체 추론을 완료하므로 가중치 업데이트와 추론이 동시에 실행되지 않습니다.

가중치 업데이트 세션 (Weight Update Session)

update_weights_from_tensorupdate_weights_from_distributed는 가중치 업데이트 세션 안에서 실행해야 합니다. 업데이트 호출은 체크포인트 형식 값을만 기록합니다. end_weight_update는 각 양자화 레이어의 process_weights_after_loading을 한 번 실행해 커널 레이아웃(Marlin repack, MXFP8 shuffle 등)으로 바꾸고, 세션 중 엔진을 통해 로드된 가중치가 없으면(P2P/RDMA 쓰기) post_load_weights를 실행합니다. begin_weight_update는 먼저 커널 레이아웃이 파라미터 형태를 바꾸는 몇 가지 방식(현재 Marlin의 compressed-tensors W4A16 MoE)을 복원해 체크포인트 형식 값이 맞도록 합니다. 하나의 세션이 많은 업데이트 호출을 포함할 수 있으므로 최종화 비용은 버킷당이 아니라 리핏당 한 번만 지불합니다. 세션 밖의 호출, 두 번째 begin_weight_update, 또는 세션 없이 end_weight_updatesuccess: false(HTTP 400)로 거부되며 가중치를 건드리지 않습니다.

엔드포인트: POST /begin_weight_update, POST /end_weight_update

begin_weight_updateselector: "target", "draft", "all"(기본값)을 받습니다. 추측 디코딩 아래에서는 업데이트 엔드포인트의 같은 셀렉터가 어떤 모델 러너가 가중치를 받을지 선택하고, end_weight_update가 세션이 연 러너들을 최종화합니다.

engine.begin_weight_update()
for bucket in buckets:
    engine.update_weights_from_tensor(bucket)
engine.end_weight_update()

텐서에서 가중치 업데이트 (Update Weights from Tensor)

사용 시점:

  • 훈련이 텐서를 직접 제공할 수 있는 공동 배치 훈련·롤아웃
  • 빠른 인메모리 업데이트

중요 제약:

이 전략은 훈련 프로세스와 롤아웃 엔진이 텐서에 대한 접근을 공유해야 합니다. 공동 배치 설정은 모델을 GPU에 유지해야 합니다. 텐서를 CPU로 옮기면 업데이트 경로가 깨집니다. 고성능 MoE나 특수 어텐션 커널의 경우 공동 배치는 분리 롤아웃에 비해 일부 최적화를 제한할 수 있습니다.

엔드포인트: POST /update_weights_from_tensor

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
serialized_named_tensors TP별 직렬화된 텐서 페이로드. 필수 타입: list[str|bytes]
load_format 선택적 로드 형식 셀렉터. None None, direct, flattened_bucket 또는 커스텀 로더 경로 문자열
flush_cache 업데이트 후 KV 캐시 플러시. True 타입: bool
abort_all_requests 업데이트 전에 실행 중인 모든 요청 중단. False 타입: bool
weight_version 서버가 추적하는 선택적 버전 라벨. None 타입: str

참고: 직렬화된 텐서 페이로드는 MultiprocessingSerializer.serialize(...)로 생성해야 하며 base64 안전 문자열이어야 합니다.

Python 엔진 API: engine.update_weights_from_tensor(named_tensors, load_format=None, flush_cache=True), begin_weight_update() / end_weight_update() 안에서 호출.

분산 그룹에서 가중치 업데이트 (Update Weights from Distributed Group)

사용 시점:

  • 분리 훈련·롤아웃
  • 훈련 워커에서 롤아웃 워커로의 NCCL·IB 기반 가중치 브로드캐스트

동작 방식:

훈련 워커가 가중치를 모으고(보통 TP rank 0에서), 이를 롤아웃 그룹에 브로드캐스트하며, 각 롤아웃 TP 샤드가 필요한 파라미터를 로드합니다. 이는 디스크 I/O를 피하고 훈련과 롤아웃을 분리하지만, 전용 통신 그룹 관리 비용이 듭니다.

가중치 업데이트 그룹 초기화

엔드포인트: POST /init_weights_update_group

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
master_address 그룹 마스터 주소. 필수 타입: str
master_port 그룹 마스터 포트. 필수 타입: int
rank_offset 로컬 rank 매핑 오프셋. 필수 타입: int
world_size 전체 world size. 필수 타입: int
group_name 그룹 이름. weight_update_group 타입: str
backend 통신 백엔드. nccl 타입: str

가중치 업데이트

엔드포인트: POST /update_weights_from_distributed

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
names 업데이트할 파라미터 이름. 필수 타입: list[str]
dtypes 각 파라미터의 dtype 문자열. 필수 타입: list[str]
shapes 텐서 형태. 필수 타입: list[list[int]]
group_name 그룹 이름. weight_update_group 타입: str
flush_cache 업데이트 후 KV 캐시 플러시. True 타입: bool
abort_all_requests 업데이트 전에 실행 중인 모든 요청 중단. False 타입: bool
weight_version 선택적 버전 라벨. None 타입: str
load_format 선택적 형식 셀렉터. None None 또는 flattened_bucket

가중치 업데이트 그룹 삭제

엔드포인트: POST /destroy_weights_update_group

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
group_name 그룹 이름. weight_update_group 타입: str

Python 엔진 API:

  • engine.init_weights_update_group(...)
  • engine.begin_weight_update(selector="all")
  • engine.update_weights_from_distributed(names, dtypes, shapes, ...)
  • engine.end_weight_update()
  • engine.destroy_weights_update_group(group_name)

토큰별 가중치 버전 귀속 (Per-Token Weight Version Attribution)

요청은 버전 변경보다 오래 살 수 있습니다. RL 흐름은 pause_generation으로 이를 철회하고 리핏 후 이어가거나, 생성 중에 POST /update_weight_version으로 라벨을 다시 붙입니다. 그러면 meta_info가 어떤 토큰이 어떤 가중치에서 나왔는지 보고합니다:

"weight_version": "42",
"weight_versions": [
    {"version": "41", "start": 0, "end": 57},
    {"version": "42", "start": 57, "end": 128}
]
  • 범위는 출력 토큰 인덱스에 대한 반개방 [start, end)이며 프롬프트는 제외됩니다. 보통은 단일 스팬입니다.

생성을 쉽게 연기 (Easy To Postpone Generation)

다회차 RL 롤아웃은 종종 전체 배치를 막는 롱테일(long-tail) 요청으로 고통받습니다. 소수의 느린 상호작용이 모든 GPU를 멈출 수 있고, 롱테일 동작은 프로파일링과 모니터링을 어렵게 합니다.

SGLang은 명시적인 일시정지/재개 API를 노출하므로 느린 요청을 일시정지하고 나중에 이어갈 수 있습니다. 이 패턴은 APRIL 같은 시스템과 일치합니다. 충분한 응답이 모이면 종료하고 불완전한 응답은 다음 단계에서 재활용합니다. 결과적으로 부분 작업을 버리지 않으면서 GPU 활용도를 높입니다.

pause_generation --- 가중치 업데이트 --- continue_generation은 훈련에서 가중치를 업데이트할 때 올바른 실행 흐름입니다. 업데이트는 SGLang이 추론 작업을 적극적으로 처리하지 않을 때만 가능합니다.

생성 일시정지 (Pause Generation)

엔드포인트: POST /pause_generation

요청 본문:

필드 (Field) 설명 (Description) 기본값 (Defaults) 옵션 (Options)
mode 일시정지 모드. abort abort, retract, in_place

모드:

  • abort: 기본 동작. abort_all이 설정된 abort 엔드포인트와 동일합니다. waiting_queuerunning_queue의 보류 요청은 호출자에게 즉시 반환됩니다.
  • retract: 엔진을 "일시정지" 상태로 둡니다. 실행 중인 요청을 waiting queue로 되돌립니다. KV 캐시는 플러시되고 나중에 재계산될 수 있습니다.
  • in_place: 요청 상태를 바꾸지 않고 엔진을 "일시정지" 상태로 둡니다. 실행 중인 요청은 계속하기 위해 KV 캐시 가용성에 의존하므로, 이후의 flush_cache 호출은 성공하지 않습니다.

생성 재개 (Continue Generation)

엔드포인트: POST /continue_generation

결정적 추론 (Deterministic Inference)

많은 RL 스택에서 롤아웃과 훈련은 서로 다른 커널이나 배칭 동작으로 구현됩니다. 가중치가 동일하더라도 토큰 확률이 달라져 on-policy 가정을 조용히 깨뜨릴 수 있습니다. 이것이 훈련-추론 불일치 문제입니다.

SGLang은 배치 형태 전반의 비결정성을 줄이는 결정적 추론 모드를 지원합니다. 이는 런타임 배칭과 커널 선택으로 인한 변동을 완화합니다. 진정한 on-policy 훈련을 더 하려면 훈련 엔진도 동일한 결정적 커널을 사용하도록 수정해야 합니다. 구현 세부사항은 miles 예시를 참고하세요: True On-PolicyTrue On-Policy for VLM. 추가 맥락은 블로그 Let Speed Be With Stability: All-In-One Solution to Training-Inference Mismatch with Miles를 참고하세요.

서버 플래그:

--enable-deterministic-inference

자세한 내용은 결정적 추론을 참고하세요.

로드 밸런싱 라우터 (Load Balancing Router)

SGLang Model Gateway는 대규모 RL 롤아웃에 권장되는 컨트롤 플레인입니다. 비동기·논블로킹 요청 처리, 캐시 인지 로드 밸런싱, 롤아웃·보상 서버 전반의 장애 허용 라우팅을 제공합니다. 이를 통해 GPU를 포화 상태로 유지하면서 롱테일 정지와 취약한 엔진-로컬 동시성 로직을 피할 수 있습니다. GLM 4.5+ 모델 훈련에 배포되었으며 프로덕션 수준의 대규모 RL 워크로드에서 매우 효율적임이 입증되었습니다.

RL 인프라를 위한 주요 이점:

  • 비동기 논블로킹 효율성: SGLang의 네이티브 비동기 서버/라우터 아키텍처(HTTPS/gRPC)는 동시성을 자동으로 관리합니다. 엔지니어의 복잡한 수동 구현 없이 최대 GPU 포화와 효과적인 지속 배칭을 보장합니다.
  • 탄력성과 장애 허용: 보상 모델과 롤아웃을 독립 서버로 캡슐화해 SGLang은 이를 논리적·물리적으로 분리합니다. 이 아키텍처는 대규모 분산 훈련에 견고한 재해 복구를 제공합니다. 서버가 실패하면 라우터는 자동으로 트래픽을 정상 노드로 리디렉션하여 훈련 프로세스가 중단 없이 계속되도록 합니다.
  • 훈련-추론 정렬: 훈련과 추론 모두에 SGLang Model Gateway를 사용하면 "보는 것이 얻는 것(What You See Is What You Get)"을 보장합니다. 이는 훈련과 배포에 다른 엔진을 사용할 때 자주 발생하는 점수 불일치와 고통스러운 백엔드 정렬 문제를 제거합니다.
  • 동적 로드 밸런싱과 롱테일 완화: 정적 분할과 달리 SGLang Model Gateway는 다회차 RL에 대한 요청 수준 동적 디스패칭을 가능하게 합니다. 대화의 서로 다른 턴을 서로 다른 서버에 분산해 워크로드를 균형 있게 하고 가변 시퀀스 길이로 인한 롱테일 지연을 제거할 수 있습니다.

배포·구성은 SGLang Model Gateway를 참고하세요.

더 알아보기 (Learn more)