실시간 RL로 Composer 개선하기
우리는 현실 세계에서 코딩 모델의 유용성과 채택이 전례 없이 증가하고 있는 것을 보고 있습니다. 추론량이 10~100배 증가하는 상황에서, 우리는 다음과 같은 질문을 던집니다. 이 수조 개의 토큰에서 어떻게 학습 신호를 추출해 모델을 개선할 수 있을까요?
우리는 실제 추론 토큰을 학습에 사용하는 이 접근법을 "실시간 RL"이라고 부릅니다. 우리는 먼저 이 기법을 Tab 학습에 사용했고, 매우 효과적이라는 점을 찾아냈습니다. 이제 우리는 비슷한 접근법을 Composer에 적용하고 있습니다. 모델 체크포인트를 프로덕션에 서빙하고, 사용자 반응을 관찰한 다음, 그 반응을 보상 신호로 집계합니다. 이 접근법을 통해 우리는 Auto 뒤에서 짧게는 5시간마다 Composer의 개선된 버전을 배포할 수 있습니다.


학습-실사용 간 불일치
Composer와 같은 코딩 모델을 학습시키는 주된 방식은, 모델이 실제 사용에서 마주하게 될 환경과 문제를 최대한 충실하게 재현하도록 의도된 시뮬레이션 코딩 환경을 만드는 것입니다. 이 방식은 매우 잘 작동해 왔습니다. 코딩이 RL에 특히 효과적인 분야인 이유 중 하나는, 로보틱스처럼 RL이 자연스럽게 적용되는 다른 분야와 비교했을 때, 모델이 배포되었을 때 작동하게 될 환경을 높은 충실도로 시뮬레이션하기가 훨씬 쉽기 때문입니다.
그럼에도 불구하고 시뮬레이션 환경을 재구성하는 과정에서는 여전히 일정한 학습-실사용 간 불일치가 발생합니다. 가장 큰 어려움은 사용자를 모델링하는 데 있습니다. Composer의 프로덕션 환경은 Composer의 명령을 실행하는 컴퓨터만으로 이루어지는 것이 아니라, 그 동작을 감독하고 지시하는 사람까지 포함합니다. 컴퓨터를 시뮬레이션하는 것보다 그것을 사용하는 사람을 시뮬레이션하는 일이 훨씬 더 어렵습니다.
사용자를 시뮬레이션하는 모델을 만드는 유망한 연구가 있기는 하지만, 이 접근법은 불가피하게 모델링 오류를 도입합니다. 학습 신호를 위해 추론 토큰을 사용하는 방식의 매력은 실제 환경과 실제 사용자를 사용할 수 있게 해준다는 점에 있으며, 이를 통해 이러한 모델링 불확실성과 학습-실사용 간 불일치의 원천을 제거할 수 있습니다.
5시간마다 새로운 체크포인트
실시간 RL을 위한 인프라는 Cursor 스택의 서로 다른 여러 계층에 의존합니다. 새로운 체크포인트를 만들어내는 과정은 사용자 상호작용을 신호로 변환하는 클라이언트 측 계측에서 시작해, 그 신호를 학습 루프에 공급하는 백엔드 데이터 파이프라인으로 이어지며, 업데이트된 체크포인트를 실제 서비스에 빠르게 반영할 수 있는 신속한 배포 경로로 마무리됩니다.
더 세부적으로 보면, 각 실시간 RL 주기는 먼저 현재 체크포인트와의 사용자 상호작용에서 수십억 개의 토큰을 수집하고 이를 보상 신호로 정제하는 것에서 시작됩니다. 다음으로, 암묵적인 사용자 피드백을 바탕으로 모델의 모든 가중치를 어떻게 조정할지 계산하고, 업데이트된 값을 적용합니다.
이 시점에서도 업데이트된 버전이 예상치 못한 면에서 이전 버전보다 오히려 나쁠 가능성이 여전히 있으므로, CursorBench를 포함한 eval 스위트로 이를 테스트해 유의미한 성능 저하가 없는지 확인합니다. 결과가 좋으면 체크포인트를 배포합니다.
이 전체 과정은 약 5시간이 걸리며, 이는 개선된 Composer 체크포인트를 하루에도 여러 번 배포할 수 있다는 뜻입니다. 이는 데이터를 완전히 또는 거의 완전히 온폴리시 상태로 유지할 수 있게 해주기 때문에 중요합니다(즉, 학습 중인 모델이 데이터를 생성한 모델과 동일하다는 뜻입니다). 온폴리시 데이터가 있더라도 실시간 RL 목표는 노이즈가 많아, 진전을 확인하려면 큰 배치가 필요합니다. 오프폴리시 학습은 추가적인 어려움을 더하고, 목표 개선이 더 이상 일어나지 않는 지점을 넘어 행동을 과도하게 최적화할 가능성을 높입니다.
Auto에서 진행한 A/B 테스트를 통해 Composer 1.5를 개선할 수 있었습니다:
| 지표 | 변화 |
|---|---|
| 코드베이스에 남아 있는 Agent 수정 | +2.28% |
| 사용자가 불만족스러운 후속 응답을 보냄 | −3.13% |
| 지연 시간 | −10.3% |
실시간 RL과 reward hacking
모델은 reward hacking에 능합니다. 나쁜 보상을 피하거나 좋은 보상을 쉽게 얻을 수 있는 편법이 있다면, 모델은 그 방법을 찾아냅니다. 예를 들어 복잡도 지표를 속이기 위해 코드를 인위적으로 아주 작은 함수들로 쪼개는 식입니다.
이 문제는 특히 실시간 RL에서 더 두드러집니다. 여기서는 모델이 앞서 설명한 전체 프로덕션 스택을 상대로 자신의 동작을 최적화하기 때문입니다. 데이터 수집 방식부터 신호로 변환되는 방식, 보상 로직에 이르기까지 스택의 각 단계는 모델이 악용하는 법을 학습할 수 있는 지점이 됩니다.
Reward hacking은 실시간 RL에서 더 큰 위험이지만, 동시에 모델이 이를 끝까지 밀어붙이기는 더 어렵습니다. 시뮬레이션 RL에서는 속임수를 쓰는 모델이 그저 더 높은 점수를 올리면 그만입니다. 벤치마크 말고는 이를 지적할 기준이 없습니다. 반면 실시간 RL에서는 실제로 일을 해내려는 사용자들이 그렇게 관대하지 않습니다. 우리의 보상이 정말로 사용자가 원하는 것을 제대로 포착한다면, 그 보상을 높이는 일은 정의상 더 나은 모델로 이어집니다. 각각의 reward hack 시도는 사실상 학습 시스템을 개선하는 데 활용할 수 있는 버그 보고서가 됩니다.
다음은 이 과제와, 이에 대응해 Composer의 학습을 어떻게 조정했는지를 보여주는 두 가지 예시입니다.
Composer가 사용자에게 응답할 때는 파일 읽기나 터미널 명령 실행 같은 도구 호출이 필요한 경우가 많습니다. 원래는 도구 호출이 유효하지 않은 예시를 버렸는데, Composer는 자신이 실패할 가능성이 높은 작업에서 일부러 깨진 도구 호출을 출력하면 부정적인 보상을 받지 않는다는 점을 알아냈습니다. 그래서 우리는 깨진 도구 호출도 부정적인 예시로 올바르게 포함하도록 수정했습니다.
더 미묘한 형태는 수정 동작에서 나타나는데, 여기서는 보상의 일부가 모델이 수행한 수정에서 나옵니다. 한때 Composer는 자신이 작성하지 않은 코드로는 벌을 받지 않는다는 점을 파악하고, 확인 질문을 하면서 위험한 수정을 미루는 법을 학습했습니다. 전반적으로 우리는 프롬프트가 모호할 때 Composer가 이를 명확히 하고, 지나치게 성급하게 수정하지 않기를 바랍니다. 하지만 보상 함수의 특정한 특성 때문에 이 유인은 끝내 반전되지 않습니다. 그대로 두면 수정 비율이 급격히 떨어집니다. 우리는 모니터링을 통해 이를 포착했고 이 동작을 안정화하도록 보상 함수를 수정했습니다.


다음 단계: 더 긴 루프와 특화 학습
오늘날 대부분의 상호작용은 여전히 비교적 짧기 때문에 Composer는 수정 사항을 제안한 뒤 한 시간 이내에 사용자 피드백을 받습니다. 하지만 에이전트의 성능이 더욱 향상됨에 따라, 백그라운드에서 더 긴 작업을 수행하게 되고 몇 시간마다 한 번 또는 그보다도 드물게만 사용자 입력을 받으러 돌아올 것으로 예상합니다.
이로 인해 학습에 활용해야 하는 피드백의 성격도 달라집니다. 빈도는 낮아지지만, 사용자가 개별 수정 하나를 따로 평가하는 대신 완성된 결과 전체를 평가하기 때문에 더 명확해집니다. 저희는 이런 저빈도·고충실도 상호작용에 맞게 실시간 RL 루프를 조정하기 위해 노력하고 있습니다.
또한 코딩 패턴이 일반적인 분포와 다른 특정 조직이나 작업 유형에 맞게 Composer를 조정하는 방법도 모색하고 있습니다. 실시간 RL은 일반적인 벤치마크가 아니라 특정 집단의 실제 상호작용을 바탕으로 학습하기 때문에, 시뮬레이션 RL과 달리 이러한 종류의 특화를 자연스럽게 지원합니다.