Pooling Models
Pooling Models (풀링 모델)
Pooling 모델은 텍스트 생성 대신 특정 작업(분류, 임베딩, 리워드, 스코어링 등)에 특화된 소형 언어 모델이에요. vLLM은 이러한 모델들을 Pooler를 통해 입력의 최종 히든 스테이트를 추출해 시퀀스 단위 또는 토큰 단위 결과로 변환해 줘요. 오프라인에서는 LLM.encode, LLM.classify, LLM.embed, LLM.score API를, 온라인에서는 /pooling, /classify, /score, Embeddings API 등을 제공해요.
출처: 문서
본문
참고: 현재 우리는 주로 편의를 위해 pooling 모델을 지원해요. 이것이 Hugging Face Transformers나 Sentence Transformers를 직접 사용하는 것보다 성능 향상을 보장하지는 않아요. vLLM에서 pooling 모델을 최적화할 계획이며, 제안이 있으면 Issue #21796에 코멘트해 주세요.
Pooling 모델이란?
NLP(자연어 처리)는 크게 두 가지 유형의 작업으로 나눌 수 있어요: 자연어 이해(NLU)와 자연어 생성(NLG). vLLM이 지원하는 생성 모델은 다양한 작업 유형을 다뤄요 — 우리가 아는 대형 언어 모델(LLM), 이미지/비디오/오디오 같은 멀티모달 입력을 다루는 멀티모달 모델(VLM), 음성-텍스트 변환 모델, 스트리밍 입력을 지원하는 실시간 모델 등. 이들의 공통점은 텍스트를 생성할 수 있다는 점이에요. 한 걸음 더 나아가 vLLM-Omni는 이미지, 비디오, 오디오를 포함한 멀티모달 콘텐츠 생성을 지원해요.
생성 모델의 능력이 계속 개선되면서 경계도 계속 확장되고 있지만, 일부 애플리케이션 시나리오는 여전히 특정 작업을 효율적으로 완수하는 특화된 소형 언어 모델을 필요로 해요. 이 모델들은 보통 다음 특성을 가져요:
- 콘텐츠 생성을 요구하지 않음
- 매우 제한된 기능만 수행하면 되며, 강한 일반화·창의성·높은 지능을 요구하지 않음
- 극도로 낮은 지연 시간을 요구하며 비용 제약이 있는 하드웨어에서 동작할 수 있음
- 텍스트 전용 모델은 보통 10억 파라미터 미만, 멀티모달 모델은 일반적으로 100억 파라미터 미만
규모는 상대적으로 작지만 여전히 Transformer 아키텍처 기반이며, 오늘날 최첨단 대형 언어 모델과 유사하거나 동일해요. 최근 릴리스된 많은 pooling 모델도 대형 언어 모델에서 fine-tuning되어 대형 모델의 지속적 개선 혜택을 받아요. 이 아키텍처 유사성 덕분에 vLLM의 인프라를 상당 부분 재사용할 수 있어요.
Cheat Sheet
아래 그림처럼 pooling 모델의 핵심 요소들 간의 관계를 요약해서 확인할 수 있어요.
시퀀스 단위 작업과 토큰 단위 작업
시퀀스 단위(sequence-wise) 작업과 토큰 단위(token-wise) 작업의 핵심 차이는 출력 세분성(granularity)이에요. 시퀀스 단위 작업은 전체 입력 시퀀스에 대해 단일 결과를 생성하고, 토큰 단위 작업은 시퀀스 내 각 토큰에 대한 결과를 생성해요.
많은 Pooling 모델이 (시퀀스) 작업과 토큰 작업을 모두 지원해요. 기본 pooling 작업(예: 시퀀스 단위 작업)이 원하는 것이 아닐 때는 오프라인에서 PoolerConfig(task=<task>), 온라인에서 --pooler-config.task <task>로 수동 지정해야 해요.
물론 사용자가 입력·출력 프로세서를 커스터마이즈할 수 있는 "plugin" 작업도 있어요. 자세한 내용은 IO Processor Plugins를 참고하세요.
Pooling 작업
| Pooling Tasks | Granularity | Outputs |
|---|---|---|
| classify (참고) | Sequence-wise | 각 시퀀스에 대한 클래스 확률 벡터 |
| embed | Sequence-wise | 각 시퀀스에 대한 벡터 표현 |
| token_classify | Token-wise | 각 토큰에 대한 클래스 확률 벡터 |
| token_embed | Token-wise | 각 토큰에 대한 벡터 표현 |
참고: 분류 작업에는 특수 하위 범주인 Cross-encoder(리랭커) 모델이 있어요. 이는 두 개의 프롬프트를 입력으로 받고
num_labels가 1인 출력을 내는, 분류 모델의 하위 집합이에요.
Pooling 타입
| Pooling Tasks | Granularity | 설명 |
|---|---|---|
| CLS pooling | Sequence-wise | BERT 유사(양방향 자기 어텐션) 모델에서 기본. 첫 토큰([CLS] 토큰)에 해당하는 last_hidden_states를 출력으로 취함 |
| LAST pooling | Sequence-wise | GPT 유사(인과 자기 어텐션) 모델에서 기본. 마지막 토큰에 해당하는 last_hidden_states를 출력으로 취함 |
| MEAN pooling | Sequence-wise | 많은 연구에서 모든 입력 토큰에 걸쳐 last_hidden_states를 평균 내는 것이 특정 하위 작업에서 더 좋다고 보여줌. 그래서 점점 더 많은 모델이 MEAN pooling을 사용 |
| ALL pooling | Token-wise | 모든 입력 토큰의 last_hidden_states를 출력 |
| STEP pooling | Token-wise | returned_token_ids가 반환한 토큰 ID에 해당하는 last_hidden_states를 필터링해 출력 |
Score 타입
스코어링 모델은 두 입력 프롬프트 간의 유사도 점수를 계산하도록 설계됐어요. 세 가지 모델 타입(일명 score_type)을 지원해요: cross-encoder, late-interaction, bi-encoder.
| Pooling Tasks | Granularity | Outputs | Score Types | scoring function |
|---|---|---|---|---|
| classify (참고) | Sequence-wise | 각 시퀀스에 대한 reranker 점수 | cross-encoder | linear classifier |
| embed | Sequence-wise | 각 시퀀스에 대한 벡터 표현 | bi-encoder | cosine similarity |
| token_classify | Token-wise | 각 토큰에 대한 클래스 확률 벡터 | N/A | N/A |
| token_embed | Token-wise | 각 토큰에 대한 벡터 표현 | late-interaction | late interaction(MaxSim) |
참고: 분류 모델이
num_labels == 1을 출력할 때만 스코어링 모델로 사용할 수 있고 scoring API가 활성화돼요.
Pooling 사용법
| Pooling Usages | 설명 |
|---|---|
| Classification | 주어진 입력에 가장 잘 대응하는 사전 정의된 카테고리/클래스/라벨을 예측 |
| Embedding | 비정형 데이터(텍스트, 이미지, 오디오 등)를 구조화된 수치 벡터(임베딩)로 변환 |
| Token Classification | 토큰 단위 분류 |
| Token Embedding | 토큰 단위 임베딩 |
| Reward | 언어 모델이 생성한 출력의 품질을 평가, 인간 선호의 대리(proxy) 역할 |
| Scoring | 두 입력 간 유사도 점수 계산. 세 모델 타입 지원: cross-encoder, late-interaction, bi-encoder |
| Plugins | 사용자가 입력·출력 프로세서를 커스터마이즈할 수 있게 함 |
또한 여러 pooling 작업을 지원하거나 특별한 사용 시나리오가 있거나 특수 입출력을 지원하는 특별 모델들도 있어요.
오프라인 추론
vLLM의 각 pooling 모델은 Pooler.get_supported_tasks에 따라 이들 작업 중 하나 이상을 지원하며, 해당 API를 활성화해요.
pooling 사용법에 대응하는 오프라인 API
| Pooling Usages | 전용 API | LLM.encode API용 pooling task | Score Types | scoring function |
|---|---|---|---|---|
| Classification | LLM.classify(...) |
classify | cross-encoder (참고) | linear classifier |
| Embedding | LLM.embed(...) |
embed | bi-encoder | cosine similarity |
| Token Classification | N/A | token_classify | N/A | N/A |
| Token Embedding | N/A | token_embed | late-interaction | late interaction(MaxSim) |
| Reward | N/A | classify & token_classify | N/A | N/A |
| Scoring | LLM.score(...) |
N/A | N/A | N/A |
| Plugins | N/A | plugin | N/A | N/A |
- LLM.classify: 각 프롬프트에 대한 확률 벡터를 출력. 주로 분류 모델용.
- LLM.embed: 각 프롬프트에 대한 임베딩 벡터를 출력. 주로 임베딩 모델용.
- LLM.score: 문장 쌍 간 유사도 점수를 출력. 주로 스코어 모델용.
- LLM.encode: vLLM의 모든 pooling 모델에서 사용 가능. 사용 시 더 구체적인 메서드 중 하나를 쓰거나 task를 직접 설정하세요.
예시:
from vllm import LLM
llm = LLM(model="intfloat/e5-small", runner="pooling")
(output,) = llm.encode("Hello, my name is", pooling_task="embed")
data = output.outputs.data
print(f"Data: {data!r}")
온라인 서빙
온라인 서버는 오프라인 API에 대응하는 엔드포인트를 제공해요:
LLM.embed대응: Cohere Embed API(/v2/embed), OpenAI 호환 Embeddings API(/v1/embeddings)LLM.classify대응: Classification API(/classify)LLM.score대응: Score API(/score,/v1/score), Cohere Rerank API(/rerank,/v1/rerank,/v2/rerank)- Pooling API(
/pooling)는LLM.encode와 비슷하며 모든 유형의 pooling 모델에 적용 가능
Pooling API(/pooling)는 LLM.encode와 유사해요. 입력 형식은 Embeddings API와 같지만 출력 데이터는 1-D float 리스트뿐 아니라 임의의 중첩 리스트를 포함할 수 있어요. 더 구체적인 API를 쓰거나 task를 직접 설정하세요.
예시:
# start a supported embeddings model server with `vllm serve`, e.g.
# vllm serve intfloat/e5-small
import requests
host = "localhost"
port = "8000"
model_name = "intfloat/e5-small"
api_url = f"http://{host}:{port}/pooling"
prompts = [
"Hello, my name is",
"The president of the United States is",
"The capital of France is",
"The future of AI is",
]
prompt = {"model": model_name, "input": prompts, "task": "embed"}
response = requests.post(api_url, json=prompt)
for output in response.json()["data"]:
data = output["data"]
print(f"Data: {data!r} (size={len(data)})")
설정
vLLM에서 pooling 모델은 VllmModelForPooling 인터페이스를 구현해요. 이 모델들은 반환하기 전에 Pooler를 사용해 입력의 최종 히든 스테이트를 추출해요.
Model Runner
--runner pooling 옵션으로 pooling 모드에서 모델을 실행할 수 있어요. vLLM이 --runner auto로 적절한 model runner를 자동 감지할 수 있으므로 대부분의 경우 설정할 필요가 없어요.
모델 변환
--convert <type> 옵션으로 다양한 pooling 작업에 맞게 모델을 변환할 수 있어요. --runner pooling이 (수동 또는 자동으로) 설정됐지만 모델이 VllmModelForPooling 인터페이스를 구현하지 않으면, vLLM이 아래 표의 아키텍처 이름에 따라 모델을 자동 변환하려 시도해요.
| Architecture | --convert | 지원되는 pooling tasks |
|---|---|---|
*ForTextEncoding, *EmbeddingModel, *Model |
embed | token_embed, embed |
*ForRewardModeling, *RewardModel |
embed | token_embed, embed |
*For*Classification, *ClassificationModel |
classify | token_classify, classify |
--convert <type>을 명시해 변환 방식을 지정할 수도 있어요.
Pooler 설정
사전 정의된 모델: 모델이 정의한 Pooler가 pooler_config를 받으면 --pooler-config 옵션으로 일부 속성을 재정의할 수 있어요.
변환된 모델: --convert로 변환된 모델에서 각 작업에 할당된 pooler는 기본적으로 다음 속성을 가져요:
| Task | Pooling Type | Normalization | Softmax |
|---|---|---|---|
| embed | LAST | ✅ | ❌ |
| classify | LAST | ❌ | ✅ |
해석 우선순위(Resolution precedence): pooling 방법과 use_activation은 필드별로 해석돼요. --pooler-config에서 명시적으로 설정한 필드가 Sentence Transformers 메타데이터보다 우선하고, 이는 다시 모델 아키텍처 또는 작업 기본값보다 우선해요. 설정되지 않은 필드는 체인을 따라 독립적으로 계속돼요.
- Pooling 방법 (pooling_type):
--pooler-config> Sentence Transformersmodules.json이 참조하는 Pooling 모듈의 compactpooling_mode또는 기존 booleanpooling_mode_*필드 > 아키텍처 기본값(시퀀스 pooling은 LAST, 토큰 pooling은 ALL, 아키텍처가 재정의하지 않는 한). 재정의:{"pooling_type": "CLS"}설정 또는seq_pooling_type/tok_pooling_type명시. - 임베딩 정규화 (use_activation):
--pooler-config> Sentence Transformers 모듈(Normalize 모듈이 있으면 true, 없으면 false) > pooling 작업 기본값(true, Sentence Transformers Pooling 모듈이 없을 때).{"use_activation": false}로 비정규화 임베딩 반환. - 분류 활성화 함수: Hugging Face
problem_type> Sentence Transformers 활성화 메타데이터 > 라벨 수에서 선택된 sigmoid 또는 softmax. 이 함수는--pooler-config로 선택할 수 없으며,{"use_activation": false}로 logits을 반환할 수 있어요.
가중치를 로드하지 않고 해석된 필드를 검사하려면:
from vllm.config import ModelConfig, PoolerConfig
from vllm.model_executor.layers.pooler.activations import get_act_fn
def inspect(requested: PoolerConfig) -> None:
model_config = ModelConfig(
"intfloat/e5-small",
runner="pooling",
pooler_config=requested,
)
resolved = model_config.pooler_config
assert resolved is not None
print({
"seq_pooling_type": resolved.seq_pooling_type,
"tok_pooling_type": resolved.tok_pooling_type,
"use_activation": resolved.use_activation,
"sequence_classification_activation": type(
get_act_fn(model_config.hf_config)
).__name__,
})
inspect(PoolerConfig())
inspect(PoolerConfig(pooling_type="CLS", use_activation=False))
intfloat/e5-small의 경우 첫 결과에는 MEAN, ALL, True가, 두 번째에는 CLS, ALL, False가 포함돼요. 둘 다 표준 시퀀스 분류 어댑터가 구성할 분류 활성화를 보고해요.
제거된 기능
Encode 작업
encode 작업을 두 개의 더 구체적인 토큰 단위 작업, token_embed와 token_classify로 분리했어요:
token_embed는embed와 같고 정규화를 활성화로 사용해요.token_classify는classify와 같고 기본적으로 softmax를 활성화로 사용해요.
Pooling 모델은 이제 토큰 단위 작업을 지원해요. 히든 스테이트 추출은 token_embed 작업을 선호하고, NER(개체명 인식)와 리워드 모델은 token_classify 작업을 선호해요.
Score 작업
score 작업은 v0.21에서 제거됐고 대신 classify를 사용해요. 분류 모델이 num_labels == 1을 출력할 때만 스코어링 모델로 사용할 수 있고 scoring API가 활성화돼요.
Pooling 멀티태스크 지원
Pooling 멀티태스크 지원은 v0.21에서 제거됐어요. 기본 pooling 작업이 원하는 것이 아닐 때는 오프라인에서 PoolerConfig(task=<task>), 온라인에서 --pooler-config.task <task>로 수동 지정해야 해요.