Transformers documentation

Serve CLI

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Serve CLI

transformers serve CLI는 로컬 또는 셀프호스팅 서버를 위한 경량 옵션입니다. vLLM과 같은 전용 추론 엔진의 추가 런타임 및 운영 오버헤드를 피할 수 있습니다. 평가, 실험, 중간 수준의 부하 배포에 사용하세요. 연속 배치 처리(continuous batching)와 같은 기능은 처리량을 높이고 지연 시간을 줄여 줍니다.

대규모 프로덕션 배포에는 Transformer 모델을 백엔드로 사용하여 vLLM 또는 SGLang을 사용하세요. 자세한 내용은 Inference backends 가이드를 참고하세요.

transformers serve 명령은 OpenAI SDK와 호환되는 로컬 서버를 시작합니다. 이 서버는 다양한 서드파티 애플리케이션과 함께 작동하며, 아래 REST API를 지원합니다.

  • 텍스트, 이미지, 오디오 및 비디오 요청에 대한 /v1/chat/completions
  • /v1/completions는 자유 형식 프롬프트에서 레거시 텍스트를 완성하는 데 사용됩니다
  • /v1/responses는 Responses API를 지원합니다
  • 음성 전사를 위한 /v1/audio/transcriptions
  • /v1/systemone는 선택형, 점수형, 예/아니오형과 같은 유형화된 질문에 대해 허용된 각 답변에 대한 확률을 반환합니다
  • /v1/models은 서드파티 통합을 위해 사용 가능한 모델을 나열합니다
  • /load_model은 SSE를 통해 모델 로딩 진행 상황을 스트리밍합니다

서빙 의존성을 설치합니다.

pip install transformers[serving]

transformers serve를 실행하여 서버를 시작합니다. 기본 서버 주소는 http://localhost:8000입니다.

transformers serve

v1/chat/completions

v1/chat/completions API는 Chat Completions API를 기반으로 합니다. LLM, VLM, 멀티모달 모델에 대한 텍스트, 이미지, 오디오, 비디오 요청을 지원합니다. curl, InferenceClient, 또는 OpenAI 클라이언트와 함께 사용할 수 있습니다.

텍스트 기반 완성

huggingface_hub
huggingface_hub (스트림)
openai
openai (스트림)
curl
curl (스트림)
from huggingface_hub import InferenceClient

messages = [{"role": "user", "content": "What is the Transformers library known for?"}]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="Qwen/Qwen2.5-0.5B-Instruct", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient는 출력된 문자열을 반환합니다.

The Transformers library is primarily known for its ability to create and manipulate large-scale language models [...]

이미지 기반 완성

huggingface_hub
huggingface_hub (스트림)
openai
openai (스트림)
curl
curl (스트림)
from huggingface_hub import InferenceClient

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "What's in this image?"},
            {
                "type": "image_url",
                "image_url": {
                    "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg",
                }
            },
        ],
    }
]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="Qwen/Qwen2.5-VL-7B-Instruct", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient는 출력된 문자열을 반환합니다.

The image depicts an astronaut in a space suit standing on what appears to be the surface of the moon, given the barren, rocky landscape and the dark sky in the background. The astronaut is holding a large egg that has cracked open, revealing a small creature inside. The scene is imaginative and playful, combining elements of space exploration with a whimsical twist involving the egg and the creature.

음성 기반 완성

Gemma 4와 Qwen2.5-Omni와 같은 멀티모달 모델은 OpenAI의 input_audio 콘텐츠 유형을 통해 오디오 입력을 받습니다. 오디오를 Base64로 인코딩하고 형식(mp3 또는 wav)을 지정하세요.

huggingface_hub
huggingface_hub (스트림)
openai
openai (스트림)
curl
curl (스트림)
import base64
from huggingface_hub.utils import httpx
from huggingface_hub import InferenceClient

audio_url = "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"
audio_b64 = base64.b64encode(httpx.get(audio_url, follow_redirects=True).content).decode()

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Transcribe this audio."},
            {"type": "input_audio", "input_audio": {"data": audio_b64, "format": "mp3"}},
        ],
    }
]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="google/gemma-4-E2B-it", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient는 출력된 문자열을 반환합니다.

This week, I traveled to Chicago to deliver my final farewell address to the nation, following in the tradition of presidents before me. It was an opportunity to say thank you. Whether we've seen eye to eye or rarely agreed at all, my conversations with you, the American people, in living rooms and schools, at farms and on factory floors, at diners, and on distant military outposts, all these conversations are what have kept me honest.

audio_url 콘텐츠 유형은 OpenAI 표준에 포함되지 않는 확장 기능이며, 향후 버전에서 변경될 수 있습니다.

audio_url 콘텐츠 유형을 사용하여 URL로 오디오를 전달하면 base64 인코딩을 생략할 수 있습니다.

completion = client.chat.completions.create(
    model="google/gemma-4-E2B-it",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Transcribe this audio."},
                {"type": "audio_url", "audio_url": {"url": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"}},
            ],
        }
    ],
)

비디오 기반 완성

video_url 콘텐츠 유형은 OpenAI 표준에 포함되지 않는 확장 기능이며, 향후 버전에서 변경될 수 있습니다.

비디오 입력에는 video_url 콘텐츠 유형을 사용합니다. 모델이 오디오를 지원하면(예: Gemma 4, Qwen2.5-Omni), 서버는 비디오에서 오디오 트랙을 추출하여 시각 프레임과 함께 처리합니다.

Video 처리에는 torchcodec가 필요합니다. pip install torchcodec을 사용하여 설치하세요.

huggingface_hub
huggingface_hub (스트림)
openai
openai (스트림)
curl
curl (스트림)
from huggingface_hub import InferenceClient

messages = [
    {
        "role": "user",
        "content": [
            {"type": "video_url", "video_url": {"url": "https://huggingface.co/datasets/merve/vlm_test_images/resolve/main/concert.mp4"}},
            {"type": "text", "text": "What is happening in the video and what is the song about?"},
        ],
    }
]
client = InferenceClient("http://localhost:8000")

result = client.chat_completion(messages, model="google/gemma-4-E2B-it", max_tokens=256)
print(result.choices[0].message.content)

InferenceClient는 출력된 문자열을 반환합니다.

The video captures a live music performance at a music festival or a large concert. There are several musicians on stage, including a central figure playing an acoustic guitar and singing. The foreground is filled with the backs of the audience, indicating a large crowd watching the show. The stage is dramatically lit with bright spotlights and blue and white stage lighting, with haze and smoke creating an immersive atmosphere.

The lyrics of the song are: "I don't care 'bout street, from that fresh street, 'cause there's no problem, another one I want to be, in the storm..."

다중 턴 대화

멀티턴 대화를 하려면 messages 리스트에 user와 assistant 역할을 번갈아 가며 전체 대화 이력을 포함해야 합니다. 모든 OpenAI 호환 서버와 마찬가지로 API는 상태가 없으므로, 각 요청에는 전체 대화 이력이 포함되어야 합니다.

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

completion = client.chat.completions.create(
    model="Qwen/Qwen2.5-0.5B-Instruct",
    messages=[
        {"role": "user", "content": "What is the capital of France?"},
        {"role": "assistant", "content": "The capital of France is Paris."},
        {"role": "user", "content": "How many people live there?"},
    ],
)
print(completion.choices[0].message.content)

후속 질문 “거기에 사는 사람은 몇 명인가요?”는 이전 문맥에 의존하므로, 모델은 파리에 대해 답변합니다.

As of 2021, the population of Paris is approximately 2.2 million people.

v1/completions

v1/completions API는 레거시 Completions API를 기반으로 합니다. /v1/chat/completions와 달리 채팅 메시지가 아닌 자유 형식의 텍스트 prompt를 입력으로 받아, 생성된 텍스트를 choices[].text에 반환합니다. 이는 채팅 템플릿이 필요 없는 베이스(비인스트럭트) 모델이나 텍스트 완성 작업에 유용합니다. 또한 중간 텍스트 삽입을 위한 suffix도 지원합니다.

curl
openai
curl -X POST http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-0.5B",
    "prompt": "The capital of France is",
    "max_tokens": 20
  }'

명령은 다음 응답을 반환합니다.

{
  "id": "chatcmpl-abc123",
  "object": "text_completion",
  "created": 1234567890,
  "model": "Qwen/Qwen2.5-0.5B@main",
  "choices": [
    {
      "text": " Paris, and the capital of the United States is Washington, D.C.",
      "index": 0,
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 5,
    "completion_tokens": 16,
    "total_tokens": 21
  }
}

v1/responses

Responses API는 OpenAI의 최신 생성용 API 엔드포인트입니다. 상태가 있는(stateful) 상호작용을 지원하며, 내장된 도구를 통합하여 모델의 기능을 확장합니다. OpenAI는 새로운 프로젝트에는 Chat Completions API보다 Responses API 사용을 권장합니다.

v1/responses API는 curl 명령어와 OpenAI 클라이언트를 통해 텍스트, 이미지, 오디오, 비디오 요청을 지원합니다.

텍스트 기반 응답

openai
openai (스트림)
curl
curl (스트림)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="Qwen/Qwen2.5-0.5B-Instruct",
    input="Tell me a three sentence bedtime story about a unicorn.",
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 클라이언트는 출력된 문자열을 반환합니다.

Once upon a time, in a faraway land, there lived a beautiful unicorn named Luna [...]

이미지 기반 응답

Responses API는 이미지, 오디오, 비디오 입력도 지원합니다.

openai
openai (스트림)
curl
curl (스트림)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="Qwen/Qwen2.5-VL-7B-Instruct",
    input=[
        {"type": "input_text", "text": "What's in this image?"},
        {
            "type": "input_image",
            "image_url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg",
        },
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 클라이언트는 출력된 문자열을 반환합니다.

The image depicts an astronaut in a space suit standing on what appears to be the surface of the moon, given the barren, rocky landscape and the dark sky in the background. The astronaut is holding a large egg that has cracked open, revealing a small creature inside. The scene is imaginative and playful, combining elements of space exploration with a whimsical twist involving the egg and the creature.

음성 기반 응답

openai
openai (스트림)
curl
curl (스트림)
import base64
from huggingface_hub.utils import httpx
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

audio_url = "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"
audio_b64 = base64.b64encode(httpx.get(audio_url, follow_redirects=True).content).decode()

response = client.responses.create(
    model="google/gemma-4-E2B-it",
    input=[
        {"type": "input_text", "text": "Transcribe this audio."},
        {"type": "input_audio", "input_audio": {"data": audio_b64, "format": "mp3"}},
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 클라이언트는 출력된 문자열을 반환합니다.

This week, I traveled to Chicago to deliver my final farewell address to the nation, following in the tradition of presidents before me. It was an opportunity to say thank you. Whether we've seen eye to eye or rarely agreed at all, my conversations with you, the American people, in living rooms and schools, at farms and on factory floors, at diners, and on distant military outposts, all these conversations are what have kept me honest.

audio_url 콘텐츠 유형은 OpenAI 표준에 포함되지 않는 확장 기능이며, 향후 버전에서 변경될 수 있습니다.

audio_url 콘텐츠 유형을 사용하여 URL로 오디오를 전달하면 base64 인코딩을 생략할 수 있습니다.

response = client.responses.create(
    model="google/gemma-4-E2B-it",
    input=[
        {"type": "input_text", "text": "Transcribe this audio."},
        {"type": "audio_url", "audio_url": {"url": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama_first_45_secs.mp3"}},
    ],
    max_output_tokens=256,
    stream=False,
)

비디오 기반 응답

video_url 콘텐츠 유형은 OpenAI 표준에 포함되지 않는 확장 기능이며, 향후 버전에서 변경될 수 있습니다.

Video 처리에는 torchcodec가 필요합니다. pip install torchcodec을 사용하여 설치하세요.

openai
openai (스트림)
curl
curl (스트림)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="google/gemma-4-E2B-it",
    input=[
        {"type": "input_text", "text": "What is happening in the video and what is the song about?"},
        {"type": "video_url", "video_url": {"url": "https://huggingface.co/datasets/merve/vlm_test_images/resolve/main/concert.mp4"}},
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

OpenAI 클라이언트는 출력된 문자열을 반환합니다.

The video captures a live music performance at a music festival or a large concert. There are several musicians on stage, including a central figure playing an acoustic guitar and singing. The foreground is filled with the backs of the audience, indicating a large crowd watching the show. The stage is dramatically lit with bright spotlights and blue and white stage lighting, with haze and smoke creating an immersive atmosphere.

The lyrics of the song are: "I don't care 'bout street, from that fresh street, 'cause there's no problem, another one I want to be, in the storm..."

다중 턴 대화

멀티턴 대화의 경우, input 필드에 role 키를 가진 메시지 목록을 전달합니다. 모든 OpenAI 호환 서버와 마찬가지로 API는 무상태(stateless)이므로, 각 요청에는 전체 대화 이력이 포함되어야 합니다.

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<random_string>")

response = client.responses.create(
    model="Qwen/Qwen2.5-0.5B-Instruct",
    input=[
        {"role": "user", "content": "What is the capital of France?"},
        {"role": "assistant", "content": "The capital of France is Paris."},
        {"role": "user", "content": "How many people live there?"},
    ],
    max_output_tokens=256,
    stream=False,
)
print(response.output[0].content[0].text)

후속 질문 “거기에 사는 사람은 몇 명인가요?”는 이전 문맥에 의존하므로, 모델은 파리에 대해 답변합니다.

As of 2021, Paris has a population of approximately 2.8 million people.

v1/systemone

v1/systemone 엔드포인트는 TypeSafe의 System One API를 따르며, 공유된 state에 대한 유형화된 질문에 응답합니다. 지원 티켓 라우팅, 긴급도 평가, 액션 선택과 같은 의사결정에 사용하세요.

요청은 서빙되는 모델 주위의 DecisionPipeline에 의해 처리되므로, 엔드포인트와 파이프라인은 동일한 프롬프트를 구성하고 동일한 답변을 반환합니다. 모델은 텍스트를 생성하지 않고 하나의 프롬프트당 하나의 포워드 패스로 허용된 답변에 점수를 매깁니다. 이 엔드포인트는 채팅 템플릿을 가진 오토레그레시브 모델을 지원합니다.

모델, 평가할 상태, 이름으로 구분된 질문을 보냅니다:

curl
TypeSafe (TypeScript)
curl http://localhost:8000/v1/systemone \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-1.5B-Instruct",
    "state": "Our integration has been failing for 3 days and we are losing sales!",
    "questions": {
      "team": {
        "type": "choice",
        "instructions": "Which team handles this?",
        "criteria": {"billing": null, "technical": "Bugs or integration problems"}
      },
      "urgency": {
        "type": "score",
        "instructions": "How urgent is this ticket?",
        "criteria": ["low", "medium", "high"]
      },
      "upset": {"type": "noul", "instructions": "Is the customer upset?"}
    }
  }'

답변은 동일한 질문 이름을 사용합니다. 예를 들어:

{
  "model": "Qwen/Qwen2.5-1.5B-Instruct",
  "answers": {
    "team": {"type": "choice", "choice": "technical", "probabilities": {"billing": 0.000006962256520637311, "technical": 0.999993085861206}, "confidence": 0.9999861717224121},
    "urgency": {"type": "score", "score": 1.6739138662815094, "legend": {"0": "low", "1": "medium", "2": "high"}, "probabilities": {"0": 0.0031353600788861513, "1": 0.31981542706489563, "2": 0.6770492196083069}, "confidence": 0.5108707791659981},
    "upset": {"type": "noul", "noul": 0.851952850818634}
  },
  "usage": {"input_tokens": 213, "output_tokens": 0}
}
질문 유형criteria답변
choicenull일 수 있는 설명으로 매핑된 옵션 이름.가장 가능성 높은 옵션, 옵션별 확률, 그리고 신뢰도.
score낮은 수준부터 높은 수준 순으로 나열된 수준 설명입니다.확률 가중 레벨 인덱스, 확률, 범례, 신뢰도.
noultrue와 false에 대한 선택적 설명입니다.true의 확률.

state, 선택 사항인 instructions, 설명에는 텍스트 또는 JSON 객체와 배열을 포함할 수 있습니다. 각 요청은 최대 64개의 질문을 허용하며, 질문당 최소 하나의 선택지 또는 점수 레벨이 필요하고 답변 라벨당 최대 하나만 사용할 수 있습니다(기본 A–Z 라벨의 경우 26개).

질문은 모델이 로드되기 전에 DecisionPipeline의 질문과 동일한 방식으로 검증됩니다. 잘못된 요청은 422를 반환하며, 이 응답의 메시지에는 모든 잘못된 질문이 나열되고 각 질문마다 유효한 질문의 예시가 함께 제공됩니다. 또한, 철자가 잘못된 유형과 필드에 대해 유사한 항목을 제안합니다:

Question 'team': expected a `type` among noul, choice, score, got type 'Choice'. Did you mean 'choice'?
Question 'upset': unknown field 'instruction'. Did you mean 'instructions'? A noul question looks like {"type": "noul", ...}

시각 처리가 가능한 모델의 경우, System One API와 마찬가지로 assets를 추가합니다. 원하는 자리표시자(가장 안전하려면 UUID 사용)로 키를 지정한 최대 4개의 이미지를 포함할 수 있습니다. state 문자열, instructions, 또는 criteria 중 어디에든 자리표시자가 나타나면 모델은 해당 위치에 이미지를 인식합니다. 자산은 {"type": "image", "format": "png", "data_base64": "..."} 형식이며, 원격 이미지 URL은 허용되지 않습니다. 모델에는 이미지를 처리할 프로세서가 필요합니다.

{
  "model": "Qwen/Qwen2.5-VL-3B-Instruct",
  "state": "The customer sent 6f1c0e2a and asks if it is damaged.",
  "assets": {"6f1c0e2a": {"type": "image", "format": "png", "data_base64": "iVBORw0..."}},
  "questions": {"damaged": {"type": "noul", "instructions": "Is the product in the photo damaged?"}}
}

결정 구성

Decision 모델은 아직 프롬프트, 답변 라벨, 캘리브레이션 설정을 저장하는 표준이 없습니다. 이러한 항목은 모델 구현에 따라 달라지므로 --decision-config를 사용하여 명시적으로 제공할 수 있습니다.

기본적으로 질문은 폴백 프롬프트와 사고(thinking) 기능이 비활성화된 모델의 채팅 템플릿을 사용합니다. 선택지 및 점수의 답변 라벨은 A~Z이며, 불리언의 경우 Yes/No입니다. 확률은 온도 1에서 해당 라벨에 대한 softmax 값입니다.

모델의 이러한 기본값을 재정의하려면 --decision-config를 전달하세요:

transformers serve openjev/openjev --decision-config decision.json

JSON 파일은 세 가지 선택 필드를 지원합니다:

필드의미
labels질문 유형별로 옵션 순서(noul의 경우 true, 그 다음 false)에 따라 답변 라벨을 지정합니다. 각 라벨은 서로 다른 알려진 단일 토큰이어야 합니다.
temperature질문 유형별 양의 온도 값으로, softmax(label_logits / temperature)로 적용됩니다.
chat_template채팅 포맷과 어시스턴트 접두사를 포함한 전체 프롬프트를 대체하는 인라인 Jinja 템플릿입니다.

예를 들어, 기본 프롬프트를 유지하면서 라벨과 보정을 변경하려면:

{
  "labels": {"noul": ["A", "B"]},
  "temperature": {"choice": 0.85, "score": 0.85, "noul": 1.5547129}
}

Gemma 3용 커스텀 템플릿은 프롬프트 전체와 답이 읽히는 위치의 <|mark|>를 작성합니다. 이미지가 포함된 문자열은 텍스트와 이미지 부분의 리스트로 전달됩니다:

{%- macro text(x) -%}
{%- if x is string or x is none %}{{ x or "" }}{% elif x is mapping %}{{ x|tojson }}{% else -%}
{%- for part in x %}{% if part.type == "image" %}<start_of_image>{% else %}{{ part.text }}{% endif %}{% endfor -%}
{%- endif -%}
{%- endmacro -%}
<start_of_turn>user
{{ text(state) }}

{{ text(question.instructions) }}
{% for option in question.options %}{{ question.labels[loop.index0] }}. {{ option }}: {{ text(question.descriptions[loop.index0]) }}
{% endfor %}<end_of_turn>
<start_of_turn>model
<|mark|>

--decision-config 파일에 chat_template로 인라인으로 포함하거나, 모델 저장소에서 decision.json 옆에 decision_template.jinja로 배포하세요.

v1/audio/transcriptions

v1/audio/transcriptions 엔드포인트는 음성 인식 모델을 사용하여 오디오를 전사합니다. 오디오 전사 API 형식을 따릅니다.

curl -X POST http://localhost:8000/v1/audio/transcriptions \
  -H "Content-Type: multipart/form-data" \
  -F "file=@/path/to/audio.wav" \
  -F "model=openai/whisper-large-v3"

명령은 다음 응답을 반환합니다.

{
  "text": "Transcribed text from the audio file",
}

v1/models

v1/models 엔드포인트는 로컬 Hugging Face 캐시를 스캔하여 다운로드된 모델 목록을 OpenAI 호환 형식으로 반환합니다. 서드파티 도구는 이 엔드포인트를 사용하여 사용 가능한 모델을 탐색합니다.

transformers serve를 실행하기 전에 모델을 다운로드하세요.

transformers download Qwen/Qwen2.5-0.5B-Instruct

다운로드가 완료되면 모델은 /v1/models 응답에 표시됩니다.

curl http://localhost:8000/v1/models

엔드포인트는 사용 가능한 모델이 포함된 JSON 객체를 반환합니다.

모델 로드

/load_model 엔드포인트는 모델을 사전 로드하고 Server-Sent Events(SSE)를 통해 진행 상황을 스트리밍합니다. transformers chat CLI는 이를 자동으로 사용하여 사용자가 멈춘 프롬프트 대신 다운로드 및 로드 진행 상황을 확인할 수 있도록 합니다. 추론 요청을 보내기 전에 모델을 미리 워밍업하는 데 사용하세요.

요청

curl -N -X POST http://localhost:8000/load_model \
  -H "Content-Type: application/json" \
  -d '{"model": "Qwen/Qwen2.5-0.5B-Instruct"}'

model 필드는 Hugging Face 모델 식별자이며, 선택적으로 @revision 접미사가 포함될 수 있습니다(Qwen/Qwen2.5-0.5B-Instruct@main). revision을 생략하면 기본적으로 main이 사용됩니다.

응답

응답은 SSE 스트림(Content-Type: text/event-stream)입니다. 각 프레임은 data: 줄에 있는 JSON 객체입니다.

data: {"status": "loading", "model": "Qwen/Qwen2.5-0.5B-Instruct@main", "stage": "processor"}

모든 이벤트에는 최소한 status와 model 필드가 포함됩니다. 추가 필드는 상태에 따라 달라집니다.

필드다음과 같은 경우설명
status항상loading, ready 또는 error
model항상정식 model_id@revision
stagestatus == "loading"processor, config, download, weights 중 하나 (아래 단계 참조)
progressdownload 및 weights 단계current과 total(정수 또는 null)을 가진 객체
cachedstatus == "ready"모델이 이미 메모리에 존재하는 경우 true
messagestatus == "error"오류 설명

단계

로딩은 이러한 단계를 순서대로 진행됩니다. 일부 단계는 건너뛸 수 있습니다(로컬에 이미 캐시된 파일이 있는 경우 download는 건너뜁니다).

단계진행되고 있나요?설명
processor아니요토크나이저/프로세서 로드
config아니요모델 구성 로드
download예 (바이트)모델 파일 다운로드
weights예 (항목)메모리에 가중치 텐서 로드

스트림은 정확히 하나의 종료 이벤트, ready(성공) 또는 error(실패)로 끝납니다.

Timeout

transformers serve는 모든 모델에 대한 요청을 처리합니다. 각 모델은 필요할 때 로드되고 GPU 메모리에 유지됩니다. 300초 동안 비활성 상태가 되면 GPU 메모리를 확보하기 위해 모델이 자동으로 언로드됩니다. --model-timeout을 다른 초 단위의 값으로 설정하거나, 언로드를 비활성화하려면 -1을 설정합니다.

transformers serve --model-timeout 400

예제 불러오기

아래 예시에는 새로 다운로드한 모델, 로컬 캐시에서 로드한 모델(다운로드 단계 생략), 그리고 이미 메모리에 로드된 모델에 대한 응답을 보여 줍니다.

새로 로드
캐시된 파일
메모리 내
data: {"status": "loading", "model": "org/model@main", "stage": "processor"}
data: {"status": "loading", "model": "org/model@main", "stage": "config"}
data: {"status": "loading", "model": "org/model@main", "stage": "download", "progress": {"current": 0, "total": 269100000}}
data: {"status": "loading", "model": "org/model@main", "stage": "download", "progress": {"current": 134600000, "total": 269100000}}
data: {"status": "loading", "model": "org/model@main", "stage": "download", "progress": {"current": 269100000, "total": 269100000}}
data: {"status": "loading", "model": "org/model@main", "stage": "weights", "progress": {"current": 1, "total": 272}}
data: {"status": "loading", "model": "org/model@main", "stage": "weights", "progress": {"current": 272, "total": 272}}
data: {"status": "ready", "model": "org/model@main", "cached": false}

Tool calling

transformers serve 서버는 OpenAI 스타일의 function calling을 지원합니다. tool-use를 위해 학습된 모델은 애플리케이션이 실행하는 구조화된 function call을 생성합니다.

Tool calling은 토크나이저가 tool call 토큰을 선언하는 모든 모델에서 작동합니다. Qwen과 Gemma 4는 추가 설정 없이 바로 사용할 수 있습니다. 특정 모델에 대한 지원을 요청하려면 issue를 열어 주세요.

OpenAI 형식을 따르는 함수 사양의 목록으로 tools를 정의합니다.

import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<KEY>")

tools = [
  {
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get the current weather in a location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "The city name, e.g. San Francisco"
          },
          "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "temperature unit"
          }
        },
        "required": ["location"]
      }
    }
  }
]

create의 extra_body 인자에 GenerationConfig 매개변수를 전달하여 생성을 사용자 지정할 수 있습니다.

generation_config = {
  "max_new_tokens": 512,
  "temperature": 0.7,
  "top_p": 0.9,
  "top_k": 50,
  "do_sample": True,
  "repetition_penalty": 1.1,
  "no_repeat_ngram_size": 3,
}

response = client.responses.create(
  model="Qwen/Qwen2.5-7B-Instruct",
  instructions="You are a helpful weather assistant. Use the get_weather tool to answer questions.",
  input="What's the weather like in San Francisco?",
  tools=tools,
  stream=True,
  extra_body={"generation_config": json.dumps(generation_config)}
)

for event in response:
  print(event)

멀티턴 도구 호출

모델이 tool call을 반환한 후, 해당 함수를 로컬에서 실행하고 결과를 후속 요청으로 다시 보내 모델의 최종 답변을 받아야 합니다. 두 API 간에 이 패턴이 약간 다릅니다. 전체 사양은 OpenAI function calling guide를 참고하세요.

아래 예제는 위에서 정의한 tools 목록을 재사용합니다.

v1/chat/completions
v1/responses

role: "tool" 메시지로 도구 결과를 전달하고, 일치하는 tool_call_id를 포함하세요.

# Model returns a tool call
messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]
response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=messages,
    tools=tools,
)
assistant_message = response.choices[0].message

# Execute the tool locally
tool_call = assistant_message.tool_calls[0]
result = {"temperature": 22, "condition": "sunny"}  # your actual function call here

# Send the tool result back
messages.append(assistant_message)
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": json.dumps(result),
})
final_response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=messages,
    tools=tools,
)
print(final_response.choices[0].message.content)

추론

Reasoning 모델은 최종 답변 전에 숨겨진 chain-of-thought를 출력합니다. 서버는 이러한 thinking span을 감지하고, 표시되는 답변에서 구분자를 제거하며, reasoning 텍스트를 전용 필드에 노출하여 클라이언트가 별도로 렌더링할 수 있도록 합니다.

  • Chat Completions는 어시스턴트 메시지에서 추론을 reasoning_content으로 반환합니다.
  • Responses API는 message 항목 앞에 reasoning 출력 항목으로 추론을 반환합니다.

추론 감지는 response template에 의존하며, 이 템플릿은 서버에 사고 구간(thinking span)의 시작과 끝 위치를 알려줍니다. 서버는 두 위치에서 순서대로 해당 템플릿을 찾습니다.

  1. 모델에 포함된 경우, 토크나이저의 response_template 속성입니다.
  2. 모델 아키텍처에 따라 매칭되는 내장 폴백으로, 아직 템플릿이 제공되지 않는 토크나이저에 사용됩니다.

둘 다 없는 모델은 응답 파싱 없이 서빙됩니다. 모델이 <think>...</think> 태그를 출력하더라도, 원시 디코딩된 텍스트가 content로 반환되고 reasoning 필드는 노출되지 않습니다.

서버에서 추론 활성화

--reasoning를 사용하여 채팅 템플릿이 사고 토큰을 출력할지 여부를 제어합니다.

값동작
auto (기본값)채팅 템플릿의 기본값을 따릅니다.
on채팅 템플릿 kwargs에서 enable_thinking=True를 설정하여 thinking을 항상 활성화합니다.
offenable_thinking=False를 설정하여 thinking을 비활성화합니다.
transformers serve Qwen/Qwen3-1.7B --reasoning on

모든 모델이 추론을 지원하지는 않습니다. --reasoning는 모델의 채팅 템플릿이 enable_thinking 변수를 처리할 때(또는 해당 토크나이저가 thinking 구분자를 선언할 때)에만 동작합니다. 다른 모델의 경우 해당 플래그는 조용히 무시됩니다.

--reasoning on과 --reasoning off는 채팅 템플릿 변수 enable_thinking를 설정하여 동작합니다. 일부 채팅 템플릿은 사고를 전환하는 데 다른 변수 이름을 사용하거나, 프롬프트가 렌더링되는 방식을 변경하는 추가 변수를 받을 수 있습니다. --chat-template-kwargs를 통해 JSON 객체로 채팅 템플릿 변수를 직접 전달할 수 있습니다.

transformers serve Qwen/Qwen3-1.7B \
  --chat-template-kwargs '{"enable_thinking": true}'

클라이언트는 요청 본문에 chat_template_kwargs를 보내서 서버를 재시작하지 않고도 단일 요청에 대해 서버 기본값을 덮어쓸 수 있습니다.

Chat Completions

Non-streaming 응답에는 어시스턴트 메시지에서 content와 함께 reasoning_content가 포함됩니다.

openai
openai (스트림)
curl
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="<KEY>")

completion = client.chat.completions.create(
    model="Qwen/Qwen3-1.7B",
    messages=[{"role": "user", "content": "What is 17 * 23?"}],
)
message = completion.choices[0].message
print("reasoning:", message.reasoning_content)
print("answer:", message.content)

Responses API

Responses API는 message 항목 앞에 오는 별도의 reasoning 출력 항목으로 추론을 반환합니다.

openai
openai (스트림)
response = client.responses.create(
    model="Qwen/Qwen3-1.7B",
    input="What is 17 * 23?",
    stream=False,
)
reasoning_item = next(i for i in response.output if i.type == "reasoning")
message_item = next(i for i in response.output if i.type == "message")
print("reasoning:", reasoning_item.content[0].text)
print("answer:", message_item.content[0].text)

멀티턴 왕복

멀티턴 대화의 경우, 이전 응답의 추론 텍스트를 다음 요청에 포함해야 합니다. 서버는 이를 채팅 템플릿으로 전달하며, 템플릿은 이를 이전 어시스턴트 턴의 일부로 렌더링하여 모델이 자신의 이전 사고 과정을 볼 수 있도록 합니다.

v1/chat/completions
v1/responses

이전 어시스턴트 메시지에 reasoning_content를 연결합니다.

first = client.chat.completions.create(
    model="Qwen/Qwen3-1.7B",
    messages=[{"role": "user", "content": "What is 17 * 23?"}],
).choices[0].message

second = client.chat.completions.create(
    model="Qwen/Qwen3-1.7B",
    messages=[
        {"role": "user", "content": "What is 17 * 23?"},
        {
            "role": "assistant",
            "content": first.content,
            "reasoning_content": first.reasoning_content,
        },
        {"role": "user", "content": "Now multiply that result by 2."},
    ],
)
print(second.choices[0].message.content)

포트 포워딩

Port forwarding를 사용하면 원격 서버에서 모델을 서빙할 수 있습니다. 서버에 SSH 접근 권한이 있는지 확인한 후, 로컬 머신에서 다음 명령을 실행하세요.

ssh -N -f -L 8000:localhost:8000 your_server_account@your_server_IP -p port_to_ssh_into_your_server

재현성

요청마다 모델 힌트가 발생하지 않도록 하고 안정적이고 재현 가능한 실행을 수행하려면 모델을 위치 인자로 전달하세요.

transformers serve Qwen/Qwen2.5-0.5B-Instruct \
  --continuous-batching \
  --dtype "bfloat16"
Update on GitHub