TrendShift 딥다이브 · 2026-07-02

AReaL-MinT 딥다이브
— 매니지드 LLM 학습 API "Tinker"를 내 GPU 클러스터에 그대로 재현하기

AReaL-MinTMinT(LLM용 RL 학습 인프라 SDK)와 완전히 동일한 HTTP API를 노출하면서, 실제 계산은 AReaL(FSDP2 분산학습)SGLang(고성능 추론) 위에서 돌리는 오픈소스·셀프호스팅 학습 런타임이다. CPU 한 대에서 데이터·손실함수·RL 환경을 정의하는 스크립트를 짜면, 이 서버가 8×GPU 클러스터에서 실제 SFT/GRPO 학습을 대신 실행해 준다 — 모델 교체는 문자열 하나만 바꾸면 된다. (저장소: areal-project/AReaL-MinT · Apache-2.0 · v0.1.0 갓 공개 · 커밋 1개 · ★20 · TrendShift Daily 16위)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

이 저장소가 대체 무엇인가.

핵심 메시지

"클라우드 매니지드 파인튜닝 API를 내 서버에" — MinT SDK 스크립트를 한 줄도 안 고치고 내 GPU 위에서 그대로 돌리는 학습 서버.

요즘 LLM을 파인튜닝하려는 사람들은 크게 두 갈래 길을 걷는다. 하나는 Tinker(Thinking Machines의 매니지드 파인튜닝 API)처럼 "코드만 보내면 클라우드가 GPU를 대신 굴려주는" 서비스를 쓰는 길이고, 다른 하나는 직접 GPU 클러스터·분산학습 코드·추론 서버까지 전부 손수 짜는 길이다. 전자는 편하지만 데이터가 남의 서버로 나가고 요금이 붙는다. 후자는 자유롭지만 진입장벽이 매우 높다.

AReaL-MinT는 이 둘 사이의 세 번째 길이다. MinT라는 오픈소스 SDK가 정의한 "학습 스크립트가 호출하는 HTTP API 규격"을 그대로 흉내 내는 서버를 내가 직접 띄운다. 비유하면: MinT SDK 스크립트 = 택배 주문서, 클라우드 매니지드 서비스 = 택배회사 물류센터, AReaL-MinT = 내 창고에 그대로 옮겨 놓은 물류센터. 주문서(스크립트) 양식은 똑같이 쓰지만, 물건(모델 가중치)이 내 창고(내 GPU)를 벗어나지 않는다.

README 첫 문장은 이렇게 못박는다: "Open-source, self-hosted MinT training runtime on AReaL." 여기서 MinT는 LLM용 RL(강화학습) 학습 인프라로, "CPU 한 대짜리 스크립트가 데이터·손실함수·RL 환경을 정의하면, MinT이 그 계산을 GPU 클러스터에서 대신 실행"하는 게 핵심 아이디어다. 모델을 바꾸고 싶으면 문자열 하나(base_model="Qwen/Qwen3.6-27B")만 바꾸면 된다.

AReaL-MinT는 이 MinT과 똑같은 HTTP API를 노출하되, 내부적으로는 실제 GitHub 코드를 열어 보면 1,165줄짜리 FastAPI 앱(app.py)이 60개가 넘는 엔드포인트로 세션 생성·순전파·역전파·최적화 스텝·체크포인트·샘플링까지 전부 처리하고 있다. 학습 계산은 AReaL(중국 AI Lab 계열의 오픈소스 RL 학습 프레임워크, FSDP2 기반)에, 추론·샘플링은 SGLang(고성능 LLM 서빙 엔진)에 위임한다. 즉 "API 얼굴은 MinT, 몸통은 AReaL+SGLang"인 조합형 런타임이다.

용어
셀프호스팅(Self-hosted) & 학습 런타임(Training Runtime)
셀프호스팅은 "서비스를 클라우드 업체가 아니라 내가 소유·관리하는 서버에서 직접 돌린다"는 뜻이다. 학습 런타임은 "학습 스크립트가 호출하면 실제로 GPU에서 forward/backward/optimizer step을 실행해 주는 백그라운드 서버 프로그램"을 말한다. AReaL-MinT는 이 둘을 합친 것 — MinT의 API 규격을 따르는 학습 런타임을, 클라우드가 아니라 내 GPU 서버에서 직접 돌리는 소프트웨어다.

2왜 주목받는가

"Tinker의 오픈소스 대안"이라는 포지셔닝 · Tinker 클라이언트 호환 · AReaL+SGLang 조합의 실전 검증.

AReaL-MinT가 공개 직후 TrendShift에 오른 이유는 세 가지 흐름이 겹치기 때문이다. 첫째, 2026년 들어 매니지드 파인튜닝 API(Tinker 등)가 "코드만 짜면 GPU를 신경 안 써도 된다"는 편의성으로 주목받으면서, 동시에 "그럼 이걸 오픈소스로 직접 돌릴 순 없나"라는 수요도 함께 커졌다. 둘째, RL 기반 파인튜닝(GRPO 등)이 SFT만으로는 못 잡는 추론 능력 향상의 표준 기법으로 자리잡았다. 셋째, AReaL과 SGLang이라는 각자 검증된 오픈소스 두 개를 하나의 서비스로 엮어 낸 통합형 접근 자체가 드물다.

Tinker(매니지드 API)와 무엇이 다른가

비교 축Tinker류 매니지드 APIAReaL-MinT의 접근
실행 위치제공사 클라우드 GPU내 소유 GPU 서버 — 데이터·가중치가 밖으로 안 나감
과금사용량 기반 API 요금전기·서버 비용만 (초기 GPU 투자는 별도)
API 표면Tinker 클라이언트 SDKMinT SDK와 동일한 APIimport mint as tinker 별칭으로 기존 코드 그대로
내부 학습 엔진비공개(블랙박스)공개 — AReaL FSDP2, 코드로 직접 확인 가능
내부 추론 엔진비공개공개 — SGLang + LoRA 핫리로드
커스터마이징제공사가 허용한 범위만소스 전체 오픈 — 원하는 대로 패치·확장 가능
주목 포인트 — "API 호환"을 코드로 증명한다
import mint as tinker — 기존 Tinker 코드가 그대로 돈다

README는 이렇게 말한다: "MinT is compatible with the Tinker client surface. Existing Tinker code runs against AReaL-MinT unchanged by aliasing the import." 실제로 저장소의 examples/smoke_sft.py·smoke_grpo.py를 열어 보면 mint.ServiceClientcreate_lora_training_clientforward_backwardoptim_stepsave_weights_and_get_sampling_client라는, 클라우드 매니지드 API를 쓸 때와 똑같은 호출 패턴이 로컬 서버를 향해 그대로 실행된다. "API가 같다"는 주장이 말뿐이 아니라 실행 가능한 스모크 테스트로 증명되어 있다는 점이 신뢰를 준다.

주목 포인트 — 검증된 두 오픈소스를 실전 배선으로 엮음
AReaL(학습) + SGLang(추론) 조합의 완성도

AReaL과 SGLang은 각각 이미 커뮤니티에서 검증된 프로젝트지만, 이 둘을 하나의 서비스로 자연스럽게 엮는 배선 코드(가중치 동기화, LoRA 키 이름 변환, 세션 생명주기 관리)를 만드는 일은 상당한 엔지니어링이다. 실제로 backends/areal.py 한 파일만 1,220줄이며, LoRA 어댑터를 Megatron 포맷에서 SGLang 포맷으로 키 이름을 재작성(rename)하는 함수까지 들어 있다 — "두 시스템을 이었다"는 말이 실제 통합의 디테일로 뒷받침된다.

기대치 조정 — 갓 공개된 v0.1.0, 개인 실습은 어렵다
8×GPU 요구 + 커밋 1개짜리 신생 프로젝트

이 저장소는 커밋이 단 1개("Release AReaL-MinT 0.1.0")뿐인 갓 공개된 알파 소프트웨어다. 지원 모델도 Qwen3.6 27B·35B-A3B 두 종류뿐이고, 실행하려면 8장의 NVIDIA GPU(학습 4장 + 추론 4장)가 필요하다 — 개인이 노트북에서 따라 하기는 사실상 불가능하다. 이 문서에서는 "GPU 없이도 개념과 코드 구조를 배우는" 방향으로 실습을 안내한다(8절·9절 참고).

3기술 스택 전체 지도

control plane=FastAPI/uvicorn/pydantic, 학습=AReaL FSDP2/torch, 추론=SGLang+LoRA, 패키징=uv/pyproject.

pyproject.toml을 열어 보면 이 프로젝트가 세 겹의 레이어로 구성돼 있다는 게 바로 드러난다. ① 가벼운 기본 의존성(FastAPI 등, CPU만 있어도 설치됨), ② [gpu] 추가 의존성(swanlab 등 GPU 실습용 도구), ③ [system] 무거운 의존성(torch·AReaL·SGLang — 도커 베이스 이미지에서 상속받는 게 기본이라 명시적 설치는 선택). 이 분리 덕분에 "코드 구조만 읽어보고 싶다"면 무거운 GPU 스택 없이도 저장소를 열어 볼 수 있다.

레이어기술 / 버전역할
웹 프레임워크FastAPI ≥ 0.110 + uvicorn ≥ 0.29control plane 본체. 60개 이상 엔드포인트로 세션·학습·추론 요청 처리
스키마 검증Pydantic ≥ 2.0모든 요청/응답 바디를 타입 안전하게 정의(schemas.py 634줄)
HTTP 클라이언트httpx + aiohttp + tenacitySGLang 서버로의 프록시 요청. tenacity로 지수 백오프 재시도
비동기 루프uvloop(Linux 전용)표준 asyncio보다 빠른 이벤트 루프 구현
학습 엔진AReaL (main 브랜치) + torch ≥ 2.4FSDP2 기반 분산학습. PPOTrainer·GatewayTrainController 제공
추론 엔진SGLang 0.5.10.post1 + sglang-kernel고성능 LLM 서빙. LoRA 어댑터 핫스왑(/load_lora_adapter) 지원
모델 라이브러리transformers ≥ 5.0, ≤ 5.3Qwen3.6 계열 모델 로딩·토크나이저
클라이언트 SDKmindlab-toolkit (MinT SDK)사용자가 학습 스크립트를 작성할 때 쓰는 import mint 패키지 (별도 저장소, git 의존)
관측/로깅swanlab + colorlog학습 실험 추적 대시보드 + 색상 콘솔 로그
OAI 호환 확장litellm + openai-agentsOpenAI 클라이언트로 이 서버를 그대로 호출할 수 있는 /oai/api/v1 경로
평가 도구math-verify수학 문제 RL 보상함수용 정답 검증 유틸
빌드/패키징hatchling + uvPEP 517 빌드 백엔드. uv.lock으로 재현 가능한 의존성 고정(40만 자 규모)
직렬화orjson(선택)SGLang과의 대용량 JSON 페이로드를 표준 json보다 빠르게 처리
테스트/린트pytest ≥ 8.0 + ruff ≥ 0.417개 테스트 파일. ruff로 E/F/I/W 규칙 검사(line-length 120)
CLI 진입점project.scriptsareal-mintareal_mint.cli:mainareal-mint serve 명령으로 서버 기동
용어
FSDP2 (Fully Sharded Data Parallel v2)
거대한 모델 하나를 여러 GPU에 쪼개어(shard) 나눠 담는 PyTorch의 분산학습 기법이다. 27B(270억 파라미터) 모델은 GPU 한 장의 메모리에 다 안 들어가므로, 파라미터·그래디언트·옵티마이저 상태를 GPU 4장에 균등하게 쪼개 저장하고, 계산이 필요한 순간에만 잠깐 모았다가(all-gather) 다시 흩뿌린다(reduce-scatter). v2는 v1보다 메모리 효율과 통신 오버랩이 개선된 버전으로, PyTorch의 최신 분산학습 표준 축이다.
용어
SGLang
LLM을 빠르게 서빙(추론 응답)하기 위한 오픈소스 엔진이다. vLLM과 같은 목적(대량 요청을 배치로 묶어 GPU 활용률을 극대화)을 갖지만, RadixAttention이라는 프리픽스 캐싱 기법과 유연한 프로그래밍 인터페이스가 특징이다. AReaL-MinT에서는 "방금 학습한 모델로 즉시 샘플링해 보는" 역할을 SGLang이 전담한다.

4아키텍처 심화 분석

FastAPI control plane의 디스패치 · future 폴링 세만틱 · FSDP2/SGLang GPU 분할 · LoRA 핫리로드 · SFT/GRPO 데이터 흐름.

README가 제시하는 아키텍처 다이어그램은 이렇다. 이 절에서는 실제 소스 코드(router.py·future_store.py·backends/areal.py·backends/areal_inf.py)를 근거로 이 다이어그램 뒤에서 정확히 무슨 일이 벌어지는지 파헤친다.

MinT SDK (사용자 학습 스크립트, CPU 한 대에서 실행) │ HTTP (forward_backward, optim_step, asample, save_weights ...) ▼ ┌─────────────────────────────────────────────────────────┐ │ AReaL-MinT — FastAPI control plane (app.py, 1165줄) │ │ │ │ service.py / router.py ─ 세션 라이프사이클 & 백엔드 디스패치│ │ future_store.py ─ 비동기 결과 폴링(future) │ │ │ │ │ │ ▼ (backend_id="areal-train") ▼ (backend_id="areal-infer") │ ArealTrainingBackend ArealInferenceBackend │ │ (backends/areal.py, 1220줄) (backends/areal_inf.py)│ └────────┬───────────────────────────────┬─────────────────┘ │ PPOTrainer / GatewayTrainController │ httpx 프록시 ▼ ▼ ┌────────────────────────┐ ┌────────────────────────┐ │ AReaL FSDP2 학습 클러스터│ ── 디스크 가중치 저장 ──▶ │ SGLang 추론 서버 │ │ GPU 4장 (fsdp:d4p1t1) │ (weight_update_mode: │ GPU 4장 (sglang:d1p1t4)│ │ actor / (선택)ref 엔진 │ disk) │ LoRA 어댑터 핫로드 │ └────────────────────────┘ └────────────────────────┘

① 진입점 — FastAPI가 요청을 받아 future로 즉시 응답한다

app.py의 모든 쓰기 엔드포인트(forward_backward·optim_step·asample 등)는 계산이 끝날 때까지 기다리지 않는다. 요청이 들어오면 _future(payload, **kwargs) 헬퍼가 future_store에 새 항목을 만들고 request_id만 즉시 반환한다. 실제 GPU 계산은 백그라운드에서 진행되고, 클라이언트는 /retrieve_future를 반복 호출(폴링)해 결과를 받는다. MinT SDK의 .result() 메서드가 이 폴링 루프를 클라이언트 쪽에서 감춰 준다.

용어
future / 폴링(Polling) 패턴
"나중에 준비될 값을 가리키는 꼬리표"가 future다. 함수를 호출하자마자 결과가 아니라 영수증(request_id)을 받고, 그 영수증을 들고 "다 됐나요?"라고 반복해서 물어보는(폴링) 방식이다. GPU 학습처럼 수 초~수십 초 걸리는 작업에서, HTTP 커넥션을 그 시간만큼 붙잡고 있는 대신 짧은 응답을 여러 번 주고받는 게 훨씬 안정적이다. future_store.pyInMemoryFutureStore가 이를 스레드 세이프하게 구현한다 — TTL(기본 600초) 뒤에는 자동 만료·삭제된다.
// future_store.py의 핵심 상태 전이 (실제 코드 발췌)
retrieve(request_id):
  if payload is _PENDING:              // 아직 안 끝남
      raise PendingFutureError(queue_state, reason)
  if pending_polls > 0:                // 인위적 지연(테스트/큐 시뮬레이션용)
      pending_polls -= 1; raise PendingFutureError(...)
  if allow_try_again_once and not sent: // "한 번 더 물어보세요" 신호
      return {"type": "try_again", ...}
  if payload_size > metadata_only_threshold:  // 너무 크면 메타데이터만
      return {"status": "complete_metadata", "response_payload_size": ...}
  del self._items[request_id]          // 정상 조회 후 즉시 삭제(1회성)
  return payload
이런 상황을 상상해봐

식당에서 주문을 넣으면 점원이 "몇 번 손님, 준비되면 불러드릴게요"라며 진동벨(request_id)을 건넨다. 음식(학습 결과)이 나올 때까지 카운터 앞에 서서 기다릴 필요 없이, 자리에 앉아 진동벨이 울리길(폴링) 기다리면 된다. allow_try_again_once는 "지금 주방이 좀 밀려요, 조금 있다 다시 확인해 주세요"에 해당하고, metadata_only_threshold는 "포장이 너무 커서 한 번에 못 드리니 일단 무게만 알려드릴게요" 같은 대용량 응답 최적화다.

② 라우팅 — BackendRouter가 세션을 백엔드에 붙들어 맨다

router.pyBackendRouter백엔드 레지스트리(학습용 1개 + 추론용 1개, backend_id로 식별)와 세션 테이블(session_id → TrainingSessionRecord)을 관리한다. SDK가 create_lora_training_client()를 호출하면 open_training_session(spec)이 실행되어 AReaL 백엔드에 PPOTrainer 인스턴스가 하나 생성되고, 이후 그 session_id로 들어오는 모든 forward·forward_backward·save_checkpoint 호출이 같은 트레이너로 디스패치된다. GRPO의 롤아웃(샘플링) 세션도 마찬가지로 rollout_session_id → (inference_backend_id, training_session_id) 매핑을 유지해, "이 롤아웃이 어느 학습 세션의 정책을 쓰는지"를 추적한다.

흥미로운 디스패치 로직은 _rollout_train_op()에 있다. 클라이언트가 보낸 loss_fn 문자열("ppo"·"grpo"·"reverse_kl" 등)을 검사해 TrainOp.FORWARD_BACKWARD_PPO 또는 TrainOp.FORWARD_BACKWARD_REVERSE_KL로 매핑한다 — 즉 SDK 코드에서 loss_fn="importance_sampling" 또는 "ppo"라고만 지정하면, 서버가 알아서 올바른 학습 연산으로 라우팅한다.

③ 학습 백엔드 — AReaL의 FSDP2 엔진을 감싼 어댑터

ArealTrainingBackend(backends/areal.py)는 세션을 열 때 YAML 설정(examples/qwen3_6_27b.yaml)을 읽어 AReaL의 PPOTrainer를 구성한다. 실제 설정 파일을 보면 actor.backend: "fsdp:d4p1t1"(데이터 병렬 4, 파이프라인 1, 텐서 1 — GPU 4장에 데이터만 쪼갬)와 rollout.backend: "sglang:d1p1t4"(데이터 1, 텐서 4 — GPU 4장으로 하나의 모델을 텐서 병렬 서빙)로 4+4 GPU 분할이 코드 레벨에서 확정돼 있다. actor.use_lora: true·lora_rank: 16이 기본값이라, 27B 모델 전체가 아니라 LoRA 어댑터만 학습하는 게 기본 동작이다.

학습 데이터는 MintTrainingAdapter(backends/mint_training.py)가 SDK의 Datum 페이로드(model_input 토큰 + loss_fn_inputstarget_tokens/weights/logprobs/advantages)를 AReaL이 이해하는 텐서 배치로 변환한다. GRPO에서 SDK가 이미 계산해 보낸 advantage 값이 있으면 그대로 쓰고(raw_advantages), 없으면 AReaL의 compute_advantages가 새로 계산하도록 흘려보낸다 — 보상함수·advantage 계산은 클라이언트(사용자 스크립트) 책임, 그 이후 PPO/GRPO 손실 계산과 그래디언트는 서버(AReaL) 책임이라는 경계가 명확하다.

④ 추론 백엔드 & 가중치 동기화 — 두 갈래 경로가 공존한다

README는 "SGLang inference + LoRA hot-reload"라고 짧게 설명하지만, 실제 areal_inf.py의 클래스 docstring을 보면 더 정교하다. AReaL v2의 기본 경로는 awex(NCCL in-place 가중치 전송)로, 학습 GPU에서 추론 GPU로 전체 가중치를 직접 복사해 LoRA 스왑 없이도 최신 가중치를 유지한다. 반면 MinT SDK 특유의 "이름 붙은 스냅샷 샘플러"(save_weights_and_get_sampling_client(name=...)) 요청이 오면, _save_lora_to_hf로 LoRA 어댑터를 HuggingFace 포맷으로 디스크에 저장하고, SGLang의 /load_lora_adapter API로 그 어댑터를 즉시 로드시킨다. 이때 Megatron/VLM 체크포인트의 파라미터 이름(model.language_model.*)과 SGLang이 기대하는 이름(model.*)이 달라, _rename_lora_keys_for_sglang()이 safetensors 파일을 열어 키 이름을 직접 재작성하는 실전적인 호환성 코드까지 들어 있다.

왜 두 경로가 공존하는가
"항상 최신 정책"(awex) vs "이름 붙은 버전 관리"(LoRA 스냅샷)

연속적인 온폴리시 RL 롤아웃에는 awex의 실시간 NCCL 전송이 유리하다(디스크 I/O 없이 즉시 반영). 반면 MinT SDK의 save_weights_and_get_sampling_client(name="policy-v0")처럼 "이 시점의 정책을 이름표 붙여 저장해두고 나중에도 다시 꺼내 쓰고 싶다"는 요구에는 디스크에 저장된 LoRA 어댑터가 맞다 — 체크포인트 재현성, 여러 버전 동시 서빙(max_loaded_loras: 8)이 가능해진다. 하나의 서버가 두 요구를 모두 만족시키려고 두 경로를 같이 갖고 있는 것이다.

⑤ SFT 데이터 흐름 — forward_backward → optim_step

SDK: build_sft_datum(prompt, completion) tokens = prompt_tokens + completion_tokens weights = [0]*len(prompt) + [1]*len(completion) # prompt는 loss 제외 │ ▼ POST /forward_backward { loss_fn: "cross_entropy", data: [Datum, ...] } control plane → future 발급 → ArealTrainingBackend.forward_backward() │ ▼ AReaL 워커: 순전파(logits 계산) → cross-entropy loss → 역전파(그래디언트 누적) │ ▼ POST /optim_step { learning_rate: 1e-5 } control plane → ArealTrainingBackend.run_train_op() → 옵티마이저가 실제로 가중치 갱신

⑥ GRPO 데이터 흐름 — 샘플링 → 보상 → advantage → 2단계 학습

1) sampler = tc.save_weights_and_get_sampling_client(name="policy-v0") └─▶ 현재 LoRA 가중치를 SGLang에 핫로드 (또는 awex로 실시간 반영) 2) result = sampler.sample(prompt, num_samples=4, temperature=0.7) └─▶ POST /asample → SGLang이 같은 프롬프트로 4개 응답 생성(rollout) 3) rewards = [reward_fn(seq) for seq in result.sequences] # 사용자 정의 보상함수 advantages = (rewards - mean(rewards)) / std(rewards) # 그룹 내부 상대 비교 4) POST /forward_backward { loss_fn: "importance_sampling", data: [Datum(advantages=...), ...] } └─▶ router가 TrainOp.FORWARD_BACKWARD_REVERSE_KL 로 디스패치 └─▶ AReaL 워커: 중요도 샘플링 비율(new_logp/old_logp) 기반 loss 5) POST /optim_step → POST /forward_backward { loss_fn: "ppo" } (클리핑 적용판) → POST /optim_step

GRPO의 핵심은 가치함수(value critic) 네트워크가 따로 없다는 점이다. PPO는 보통 "이 상태가 얼마나 좋은가"를 별도로 추정하는 크리틱 모델을 학습시켜야 하지만, GRPO는 같은 프롬프트에 대해 여러 응답을 뽑아(그룹) 그 그룹 내부의 평균·표준편차만으로 advantage를 정규화한다. 크리틱 모델 학습·저장·서빙이 통째로 사라지므로 인프라가 단순해진다 — qwen3_6_27b.yamlreward_norm: {mean_level: group, std_level: group} 설정이 이를 그대로 반영한다.

5디렉토리 구조 해부

src/·examples/·scripts/·docs/·tests/ 실제 내용 기반.

AReaL-MinT/ ├── src/areal_mint/ # 핵심 패키지 (6,668줄, 28개 파일) │ ├── app.py # FastAPI 앱 정의 — 60+ 엔드포인트 (1165줄 ★최대) │ ├── router.py # BackendRouter — 세션↔백엔드 디스패치 (415줄) │ ├── service.py # MintService — TrainingService/InferenceService 조합 (508줄) │ ├── future_store.py # 비동기 future/폴링 저장소 (158줄) │ ├── schemas.py # Pydantic 요청/응답 스키마 전체 (634줄) │ ├── contracts.py # 백엔드 내부 dataclass 계약 (228줄) │ ├── engine.py # MintEngine — 워커에서 __mint_command__ 디스패치 (289줄) │ ├── oai.py # /oai/api/v1 OpenAI 호환 라우트 (324줄) │ ├── storage.py # 로컬 파일 저장소 + 공유 경로 검증 (130줄) │ ├── cli.py # areal-mint CLI 진입점 (argparse, 150줄) │ ├── errors.py · logging.py · mint_batch.py # 예외/로깅/배치 헬퍼 │ │ │ ├── backends/ # 학습·추론 실제 구현체 │ │ ├── areal.py # ArealTrainingBackend — FSDP2 학습 (1220줄 ★최대) │ │ ├── areal_inf.py # ArealInferenceBackend — SGLang 프록시 (213줄) │ │ ├── mint_training.py # SDK↔AReaL 데이터 포맷 변환 어댑터 (379줄) │ │ ├── base.py # Backend/TrainingBackend/InferenceBackend 추상클래스 │ │ └── fake.py # GPU 없이 도는 가짜 백엔드 — 로컬 개발/테스트용 │ │ │ └── patches/ # AReaL/SGLang/transformers 런타임 몽키패치 (8종) │ ├── _hook.py # 모듈 import 감지 후 자동 패치 적용 (post-import hook) │ ├── areal_fsdp_engine.py # FSDP2 LoRA 체크포인트 로드 추가 │ ├── areal_controller.py # AReaL 컨트롤러 패치 │ ├── areal_scheduler.py # awex 스케줄러 폴백 패치 │ ├── sglang_moe_lora.py # MoE 모델(35B-A3B)의 LoRA 지원 패치 │ └── transformers_qwen35.py # Qwen3.6 계열 transformers 호환 패치 │ ├── examples/ # 실행 가능한 SDK 스크립트 + AReaL 설정 │ ├── qwen3_6_27b.yaml # 27B Dense 모델 학습 설정 (fsdp:d4p1t1 + sglang:d1p1t4) │ ├── qwen3_6_35b_a3b.yaml # 35B MoE 모델 학습 설정 │ ├── smoke_sft.py # SFT → 샘플링 → 체크포인트 재개 → OAI API 검증 │ ├── smoke_grpo.py # 그룹 샘플링 → 보상 → advantage → PPO 학습 검증 │ ├── smoke_rl_convergence.py # 멀티스텝 RL — 보상이 실제로 개선되는지 assert │ ├── smoke_helpers.py # 공용 유틸(Datum 빌더, 그룹 advantage 계산 등) │ └── smoke_e2e.sh # SFT→GRPO→수렴 전체를 잇는 원커맨드 검증 (~15분) │ ├── scripts/ # 운영 스크립트 │ ├── mintctl # start/stop/restart/status/logs — PID+PGID 기반 프로세스 관리 │ ├── serve_qwen3_6_27b.sh # 27B 서버 기동 (환경변수 로드 + areal-mint serve 실행) │ └── serve_qwen3_6_35b_a3b.sh # 35B MoE 서버 기동 │ ├── docs/ │ ├── guide.md # 개발자 가이드(중국어) — SFT/GRPO 전체 코드 예제 포함 │ └── assets/ # 배너 SVG, 로고 │ ├── tests/ # pytest 17개 파일 — 세션/라우터/체크포인트/OAI 등 단위 테스트 ├── .env.example # 클러스터 경로 템플릿(모델 경로·HF_HOME) ├── pyproject.toml # hatchling 빌드 + 3단 의존성(기본/gpu/system) └── uv.lock # 재현 가능한 의존성 고정 (40만 자 규모)
한눈에 읽는 법

파일 크기가 곧 "책임의 무게"를 보여준다. app.py(1165줄)가 "바깥세상과 대화하는 접수창구"라면, backends/areal.py(1220줄)는 "실제로 GPU를 굴리는 공장"이다. 이 둘 사이를 router.py·service.py·future_store.py가 이어주는 배관이고, schemas.py(634줄)는 접수창구에서 쓰는 "정해진 양식지" 뭉치다. patches/는 조금 특이한데, AReaL·SGLang·transformers라는 남의 라이브러리를 import 시점에 몰래 고쳐 끼우는(monkeypatch) 코드 모음이다 — 오픈소스를 조합할 때 버전 간 미묘한 API 불일치를 메꾸는 실전 기법을 보여준다. 처음 읽는다면 examples/smoke_sft.py(클라이언트 관점) → app.py/forward_backward 엔드포인트 → router.pybackends/areal.py 순서로, "요청이 어디를 거쳐 GPU까지 가는지" 한 번 따라가 보는 게 가장 빠르다.

6학습 포인트 (기술별)

이 저장소에서 실제로 무엇을 배울 수 있는가 + 각 주제 실습 아이디어.

학습 1 · RLHF/GRPO — 크리틱 없는 강화학습

"보상함수만 있으면 되는 가벼운 RL 파인튜닝"

router.py_rollout_train_op()examples/smoke_grpo.py를 나란히 읽으면, GRPO가 실무에서 어떤 형태의 코드로 구현되는지 감을 잡을 수 있다. "같은 프롬프트로 여러 응답을 뽑고, 그룹 내부 상대 비교로 advantage를 매긴다"는 아이디어가 왜 크리틱 네트워크를 없앨 수 있는지, 코드 레벨에서 확인해 보자.

실습: 종이 위에서 보상 4개 [0.8, 0.2, 0.5, 0.9]를 손으로 정규화해 advantage를 계산해 보고, PPO였다면 이 대신 무엇이 필요했을지 비교표를 만들어 보기.

학습 2 · FSDP2 분산학습

"거대 모델을 GPU 여러 장에 쪼개는 법"

examples/qwen3_6_27b.yamlactor.backend: "fsdp:d4p1t1"fsdp.memory_efficient_load: true 설정을 실마리로, PyTorch FSDP2 공식 문서를 함께 읽으면 "파라미터 샤딩·all-gather·reduce-scatter"가 실제 설정 파일에서 어떻게 한 줄짜리 표기법(d/p/t)으로 압축되는지 배울 수 있다.

실습: 작은 모델(예: GPT-2 124M)을 torch FSDP로 2개의 가상 GPU(또는 CPU 프로세스)에 쪼개 학습시켜, 단일 GPU 학습과 메모리 사용량을 비교.

학습 3 · SGLang 추론 서빙

"학습과 추론을 분리하고 다시 잇는 설계"

backends/areal_inf.py는 SGLang을 "학습 엔진과 별개 프로세스로 띄워 놓고 HTTP로 프록시"하는 패턴을 보여준다. 왜 학습과 추론을 같은 프로세스에 두지 않았을까? generate() 메서드의 재시도 로직(tenacity)과 _build_payload()가 SGLang의 /generate API 스펙(sampling_params·lora_path 등)에 어떻게 맞춰졌는지 살펴보면 "추론 엔진을 남이 만든 걸 그대로 갖다 쓰는 법"을 배운다.

실습: SGLang(또는 vLLM) 서버를 로컬에 작은 모델로 띄우고, httpx로 직접 /generate를 호출해 응답 구조를 관찰.

학습 4 · LoRA — 파라미터 효율적 파인튜닝

"270억 개 파라미터 중 극히 일부만 학습한다"

lora_rank: 16 설정 하나가 실제로 무엇을 의미하는지, patches/areal_fsdp_engine.py_load_lora_from_hf(어댑터만 골라 로드)와 backends/areal.py_rename_lora_keys_for_sglang(포맷 변환)을 같이 읽으면 "LoRA 어댑터가 저장·전송·서빙되는 전체 파이프라인"을 코드로 체감할 수 있다.

실습: HuggingFace PEFT 라이브러리로 작은 모델에 LoRA(rank=8)를 붙여 파인튜닝하고, 원본 대비 학습 가능한 파라미터 개수를 직접 출력해 비교.

학습 5 · FastAPI 비동기 control plane 설계

"오래 걸리는 작업을 웹 API로 어떻게 노출하는가"

app.py의 미들웨어·예외 핸들러 패턴(@app.exception_handler(UnknownSessionError) 등)과, 거의 모든 엔드포인트에 /api/v1/xxx/xxx 두 경로를 동시에 등록하는 방식(하위 호환·클라이언트 유연성)은 실전 API 설계의 좋은 참고 자료다.

실습: 미니 FastAPI 앱을 만들어, 오래 걸리는 작업(예: time.sleep(10))을 즉시 request_id를 반환하고 백그라운드 태스크로 처리한 뒤 /status/{id}로 폴링하는 구조를 직접 구현.

학습 6 · future/폴링 패턴

"긴 작업과 짧은 HTTP 응답을 분리하는 범용 기법"

future_store.pyInMemoryFutureStore는 스레드 락·TTL 만료·"try_again" 신호·"메타데이터만 우선 반환"까지 갖춘, 실무에서 바로 참고할 만한 미니 구현체다. GPU 학습뿐 아니라 이메일 발송·이미지 처리·긴 배치 작업 등 어디에나 적용 가능한 패턴이다.

실습: future_store.py를 그대로(또는 단순화해) 복사해, 나만의 "긴 작업 큐 + 폴링 API"를 만들어 보고 TTL 만료가 실제로 동작하는지 테스트.

학습 7 · 런타임 몽키패치로 오픈소스 조합하기

"내가 못 고치는 남의 라이브러리를 안전하게 고쳐 쓰는 법"

patches/_hook.pyPostImportHookbuiltins.__import__를 감싸, "특정 모듈이 로드되는 순간"을 감지해 자동으로 패치 함수를 적용한다. AReaL·SGLang·transformers라는 각각 독자적으로 진화하는 오픈소스 셋을 하나로 엮을 때 생기는 버전 불일치·API 갭을 이런 식으로 메운다는 걸 배울 수 있다.

실습: 작은 파이썬 패키지를 만들어, 특정 표준 라이브러리 모듈이 import될 때 함수 하나를 몽키패치하는 미니 post-import hook을 직접 작성.

7하드웨어 / 시스템 요구사항

공식 요구사항 + 현실적인 개인 대안.

항목공식 요구사항
GPU8× NVIDIA(학습용 4장 + 추론용 4장, 텐서/데이터 병렬 분할)
Python>=3.11, <3.13
torch>=2.4 + CUDA (도커 베이스 이미지에서 상속 권장)
AReaLmain 브랜치 (git 의존성)
SGLang0.5.10.post1 + sglang-kernel==0.4.1
transformers>=5.0.0, <=5.3.0
지원 모델Qwen3.6 27B(Dense) · Qwen3.6 35B-A3B(MoE) — 이 두 종류만 실행 가능
패키지 관리uv sync --extra gpu --extra system 또는 pip install -e '.[gpu]'
기동 시간모델 로드 약 3분, 검증 스크립트(smoke_e2e.sh) 27B 기준 약 15분
라이선스Apache-2.0 — 상업적 사용 가능
현실 체크 — 8×GPU는 개인이 갖추기 어렵다
27B/35B급 모델 학습은 클라우드 A100/H100 8장급 인프라가 필요

가정용 GPU(RTX 4090 등) 1~2장으로는 이 저장소를 있는 그대로 실행할 수 없다. 27B 모델 자체가 bfloat16 기준으로도 50GB가 넘는 가중치를 가지며, FSDP2로 쪼개더라도 학습에는 상당한 VRAM이 필요하다. "직접 돌려보기"보다는 아래 대안으로 접근하는 것을 권장한다.

개인 학습을 위한 현실적 대안
축소 실습 & 클라우드 스팟 인스턴스

① 코드 읽기 실습(GPU 불필요)fake.py 백엔드는 GPU 없이 control plane 흐름을 로컬에서 그대로 체험할 수 있게 해 준다(areal-mint serve --training-backend fake). ② 소형 모델 대체 실습 — 이 저장소의 구조(FastAPI+future+백엔드 분리)를 참고해 GPT-2 124M·Qwen 0.5B 같은 소형 모델로 나만의 미니 학습 서버를 직접 만들어 보기. ③ 클라우드 스팟/시간 단위 대여 — RunPod·Lambda Labs·Vast.ai 등에서 A100 4~8장을 시간 단위로 빌려 smoke_e2e.sh를 한 번 돌려 보는 방식(비용 발생 주의, 사전 예산 확인 필수).

용어
텐서 병렬(Tensor Parallel) vs 데이터 병렬(Data Parallel)
설정 파일의 d4p1t1·d1p1t4 표기가 각각 데이터(d)·파이프라인(p)·텐서(t) 병렬 개수를 뜻한다. 데이터 병렬은 "같은 모델을 GPU마다 복사해 두고 서로 다른 데이터 배치를 처리"하는 방식(학습에서 흔함)이고, 텐서 병렬은 "모델 하나의 레이어 연산 자체를 여러 GPU가 쪼개서 나눠 계산"하는 방식(추론 지연시간 단축에 유리)이다. AReaL-MinT는 학습(actor)엔 데이터 병렬 4, 추론(sglang)엔 텐서 병렬 4를 각각 다르게 골라, 목적에 맞는 병렬화 전략을 나눠 쓴다.

8직접 해볼 수 있는 실습 과제

난이도별 5단계 — GPU 없이도 시작할 수 있는 개념 실습부터, 실제 배선을 뜯어보는 심화 과제까지.

과제 1 · fake 백엔드로 control plane 뼈대 체험 난이도 ★☆☆☆☆

GPU 없이도 가장 먼저 할 수 있는 실습이다. 저장소를 클론해 기본 의존성만 설치(pip install -e '.[dev]')한 뒤, areal-mint serve --training-backend fake로 서버를 띄운다. 그 다음 curl이나 httpx/healthz·/api/v1/server_info를 호출해 응답 구조를 확인하고, /create_session으로 가짜 세션을 열어 future_store의 폴링 흐름을 직접 눈으로 관찰한다.

# 로컬 실행 (GPU 불필요, fake 백엔드)
git clone https://github.com/areal-project/AReaL-MinT.git
cd AReaL-MinT
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]' --no-deps
pip install fastapi pydantic uvicorn colorlog tenacity aiohttp aiofiles httpx requests flask

areal-mint serve --training-backend fake --port 8000
# 다른 터미널에서:
curl http://localhost:8000/healthz
curl http://localhost:8000/api/v1/server_info

과제 2 · future_store 단위 테스트 읽고 확장하기 난이도 ★★☆☆☆

tests/test_future_store.py를 열어 어떤 시나리오(정상 완료·TTL 만료·pending_polls·metadata_only)를 테스트하는지 파악한 뒤, "결과가 3번 폴링 후에야 준비되는" 새로운 시나리오를 직접 추가해 테스트를 통과시킨다. pytest tests/test_future_store.py -v로 검증.

과제 3 · SFT Datum을 손으로 만들어 API로 전송 난이도 ★★★☆☆

docs/guide.mdbuild_sft_datum 함수를 참고해, 토크나이저 없이도 정수 토큰 ID 리스트를 직접 만들어 fake 백엔드의 /forward_backward 엔드포인트에 POST 요청을 보내 본다. weights 배열이 prompt 구간은 0, completion 구간은 1이 되는 걸 직접 손으로 구성해 보면 "SFT loss mask"의 의미가 체화된다.

과제 4 · 미니 GRPO advantage 계산기 만들기 난이도 ★★★☆☆

examples/smoke_helpers.pycompute_group_advantages 함수를 참고하지 않고 직접 구현해 본다. 보상 리스트를 입력받아 (reward - group_mean) / group_std를 반환하는 순수 함수를 작성하고, 표준편차가 0일 때(모든 응답이 똑같은 보상을 받았을 때) 어떻게 나눗셈 오류를 막을지도 함께 고민해 본다.

// 직접 구현해 볼 시그니처
def compute_group_advantages(rewards: list[float]) -> list[float]:
    # TODO: 그룹 평균·표준편차로 정규화
    # 힌트: std가 0에 가까우면 1.0으로 클램프해 0-division 방지
    ...

assert compute_group_advantages([1.0, 1.0, 1.0, 1.0]) == [0.0, 0.0, 0.0, 0.0]
print(compute_group_advantages([0.9, 0.1, 0.5, 0.3]))

과제 5 · (GPU 보유자용) smoke_e2e.sh 전체 파이프라인 실행 난이도 ★★★★★

8×GPU 클라우드 인스턴스에 접근할 수 있다면, README의 Quick Start를 그대로 따라 mintctl start 27b로 서버를 띄우고 bash examples/smoke_e2e.sh를 실행해 SFT → GRPO → RL 수렴까지 이어지는 전체 파이프라인을 직접 관찰한다. mintctl logs -f로 실시간 로그를 보며, 4절에서 설명한 future 폴링·LoRA 핫로드가 실제 로그 라인으로 어떻게 나타나는지 확인해 보는 것이 목표.

9관련 기술 심화 학습 로드맵

RL 기초 → PPO/GRPO → 분산학습 → 추론 최적화 — 6주 계획.

주차주제할 일
1주차LLM 파인튜닝 기초 & SFT지도학습 파인튜닝(SFT) 개념, loss mask·프롬프트/완성 분리. docs/guide.md SFT 예제 정독 + 과제 3 완주
2주차강화학습 기초정책(policy)·보상(reward)·가치함수(value) 개념. OpenAI Spinning Up의 RL 입문 자료로 policy gradient 원리 익히기
3주차PPO → GRPOPPO의 클리핑·중요도 샘플링 원리, GRPO가 크리틱을 없앤 이유. 과제 4(advantage 계산기) 완주
4주차분산학습 (FSDP2)데이터/텐서/파이프라인 병렬의 차이. PyTorch FSDP2 튜토리얼 + qwen3_6_27b.yaml의 병렬 설정 재해석
5주차추론 서빙 최적화배치 처리·KV 캐시·프리픽스 캐싱(RadixAttention). SGLang 공식 문서 + areal_inf.py 프록시 코드 재독
6주차제어 평면 설계 & LoRAfuture/폴링 패턴, 비동기 API 설계. LoRA 원 논문 요약 + patches/ 폴더의 몽키패치 기법 정리. 과제 5(GPU 있으면) 도전
학습 순서 팁

이 저장소는 "RL 이론 → 분산학습 시스템 → API 설계"라는 세 개의 서로 다른 전문 분야가 겹쳐 있다. 한 번에 다 이해하려 하면 좌절하기 쉽다. 먼저 클라이언트 관점(SDK가 뭘 보내는지) → 그다음 control plane(어떻게 라우팅하는지) → 마지막에 백엔드 내부(AReaL·SGLang이 실제로 뭘 하는지) 순서로 3단 겹겹이 파고드는 게 좋다. RL 이론이 약하다면 2~3주차를 건너뛰지 말고 먼저 다지고 오는 걸 추천한다 — GRPO 코드를 읽어도 "왜 그렇게 계산하는지"가 안 잡히면 암기가 되어버린다.

10핵심 키워드 사전

이 문서와 저장소에 나온 주요 용어 정리.

용어
RLHF (Reinforcement Learning from Human Feedback)
사람(또는 사람을 흉내 낸 보상함수)의 피드백을 보상 신호로 삼아 LLM을 강화학습으로 다듬는 기법. GRPO도 이 큰 틀에 속하는 한 방법론이다.
용어
SFT (Supervised Fine-Tuning, 지도 파인튜닝)
"프롬프트 → 정답 완성문" 쌍으로 이루어진 데이터로 모델이 원하는 답변 스타일을 흉내 내도록 학습시키는, 가장 기본적인 파인튜닝 방식. 이 저장소의 loss_fn="cross_entropy" 경로가 이에 해당한다.
용어
GRPO (Group Relative Policy Optimization, 그룹 상대 정책 최적화)
같은 프롬프트에 대해 여러 응답을 샘플링(그룹)하고, 그 그룹 내부의 상대적 우열로 advantage를 매겨 정책을 학습시키는 RL 기법. 별도의 가치함수(value critic) 네트워크가 필요 없어 PPO보다 인프라가 단순하다.
용어
PPO (Proximal Policy Optimization, 근접 정책 최적화)
정책을 한 번에 너무 크게 바꾸지 않도록 "클리핑(clipping)"으로 업데이트 폭을 제한하는 강화학습 알고리즘. 이 저장소에서는 loss_fn="ppo" 경로로 GRPO의 두 번째 단계(클리핑 적용판)에도 쓰인다.
용어
LoRA (Low-Rank Adaptation, 저랭크 적응)
모델 전체를 다시 학습시키는 대신, 각 레이어 옆에 아주 작은 저랭크 행렬 한 쌍만 붙여 그것만 학습시키는 파라미터 효율적 파인튜닝 기법. 270억 파라미터 모델도 lora_rank=16 정도면 극히 일부만 학습해 GPU 메모리·저장공간을 크게 아낄 수 있다.
용어
FSDP2 (Fully Sharded Data Parallel v2)
모델의 파라미터·그래디언트·옵티마이저 상태를 여러 GPU에 쪼개어(shard) 저장하고, 계산 순간에만 모았다가 다시 흩뿌리는 PyTorch의 최신 분산학습 기법. 단일 GPU 메모리에 못 들어가는 거대 모델을 학습 가능하게 한다.
용어
SGLang
LLM 추론(응답 생성)을 빠르게 처리하기 위한 오픈소스 서빙 엔진. RadixAttention이라는 프리픽스 캐싱 기법으로 반복되는 프롬프트 앞부분의 계산을 재사용한다.
용어
롤아웃(Rollout)
현재 정책(모델)으로 실제 응답을 생성해 보는 과정. GRPO에서는 "같은 프롬프트로 여러 개 롤아웃을 뽑아 그룹을 구성"하는 게 핵심 단계다.
용어
어드밴티지(Advantage)
"이 행동(응답)이 평균보다 얼마나 더/덜 좋았는가"를 나타내는 값. GRPO에서는 그룹 내 보상의 평균·표준편차로 정규화해 계산한다. 양수면 그 응답 방향으로, 음수면 반대 방향으로 정책을 이동시킨다.
용어
가치 크리틱(Value Critic)
전통적 PPO에서 "이 상태가 앞으로 얼마나 좋은 보상을 낼지" 미리 추정하는 별도의 신경망. GRPO는 그룹 상대 비교로 advantage를 대신 계산해 이 네트워크를 아예 없앤다.
용어
control plane (제어 평면)
실제 무거운 계산(GPU 학습·추론)을 직접 하지 않고, 요청을 받아 "누구에게 어떤 작업을 맡길지" 결정하고 상태를 관리하는 계층. AReaL-MinT에서는 FastAPI 앱 전체가 이 역할이다.
용어
future / 폴링(Polling)
오래 걸리는 작업의 결과를 "나중에 준비될 값"으로 즉시 반환받고(future), 클라이언트가 주기적으로 "다 됐나요?"라고 물어보는(폴링) 비동기 패턴. HTTP 커넥션을 장시간 붙잡지 않아도 되는 장점이 있다.
용어
MoE (Mixture of Experts, 전문가 혼합)
모델 내부에 여러 개의 "전문가" 서브네트워크를 두고, 입력마다 일부만 선택적으로 활성화시키는 아키텍처. Qwen3.6 35B-A3B가 이 구조로, 전체 파라미터는 크지만 추론 시 실제 활성화되는 파라미터(A3B ≈ 3B)는 훨씬 작다.
용어
Tinker
Thinking Machines가 제공하는 매니지드(클라우드 호스팅) LLM 파인튜닝 API. AReaL-MinT는 이 Tinker의 클라이언트 API 표면과 호환되도록 설계되어, 기존 Tinker용 코드를 import mint as tinker로 그대로 재사용할 수 있다.

11참고 링크

공식 저장소 · 관련 프로젝트 · 논문.

구분링크
GitHub 저장소github.com/areal-project/AReaL-MinT
MinT SDKgithub.com/MindLab-Research/mindlab-toolkit
MinT 문서github.com/MindLab-Research/mint-doc
MinT Quickstartgithub.com/MindLab-Research/mint-quickstart
MinT Cookbookgithub.com/MindLab-Research/mint-cookbook
AReaL (학습 엔진)github.com/areal-project/AReaL
SGLang (추론 엔진)github.com/sgl-project/sglang
Tinker 문서(비교 대상)tinker-docs.thinkingmachines.ai
논문 (MinT)arXiv:2605.13779 — "MinT: Managed Infrastructure for Training and Serving Millions of LLMs" (Mind Lab, 2026)
라이선스Apache-2.0 (상업적 이용 가능)