AGI Soon As Possible
Article

허깅페이스는 NCCL 없이 노드를 쪼갠 비동기 GRPO 학습을 3시간 27분에서 53분으로 줄였다

2026-09-14 · 10분 읽기

허깅페이스 엔지니어 4인이 2026년 9월 10일 공개한 기술 블로그 'Async GRPO with LoRA across HF Jobs'는 학습 잡과 추론 잡을 서로 다른 머신에 올리고 NCCL 통신을 아예 없앤 강화학습 구성으로 500스텝 학습을 3시간 27분에서 53분으로 단축했다고 밝혔다. 핵심 장치는 랭크 1 LoRA 어댑터로, 1.5B 모델의 전체 가중치가 약 3GB인 데 비해 이 어댑터는 수 메가바이트에 불과해 스토리지 버킷을 통해 파일로 옮길 수 있다. ASAP은 이 글에 실린 다섯 차례 실험 기록과 계측값만으로 병목이 어떻게 옮겨 다녔는지 정리한다.

랭크 1 어댑터가 수 메가바이트라는 사실이 이 구성 전체를 가능하게 만든다

강화학습에서 LoRA가 통하는 근거는 학습 신호의 양에 있다. 씽킹 머신스 랩이 2025년 9월 공개한 'LoRA Without Regret'은 정책 경사 강화학습에서 LoRA가 랭크 1로도 전체 미세조정에 필적한다고 보였고, 그 이유로 이점 함수가 에피소드당 약 1비트 수준의 정보만 준다는 점을 들었다. 흡수할 정보가 적으니 랭크 1 어댑터의 용량으로 충분하다는 논리다.

허깅페이스 글이 짚은 것은 여기에 딸려 오는 시스템 측면의 결과다. 1.5B 모델의 랭크 1 어댑터는 수 메가바이트이고 전체 모델은 약 3GB이므로, 정책을 갱신할 때마다 기가바이트를 추론 워커로 보내는 대신 어댑터만 보내면 된다. vLLM은 어댑터를 여러 개 동시에 올려 둘 수 있어서, 이미 시작된 롤아웃은 시작할 때의 정책으로 끝내고 새 롤아웃만 최신 정책을 쓴다.

제약은 허깅페이스 잡(HF Jobs)의 구조에서 나왔다. 잡 하나는 VM 한 대에서 도는 컨테이너 하나이며 노드당 최대 8xH200까지다. 잡 하나가 여러 노드를 띄워 트레이너와 vLLM 서버 무리를 함께 담을 수 없고, 잡끼리는 노드를 가로질러 통신하지도 못한다. 공유 로컬 디스크도 없고 공유 localhost도 없다. 전체 가중치를 동기화해야 한다면 여기서 끝났을 구성이다.

해법은 파일시스템 쪽에서 나왔다. 허깅페이스 잡은 스토리지 버킷을 볼륨으로 제공하고, 이 버킷은 FUSE 파일시스템으로 모든 잡에 마운트된다. 트레이너는 몇 스텝마다 어댑터를 저장하고 원자적 이름 변경으로 디렉터리를 공개한 뒤 그 경로를 vLLM의 /v1/load_lora_adapter 엔드포인트로 보낸다. vLLM은 텐서가 아니라 경로를 받으므로 두 프로세스가 같은 경로를 보기만 하면 된다. TRL과 vLLM 어느 쪽도 코드를 고치지 않았다.

구성은 잡 세 개와 버킷 하나와 프록시 하나로 끝난다

실행 구성은 AsyncGRPOTrainer를 LoRA와 FSDP로 돌리는 트레이너 잡 하나, 베이스 모델과 어댑터를 서빙하는 vLLM 잡 두 개, 세 잡 모두에 같은 절대 경로로 마운트된 스토리지 버킷 하나, 그리고 트레이너 잡의 127.0.0.1:8000에서 도는 프록시 하나다. 트레이너는 h200x2 잡을 쓰고 각 vLLM 복제본은 H200 한 장을 쓰며, 세 잡을 함께 돌리는 비용은 시간당 약 20달러다.

어댑터 슬롯 수는 max_staleness에서 따라 나온다. 동기화 한 번마다 정책 버전이 1씩 오르고, max_staleness는 롤아웃 샘플이 현재 정책보다 몇 버전까지 뒤처져도 학습에 쓰이는지를 정한다. 값이 4이면 vLLM은 현재 정책과 그 앞 네 개를 동시에 서빙해야 하고, 교체 중에는 슬롯이 하나 더 필요하다. 그래서 --max-loras 6이다. 5로 두면 아직 롤아웃이 진행 중인 정책을 vLLM이 조용히 밀어낸다.

버전별 이름을 쓰는 이유는 캐시 오염 때문이다. 어댑터를 항상 같은 이름으로 덮어쓰면 vLLM이 어댑터 이름으로 프리픽스 캐시를 키잉하므로, 이전 가중치로 계산된 KV 블록이 교체 후에도 매칭돼 프리필이 다시 돌지 않는다. 롤아웃 하나가 프리픽스는 옛 정책, 디코드는 새 정책으로 만들어져도 트레이너는 알 길이 없고, 비율 지표가 1에서 멀어지는 증상으로만 나타난다.

프록시가 필요한 이유는 두 가지다. 첫째, 잡의 노출 포트는 매 요청에 허깅페이스 토큰 인증 헤더를 요구하므로 그 헤더를 붙일 곳이 필요하다. 둘째, 복제본이 둘 이상일 때 어댑터 로드를 전부에 뿌려야 한다. vLLM의 데이터 병렬 모드에서는 /v1/load_lora_adapter 호출이 응답한 랭크에만 닿기 때문에 TRL이 어댑터 전용 동기화를 거부하며, 잡 구조에서는 각 복제본이 별도 머신이라 데이터 병렬이 한 층 위로 올라간다. TRL 입장에서 이 프록시는 data_parallel_size=1인 단일 vLLM 서버로 보인다.

라우터는 롤아웃 6만 4728건에서 친화도 적중 84.5퍼센트를 냈다

프록시의 두 번째 기능인 라우팅은 KV 캐시 재사용을 노린다. GRPO는 같은 프롬프트로 요청 G개를 보내며 이 실험에서 G는 8이다. 여덟 요청이 같은 복제본에 닿으면 첫 요청만 프리필을 계산하고 나머지 일곱이 그것을 재사용한다. 라운드로빈으로 뿌리면 절반이 프리필을 다시 계산해 GPU 연산을 버린다.

라우터는 vLLM과 같은 16토큰 블록으로 프롬프트를 자르고 블록 해시를 이어서 계산한다. 3번 블록의 해시는 1번과 2번과 3번을 함께 나타내며, 이는 인과 어텐션의 성질을 그대로 옮긴 것이다. 해시 체인의 시작값에는 어댑터 이름을 넣는다. v3에서 캐시된 프리픽스는 v4에 쓸모없기 때문이다.

까다로운 부분은 공통 프리픽스다. 이 실험에서 1,460개 문제는 전부 같은 채팅 템플릿 23토큰으로 시작하고, 이 블록은 몇 초 안에 모든 복제본 캐시에 들어간다. 단순 최장 프리픽스 매칭을 쓰면 새 프롬프트마다 첫 복제본이 적중한 것처럼 보인다. 라우터는 후속 블록의 분기 수로 이를 걸러낸다. 후속이 여럿인 블록은 공통으로 보고 무시하며, 항상 같은 후속으로 이어지는 블록만 특정 프롬프트의 것으로 센다.

배치 규칙은 세 갈래다. 특정 블록을 가진 복제본이 있고 그 복제본이 최저 부하 복제본보다 8건 이상 앞서 있지 않으면 그쪽으로 보내며 이를 친화도 적중으로 센다. 8건을 넘게 앞서 있으면 캐시를 포기하고 최저 부하 쪽으로 보내며 이를 스필로 센다. 어디에도 특정 블록이 없으면 새 프롬프트이므로 최저 부하 쪽으로 보내고 미매칭으로 센다. 실행이 끝난 시점의 카운터는 롤아웃 6만 4728건에서 친화도 5만 4712건과 미매칭 9,196건과 스필 820건, 즉 84.5퍼센트와 14.2퍼센트와 1.3퍼센트였다. 프롬프트당 여덟 요청 중 최소 하나는 차가울 수밖에 없으므로 미매칭의 이론적 하한은 12.5퍼센트다.

어댑터 브로드캐스트는 전부 아니면 전무로 처리한다. 복제본마다 버킷 마운트가 따로라 새 어댑터를 보는 시점이 어긋날 수 있고, 경로를 찾지 못했다는 오류가 나면 그 복제본만 재시도한다. 다른 오류가 나면 이미 받아들인 복제본에서 어댑터를 내려 정책 이름이 일부 복제본에만 존재하는 상태를 막는다. 실행 전체에서 어댑터 로드 252건이 모두 성공했고, 이는 동기화 126회에 복제본 2개를 곱한 수다.

다섯 차례 실험의 실제 교훈은 병목이 한 번도 같은 곳에 머물지 않았다는 점이다

여기서부터는 ASAP의 해석이다. 이 글의 성능 서사는 최적화 다섯 개를 쌓아 3.9배를 얻었다는 흔한 구조가 아니다. 매번 고친 쪽이 아니라 반대쪽이 새 병목이 됐고, 그 사실을 계측값이 즉시 드러냈다는 것이 실제 내용이다.

실행 1에서 스텝은 22.9초였고 그중 순전파와 역전파가 21.9초로 96퍼센트를 차지했다. 트레이너 MFU는 3.9퍼센트였고 롤아웃 대기는 0.02초, 큐는 512 중 476에 고정돼 있었다. 두 번째 복제본은 사실상 놀고 있었다. 원인은 참조 레시피의 per_device_train_batch_size=1이었고, 랭크당 1.2k 토큰짜리 시퀀스 하나를 스텝마다 64번 처리하는 구성은 H200에서 완전히 지연 시간에 묶인다.

실행 2의 수정은 배치 크기가 아니라 배치 모양이었다. 스텝당 완성 128개는 그대로 두고 token_budget을 16384로, 경사 누적을 6으로 설정해 여러 샘플을 패딩 없이 한 행에 채웠다. 행당 샘플이 1.0에서 약 12.7로 오르고 마이크로배치가 64에서 6으로 줄면서 순전파와 역전파는 21.9초에서 5.6초, MFU는 19퍼센트가 됐다. 여기서 가장 흥미로운 숫자는 생성 쪽이다. vLLM 설정을 하나도 건드리지 않았는데 생성량이 초당 4.6k 토큰에서 25k 토큰으로 올랐다. 큐가 더 이상 꽉 차 있지 않으니 복제본이 비로소 제 속도로 돈 것이다.

실행 3은 기본값 하나를 껐다. AsyncGRPOConfig의 gradient_checkpointing이 기본으로 켜져 있어 마이크로배치마다 역전파 중 순전파를 다시 계산하고 있었고, 16k 토큰 행이 141GB H200에서 25GB만 쓰는 이상 현상이 그 증거였다. 끄자 순전파와 역전파가 4.6초로 정확히 순전파 한 번만큼 줄고 MFU는 23퍼센트가 됐다. 그러자 큐가 71까지 떨어지고 롤아웃 대기가 0.6초로 올랐다. 병목이 생성으로 넘어간 것이다.

실행 4에서 드러난 것은 vLLM의 한계가 아니라 자기 코드의 상수였다

실행 4에서 연구진은 복제본을 세 개로 늘렸고, 생성량은 초당 25k에서 26k 토큰으로만 움직였다. 세 번째 GPU가 거의 아무 일도 하지 않았다. 원인은 rollout/inflight 지표가 두 실행 모두 128로 같았다는 데 있었고, 프록시가 보여 준 분배는 44와 43과 41이었다. max_inflight_tasks가 복제본별이 아니라 롤아웃 워커 전체의 동시성을 제한하고 있었다.

이 지점이 이 글에서 가장 실무적인 대목이다. 1.5B 모델을 H200에 올리면 동시 시퀀스 43개와 130개의 토큰당 비용이 거의 같다. 128개를 GPU 세 장에 나누는 것은 두 장에 나누는 것과 처리량이 사실상 같다는 뜻이다. 하드웨어를 늘려도 성능이 오르지 않을 때 원인이 하드웨어가 아니라 클라이언트 쪽 상수인 경우가 있고, 그 상수는 대개 초기에 안전하게 잡아 둔 뒤 검증 없이 남는다. 이 팀도 공개 잡 프록시를 통과하는 긴 HTTPS 요청 수백 건의 거동을 몰라 보수적으로 잡았다고 적었으며, 그 시점까지 롤아웃 완성 13만 건이 전송 오류 없이 통과한 상태였다.

실행 5는 max_inflight_tasks를 384로, queue_maxsize를 768로 올린 것이 전부다. 복제본당 128개가 배정되면서 큐는 768 중 약 690까지 차고 롤아웃 대기는 0.03초로 떨어졌다. 병목은 다시 학습으로 돌아왔고 중앙값 스텝 시간은 4.8초가 됐다. 500스텝 기준으로 실행 1의 3시간 27분이 실행 5에서 53분이 됐고, MFU는 3.9퍼센트에서 23.5퍼센트로, 학습에 쓰인 샘플은 6만 4000개에서 8만 4078개로 31퍼센트 늘었다.

이 기록에서 검증된 것과 검증되지 않은 것을 구분해야 한다

가장 확실하게 검증된 항목은 정확성이다. 비율 지표가 전 구간에서 1.000을 유지했고 126회 동기화 전부에서 그랬다. vLLM이 서빙한 정책과 트레이너가 롤아웃을 채점할 때 쓴 정책이 언제나 일치했다는 뜻이며, 파일 기반 동기화가 조용히 어긋나지 않았다는 직접 증거다. 평균 지연은 실행 1에서 1.5버전, 실행 5에서 2.0버전으로 max_staleness 4보다 낮게 유지됐다.

데이터셋 선택도 의도적이다. 연구진은 sail/Sanity-Test-R1D-1.5B를 썼는데, 이 데이터셋은 DeepSeek-R1-Distill-Qwen-1.5B로 MATH 문제마다 답 40개를 생성해 성공률이 20퍼센트에서 80퍼센트 사이인 문제 1,460개만 남긴 것이다. 너무 쉽지도 너무 어렵지도 않아 몇십 스텝 안에 학습 곡선이 움직이므로, 복제본 하나가 어댑터 이름으로 베이스 모델을 서빙하는 종류의 조용한 고장이 곡선에서 바로 드러난다. 인프라 실험의 검증 장치로 데이터셋을 고른 셈이다.

반면 검증되지 않은 것도 명확하다. 보상은 처음 20스텝 0.145에서 마지막 20스텝 0.438로 올랐지만, 이는 1.5B 수학 모델의 위생 검사용 수치이지 성능 주장이 아니다. 실행 5의 보상은 0.416으로 실행 1보다 약간 낮으며, 글은 이를 같은 보상 곡선을 더 짧은 시간에 그렸다는 의미로 제시한다. 모델 품질이 좋아졌다는 근거로 읽으면 안 된다.

구성 요소의 고정 조건도 남는다. vLLM은 v0.27.1로 고정돼 있고 글은 이 버전을 레시피의 일부로 취급하라고 명시한다. LoRA 지원은 TRL PR #7017로 들어와 v1.14에 실렸으며, 그보다 이전 버전에서는 이 경로 자체가 없다. DoRA나 modules_to_save처럼 vLLM이 그대로 서빙할 수 없는 구성은 병합 가중치 동기화로 폴백하며, 그 순간 이 구성의 전제인 수 메가바이트 전송이 깨진다.

국내 팀이 가져갈 지점은 노드 제약을 우회하는 발상 쪽이다

이 구성이 국내 환경에서 갖는 가치는 비용보다 제약 회피에 있다. 다수 국내 팀이 마주하는 조건은 GPU 부족보다 여러 노드를 하나의 학습 잡으로 묶을 권한이 없는 상황이다. 공용 클러스터의 할당 정책이나 클라우드 서비스의 잡 모델이 노드 간 집합 통신을 허용하지 않을 때, 지금까지의 기본 대응은 단일 노드 안으로 규모를 줄이는 것이었다.

이 글이 제시하는 대안은 동기화 대상을 줄이면 통신 요구 자체가 사라진다는 것이다. 수 메가바이트짜리 어댑터는 오브젝트 스토리지를 파일시스템처럼 마운트한 경로로 옮길 수 있고, 그 순간 NCCL도 공유 디스크도 필요 없어진다. 느슨하게 연결된 자원을 하나의 학습 파이프라인으로 엮는 일반적인 패턴이며, 사내 클러스터와 클라우드를 동시에 쓰는 구성에도 같은 논리가 적용된다.

두 번째로 가져갈 것은 계측 습관이다. 이 글의 다섯 실행은 전부 대시보드의 네 묶음 지표로 진단됐다. 스텝 시간과 순전파 역전파 시간, 롤아웃 대기 시간, 큐 점유율, 역압 시간이다. 큐가 꽉 찼는데 롤아웃 대기가 0이고 역압이 높으면 트레이너가 느린 것이고, 큐가 비었는데 롤아웃 대기가 오르고 역압이 없으면 생성이 느린 것이다. 글은 각 경우 대시보드가 10분 안에 답을 줬다고 적었다. 비동기 파이프라인을 돌리는 팀이라면 이 두 지표 쌍을 먼저 붙이는 것이 순서다.

마지막은 규모에 대한 정직한 인식이다. 이 실험은 H200 세 장에 1.5B 모델과 문제 1,460개짜리 데이터셋이며, 파이썬 asyncio 프록시가 병목이 되지 않은 것도 동시 비스트리밍 JSON 요청이 최대 128건뿐이었기 때문이라고 글이 직접 밝힌다. 더 많은 트래픽을 감당할 정교한 라우터는 더 빠른 언어로 써야 할 것이라는 문장도 함께 들어 있다. 재현 가능한 실험 기록이며 프로덕션 학습 스택의 검증서가 아니다.

출처: Amine Dirhoussi, Quentin Gallouédec, Kashif Rasul, Sergio Paniego, 'Async GRPO with LoRA across HF Jobs: a bucket, a proxy, and no NCCL'(Hugging Face Blog, 2026년 9월 10일) · 재현 코드 github.com/AmineDiro/hfjobs-lora-buckets

ASAP — AGI Soon As Possible

AI·테크 이슈,
가장 깊게

단순 소식을 넘어, 맥락과 구조까지 파고듭니다

AGI Soon As Possible · asapai.co.kr

← 전체 글 보기