# SPDX-License-Identifier: Apache-2.0# SPDX-FileCopyrightText: Copyright contributors to the vLLM project"""Disaggregated multimodal serving: render → generate round-trip.Demonstrates the two-phase disaggregated flow: 1. /v1/chat/completions/render – preprocesses a multimodal chat request into token IDs and serialized tensor features. 2. /inference/v1/generate – runs inference on the preprocessed tokens.The render response is passed *directly* to generate with only``sampling_params`` added, showing that the two endpoints compose withzero client-side transformation.Launch the server first: vllm serve Qwen/Qwen3-VL-2B-Instruct \ --dtype bfloat16 --max-model-len 4096 --enforce-eager \ --enable-scale-outThen run this script: python example_mm_serve.py"""importioimportpybase64asbase64importrequestsfromPILimportImagefromtransformersimportAutoTokenizerBASE_URL="http://localhost:8000"MODEL_NAME="Qwen/Qwen3-VL-2B-Instruct"defmake_data_url(image:Image.Image)->str:"""Encode a PIL image as a base64 data URL."""buf=io.BytesIO()image.save(buf,format="PNG")b64=base64.b64encode(buf.getvalue()).decode()returnf"data:image/png;base64,{b64}"defmain():# -- Step 1: Create a test image (solid red) -------------------------image=Image.new("RGB",(224,224),color=(255,0,0))data_url=make_data_url(image)print("Created 224x224 red test image")# -- Step 2: Render (preprocess) -------------------------------------render_payload={"model":MODEL_NAME,"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":data_url}},{"type":"text","text":"What color is this image? Answer in one word.",},],}],}print("\n--- Render ---")render_resp=requests.post(f"{BASE_URL}/v1/chat/completions/render",json=render_payload)render_resp.raise_for_status()render_data=render_resp.json()print(f"Response keys: {list(render_data.keys())}")print(f"Number of token_ids: {len(render_data['token_ids'])}")features=render_data.get("features")iffeaturesandfeatures.get("kwargs_data"):print(f"kwargs_data modalities: {list(features['kwargs_data'].keys())}")formodality,itemsinfeatures["kwargs_data"].items():print(f" {modality}: {len(items)} item(s), "f"first item type: {type(items[0])} length: {len(items[0])}"ifitemselse"First item: (empty)")else:print("WARNING: no kwargs_data in render response")# -- Step 3: Generate (inference) ------------------------------------# Pass the render output directly — only add sampling_params.generate_payload=render_datagenerate_payload["sampling_params"]={"max_tokens":20,"temperature":0.0,}print("\n--- Generate ---")gen_resp=requests.post(f"{BASE_URL}/inference/v1/generate",json=generate_payload)gen_resp.raise_for_status()gen_data=gen_resp.json()# -- Step 4: Decode & print ------------------------------------------output_ids=gen_data["choices"][0]["token_ids"]tokenizer=AutoTokenizer.from_pretrained(MODEL_NAME)text=tokenizer.decode(output_ids,skip_special_tokens=True)print(f"Output token count: {len(output_ids)}")print(f"Generated text: {text!r}")if"red"intext.lower():print("\nModel correctly identified the red image.")else:print(f"\nWARNING: Expected 'red' in output, got: {text!r}")if__name__=="__main__":main()