Repository navigation
Load AOTI plugins into hosts that don't share their C++ runtime - #24818
mattiasbrand wants to merge 3 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24818
Note: Links to docs will display an error until the docs builds have been completed. ❌ 1 Cancelled JobAs of commit c7a73e6 with merge base dd38e81 ( CANCELLED JOB - The following job was cancelled. Please retry:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@mattiasbrand has exported this pull request. If you are a Meta employee, you can view the originating Diff in D129654474. |
This PR needs a
|
Summary: Pull Request resolved: pytorch#24643 Make the Buck CUDA runtime link its AOTI shims the way the CMake build already does. A compiled AOTInductor shared library does not contain the tensor runtime. When the CUDA backend loads it, the library resolves its `aoti_torch_*` shim calls from the host process by name. Nothing in the host code calls most of these functions directly, so a static link without whole-archive drops them from `common_shims_slim` and loading fails with unresolved symbols. CMake already links `aoti_common_shims_slim` with `--whole-archive`; this sets `link_whole = True` on the Buck target to match, along with `supports_python_dlopen = True` like the other force-linked AOTI and CUDA targets. An executable that loads an AOTI library also has to export these symbols, as CMake does with `-Wl,--export-dynamic`. That includes a Buck `python_binary` that uses the Python bindings, because the extension links into its native executable. `backends/aoti/tests:test_common_shims_slim_link` looks every shim up by name in a statically linked test binary that never calls them. It fails for every shim when `link_whole` is removed. `test_common_shims_slim` cannot catch this, because it calls the shims directly and that keeps them linked. `backends/cuda/runtime/shims/tensor_attribute.cpp` defined `aoti_torch_get_device_type` and `aoti_torch_device_type_cuda` for ETensor, which the CUDA backend no longer uses. `common_shims_slim` defines the same `extern "C"` symbols for SlimTensor, so once both are force-linked they collide. CMake already leaves this file out of the CUDA shim sources. This deletes the file and its header and removes them from the Buck target. `executorch_pybindings(use_cuda = True)` compiles code that calls the CUDA runtime directly but did not declare the CUDA dependency, so such a target could not link. The macro now adds it when `use_cuda` is set, together with `//executorch/extension/cuda:cuda_allocator`, which `pybindings_cuda.h` includes and calls. Previously that dependency was only met indirectly through the AOTI CUDA backend. `//executorch/extension/pybindings:cuda_lib` builds these bindings with the AOTI CUDA backend. LibTorch defines the same `aoti_torch_*` symbols with a different tensor handle ABI, so it cannot link into the same binary. `extension/pybindings/test:test_pybindings_cuda_lib` exports a CUDA-delegated program with external weights, runs it through `cuda_lib` in a separate LibTorch-free binary, and compares the output with eager PyTorch. Without `link_whole`, or without `-Wl,--export-dynamic` on that binary, it fails with `undefined symbol: aoti_torch_dtype_float32`. `pytest.ini` and `pytest-windows.ini` leave it out of the pytest runs, because it needs a GPU and the Buck-built runner. Differential Revision: D129530257
…ytorch#24817) Summary: Pull Request resolved: pytorch#24817 The CUDA backend's C++ runtime builds only in fbcode, and only with the LLM extension's KV cache linked in. So a C++ application that runs an exported vision or audio model on an NVIDIA GPU can't use it from any other Buck cell, cross-compiled aarch64 targets such as Jetson included. To run the same AOTInductor-compiled kernels it has to link LibTorch instead. XNNPACK, Vulkan and QNN already define their C++ targets in both cells; this does the same for CUDA. - **Targets in both cells.** The `aoti/slim`, `extension/cuda` and `cuda/runtime` C++ targets are now defined outside fbcode too. There they carry `compatible_with = ["ovr_config//cuda:has_cuda"]`, so CPU-only builds skip them instead of failing to compile, and SlimTensor `storage` picks up its CUDA deps through a `select`. The Python AOTI targets stay fbcode-only. - **Public headers.** Headers that dependents include (`slim_tensor.h`, `storage.h`, `runtime_api.h`, the shim and factory headers) move from `headers` to `exported_headers`, and `runtime_api`'s CUDA dependency becomes an exported external dep. Outside fbcode `headers` are private to the target, so nothing could compile against them. fbcode already exports both, so nothing changes there. - **LLM cache optional, as in CMake.** CMake already builds `cuda_kv_cache.cpp` and `cuda_kv_pool.cpp` only with `EXECUTORCH_BUILD_EXTENSION_LLM`. Buck now does the same outside fbcode: the cache code in `cuda_backend.cpp` and `cuda_kv_cache.h` is behind that define, and the define is exported so that dependents see the same declarations as the backend. CMake now exports it too (`PUBLIC` instead of `PRIVATE` on `aoti_cuda_backend`), since the header's declarations depend on it; `test_cuda_kv_cache` and `cuda_batching` include that header. fbcode keeps the cache. A backend built without it still refuses a program whose KV cache lives off-graph, because `requires_offgraph_kv_storage` reads that from the program's constants and sits outside the gate. - **Build flags.** The `nvcc` architecture flags stay fbcode-only. Outside fbcode the shims undefine `CPU_CAPABILITY_AVX2` and `CPU_CAPABILITY_AVX512` for CUDA sources: c10 exports them on x86, and `Half.h` then pulls host AVX2 `vec_half` code into nvcc compiles. `cuda-lazy` maps to `libcudart`, and the shims link `curand` and `thrust` for `rand.cu` and `sort.cu`. fbcode builds keep the same sources and flags, and the CMake build is untouched. Differential Revision: D129654471
Summary:
An AOTInductor plugin is compiled separately from the program that loads it, so the two don't have to share a C++ standard library. Two places in the CUDA backend assume they do.
- **Constants pass through a `std::unordered_map`.** Cross-method weight sharing, mutable-state sessions and the CUDA graph host-constant check read a method's constants with `AOTInductorModelContainerExtractConstantsMap`, which hands the plugin a pointer to the host's map to fill in. That's only safe when both sides use the same standard library. It isn't safe for a Windows plugin cross-compiled with MinGW and loaded by an MSVC host, or a plugin loaded by a libc++ host. PyTorch's AOTI interface marks that call deprecated in favour of `AOTInductorModelContainerExtractConstantsMapEntries`, which returns a container-owned array of `{name, handle}` C structs. The backend now reads constants only through that call, so no C++ container crosses the plugin boundary.
- **The plugin's C++ runtime has to already be in the process.** On Linux an x86 plugin references libstdc++ (`std::cerr`, exception handling, `operator new`) without listing `libstdc++.so.6` as a dependency. Loaded from a process that doesn't link libstdc++, `dlopen` fails with `undefined symbol: _ZSt4cerr`. When the host does link libstdc++, the plugin loads only if the host's dynamic symbol table happens to include every libstdc++ symbol the plugin uses. `load_library` now opens `libstdc++.so.6` with `RTLD_GLOBAL` once before the first plugin, and returns `AccessFailed` if it can't, since such a plugin can't load anyway. The preload adds libstdc++ to the global lookup scope after the host, so symbols the host already defines still win.
Plugins that don't export the entries call still run, with reduced features:
- each method loads its own weights from the blob, without cross-method sharing;
- mutable-state sessions report `NotSupported`;
- CUDA graphs stay off, since their constants can't be checked for host memory.
Malformed results are never dereferenced: a null array with a nonzero count, a null name or handle, or an extraction error is rejected with `InvalidProgram`, or keeps graphs off.
`test_cuda_mutable_state` gains cases for the graph check (no entries call, no constants, a host constant, a device constant, malformed results) and for mutable-state extraction failures. It also builds outside fbcode as `test_cuda_mutable_state_native`.
Differential Revision: D129654474
4af9528 to
c7a73e6
Compare
|
@claude review effort=max |
|
Claude finished @shoumikhin's task in 58m 32s —— View job PR Review: #24818Request Changes: a plugin without Detailed reviewSummaryThis review covers all three commits in the stack (a42c487, 2b4739c, c7a73e6). Together they:
The blocking problem is that plugins without the new symbol now lose features without any warning. The other problems:
All 34 files were reviewed. Nothing was built or run. I couldn't read upstream PyTorch's Code Quality
Infrastructure
Testing
API Design
Backward Compatibility
Specific Comments
RecommendationRequest Changes.
|
Summary:
An AOTInductor plugin is compiled separately from the program that loads it, so the two don't have to share a C++ standard library. Two places in the CUDA backend assume they do.
std::unordered_map. Cross-method weight sharing, mutable-state sessions and the CUDA graph host-constant check read a method's constants withAOTInductorModelContainerExtractConstantsMap, which hands the plugin a pointer to the host's map to fill in. That's only safe when both sides use the same standard library. It isn't safe for a Windows plugin cross-compiled with MinGW and loaded by an MSVC host, or a plugin loaded by a libc++ host. PyTorch's AOTI interface marks that call deprecated in favour ofAOTInductorModelContainerExtractConstantsMapEntries, which returns a container-owned array of{name, handle}C structs. The backend now reads constants only through that call, so no C++ container crosses the plugin boundary.std::cerr, exception handling,operator new) without listinglibstdc++.so.6as a dependency. Loaded from a process that doesn't link libstdc++,dlopenfails withundefined symbol: _ZSt4cerr. When the host does link libstdc++, the plugin loads only if the host's dynamic symbol table happens to include every libstdc++ symbol the plugin uses.load_librarynow openslibstdc++.so.6withRTLD_GLOBALonce before the first plugin, and returnsAccessFailedif it can't, since such a plugin can't load anyway. The preload adds libstdc++ to the global lookup scope after the host, so symbols the host already defines still win.Plugins that don't export the entries call still run, with reduced features:
NotSupported;Malformed results are never dereferenced: a null array with a nonzero count, a null name or handle, or an extraction error is rejected with
InvalidProgram, or keeps graphs off.test_cuda_mutable_stategains cases for the graph check (no entries call, no constants, a host constant, a device constant, malformed results) and for mutable-state extraction failures. It also builds outside fbcode astest_cuda_mutable_state_native.Differential Revision: D129654474