Skip to content

CUDA backend: CUDA graph fixes for mask indexing, .item() and changed input shapes, masked copies as where, tensor checks dropped before compiling, float64, _reshape_copy, scatter_reduce and varlen_attn, follow-ups - #24512

Merged
JacobSzwejbka merged 2 commits into
release/1.6from
cherry-pick-24326-by-pytorch_bot_bot_
Nov 3, 2026

Conversation

@pytorchbot

Copy link
Copy Markdown
Collaborator

What is wrong today

With enable_cuda_graph_for_method set, the CUDA backend runs a method a few times normally, captures a CUDA graph on the fourth call, and replays that graph after it. Some calls go wrong:

  • A call that cannot be captured fails. A mask index, such as x[mask] or x[mask] = v, reads a count back to the host, and a capture cannot record that. The capture call returned an error. An output that is first strided at the capture call failed the same way. A .item() on a device tensor, such as a torch.cond predicate computed on the GPU, ended the process.
  • A replay can return a wrong result with no error. Replay checked only each input's byte count. An input that changes from (2, 3, 4, 4) to (2, 4, 3, 4) keeps its byte count, so it replayed the captured shape and returned the wrong output.

What this change does

  • Replay compares each input's shape with the captured one and refuses a different shape with InvalidArgument. The error names the dim and both sizes. A captured graph only works for the input shapes of the capture call, and the code comment on the graph phases now says so.
  • When a capture fails, the backend runs the same call again without a graph and returns its result. Graphs stay off for that method, as before. The retry is safe because a capture records kernels without running them, and it happens only if the new graph never started running. The .item() shims now refuse to read inside a capture, as the mask index does, so a capture that reads a device value is retried too instead of ending the process.
  • A method whose program keeps a constant in host memory, such as a torch.cond flag updated on every call, now runs without a graph. Host code runs only during the capture, so a graph would keep the branch of the capture call, and a retry after a failed capture would take the other one. The log names the constant found in host memory, or says the constants could not be listed.
  • An expected capture failure, such as the mask index refusing to run inside a capture, is logged at Info instead of Error, since the call then succeeds. A real failure of the call without a graph is still logged at Error. AOTInductor's generated code still prints its own line for the refused call.
  • The allocations a captured graph makes are recorded only after its first launch succeeds. Before, a failed first launch left addresses that were never allocated, and a later cleanup passed them to cudaFree. A failed capture now cleans up with CudaGraphState::release() instead of a copy of it.
  • These CUDA graph changes do not change the exported program, so older programs get them too. A program that uses the new shims described below (scatter, scatter_reduce, float64 or float16) needs a runtime that has them.

Smaller commits:

  • The CUDA random shims seed from std::random_device instead of the clock, so two processes started in the same second no longer draw the same numbers. If std::random_device throws, or the copy of the seed to the GPU fails, the shim logs an error and frees what it allocated, and the next call tries again.
  • The wheel's shared library check now lists the mask index and index_put shims.
  • With PyTorch 2.14, a program that writes a one-element buffer, such as a counter, kept returning its first update, because Inductor folded the update into a constant. The AOTI backends (CUDA and Metal) now turn off Inductor's joint-graph constant folding, for the whole program, when a program writes a buffer. On a small Gemma 4 decoder that writes its KV cache, folding changes two nodes per graph; with it off, export and decode times stayed within run-to-run noise and the generated tokens were identical.
  • Unit tests for aoti_torch_get_size.
  • Comments on the index shims and on set_output_data_ptr that described accumulation, the sort count limit and the output index wrongly are corrected.
  • The mask index tests move to a new test_index_shims.py, since they need the Python runtime. They add a 2-D mask, and index_put with repeated indices in deterministic mode (half and bfloat16 accumulation, last write wins, an int32 index).
  • test_output_shape_changes_at_capture ends with a call that only a live graph rejects, so it fails if graphs are off. The KV cache recapture test checks that dropping the graph also clears the captured input shapes.

Follow-ups to #24314

  • Delete test_align_corners_compiles_with_dynamic_sizes. It could not fail: it passed with no resize rewrite at all and with the min and max in the align_corners ratio replaced by a plain if, because torch.compile compiles again for an output size of 1. With torch 2.14 and CI set it also failed for an unrelated reason, and it is the one failing test of the CUDA unit test job on main. test_bicubic_antialias_resize already runs an exported program at an output height of 1, where a plain if breaks.
  • The comment on that min and max now gives the real reason: an exported program keeps the branch it was traced with, so a plain if divides by zero at an output size of 1.
  • A backend that keeps its data in the .pte, such as Metal, now refuses a data_name compile spec built by hand too, before compiling anything. Before, only generate_data_name_compile_spec refused it, and a hand-built spec was ignored with no name check.
  • The CUDA docs warn that Windows and macOS usually treat data names that differ only in case as the same file.

Copies through a boolean mask no longer read the mask count back to the host

out[mask] = src[mask] ran as a mask index and a mask index_put. Each of them read the mask's count back to the host and waited for the GPU, on every call. Transformers' SmolVLM vision embeddings do this for their position ids.

When the values are the same mask's picks from a tensor shaped like the destination, every element goes back to where it came from. So a new pass in the CUDA backend writes the copy as torch.where(mask, src, out), which gives the same result, bit for bit. It does so inside torch.cond branches and other subgraphs too. It rewrites only when where would lay out its result like the destination, since where takes its layout from its inputs, the mask first, while index_put keeps the destination's layout. It also drops the index and the checks on its size when nothing else reads them and each check tests only the index's count and holds for any mask. A user check on the count, such as torch._check(x[mask].shape[0] >= 1), keeps the index. The pass compares the two masks with a work list, so masks with long histories do not hit Python's recursion limit. On an A100, five calls of the SmolVLM position id code went from 10 count reads and 10 stream waits to none.

Tensor checks no longer stop the CUDA export

The argument checks in torch.distributions export as aten._assert_async.msg. The CUDA backend compiled each one into a device assert inside a Triton kernel. While compiling, AOTInductor runs every kernel once to pick its launch settings, on made-up buffers: random floats, and zeros for integers and bools. So a check that reads a bool from an earlier kernel saw false and failed, even though it holds for every real input. The export then stopped with "device-side assert triggered". A Gaussian policy head, MultivariateNormal(mean, scale_tril=...) with 14 actions, hits this.

A new pass in the CUDA backend now removes these checks before compiling, so the CUDA delegate no longer runs them. The rest of ExecuTorch already removes the same checks before a program is saved. A failed device assert could not come back as an error anyway: it breaks the CUDA context for the rest of the process. Checks on sizes (_assert_scalar) stay, since they run on the host.

float64 weights load in the CUDA backend

A program with a float64 weight lowered and saved, then failed to load with Malformed CUDA FQN weight metadata. This happened with a float64 buffer, and with float64 math that constant propagation folds into one constant, such as a sinusoidal time embedding. The export wrote the weight correctly, but the runtime had no float64 dtype: the weight cache refused it, and the runtime's tensor type and dtype shims had none either.

  • The runtime's tensor type now has Double, with its 8 byte size. The same type wraps a method's inputs and outputs, so a float64 input or output works too.
  • The shims export aoti_torch_dtype_float64, which compiled code calls to name the dtype of a float64 constant.
  • The weight cache accepts Double, and uses 8 bytes when it checks that a weight fits its storage.
  • The shims that still refused float64 accept it: a float64 .item() (aoti_torch_item_float64, which did not exist, so the first execute ended the process), the single-element read those entry points share, index_put with accumulate, and the sort shim. A float16 .item() had the same problem with aoti_torch_item_float16 and works now too.
  • A float read with .item() reached Triton kernels truncated to an integer, in float32 too, so a scale of 1.5 computed as 1 with no error. AOTInductor declares every symbolic argument of a kernel's launcher int64_t. A compile-time patch in the CUDA backend now declares it with the float type the kernel takes, the change that the open [inductor] Support host-computed float scalar args for Triton templates pytorch#201882 makes in PyTorch.

aten._reshape_copy lowers to the CUDA backend

Torch-TensorRT turns aten.view into aten._reshape_copy. A view that TensorRT leaves to PyTorch reaches the CUDA backend in that form, and the export failed with missing fallback kernels: aten::_reshape_copy, since Inductor has no lowering for it and torch registers no decomposition. The CUDA backend now decomposes it the way ATen computes it: a contiguous copy, viewed to the new shape.

varlen_attn lowers to the CUDA backend

torch.nn.attention.varlen.varlen_attn runs attention over several sequences packed into one tensor. Lowering it failed with missing fallback kernels: torch_attn::_varlen_attn, since it calls Flash or cuDNN attention, which this backend does not ship. The CUDA backend now decomposes it into ops Inductor lowers: attention over all the packed tokens, with a mask that keeps each query in its own sequence. The output is contiguous, like the op's output for a query in the usual (tokens, heads, dim) order, so reshaping it before the output projection works.

The decomposition matches the op for causal masks, sliding windows, grouped-query attention, and query and key sequences of different lengths. For a query sequence longer than max_k + 1, Flash attention 2 (PyTorch's default) and Flash attention 3 give different results for some windows, because version 2 changes the window based on max_k. So a window other than (left, 0) lowers only when max_q <= max_k + 1 is known at export time, as in self-attention, and raises a clear error otherwise. seqused_k and block_table (paged KV) raise a clear error too. It computes the weights with a softmax, which subtracts each row's largest score first, so the weights still add up to one when scores are large. Tokens before the first sequence or past the last one, such as the padding of a fixed-size buffer, do not change any sequence's output, even when they hold NaN. Unlike in Flash attention, a NaN or inf in one sequence's values does reach the other sequences. It holds a float32 score per head for every pair of packed tokens and runs much slower than Flash attention, so it suits packs of a few thousand tokens, such as a few camera images.

scatter_reduce with amax, amin, prod and mean lowers to the CUDA backend

Lowering x.scatter_reduce(dim, index, src, "amax") failed with missing fallback kernels: aoti_torch_cuda_scatter_reduce_two_out, and so did "amin", "prod" and "mean". Inductor writes a scatter itself only when it can add atomically, and leaves the other reductions to an ATen kernel that this backend did not ship. A sum into int64 or bool, a sum into bfloat16 on GPUs older than sm_90, and deterministic mode use the same kernel.

  • A new shim next to the index shims implements it the way ATen does: include_self, elements the index does not name, the count and integer rounding of the mean, NaN in amax and amin, and bool for sum only. Half and bfloat16 sums add in float, as ATen does in deterministic mode. A product rounds to the tensor's dtype at every step and a mean rounds its sum before dividing, as ATen does, so half overflows and integers wrap where ATen's do. A mean also counts its terms in the tensor's dtype, as ATen does, so 128 int8 ones average to 1.
  • It sorts the writes by the element they land on and reduces each element's values in index order, so the result is deterministic. It also works inside a CUDA graph. The sort is now one helper that index_put uses too.
  • It is slower than ATen's atomic kernel: 1.5 to 3.2 times on an A100 at 512 output rows, 4096 source rows and 1024 columns, and up to 7 times when many values land on few elements. A decomposition into ops Inductor lowers natively was measured first. It has to compare every output element with every source element, and it was 8 to 89 times slower than ATen's kernel.
  • aoti_cuda_shims.lib and the wheel's shared library check list the new shim. The import library now also lists the shims the DLL exported but it left out, such as aoti_torch_dtype_float16 and the aoti_torch_item_ getters, so a float16 model lowered for Windows links.

CUDA programs load without torch on machines with no libgomp

On Linux, Inductor adds OpenMP (-fopenmp, -lgomp) to every link. Unless the compiler passes --as-needed to the linker, as Ubuntu's GCC does by default, the compiled library lists libgomp.so.1 as a dependency even when it calls no OpenMP function. PyTorch ships a libgomp, so this went unnoticed while torch was imported. A process without torch failed to load the program with libgomp.so.1: cannot open shared object file on any machine with no system libgomp, such as a minimal container.

The CUDA backend now links with -Wl,--as-needed, so the linker keeps only the libraries the program uses. A program that does call OpenMP keeps libgomp. PyTorch already does the same for MPS builds on macOS, and pytorch/pytorch#202902 adds it to Inductor for Linux. The torch-free tests in test_pybindings_cuda_dlpack.py now run without a system libgomp, and the ones that check which libraries got loaded also check for libgomp. On an A100, a small MLP and CNN exported with this change load without torch and match eager where the loader cannot find libgomp.

This patch, and the one that lets Triton templates run on GPUs with few SMs, now stay in place while any thread lowers. Before, when two threads lowered at once, the first one to finish removed the patch from the other, and the rest of that compile ran without it.

A failed CUDA load no longer leaves its library in the temporary directory

To load a method, the CUDA backend writes the program's compiled library to a temporary file, such as /tmp/executorch_cuda_<pid>_0.so. Only destroy() removed that file, and destroy() never runs when the load fails, so every load that failed after writing the file left it behind. A scope guard now removes the file unless the load succeeds. test_missing_weights_blob.py now checks that a load that fails for a missing weights blob leaves nothing in the temporary directory. Without the fix, that check fails.

The XNNPACK high-rank permute fix moved to #24380.

The Triton SDPA leaves keys past the end out of the softmax

With triton_kernel_mode on (the default), the CUDA backend runs scaled_dot_product_attention as its Triton kernel, triton::sdpa. For power-of-two head sizes, its main kernel reads keys in tiles of 32, 64 or 128. When the number of keys is not a multiple of the tile, the last tile reaches past the last key. The kernel loaded those positions as zeros but never removed them from the softmax, so without an attention mask or a causal mask each one got a score of 0 and took part of the attention weight. Calls that pass kv_len with neither a mask nor is_causal, such as the DFlash draft model's global attention layers, had the same problem. On an A100, with the query at 0 and the values at 1, where the right output is 1, one key gave 0.031, 33 keys gave 0.516 and 80 keys gave 0.625. With random inputs and one key, the output was off by 3.4 when the largest value was 3.67. All of these now match eager PyTorch.

The kernel now gives every position past the last key, or past kv_len, a score of minus infinity, as the other attention kernels in sdpa.py already do. The first version of the kernel did this, and a later rewrite dropped it. Checking every tile cost up to 8% where no tile reaches past the end, so only calls that need the check pay for it. A mask or a causal bound already drops those keys, so calls with one skip it. The launcher also passes the largest tile size that divides the number of keys, and tiles that divide it skip the check at compile time. So calls with a mask or a causal bound, and tiles that divide a key count known at export time, compile to the same code as before. Without a mask or a causal bound, a kv_len or a symbolic key count checks every tile. test_triton_sdpa.py drops its note that called this a kernel limitation, runs its basic test with a single key, and runs every tile without a mask over 80 and 96 keys and over a kv_len of 80. These fail without the fix.

scatter with a tensor source lowers to the CUDA backend

Lowering x.scatter(dim, index, src) failed with a missing fallback kernels error that names aoti_torch_cuda_scatter_src_out when src is bfloat16, int64 or bool. Inductor writes a scatter from a tensor itself only in a dtype it can add atomically, so it leaves int64, bool and, on GPUs older than sm_90, bfloat16 to an ATen kernel, and every dtype in deterministic mode. A mixture-of-experts layer that writes its expert outputs back with torch.zeros_like(h).scatter(1, index, src) in bfloat16 hit this on an A100.

A new shim next to the scatter_reduce one implements it with the same checks, the same sorting of the writes by the element they land on, and the same kernel. The kernel gets a new mode in which only the last write to each element stores its value. So when values land on the same element, the last one in index order wins, as ATen does in deterministic mode; ATen's default CUDA kernel keeps any one of them. As in that default kernel, an index out of range, negative ones included, triggers a device-side assert. In deterministic mode, ATen's scatter counts a negative index from the end instead. The shim takes float, double, half, bfloat16, the integer dtypes and bool. Like ATen, both scatter shims now take an int32 index as well as an int64 one. Mostly because of the sort, the shim is about 10 to 15 times slower than ATen's default kernel for a mixture-of-experts style bfloat16 scatter of 4 to 34 million values on an A100. The Windows import library aoti_cuda_shims.lib and the wheel's shared library check now list the new shim. On an A100 with PyTorch 2.14 and 2.15, scatters in float32, float16, bfloat16, int64 and bool lower, run and match eager PyTorch exactly. So does a small mixture-of-experts layer built the same way. test_scatter_with_tensor_source fails without the fix, and its int32 index case fails when the shims take only int64.

Review order

Start with the CUDA graph changes: cuda_backend.cpp and cuda_delegate_handle.h in backends/cuda/runtime, the mask and .item() shims in runtime/shims/index.cu and memory.cpp, and their tests in test_output_stride_rearrange.py. Then the lowering side: the passes in backends/cuda/passes, the decompositions and compile-time patches in backends/cuda/cuda_backend.py, and the folding switch in backends/aoti/aoti_backend.py. Then the scatter and scatter_reduce shims in index.cu with test_index_shims.py. The remaining commits are small follow-ups that stand on their own.

Testing

On an A100, with the CUDA backend built from this branch:

  • test_index_shims.py, test_output_stride_rearrange.py, test_cuda_export.py, test_antialiased_resize.py, test_merge_ptes.py and test_cuda_weight_metadata.py pass.

  • With the backend built from main, the new CUDA graph tests fail.

  • The replay error names the dim, for example "input 0 has size 4 in dim 1, captured with 3".

  • test_cuda_kv_cache passes, and fails when release() keeps the captured input shapes.

  • With the min and max replaced by a plain if, test_bicubic_antialias_resize fails.

  • With the new data name refusal removed, or moved back behind the compile, the data name test fails.

  • A torch.cond on a host-memory flag matches eager on every call; with a graph it returned the captured branch.

  • A random program run from two processes started together drew the same numbers in 2 of 3 tries before the seed change, and different ones in 3 of 3 after.

  • Mask index programs exported from this branch match eager PyTorch on every call, both on a runtime built from main with CUDA graphs off and on this branch's runtime with CUDA graphs on.

  • The shared shim unit tests pass in a standalone googletest build.

  • test_replace_masked_copy_with_where.py passes, and fails when the pass does nothing, is not registered, or skips its accumulate, same-mask, boolean mask, shared picks or count check. It writes into a destination filled with 7, so it fails if where loses the values the mask does not pick. A contiguous destination with a channels_last source, or with a channels_last mask, keeps its index_put; before, the rewrite failed to lower when a later view(-1) needed the destination's layout. It also fails when the pass walks only the top-level graph, since a masked copy in a torch.cond branch then keeps its index, and when it takes two draws of a random mask for the same mask. Two masks computed with over a thousand ops each raised RecursionError before. A check that reads the index's count but tests another size keeps the index; before, the pass removed it.

  • A masked copy lowered through the CUDA partitioner matches eager for masks that pick some, none and all positions. The test records the graph the pass returns, and fails when the pass is not registered or leaves the index in place.

  • A module that adds one to a device buffer on every call and reads a mask's count returns the call number on every call, so the capture call that runs again without a graph updates the buffer once. With the retry made to run the call twice, the capture call returns one too many. With PyTorch 2.14 it returned one on every call even without a graph, because Inductor folded the written buffer into a fill; it passes now with 2.14 and with 2.15 and 2.16 nightlies.

  • On the tiny Gemma 4 decoder from the Gemma 4 31B example's tests, which writes a KV cache, with PyTorch 2.14 on an A100: with the folding off, export took 193 s against 190.5 s with it on, a decode call took 945 µs against 1019 µs (medians of three exports and three runs on a busy machine), and the 41 generated tokens were the same.

  • The mask tests check that the runtime log reports one failed capture, so they fail when CUDA graphs are off or a failed capture is tried again. test_host_read_runs_without_a_graph also runs a module that scales by a .item() read; without the capture check in the item shims, the process aborts.

  • With the debug check on the phase before the retry turned into a release check, the mask tests pass.

  • test_distribution_argument_check passes with PyTorch 2.14 and fails with "device-side assert triggered" without the new pass. It also checks that the backend lists the pass, since without the pass the export fails only when autotuning happens to trip the assert. It writes the MultivariateNormal check as torch._assert_async, since PyTorch 2.14 cannot export the distribution itself. It also checks that the pass drops the check and the math that only fed it. A Gaussian policy head with hidden sizes 16, 64 and 256 exports, runs, and matches eager PyTorch.

  • A float64 buffer and a folded float64 constant load and match eager PyTorch. Before, both failed to load. test_float64_weight_loads fails without the fix.

  • test_reshape_copy in test_cuda_export.py fails without the fix. The one in test_index_shims.py runs _reshape_copy on a transposed, a sliced and a contiguous input and matches eager PyTorch.

  • test_varlen_attn in test_cuda_export.py fails without the fix. It lowers a program that flattens the output, compares the decomposition with the op on CUDA only (on ROCm, varlen_attn runs AOTriton, which lines an explicit window up with the start of each sequence, while the decomposition follows Flash attention on CUDA, which lines it up with the end), including a window in self-attention and NaN values outside the key sequences, and checks that a window the Flash attention versions disagree on raises. It also fails when the output is left non-contiguous, when the window check is removed or refuses every such window, and when the values outside the key sequences are not zeroed. Both test_varlen_attn tests include queries and keys filled with 1024, where every key gets the same weight; computing the weights as exp(scores - logsumexp) fails them, since at that size float32 rounds the log-sum-exp to a whole number and the weights no longer add up to one. The test_varlen_attn in test_index_shims.py lowers and runs programs that call varlen_attn with no window, a causal mask and the windows (8, 0) and (4, 2), flatten each output, and take cu_seq_q and cu_seq_k only at run time. They match eager PyTorch.

  • test_missing_weights_blob.py, test_cuda_weight_reuse.py and test_device_placement.py pass.

  • test_scatter_reduce now runs the sum too, which for int64 goes to the new shim. The test that a missing scatter kernel fails at export now tries to lower a scatter of a scalar in deterministic mode. The backend still lacks that kernel, so the test still checks the scatter fallback now that the scatter_reduce and scatter shims exist. It fails when the check stops watching that fallback. test_scatter_mean_counts_in_its_dtype fails with the old count and when a count that wraps to zero is not turned into one. test_float64_and_float16_values (a sort and .item() in each dtype) and the float64 masked accumulate fail with the old shims, and all pass now. The .item() scale is 1.5, so that test also fails without the patch that passes a float to Triton kernels as a float.

  • test_scatter_reduce and test_sort_shim.py pass. test_scatter_reduce fails without the fix, and also when the shim ignores include_self, rounds the integer mean toward zero, or lets a NaN lose in amax and amin. test_scatter_strides scatters along the middle dim of a 3-D tensor, given as a negative dim, from a strided source and from transposed operands, and fails when the shims walk only the last two dims. test_scatter_reduce_edge_cases covers a bool sum, int32 and half products that wrap and overflow, and an empty index. test_scatter_reduce_sum_adds_in_float sums 1000 half and bfloat16 values in deterministic mode, and fails when the sum rounds to the dtype at every step. test_scatter_reduce_replays_in_a_graph captures a scatter_reduce program in a CUDA graph and matches eager on every replay.

  • The weight cache, runtime tensor and shim unit tests pass in a standalone googletest build. Without the fix, the weight cache test of a float64 entry fails.

  • test_small_gpu_override_survives_a_thread_that_leaves_first overlaps two threads inside the small-GPU patch, and fails with the old save and restore.

  • With the seed copy made to fail once, the random shim logs it, fails that call, and seeds on the next one; before, it went on with an unseeded state.

Not tested: a failed first graph launch (it cannot be triggered from a test), a std::random_device that throws, Windows, macOS case-insensitive file systems, the buffer folding change on Metal, and the ROCm build. No test covers the seed change, since a test cannot make two processes start in the same second.

Checked on real robot policies

On the release/1.6 stack with this change, on A100 and H100:

  • SmolVLA on the CUDA backend: one device-to-host copy per action chunk (only the action download), matching eager within the usual tolerance.
  • A Gaussian policy whose torch.distributions check used to stop the CUDA export: exports and runs, 0.76 ms per chunk.
  • A float64 weight that used to fail at load with "Malformed CUDA FQN weight metadata": loads and runs, max difference 0.
  • Four processes started together: four different random draws, against two out of four before.

shoumikhin and others added 2 commits November 3, 2026 18:02
… input shapes, masked copies as where, tensor checks dropped before compiling, float64, _reshape_copy, scatter_reduce and varlen_attn, follow-ups (#24326)

## What is wrong today

With `enable_cuda_graph_for_method` set, the CUDA backend runs a method
a few times normally, captures a CUDA graph on the fourth call, and
replays that graph after it. Some calls go wrong:

- **A call that cannot be captured fails.** A mask index, such as
`x[mask]` or `x[mask] = v`, reads a count back to the host, and a
capture cannot record that. The capture call returned an error. An
output that is first strided at the capture call failed the same way. A
`.item()` on a device tensor, such as a `torch.cond` predicate computed
on the GPU, ended the process.
- **A replay can return a wrong result with no error.** Replay checked
only each input's byte count. An input that changes from `(2, 3, 4, 4)`
to `(2, 4, 3, 4)` keeps its byte count, so it replayed the captured
shape and returned the wrong output.

## What this change does

- Replay compares each input's shape with the captured one and refuses a
different shape with `InvalidArgument`. The error names the dim and both
sizes. A captured graph only works for the input shapes of the capture
call, and the code comment on the graph phases now says so.
- When a capture fails, the backend runs the same call again without a
graph and returns its result. Graphs stay off for that method, as
before. The retry is safe because a capture records kernels without
running them, and it happens only if the new graph never started
running. The `.item()` shims now refuse to read inside a capture, as the
mask index does, so a capture that reads a device value is retried too
instead of ending the process.
- A method whose program keeps a constant in host memory, such as a
`torch.cond` flag updated on every call, now runs without a graph. Host
code runs only during the capture, so a graph would keep the branch of
the capture call, and a retry after a failed capture would take the
other one. The log names the constant found in host memory, or says the
constants could not be listed.
- An expected capture failure, such as the mask index refusing to run
inside a capture, is logged at Info instead of Error, since the call
then succeeds. A real failure of the call without a graph is still
logged at Error. AOTInductor's generated code still prints its own line
for the refused call.
- The allocations a captured graph makes are recorded only after its
first launch succeeds. Before, a failed first launch left addresses that
were never allocated, and a later cleanup passed them to `cudaFree`. A
failed capture now cleans up with `CudaGraphState::release()` instead of
a copy of it.
- These CUDA graph changes do not change the exported program, so older
programs get them too. A program that uses the new shims described below
(scatter, scatter_reduce, float64 or float16) needs a runtime that has
them.

Smaller commits:

- The CUDA random shims seed from `std::random_device` instead of the
clock, so two processes started in the same second no longer draw the
same numbers. If `std::random_device` throws, or the copy of the seed to
the GPU fails, the shim logs an error and frees what it allocated, and
the next call tries again.
- The wheel's shared library check now lists the mask index and
`index_put` shims.
- With PyTorch 2.14, a program that writes a one-element buffer, such as
a counter, kept returning its first update, because Inductor folded the
update into a constant. The AOTI backends (CUDA and Metal) now turn off
Inductor's joint-graph constant folding, for the whole program, when a
program writes a buffer. On a small Gemma 4 decoder that writes its KV
cache, folding changes two nodes per graph; with it off, export and
decode times stayed within run-to-run noise and the generated tokens
were identical.
- Unit tests for `aoti_torch_get_size`.
- Comments on the index shims and on `set_output_data_ptr` that
described accumulation, the sort count limit and the output index
wrongly are corrected.
- The mask index tests move to a new `test_index_shims.py`, since they
need the Python runtime. They add a 2-D mask, and `index_put` with
repeated indices in deterministic mode (half and bfloat16 accumulation,
last write wins, an int32 index).
- `test_output_shape_changes_at_capture` ends with a call that only a
live graph rejects, so it fails if graphs are off. The KV cache
recapture test checks that dropping the graph also clears the captured
input shapes.

## Follow-ups to #24314

- Delete `test_align_corners_compiles_with_dynamic_sizes`. It could not
fail: it passed with no resize rewrite at all and with the min and max
in the align_corners ratio replaced by a plain `if`, because
`torch.compile` compiles again for an output size of 1. With torch 2.14
and `CI` set it also failed for an unrelated reason, and it is the one
failing test of the CUDA unit test job on main.
`test_bicubic_antialias_resize` already runs an exported program at an
output height of 1, where a plain `if` breaks.
- The comment on that min and max now gives the real reason: an exported
program keeps the branch it was traced with, so a plain `if` divides by
zero at an output size of 1.
- A backend that keeps its data in the `.pte`, such as Metal, now
refuses a `data_name` compile spec built by hand too, before compiling
anything. Before, only `generate_data_name_compile_spec` refused it, and
a hand-built spec was ignored with no name check.
- The CUDA docs warn that Windows and macOS usually treat data names
that differ only in case as the same file.

## Copies through a boolean mask no longer read the mask count back to
the host

`out[mask] = src[mask]` ran as a mask index and a mask `index_put`. Each
of them read the mask's count back to the host and waited for the GPU,
on every call. Transformers' SmolVLM vision embeddings do this for their
position ids.

When the values are the same mask's picks from a tensor shaped like the
destination, every element goes back to where it came from. So a new
pass in the CUDA backend writes the copy as `torch.where(mask, src,
out)`, which gives the same result, bit for bit. It does so inside
`torch.cond` branches and other subgraphs too. It rewrites only when
`where` would lay out its result like the destination, since `where`
takes its layout from its inputs, the mask first, while `index_put`
keeps the destination's layout. It also drops the index and the checks
on its size when nothing else reads them and each check tests only the
index's count and holds for any mask. A user check on the count, such as
`torch._check(x[mask].shape[0] >= 1)`, keeps the index. The pass
compares the two masks with a work list, so masks with long histories do
not hit Python's recursion limit. On an A100, five calls of the SmolVLM
position id code went from 10 count reads and 10 stream waits to none.

## Tensor checks no longer stop the CUDA export

The argument checks in `torch.distributions` export as
`aten._assert_async.msg`. The CUDA backend compiled each one into a
device assert inside a Triton kernel. While compiling, AOTInductor runs
every kernel once to pick its launch settings, on made-up buffers:
random floats, and zeros for integers and bools. So a check that reads a
bool from an earlier kernel saw false and failed, even though it holds
for every real input. The export then stopped with "device-side assert
triggered". A Gaussian policy head, `MultivariateNormal(mean,
scale_tril=...)` with 14 actions, hits this.

A new pass in the CUDA backend now removes these checks before
compiling, so the CUDA delegate no longer runs them. The rest of
ExecuTorch already removes the same checks before a program is saved. A
failed device assert could not come back as an error anyway: it breaks
the CUDA context for the rest of the process. Checks on sizes
(`_assert_scalar`) stay, since they run on the host.

## float64 weights load in the CUDA backend

A program with a float64 weight lowered and saved, then failed to load
with `Malformed CUDA FQN weight metadata`. This happened with a float64
buffer, and with float64 math that constant propagation folds into one
constant, such as a sinusoidal time embedding. The export wrote the
weight correctly, but the runtime had no float64 dtype: the weight cache
refused it, and the runtime's tensor type and dtype shims had none
either.

- The runtime's tensor type now has `Double`, with its 8 byte size. The
same type wraps a method's inputs and outputs, so a float64 input or
output works too.
- The shims export `aoti_torch_dtype_float64`, which compiled code calls
to name the dtype of a float64 constant.
- The weight cache accepts `Double`, and uses 8 bytes when it checks
that a weight fits its storage.
- The shims that still refused float64 accept it: a float64 `.item()`
(`aoti_torch_item_float64`, which did not exist, so the first execute
ended the process), the single-element read those entry points share,
`index_put` with accumulate, and the sort shim. A float16 `.item()` had
the same problem with `aoti_torch_item_float16` and works now too.
- A float read with `.item()` reached Triton kernels truncated to an
integer, in float32 too, so a scale of 1.5 computed as 1 with no error.
AOTInductor declares every symbolic argument of a kernel's launcher
`int64_t`. A compile-time patch in the CUDA backend now declares it with
the float type the kernel takes, the change that the open
pytorch/pytorch#201882 makes in PyTorch.

## `aten._reshape_copy` lowers to the CUDA backend

Torch-TensorRT turns `aten.view` into `aten._reshape_copy`. A view that
TensorRT leaves to PyTorch reaches the CUDA backend in that form, and
the export failed with `missing fallback kernels: aten::_reshape_copy`,
since Inductor has no lowering for it and torch registers no
decomposition. The CUDA backend now decomposes it the way ATen computes
it: a contiguous copy, viewed to the new shape.

## `varlen_attn` lowers to the CUDA backend

`torch.nn.attention.varlen.varlen_attn` runs attention over several
sequences packed into one tensor. Lowering it failed with `missing
fallback kernels: torch_attn::_varlen_attn`, since it calls Flash or
cuDNN attention, which this backend does not ship. The CUDA backend now
decomposes it into ops Inductor lowers: attention over all the packed
tokens, with a mask that keeps each query in its own sequence. The
output is contiguous, like the op's output for a query in the usual
(tokens, heads, dim) order, so reshaping it before the output projection
works.

The decomposition matches the op for causal masks, sliding windows,
grouped-query attention, and query and key sequences of different
lengths. For a query sequence longer than `max_k + 1`, Flash attention 2
(PyTorch's default) and Flash attention 3 give different results for
some windows, because version 2 changes the window based on `max_k`. So
a window other than `(left, 0)` lowers only when `max_q <= max_k + 1` is
known at export time, as in self-attention, and raises a clear error
otherwise. `seqused_k` and `block_table` (paged KV) raise a clear error
too. It computes the weights with a softmax, which subtracts each row's
largest score first, so the weights still add up to one when scores are
large. Tokens before the first sequence or past the last one, such as
the padding of a fixed-size buffer, do not change any sequence's output,
even when they hold NaN. Unlike in Flash attention, a NaN or inf in one
sequence's values does reach the other sequences. It holds a float32
score per head for every pair of packed tokens and runs much slower than
Flash attention, so it suits packs of a few thousand tokens, such as a
few camera images.

## `scatter_reduce` with amax, amin, prod and mean lowers to the CUDA
backend

Lowering `x.scatter_reduce(dim, index, src, "amax")` failed with
`missing fallback kernels: aoti_torch_cuda_scatter_reduce_two_out`, and
so did "amin", "prod" and "mean". Inductor writes a scatter itself only
when it can add atomically, and leaves the other reductions to an ATen
kernel that this backend did not ship. A sum into int64 or bool, a sum
into bfloat16 on GPUs older than sm_90, and deterministic mode use the
same kernel.

- A new shim next to the index shims implements it the way ATen does:
`include_self`, elements the index does not name, the count and integer
rounding of the mean, NaN in amax and amin, and bool for sum only. Half
and bfloat16 sums add in float, as ATen does in deterministic mode. A
product rounds to the tensor's dtype at every step and a mean rounds its
sum before dividing, as ATen does, so half overflows and integers wrap
where ATen's do. A mean also counts its terms in the tensor's dtype, as
ATen does, so 128 int8 ones average to 1.
- It sorts the writes by the element they land on and reduces each
element's values in index order, so the result is deterministic. It also
works inside a CUDA graph. The sort is now one helper that `index_put`
uses too.
- It is slower than ATen's atomic kernel: 1.5 to 3.2 times on an A100 at
512 output rows, 4096 source rows and 1024 columns, and up to 7 times
when many values land on few elements. A decomposition into ops Inductor
lowers natively was measured first. It has to compare every output
element with every source element, and it was 8 to 89 times slower than
ATen's kernel.
- `aoti_cuda_shims.lib` and the wheel's shared library check list the
new shim. The import library now also lists the shims the DLL exported
but it left out, such as `aoti_torch_dtype_float16` and the
`aoti_torch_item_` getters, so a float16 model lowered for Windows
links.

## CUDA programs load without torch on machines with no libgomp

On Linux, Inductor adds OpenMP (`-fopenmp`, `-lgomp`) to every link.
Unless the compiler passes `--as-needed` to the linker, as Ubuntu's GCC
does by default, the compiled library lists `libgomp.so.1` as a
dependency even when it calls no OpenMP function. PyTorch ships a
libgomp, so this went unnoticed while torch was imported. A process
without torch failed to load the program with `libgomp.so.1: cannot open
shared object file` on any machine with no system libgomp, such as a
minimal container.

The CUDA backend now links with `-Wl,--as-needed`, so the linker keeps
only the libraries the program uses. A program that does call OpenMP
keeps libgomp. PyTorch already does the same for MPS builds on macOS,
and pytorch/pytorch#202902 adds it to Inductor for Linux. The torch-free
tests in `test_pybindings_cuda_dlpack.py` now run without a system
libgomp, and the ones that check which libraries got loaded also check
for libgomp. On an A100, a small MLP and CNN exported with this change
load without torch and match eager where the loader cannot find libgomp.

This patch, and the one that lets Triton templates run on GPUs with few
SMs, now stay in place while any thread lowers. Before, when two threads
lowered at once, the first one to finish removed the patch from the
other, and the rest of that compile ran without it.

## A failed CUDA load no longer leaves its library in the temporary
directory

To load a method, the CUDA backend writes the program's compiled library
to a temporary file, such as `/tmp/executorch_cuda_<pid>_0.so`. Only
`destroy()` removed that file, and `destroy()` never runs when the load
fails, so every load that failed after writing the file left it behind.
A scope guard now removes the file unless the load succeeds.
`test_missing_weights_blob.py` now checks that a load that fails for a
missing weights blob leaves nothing in the temporary directory. Without
the fix, that check fails.

The XNNPACK high-rank permute fix moved to #24380.

## The Triton SDPA leaves keys past the end out of the softmax

With `triton_kernel_mode` on (the default), the CUDA backend runs
`scaled_dot_product_attention` as its Triton kernel, `triton::sdpa`. For
power-of-two head sizes, its main kernel reads keys in tiles of 32, 64
or 128. When the number of keys is not a multiple of the tile, the last
tile reaches past the last key. The kernel loaded those positions as
zeros but never removed them from the softmax, so without an attention
mask or a causal mask each one got a score of 0 and took part of the
attention weight. Calls that pass `kv_len` with neither a mask nor
`is_causal`, such as the DFlash draft model's global attention layers,
had the same problem. On an A100, with the query at 0 and the values at
1, where the right output is 1, one key gave 0.031, 33 keys gave 0.516
and 80 keys gave 0.625. With random inputs and one key, the output was
off by 3.4 when the largest value was 3.67. All of these now match eager
PyTorch.

The kernel now gives every position past the last key, or past `kv_len`,
a score of minus infinity, as the other attention kernels in `sdpa.py`
already do. The first version of the kernel did this, and a later
rewrite dropped it. Checking every tile cost up to 8% where no tile
reaches past the end, so only calls that need the check pay for it. A
mask or a causal bound already drops those keys, so calls with one skip
it. The launcher also passes the largest tile size that divides the
number of keys, and tiles that divide it skip the check at compile time.
So calls with a mask or a causal bound, and tiles that divide a key
count known at export time, compile to the same code as before. Without
a mask or a causal bound, a `kv_len` or a symbolic key count checks
every tile. `test_triton_sdpa.py` drops its note that called this a
kernel limitation, runs its basic test with a single key, and runs every
tile without a mask over 80 and 96 keys and over a `kv_len` of 80. These
fail without the fix.

## `scatter` with a tensor source lowers to the CUDA backend

Lowering `x.scatter(dim, index, src)` failed with a `missing fallback
kernels` error that names `aoti_torch_cuda_scatter_src_out` when `src`
is bfloat16, int64 or bool. Inductor writes a scatter from a tensor
itself only in a dtype it can add atomically, so it leaves int64, bool
and, on GPUs older than sm_90, bfloat16 to an ATen kernel, and every
dtype in deterministic mode. A mixture-of-experts layer that writes its
expert outputs back with `torch.zeros_like(h).scatter(1, index, src)` in
bfloat16 hit this on an A100.

A new shim next to the `scatter_reduce` one implements it with the same
checks, the same sorting of the writes by the element they land on, and
the same kernel. The kernel gets a new mode in which only the last write
to each element stores its value. So when values land on the same
element, the last one in index order wins, as ATen does in deterministic
mode; ATen's default CUDA kernel keeps any one of them. As in that
default kernel, an index out of range, negative ones included, triggers
a device-side assert. In deterministic mode, ATen's scatter counts a
negative index from the end instead. The shim takes float, double, half,
bfloat16, the integer dtypes and bool. Like ATen, both scatter shims now
take an int32 index as well as an int64 one. Mostly because of the sort,
the shim is about 10 to 15 times slower than ATen's default kernel for a
mixture-of-experts style bfloat16 scatter of 4 to 34 million values on
an A100. The Windows import library `aoti_cuda_shims.lib` and the
wheel's shared library check now list the new shim. On an A100 with
PyTorch 2.14 and 2.15, scatters in float32, float16, bfloat16, int64 and
bool lower, run and match eager PyTorch exactly. So does a small
mixture-of-experts layer built the same way.
`test_scatter_with_tensor_source` fails without the fix, and its int32
index case fails when the shims take only int64.

## Review order

Start with the CUDA graph changes: `cuda_backend.cpp` and
`cuda_delegate_handle.h` in `backends/cuda/runtime`, the mask and
`.item()` shims in `runtime/shims/index.cu` and `memory.cpp`, and their
tests in `test_output_stride_rearrange.py`. Then the lowering side: the
passes in `backends/cuda/passes`, the decompositions and compile-time
patches in `backends/cuda/cuda_backend.py`, and the folding switch in
`backends/aoti/aoti_backend.py`. Then the scatter and scatter_reduce
shims in `index.cu` with `test_index_shims.py`. The remaining commits
are small follow-ups that stand on their own.

## Testing

On an A100, with the CUDA backend built from this branch:

- `test_index_shims.py`, `test_output_stride_rearrange.py`,
`test_cuda_export.py`, `test_antialiased_resize.py`,
`test_merge_ptes.py` and `test_cuda_weight_metadata.py` pass.
- With the backend built from main, the new CUDA graph tests fail.
- The replay error names the dim, for example "input 0 has size 4 in dim
1, captured with 3".
- `test_cuda_kv_cache` passes, and fails when `release()` keeps the
captured input shapes.
- With the min and max replaced by a plain `if`,
`test_bicubic_antialias_resize` fails.
- With the new data name refusal removed, or moved back behind the
compile, the data name test fails.
- A `torch.cond` on a host-memory flag matches eager on every call; with
a graph it returned the captured branch.
- A random program run from two processes started together drew the same
numbers in 2 of 3 tries before the seed change, and different ones in 3
of 3 after.
- Mask index programs exported from this branch match eager PyTorch on
every call, both on a runtime built from main with CUDA graphs off and
on this branch's runtime with CUDA graphs on.
- The shared shim unit tests pass in a standalone googletest build.
- `test_replace_masked_copy_with_where.py` passes, and fails when the
pass does nothing, is not registered, or skips its accumulate,
same-mask, boolean mask, shared picks or count check. It writes into a
destination filled with 7, so it fails if `where` loses the values the
mask does not pick. A contiguous destination with a channels_last
source, or with a channels_last mask, keeps its `index_put`; before, the
rewrite failed to lower when a later `view(-1)` needed the destination's
layout. It also fails when the pass walks only the top-level graph,
since a masked copy in a `torch.cond` branch then keeps its index, and
when it takes two draws of a random mask for the same mask. Two masks
computed with over a thousand ops each raised RecursionError before. A
check that reads the index's count but tests another size keeps the
index; before, the pass removed it.
- A masked copy lowered through the CUDA partitioner matches eager for
masks that pick some, none and all positions. The test records the graph
the pass returns, and fails when the pass is not registered or leaves
the index in place.
- A module that adds one to a device buffer on every call and reads a
mask's count returns the call number on every call, so the capture call
that runs again without a graph updates the buffer once. With the retry
made to run the call twice, the capture call returns one too many. With
PyTorch 2.14 it returned one on every call even without a graph, because
Inductor folded the written buffer into a fill; it passes now with 2.14
and with 2.15 and 2.16 nightlies.
- On the tiny Gemma 4 decoder from the Gemma 4 31B example's tests,
which writes a KV cache, with PyTorch 2.14 on an A100: with the folding
off, export took 193 s against 190.5 s with it on, a decode call took
945 µs against 1019 µs (medians of three exports and three runs on a
busy machine), and the 41 generated tokens were the same.
- The mask tests check that the runtime log reports one failed capture,
so they fail when CUDA graphs are off or a failed capture is tried
again. `test_host_read_runs_without_a_graph` also runs a module that
scales by a `.item()` read; without the capture check in the item shims,
the process aborts.
- With the debug check on the phase before the retry turned into a
release check, the mask tests pass.
- `test_distribution_argument_check` passes with PyTorch 2.14 and fails
with "device-side assert triggered" without the new pass. It also checks
that the backend lists the pass, since without the pass the export fails
only when autotuning happens to trip the assert. It writes the
`MultivariateNormal` check as `torch._assert_async`, since PyTorch 2.14
cannot export the distribution itself. It also checks that the pass
drops the check and the math that only fed it. A Gaussian policy head
with hidden sizes 16, 64 and 256 exports, runs, and matches eager
PyTorch.
- A float64 buffer and a folded float64 constant load and match eager
PyTorch. Before, both failed to load. `test_float64_weight_loads` fails
without the fix.
- `test_reshape_copy` in `test_cuda_export.py` fails without the fix.
The one in `test_index_shims.py` runs `_reshape_copy` on a transposed, a
sliced and a contiguous input and matches eager PyTorch.
- `test_varlen_attn` in `test_cuda_export.py` fails without the fix. It
lowers a program that flattens the output, compares the decomposition
with the op on CUDA only (on ROCm, varlen_attn runs AOTriton, which
lines an explicit window up with the start of each sequence, while the
decomposition follows Flash attention on CUDA, which lines it up with
the end), including a window in self-attention and NaN values outside
the key sequences, and checks that a window the Flash attention versions
disagree on raises. It also fails when the output is left
non-contiguous, when the window check is removed or refuses every such
window, and when the values outside the key sequences are not zeroed.
Both `test_varlen_attn` tests include queries and keys filled with 1024,
where every key gets the same weight; computing the weights as
`exp(scores - logsumexp)` fails them, since at that size float32 rounds
the log-sum-exp to a whole number and the weights no longer add up to
one. The `test_varlen_attn` in `test_index_shims.py` lowers and runs
programs that call `varlen_attn` with no window, a causal mask and the
windows (8, 0) and (4, 2), flatten each output, and take `cu_seq_q` and
`cu_seq_k` only at run time. They match eager PyTorch.
- `test_missing_weights_blob.py`, `test_cuda_weight_reuse.py` and
`test_device_placement.py` pass.
- `test_scatter_reduce` now runs the sum too, which for int64 goes to
the new shim. The test that a missing scatter kernel fails at export now
tries to lower a scatter of a scalar in deterministic mode. The backend
still lacks that kernel, so the test still checks the scatter fallback
now that the scatter_reduce and scatter shims exist. It fails when the
check stops watching that fallback.
`test_scatter_mean_counts_in_its_dtype` fails with the old count and
when a count that wraps to zero is not turned into one.
`test_float64_and_float16_values` (a sort and `.item()` in each dtype)
and the float64 masked accumulate fail with the old shims, and all pass
now. The `.item()` scale is 1.5, so that test also fails without the
patch that passes a float to Triton kernels as a float.
- `test_scatter_reduce` and `test_sort_shim.py` pass.
`test_scatter_reduce` fails without the fix, and also when the shim
ignores `include_self`, rounds the integer mean toward zero, or lets a
NaN lose in amax and amin. `test_scatter_strides` scatters along the
middle dim of a 3-D tensor, given as a negative dim, from a strided
source and from transposed operands, and fails when the shims walk only
the last two dims. `test_scatter_reduce_edge_cases` covers a bool sum,
int32 and half products that wrap and overflow, and an empty index.
`test_scatter_reduce_sum_adds_in_float` sums 1000 half and bfloat16
values in deterministic mode, and fails when the sum rounds to the dtype
at every step. `test_scatter_reduce_replays_in_a_graph` captures a
`scatter_reduce` program in a CUDA graph and matches eager on every
replay.
- The weight cache, runtime tensor and shim unit tests pass in a
standalone googletest build. Without the fix, the weight cache test of a
float64 entry fails.

- `test_small_gpu_override_survives_a_thread_that_leaves_first` overlaps
two threads inside the small-GPU patch, and fails with the old save and
restore.

- With the seed copy made to fail once, the random shim logs it, fails
that call, and seeds on the next one; before, it went on with an
unseeded state.

Not tested: a failed first graph launch (it cannot be triggered from a
test), a `std::random_device` that throws, Windows, macOS
case-insensitive file systems, the buffer folding change on Metal, and
the ROCm build. No test covers the seed change, since a test cannot make
two processes start in the same second.

### Checked on real robot policies

On the release/1.6 stack with this change, on A100 and H100:
- SmolVLA on the CUDA backend: one device-to-host copy per action chunk
(only the action download), matching eager within the usual tolerance.
- A Gaussian policy whose `torch.distributions` check used to stop the
CUDA export: exports and runs, 0.76 ms per chunk.
- A float64 weight that used to fail at load with "Malformed CUDA FQN
weight metadata": loads and runs, max difference 0.
- Four processes started together: four different random draws, against
two out of four before.

(cherry picked from commit 59541e1)
@pytorch-bot

pytorch-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24512

Note: Links to docs will display an error until the docs builds have been completed.

⏳ No Failures, 357 Pending

As of commit d882326 with merge base 4db778e (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@JacobSzwejbka
JacobSzwejbka merged commit 5366fa1 into release/1.6 Nov 3, 2026
437 of 449 checks passed
@JacobSzwejbka
JacobSzwejbka deleted the cherry-pick-24326-by-pytorch_bot_bot_ branch November 3, 2026 18:27
@shoumikhin
shoumikhin deployed to upload-benchmark-results November 4, 2026 00:24 — with GitHub Actions Active

This branch was successfully deployed

1 active and 1 inactive deployments
upload-benchmark-results — d8823260 Deployed Nov 4, 2026 by shoumikhin via upload-benchmark-results #21829
cadence — d8823260 Deployed Nov 3, 2026 by shoumikhin via hifi-op-test / hifi4 #36304
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants