Text generation
Text generation
대형 언어 모델(LLM)에서 가장 많이 쓰는 기능은 역시 텍스트 생성이에요. LLM은 주어진 초기 텍스트(프롬프트)와 그동안 스스로 만들어 낸 출력을 바탕으로 다음 단어(토큰)를 생성하도록 학습돼요. 미리 정해진 길이에 도달하거나 종료 시퀀스(EOS) 토큰에 닿을 때까지 이 과정을 반복하죠. Transformers에서는 generate() API가 텍스트 생성의 모든 것을 담당해요. 생성 능력이 있는 모든 모델에서 사용할 수 있어요. 이 가이드에서는 generate()의 기본 사용법과 자주 겪게 될 함정 몇 가지를 살펴볼게요.
기본 generate
시작 전에 매우 큰 모델을 양자화해 메모리 사용량을 줄이려면 bitsandbytes를 설치해 두면 좋아요.
!pip install -U transformers bitsandbytes
Bitsandbytes는 CUDA 기반 GPU 외에도 여러 백엔드를 지원해요. 멀티백엔드 설치 가이드를 참고해요.
from_pretrained()로 LLM을 불러올 때 메모리 요구량을 줄이기 위해 두 파라미터를 추가해요.
device_map="auto"는 Accelerate의 Big Model Inference 기능을 켜서 사용 가능한 모든 디바이스(가장 빠른 GPU부터)에 모델 가중치를 자동으로 분배해요.quantization_config는 양자화 설정을 정의하는 구성 객체예요. 이 예시는 bitsandbytes를 양자화 백엔드로 쓰고 모델을 4-bits로 불러와요.
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(load_in_4bit=True)
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1", device_map="auto", quantization_config=quantization_config)
입력을 토크나이즈하고, LLM은 패딩 토큰부터 생성을 이어가도록 학습되지 않았으므로 padding_side 파라미터를 "left"로 설정해요. 토크나이저는 input ids와 attention mask를 반환해요.
토크나이저에 문자열 리스트를 넘기면 여러 프롬프트를 한 번에 처리할 수 있어요. 배치를 쓰면 지연시간과 메모리 비용이 조금 늘지만 처리량은 크게 올라가요.
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1", padding_side="left")
model_inputs = tokenizer(["A list of colors: red, blue"], return_tensors="pt").to(model.device)
generate()에 입력을 넣어 토큰을 생성하고, batch_decode()로 생성된 토큰을 다시 텍스트로 바꿔요.
generated_ids = model.generate(**model_inputs)
tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
"A list of colors: red, blue, green, yellow, orange, purple, pink,"
Generation configuration
모든 생성 설정은 GenerationConfig에 담겨요. 위 예시에서 생성 설정은 mistralai/Mistral-7B-v0.1의 generation_config.json 파일에서 비롯돼요. 모델에 저장된 구성이 없으면 기본 디코딩 전략이 사용돼요.
generation_config 속성으로 구성을 확인할 수 있어요. 기본 구성과 다른 값만 보여주는데, 이 경우 bos_token_id와 eos_token_id네요.
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1", device_map="auto")
model.generation_config
GenerationConfig {
"bos_token_id": 1,
"eos_token_id": 2
}
GenerationConfig의 파라미터와 값을 덮어써서 generate()를 커스터마이즈할 수 있어요. 아래는 자주 조정하는 파라미터예요.
# enable beam search sampling strategy
model.generate(**inputs, num_beams=4, do_sample=True)
generate()는 외부 라이브러리나 커스텀 코드로도 확장할 수 있어요.
logits_processor파라미터는 다음 토큰 확률 분포를 조작하는 커스텀LogitsProcessor인스턴스를 받아요.stopping_criteria파라미터는 텍스트 생성을 멈추는 커스텀StoppingCriteria를 지원해요.custom_generate플래그로 다른 커스텀 생성 메서드를 불러올 수 있어요.
탐색·샘플링·디코딩 전략에 대한 더 자세한 내용은 Generation strategies 가이드를 참고해요.
저장
GenerationConfig 인스턴스를 만들고 원하는 디코딩 파라미터를 지정해요.
from transformers import AutoModelForCausalLM, GenerationConfig
model = AutoModelForCausalLM.from_pretrained("my_account/my_model")
generation_config = GenerationConfig(
max_new_tokens=50, do_sample=True, top_k=50, eos_token_id=model.config.eos_token_id
)
save_pretrained()로 특정 생성 구성을 저장하고 push_to_hub 파라미터를 True로 설정하면 허브에 업로드돼요.
generation_config.save_pretrained("my_account/my_model", push_to_hub=True)
config_file_name 파라미터는 비워 둬요. 이 파라미터는 한 디렉토리에 여러 생성 구성을 저장할 때 어떤 구성을 불러올지 지정하는 데 쓰여요. 하나의 모델에 여러 생성 작업(샘플링으로 창의적 생성, 빔 서치로 요약)을 위한 서로 다른 구성을 만들어 둘 수 있어요.
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, GenerationConfig
tokenizer = AutoTokenizer.from_pretrained("google-t5/t5-small")
model = AutoModelForSeq2SeqLM.from_pretrained("google-t5/t5-small")
translation_generation_config = GenerationConfig(
num_beams=4,
early_stopping=True,
decoder_start_token_id=0,
eos_token_id=model.config.eos_token_id,
pad_token=model.config.pad_token_id,
)
translation_generation_config.save_pretrained("/tmp", config_file_name="translation_generation_config.json", push_to_hub=True)
generation_config = GenerationConfig.from_pretrained("/tmp", config_file_name="translation_generation_config.json")
inputs = tokenizer("translate English to French: Configuration files are easy to use!", return_tensors="pt")
outputs = model.generate(**inputs, generation_config=generation_config)
print(tokenizer.batch_decode(outputs, skip_special_tokens=True))
흔한 선택지
generate()는 강력해서 크게 커스터마이즈할 수 있지만, 그래서 새 사용자에겐 부담스러울 수 있어요. 아래는 Transformers의 대부분 텍스트 생성 도구(generate(), GenerationConfig, pipelines, chat CLI)에서 공통으로 정의하는 인기 생성 옵션 목록이에요.
| Option name | Type | 간단 설명 |
|---|---|---|
max_new_tokens |
int |
최대 생성 길이를 조절해요. 보통 기본값이 작으니 반드시 지정해 주는 게 좋아요. |
do_sample |
bool |
다음 토큰을 샘플링할지(True), 탐욕적(greedy)으로 고를지(False)를 정해요. 대부분의 경우 True로 설정해요. |
temperature |
float |
다음으로 선택될 토큰의 예측 불가능성을 나타내요. 높은 값(>0.8)은 창의적 작업에, 낮은 값(예: <0.4)은 '생각'을 요하는 작업에 좋아요. do_sample=True가 필요해요. |
num_beams |
int |
>1로 설정하면 빔 서치 알고리즘이 활성화돼요. 빔 서치는 입력에 근거한 작업에 좋아요. |
repetition_penalty |
float |
모델이 반복을 자주 한다면 >1.0으로 설정해요. 값이 클수록 패널티가 커져요. |
eos_token_id |
list[int] |
생성을 멈추게 하는 토큰이에요. 기본값이 보통 좋지만 다른 토큰을 지정할 수 있어요. |
함정
텍스트 생성 중 겪는 흔한 문제와 해결법을 살펴볼게요.
출력 길이
모델의 GenerationConfig에 따로 지정하지 않으면 generate()는 기본적으로 최대 20개 토큰을 반환해요. max_new_tokens 파라미터로 생성할 토큰 수를 직접 지정하는 걸 강력히 권장해요. 디코더 전용 모델은 프롬프트와 생성된 토큰을 함께 반환해요.
model_inputs = tokenizer(["A sequence of numbers: 1, 2"], return_tensors="pt").to(model.device)
generated_ids = model.generate(**model_inputs)
tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A sequence of numbers: 1, 2, 3, 4, 5'
max_new_tokens를 지정하지 않으면 위처럼 짧은 출력이 나와요.
디코딩 전략
generate()의 기본 디코딩 전략은 가장 가능성이 높은 다음 토큰을 고르는 greedy search예요. 전사·번역처럼 입력에 근거한 작업에는 잘 맞지만, 스토리 작성·챗 애플리케이션 같은 창의적인 용도에는 최적이 아니에요.
예를 들어 다항 샘플링(multinomial sampling) 전략을 켜면 더 다양한 출력을 만들 수 있어요. 더 많은 디코딩 전략은 Generation strategy 가이드를 참고해요.
패딩 위치
입력 길이가 서로 다르면 패딩이 필요해요. 그런데 LLM은 패딩 토큰부터 생성을 이어가도록 학습되지 않아서, padding_side 파라미터가 입력의 왼쪽으로 설정돼야 해요.
오른쪽 패딩을 쓰면 왼쪽에 패딩 토큰이 남아 모델이 패딩부터 생성하려다 잘못된 출력이 나올 수 있어요.
model_inputs = tokenizer(
["1, 2, 3", "A, B, C, D, E"], padding=True, return_tensors="pt"
).to(model.device)
generated_ids = model.generate(**model_inputs)
tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'1, 2, 33333333333'
프롬프트 형식
어떤 모델과 작업은 특정 입력 프롬프트 형식을 기대해서, 형식이 틀리면 최적이 아닌 출력을 돌려줘요. 예를 들어 챗 모델은 입력을 chat template으로 기대해요. 프롬프트에 누가 대화에 참여하는지 표시하는 role과 content가 있어야 해요. 프롬프트를 단일 문자열로 넘기면 모델이 항상 기대한 출력을 주진 않아요.
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-alpha")
model = AutoModelForCausalLM.from_pretrained(
"HuggingFaceH4/zephyr-7b-alpha", device_map="auto", quantization_config=BitsAndBytesConfig(load_in_4bit=True)
)
chat template으로 프롬프트를 감싸면 훨씬 좋은 출력이 나와요. 형식 없이 단일 문자열로 넘기면 모델이 문맥을 제대로 파악하지 못해요.
prompt = """How many cats does it take to change a light bulb? Reply as a pirate."""
model_inputs = tokenizer([prompt], return_tensors="pt").to(model.device)
input_length = model_inputs.input_ids.shape[1]
generated_ids = model.generate(**model_inputs, max_new_tokens=50)
print(tokenizer.batch_decode(generated_ids[:, input_length:], skip_special_tokens=True)[0])
"Aye, matey! 'Tis a simple task for a cat with a keen eye and nimble paws. First, the cat will climb up the ladder, carefully avoiding the rickety rungs. Then, with"
리소스
텍스트 생성에 특화된 더 전문적인 라이브러리들을 소개할게요.
- Optimum: 특정 하드웨어에서 학습·추론을 최적화하는 데 초점을 둔 Transformers 확장.
- Outlines: 제약된 텍스트 생성 라이브러리(예: JSON 생성).
- SynCode: 문맥 자유 문법으로 안내하는 생성 라이브러리(JSON, SQL, Python).
- Text Generation Inference: LLM용 프로덕션 서버.
- Text generation web UI: Gradio 기반 텍스트 생성 웹 UI.
- logits-processor-zoo: 텍스트 생성을 제어하는 추가 logits 프로세서.
더 알아보기
- Quickstart — 파이프라인으로 시작하기
- Fine-tuning — Trainer로 파인튜닝
- Pipeline — 추론 API 살펴보기