vLLM Hook 소개
대규모 언어 모델(LLM)을 서비스로 배포할 때는 보통 vLLM 같은 추론 엔진 위에 모델을 올립니다. 추론 엔진은 지연 시간과 메모리, 하드웨어 사용을 최적화하기 위해 개발 단계에서 쓰던 여러 기능을 꺼 버리는데, 그 과정에서 모델 내부 상태에 접근하고 수정하는 길도 함께 막힙니다. vLLM Hook은 이렇게 닫혀 있던 어텐션(attention)·어텐션 헤드(attention head)·활성값(activation) 같은 내부 상태를 다시 프로그래밍할 수 있게 열어 주는 vLLM 플러그인입니다.
이 접근이 중요한 이유는 최근 주목받는 여러 테스트 시점(test-time) 기법이 모델 내부 상태에 손을 대야 동작하기 때문입니다. 예를 들어 어텐션 패턴으로 프롬프트 인젝션(prompt injection)을 탐지하거나, 활성값에 방향 벡터를 더해 응답을 조정하는 활성값 스티어링(activation steering)은 배포된 vLLM 모델에서는 그대로 쓰기 어렵습니다. 저자들은 이 공백을 메우기 위해, 설정 파일 하나로 어떤 내부 상태를 다룰지 지정하면 vLLM에 자연스럽게 붙는 플러그인을 설계했습니다.
vLLM Hook은 IBM Research의 Ching-Yun Ko와 Pin-Yu Chen이 만들었고, Apache 2.0 라이선스로 공개되어 있습니다. 프로젝트는 크게 두 가지 기능인 수동 프로그래밍(passive programming) 과 능동 프로그래밍(active programming) 을 제공합니다. 수동 프로그래밍은 모델 생성 결과는 그대로 둔 채 지정한 내부 상태만 관찰(probe)해 저장하고, 능동 프로그래밍은 생성 도중 내부 상태를 바꿔 출력에 개입합니다. 본 게시물에서는 이 두 기능의 동작 원리와 아키텍처, v0에서 제시한 활용 사례, 그리고 기존 방식과의 성능 비교를 정리합니다.
vLLM Hook이 여는 두 가지 프로그래밍: 수동과 능동
vLLM Hook의 출발점은 입력에 설정 파일(Config)을 함께 넘긴다는 점입니다. 네이티브 vLLM이 프롬프트만 받아 생성 결과를 돌려준다면, vLLM Hook은 프롬프트에 더해 "모델 내부에서 무엇을 관찰할지"를 적은 설정 파일을 함께 받습니다. 그 뒤 동작은 두 갈래로 나뉩니다.
수동 프로그래밍은 모델의 생성 과정을 건드리지 않습니다. 지정한 내부 상태를 순전파(forward pass) 도중에 캡처해 저장해 두고, 생성이 끝난 뒤 저장된 상태를 분석합니다. 모델이 원래 내놓았을 출력은 그대로 유지되므로, 프롬프트 인젝션 점수나 문서 관련도 점수처럼 "생성에 영향을 주지 않는 관찰"에 적합합니다.
능동 프로그래밍은 반대로 생성 중에 내부 상태를 바꿔 출력을 조정합니다. 활성값 스티어링처럼 특정 레이어의 활성값에 방향 벡터를 주입해 모델의 행동을 원하는 쪽으로 유도하는 개입이 여기에 해당합니다. 저자들은 다만 프로그래밍과 유용성 사이의 트레이드오프를 함께 강조합니다. 너무 많은 상태를 후킹하거나 계산량이 큰 프로그래밍 함수를 쓰면 추론 엔진의 처리량이 크게 떨어질 수 있으므로, 내부 상태에 대한 후크(hook)를 최소한으로 유지하는 설정 파일을 만들라고 권합니다.
vLLM Hook의 아키텍처: Worker와 Analyzer
vLLM Hook은 네이티브 vLLM 시스템을 감싸는 가벼운 래퍼로 구현되어 있습니다. HookLLM 클래스로 LLM 인스턴스를 초기화하고, 응답 생성에는 llm.generate 를, 저장된 상태를 분석할 때는 llm.analyze 를 호출하는 구조입니다.
핵심 추상화는 Worker 와 Analyzer 두 가지입니다. Worker는 vLLM 파이프라인 안에서 언제 어떻게 프로그래밍할지를 정의하고, Analyzer는 저장된 통계를 바탕으로 평가 지표를 계산합니다. Worker는 vLLM의 GPU 워커를 상속해 load_model 을 오버라이드하고, 기본 모델이 로드된 뒤 선택한 모듈에 PyTorch 순전파 후크를 설치합니다. 저장소의 docs/vLLM_Hook_v0.pdf에 실린 Worker 예시는 다음과 같은 형태입니다.
from vllm.v1.worker.gpu_worker import Worker as V1Worker
class ProbeHookQKWorker(V1Worker):
def load_model(self, *args, **kwargs):
r = super().load_model(*args, **kwargs)
self._install_hooks()
return r
def _install_hooks(self):
# 어떤 레이어/헤드를 후킹할지 설정에서 파싱
self.layer_to_heads = self._parse_layer_heads()
self.important_layers = set(self.layer_to_heads.keys())
model = getattr(self.model_runner, "model", None)
# 대상 어텐션 모듈에 forward hook 등록
for name, module in model.named_modules():
if name.endswith(".self_attn.attn") and "Attention" in str(type(module)):
layer_num = int(name.split('model.layers.')[1].split('.')[0])
if layer_num in self.important_layers:
module.register_forward_hook(...)
무엇을 후킹할지는 설정 파일이 결정합니다. 아래는 논문에 실린 설정 파일 예시로, model_info 에 대상 모델을, params/important_heads 에 관찰할 [레이어, 헤드] 인덱스를, hookq/hookq_mode 에 마지막 토큰의 쿼리 캐시만 캡처할지를 지정합니다.
{
"model_info": {
"name": "granite3-8b-attn",
"model_id": "ibm-granite/granite-3.1-8b-instruct"
},
"params": {
"important_heads": [[6, 9], [7, 20], [8, 1], [8, 13], [8, 14], ...]
},
"hookq": {
"hookq_mode": "last_token"
}
}
동작 방식은 모드에 따라 갈립니다. 능동 프로그래밍에서는 Worker가 모델 실행 단계에 개입해 맞춤 생성을 수행하고, 수동 프로그래밍에서는 기본 생성 흐름을 따르면서 순전파 도중 어텐션 같은 내부 상태를 캡처합니다. 순전파가 끝나면 사용자가 llm.analyze 를 호출하고, Analyzer가 저장된 상태를 모아 프롬프트 인젝션 점수나 문단 관련도 점수 같은 통계를 계산합니다.
vLLM Hook은 실행 경로와 저장 방식을 여러 축으로 조합해 지원합니다. 실행 경로는 인프로세스 HookLLM 을 쓰는 offline 과 vllm serve 로 띄운 서버에 붙는 serve, 저장 방식은 인메모리 rpc·디스크 disk·공유 메모리 shm, 디스크 포맷은 pt(torch.save)와 safetensors, 저장 시점은 인라인 sync 와 백그라운드 async 로 나뉩니다. 조합별 지원 범위는 저장소의 docs/configs.md에 정리되어 있습니다.
vLLM Hook의 개발 사이클: Build, Probe, Program
저자들은 vLLM Hook을 쓰는 전체 흐름을 세 단계로 설명합니다.
첫 단계 Build 는 vLLM 밖에서 이뤄집니다. 배포될 모델을 살펴 어떤 내부 상태(레이어·헤드 등)가 중요한지 찾아내는 단계로, 경우에 따라 데이터가 필요합니다. 두 번째 Probe 는 vLLM 위에서 후크로 대상 상태를 실제로 측정하는 단계이고, 마지막 Program 은 저장된 상태를 수동 모니터링에 쓰거나 능동적으로 개입에 활용하는 단계입니다. Build 단계에서 만든 설정 파일은 오픈소스로 공유해 다른 개발자가 자신의 작업에 재사용할 수 있으며, 저자들은 Build 단계 자체는 현재 vLLM Hook의 범위 밖이라고 밝히고 있습니다.
vLLM Hook v0의 세 가지 활용 사례
논문은 v0에서 서로 다른 내부 상태를 다루는 세 가지 예시를 제시합니다.
어텐션 트래커(Attention Tracker) 는 수동 프로그래밍으로 프롬프트 인젝션을 탐지합니다. 트랜스포머 모델의 선택된 어텐션 통계로 인젝션 시도를 잡아내는 "인모델 안전 가드레일" 방식으로, 배포된 모델의 입력과 출력을 별도 검열 모델로 검사하는 기존의 "캐스케이드형 가드레일" 과 다릅니다. AttntrackerAnalyzer 가 저장된 쿼리·키(query·key)로 선택 어텐션 가중치를 다시 계산해 공격 위험 점수를 매깁니다.
Core Reranker(CoRer) 는 정보 검색에서 문서 관련도를 재순위화하는 데 쓰입니다. 배포된 모델 안에서 선택된 어텐션 헤드만 활성화해 재순위 성능을 높이는 접근으로, 두 번의 실행 결과 차이를 계산해야 하므로 본질적으로 2패스(two-pass)이며 디스크 경로만 사용합니다.
활성값 스티어링(Activation Steering) 은 능동 프로그래밍의 대표 사례입니다. 배포된 모델의 특정 레이어 활성값에 스티어링 벡터를 주입해 지시 따르기(instruction following) 같은 행동을 개입 시점에 강화합니다. 잔차 스트림(residual stream)을 즉석에서 수정하며 별도 산출물을 남기지 않습니다.
이 세 가지 외에도 저장소에는 은닉 상태 프로브(Hidden-State Probe), 과학 환각 탐지기(Science Hallucination Detector), Spotlight, Token Highlighter 같은 커뮤니티 기여 사례가 함께 정리되어 있으며, 각각의 Worker·Analyzer 매핑은 docs/use_cases/README.md에서 확인할 수 있습니다.
vLLM Hook의 성능: Native vLLM Eagle과의 비교
저장소의 docs/numerical_analysis/에는 은닉 상태(hidden state) 추출을 기준으로 vLLM Hook과 네이티브 vLLM의 Eagle 기반 추출기(ExampleHiddenStatesConnector)를 비교한 벤치마크가 실려 있습니다. 프롬프트 길이는 약 16·64·256·512 토큰, 추출 레이어 수는 1개부터 전체까지, 실행당 10회 반복 조건입니다.
핵심은 마지막 토큰의 표현만 필요할 때 vLLM Hook의 last_token 모드가 프롬프트 길이에 거의 무관하게 낮은 지연을 유지한다는 점입니다. 전체 레이어에서 은닉 상태를 뽑을 때의 생성 지연은 다음과 같습니다(원문 §Key Findings 표).
| 프롬프트 길이 | vLLM-Hook (last_token) | vLLM-Hook (all_tokens) | Native vLLM Eagle |
|---|---|---|---|
| 16 토큰 | 39.9 ms | 37.9 ms | 31.5 ms |
| 64 토큰 | 47.1 ms | 46.3 ms | 49.7 ms |
| 256 토큰 | 45.6 ms | 62.3 ms | 123.4 ms |
| 512 토큰 | 58.4 ms | 103.5 ms | 539.2 ms |
레이어와 프롬프트가 적을 때는 세 방식의 지연이 비슷하지만, 512 토큰·28개 레이어 구간에서는 last_token 이 58.4 ms로, all_tokens(103.5 ms)와 네이티브 Eagle(539.2 ms)보다 크게 낮습니다. last_token 은 캡처 시점에 마지막 토큰만 잘라내 레이어당 벡터 하나((hidden_size,))만 디스크에 쓰기 때문입니다.
산출물 크기와 GPU 메모리에서도 차이가 있습니다. last_token 은 프롬프트 길이와 무관하게 약 677 KB로 평평하게 유지되는 반면, all_tokens 나 네이티브 Eagle은 긴 시퀀스에서 산출물이 수백 배로 커집니다(28개 레이어·512 토큰에서 약 344 MB). 또한 네이티브 vLLM의 추출기는 Eagle-3 추측 디코딩(speculative decoding) 인프라 위에 있어 더미 초안 모델(drafter)을 함께 올려야 하고, 실측에서 약 2.3 GiB의 GPU 메모리를 KV 캐시에서 잠식했습니다(vLLM Hook 18.45 GiB 가용, 네이티브 16.13 GiB 가용). 이 벤치마크는 Qwen2-1.5B-Instruct 모델과 A100/H100급 단일 GPU에서 측정한 값입니다.
vLLM Hook 설치 및 사용법
vLLM Hook은 저장소를 클론한 뒤 플러그인 패키지를 설치해 사용합니다. Python 3.12 환경이 권장됩니다.
git clone https://github.com/IBM/vLLM-Hook.git
cd vLLM-Hook
# (선택) conda 환경 생성
conda create -n vllm_hook_env python=3.12 pip
conda activate vllm_hook_env
# 플러그인과 의존성 설치
pip install -e vllm_hook_plugins
각 활용 사례는 examples/ 의 CLI 스크립트나 notebooks/ 의 노트북으로 실행해 볼 수 있습니다.
python examples/demo_attntracker.py # 어텐션 트래커 (프롬프트 인젝션 탐지)
python examples/demo_corer.py # Core Reranker (문서 관련도 재순위화)
python examples/demo_actsteer.py # 활성값 스티어링 (지시 따르기 강화)
모델별 설정은 model_configs/<예시명>/<모델명>.json 형식으로 바꿀 수 있습니다. 위 벤치마크는 vLLM 0.18.0과 PyTorch 2.10.0+cu129 환경에서 재현되었으며, 재현 절차는 앞서 언급한 docs/numerical_analysis/README.md 에 정리되어 있습니다.
vLLM Hook의 라이선스
vLLM Hook은 Apache 2.0 라이선스로 공개되어 있어 개인 및 상업적 목적으로 자유롭게 사용할 수 있습니다.
vLLM Hook 논문
vLLM Hook 프로젝트 GitHub 저장소
더 읽어보기
-
Qwen-Scope: Sparse Autoencoder로 LLM 내부를 해석하고 제어하는 Qwen 팀의 오픈소스 도구
-
Circuit Tracer: Anthropic이 공개한, LLM의 내부 동작 원리를 분석하고 시각화하는 도구 (feat. Decode Research)
-
사고 패치(Thought Patching): 프롬프트를 가중치로 바꾸는 모델 편집에 대한 연구 (feat. Google)
이 글은 GPT 모델로 정리한 글을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 덧글로 알려주시기를 부탁드립니다. ![]()
파이토치 한국 사용자 모임
이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일
로 보내드립니다! 텔레그램(Telegram)이나 Slack/Discord/Teams/Dooray/GoogleChat 등으로도 새 글 알림을 받으실 수 있습니다. ![]()
아래
쪽에 좋아요
를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ ![]()




