feat: Add json+cuda_ipc array encoding for GPU-direct tensor transfer - #588
Merged
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #588 +/- ##
==========================================
- Coverage 78.46% 78.39% -0.07%
==========================================
Files 40 41 +1
Lines 4857 5295 +438
Branches 794 866 +72
==========================================
+ Hits 3811 4151 +340
- Misses 733 797 +64
- Partials 313 347 +34 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
Benchmark ResultsBenchmarks use a no-op Tesseract to measure pure framework overhead. 🚀 1 faster, Notable changes
Full results
|
The 'Merge branch main into dion/gpu-go-brr' conflict resolution left output_format/timeout params at 7-space indent and an over-length is_leaf lambda. ruff-format clean now so the pre-commit/CI gate passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 12, 2026
dionhaefner
commented
Aug 13, 2026
nmheim
reviewed
Aug 14, 2026
nmheim
left a comment
Contributor
There was a problem hiding this comment.
Mostly nits except my last comment. It seems like the client is not releasing previously exported arrays?
dionhaefner
commented
Aug 18, 2026
nmheim
approved these changes
Aug 18, 2026
nmheim
left a comment
Contributor
There was a problem hiding this comment.
Neat, I like the context manager for the client!
jpbrodrick89
approved these changes
Aug 25, 2026
dionhaefner
enabled auto-merge (squash)
August 25, 2026 10:47
dionhaefner
disabled auto-merge
August 25, 2026 11:17
dionhaefner
enabled auto-merge (squash)
August 25, 2026 11:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relevant issue or PR
None.
Description of changes
Serving a Tesseract that computes on the GPU currently incurs a round trip through host memory on every call: the runtime copies the result off the device, serializes it (base64 or binref), ships it over HTTP, and the client copies it back onto the device. For large arrays exchanged between co-located GPU processes that copy dominates the call and defeats the point of keeping data on the device. This prevents e.g. most differentiable physics / solver-in-the-loop applications where the simulation kernel runs on the GPU and the outer loop runs in Python on the same machine.
This PR adds a
json+cuda_ipcoutput format that instead passes GPU arrays by reference. When an endpoint returns an array exposing__cuda_array_interface__(CuPy, PyTorch, JAX, Numba, anything implementing the protocol), the runtime exports a 64-bytecudaIpcMemHandle_tin place of the array data. Only the tiny handle travels over HTTP, while the data never leaves the GPU. Any non-GPU arrays in the same response still go out as base64, so mixed outputs keep working.From an end user's perspective, it's one extra flag on the server plus the experimental opt-in:
Key design choices:
ctypes rather than a framework dependency. The export/import path calls
libcudartandlibcudadirectly (cudaIpcGetMemHandle/cudaIpcOpenMemHandle) instead of going through CuPy or Torch. That keeps the runtime free of any hard GPU-framework dependency and means it can export from any producer that implements the CUDA array interface, not just one blessed library. The cost is a small amount of ctypes plumbing and manual error-string handling.Opt-in and explicitly experimental/undocumented. The format is gated behind a new
enable_experimental_cuda_ipcruntime config flag (TESSERACT_ENABLE_EXPERIMENTAL_CUDA_IPC=1) and is omitted fromavailable_formats()unless enabled. This feature needs proper support in Tesseract-JAX/Tesseract-Torch, a lot more testing, and careful documentation before it can be considered stable.Keepalive buffer on the server. CUDA IPC is inherently cross-process (a process can't open a handle it exported itself), and a pooled allocator may recycle an exported buffer before the consumer ever opens it, silently corrupting the result. The server therefore pins the arrays it exported for the current request and only releases them at the start of the next one. This bounds pinned GPU memory to a single request's worth of exports while guaranteeing each export stays alive long enough for a serial client to copy it out. The trade-off is that it assumes a serial request pattern. It is not safe for a client pipelining overlapping requests against the same server.
Staging fallback for VMM allocations. Arrays backed by CUDA's virtual-memory-management allocator (notably JAX) cannot be exported through the legacy
cudaIpcGetMemHandlepath. Rather than reimplement the VMM export machinery, such arrays are first copied into a plaincudaMallocstaging buffer, which is then exported and released alongside the normal keepalive ring. We could add proper VMM support later and save the extra copy, but this is a pragmatic first step that works for most use cases.Testing done
tests/test_cuda_ipc_cpu.py: GPU-free tests of the orchestration (payload assembly, offset arithmetic, device-ordinal detection, export registry, the serve-side release hook, the--ipc=hostwiring, and the CLI guard) by feeding fake__cuda_array_interface__objects and monkeypatching the ctypes/CUDA wrappers. These run on ordinary CI runners.tests/test_cuda_ipc.py: GPU tests that drive real cross-process IPC (basic round trip, dtypes, non-zero offsets, serial reuse, Torch/JAX interop, VMM and staging fallbacks).tests/endtoend_tests/test_serving_gpu.py: end-to-end tests that build a GPU Tesseract image and serve it in a container with--gpus all/--ipc=host, round-tripping device memory through a genuine handle.The GPU tests run on a new
tesseract-ci-gpujob (markedgpu, skipped on GPU-less runners) wired into theall-okgate. CI passes.