Feature description
Follow-up to #4477. IndexingUpdateOp now has all five variants (Assign, Add, Mul, Min, Max) and both Tensor::scatter and Tensor::select_assign take it, but most backends still only implement a subset and fall through to unimplemented!() at runtime.
scatter_nd / select-family aside, here is the current state on main for the element-wise ops:
scatter (element-wise)
| Backend |
Assign |
Add |
Mul |
Min |
Max |
| ndarray |
yes |
yes |
yes |
no |
no |
| flex |
no |
yes |
yes |
no |
no |
| cubecl (wgpu/cuda/metal/rocm) |
no |
yes |
yes |
no |
no |
| tch |
no |
yes |
yes |
no |
no |
| autodiff |
yes |
yes |
yes |
no |
no |
| bool (bridge) |
no |
yes (or) |
no |
no |
no |
select_assign
| Backend |
Assign |
Add |
Mul |
Min |
Max |
| ndarray |
yes |
yes |
yes |
no |
no |
| flex |
no |
yes |
yes |
no |
no |
| cubecl |
no |
yes |
yes |
no |
no |
| tch |
no |
yes |
yes |
no |
no |
| bool (bridge) |
no |
yes (or) |
no |
no |
no |
scatter_nd (for contrast, already complete)
All five reductions are implemented in ndarray, flex and cubecl. This is the shape the element-wise ops should reach.
So the concrete gaps are:
Assign missing in burn-flex, burn-cubecl and burn-tch for both scatter and select_assign (ndarray has it).
Min / Max missing in every backend for both scatter and select_assign.
The default trait bodies in burn-backend/src/backend/ops/tensor.rs (float_scatter, float_select_assign) forward only Add, so a backend that does not override them silently inherits Add-only support and panics at runtime rather than failing to compile.
Feature motivation
This is what blocks the ONNX side from finishing tracel-ai/burn-onnx#486.
ONNX ScatterElements has five reduction modes that map one-to-one onto IndexingUpdateOp. ScatterND can already lower all five to scatter_nd because that op is complete. ScatterElements cannot:
| ONNX reduction |
Lowering today in burn-onnx |
add |
scatter(..., Add) |
mul |
scatter(..., Mul) |
none |
gather-diff-scatter-add workaround, because Assign is unavailable on the default CPU backend (flex) |
max |
data + (updates - gathered).clamp_min(0) scattered with Add |
min |
data + (updates - gathered).clamp_max(0) scattered with Add |
The none/max/min rewrites are correct for unique indices but diverge from ONNX semantics when indices contains duplicates, where ONNX defines the reduction as applied repeatedly. They also do an extra gather pass over the data. Native Assign/Min/Max would remove both problems and let all five modes be a single scatter() call.
Bool tensors are a smaller related gap: the bridge only maps Add to bool_scatter_or, and or cannot clear a bit, so ONNX ScatterElements/ScatterND on bool has to round-trip through i64.
Suggest a Solution
- Implement
Assign for scatter and select_assign in burn-flex, burn-cubecl and burn-tch.
- Implement
Min / Max for scatter and select_assign across the backends. The cubecl side already has BinaryMinOp / BinaryMaxOp wired up for scatter_nd_kernel, so the element-wise scatter_kernel should be able to reuse them.
- Extend
burn-autodiff to cover whichever of these are differentiable (Min/Max gradients route to the argmin/argmax side, same as scatter_nd).
- Consider making the missing combinations a compile-time or documented capability rather than a runtime
unimplemented!(), so downstream code generators can pick a supported lowering instead of discovering the gap on the first forward pass.
Feature description
Follow-up to #4477.
IndexingUpdateOpnow has all five variants (Assign,Add,Mul,Min,Max) and bothTensor::scatterandTensor::select_assigntake it, but most backends still only implement a subset and fall through tounimplemented!()at runtime.scatter_nd/select-family aside, here is the current state onmainfor the element-wise ops:scatter(element-wise)or)select_assignor)scatter_nd(for contrast, already complete)All five reductions are implemented in ndarray, flex and cubecl. This is the shape the element-wise ops should reach.
So the concrete gaps are:
Assignmissing inburn-flex,burn-cubeclandburn-tchfor bothscatterandselect_assign(ndarray has it).Min/Maxmissing in every backend for bothscatterandselect_assign.The default trait bodies in
burn-backend/src/backend/ops/tensor.rs(float_scatter,float_select_assign) forward onlyAdd, so a backend that does not override them silently inherits Add-only support and panics at runtime rather than failing to compile.Feature motivation
This is what blocks the ONNX side from finishing tracel-ai/burn-onnx#486.
ONNX
ScatterElementshas five reduction modes that map one-to-one ontoIndexingUpdateOp.ScatterNDcan already lower all five toscatter_ndbecause that op is complete.ScatterElementscannot:addscatter(..., Add)mulscatter(..., Mul)noneAssignis unavailable on the default CPU backend (flex)maxdata + (updates - gathered).clamp_min(0)scattered withAddmindata + (updates - gathered).clamp_max(0)scattered withAddThe
none/max/minrewrites are correct for unique indices but diverge from ONNX semantics whenindicescontains duplicates, where ONNX defines the reduction as applied repeatedly. They also do an extragatherpass over the data. NativeAssign/Min/Maxwould remove both problems and let all five modes be a singlescatter()call.Bool tensors are a smaller related gap: the bridge only maps
Addtobool_scatter_or, andorcannot clear a bit, so ONNXScatterElements/ScatterNDon bool has to round-trip throughi64.Suggest a Solution
Assignforscatterandselect_assigninburn-flex,burn-cubeclandburn-tch.Min/Maxforscatterandselect_assignacross the backends. The cubecl side already hasBinaryMinOp/BinaryMaxOpwired up forscatter_nd_kernel, so the element-wisescatter_kernelshould be able to reuse them.burn-autodiffto cover whichever of these are differentiable (Min/Maxgradients route to the argmin/argmax side, same asscatter_nd).unimplemented!(), so downstream code generators can pick a supported lowering instead of discovering the gap on the first forward pass.