텍스트 생성
텍스트 생성 (Text generation)
텍스트 생성은 대형 언어 모델(LLM)에서 가장 인기 있는 애플리케이션이에요. LLM은 초기 텍스트(프롬프트)가 주어지면 자기 자신이 생성한 출력까지 이어가며, 미리 정의된 길이에 도달하거나 시퀀스 끝(EOS) 토큰을 만날 때까지 다음 단어(토큰)를 생성하도록 훈련돼요.
Transformers에서 [~GenerationMixin.generate] API가 텍스트 생성을 담당하며, 생성 능력이 있는 모든 모델에서 사용할 수 있어요. 이 가이드는 [~GenerationMixin.generate]로 텍스트 생성의 기본과 피해야 할 흔한 함정을 보여줄게요.
[!TIP] 아래 명령은
transformers serve가 실행 중인지 확인해 주세요.transformers chat Qwen/Qwen2.5-0.5B-Instruct
기본 generate
시작하기 전에 bitsandbytes를 설치해 정말 큰 모델을 양자화하고 메모리 사용량을 줄이는 데 도움을 받는 게 좋아요.
!pip install -U transformers bitsandbytes
Bitsandbytes는 CUDA 기반 GPU 외에도 여러 백엔드를 지원해요. 더 자세한 내용은 multi-backend 설치 가이드를 참조해요.
[~PreTrainedModel.from_pretrained]으로 LLM을 로드하고 메모리 요구를 줄이기 위해 다음 두 파라미터를 추가해요.
device_map="auto"는 Accelerate의 Big Model Inference 기능을 활성화해 모델 뼈대를 자동 초기화하고, 가장 빠른 장치(GPU)부터 시작해 모든 사용 가능한 장치에 모델 가중치를 로드·분배해요.quantization_config는 양자화 설정을 정의하는 설정 객체예요. 이 예시는 bitsandbytes를 양자화 백엔드로 쓰며(가능한 다른 백엔드는 Quantization 섹션 참조), 모델을 4비트로 로드해요.
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)
입력을 토큰화하고, [~PreTrainedTokenizer.padding_side] 파라미터를 "left"로 설정해요. LLM은 패딩 토큰에서 생성을 이어가도록 훈련되지 않았기 때문이에요. 토크나이저는 input ids와 attention mask를 반환해요.
[!TIP] 문자열 리스트를 토크나이저에 넘겨 한 번에 둘 이상의 프롬프트를 처리해요. 입력을 배칭하면 대기 시간과 메모리에 약간의 비용을 들여 처리량을 높일 수 있어요.
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)
입력을 [~GenerationMixin.generate]에 넘겨 토큰을 생성하고, 생성된 토큰을 [~PreTrainedTokenizer.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]의 파라미터와 값을 재정의해 [~GenerationMixin.generate]를 커스터마이즈할 수 있어요. 자주 조정하는 파라미터는 아래 섹션을 참조해요.
# beam search 샘플링 전략 활성화
model.generate(**inputs, num_beams=4, do_sample=True)
[~GenerationMixin.generate]는 외부 라이브러리나 커스텀 코드로도 확장할 수 있어요.
logits_processor파라미터는 다음 토큰 확률 분포를 조작하는 커스텀 [LogitsProcessor] 인스턴스를 받아요.stopping_criteria파라미터는 텍스트 생성을 중단하는 커스텀 [StoppingCriteria]를 지원해요.- 다른 커스텀 생성 메서드는
custom_generate플래그로 로드할 수 있어요(문서).
검색·샘플링·디코딩 전략에 대해 더 알아보려면 Generation strategies 가이드를 참조해요.
저장 (Saving)
[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
)
[~GenerationConfig.save_pretrained]으로 특정 생성 설정을 저장하고, Hub에 업로드하려면 push_to_hub 파라미터를 True로 설정해요.
generation_config.save_pretrained("my_account/my_model", push_to_hub=True)
config_file_name 파라미터는 비워 두는 게 좋아요. 이 파라미터는 단일 디렉토리에 여러 생성 설정을 저장할 때 써요. 어떤 생성 설정을 로드할지 지정하는 방법을 주니까요. 하나의 모델을 위해 생성 작업마다 다른 설정을 만들 수 있어요(샘플링을 쓰는 창의적 텍스트 생성, beam search를 쓰는 요약 등).
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))
공통 옵션 (Common Options)
[~GenerationMixin.generate]는 강력한 도구라 크게 커스터마이즈할 수 있어요. 새 사용자에겐 부담스러울 수 있죠. 이 섹션은 Transformers의 대부분 텍스트 생성 도구([~GenerationMixin.generate], [GenerationConfig], pipelines, chat CLI, ...)에서 정의할 수 있는 인기 있는 생성 옵션 목록을 담고 있어요.
| 옵션 이름 | 타입 | 간단한 설명 |
|---|---|---|
max_new_tokens |
int |
최대 생성 길이를 제어해요. 보통 작은 값이 기본이므로 반드시 정의해 주세요. |
do_sample |
bool |
생성이 다음 토큰을 샘플링할지(True), 아니면 그리디로 할지(False) 정의해요. 대부분의 사용 사례는 이 플래그를 True로 설정해야 해요. 자세한 내용은 가이드 참조. |
temperature |
float |
선택될 다음 토큰이 얼마나 예측 불가능할지예요. 높은 값(>0.8)은 창의적 작업에, 낮은 값(예: <0.4)은 "사고"가 필요한 작업에 좋아요. do_sample=True가 필요해요. |
num_beams |
int |
>1로 설정하면 beam search 알고리즘을 활성화해요. Beam search는 입력이 grounded된 작업에 좋아요. 자세한 내용은 가이드 참조. |
repetition_penalty |
float |
모델이 스스로 자주 반복한다면 >1.0으로 설정해요. 값이 클수록 더 큰 패널티가 적용돼요. |
eos_token_id |
list[int] |
생성이 중단되게 하는 토큰이에요. 기본값이 보통 좋지만 다른 토큰을 지정할 수 있어요. |
함정 (Pitfalls)
아래 섹션은 텍스트 생성 중 마주칠 수 있는 흔한 문제와 해결 방법을 다뤄요.
출력 길이 (Output length)
[~GenerationMixin.generate]는 모델의 [GenerationConfig]에 달리 지정되지 않으면 기본적으로 최대 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:
generated_ids = model.generate(**model_inputs, max_new_tokens=50)
tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A sequence of numbers: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,'
디코딩 전략 (Decoding strategy)
[~GenerationMixin.generate]의 기본 디코딩 전략은 모델의 [GenerationConfig]에 달리 지정되지 않으면 *그리디 검색(greedy search)*으로, 가장 가능성 높은 토큰을 선택해요. 이 디코딩 전략은 입력이 grounded된 작업(전사, 번역)에 잘 맞지만, 더 창의적인 사용 사례(스토리 쓰기, 채팅 앱)에는 최적이 아니에요.
예를 들어 multinomial sampling 전략을 활성화해 더 다양한 출력을 생성해요. 더 많은 디코딩 전략은 Generation strategy 가이드를 참조해요.
model_inputs = tokenizer(["I am a cat."], return_tensors="pt").to(model.device)
그리디 검색:
generated_ids = model.generate(**model_inputs)
tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
multinomial 샘플링:
generated_ids = model.generate(**model_inputs, do_sample=True)
tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
패딩 쪽 (Padding side)
입력 길이가 같지 않으면 패딩이 필요해요. 하지만 LLM은 패딩 토큰에서 생성을 이어가도록 훈련되지 않았으므로, [~PreTrainedTokenizer.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'
왼쪽 패딩:
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1", padding_side="left")
tokenizer.pad_token = tokenizer.eos_token
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, 3, 4, 5, 6,'
프롬프트 형식 (Prompt format)
어떤 모델·작업은 특정 입력 프롬프트 형식을 기대하며, 형식이 틀리면 모델이 차선의 출력을 반환해요. 프롬프팅에 대해 더 알아보려면 prompt engineering 가이드를 참조해요.
예를 들어 채팅 모델은 입력을 채팅 템플릿으로 기대해요. 프롬프트에는 대화에 참여하는 사람이 누구인지 나타내는 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)
)
형식 없음:
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"
채팅 템플릿:
messages = [
{
"role": "system",
"content": "You are a friendly chatbot who always responds in the style of a pirate",
},
{"role": "user", "content": "How many cats does it take to change a light bulb?"},
]
model_inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt").to(model.device)
input_length = model_inputs["input_ids"].shape[1]
generated_ids = model.generate(**model_inputs, do_sample=True, max_new_tokens=50)
print(tokenizer.batch_decode(generated_ids[:, input_length:], skip_special_tokens=True)[0])
"Arr, matey! According to me beliefs, 'twas always one cat to hold the ladder and another to climb up it an’ change the light bulb, but if yer looking to save some catnip, maybe yer can"
리소스 (Resources)
아래는 더 구체적이고 전문화된 텍스트 생성 라이브러리예요.
- Optimum: 특정 하드웨어에서 훈련·추론 최적화에 초점을 맞춘 Transformers 확장
- Outlines: 제약된 텍스트 생성 라이브러리(예: JSON 파일 생성)
- SynCode: 문맥 자유 문법이 이끄는 생성(JSON, SQL, Python) 라이브러리
- Text Generation Inference: LLM용 운영 서버
- Text generation web UI: 텍스트 생성용 Gradio 웹 UI
- logits-processor-zoo: 텍스트 생성 제어용 추가 logits processors
더 알아보기 (Learn more)
- Generation strategies — 검색·샘플링·디코딩 전략
- Quantization — 4비트/8비트 양자화로 메모리 절약
- Chat templates — 채팅 모델용 프롬프트 형식