Skip to content

Commit ff1bab3

Browse files
authored
Fix CUDA mask indexing, CUDA graph outputs and the runtime output-buffer check (#24299)
## What is wrong today Robot policies and follow-up checks on #24222 found these bugs in the CUDA backend and the runtime: 1. **CUDA mask indexing:** `x[mask]` and `x[mask] = values` have no C shims, so lowering fails with "missing fallback kernels". This blocks SmolVLA. 2. **CUDA graphs:** a channels-last output turns CUDA graphs off, although a raw copy is correct. 3. **CUDA graph shapes:** when the captured call has a new batch size, it and every replay return the previous output shape. 4. **Output buffers:** `Method::set_output_data_ptr` checks a buffer against the output's current size. After a dynamic output shrinks, a too-small buffer is accepted and a later, larger call writes past it. ## What this change does One commit per fix: 1. New shims for index and index_put follow ATen's indexing rules. The Windows import library gains the new names. Inside a CUDA graph capture, a mask index fails with a clear error. 2. The check compares output strides with the strides the ETensor's dim order gives. 3. The capture step resizes each output to the captured shape. 4. The buffer is checked against the output's upper bound. Code that binds a smaller buffer after a call now gets `InvalidArgument`. ## Tests Each new test fails without its fix. - **CUDA (A100):** mask reads and writes, also inside a capture; the raw-copy check; a capture call with a new batch size. - **Runtime:** a too-small output buffer is refused after a dynamic output shrinks; the runtime tests pass. On LeRobot policies, SmolVLA now lowers to CUDA and runs in 43 ms per call on an A100, against 504 ms in eager PyTorch (cosine similarity 0.99996).
1 parent 1fdd466 commit ff1bab3

18 files changed

Lines changed: 1403 additions & 43 deletions

‎backends/aoti/common_shims_slim.cpp‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ AOTITorchError aoti_torch_get_sizes(Tensor* tensor, int64_t** ret_sizes) {
3636
return Error::Ok;
3737
}
3838

39+
AOTITorchError
40+
aoti_torch_get_size(Tensor* tensor, int64_t d, int64_t* ret_size) {
41+
if (tensor == nullptr || ret_size == nullptr) {
42+
return Error::InvalidArgument;
43+
}
44+
const auto ndim = static_cast<int64_t>(tensor->dim());
45+
if (d < -ndim || d >= ndim) {
46+
return Error::InvalidArgument;
47+
}
48+
*ret_size = tensor->size(d);
49+
return Error::Ok;
50+
}
51+
3952
AOTITorchError aoti_torch_get_strides(Tensor* tensor, int64_t** ret_strides) {
4053
if (tensor == nullptr || ret_strides == nullptr) {
4154
return Error::InvalidArgument;

‎backends/aoti/common_shims_slim.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ aoti_torch_get_data_ptr(Tensor* tensor, void** ret_data_ptr);
3939
AOTI_SHIM_EXPORT AOTITorchError
4040
aoti_torch_get_sizes(Tensor* tensor, int64_t** ret_sizes);
4141

42+
// Generated code reads the size of a result whose shape depends on the data
43+
// through this, one dim at a time.
44+
AOTI_SHIM_EXPORT AOTITorchError
45+
aoti_torch_get_size(Tensor* tensor, int64_t d, int64_t* ret_size);
46+
4247
AOTI_SHIM_EXPORT AOTITorchError
4348
aoti_torch_get_strides(Tensor* tensor, int64_t** ret_strides);
4449

‎backends/cuda/CMakeLists.txt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ set(_aoti_cuda_shim_sources runtime/shims/memory.cpp
255255

256256
# Only build CUDA-only operator shims when the CUDA toolchain is available.
257257
if(NOT EXECUTORCH_BUILD_ROCM AND CMAKE_CUDA_COMPILER)
258-
list(APPEND _aoti_cuda_shim_sources runtime/shims/int4mm.cu
259-
runtime/shims/sort.cu runtime/shims/rand.cu
258+
list(APPEND _aoti_cuda_shim_sources runtime/shims/index.cu
259+
runtime/shims/int4mm.cu runtime/shims/sort.cu runtime/shims/rand.cu
260260
)
261261
endif()
262262

‎backends/cuda/cuda_backend.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,8 @@ def get_supported_fallback_kernels(cls) -> Dict[str, Any]:
839839
"at::_ops::sort_stable::call": None,
840840
"aoti_torch_cuda_sort_stable": None,
841841
"aoti_torch_cuda_randint_low_out": None,
842+
"aoti_torch_cuda_index_Tensor": None,
843+
"aoti_torch_index_put_out": None,
842844
}
843845

844846
@classmethod
2.39 KB
Binary file not shown.

‎backends/cuda/runtime/aoti_cuda_shims.lib.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pin moves and the generated wrapper starts calling something new:
1010

1111
```
1212
nm --defined-only aoti_cuda_shims.lib \
13-
| sed -n 's/.* T _\?\(aoti_torch_[a-z_0-9]*\)$/\1/p' \
13+
| sed -n 's/.* T _\?\(aoti_torch_[A-Za-z_0-9]*\)$/\1/p' \
1414
| sort -u > exports.txt
1515
# add the new names to exports.txt, then
1616
{ echo 'LIBRARY aoti_cuda_shims.dll'; echo 'EXPORTS'; sed 's/^/ /' exports.txt; } \

‎backends/cuda/runtime/cuda_backend.cpp‎

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,6 @@ constexpr char kUseSharedCudaStream[] = "use_shared_cuda_stream";
8787
constexpr char kEnableCudaGraphForMethod[] = "enable_cuda_graph_for_method";
8888
constexpr int kCudaGraphWarmupSteps = 3;
8989
constexpr char kWeightSharingAcrossMethods[] = "weight_sharing_across_methods";
90-
91-
// A CUDA graph replay copies an output's raw bytes into its ETensor, which is
92-
// right only when every element sits at the same offset in both. Size-1 dims
93-
// never move an element, so their strides may differ.
94-
bool output_is_raw_copyable(const SlimTensor* output, const Tensor* et_output) {
95-
if (!output->is_contiguous() ||
96-
output->dim() != static_cast<size_t>(et_output->dim())) {
97-
return false;
98-
}
99-
// The ETensor still has its previous shape here, so its strides can be stale
100-
// for dynamic shapes; the default dim order is enough on its own.
101-
if (executorch::runtime::is_contiguous_dim_order(
102-
et_output->dim_order().data(), et_output->dim())) {
103-
return true;
104-
}
105-
for (size_t d = 0; d < output->dim(); d++) {
106-
if (output->size(d) != 1 && output->stride(d) != et_output->strides()[d]) {
107-
return false;
108-
}
109-
}
110-
return true;
111-
}
11290
} // anonymous namespace
11391

11492
bool CudaGraphState::start_warmup() {
@@ -1090,6 +1068,21 @@ class ET_EXPERIMENTAL CudaBackend final
10901068
handle->method_name.c_str());
10911069
return Error::NotSupported;
10921070
}
1071+
// Replay copies raw bytes and never resizes, so the ETensor takes the
1072+
// captured shape here and keeps it for every replay.
1073+
const Error resize_err =
1074+
executorch::ET_RUNTIME_NAMESPACE::resize_tensor(
1075+
args[i + n_inputs]->toTensor(), slim_outputs[i]->sizes());
1076+
if (resize_err != Error::Ok) {
1077+
std::fill(slim_outputs.begin(), slim_outputs.end(), nullptr);
1078+
ET_LOG(
1079+
Error,
1080+
"CUDA graph: cannot resize output %zu of '%s' to its captured "
1081+
"shape; this call fails and later calls run without a graph",
1082+
i,
1083+
handle->method_name.c_str());
1084+
return resize_err;
1085+
}
10931086
}
10941087

10951088
gerr = cudaGraphInstantiate(

0 commit comments

Comments
 (0)