Skip to content

NIXL/UCX: enforce VRAM memtype hint query behavior and add tests - #1536

Open
yafshar wants to merge 6 commits into
ai-dynamo:mainfrom
yafshar:fix/ucx-vram-memtype-hint
Open

yafshar wants to merge 6 commits into
ai-dynamo:mainfrom
yafshar:fix/ucx-vram-memtype-hint

Conversation

@yafshar

@yafshar yafshar commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

What?

This PR hardens UCX VRAM memtype hint handling and aligns documentation and
tests with runtime behavior.

  • Enforce fail-fast behavior when an explicit ucx_vram_memtype_hint is set
    but UCX context memory types cannot be queried.
  • Keep auto and none as non-failing modes that proceed without hinting
    when memory-type queries are unavailable.
  • Document the exact supported values for ucx_vram_memtype_hint:
    auto, none, cuda, cuda-managed, rocm, ze-device.
  • Clarify case-sensitive parsing and failure behavior for explicit hints.
  • Add gtests to verify default parameter exposure and rejection of
    case-mismatched values (e.g. CUDA).
  • Suppress expected error logs in negative-path tests to avoid triggering
    global log-failure checks.

Why?

Previous behavior around explicit VRAM memtype hints was not fully documented
or covered by tests, which could lead to confusion and fragile failure paths.

This change improves correctness and predictability by:

  • Making explicit-hint behavior deterministic when UCX cannot report
    memory-type capabilities.
  • Ensuring documentation reflects actual accepted values and parsing rules.
  • Adding regression coverage for default and invalid-input paths.

How?

  • Update UCX memory-type resolution to require a successful UCX context
    memory-type query for explicit hint modes.
  • Keep auto and none permissive when queries are unavailable.
  • Update backend and Python documentation to reflect exact values and
    failure semantics.
  • Add UCX backend parameter tests covering:
    • default ucx_vram_memtype_hint exposure (auto)
    • rejection of case-mismatched hint values
  • Add log-ignore guards for expected error paths in negative tests.

Testing performed

  • Ran the targeted gtest suite via the Meson test wrapper.
  • Result: 12 tests from 3 suites passed, 0 failed.

Credits

Co-authored-by: Daniel Socek daniel.socek@intel.com

Summary by CodeRabbit

  • Documentation

    • Added docs for UCX backend initialization parameters and how to override them; documented ucx_error_handling_mode and ucx_vram_memtype_hint, accepted values, defaults, case-sensitivity, and failure semantics.
  • New Features

    • Added ucx_vram_memtype_hint backend option to control VRAM memory-type hinting (auto, none, explicit accelerator hints; default: auto). Hint is resolved at backend init and applied to VRAM registrations when available.
  • Tests

    • Added tests verifying ucx_vram_memtype_hint default, case-sensitive handling, and behavior when explicit hints are unsupported or unqueryable.

@copy-pr-bot

copy-pr-bot Bot commented Apr 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

Copy link
Copy Markdown

👋 Hi yafshar! Thank you for contributing to ai-dynamo/nixl.

Your PR reviewers will review your contribution then trigger the CI to test your changes.

🚀

@yafshar
yafshar marked this pull request as draft April 15, 2026 17:45
@yafshar
yafshar marked this pull request as ready for review April 15, 2026 17:48
@coderabbitai

coderabbitai Bot commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Adds a UCX backend initialization option ucx_vram_memtype_hint with parsing and string↔enum helpers; UCX context now queries supported memory types during construction, resolves a memtype hint (AUTO/NONE/explicit), validates it, and applies a resolved memtype when registering VRAM segments.

Changes

Cohort / File(s) Summary
Documentation
docs/BackendGuide.md, docs/python_api.md
New docs describing backend init params and usage: ucx_vram_memtype_hint and ucx_error_handling_mode, accepted values (auto, none, cuda, cuda-managed, rocm, ze-device), case-sensitive matching, and failure behavior for explicit hints.
UCX core utils & API
src/plugins/ucx/ucx_utils.h, src/plugins/ucx/ucx_utils.cpp
Add nixl_ucx_vram_memtype_hint_t enum and param name constant; implement string↔enum helpers, policy→memtype mapping, ucx_auto_detect_vram_hint, supported-memtype tracking, resolveMemoryTypeConfig(), store resolved hint, and set memory type param during VRAM memReg().
Backend init
src/plugins/ucx/ucx_backend.cpp
Read ucx_vram_memtype_hint plugin param in nixlUcxEngine constructor (default AUTO) and pass the policy into nixlUcxContext.
Examples
examples/device/ep/csrc/nixl_ep.cpp
Example UCX init params now include ucx_vram_memtype_hint = "auto".
Tests
test/gtest/error_handling.cpp
Add constants and tests verifying the new default "auto", behavior for explicit hints (expect failure when unsupported or unqueryable), and helper to set the plugin directory.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Plugin as "Backend Plugin"
    participant Context as "nixlUcxContext"
    participant UCX as "UCX (ucp_context_query)"

    User->>Plugin: get_plugin_params("UCX")
    Plugin-->>User: returns params (ucx_vram_memtype_hint default = "auto")
    User->>Plugin: create_backend("UCX", params)
    Plugin->>Plugin: parse ucx_vram_memtype_hint -> policy
    Plugin->>Context: construct(..., vram_mem_type_hint_policy)
    Context->>UCX: ucp_context_query() -> supported memtype mask
    alt policy == AUTO
        UCX-->>Context: mask
        Context->>Context: ucx_auto_detect_vram_hint(mask) -> maybe set resolved memtype
    else policy == NONE
        UCX-->>Context: mask
        Context->>Context: leave hint unset
    else explicit policy
        UCX-->>Context: mask
        Context->>Context: validate policy in mask (throw if unsupported)
    end
    Context-->>Plugin: constructed (resolved hint state)
    User->>Plugin: memReg(VRAM_SEG)
    Plugin->>Context: memReg -> include UCP_MEM_MAP_PARAM_FIELD_MEMORY_TYPE if resolved hint present
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: enforcing VRAM memtype hint query behavior in UCX and adding tests.
Description check ✅ Passed The description follows the required template with clear What, Why, and How sections, plus testing details and credits.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/ucx/ucx_backend.cpp (1)

849-869: ⚠️ Potential issue | 🟡 Minor

Run clang-format on the new UCX hint parsing block.

This hunk is currently tripping the clang-format check, so CI stays red until it is reformatted.

As per coding guidelines, "Enforce formatting constraints: 4-space indentation, max 100 char lines, braces on same line (including if/for/try/catch), and break long function signatures/calls across lines."

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

In `@src/plugins/ucx/ucx_backend.cpp` around lines 849 - 869, Reformat the new UCX
hint parsing block to satisfy clang-format: enforce 4-space indentation, max
100-char lines, braces on same line, and break long calls/initializers across
lines; specifically adjust the nixl_ucx_vram_memtype_hint_t declaration and the
vram_memtype_hint_it lookup/assignment so lines do not exceed 100 chars and
align with 4-space indent, and reflow the uc =
std::make_unique<nixlUcxContext>(...) call arguments (devs,
init_params.enableProgTh, num_workers, init_params.syncMode,
num_device_channels, engine_config, vram_memtype_hint_policy) onto separate
indented lines so the call fits formatting rules, then run clang-format to apply
the project's style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/python_api.md`:
- Around line 60-66: Update the ucx_vram_memtype_hint docs to document the
additional failure path: besides the case where UCX context memory types cannot
be queried, explicitly mention that if UCX context is queried but does not
advertise the requested memory type (e.g., user selects
`cuda`/`rocm`/`ze-device` but UCX reports no such memtype), backend creation
will be rejected for those explicit hints; reference the same behavior described
in BackendGuide.md and the ucx_utils.cpp logic to keep the doc consistent with
implementation.

In `@test/gtest/error_handling.cpp`:
- Around line 26-29: The new pointer constant declarations
(ucx_err_handling_mode_key, ucx_err_handling_mode_peer,
ucx_vram_memtype_hint_key, ucx_vram_memtype_hint_auto) are misformatted and
failing clang-format; reformat them to match the project's pointer spacing style
(adjust spacing around the '*' to be consistent with existing constants) and run
clang-format to apply the repository style so the declarations conform to the
file's formatting rules.
- Around line 441-484: Set the plugin directory and add a runtime-negative case:
ensure these tests set the same NIXL_PLUGIN_DIR used by TestErrorHandling (so
UCX is discovered) before calling agent.getAvailPlugins (mirror the setup at
TestErrorHandling lines that export NIXL_PLUGIN_DIR), and replace the
rejected-from-parser hint ("CUDA") with two things: 1) keep the existing
RejectsCaseMismatchedVramHint test but set the env/plugin dir so it doesn't
GTEST_SKIP, and 2) add a new negative test that sets
params[nixl::ucx_vram_memtype_hint_key] to a syntactically valid hint string
(one accepted by nixl::ucx_vram_memtype_hint_from_string) so that code reaches
nixlUcxContext::resolveMemoryTypeConfig() and triggers the intended runtime
failure path; locate params by the symbol params and the parser by
nixl::ucx_vram_memtype_hint_from_string and the runtime logic by
nixlUcxContext::resolveMemoryTypeConfig when making changes.

---

Outside diff comments:
In `@src/plugins/ucx/ucx_backend.cpp`:
- Around line 849-869: Reformat the new UCX hint parsing block to satisfy
clang-format: enforce 4-space indentation, max 100-char lines, braces on same
line, and break long calls/initializers across lines; specifically adjust the
nixl_ucx_vram_memtype_hint_t declaration and the vram_memtype_hint_it
lookup/assignment so lines do not exceed 100 chars and align with 4-space
indent, and reflow the uc = std::make_unique<nixlUcxContext>(...) call arguments
(devs, init_params.enableProgTh, num_workers, init_params.syncMode,
num_device_channels, engine_config, vram_memtype_hint_policy) onto separate
indented lines so the call fits formatting rules, then run clang-format to apply
the project's style.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0912178a-5e1f-4234-9199-e54269f2b338

📥 Commits

Reviewing files that changed from the base of the PR and between 3096b2d and 3dc5395.

📒 Files selected for processing (7)
  • docs/BackendGuide.md
  • docs/python_api.md
  • examples/device/ep/csrc/nixl_ep.cpp
  • src/plugins/ucx/ucx_backend.cpp
  • src/plugins/ucx/ucx_utils.cpp
  • src/plugins/ucx/ucx_utils.h
  • test/gtest/error_handling.cpp

Comment thread docs/python_api.md Outdated
Comment thread test/gtest/error_handling.cpp Outdated
Comment thread test/gtest/error_handling.cpp Outdated

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/gtest/error_handling.cpp`:
- Around line 502-544: The loop currently only sets
found_runtime_unsupported_path when lig_expected_runtime_unsupported reports a
match; update the failure handling after calling agent.createBackend(...) so
that if status != NIXL_SUCCESS and backend==nullptr you also treat a matched
lig_expected_runtime_query_failure as a covered runtime-negative path: check
lig_expected_runtime_query_failure.getIgnoredCount() > 0 (in addition to the
existing lig_expected_runtime_unsupported check) and set
found_runtime_unsupported_path = true and break; reference this change around
the createBackend/status/backend handling block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 264439e5-867b-47c3-9183-806e663789e7

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc5395 and 2063699.

📒 Files selected for processing (3)
  • docs/python_api.md
  • src/plugins/ucx/ucx_backend.cpp
  • test/gtest/error_handling.cpp

Comment thread test/gtest/error_handling.cpp Outdated

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 467-475: In ucx_memory_type_to_string, the bounds check uses
`mem_type_index > static_cast<unsigned>(UCS_MEMORY_TYPE_LAST)` which is
off-by-one; change the comparison to `>=
static_cast<unsigned>(UCS_MEMORY_TYPE_LAST)` so that when mem_type ==
UCS_MEMORY_TYPE_LAST we return "invalid" instead of indexing
`ucs_memory_type_names` out-of-bounds; update the check around the
`mem_type_index` variable in the ucx_memory_type_to_string function accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b92f7346-d303-43fe-985d-17b2ad7e6791

📥 Commits

Reviewing files that changed from the base of the PR and between 2063699 and f6da73e.

📒 Files selected for processing (2)
  • src/plugins/ucx/ucx_utils.cpp
  • src/plugins/ucx/ucx_utils.h

Comment thread src/plugins/ucx/ucx_utils.cpp

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/plugins/ucx/ucx_utils.cpp (1)

579-586: ⚠️ Potential issue | 🟠 Major

Constructor leaks UCX context if resolveMemoryTypeConfig() throws.

At line 579, ucp_init() acquires a UCX context. At line 585, resolveMemoryTypeConfig() can throw. If it does, the constructor never completes, the destructor is not invoked, and ucp_cleanup(ctx) at line 589 never runs.

Suggested fix
     const auto status = ucp_init(&ucp_params, config.getUcpConfig(), &ctx);
     if (status != UCS_OK) {
         throw std::runtime_error("Failed to create UCX context: " +
                                  std::string(ucs_status_string(status)));
     }
-
-    resolveMemoryTypeConfig();
+    try {
+        resolveMemoryTypeConfig();
+    }
+    catch (...) {
+        ucp_cleanup(ctx);
+        ctx = nullptr;
+        throw;
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/ucx/ucx_utils.cpp` around lines 579 - 586, The constructor
currently calls ucp_init(...) to set ctx and then calls
resolveMemoryTypeConfig(), which may throw and leak the UCX context; fix by
ensuring ctx is cleaned up on exceptional exit—either wrap ctx in a RAII guard
(e.g., a small local scope guard or std::unique_ptr with a custom deleter that
calls ucp_cleanup) immediately after successful ucp_init, or surround
resolveMemoryTypeConfig() with a try/catch that calls ucp_cleanup(ctx) and
rethrows; update symbols: ucp_init, ctx, resolveMemoryTypeConfig, and
ucp_cleanup so ctx is always cleaned if construction fails.
♻️ Duplicate comments (1)
test/gtest/error_handling.cpp (1)

532-546: ⚠️ Potential issue | 🟠 Major

Unexpected backend failures are still silently converted into skip.

When createBackend() fails but neither expected runtime log is matched, the test currently keeps looping and can end in GTEST_SKIP() instead of failing. This can hide real regressions in the new fail-fast path.

Suggested tightening
-    bool found_runtime_unsupported_path = false;
+    bool found_expected_runtime_failure = false;
@@
         nixlBackendH *backend = nullptr;
         const auto status = agent.createBackend(*it, params, backend);
         if (status != NIXL_SUCCESS) {
             EXPECT_EQ(nullptr, backend);
-            if ((backend == nullptr) &&
-                ((lig_expected_runtime_unsupported.getIgnoredCount() > 0) ||
-                 (lig_expected_runtime_query_failure.getIgnoredCount() > 0))) {
-                found_runtime_unsupported_path = true;
-                break;
-            }
+            const bool saw_runtime_unsupported =
+                lig_expected_runtime_unsupported.getIgnoredCount() > 0;
+            const bool saw_runtime_query_failure =
+                lig_expected_runtime_query_failure.getIgnoredCount() > 0;
+            ASSERT_TRUE(saw_runtime_unsupported || saw_runtime_query_failure)
+                << "createBackend() failed for hint '" << hint
+                << "' without expected runtime failure path";
+            found_expected_runtime_failure = true;
+            break;
         }
@@
-    if (!found_runtime_unsupported_path) {
+    if (!found_expected_runtime_failure) {
         GTEST_SKIP()
-            << "No explicit hint exercised the unsupported-memtype runtime path on this system";
+            << "No explicit hint exercised an expected runtime failure path on this system";
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/gtest/error_handling.cpp` around lines 532 - 546, When createBackend()
fails (status != NIXL_SUCCESS) and backend == nullptr but neither
lig_expected_runtime_unsupported nor lig_expected_runtime_query_failure show
ignored hits, don't allow the loop to continue into a GTEST_SKIP; instead fail
the test immediately. In the block guarded by "if (status != NIXL_SUCCESS)"
check the condition where backend == nullptr and both
lig_expected_runtime_unsupported.getIgnoredCount() == 0 and
lig_expected_runtime_query_failure.getIgnoredCount() == 0, and call GTEST_FAIL()
(or ASSERT/FAIL) with a clear message and exit the test rather than setting
found_runtime_unsupported_path or continuing the loop; keep the existing path
that sets found_runtime_unsupported_path when an expected ignored count is
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 579-586: The constructor currently calls ucp_init(...) to set ctx
and then calls resolveMemoryTypeConfig(), which may throw and leak the UCX
context; fix by ensuring ctx is cleaned up on exceptional exit—either wrap ctx
in a RAII guard (e.g., a small local scope guard or std::unique_ptr with a
custom deleter that calls ucp_cleanup) immediately after successful ucp_init, or
surround resolveMemoryTypeConfig() with a try/catch that calls ucp_cleanup(ctx)
and rethrows; update symbols: ucp_init, ctx, resolveMemoryTypeConfig, and
ucp_cleanup so ctx is always cleaned if construction fails.

---

Duplicate comments:
In `@test/gtest/error_handling.cpp`:
- Around line 532-546: When createBackend() fails (status != NIXL_SUCCESS) and
backend == nullptr but neither lig_expected_runtime_unsupported nor
lig_expected_runtime_query_failure show ignored hits, don't allow the loop to
continue into a GTEST_SKIP; instead fail the test immediately. In the block
guarded by "if (status != NIXL_SUCCESS)" check the condition where backend ==
nullptr and both lig_expected_runtime_unsupported.getIgnoredCount() == 0 and
lig_expected_runtime_query_failure.getIgnoredCount() == 0, and call GTEST_FAIL()
(or ASSERT/FAIL) with a clear message and exit the test rather than setting
found_runtime_unsupported_path or continuing the loop; keep the existing path
that sets found_runtime_unsupported_path when an expected ignored count is
present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9db16a46-f3e7-4b09-a374-87bd004606fd

📥 Commits

Reviewing files that changed from the base of the PR and between f6da73e and 29be759.

📒 Files selected for processing (2)
  • src/plugins/ucx/ucx_utils.cpp
  • test/gtest/error_handling.cpp

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/ucx/ucx_utils.cpp`:
- Around line 584-590: The catch qualifier in the try/catch around
resolveMemoryTypeConfig() is not on its own line; update the block in the
function containing resolveMemoryTypeConfig() so the catch is on a separate line
(i.e., place "catch (...)" on its own line), preserving the ucp_cleanup(ctx)
call and rethrow behavior that currently follows in the catch block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 10865e65-3ecd-40b1-9e95-60fbfb0c3c14

📥 Commits

Reviewing files that changed from the base of the PR and between 29be759 and 3380f8e.

📒 Files selected for processing (2)
  • src/plugins/ucx/ucx_utils.cpp
  • test/gtest/error_handling.cpp

Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
@yafshar
yafshar force-pushed the fix/ucx-vram-memtype-hint branch from 3380f8e to 9b019c7 Compare April 16, 2026 16:43

@brminich brminich 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.

why is it needed? Isn't it enough to check if GPU memory type is not detected as host?
I'd not expectd that rocm memory can be detected as cuda

@yafshar

yafshar commented Apr 17, 2026 •

Copy link
Copy Markdown
Contributor Author

why is it needed? Isn't it enough to check if GPU memory type is not detected as host?
I'd not expectd that rocm memory can be detected as cuda

This PR is needed beyond any single platform: memory-type auto-detection can be unreliable or ambiguous when allocations come from external runtimes/allocators, cross-context boundaries, or incomplete cache coverage. In those cases, relying on automatic detection alone can misclassify VRAM and fail registration.
So instead of changing UCX global slowpath behavior (which has broader performance/correctness tradeoffs), we allow explicit memtype hints from the integration layer. The init-time fail-fast check validates that the requested explicit hint is actually supported by the current UCX context (from ucp_context_query memory_types), while the host-detection check remains a separate runtime guard on the mapped allocation.
In short: one check validates configured capability early, the other validates runtime outcome.

This NIXL change was motivated by the discussion in UCX PR 11332: openucx/ucx#11332. We are not changing UCX slowpath/cache semantics here; instead, we add an integration-level explicit memtype hint with early capability validation to reduce reliance on auto-detection in ambiguous runtime environments.

@yafshar

yafshar commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

Hi @brminich, @mkhazraee just pinging for visibility. It’s been open for a few weeks and I wanted to check if you had a chance to look, or if there’s anything I can do to help move it forward.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agent — nixl-ci-dl-gpu · commit d6b3d4f8

TL;DR: test/python/test_nixl_api.py::test_prep_mem_view failed on the GB200 DL node because both ranks' local prepMemView was rejected by UCX (invalid memh for md_index=7/6) — the VRAM registration produced by the new memtype-hint logic in this PR's commit no longer carries a CUDA memory-type hint on CUDA hosts, so the resulting ucp_mem_h isn't registered on the GPU-device (GDA) memory domain the device mem-list needs. Make the AUTO policy resolve to UCS_MEMORY_TYPE_CUDA when the UCX context reports CUDA (or otherwise keep hinting CUDA for VRAM_SEG).

Full analysis

Summary: Stage "Run DL Python tests" (build #2449) failed: 1 failed, 25 passed — test_prep_mem_view raised torch.multiprocessing.spawn.ProcessRaisedException wrapping nixl_cu13._bindings.nixlBackendError: NIXL_ERR_BACKEND.

Root cause: In the failing run both spawned ranks logged, from UCX, before the Python failure:

ucp_device.c:249  UCX ERROR invalid memh for md_index=7   (rank 1)
ucp_device.c:249  UCX ERROR invalid memh for md_index=6   (rank 0)
ucp_device.c:341/466 failed to pack local mem list element for element=0 / failed to create local mem list handle: Invalid parameter
E ucx_backend.cpp:803] Failed to prepare local memory view: Failed to create device memory list(local): Invalid parameter

The Python traceback lands on test_nixl_api.py:293 → _api.py:772 prepMemView, i.e. the local overload: nixl::ucx::createMemList(nixl_meta_dlist_t…) passes md->getMem().getMemh() (mem_list.cpp:119) straight to ucp_device_local_mem_list_create, and UCX rejects it because that ucp_mem_h has no registration on the md index the device (GDA/cuda_ipc) lane requires.

That memh is produced by the code this PR's HEAD commit rewrote: nixlUcxContext::memReg() now only sets UCP_MEM_MAP_PARAM_FIELD_MEMORY_TYPE for VRAM_SEG when vramMemTypeHint_ has a value (ucx_utils.cpp:820-825), and ucx_auto_detect_vram_hint() (ucx_utils.cpp:505-520) deliberately returns std::nullopt on CUDA hosts ("CUDA is left to UCX auto-detection"). The default backend param is ucx_vram_memtype_hint=auto (ucx_utils.cpp:41-42), so on this aarch64/GB200 node no memory-type hint is passed to ucp_mem_map, the buffer is registered with a memory-domain set that omits the device-API md, and prepMemView returns NIXL_ERR_BACKEND. The commit is the only change in this build touching the UCX registration path, and the same stage passed on the immediately preceding build #2448 of this job.

Implicated commit: [REDACTED:Hex High Entropy String] — Yaser Afshar, "ucx: enforce VRAM memtype hint behavior and add tests"

File: src/plugins/ucx/ucx_utils.cpp:505-520 (ucx_auto_detect_vram_hint) and src/plugins/ucx/ucx_utils.cpp:820-825 (memReg hint application); failure surfaces at src/plugins/ucx/ucx_backend.cpp:803 via src/plugins/ucx/mem_list.cpp:119 and test/python/test_nixl_api.py:293.

Suggested fix: Preserve the pre-PR behaviour for CUDA under AUTO: in ucx_auto_detect_vram_hint(), return UCS_MEMORY_TYPE_CUDA when the context's memory_types mask has CUDA (and no ROCm/ZE ambiguity), rather than std::nullopt — i.e. treat "CUDA-only accelerator stack" symmetrically with the ZE-only case. If leaving CUDA to UCX auto-detection is intentional, then when HAVE_UCX_GPU_DEVICE_API is built in, memReg() must still hint the accelerator memory type for VRAM_SEG so the memh covers the device md; additionally extend the new gtests to assert that a VRAM_SEG registration under auto yields a memh accepted by ucp_device_local_mem_list_create, so this is caught before the DL job.

Related: PR #1536 ("NIXL/UCX: enforce VRAM memtype hint query behavior and add tests"); PR #1715 added test_prep_mem_view; PR #2194 ("bugfix: test: make the device API tests actually run") touches the same device-API test surface.

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id e254a759-ea04-49f9-9690-af1674bb9d45 in the triage console for the audit trail.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agent — nixl-ci-dl-gpu-ep · commit d6b3d4f8

TL;DR: The Build Docker stage for the EP CI image died at STEP 35/38 because the Dockerfile resolves the latest vLLM release tag through an unauthenticated api.github.com call, which returned HTTP 403: rate limit exceeded. Pin VLLM_REF to an explicit tag (or resolve it via git ls-remote / an authenticated token with retries) instead of hitting the GitHub REST API at image-build time.

Full analysis

Summary: nixl-ci-dl-gpu-ep #1367 failed in the Parallel → Build Docker stage (node 70); the BUILD_VLLM_ELASTIC_TEST RUN layer exited status 1 while determining the vLLM release to install.

Root cause: The image build step runs, inline in a RUN, a Python one-liner that fetches https://api.github.com/repos/vllm-project/vllm/releases/latest with no credentials to populate VLLM_REF when it is empty. The build host's unauthenticated GitHub API quota was exhausted, so the request raised:

urllib.error.HTTPError: HTTP Error 403: rate limit exceeded
subprocess exited with status 1
Error: building at STEP "RUN if [ "${BUILD_VLLM_ELASTIC_TEST}" = "true" ]; then ... VLLM_REF="$(python3 -c 'import json, urllib.request; ...')" ...": exit status 1

Because the tag lookup is inside a $(...) substitution guarded by &&, the 403 aborts the whole layer and fails the stage. Everything before it (Mooncake, azure-sdk-for-cpp, azure-cli, gtest-parallel) built and installed cleanly, and the log shows continuous activity with no stalls — this is a hard external-service failure, not a timeout and not related to the PR under test (fix/ucx-vram-memtype-hint, d6b3d4f). It is an environmental/flaky failure that will recur on any branch whenever the runner IP is over quota.

Implicated commit: c984ce6 — lishapira, "CI: add vLLM + NIXL EP test to the EP CI job (#2154)", which introduced this step and the companion .ci/patches/nixl_ep_vllm_release_test.patch.

File: the EP CI Dockerfile under .ci/dockerfiles/ (the RUN guarded by BUILD_VLLM_ELASTIC_TEST, STEP 35/38 — the VLLM_REF resolution line); patch it alongside .ci/patches/nixl_ep_vllm_release_test.patch.

Suggested fix: Stop depending on an unauthenticated GitHub API call in the critical path. In order of preference:

  1. Give ARG VLLM_REF an explicit pinned default (e.g. the tag currently being tested) and bump it deliberately, the same way UCX/libfabric versions are pinned. This also makes the EP image reproducible — today the image silently changes whenever vLLM cuts a release, and the applied patch can break without any nixl commit.
  2. If auto-resolution must stay, replace the REST call with a call that isn't subject to the 60-req/hr anonymous API limit:
    VLLM_REF="$(git ls-remote --refs --tags --sort=-v:refname https://github.com/vllm-project/vllm.git 'v*' | head -1 | sed 's@.*refs/tags/@@')"
  3. Alternatively, pass a GitHub token into the build as a BuildKit secret (never as an ARG/ENV that lands in image history) and wrap the fetch in a bounded retry with backoff, falling back to a pinned tag on failure.

Retrying build #1367 will likely pass once the quota window resets, but the underlying fragility remains until VLLM_REF is pinned.

Related: PR #2154 (introduced the step); PR #2291 "CI: retry git fetches and trim gRPC submodules in dependency builds" addresses the same class of transient network fragility in dependency builds and would be a natural place to land the retry/pin.

- Use UCS_MEMORY_TYPE_UNKNOWN as the internal no-hint sentinel;
  leave UCP_MEM_MAP_PARAM_FIELD_MEMORY_TYPE unset when no hint is selected.
- Move and consolidate hint tests in configuration.cpp: check the
  default, auto/none, a case-mismatched value, and supported or
  unsupported explicit hints.
- Remove the redundant auto setting from the EP example.
- Clarify when auto and none differ in the documentation.

Signed-off-by: Yaser Afshar <yaser.afshar@intel.com>
@yafshar

yafshar commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @iyastreb and @brminich. I pushed 7aa36c6 to address the review comments.

I built and tested on Intel Arc Pro (ZE) and NVIDIA (CUDA). The builds, clang-format, Config.UcxVramMemtype*, */TestErrorHandling.*, and HardwareWarningTest.* passed, with only hardware-dependent warning tests skipped. With auto, Intel Arc selects ze-device; NVIDIA GPU supplies no memory-type hint.

Regarding the earlier CI failures:

  • test_prep_mem_view (invalid memh for md_index=6/7) also fails with the same error on #2174, #2290, #2292, and #2194. This PR adds no CUDA memory-type hint under the default auto policy, as in the pre-PR registration path. Based on that evidence, I don't think this failure is caused by this PR.
  • nixl-ci-dl-gpu-ep failed when the vLLM release lookup received a GitHub API 403 rate-limit response.
  • TSAN failed initially and passed on rerun.

Could one of you trigger Jenkins CI for 7aa36c6?

Comment thread test/gtest/configuration.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp
Comment thread src/plugins/ucx/ucx_utils.cpp Outdated
Comment thread src/plugins/ucx/ucx_utils.cpp
@brminich

Copy link
Copy Markdown
Contributor

/ok to test 7aa36c6

@brminich

Copy link
Copy Markdown
Contributor

/build

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agent — nixl-ci-dl-gpu-ep · commit 7aa36c65

TL;DR: The Build Docker stage failed at Dockerfile STEP 34/38 because git apply /tmp/nixl_ep_vllm_release_test.patch no longer applies to tests/distributed/test_elastic_ep.py in vLLM v0.30.0 — the build resolves VLLM_REF from GitHub's "latest release" API, so an upstream vLLM release silently broke the pinned patch. Fix: pin VLLM_REF to a known-good vLLM tag (as PR #2292 already proposes) and refresh the patch against it.

Full analysis

Summary: nixl-ci-dl-gpu-ep #1405 failed in the Build Docker stage (node 57); the vLLM elastic-EP test image layer aborted with exit status 1.

Root cause: Not a code defect in this PR and unrelated to fix/ucx-vram-memtype-hint. The Dockerfile layer that prepares the vLLM elastic-EP test does:

VLLM_REF="$(python3 ... api.github.com/repos/vllm-project/vllm/releases/latest ... ["tag_name"])"
git clone --depth 1 --branch "${VLLM_REF}" https://github.com/vllm-project/vllm.git ...
git -C ... apply /tmp/nixl_ep_vllm_release_test.patch

Because VLLM_REF is unpinned, the build picked up the newly-published v0.30.0 (log: Using vLLM release v0.30.0). The vendored patch was authored against an earlier release, so it fails against the changed upstream file:

error: patch failed: tests/distributed/test_elastic_ep.py:175
error: tests/distributed/test_elastic_ep.py: patch does not apply
subprocess exited with status 1
Error: building at STEP "RUN if [ "${BUILD_VLLM_ELASTIC_TEST}" = "true" ]; then ... git apply ..."

There is no hang or timeout — the log is continuously active (12:48 → 12:52) and terminates on a hard non-zero exit. Everything before this (Mooncake, azure-sdk-for-cpp, azure-cli, gtest-parallel) built and installed cleanly; the warnings from ylt/coro_rpc and the NO_PUBKEY [REDACTED:Hex High Entropy String] / libdoca_gpunetio_host.so.2 is not a symbolic link messages are pre-existing noise, not the failure.

Implicated commit: The mechanism was introduced by c984ce65 — lishapira, "CI: add vLLM + NIXL EP test to the EP CI job (#2154)". The actual trigger is an external event (vLLM v0.30.0 release), not a nixl commit; [REDACTED:Hex High Entropy String] is innocent.

File: The BUILD_VLLM_ELASTIC_TEST layer (STEP 35/38) in the EP CI Dockerfile, plus the vendored patch installed as /tmp/nixl_ep_vllm_release_test.patch; the conflict is at tests/distributed/test_elastic_ep.py:175 in vLLM v0.30.0.

Suggested fix:

  1. Pin VLLM_REF to a specific, known-good vLLM tag instead of querying releases/latest, so a new upstream release cannot break unrelated PRs. This is exactly what PR CI: pin the vLLM release for the elastic EP test #2292 ("CI: pin the vLLM release for the elastic EP test") does — land it.
  2. Regenerate nixl_ep_vllm_release_test.patch against the pinned tag (or against v0.30.0 if you want to move forward), since test_elastic_ep.py was restructured around line 175.
  3. Defensively, make the step fail loudly and actionably — e.g. git apply --3way (or --check first) and echo the resolved VLLM_REF plus the patch's target file hash — so future upstream drift is obvious from the log rather than appearing as a generic Docker build failure.
  4. Since this blocks every PR touching anything, consider guarding BUILD_VLLM_ELASTIC_TEST so a broken third-party patch doesn't gate unrelated changes.

Related: #2292 (pin the vLLM release for the elastic EP test — the fix); #2154 (introduced the unpinned vLLM elastic-EP test layer)

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id b6235b04-8aa7-49d4-b860-0b9641b016f6 in the triage console for the audit trail.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agent — nixl-ci-non-gpu · commit 7aa36c65

TL;DR: The build failed on a single matrix variant because the gtest MDManagerEtcdFixture.InvalidateLocalRemovesRemote timed out waiting 3 s for the ETCD metadata deletion to propagate (checkRemoteMD still returned NIXL_SUCCESS); this is a flaky timing bound in the test against a badly overloaded in-container etcd, unrelated to the ucx_vram_memtype_hint change in PR #1536.

Full analysis

Summary: Stage "Test CPP" (node 463, container nixl-ci-non-gpu-base-cuda129-3257-31rr7-t9r0x, aarch64) failed with 1 of 262 gtests failing: MDManagerEtcdFixture.InvalidateLocalRemovesRemote, test/gtest/md_manager.cpp:298.

Root cause: Timing race in the test's bounded poll, not a code defect from this PR.

  • Failure text: Expected equality of these values: waitForRemoteMD(dst.agent.get(), src.name, {DRAM_SEG}, NIXL_ERR_NOT_FOUND) Which is: 0 / NIXL_ERR_NOT_FOUND Which is: -4. The remote MD was still present after invalidateLocalMD(); the test polled for only the default 3 s deadline (md_manager.cpp:83) and reported after 3664 ms — i.e. it consumed the whole timeout, it did not fail fast.
  • The etcd instance started by .gitlab/test_cpp.sh was under severe load in this run: etcdserver: request ... put:<key:"/nixl/cpp_ci/10501/agent_0/"...> took too long (183.620476ms) and read-only range request ... took too long (228.441158ms), with the neighbouring etcd tests taking 40 985 ms, 50 989 ms and 20 974 ms. With per-op latencies in the hundreds of ms, propagation of the key deletion to the second agent can exceed the 3 s bound.
  • Same test, same build, other variants: it passed in 1295 ms (x86 base-cuda129, stage 446) and the equivalent MDManagerTcpStoreFixture.InvalidateLocalRemovesRemote passed in the failing variant too (932 ms) — so the assertion is sound and only the ETCD-backed timing is unstable.
  • PR NIXL/UCX: enforce VRAM memtype hint query behavior and add tests #1536's surface is visible and healthy in the same log (ucx_vram_memtype_hint = auto in plugin params; Config.UcxVramMemtypeHint and Config.UcxVramMemtypeHintUnsupportedByContext both passed in every variant). No UCX/memtype code path is involved in the ETCD KV metadata invalidation.

Implicated commit: unknown for the flake itself; the test was introduced in 2ff0105f "Metadata manager pr5 (#1930)" by aschwartz12. The build's head commit [REDACTED:Hex High Entropy String] (fix/ucx-vram-memtype-hint) is not implicated.

File: test/gtest/md_manager.cpp:298 (assertion), test/gtest/md_manager.cpp:83 (3 s default poll timeout)

Suggested fix:

  1. Re-run the job — this is a retryable flake on that variant.
  2. Make the ETCD fixture's negative wait tolerant of a slow store: raise the timeout for the ETCD cases specifically (e.g. pass std::chrono::seconds(30) for the NIXL_ERR_NOT_FOUND wait in MDManagerEtcdFixture, matching the 20–50 s the other etcd tests already need), or derive it from the KV backend's refresh/watch interval instead of a hard-coded 3 s. Keep the fast 3 s bound for the P2P/TCPStore fixtures where it is realistic.
  3. Optional CI hardening: the local etcd in .gitlab/test_cpp.sh is clearly contended when several matrix variants run concurrently on one host (repeated "took too long" warnings). Giving etcd its own data dir on local disk and/or serialising the ETCD test group would remove the underlying jitter.

Related: #1930 (added test/gtest/md_manager.cpp), #1745 (etcd backend for nixlMDManager), #1743 (open: "TEST/GTEST: Run in single process." — touches the same gtest-parallel harness)

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 95a0d4b5-44d3-4e42-a6e5-c45854b70c5a in the triage console for the audit trail.

- Use shared enum conversion helpers for error handling modes and VRAM
  memtype hints while preserving invalid-value diagnostics.
- Assert that ucp_context_query succeeds when resolving the hint,
  replacing the unreachable query-error branch.
- Remove an unnecessary constructor comment.
- Use the default no-progress-thread configuration in the hint tests.

Signed-off-by: Yaser Afshar <yaser.afshar@intel.com>
@brminich

brminich commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

/ok to test 2bbc556

@brminich

brminich commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

/build

iyastreb
iyastreb previously approved these changes Oct 1, 2026
@svc-nixl

svc-nixl commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agent — nixl-ci-gpu · commit 2bbc556a

TL;DR: The "Run CPP tests" stage never ran a test — enroot/pyxis was SIGKILLed (exit 137) while converting the ~30 GB CI image into a squashfs inside the job's 128 GB memory cgroup on mizu01, so the container could not start. Fix is CI-infra side: give the enroot import room (bigger --mem, disk-backed ENROOT_TEMP_PATH/output instead of tmpfs, and/or shrink the image) — nothing to do with the PR's code.

Full analysis

Summary: nixl-ci-gpu #3802 stage "Run CPP tests" (node 193) failed because pyxis could not import nixl-ci-gpu-test:3802: pyxis: child 1537785 failed with error code: 137 → failed to import docker image → couldn't start container → srun: error: mizu01: task 0: Exited with exit code 1.

Root cause: Out-of-memory kill of the image-import pipeline, not a test or build defect. The enroot log shows the import got all the way to Creating squashfs filesystem... on /run/pyxis/148069/99567.0.squashfs (tmpfs, i.e. RAM-backed) with Parallel mksquashfs: Using 24 processors, producing a 24,791 MB image from 30,235 MB of uncompressed layers, then /usr/lib/enroot/docker.sh: line 194: 1537809 Killed — exit 137 = SIGKILL. The slurm allocation (stage 173) was salloc -N 1 -p mizu ... --gres=gpu:4 --mincpus=24 --mem=128G, job ID 99567 on mizu01; so the import ran under a 128 GB memory cgroup while writing tens of GB of tmpfs pages that are charged to that cgroup.

Infra telemetry corroborates that this was a cgroup limit, not a sick or host-OOM node: node_metrics.mem_local for mizu01 over 14:28–14:40 (window fully covered, 10 s samples) shows 2015 GB total with used only rising from ~68 GB to a peak of 153 GB at 14:33:50 and shared (tmpfs) ballooning 20.7 GB → 74.3 GB over 14:32:40–14:33:50, then collapsing back right as the kill lands at 14:34:07. The host had ~1.86 TB available throughout — the kernel killed mksquashfs because of the job's 128 GB --mem, while mksquashfs sizes its own buffers from host RAM (2 TB) and is blind to the cgroup limit. Slurm records the terminal state of job 99567 as CANCELLED at 14:35:21 after the step failed.

Secondary contributing factor: the test image is very large (30 GB unpacked) and recently grew — 54890beb "build: bump DOCA to 3.5 (#2250)" by e-eygin (2026-09-28), three days before this build, on contrib/Dockerfile.

The follow-up exportEnroot error (No such file or directory: .../pyxis_nixl-ci-3802) is just fallout from the failed import and is logged as non-fatal.

Implicated commit: No code commit — infrastructure/CI-resource failure. 2bbc556a (PR #1536, fix/ucx-vram-memtype-hint) is not implicated. Aggravating change: 54890beb (e-eygin, "build: bump DOCA to 3.5", #2250) for image growth.

File: contrib/Dockerfile (image size) and the nixl-ci-gpu Jenkins pipeline's slurm.allocation extraArgs: [--gres=gpu:4, --mincpus=24, --mem=128G] (shown in stage 173 log; sourced from the in-repo .ci/jenkins pipeline definition / swx-jenkins-lib)

Suggested fix:

  1. Immediate: retrigger the build — this is a transient resource failure, not a code regression.
  2. Stop the squashfs conversion from running in RAM: point ENROOT_TEMP_PATH and the pyxis container/squashfs path at local disk (e.g. the same disk-backed filesystem already used for /enroot_images) rather than /run/pyxis, which is tmpfs and charges every written byte to the job's memory cgroup.
  3. Bound mksquashfs: set ENROOT_MAX_PROCESSORS to something well below 24 and pass -mem <N> via ENROOT_SQUASH_OPTIONS so it sizes buffers against the cgroup limit, not the 2 TB host.
  4. Raise --mem in the nixl-ci-gpu allocation extraArgs (e.g. 256G) — the node has 2 TB and was ~95% free, so this is cheap insurance.
  5. Longer term: trim the test image (the DOCA 3.5 bump pushed it to 30 GB uncompressed); consider a pre-imported/cached .sqsh on the mizu nodes so each build doesn't re-convert 30 GB.

Related: PR #1536 (the branch under test, unaffected); #2250 / 54890beb (DOCA 3.5 bump that grew the CI image). No existing issue tracks this pyxis import OOM — worth filing one against the CI infra.

kpjeeja added a commit to kpjeeja/sglang that referenced this pull request Oct 4, 2026
Replace hardcoded torch.cuda calls in the Encoder/Prefill/Decode path with
torch.get_device_module equivalents, so staging-buffer KV transfer works on
Intel XPU. Two of the fixes are not XPU-specific: the encoder /send
synchronize was gated on is_cuda and so was skipped on every other
accelerator, and staging init defaulted device_type to "cuda".

Breaking change for out-of-tree backends: init_staging_buffers,
init_staging_allocator and init_dcp_pack_buffers take a required keyword-only
device_type. The "cuda" default was dropped so no backend can silently
allocate staging on CUDA.

The DCP pack kernel is Triton-only, so off CUDA init_dcp_pack_buffers returns
no buffers and DCP transfers fall back to per-token RDMA.

Adds create_device_event and current_device_stream to srt/utils/common.py.

The XPU PD tests run every scenario on both the NIXL and Mooncake backends.
The NIXL classes are skipped: UCX treats SYCL device allocations as host memory,
so NIXL cannot register XPU KV buffers until ai-dynamo/nixl#1536 and
openucx/ucx#11218 land. The Mooncake classes run in CI (nightly-xpu-2-gpu and
nightly-xpu-4-gpu). docker/xpu.Dockerfile builds the Mooncake transfer engine
from a pinned upstream commit with icpx (-DUSE_XPU=ON -DUSE_TENT=ON
-DWITH_STORE=OFF) and installs sglang-router. The Mooncake classes skip when
mooncake.engine is not importable, so the nightly stays green on an image
built before that Dockerfile step.

Mooncake's XPU support lives in its TENT engine, so those classes set
MC_USE_TENT=1 and MOONCAKE_PROTOCOL=tcp and need no RDMA NIC. The staging
decode server (TP 2) runs with TORCHDYNAMO_DISABLE=1: at TP > 1 the compiled
vocab-parallel embedding segfaults in inductor's static Triton launcher on XPU.

Verified on 3x Intel Arc Pro B60: xpu/test_disaggregation_staging_xpu.py
(prefill TP 1, decode TP 2, so KV goes through the staging path), 4 passed;
test/registered/unit/disaggregation, 487 passed. With Mooncake over TCP:
disaggregation/test_disaggregation_xpu.py 2 passed, staging 4 passed. The same
Mooncake tests pass against a transfer-engine-only install of upstream Mooncake
16b7ba3c built with the Dockerfile steps.
kpjeeja added a commit to kpjeeja/sglang that referenced this pull request Oct 7, 2026
Replace hardcoded torch.cuda calls in the Encoder/Prefill/Decode path with
torch.get_device_module equivalents, so staging-buffer KV transfer works on
Intel XPU. Two of the fixes are not XPU-specific: the encoder /send
synchronize was gated on is_cuda and so was skipped on every other
accelerator, and staging init hardcoded a "cuda:{gpu_id}" device string.

Breaking change for out-of-tree backends: init_staging_buffers,
init_staging_allocator and init_dcp_pack_buffers take a required keyword-only
device_type. The "cuda" default was dropped so no backend can silently
allocate staging on CUDA.

The DCP pack kernel is Triton-only, so off CUDA init_dcp_pack_buffers returns
no buffers and DCP transfers fall back to per-token RDMA.

Adds create_device_event and current_device_stream to srt/utils/common.py.

The decode-side scatter now waits on an event recorded on schedule_stream when
the room is registered, right after pre-alloc writes its req_to_token row,
instead of having no ordering edge. Waiting on the stream itself would also
queue each scatter behind any forward running there without overlap.

The XPU PD tests run every scenario on both the NIXL and Mooncake backends.
The NIXL classes are skipped: UCX treats SYCL device allocations as host memory,
so NIXL cannot register XPU KV buffers until ai-dynamo/nixl#1536 and
openucx/ucx#11218 land. The Mooncake classes run in CI (nightly-xpu-2-gpu and
nightly-xpu-4-gpu). docker/xpu.Dockerfile builds the Mooncake transfer engine
from a pinned upstream commit with icpx (-DUSE_XPU=ON -DUSE_TENT=ON
-DWITH_STORE=OFF) and installs sglang-router. The Mooncake classes skip when
mooncake.engine is not importable, so the nightly stays green on an image
built before that Dockerfile step.

Mooncake's XPU support lives in its TENT engine, so those classes set
MC_USE_TENT=1 and MOONCAKE_PROTOCOL=tcp and need no RDMA NIC. The staging
decode server (TP 2) runs with TORCHDYNAMO_DISABLE=1: at TP > 1 the compiled
vocab-parallel embedding segfaults in inductor's static Triton launcher on XPU.

Verified on 3x Intel Arc Pro B60: xpu/test_disaggregation_staging_xpu.py
(prefill TP 1, decode TP 2, so KV goes through the staging path), 4 passed;
test/registered/unit/disaggregation, 487 passed. With Mooncake over TCP:
disaggregation/test_disaggregation_xpu.py 2 passed, staging 4 passed. The same
Mooncake tests pass against a transfer-engine-only install of upstream Mooncake
built with the Dockerfile steps.
…e-hint

Signed-off-by: Yaser Afshar <yaser.afshar@intel.com>
@yafshar

yafshar commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@brminich Friendly ping, this is unblocked and ready for a decision.

Merged with latest main today (c165a4d); conflicts resolved, GitHub reports it mergeable, all checks green. Only REVIEW_REQUIRED remains — nothing outstanding on my side.

CI has no ZE runner, so I validated on an Intel Arc Pro B60 (ZE-only UCX context):
auto → ze-device, ze-device accepted, cuda/cuda-managed/rocm rejected as unsupported by the context, CUDA rejected case-sensitively. Both new gtests pass.

The SGLang team needs this for GPU Direct in PD disaggregation, and other teams are waiting. Open ~5 months, all review rounds addressed.

The automated review comments are pre-existing test-suite characteristics plus one doc wording nit (createBackend returns NIXL_ERR_BACKEND) — happy to take them as follow-ups, or fold the doc line in now if you'd rather.

If it looks good, could we land it today?

@iyastreb

iyastreb commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

/build

@ColinNV

ColinNV commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

/build

This branch was successfully deployed

1 active (outdated) deployment
SWX_AWS — 2bbc556a Deployed Oct 1, 2026 by copy-pr-bot[bot] via Run AWS Tests #9619
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants