Skip to content

[Docs] Add Qwen2.5-VL guide for the TRT-LLM PyTorch backend, deprecate Llava1.5 guide - #169

Merged
whoisj merged 11 commits into
triton-inference-server:mainfrom
faradawn:docs/multimodal-pytorch-backend-guide
Sep 11, 2026
Merged

whoisj merged 11 commits into
triton-inference-server:mainfrom
faradawn:docs/multimodal-pytorch-backend-guide

Conversation

@faradawn

@faradawn faradawn commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
image

Related TRTLLM: NVIDIA/TensorRT-LLM#18381

Related Triton Docs Indexing: triton-inference-server/server#8950

Related issue: triton-inference-server/server#8945

Tested on B200 successfully

pip install --target=$WORK/pylibs -U openai

cat > ./model_repository/tensorrt_llm/1/model.yaml <<'EOF'
model: Qwen/Qwen2.5-VL-3B-Instruct
backend: "pytorch"
tensor_parallel_size: 1
kv_cache_config:
  free_gpu_memory_fraction: 0.5

triton_config:
  max_batch_size: 0
  decoupled: False
  multimodal: True
EOF


trtllm-llmapi-launch tritonserver \
  --model-repository=$WORK/model_repository \
  --http-port=8000 --grpc-port=8001 --metrics-port=8002



root@prenyx0211:/opt/tritonserver# curl -s http://localhost:8000/v2/models/tensorrt_llm/infer -H 'Content-Type: application/json' -d '{
  "inputs": [
    {"name":"text_input","shape":[1],"datatype":"BYTES","data":["What color is the bus and what does the sign say?"]},
    {"name":"image_url","shape":[1],"datatype":"BYTES","data":["http://images.cocodataset.org/test2017/000000155781.jpg"]},
    {"name":"sampling_param_max_tokens","shape":[1],"datatype":"INT32","data":[64]},
    {"name":"sampling_param_exclude_input_from_output","shape":[1],"datatype":"BOOL","data":[true]}
  ],
  "outputs": [{"name":"text_output"}]
}'

>>> {"model_name":"tensorrt_llm","model_version":"1","outputs":[{"name":"text_output","datatype":"BYTES","shape":[1],"data":["The bus is yellow and white, and the sign on the bus says \"Out of Service.\""]}]}root@prenyx0211:/opt/tritonserver#

Add Popular_Models_Guide/Qwen2.5-VL/qwen2_5_vl_trtllm_guide.md, documenting
how to serve a multimodal (vision) model on Triton via the TensorRT-LLM
PyTorch backend through the llmapi backend. No engine build is required.

Also add a deprecation banner to the Llava1.5 TensorRT-LLM guide, whose
prebuilt-engine multimodal path is end-of-life as of TensorRT-LLM v1.2.

The multimodal image_url input and triton_config.multimodal opt-in used by
the new guide are added by NVIDIA/TensorRT-LLM#18381, which is not yet
merged; the guide states this prominently.

Signed-off-by: Faradawn Yang <73060648+faradawn@users.noreply.github.com>
all_models/llmapi/ holds exactly one model directory, so it can be used as a
Triton model repository directly instead of copying files into a new one. Give
the concrete clone and model.yaml commands, and add a curl example with the
response it returns.

Signed-off-by: Faradawn Yang <73060648+faradawn@users.noreply.github.com>
whoisj
whoisj previously approved these changes Sep 2, 2026

@whoisj whoisj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

The backend now accepts only http(s) URLs and inline data URIs; local
filesystem paths and file:// are rejected because the input is
client-controlled. Document that and drop the local-path example.

Signed-off-by: Faradawn Yang <73060648+faradawn@users.noreply.github.com>
faradawn and others added 7 commits September 8, 2026 12:33
The backend rejects local paths, file:// and data: URIs, so document the
http(s)-only scope and drop the data-URI examples.

Signed-off-by: Faradawn Yang <73060648+faradawn@users.noreply.github.com>
The guide claimed validation on TensorRT-LLM 1.2.1, but following it verbatim
on nvcr.io/nvidia/tritonserver:26.07-trtllm-python-py3 -- the newest published
-trtllm-python-py3 tag -- fails every request that carries an image:

    Error generating request: cannot import name
    'async_build_multimodal_prompt' from 'tensorrt_llm.inputs'

The backend files readers clone call `async_build_multimodal_prompt`, which
NVIDIA/TensorRT-LLM#18381 adds to `tensorrt_llm/inputs/utils.py`. That module
ships inside the wheel, not in the `triton_backend/` tree, so on a 1.2.1
container the caller is present and the callee never is. The server still
starts, still logs `multimodal input enabled`, and still answers text-only
prompts, so the deployment looks healthy right up until the first image.

Add a "Patching model.py for TensorRT-LLM 1.2.1" section carrying the
replacement method and the call-site diff, gated behind a note to skip it once
a container ships with #18381 in it. Say in the validation table that 1.2.1
needs that patch, and record the torch build.

Also fix three things found while testing:

  - The multiple-images example passed `/workspace/images/second.jpg`, a local
    path the guide's own "Allowed scope of access" section says is rejected.
    That example could only ever error. Use two live http URLs and show the
    real two-image answer.
  - Show the actual rejection and connection-failure responses, including the
    `ssl:default [Name or service not known]` tail that was trimmed, as JSON
    response bodies rather than log lines.
  - Add troubleshooting rows for the ImportError and for a rejected scheme.

Verified on 1x B200 with Qwen/Qwen2.5-VL-3B-Instruct: single image returns the
answer this guide quotes, two images are described in order, and the rejection
and unreachable-host paths surface as errors rather than silently degrading to
text-only. Checked by applying the patch text extracted from this file to a
fresh clone, so what is documented is what was run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…our files

Follow-up to the previous commit, which described the model.py change but left
the reader to make it by hand. model.py is ~790 lines, and splicing a 40-line
method into the right class at the right indentation is a reliable way to end up
with a broken model repository.

Ship the edit as trtllm_121_compat.py next to the guide instead, invoked in one
command. This is how this directory already works: the Llava1.5 guide being
deprecated here ships multi_modal_client.py beside it, and Llama2 ships
deploy_trtllm_llama.sh.

The script is defensive, because it edits a file the reader did not write:
it is idempotent, it refuses to write source that does not parse, and if the
call it targets is absent it explains that #18381 has probably merged and gives
the one-line import check to confirm, rather than corrupting the checkout.

The guide keeps the explanation -- the call-site diff and a table mapping each
1.3 API onto the 1.2.1 equivalent -- so a reader can still see what changes and
why, without having to type it.

Separately, replace the full clone with a blobless, LFS-skipped sparse checkout:

    GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1 --filter=blob:none --sparse ...
    git -C ... sparse-checkout set triton_backend/all_models/llmapi

Four seconds and 9.1 MB, against roughly 900 MB of Git LFS payload for four
small text files.

Also state plainly that 1.2.1 is the standing target: every published
-trtllm-python-py3 image ships it, so #18381 merging upstream does not remove
the need for this patch. Only a new container image does.

Verified end to end on 1x B200 by running the guide's own commands, including
the shipped script against a fresh sparse clone with nothing hand-edited:
single image returns the quoted answer, two images are described in order, a
rejected scheme and an unreachable host both surface as errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nd files

The guide cloned the #18381 branch, which targets TensorRT-LLM 1.3, and then
patched it back down to work against the 1.2.1 wheel in the container. That is
backwards: it carried 358 lines of difference from v1.2.1, of which only ~30 is
the image feature and the rest is unrelated 1.3 drift running against a 1.2.1
runtime.

Start from the v1.2.1 tag instead, which is exactly the TensorRT-LLM installed
in nvcr.io/nvidia/tritonserver:26.07-trtllm-python-py3, and add the image input
on top. That is 109 lines on a base that matches the wheel.

Two consequences:

  - The guide no longer depends on a personal fork or an unmerged pull request.
    Everything comes from the official NVIDIA v1.2.1 tag plus two files shipped
    here, following the convention of the Llava1.5 guide next door.
  - There is nothing for the reader to patch. model.py and config.pbtxt are
    provided; helpers.py comes from v1.2.1 unchanged; model.yaml is written in
    the guide. Copy two files instead of editing an 800-line one.

model.py carries a provenance header naming its v1.2.1 source, and
scripts that regenerate it are described in the guide.

Also rewrite the guide as plain steps -- container, openai, model repository,
serve, request -- following the structure of llava_trtllm_guide.md, and drop
the running commentary about what does not work. 480 lines to 231.

Verified on 1x B200 with Qwen/Qwen2.5-VL-3B-Instruct by running the guide's
commands as written: single image returns the quoted answer, two images are
described in order, a local path and an unreachable host both surface errors,
and text-only requests still work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The header ended with "Regenerate with tutorials/Popular_Models_Guide/
Qwen2.5-VL, see the guide", which pointed at nothing: no generator is shipped
and the guide does not describe one. model.py is a plain file to copy, and that
is the whole intent.

Replace that line with the two-command diff against the stock v1.2.1 file, and
name the four additions, so a reader can audit what changed without needing any
tooling from us.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut the framing that does not help someone deploying the model: the comparison
with the deprecated engine-build path, the note that this supersedes the
Llava1.5 guide, the tested-configuration paragraph, the MPI_ERR_SPAWN
explanation, and the troubleshooting and references sections. Say only that the
guide uses 26.07, which is the newest -trtllm-python-py3 tag on NGC.

Reduce the openai section to why and how, one line each.

NVIDIA/TensorRT-LLM#18381 has merged, so describe the two provided files by what
they are rather than by a pending pull request: the 26.07 container ships
TensorRT-LLM v1.2.1, whose llmapi backend has no image input, and these two
files add it for that version, adapted from main. Once a container ships v1.3.0
or newer, take model.py and config.pbtxt from main and skip both.

231 lines to 198. Re-verified end to end on 1x B200 after the trim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drop the claim that 26.07 is the newest tag on NGC, which would go stale on the
next release; name the container and leave it there. Drop the sentence about
where the two provided files were adapted from, keeping only what a reader acts
on: once a Triton container ships TensorRT-LLM v1.3.0, copy model.py and
config.pbtxt from main and skip them. Drop the rationale behind the http(s)
restriction and keep the rule itself.

Prose only; no command changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@faradawn
faradawn marked this pull request as ready for review September 10, 2026 17:26
@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 2/5

The PR is not safe to merge until arbitrary media URLs are fetched safely and the optional numeric response tensors conform to their declared Triton datatypes.

Findings

  1. P1 Security Unsafe image URL fetching
  2. P1 Numeric outputs use strings
  3. P2 Deprecation names wrong architecture
  4. P2 Repository copy is state-dependent

Summary

  • Adds optional image-URL request handling and multimodal prompt construction.
  • Documents creation and launch of a Qwen2.5-VL model repository using the 26.07 Triton container.
  • Adds the matching Triton input/output contract.
  • Redirects users away from the legacy Llava1.5 deployment.
  • The new media-fetching path needs destination and resource protections, and optional numeric responses currently use datatypes inconsistent with the model configuration.

Diagram

sequenceDiagram
    participant Client
    participant Triton as Triton llmapi backend
    participant Loader as async_load_image
    participant Target as Supplied HTTP(S) target
    participant Model as Qwen2.5-VL

    Client->>Triton: text_input + image_url[]
    Triton->>Triton: Check URL scheme only
    loop Every image URL
        Triton->>Loader: Load URL
        Loader->>Target: Outbound HTTP(S) request
        Target-->>Loader: Image response
    end
    Loader-->>Triton: Multimodal image data
    Triton->>Model: Rendered prompt + images
    Model-->>Triton: Generated text
    Triton-->>Client: text_output
Loading

Reviews (1) · Last reviewed commit: "docs: trim three more lines from the Qwe..."

Comment thread Popular_Models_Guide/Qwen2.5-VL/model.py
Comment on lines +688 to +695
for config_key, (output_name, extractor) in output_fields.items():
if output_config[config_key]:
tensor_data = [
str(extractor(output)) for output in request_output.outputs
]
response.append(
pb_utils.Tensor(output_name,
np.asarray(tensor_data, dtype=np.object_)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Numeric outputs use strings

Optional numeric outputs are constructed with string or object NumPy dtypes even though config.pbtxt declares them as TYPE_FP32, TYPE_INT32, or TYPE_INT64. For example, requesting return_cumulative_logprob emits an object/string tensor for a TYPE_FP32 output. The performance metrics have the same problem because they use self.output_dtype, which comes from the TYPE_STRING text_output configuration. Triton can therefore reject these responses instead of returning the requested metrics. Construct each tensor with the dtype declared for that output.

Comment on lines +31 to +34
> [!WARNING]
> **Deprecated.** This guide describes the prebuilt-TensorRT-engine multimodal
> path (`tensorrtllm_backend`'s `all_models/multimodal`), which TensorRT-LLM has
> declared end-of-life as of TensorRT-LLM v1.2. It is no longer maintained.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Deprecation names wrong architecture

The warning says this guide describes tensorrtllm_backend's all_models/multimodal path, but the guide actually builds TensorRT engines itself and serves a tutorial-local repository containing custom Python vision and wrapper models. This gives readers an inaccurate explanation of which deployment architecture reached end of life. Please describe the actual manual-engine, custom-repository path being deprecated or cite the precise upstream component that applies to it.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

--branch v1.2.1 https://github.com/NVIDIA/TensorRT-LLM.git /workspace/trtllm
git -C /workspace/trtllm sparse-checkout set triton_backend/all_models/llmapi

cp -r /workspace/trtllm/triton_backend/all_models/llmapi /workspace/model_repository

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Repository copy is state-dependent

This command only creates the documented layout when /workspace/model_repository does not already exist. On a rerun or after a partial attempt, cp -r creates model_repository/llmapi/tensorrt_llm, while the following commands expect model_repository/tensorrt_llm and fail. Make the destination state explicit or copy the source directory's contents so readers can safely repeat the tutorial.

@whoisj
whoisj merged commit 9511717 into triton-inference-server:main Sep 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants