Skip to content

Commit e875b0f

Browse files
authored
PDX fast path for spatial mean.out of channels-last tensors (#24164)
Summary: A float `mean` over the spatial dims of a channels-last tensor, `x[N,H,W,C].mean((1,2))` (global average pooling), runs `mean.out` through NNLib's `xa_nn_mean_f32_f32`. For this reduction (dims {1,2} of an `[N,H,W,C]` tensor) NNLib uses a strided generic loop that costs ~10 cycles per element, which makes it a hotspot in networks that pool this way. Add `mean_nhwc_spatial_simd` in front of the NNLib call. For a float `[N,H,W,C]` tensor reduced over dims {1,2} (negative dims accepted, any `keepdim`), it zeroes the output row for each batch element, adds every position row into it with `PDX_ADD_MXF32` (four channels per vector, positions in order), then divides by the position count with `PDX_DIV_MXF32`. The per-channel summation order is the sequential position order, and the division is an exact IEEE division. PDX vector accesses must be 16-byte aligned on Fusion G3 (an unaligned access raises `LoadStoreAlignmentCause`), so the fast path is only taken when both buffers are 16-byte aligned and `C % 4 == 0`; every other case falls through to the existing NNLib / portable paths unchanged. On a Fusion G3 core, `mean.out` on these shapes is ~13x faster, with unchanged output. Differential Revision: D127346731
1 parent 10b5018 commit e875b0f

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,72 @@ int prepare_data(
5959
return num_axis_dims;
6060
}
6161

62+
namespace {
63+
64+
constexpr size_t kSimdLanes = 4;
65+
constexpr uintptr_t kSimdAlignmentBytes = 16;
66+
67+
// Spatial mean of a channels-last float tensor [N, H, W, C] over dims {1, 2},
68+
// the pooling of squeeze-and-excitation blocks. NNLib reduces it with a strided
69+
// generic loop; here every position row is added into the output row with PDX
70+
// vectors. PDX vector accesses must be 16-byte aligned, so unaligned tensors
71+
// and channel counts that are not a multiple of the lane width return false.
72+
bool mean_nhwc_spatial_simd(
73+
const Tensor& in,
74+
optional<ArrayRef<int64_t>> dim_list,
75+
Tensor& out) {
76+
if (!dim_list.has_value() || dim_list.value().size() != 2 || in.dim() != 4) {
77+
return false;
78+
}
79+
int64_t first_dim = dim_list.value()[0];
80+
int64_t second_dim = dim_list.value()[1];
81+
first_dim += first_dim < 0 ? in.dim() : 0;
82+
second_dim += second_dim < 0 ? in.dim() : 0;
83+
if (!((first_dim == 1 && second_dim == 2) ||
84+
(first_dim == 2 && second_dim == 1))) {
85+
return false;
86+
}
87+
88+
const size_t batch = in.size(0);
89+
const size_t positions = static_cast<size_t>(in.size(1)) * in.size(2);
90+
const size_t channels = in.size(3);
91+
if (positions == 0 || channels == 0 || channels % kSimdLanes != 0 ||
92+
static_cast<size_t>(out.numel()) != batch * channels) {
93+
return false;
94+
}
95+
96+
const float* p_in = in.const_data_ptr<float>();
97+
float* p_out = out.mutable_data_ptr<float>();
98+
if (reinterpret_cast<uintptr_t>(p_in) % kSimdAlignmentBytes != 0 ||
99+
reinterpret_cast<uintptr_t>(p_out) % kSimdAlignmentBytes != 0) {
100+
return false;
101+
}
102+
103+
const size_t channel_vectors = channels / kSimdLanes;
104+
const float count = static_cast<float>(positions);
105+
const xb_vecMxf32 v_zero{0.0f, 0.0f, 0.0f, 0.0f};
106+
const xb_vecMxf32 v_count{count, count, count, count};
107+
for (size_t n = 0; n < batch; ++n) {
108+
auto v_out = reinterpret_cast<xb_vecMxf32*>(p_out + n * channels);
109+
for (size_t v = 0; v < channel_vectors; ++v) {
110+
v_out[v] = v_zero;
111+
}
112+
for (size_t p = 0; p < positions; ++p) {
113+
auto v_in = reinterpret_cast<const xb_vecMxf32*>(
114+
p_in + (n * positions + p) * channels);
115+
for (size_t v = 0; v < channel_vectors; ++v) {
116+
v_out[v] = PDX_ADD_MXF32(v_out[v], v_in[v]);
117+
}
118+
}
119+
for (size_t v = 0; v < channel_vectors; ++v) {
120+
v_out[v] = PDX_DIV_MXF32(v_out[v], v_count);
121+
}
122+
}
123+
return true;
124+
}
125+
126+
} // namespace
127+
62128
Tensor& mean_out(
63129
KernelRuntimeContext& ctx,
64130
const Tensor& in,
@@ -100,6 +166,10 @@ Tensor& mean_out(
100166
if (in.dim() > kNnlibMaxDim)
101167
optimized = false;
102168

169+
if (optimized && mean_nhwc_spatial_simd(in, dim_list, out)) {
170+
return out;
171+
}
172+
103173
if (optimized) {
104174
float* __restrict__ p_out = out.mutable_data_ptr<float>();
105175
const float* __restrict__ p_inp =

0 commit comments

Comments
 (0)