Skip to content

Fix/2032 gds mt interior pointer - #2062

Open
Chije wants to merge 3 commits into
ai-dynamo:mainfrom
Chije:fix/2032-gds-mt-interior-pointer
Open

Chije wants to merge 3 commits into
ai-dynamo:mainfrom
Chije:fix/2032-gds-mt-interior-pointer

Conversation

@Chije

@Chije Chije commented Aug 10, 2026

Copy link
Copy Markdown

What?

Fix GDS_MT handling of interior GPU pointers.

The GDS_MT backend currently treats the transfer descriptor pointer as the
cuFile registered base pointer. This is incorrect when a transfer descriptor
refers to an interior region of a larger GPU allocation.

This PR:

  • Preserves the GPU buffer base registered with cuFile.
  • Computes the descriptor-relative offset.
  • Passes the registered base and descriptor offset to the GDS_MT transfer path.
  • Rejects descriptors outside the registered allocation.
  • Adds unit and integration test coverage.

Fixes #2032

Why?

A GPU allocation may be registered once with cuFile while individual transfer
descriptors refer to subranges inside that allocation.

Without preserving the registered base and descriptor-relative offset:

  • An interior descriptor may be passed to cuFile as the buffer base with
    devPtr_offset = 0, even though the larger allocation was registered at a
    different base address.
  • A descriptor outside the registered allocation is not rejected during NIXL
    request preparation and may reach cuFile, where it can fail later as a
    backend I/O error.

This change makes GDS_MT follow cuFile registered-buffer semantics for
interior GPU pointers.

How?

The GDS_MT buffer-resolution path distinguishes between:

  • The base pointer registered with cuFile.
  • The descriptor offset relative to the registered base.

The request preparation path validates that the complete transfer descriptor is
contained within the registered allocation before creating a transfer request.

Unit tests cover:

  • Interior descriptor offset calculation.
  • Descriptor equal to the registered base.
  • Descriptor before the registered base.
  • Descriptor extending beyond the registered allocation.

A separate GDS_MT integration test:

  • Allocates a 64 MiB GPU buffer.
  • Registers the full allocation with cuFile.
  • Uses an 8 MiB transfer descriptor at a 4 MiB interior offset.
  • Performs a GDS_MT file write.
  • Clears the GPU buffer.
  • Performs a GDS_MT file read using the same interior descriptor.
  • Verifies payload integrity.
  • Verifies that the regions before and after the descriptor remain unchanged.
  • Records write/read latency through GTest properties.

Relationship to #1856

PR #1856 proposes consolidating the GDS and GDS_MT implementations under the
cuda_gds source tree and sharing request preparation logic.

This PR is based on the current pre-consolidation GDS_MT source layout and does
not include the changes from #1856. If #1856 is merged first, this change will
need to be rebased and ported to the shared cuda_gds request-preparation
layer.

The intended follow-up is to preserve the same registered-base and
descriptor-offset semantics for both GDS and GDS_MT after the consolidation,
while ensuring that the offset is calculated and applied exactly once.

Testing

Focused validation passed:

6/6 tests passed

Passed tests:
- nixl:posix_plugin_test
- gds_mt_integration: nixl:gds_mt_interior_pointer_integration
- nixl:unit
- sanitizer: nixl:gtest
- nixl:telemetry_benchmark
- nixl:tracing_nsys

Build environment

Validation was performed with:

  • CUDA 13.1
  • GDS enabled
  • Custom UCX, etcd-cpp-api, and Abseil prefixes
  • Local etcd endpoint: http://127.0.0.1:2379
  • DOCA telemetry disabled because the available exporter library was
    ABI-incompatible

The dependency paths below are represented by environment variables because
they are machine-specific.

Build configuration

$ meson setup build-gds-mt-pr-focused \
  -Dbuild_tests=true \
  -Dbuild_examples=false \
  -Dbuildtype=debug \
  -Ddisable_plugins=TELEMETRY_DOCA \
  -Ddisable_gds_backend=false \
  -Dgds_path="$CUDA_HOME" \
  -Detcd_inc_path="$ETCD_PREFIX/include" \
  -Detcd_lib_path="$ETCD_PREFIX/lib"

Build

$ ninja -C build-gds-mt-pr-focused

Test execution

$ meson test -C build-gds-mt-pr-focused \
  --no-rebuild \
  --timeout-multiplier 12 \
  --print-errorlogs \
  --logbase=pr-focused-testlog \
  posix_plugin_test \
  unit \
  gtest \
  telemetry_benchmark \
  tracing_nsys \
  gds_mt_interior_pointer_integration

The four GDS_MT offset unit tests passed as part of nixl:unit.

The two existing GDS path-mode smoke tests were excluded from this focused
validation because their runtime-directory setup issue is unrelated to #2032.
No smoke-test infrastructure changes are included in this PR.

TELEMETRY_DOCA was disabled because the available DOCA telemetry exporter
library was ABI-incompatible with the required symbols. The GDS and GDS_MT
validation was executed with GDS enabled.

Summary by CodeRabbit

  • Bug Fixes

    • Improved GDS multi-threaded transfers for interior pointers within registered GPU memory.
    • Added validation to reject missing, invalid, or out-of-bounds transfer metadata.
    • Ensured file transfers use the correct registered buffer location and offset.
  • Tests

    • Added coverage for valid offsets, exact-base transfers, and invalid buffer ranges.
    • Added GPU-backed integration testing to verify interior-region transfers preserve surrounding data.

Chije added 3 commits August 4, 2026 17:29
Signed-off-by: chije.park <chije.park@sk.com>
Signed-off-by: chije.park <chije.park@sk.com>
Signed-off-by: chije.park <chije.park@sk.com>
@Chije
Chije requested review from a team, brminich, ofer, vvenkates27 and w1ldptr as code owners August 10, 2026 07:31
@copy-pr-bot

copy-pr-bot Bot commented Aug 10, 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 Chije! Thank you for contributing to ai-dynamo/nixl.

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

🚀

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The GDS_MT backend now preserves registered GPU buffer bases and descriptor offsets. It validates descriptor ranges and adds unit and integration coverage for interior-pointer transfers.

Changes

GDS interior-pointer handling

Layer / File(s) Summary
Registered-buffer resolution contract
src/plugins/gds_mt/gds_mt_backend.h, src/plugins/gds_mt/gds_mt_backend.cpp, test/gtest/unit/plugins/gds_mt/gds_mt_offset_test.cpp
Adds the resolver contract and implementation. Valid descriptors return the registered base and relative offset. Invalid ranges return errors.
Backend transfer flow
src/plugins/gds_mt/gds_mt_backend.cpp
Stores registered bases and offsets in transfer requests. Metadata extraction validates descriptors. cuFile operations and diagnostics use the resolved values.
GDS_MT validation and build wiring
test/gtest/unit/meson.build, test/gtest/unit/plugins/gds_mt/meson.build, test/gtest/unit/plugins/gds_mt/gds_mt_interior_pointer_test.cpp
Adds conditional test configuration and an integration test that writes and reads an interior GPU range while checking surrounding bytes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: brminich

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation meets issue #2032 by preserving the registered base, resolving offsets, rejecting invalid ranges, and adding required tests.
Out of Scope Changes check ✅ Passed The code and test changes remain focused on GDS_MT interior-pointer handling and its validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly identifies the GDS_MT interior-pointer fix and references the related issue.
Description check ✅ Passed The description includes complete What, Why, and How sections, issue context, testing details, and build configuration.
✨ 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plugins/gds_mt/gds_mt_backend.cpp`:
- Around line 99-102: Rename the helper method effectiveAddr to effective_addr
and update its invocation in the surrounding code, including the call currently
near line 188, while preserving its behavior.

In `@src/plugins/gds_mt/gds_mt_backend.h`:
- Around line 50-55: Rename the public resolver gdsMtResolveRegisteredBuffer to
snake_case as gds_mt_resolve_registered_buffer in the header declaration, its
definition in gds_mt_backend.cpp, and every call site, preserving the existing
signature and behavior.
- Around line 36-39: Add `///<` documentation comments to both public members,
`devPtrBase` and `devPtrOffset`, in `gdsMtResolvedBuffer`, describing the
meaning of the base device pointer and its offset.
🪄 Autofix

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: Enterprise

Run ID: a0eb5862-42da-4d3d-aa19-483c4f0dbd88

📥 Commits

Reviewing files that changed from the base of the PR and between b475f75 and 64b6d24.

📒 Files selected for processing (6)
  • src/plugins/gds_mt/gds_mt_backend.cpp
  • src/plugins/gds_mt/gds_mt_backend.h
  • test/gtest/unit/meson.build
  • test/gtest/unit/plugins/gds_mt/gds_mt_interior_pointer_test.cpp
  • test/gtest/unit/plugins/gds_mt/gds_mt_offset_test.cpp
  • test/gtest/unit/plugins/gds_mt/meson.build

Comment on lines +99 to +102
uintptr_t
effectiveAddr() const {
return reinterpret_cast<uintptr_t> (devPtrBase) + devPtrOffset;
}

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rename effectiveAddr to snake_case.

Rename this helper to effective_addr and update its call on Line 188.

Proposed change
-    effectiveAddr() const {
+    effective_addr() const {
-<< " effective_address=" << reinterpret_cast<void *> (req->effectiveAddr());
+<< " effective_address=" << reinterpret_cast<void *> (req->effective_addr());

As per path instructions, use snake_case for functions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/gds_mt/gds_mt_backend.cpp` around lines 99 - 102, Rename the
helper method effectiveAddr to effective_addr and update its invocation in the
surrounding code, including the call currently near line 188, while preserving
its behavior.

Source: Path instructions

Comment on lines +36 to +39
struct gdsMtResolvedBuffer {
void *devPtrBase;
size_t devPtrOffset;
};

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Document each public result member.

Add ///< documentation for devPtrBase and devPtrOffset. The public type currently documents only the structure.

Proposed change
 struct gdsMtResolvedBuffer {
-    void *devPtrBase;
-    size_t devPtrOffset;
+    void *devPtrBase;     ///< Base pointer registered with cuFile.
+    size_t devPtrOffset;  ///< Descriptor offset from devPtrBase.
 };

As per path instructions, use ///< for member documentation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct gdsMtResolvedBuffer {
void *devPtrBase;
size_t devPtrOffset;
};
struct gdsMtResolvedBuffer {
void *devPtrBase; ///< Base pointer registered with cuFile.
size_t devPtrOffset; ///< Descriptor offset from devPtrBase.
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/gds_mt/gds_mt_backend.h` around lines 36 - 39, Add `///<`
documentation comments to both public members, `devPtrBase` and `devPtrOffset`,
in `gdsMtResolvedBuffer`, describing the meaning of the base device pointer and
its offset.

Source: Path instructions

Comment on lines +50 to +55
nixl_status_t
gdsMtResolveRegisteredBuffer(void *registered_base,
size_t registered_size,
uintptr_t descriptor_addr,
size_t descriptor_size,
gdsMtResolvedBuffer &resolved);

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rename the public resolver to snake_case.

gdsMtResolveRegisteredBuffer does not follow the required function naming style. Rename the declaration, definition in src/plugins/gds_mt/gds_mt_backend.cpp, and all call sites together.

As per path instructions, use snake_case for functions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/gds_mt/gds_mt_backend.h` around lines 50 - 55, Rename the public
resolver gdsMtResolveRegisteredBuffer to snake_case as
gds_mt_resolve_registered_buffer in the header declaration, its definition in
gds_mt_backend.cpp, and every call site, preserving the existing signature and
behavior.

Source: Path instructions

@mbapatu-spec

Copy link
Copy Markdown

@Chije did you get a chance to run nixl bench on these changes?

@Chije

Chije commented Aug 11, 2026

Copy link
Copy Markdown
Author

@mbapatu-spec I ran an additional NIXLBench comparison between mainline and
this PR after changing the NVMe device.

Configuration:

  • Backend: GDS_MT
  • Initiator memory: VRAM
  • Total GPU allocation: 64 MiB
  • Block size: 4 MiB
  • Batch size: 16
  • Iterations: 100
  • Warmup iterations: 20
  • GDS_MT threads: 16
  • Direct storage I/O enabled
  • CUDA 13.1 with GDS enabled
  • The same benchmark configuration and NVMe device were used for both runs

Results:

Operation Mainline BW (GB/s) PR BW (GB/s) BW delta Mainline Avg Lat. (us) PR Avg Lat. (us)
WRITE 6.303 6.266 -0.59% 665.4 669.4
READ 12.648 12.855 +1.64% 331.6 326.3

The results show comparable WRITE performance and slightly higher READ
throughput for the PR. No meaningful throughput or latency regression was
observed in this run.

The benchmark uses a 64 MiB GPU allocation with multiple 4 MiB transfer
descriptors, including descriptors at interior offsets within the allocation.

This benchmark is supplementary to the unit and GDS_MT integration tests,
which validate the registered-base and descriptor-offset behavior.

The full raw logs are attached as : nixlbench-pr2062-logs.tar.gz

@tell-rebanta

tell-rebanta commented Aug 18, 2026

Copy link
Copy Markdown

If you are doing I/O using multiple threads simultaneously with the same base_ptr, then cufile library may fallback to the slower I/O path due to the fact that base_ptr is busy with another I/O. Given that, you may not see any gain in performance defeating the purpose of your change.

@Chije

Chije commented Aug 19, 2026

Copy link
Copy Markdown
Author

@tell-rebanta Thanks for pointing this out.
We tested mainline and PR #2062 with
144 concurrent GDS_MT threads, a 256 MiB registered GPU buffer, and
1 MiB transfers.

Both versions issued the same number of GDS operations, with no POSIX
fallback events observed. The internal bounce-buffer completion
tracepoint was observed in both versions, with Registered=1 and
Unaligned=0.

Performance was effectively unchanged:

READ: 13.181 GB/s (mainline) vs 13.248 GB/s (PR)
WRITE: 6.197 GB/s (mainline) vs 6.199 GB/s (PR)

Therefore, we did not observe a meaningful performance regression, and
the bounce-buffer behavior was not introduced by this PR.

The goal of PR #2062 is to correctly preserve the cuFile-registered
base pointer and apply the descriptor-relative offset for interior GPU
pointers, rather than to change cuFile's internal bounce-buffer
selection policy.

The current tracepoint does not expose the exact reason for selecting
the bounce-buffer path, so this experiment cannot conclusively confirm
that a busy base_ptr is the cause. We can investigate this further with
a concurrency sweep if needed.

@mbapatu-spec

Copy link
Copy Markdown

LGTM

@tell-rebanta

Copy link
Copy Markdown

While I agree that the performance has not been regressed, it did not improve either with this change if that is the goal. FYI, If you enable cufile logging in TRACE mode, cufile.log file can show if it has taken registered/unregistered path or not (specifically, I would search "unregistered work item" in the log).

@Chije

Chije commented Sep 14, 2026

Copy link
Copy Markdown
Author

Additional cuFile route-trace validation

I added cuFile route-trace results from a Dynamo KVBM + vLLM workload using
Qwen3-30B-A3B-Instruct-2507.

Test configuration:

  • 48 layers, TP=1, PP=1
  • Block size: 128
  • KV path: /mnt/kvbm/Qwen3-30B
  • Duration: 500 seconds
  • 3 repeats per configuration
  • GDS/cuFile: 1.15.1.6
  • Pre-PR runtime image: vllm-runtime:1.3.0

The route trace shows a clear difference in READ handling:

Configuration READ/BB READ/DIRECT
Pre-PR vanilla 100% 0%
PR-applied 65.32–68.26% 31.74–34.68%

In the pre-PR configuration, all READ operations used the bounce-buffer
route even though the trace reported zero unaligned and zero unregistered
operations. With the PR applied, approximately one third of READ operations
used the DIRECT route.

This is consistent with the intended behavior of preserving the cuFile
registered base pointer and applying the descriptor-relative offset for
interior GPU pointers. No read/write return errors were observed.

The attached route-specific latency histograms show the distributions for
READ/BB and READ/DIRECT. The average per-repeat percentiles were:

Route p50 p95 p99
Pre-PR READ/BB 2.70 ms 14.96 ms 31.97 ms
PR READ/BB 1.54 ms 6.62 ms 12.47 ms
PR READ/DIRECT 0.78 ms 1.82 ms 1.98 ms

The histogram is intentionally split by route because an aggregate READ
latency would combine BB and DIRECT operations and hide the route-specific
tail behavior.

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.

[gds_mt] Preserve cuFile registered base and descriptor offset for interior GPU pointers in gds_mt

3 participants