글 목록으로

vLLM 오픈소스 기여 후기

PR 제출부터 머지까지, 4개월간의 vLLM 기여 과정

개요

vLLM은 LLM 추론을 위한 대표적인 오픈소스 프로젝트입니다. 2025년 4월, Hermes2ProToolParser 개선 사항을 PR #16890으로 기여했고, 8월에 main 브랜치에 병합되었습니다.

이 글에서는 기여하게 된 배경과 구현 내용, 그리고 오픈소스 기여 과정에서 배운 점을 공유합니다.

문제 상황

당시 저는 개인 프로젝트로 tool calling 특화 모델을 학습하고 있었습니다. xLAM, BitAgent, ToolACE 등 3B~8B 규모 모델들이 BFCL(Berkeley Function Calling Leaderboard) 상위권을 기록하던 시기였고, 저도 비슷한 규모에서 어디까지 성능을 끌어올릴 수 있는지 실험하고 있었습니다.

학습을 마치고 vLLM으로 추론 테스트를 하던 중, 벤치마크 점수가 예상보다 한참 낮게 나왔습니다. 모델 문제인가 싶어 디버깅을 시작했는데, 알고 보니 파서 쪽 문제였습니다.

vLLM의 기존 Hermes2ProToolParser는 <tool_call></tool_call> 태그가 토크나이저에서 별도의 special token으로 정의된 모델에서만 정상 동작했습니다.

예를 들어, NousResearch/Hermes-3-Llama-3.1-8B는 Llama 3 토크나이저를 편집해 128002, 128013 토큰을 도구 호출용으로 할당한 케이스입니다.

하지만 Llama 계열 모델을 별도의 토크나이저 편집 없이 Hermes 포맷으로 fine-tuning 하면, 이 태그들이 special token이 아니라 일반 텍스트로 토큰화됩니다.

"<tool_call>" → ["<", "tool", "_", "call", ">"]

이렇게 여러 토큰(또는 스트리밍 델타)으로 분리되면, 기존 파서가 tool call을 제대로 감지하지 못하는 문제가 발생했습니다.

물론 NousResearch처럼 reserved_special_token 일부를 편집해 도구 호출 전용 special token을 할당한 뒤 학습할 수도 있습니다. 다만 제 실험에서는 LoRA로 학습할 경우, special token을 추가하지 않고 학습한 모델 대비 오히려 성능이 떨어지는 경향을 확인했습니다.

왜 LoRA에서 special token이 잘 학습되지 않는가?

새로운 토큰을 토크나이저에 추가하면, 해당 토큰의 embedding을 학습해야 합니다. Axolotl에서는 lora_modules_to_saveembed_tokens(토큰→임베딩)와 lm_head(임베딩→토큰 확률)를 지정해 이 문제를 해결할 수 있습니다. 이 설정은 PEFT의 modules_to_save를 사용하며, 지정된 모듈은 LoRA가 아닌 full fine-tuning으로 학습됩니다.

학습 자체는 잘 되지만, embedding matrix 전체가 체크포인트에 포함되면서 adapter 파일이 GB급으로 커집니다. LoRA 본연의 장점인 작은 파일 크기와, 다른 base model에 adapter만 교체해서 적용할 수 있는 유연성이 사라지는 셈이죠.

반대로 이 설정 없이 학습하면 LoRA의 장점은 유지되지만, 새로 추가한 special token의 embedding이 학습되지 않습니다. 실험 결과, special token을 추가해서 embedding까지 학습시킨 경우(minpeter/QLoRA-Llama-3.2-1B-chatml-tool-v3)와 기존 토큰 조합으로 학습한 경우(minpeter/QLoRA-Llama-3.2-1B-chatml-tool-v4) 사이에 유의미한 성능 차이도 없었습니다.

결국 토크나이저를 편집하지 않고 기존 토큰 조합으로 학습하는 것이 LoRA 환경에서는 더 실용적인 선택이었습니다.

해결 방법

당장 벤치마크를 돌려야 했기에, 먼저 vLLM의 Tool Parser Plugin 기능을 활용해 minpeter/hermes-llama-parse에서 프로토타입을 구현했습니다. 막상 완성하고 보니 메인스트림에 올려도 될 만한 퀄리티 같아서, 이를 vLLM에 PR로 제출하게 되었습니다.

핵심 아이디어는 버퍼링 메커니즘을 추가하는 것입니다. 스트리밍 출력은 한 번에 <tool_call>이 통째로 들어오지 않고, 중간 단위(delta)로 쪼개져 들어오는 경우가 있습니다. 이때 태그 시작 가능성이 보이면 버퍼에 누적했다가, 태그가 완성되는 시점에만 파서로 넘겨주도록 했습니다.

  1. 스트리밍 출력에서 <로 시작하는 구간이 등장하면 버퍼링 시작
  2. <tool_call> 또는 </tool_call>이 완성될 때까지 버퍼에 누적
  3. 완성되면 tool call로 파싱하고, 끝내 완성되지 않으면 일반 텍스트로 처리

아래는 버퍼링 로직의 핵심 부분입니다.

def tool_call_delta_buffer(self, delta_text: str):
    if (delta_text in self.tool_call_start_token_array
            or delta_text in self.tool_call_end_token_array):
        if (delta_text == self.tool_call_start_token_array[-1]
                or delta_text == self.tool_call_end_token_array[-1]):
            buffered_text = self.buffered_delta_text
            self.buffered_delta_text = ""
            return buffered_text + delta_text
        else:
            self.buffered_delta_text = self.buffered_delta_text + delta_text
            return ""
    else:
        if self.buffered_delta_text:
            buffered_text = self.buffered_delta_text
            self.buffered_delta_text = ""
            return buffered_text + delta_text
        else:
            return delta_text

vLLM 기여 과정

1. PR 제출 (4월 20일)

hermes-llama-parse에서 검증된 구현을 vLLM에 PR로 제출했습니다.

2. 코드 리뷰 (6월)

vLLM 메인테이너 @aarnphm으로부터 테스트 케이스 추가 요청을 받았습니다.

"Is there a test fine-tuned model that we can use to test this?"

테스트를 위해 직접 fine-tuning 한 모델 minpeter/LoRA-Llama-3.2-1B-tool-vllm-ci를 사용해 e2e 테스트를 작성했습니다.

처음에는 기존에 학습해 둔 3B 모델(minpeter/m-3b-v1-iteration-00-sf-xlam-09)로 e2e 테스트를 작성했는데, 리뷰가 바로 진행되지는 않았습니다. 예전에 메인테이너가 "maybe a very small finetune llama3.2-1b would work here."라고 언급했던 게 떠올라, 혹시 모델 크기 때문에 검토가 지연되는 건가 싶었습니다. 그래서 Llama 3.2 1B 기반으로 새로 학습해 모델을 교체했던 기억이 납니다.

3. 머지 (8월 16일)

약 4개월간의 기다림 끝에 최종 머지되었습니다.

Approved 이후 메인테이너가 PR에 auto-merge를 켜 주셨는데, 당시 업스트림 이슈로 인해 일부 CI가 실패하던 상황이었습니다. 머지를 위해서는 모든 CI가 통과해야 했지만, 제 쪽에서 재시도할 방법이 없어서 main 브랜치에 커밋이 생길 때마다 Update branch를 누르며 세 번 정도 기다렸고, 그 끝에 겨우 머지되었습니다.

배운 점

오픈소스 기여는 인내가 필요하다

PR 제출부터 머지까지 약 4개월이 걸렸습니다. 대형 오픈소스 프로젝트는 PR이 많이 쌓여 있고, 메인테이너들도 바쁘기 때문에 처리에 시간이 걸릴 수 있습니다. 생각보다 더 느긋한 마음으로 기다리는 게 중요하다고 느꼈습니다.

물론 저는 기다림과는 거리가 멀어서 vLLM Slack의 #feat-tool-calling 채널까지 찾아가 리뷰를 부탁하긴 했는데, 이게 도움이 됐는지는 잘 모르겠습니다.

CONTRIBUTING.md를 (꼼꼼히) 읽자

PR을 올리기 전에 CONTRIBUTING.md를 정독하는 습관이 정말 중요하다고 느꼈습니다. PR 제목 컨벤션, DCO 서명, 린팅 등 디테일이 생각보다 많습니다. 리뷰어가 하루에 수십 개 PR을 보는 상황을 생각하면, 사소한 컨벤션(예: snake_case)까지 한 번 더 점검하는 것만으로도 서로가 훨씬 편해질 수 있겠다는 생각이 들었습니다.

마무리

이번 기여를 통해 vLLM에서 Hermes 포맷으로 fine-tuning 된 Llama 기반 모델들의 tool calling이 정상 동작하게 되었습니다. 이제 토크나이저를 별도로 편집하지 않아도 Hermes 포맷의 tool call이 스트리밍에서 안정적으로 파싱됩니다.

오픈소스 기여는 막상 해보면 생각보다 접근 가능합니다. 사용하면서 불편한 점을 발견했다면, 그것이 곧 기여의 출발점이 될 수 있습니다.

관련 링크

초안:
발행:
수정:

이전글 / 다음글