Transformers로 Qwen 추론하기
Transformers로 Qwen 추론하기
Transformers는 추론과 훈련을 위한 사전 훈련된 자연어 처리 라이브러리예요. 개발자는 Transformers로 자신의 데이터에 모델을 훈련하고, 추론 애플리케이션을 만들고, 대규모 언어 모델로 텍스트를 생성할 수 있어요.
출처: 문서
본문
환경 설정
transformers>=4.51.0torch>=2.6권장- GPU 권장
기본 사용법
transformers에서 Qwen3로 텍스트를 생성하려면 pipeline() 인터페이스나 generate() 인터페이스를 사용할 수 있어요.
일반적으로 pipeline 인터페이스는 보일러플레이트 코드가 덜 필요해요 (아래 참고). 다음은 다중 턴 대화에 pipeline을 사용하는 기본 예시예요.
from transformers import pipeline
model_name_or_path = "Qwen/Qwen3-8B"
generator = pipeline(
"text-generation",
model_name_or_path,
torch_dtype="auto",
device_map="auto",
)
messages = [
{"role": "user", "content": "Give me a short introduction to large language models."},
]
messages = generator(messages, max_new_tokens=32768)[0]["generated_text"]
# print(messages[-1]["content"])
messages.append({"role": "user", "content": "In a single sentence."})
messages = generator(messages, max_new_tokens=32768)[0]["generated_text"]
# print(messages[-1]["content"])
pipeline을 만들 때 중요한 파라미터가 몇 가지 있어요.
모델: model_name_or_path는 Qwen/Qwen3-8B 같은 모델 ID이거나 로컬 경로일 수 있어요.
모델 파일을 로컬 디렉터리에 다운로드하려면 다음을 사용할 수 있어요:
huggingface-cli download --local-dir ./Qwen3-8B Qwen/Qwen3-8B
중국 본토에 있다면 ModelScope로 모델 파일을 다운로드할 수도 있어요:
modelscope download --local_dir ./Qwen3-8B Qwen/Qwen3-8B
디바이스 배치: device_map="auto"는 가능하면 모델 파라미터를 여러 디바이스에 자동으로 로드해요. 이는 accelerate 패키지에 의존해요. 단일 디바이스를 사용하려면 device_map 대신 device를 전달할 수 있어요. device=-1 또는 device="cpu"는 CPU 사용, device="cuda"는 현재 GPU 사용, device="cuda:1" 또는 device=1은 두 번째 GPU 사용을 뜻해요. device_map과 device를 동시에 사용하지 마세요!
계산 정밀도: torch_dtype="auto"는 체크포인트의 원본 정밀도와 여러분의 디바이스가 지원하는 정밀도에 따라 사용할 데이터 타입을 자동으로 결정해요. 최신 디바이스에서는 bfloat16으로 결정돼요. torch_dtype="auto"를 전달하지 않으면 기본 데이터 타입은 float32이며, 이는 메모리를 두 배로 차지하고 계산도 느려요.
텍스트 생성 pipeline 호출은 모델 파일의 생성 설정(예: generation_config.json)을 사용해요. 이 설정은 호출에 인자를 직접 전달해 덮어쓸 수 있어요. 기본값은 다음과 동일해요:
messages = generator(messages, do_sample=True, temperature=0.6, top_k=2, top_p=0.95, eos_token_id=[151645, 151643])[0]{"generated_text"}
생성 파라미터 구성의 모범 사례는 모델 카드를 참고하세요.
Thinking & Non-Thinking 모드
기본적으로 Qwen3 모델은 응답하기 전에 먼저 생각해요. pipeline() 인터페이스에서도 마찬가지예요. thinking과 non-thinking 모드를 전환하는 두 가지 방법이 있어요.
방법 1: thinking\n\n response\n\n만 포함하는 마지막 assistant 메시지를 추가하세요. 이 방식은 상태가 없어서(stateless) 그 한 턴에만 동작해요. 또한 모델이 thinking 콘텐츠를 생성하는 것을 엄격히 막아요. 예를 들어:
messages = [
{"role": "user", "content": "Give me a short introduction to large language models."},
{"role": "assistant", "content": " thinking\n\n response\n\n"},
]
messages = generator(messages, max_new_tokens=32768)[0]["generated_text"]
# print(messages[-1]["content"])
messages.append({"role": "user", "content": "In a single sentence."})
messages = generator(messages, max_new_tokens=32768)[0]["generated_text"]
# print(messages[-1]["content"])
방법 2: 사용자(또는 시스템) 메시지에 /no_think를 추가해 thinking을 끄고, /think를 추가해 thinking을 켜세요. 이 방식은 상태가 있어서(stateful), 다중 턴 대화에서 모델이 가장 최근의 지시를 따라요.
messages = [
{"role": "user", "content": "Give me a short introduction to large language models./no_think"},
]
messages = generator(messages, max_new_tokens=32768)[0]["generated_text"]
# print(messages[-1]["content"])
messages.append({"role": "user", "content": "In a single sentence./think"})
messages = generator(messages, max_new_tokens=32768)[0]["generated_text"]
# print(messages[-1]["content"])
Thinking 콘텐츠 파싱
더 구조화된 assistant 메시지 형식을 원한다면, 다음 함수로 thinking 콘텐츠를 vLLM, SGLang 등에서 사용하는 것과 유사한 reasoning_content 필드로 추출할 수 있어요.
import copy
import re
def parse_thinking_content(messages):
messages = copy.deepcopy(messages)
for message in messages:
if message["role"] == "assistant" and (m := re.match(r" thinking\n(.+) response\n\n", message["content"], flags=re.DOTALL)):
message["content"] = message["content"][len(m.group(0)):]
if thinking_content := m.group(1).strip():
message["reasoning_content"] = thinking_content
return messages
도구 호출 파싱
Transformers에서 도구 호출을 사용하려면 우리의 함수 호출 가이드를 참고하세요.
양자화 모델 서빙
Qwen3에는 FP8과 AWQ 두 가지 유형의 사전 양자화 모델이 제공돼요. 이 모델들을 서빙하는 명령은 이름만 바뀌고 원본 모델과 동일해요:
from transformers import pipeline
model_name_or_path = "Qwen/Qwen3-8B-FP8" # FP8 models
# model_name_or_path = "Qwen/Qwen3-8B-AWQ" # AWQ models
generator = pipeline(
"text-generation",
model_name_or_path,
torch_dtype="auto",
device_map="auto",
)
📝 참고: FP8 연산은 컴퓨트 능력이 8.9보다 큰 NVIDIA GPU, 즉 Ada Lovelace, Hopper 이후 GPU에서 지원돼요. 더 나은 성능을 위해
triton과 여러분 환경의torchCUDA 버전과 호환되는 CUDA 컴파일러가 설치되어 있는지 확인하세요.
⚠️ 중요: 4.51.0 기준으로, Transformers는 GPU 간에 이들 체크포인트를 실행할 때 문제가 있어요. 다음 방법으로 문제를 우회할 수 있어요:
- 스크립트 실행 전에 환경 변수
CUDA_LAUNCH_BLOCKING=1을 설정하거나,- 로컬 설치의
transformers에서 해당 줄의 주석을 해제하세요.
긴 컨텍스트 활성화
Qwen3 모델의 사전 훈련 최대 컨텍스트 길이는 32,768 토큰이에요. RoPE 스케일링 기법으로 131,072 토큰까지 확장할 수 있어요. YaRN으로 성능을 검증했어요.
Transformers는 YaRN을 지원하며, 모델 파일을 수정하거나 모델 로딩 시 기본 인자를 덮어써서 활성화할 수 있어요.
모델 파일 수정 — config.json 파일에 rope_scaling 필드를 추가하세요:
{
...,
"max_position_embeddings": 131072,
"rope_scaling": {
"rope_type": "yarn",
"factor": 4.0,
"original_max_position_embeddings": 32768
}
}
기본 인자 덮어쓰기:
from transformers import pipeline
model_name_or_path = "Qwen/Qwen3-8B"
generator = pipeline(
"text-generation",
model_name_or_path,
torch_dtype="auto",
device_map="auto",
model_kwargs={
"max_position_embeddings": 131072,
"rope_scaling": {
"rope_type": "yarn",
"factor": 4.0,
"original_max_position_embeddings": 32768,
},
}
)
⚠️ 주의: Transformers 4.52.3부터는 지정된
rope_scaling.factor와 무관하게max_position_embeddings/rope_scaling.original_max_position_embeddings를rope_scaling.factor로 사용해요. 자세한 내용은 이 이슈를 참고하세요.
📝 참고: Transformers는 정적 YaRN을 구현하므로 스케일링 인자가 입력 길이와 무관하게 일정하며, 이는 짧은 텍스트의 성능에 영향을 줄 수 있어요. 긴 컨텍스트 처리가 필요한 경우에만
rope_scaling설정을 추가하는 것을 권장해요. 필요에 따라factor를 수정하는 것도 권장해요. 예를 들어 애플리케이션의 일반적인 컨텍스트 길이가 65,536 토큰이라면factor를 2.0으로 설정하는 것이 좋아요.
스트리밍 생성
TextStreamer를 이용하면 Qwen3와의 대화를 스트리밍 모드로 바꿀 수 있어요. 응답이 생성되는 대로 콘솔이나 터미널에 출력돼요.
from transformers import pipeline, TextStreamer
model_name_or_path = "Qwen/Qwen3-8B"
generator = pipeline(
"text-generation",
model_name_or_path,
torch_dtype="auto",
device_map="auto",
)
streamer = TextStreamer(pipe.tokenizer, skip_prompt=True, skip_special_tokens=True)
messages= generator(messages, max_new_tokens=32768, streamer=streamer)[0]["generated_text"]
TextStreamer 외에도 TextIteratorStreamer를 사용할 수 있어요. 이는 출력 준비된 텍스트를 큐에 저장해서 다운스트림 애플리케이션이 iterator로 사용할 수 있게 해줘요.
from transformers import pipeline, TextIteratorStreamer
model_name_or_path = "Qwen/Qwen3-8B"
generator = pipeline(
"text-generation",
model_name_or_path,
torch_dtype="auto",
device_map="auto",
)
streamer = TextIteratorStreamer(pipe.tokenizer, skip_prompt=True, skip_special_tokens=True)
# Use Thread to run generation in background
# Otherwise, the process is blocked until generation is complete
# and no streaming effect can be observed.
from threading import Thread
generation_kwargs = dict(text_inputs=messages, max_new_tokens=32768, streamer=streamer)
thread = Thread(target=pipe, kwargs=generation_kwargs)
thread.start()
generated_text = ""
for new_text in streamer:
generated_text += new_text
print(generated_text)
배치 생성
📝 참고: 배칭이 항상 성능에 유리한 것은 아니에요.
from transformers import pipeline
model_name_or_path = "Qwen/Qwen3-8B"
generator = pipeline(
"text-generation",
model_name_or_path,
torch_dtype="auto",
device_map="auto",
)
generator.tokenizer.padding_side="left"
batch = [
[{"role": "user", "content": "Give me a short introduction to large language models."}],
[{"role": "user", "content": "Give me a detailed introduction to large language models."}],
]
results = generator(batch, max_new_tokens=32768, batch_size=2)
batch = [result[0]["generated_text"] for result in results]
FAQ
Transformers로 분산 추론이 상상했던 것만큼 빠르지 않다는 것을 알 수 있어요. device_map="auto"를 쓴 Transformers는 텐서 병렬을 적용하지 않아 한 번에 하나의 GPU만 사용해요. 텐서 병렬을 지원하는 Transformers는 해당 문서를 참고하세요.