Skip to content

Draft: [None][feat] Multimodal encoder cache: per-item partial hits#16817

Draft
aswinvisva wants to merge 2 commits into
NVIDIA:mainfrom
aswinvisva:avisva/mm-encoder-granular-caching
Draft

Draft: [None][feat] Multimodal encoder cache: per-item partial hits#16817
aswinvisva wants to merge 2 commits into
NVIDIA:mainfrom
aswinvisva:avisva/mm-encoder-granular-caching

Conversation

@aswinvisva

@aswinvisva aswinvisva commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Description

Persistent multimodal encoder cache is currently all-or-nothing per MultimodalParams: if any item of a request misses the cache, every item is re-encoded.

Add an opt-in per-item partial-hit path to MultimodalModelMixin:

  • EncoderCachePartition records the per-item cache lookup result (hits / miss indices / keys).
  • _partition_encoder_cache replaces _attach_encoder_cache_hit and records every item lookup rather than bailing on first miss.
  • _encode_with_partial_cache encodes only the miss items via a residual MultimodalParams, then interleaves cached and freshly encoded rows in original per-item order so downstream get_multimodal_embeddings treats the param as fully cached.
  • _apply_metadata_slice re-slices multimodal_embedding_lengths and multimodal_hashes on the residual so every model gets the parallel per-item metadata slice identically.
  • Models opt in with supports_granular_encoder_cache = True and implement slice_multimodal_data_items, which slices raw modality tensors (pixel_values, image_grid_thw, ...) for the given item indices. Default remains False; existing models continue on the all-or-nothing path unchanged.

Onboard Mistral 3 (Mistral-Small-3.1-24B) as the first opted-in model by slicing pixel_values on dim 0 and image_sizes list in parallel.

Perf

Real trtllm-serve run against mistralai/Mistral-Small-3.1-24B-Instruct-2503 on 1x H200 (BF16, TP=1, max_seq_len=65536, encoder_cache_max_bytes=2GiB). Two requests each with 100 items of 512x512 images; request 2 shares 99 items with request 1 and adds 1 novel item:

config req1 [100 fresh] req2 [99 shared + 1 novel] req2 delta
baseline 5966 ms 5543 ms -423 ms
granular 5962 ms 5136 ms -826 ms

Direct req2 comparison: granular is 406 ms faster than the all-or-nothing baseline, matching the encoder cost of 99 Pixtral + projector passes that granular skips.

Dev Engineer Review

  • Adds opt-in granular persistent encoder-cache reuse through MultimodalModelMixin.
  • Partitions per-item hits and misses, encodes only misses, restores original ordering, and updates cache entries.
  • Preserves all-or-nothing behavior by default.
  • Adds metadata slicing support and onboards Mistral 3 for pixel_values and image_sizes.
  • No configuration or test-list changes identified.

QA Engineer Review

No test changes.

Description

Test Coverage

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

* Why?

Persistent multimodal encoder cache is currently all-or-nothing per
`MultimodalParams`: if any item of a request misses the cache, every
item is re-encoded. In practice this makes the cache inert for common
VLM workloads where consecutive requests share most of their images
but differ in one -- e.g. multi-turn conversations that grow context,
or comparison tasks that anchor on a shared reference set.

* What?

Add an opt-in per-item partial-hit path to `MultimodalModelMixin`:

- `EncoderCachePartition` records the per-item cache lookup result
  (hits / miss indices / keys).
- `_partition_encoder_cache` replaces `_attach_encoder_cache_hit` and
  records every item lookup rather than bailing on first miss.
- `_encode_with_partial_cache` encodes only the miss items via a
  residual `MultimodalParams`, then interleaves cached and freshly
  encoded rows in original per-item order so downstream
  `get_multimodal_embeddings` treats the param as fully cached.
- `_apply_metadata_slice` re-slices `multimodal_embedding_lengths`
  and `multimodal_hashes` on the residual so every model gets the
  parallel per-item metadata slice identically.
- Models opt in with `supports_granular_encoder_cache = True` and
  implement `slice_multimodal_data_items`, which slices raw modality
  tensors (`pixel_values`, `image_grid_thw`, ...) for the given
  item indices. Default remains `False`; existing models continue on
  the all-or-nothing path unchanged.

Onboard Mistral 3 (Mistral-Small-3.1-24B) as the first opted-in model
by slicing `pixel_values` on dim 0 and `image_sizes` list in parallel.

* Numbers

Real trtllm-serve run against `mistralai/Mistral-Small-3.1-24B-Instruct-2503`
on 1x H200 (BF16, TP=1, max_seq_len=65536, encoder_cache_max_bytes=2GiB).
Two requests each with 100 items of 512x512 images; request 2 shares
99 items with request 1 and adds 1 novel item:

| config   | req1 [100 fresh] | req2 [99 shared + 1 novel] | req2 delta |
|----------|-----------------:|---------------------------:|-----------:|
| baseline |         5966 ms  |                    5543 ms |    -423 ms |
| granular |         5962 ms  |                    5136 ms |    -826 ms |

Direct req2 comparison: granular is 406 ms faster than the
all-or-nothing baseline, matching the encoder cost of 99 Pixtral +
projector passes that granular skips.

Signed-off-by: Aswin Visva <31215515+aswinvisva@users.noreply.github.com>
@aswinvisva
aswinvisva requested a review from a team as a code owner July 23, 2026 23:27
@aswinvisva
aswinvisva requested a review from schetlur-nv July 23, 2026 23:27
@aswinvisva
aswinvisva marked this pull request as draft July 23, 2026 23:27
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8d84e57a-627d-4dc8-a965-4789dbb7d780

📥 Commits

Reviewing files that changed from the base of the PR and between 80b4eb3 and b0e1045.

📒 Files selected for processing (2)
  • tensorrt_llm/_torch/models/modeling_mistral.py
  • tensorrt_llm/_torch/models/modeling_multimodal_mixin.py

Walkthrough

Adds granular multimodal encoder-cache reuse to MultimodalModelMixin and enables it for Mistral3VLM, including per-item cache partitioning, partial encoding, embedding reassembly, metadata slicing, and image-input slicing.

Changes

Granular encoder cache

Layer / File(s) Summary
Cache partition and slicing contracts
tensorrt_llm/_torch/models/modeling_multimodal_mixin.py
Adds EncoderCachePartition, the granular-cache capability flag, and an overridable multimodal item-slicing hook.
Partial cache orchestration
tensorrt_llm/_torch/models/modeling_multimodal_mixin.py
Partitions per-item cache state, reuses full hits, encodes miss-only slices, reassembles embeddings, reapplies metadata, and stores newly encoded items.
Mistral3VLM image slicing
tensorrt_llm/_torch/models/modeling_mistral.py
Enables granular caching and slices image tensors and image sizes for selected item indices while copying multimodal input metadata.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MultimodalModelMixin
  participant EncoderCache
  participant Mistral3VLM
  MultimodalModelMixin->>EncoderCache: Partition per-item cache keys
  EncoderCache-->>MultimodalModelMixin: Return hits and miss indices
  MultimodalModelMixin->>Mistral3VLM: Slice multimodal data for misses
  Mistral3VLM-->>MultimodalModelMixin: Return miss-only parameters
  MultimodalModelMixin->>EncoderCache: Store newly encoded miss items
Loading

Suggested reviewers: schetlur-nv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the change set and follows the required [None][feat] pattern, though the Draft prefix is a bit noisy.
Description check ✅ Passed The description explains the problem, solution, and performance impact, but the Test Coverage section is still empty.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

… slicer

* Why?

To land ahead of the item-level MM encoder scheduling work on
yechank-nvidia/multimodal-encoder-runtime-scheduling without forcing a
churny rebase, the partial-cache primitives need a stable public
surface his executor can call into. Two smaller shifts fall out
naturally:
  - the per-model slicing hook can be a mixin default for the two
    common layouts, removing the only Mistral 3 code delta;
  - the second opt-in flag is redundant with `supports_encoder_cache`.

* What?

- Rename the partial-cache primitives to drop the underscore prefix so
  callers outside the mixin can consume them:
    `_partition_encoder_cache`   -> `partition_encoder_cache`
    `_assemble_full_embedding`   -> `assemble_full_embedding`
    `slice_multimodal_data_items` -> `build_multimodal_encoder_input`
  The last rename also aligns naming with the item-scheduling branch,
  which already uses `build_multimodal_encoder_input`.

- Provide a default `build_multimodal_encoder_input` in the mixin
  covering the two common single-modality layouts:
    * Stacked on dim 0 (Pixtral / LLaVA-family): slice `pixel_values`
      dim 0 and the parallel `image_sizes` list.
    * Packed with `*_grid_thw` offsets (Qwen2-VL family): prefix-sum
      patch counts to locate each item's slab, concat requested items
      in item-index order, slice `image_grid_thw` in parallel.
  Models with a different layout override.

- Drop `supports_granular_encoder_cache`. `supports_encoder_cache = True`
  already carries the opt-in signal; a model that opts into the cache
  and has an unhandled layout gets a loud `NotImplementedError` from
  the default slicer -- the correct signal to override.

- Delete Mistral 3's `build_multimodal_encoder_input` override: the
  default now covers it. Mistral 3's net diff shrinks to zero.

* Numbers

Same 1x H200 / Mistral-Small-3.1-24B / N=100 / 99-shared A/B run as
the previous commit, now with the mixin default doing the slicing:

  req1 [100 fresh]           5928 ms
  req2 [99 shared + 1 novel] 5114 ms  (delta -814 ms / -13.7%)

Within measurement noise of the override-based numbers, confirming
the default reproduces the override end-to-end.

Signed-off-by: Aswin Visva <31215515+aswinvisva@users.noreply.github.com>
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.

1 participant