Skip to content

refactor: [vLLM] Abstract embedding loading to a class. Encapsulate frontend decoding receiver detail into ImageLoader - #7482

Merged
GuanLuo merged 3 commits into
mainfrom
gluo/multimodal-refactor
Mar 18, 2026
Merged

GuanLuo merged 3 commits into
mainfrom
gluo/multimodal-refactor

Conversation

@GuanLuo

@GuanLuo GuanLuo commented Mar 17, 2026 •

Copy link
Copy Markdown
Contributor

Signed-off-by: Guan Luo 41310872+GuanLuo@users.noreply.github.com

Overview:

Details:

Where should the reviewer start?

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • closes GitHub issue: #xxx

Summary by CodeRabbit

  • New Features

    • Added optional frontend image decoding support to image loader configuration.
  • Refactor

    • Refactored multimodal embedding loading from function-based to class-based architecture.
    • Improved async execution handling with context-aware logic.
    • Simplified image loading configuration by moving decoding settings from per-batch to initialization phase.

@GuanLuo
GuanLuo requested a review from a team as a code owner March 17, 2026 22:53
@GuanLuo
GuanLuo requested a review from a team March 17, 2026 22:53
@github-actions github-actions Bot added refactor backend::vllm Relates to the vllm backend multimodal labels Mar 17, 2026
@coderabbitai

coderabbitai Bot commented Mar 17, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

This PR refactors multimodal embedding handling by introducing a new run_async utility for context-agnostic async execution, converting the load_multimodal_embeddings function into a class-based MultiModalEmbeddingLoader, and shifting frontend decoding initialization from caller-provided parameters to instance-level configuration in ImageLoader.

Changes

Cohort / File(s) Summary
Async Runtime Utilities
components/src/dynamo/common/utils/runtime.py, components/src/dynamo/common/multimodal/embedding_transfer.py
Added run_async(func, *args, **kwargs) function to execute async code in context-agnostic manner with ThreadPoolExecutor fallback. Updated NixlReadEmbeddingSender and NixlReadEmbeddingReceiver initializers to use this utility instead of inline event loop detection.
Image Loader Refactor
components/src/dynamo/common/multimodal/image_loader.py, components/src/dynamo/vllm/handlers.py
Moved frontend decoding configuration from method parameters to ImageLoader.__init__ with enable_frontend_decoding parameter. Simplified load_image_batch signature by removing enable_frontend_decoding and nixl_connector parameters; state now managed via instance attributes self.enable_frontend_decoding and self.nixl_connector.
Embedding Loader Refactor
components/src/dynamo/vllm/multimodal_utils/prefill_worker_utils.py, components/src/dynamo/vllm/multimodal_utils/__init__.py, components/src/dynamo/vllm/multimodal_handlers/multimodal_pd_worker_handler.py
Converted standalone load_multimodal_embeddings() function to class-based MultiModalEmbeddingLoader with constructor accepting encode_worker_client, receiver, and optional embedding_cache_manager. Updated public API export and multimodal_pd_worker_handler to instantiate loader and delegate embedding loading.
Test Updates
components/src/dynamo/vllm/tests/multimodal_handlers/test_vllm_multimodal_pd_worker_handler.py, components/src/dynamo/vllm/tests/multimodal_utils/test_vllm_prefill_worker_utils.py
Updated test structure to work with class-based MultiModalEmbeddingLoader; renamed test class and modified invocation patterns to instantiate loader before calling load_multimodal_embeddings(). Removed test validating cache parameter passing to function.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Async hops now graceful and free,
With run_async handling the loop with glee,
ImageLoader blooms with state inside,
While embeddings dance in their class-based stride,
Refactored with care—a rabbit's delight! 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description lacks substantive content for all required sections and only contains template placeholders with no actual information. Provide concrete details in Overview, Details, and Where to start sections. Reference specific files and explain the architectural changes and rationale.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main refactoring work: abstracting embedding loading to a class and encapsulating frontend decoding details into ImageLoader, which aligns with the core changes across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
components/src/dynamo/common/utils/runtime.py (1)

76-97: Hardcoded 10-second timeout may cause silent failures.

The timeout=10 is hardcoded and not configurable. For operations like NIXL connector initialization that may take variable time depending on network conditions, this could cause unexpected TimeoutError exceptions.

Consider making the timeout configurable:

♻️ Proposed refactor to add configurable timeout
-def run_async(func, *args, **kwargs):
+def run_async(func, *args, timeout: float = 10.0, **kwargs):
     """Run an async function as if it is synchronous, handling both sync and async contexts.
 
     Args:
         func: An async function to execute.
         *args: Positional arguments to pass to the function.
+        timeout: Timeout in seconds for async context execution (default: 10.0).
         **kwargs: Keyword arguments to pass to the function.
 
     Returns:
         The result of the async function.
+
+    Raises:
+        TimeoutError: If the function does not complete within the timeout period
+            when running from an async context.
     """
     # Handle both sync and async contexts
     try:
         asyncio.get_running_loop()  # Check if we're in async context
         # If we're in an async context, we need to run the connection creation in a separate thread to avoid blocking the event loop
         import concurrent.futures
 
         with concurrent.futures.ThreadPoolExecutor() as pool:
-            return pool.submit(asyncio.run, func(*args, **kwargs)).result(timeout=10)
+            return pool.submit(asyncio.run, func(*args, **kwargs)).result(timeout=timeout)
     except RuntimeError:
         # No running loop - safe to use asyncio.run()
         return asyncio.run(func(*args, **kwargs))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/src/dynamo/common/utils/runtime.py` around lines 76 - 97, The
run_async helper hardcodes a 10s timeout which can cause silent TimeoutError;
update the run_async function signature to accept an optional timeout parameter
(e.g., timeout: Optional[float] = None), use that value instead of the hardcoded
10 when calling pool.submit(...).result(timeout=...), and ensure callers are
updated (or default remains safe) so longer connector initialization can
complete; keep the existing ThreadPoolExecutor/asyncio.run flow and propagate
any TimeoutError rather than swallowing it.
components/src/dynamo/vllm/multimodal_handlers/multimodal_pd_worker_handler.py (1)

111-115: Consider updating MultiModalEmbeddingLoader type signature to accept Client | None.

The # type: ignore on line 112 suppresses the type mismatch where encode_worker_client may be None but the loader expects Client. While this is safe at runtime (the loader's load_multimodal_embeddings has an early-return guard), aligning the type signature in MultiModalEmbeddingLoader.__init__ would be cleaner:

def __init__(
    self,
    encode_worker_client: Client | None,  # Accept None explicitly
    ...
)

This would eliminate the need for # type: ignore and make the contract explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/src/dynamo/vllm/multimodal_handlers/multimodal_pd_worker_handler.py`
around lines 111 - 115, The MultiModalEmbeddingLoader is being passed
encode_worker_client which can be None but the constructor currently types it as
Client, causing a suppressed type error; update
MultiModalEmbeddingLoader.__init__ to accept encode_worker_client: Client | None
(or Optional[Client]) in its signature and adjust any internal type hints
accordingly (noting load_multimodal_embeddings already guards for None), remove
the need for the call-site # type: ignore, and run type checks to ensure no
other callers require changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/src/dynamo/common/multimodal/image_loader.py`:
- Around line 192-195: The code incorrectly references non-existent attributes
self.enable_frontend_decoding and self.nixl_connector; change those references
to the actual instance attributes self._enable_frontend_decoding and
self._nixl_connector in the block that builds image_futures (the conditional
using DECODED_VARIANT_KEY and the call to read_decoded_media_via_nixl) so the
runtime AttributeError is resolved.

---

Nitpick comments:
In `@components/src/dynamo/common/utils/runtime.py`:
- Around line 76-97: The run_async helper hardcodes a 10s timeout which can
cause silent TimeoutError; update the run_async function signature to accept an
optional timeout parameter (e.g., timeout: Optional[float] = None), use that
value instead of the hardcoded 10 when calling
pool.submit(...).result(timeout=...), and ensure callers are updated (or default
remains safe) so longer connector initialization can complete; keep the existing
ThreadPoolExecutor/asyncio.run flow and propagate any TimeoutError rather than
swallowing it.

In
`@components/src/dynamo/vllm/multimodal_handlers/multimodal_pd_worker_handler.py`:
- Around line 111-115: The MultiModalEmbeddingLoader is being passed
encode_worker_client which can be None but the constructor currently types it as
Client, causing a suppressed type error; update
MultiModalEmbeddingLoader.__init__ to accept encode_worker_client: Client | None
(or Optional[Client]) in its signature and adjust any internal type hints
accordingly (noting load_multimodal_embeddings already guards for None), remove
the need for the call-site # type: ignore, and run type checks to ensure no
other callers require changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9b46a920-f94a-4e4c-934a-9ad534336844

📥 Commits

Reviewing files that changed from the base of the PR and between d92d5c7 and 37a779d.

📒 Files selected for processing (9)
  • components/src/dynamo/common/multimodal/embedding_transfer.py
  • components/src/dynamo/common/multimodal/image_loader.py
  • components/src/dynamo/common/utils/runtime.py
  • components/src/dynamo/vllm/handlers.py
  • components/src/dynamo/vllm/multimodal_handlers/multimodal_pd_worker_handler.py
  • components/src/dynamo/vllm/multimodal_utils/__init__.py
  • components/src/dynamo/vllm/multimodal_utils/prefill_worker_utils.py
  • components/src/dynamo/vllm/tests/multimodal_handlers/test_vllm_multimodal_pd_worker_handler.py
  • components/src/dynamo/vllm/tests/multimodal_utils/test_vllm_prefill_worker_utils.py

Comment thread components/src/dynamo/common/multimodal/image_loader.py Outdated
@GuanLuo GuanLuo changed the title refactor: Abstract embedding loading to a class. Encapsulate frontend decoding receiver detail into ImageLoader refactor: [vLLM] Abstract embedding loading to a class. Encapsulate frontend decoding receiver detail into ImageLoader Mar 17, 2026
… decoding receiver detail into ImageLoader

Signed-off-by: Guan Luo <41310872+GuanLuo@users.noreply.github.com>
Comment thread components/src/dynamo/common/utils/runtime.py
Comment thread components/src/dynamo/vllm/multimodal_handlers/multimodal_pd_worker_handler.py Outdated
Comment thread components/src/dynamo/common/utils/runtime.py Outdated
Signed-off-by: Guan Luo <41310872+GuanLuo@users.noreply.github.com>
@GuanLuo
GuanLuo requested a review from KrishnanPrash March 18, 2026 22:15
Signed-off-by: Guan Luo <41310872+GuanLuo@users.noreply.github.com>
@GuanLuo
GuanLuo enabled auto-merge (squash) March 18, 2026 22:42
@GuanLuo
GuanLuo merged commit 3ccf39e into main Mar 18, 2026
146 checks passed
@GuanLuo
GuanLuo deleted the gluo/multimodal-refactor branch March 18, 2026 23:16
ShounakRay pushed a commit to ShounakRay/fuzzy-dynamo that referenced this pull request Mar 20, 2026
…rontend decoding receiver detail into ImageLoader (ai-dynamo#7482)

Signed-off-by: Guan Luo <41310872+GuanLuo@users.noreply.github.com>
yao531441 pushed a commit to yao531441/dynamo that referenced this pull request May 13, 2026
…rontend decoding receiver detail into ImageLoader (ai-dynamo#7482)

Signed-off-by: Guan Luo <41310872+GuanLuo@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
GITLAB — 1bbfa6b7 Deployed Mar 18, 2026 by copy-pr-bot[bot] via Trigger CI Pipeline #21128
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants