Skip to content

Commit bec367b

Browse files
zonglinpengfacebook-github-bot
authored andcommitted
Keep cat_out's nnlib input arrays off variable-length stack arrays (#24319)
Summary: `impl::G3::native::cat_out` declared variable-length arrays sized by the number of inputs (about 76 B per input) before it checked whether the `xa_nn_cat` path applies. A 256-input int64 cat in RT-DETR (`rtdetr_r18vd`, int8) therefore grew the frame by about 19 KB on the DSP thread that runs the operator, overflowed that thread's 7488 B stack and tripped the Zephyr stack sentinel on the first inference. Raising the stack did not help: 8 KB, 11.5 KB and 16 KB stacks all overflowed. The nnlib path now runs only for same-dtype, supported-dtype cats with at most 16 inputs, and uses fixed-size arrays. Larger cats take the existing generic loop. The frame is a fixed 1440 B: the number of dynamic stack adjustments in `cat_out` goes from 11 to 0 in the built DSP image. Adds a 40-input `cat` test case so the fallback branch is covered. The 256 inputs are one tensor repeated: every entry of that cat references the same value and buffer. RT-DETR's decoder gathers with `topk_ind.unsqueeze(-1).repeat(1, 1, d_model)`, and Cadence's `ReplaceRepeatWithCatPass` lowers a `repeat` as a cat of the same tensor, so `d_model` = 256 becomes a 256-entry cat. The kernel is still the place to fix the overflow: the arrays are sized by entry count, not by distinct tensors, and a 100-entry cat of distinct inputs (EfficientDet-Lite0's full graph) would need about 7.6 KB of them, more than the whole 7488 B stack. Lowering `repeat` as a broadcast copy would remove RT-DETR's long list but leave genuinely wide cats exposed, so it is at most a later optimization. Differential Revision: D128236590
1 parent a6029d2 commit bec367b

1 file changed

Lines changed: 34 additions & 30 deletions

File tree

‎backends/cadence/fusion_g3/operators/op_cat.cpp‎

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ namespace impl {
3333
namespace G3 {
3434
namespace native {
3535

36+
namespace {
37+
// Upper bound on inputs for the xa_nn_cat path, whose per-input arrays live on
38+
// the DSP thread stack. Larger cats take the generic loop.
39+
constexpr size_t kMaxNnlibCatInputs = 16;
40+
} // namespace
41+
3642
Tensor& cat_out(
3743
KernelRuntimeContext& ctx,
3844
ArrayRef<Tensor> tensors,
@@ -42,7 +48,7 @@ Tensor& cat_out(
4248
dim += out.dim();
4349
}
4450

45-
int kTensorDimensionLimit = executorch::runtime::kTensorDimensionLimit;
51+
constexpr int kTensorDimensionLimit = executorch::runtime::kTensorDimensionLimit;
4652

4753
#ifdef OP_ARG_CHECK
4854

@@ -76,34 +82,6 @@ Tensor& cat_out(
7682
return out;
7783
}
7884

79-
const signed char* inp_tensors[tensors.size()];
80-
const int* inp_tensors_shapes[tensors.size()];
81-
82-
int inp_shapes_size[tensors.size()];
83-
84-
int temp_sizes[tensors.size()][kTensorDimensionLimit];
85-
ArrayRef<Tensor::SizesType> temp_size;
86-
87-
for (int i = 0; i < tensors.size(); i++) {
88-
inp_tensors[i] = tensors[i].const_data_ptr<signed char>();
89-
temp_size = tensors[i].sizes();
90-
91-
for (int j = 0; j < temp_size.size(); j++) {
92-
temp_sizes[i][j] = temp_size[j];
93-
}
94-
inp_tensors_shapes[i] = temp_sizes[i]; // input shapes
95-
inp_shapes_size[i] = temp_size.size(); // number of input dimensions
96-
}
97-
98-
signed char* out_data = out.mutable_data_ptr<signed char>();
99-
100-
const ArrayRef<Tensor::SizesType> out_size = out.sizes();
101-
int out_shapes[kTensorDimensionLimit];
102-
for (int i = 0; i < out_size.size(); i++) // output shapes
103-
{
104-
out_shapes[i] = out_size[i];
105-
}
106-
10785
bool all_same_dtype = true;
10886
for (int i = 0; i < tensors.size(); i++) {
10987
if (out.scalar_type() != tensors[i].scalar_type()) {
@@ -120,7 +98,33 @@ Tensor& cat_out(
12098
out.scalar_type() == ScalarType::Byte ||
12199
out.scalar_type() == ScalarType::Float;
122100

123-
if (all_same_dtype && supported_dtype) {
101+
if (all_same_dtype && supported_dtype &&
102+
tensors.size() <= kMaxNnlibCatInputs) {
103+
const signed char* inp_tensors[kMaxNnlibCatInputs];
104+
const int* inp_tensors_shapes[kMaxNnlibCatInputs];
105+
int inp_shapes_size[kMaxNnlibCatInputs];
106+
int temp_sizes[kMaxNnlibCatInputs][kTensorDimensionLimit];
107+
108+
for (int i = 0; i < tensors.size(); i++) {
109+
inp_tensors[i] = tensors[i].const_data_ptr<signed char>();
110+
const ArrayRef<Tensor::SizesType> temp_size = tensors[i].sizes();
111+
112+
for (int j = 0; j < temp_size.size(); j++) {
113+
temp_sizes[i][j] = temp_size[j];
114+
}
115+
inp_tensors_shapes[i] = temp_sizes[i]; // input shapes
116+
inp_shapes_size[i] = temp_size.size(); // number of input dimensions
117+
}
118+
119+
signed char* out_data = out.mutable_data_ptr<signed char>();
120+
121+
const ArrayRef<Tensor::SizesType> out_size = out.sizes();
122+
int out_shapes[kTensorDimensionLimit];
123+
for (int i = 0; i < out_size.size(); i++) // output shapes
124+
{
125+
out_shapes[i] = out_size[i];
126+
}
127+
124128
XT_KERNEL_CHECK(
125129
ctx,
126130
out,

0 commit comments

Comments
 (0)