디렌더러 API
디렌더러 API (Derenderer APIs)
렌더러(renderer)가 요청을 토큰 ID로 바꾸는 전처리라면, 디렌더러(derenderer)는 그 반대인 후처리를 담당해요. 생성된 토큰 ID를 다시 완전한 OpenAI 호환 응답으로 바꿔주죠. 분리 서빙(disaggregated serving)에서 token-in / token-out 엔진을 구축할 때 이 둘을 함께 써요. 이 페이지에서 디렌더러 API를 살펴볼게요.
디렌더러 API는 렌더러 API의 후처리 대응물이에요. /render가 요청을 토큰 ID로 바꾸는 전처리라면, /derender는 생성된 토큰 ID를 다시 완전한 OpenAI 호환 응답으로 바꿔요(디토크나이제이션, reasoning 파싱, tool call 파싱). 이 모든 것이 GPU 없이 이루어져요.
이렇게 하면 분리 서빙에서 token-in / token-out 엔진의 루프가 닫혀요.
- GPU 없는 후처리: 디토크나이제이션, reasoning 파싱, tool call 파싱이
/render를 호스팅하는 것과 같은 GPU 없는 프론트엔드에서 실행돼요. - 파서 일관성: 디렌더러가 vLLM의 tool·reasoning 파서를 재사용하므로, 분리 배포가 표준
vllm serve서버와 같은content/reasoning/tool_calls분리를 만들어요. - 비스트리밍: 엔드포인트는 모든 토큰 ID가 있는 완전한
GenerateResponse를 기대하고 원샷 파싱을 수행해요. 스트리밍 derender는 별도의 엔드포인트 설계가 필요하며 현재는 지원되지 않지만 파이프라인에 있어요.
두 엔드포인트 모두 vllm launch render로 시작하는 GPU 없는 렌더링 서버가 /render 엔드포인트와 함께 호스팅해요.
파이프라인 (Pipeline)
render generate derender
request ───────────────▶ token_ids ─────────▶ token_ids ──────────▶ response
(chat / (GPU less) (token-in / (GPU less) (OpenAI
completion) │ token-out engine) ▲ compatible)
└─────────────── request + prompt_tokens ──┘
derender 단계는 엔진의 token_ids만으로는 부족해요. render 단계에서 가져온 원본 chat_request/completion_request와 prompt_tokens도 소비해요(요청 형식 참고). 그래야 tool·reasoning 파서가 필요한 컨텍스트를 갖거든요.
API 참조 (API Reference)
- Chat Completions Derender API (
/v1/chat/completions/derender)- 단일
GenerateResponse를ChatCompletionResponse로 후처리.
- 단일
- Completions Derender API (
/v1/completions/derender)GenerateResponse객체 목록(프롬프트당 하나)을CompletionResponse로 후처리.
요청 형식 (Request format)
각 요청은 엔진의 GenerateResponse와, GPU 없이 최종 응답을 재구성하는 데 필요한 호출자 메타데이터를 함께 감싸요.
/v1/chat/completions/derender:
stream: Literal[False] = False
model: str | None = None
"""Served model name. Defaults to the server's served model name."""
generate_response: GenerateResponse
"""The complete token-in / token-out engine response to derender."""
prompt_tokens: int | None = None
"""Prompt token count for usage; defaults to 0 if omitted.
GenerateResponse carries only output tokens; the caller already has
len(GenerateRequest.token_ids) from the render step.
"""
chat_request: ChatCompletionRequest | None = None
"""The original (post-adjust_request) ChatCompletionRequest from /render.
Required by the parsing so that tool/reasoning parsers can receive the full
request context they expect (request.tools, request.tool_choice,
request._grammar_from_parser, etc.).
"""
/v1/completions/derender:
stream: Literal[False] = False
model: str | None = None
"""Served model name. Defaults to the server's served model name."""
generate_responses: list[GenerateResponse]
"""One response per prompt, parallel to the list[GenerateRequest]
returned by /v1/completions/render."""
prompt_tokens: list[int] | None = None
"""One prompt token count per response; each defaults to 0 if omitted.
If provided, len(prompt_tokens) must equal len(generate_responses).
"""
completion_request: CompletionRequest | None = None
"""The original (post-adjust_request) CompletionRequest from /render.
Mirrors chat_request on DerenderChatRequest. Required by the parsing
so parsers receive the full request context.
"""
과도하게 큰 페이로드는 tokenizer.decode()나 파서가 실행되기 전에 400으로 거부돼요.
예제 (Example)
아래 예시는 GPU 없는 렌더 서버(/render, /derender)와 token-in / token-out 엔진(/inference/v1/generate)에 대해 챗 요청의 전체 render → generate → derender 왕복을 구동해요.
먼저 두 서버를 실행하세요.
vllm launch render meta-llama/Llama-3.2-1B-Instruct --port 8100
VLLM_ENABLE_SCALE_OUT_ENDPOINTS=1 vllm serve \
meta-llama/Llama-3.2-1B-Instruct --tokens-only --port 8200
import httpx
MODEL = "meta-llama/Llama-3.2-1B-Instruct"
RENDER = "http://localhost:8100" # vllm launch render ...
ENGINE = "http://localhost:8200" # token-in / token-out engine
chat_request = {
"model": MODEL,
"messages": [{"role": "user", "content": "What is 2+2?"}],
"max_tokens": 32,
}
with httpx.Client(timeout=60.0) as client:
# 1. Render: request -> token IDs (GPU less)
generate_request = client.post(
f"{RENDER}/v1/chat/completions/render", json=chat_request
).json()
prompt_tokens = len(generate_request["token_ids"])
# 2. Generate: token IDs -> token IDs (token-in / token-out engine)
generate_response = client.post(
f"{ENGINE}/inference/v1/generate", json=generate_request
).json()
# 3. Derender: token IDs -> ChatCompletionResponse (GPU less)
response = client.post(
f"{RENDER}/v1/chat/completions/derender",
json={
"model": MODEL,
"generate_response": generate_response,
"prompt_tokens": prompt_tokens,
"chat_request": chat_request,
},
).json()
print(response["choices"][0]["message"]["content"])
chat_request를 넘기면 디렌더러가 설정된 tool·reasoning 파서를 실행할 수 있어요. 즉 response["choices"][0]["message"]가 vllm serve 서버가 만들 것과 같은 content/reasoning/tool_calls 분리를 갖게 돼요. chat_request를 생략하면 단순 디토크나이제이션만 수행돼요.