Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
.gitignore
my_contrib
executorch_overview.html

# System files
.DS_Store

Expand Down
20 changes: 16 additions & 4 deletions kernels/portable/cpu/op_log_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& log_softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_log_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& log_softmax_out(
size,
stride);

CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -75,7 +85,9 @@ Tensor& log_softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return val_in - max_in - temp_sum;
return static_cast<CTYPE>(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
31 changes: 23 additions & 8 deletions kernels/portable/cpu/op_mean.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/kernel_ops_util.h>
#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
Expand DownExpand Up@@ -58,17 +60,24 @@ Tensor& mean_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "mean.out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior.
ET_SWITCH_FLOATHBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
const CTYPE denom = static_cast<CTYPE>(reduce_size);
const ACC denom = static_cast<ACC>(reduce_size);
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc / denom;
out_data[i] = static_cast<CTYPE>(acc / denom);
}
});
return out;
Expand All@@ -83,19 +92,25 @@ Tensor& mean_dim_out(
static constexpr const char op_name[] = "mean.out";
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_FLOATHBF16_TYPES(out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const size_t num = get_reduced_dim_product(in, dim_list);
const bool success = parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) { return static_cast<CTYPE_OUT>(v); },
[](CTYPE_OUT outv, CTYPE_OUT acc) { return acc + outv; },
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum / static_cast<float>(num);
out_data[out_ix] =
static_cast<CTYPE_OUT>(sum / static_cast<float>(num));
}
});
ET_KERNEL_CHECK_MSG(ctx, success, Internal, , "parallel_for failed");
Expand Down
22 changes: 18 additions & 4 deletions kernels/portable/cpu/op_softmax.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@
*/

#include <cmath>
#include <type_traits>

#include <executorch/kernels/portable/cpu/util/activation_ops_util.h>
#include <executorch/kernels/portable/cpu/util/functional_util.h>
Expand DownExpand Up@@ -42,8 +43,16 @@ Tensor& softmax_out(
// Adjust for negative dim
dim = dim < 0 ? dim + nonzero_dim(in) : dim;

// For half-precision inputs, the exp-sum is accumulated in float to avoid
// saturation (BFloat16 saturates near 256, Half near 2048). Matches ATen's
// acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_FLOATHBF16_TYPES(
in.scalar_type(), ctx, "_softmax.out", CTYPE, [&]() {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* const in_data = in.const_data_ptr<CTYPE>();
CTYPE* const out_data = out.mutable_data_ptr<CTYPE>();

Expand All@@ -61,11 +70,12 @@ Tensor& softmax_out(
size,
stride);

const CTYPE temp_sum = apply_unary_map_reduce_fn<CTYPE, CTYPE>(
const ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
[max_in](const CTYPE val_in) {
return std::exp(val_in - max_in);
return std::exp(
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
},
[](const CTYPE mapped_in, CTYPE val_accum) {
[](const ACC mapped_in, ACC val_accum) {
return val_accum + mapped_in;
},
in_data + base,
Expand All@@ -74,7 +84,11 @@ Tensor& softmax_out(

apply_unary_map_fn(
[max_in, temp_sum](const CTYPE val_in) {
return std::exp(val_in - max_in) / temp_sum;
return static_cast<CTYPE>(
std::exp(
static_cast<ACC>(val_in) -
static_cast<ACC>(max_in)) /
temp_sum);
},
in_data + base,
out_data + base,
Expand Down
32 changes: 21 additions & 11 deletions kernels/portable/cpu/op_sum.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@
*/
#include <c10/util/irange.h>

#include <type_traits>

#include <executorch/kernels/portable/cpu/util/reduce_util.h>
#include <executorch/runtime/kernel/kernel_includes.h>
#include <executorch/runtime/platform/assert.h>
Expand DownExpand Up@@ -60,16 +62,23 @@ Tensor& sum_dim_out(

// @lint-ignore CLANGTIDY facebook-hte-CArray
static constexpr const char op_name[] = "sum.IntList_out";
// For half-precision inputs, accumulate in float to avoid saturation.
// Matches ATen's acc_type behavior. See also op_grid_sampler_2d.cpp.
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE, executorch::aten::Half> ||
std::is_same_v<CTYPE, executorch::aten::BFloat16>,
float,
CTYPE>;
const CTYPE* in_data = in.const_data_ptr<CTYPE>();
CTYPE* out_data = out.mutable_data_ptr<CTYPE>();
for (int64_t i = 0; i < outer_size; i++) {
const CTYPE* row = in_data + i * reduce_size;
CTYPE acc = 0;
ACC acc = 0;
for (int64_t j = 0; j < reduce_size; j++) {
acc += row[j];
}
out_data[i] = acc;
out_data[i] = static_cast<CTYPE>(acc);
}
});
return out;
Expand DownExpand Up@@ -108,23 +117,24 @@ Tensor& sum_dim_out(
ET_SWITCH_REALHBBF16_TYPES(in.scalar_type(), ctx, op_name, CTYPE_IN, [&] {
ET_SWITCH_REALHBBF16_TYPES(
out.scalar_type(), ctx, op_name, CTYPE_OUT, [&] {
using ACC = std::conditional_t<
std::is_same_v<CTYPE_OUT, executorch::aten::Half> ||
std::is_same_v<CTYPE_OUT, executorch::aten::BFloat16>,
float,
CTYPE_OUT>;
CTYPE_OUT* out_data = out.mutable_data_ptr<CTYPE_OUT>();
const bool success =
parallel_for_each_reduce_over_dim_list_output_index(
in, dim_list, out, [&](const auto begin, const auto end) {
for (const auto out_ix : c10::irange(begin, end)) {
CTYPE_OUT sum = 0;
ACC sum = 0;
if (plan.has_value()) {
sum = plan->execute<CTYPE_IN, CTYPE_OUT>(
[](CTYPE_IN v) {
return static_cast<CTYPE_OUT>(v);
},
[](CTYPE_OUT outv, CTYPE_OUT acc) {
return acc + outv;
},
sum = plan->execute<CTYPE_IN, ACC>(
[](CTYPE_IN v) { return static_cast<ACC>(v); },
[](ACC outv, ACC acc) { return acc + outv; },
out_ix);
}
out_data[out_ix] = sum;
out_data[out_ix] = static_cast<CTYPE_OUT>(sum);
}
});
ET_KERNEL_CHECK_MSG(
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_log_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -369,6 +369,19 @@ TEST_F(OpLogSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpLogSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation, the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~-log(256) instead of -log(512).
// atol=1e-1 can catch pre-fix error: |log(512) - log(256)| = log(2)
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_log_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, -std::log(static_cast<float>(N)));
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-1);
}

TEST_F(OpLogSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_mean_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,35 @@ void OpMeanOutTest::
test_mean_dim_out_bool<ScalarType::Double>();
}

TEST_F(OpMeanOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256, giving
// 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16, giving 256/512 = 0.5 instead of 1.0.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_mean_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, 1.0f);
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpMeanOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
13 changes: 13 additions & 0 deletions kernels/test/op_softmax_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -251,6 +251,19 @@ TEST_F(OpSoftmaxOutTest, SimpleGeneratedCase) {
EXPECT_TENSOR_CLOSE(out, expected_result);
}

TEST_F(OpSoftmaxOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512: without fp32 accumulation the exp-sum saturates at BFloat16's
// precision limit (~256), so the output is ~1/256 instead of 1/512.
// 1e-3 is tight enough to catch pre-fix error: |1/256 - 1/512| ≈ 0.00195
constexpr int N = 512;
Tensor x = tf.zeros({1, N});
Tensor out = tf.zeros({1, N});
op_softmax_out(x, /*dim=*/1, /*half_to_float=*/false, out);
Tensor expected = tf.full({1, N}, 1.0f / N);
EXPECT_TENSOR_CLOSE_WITH_TOL(out, expected, /*rtol=*/1e-5, /*atol=*/1e-3);
}

TEST_F(OpSoftmaxOutTest, DynamicShapeUpperBoundSameAsExpected) {
TensorFactory<ScalarType::Float> tf;

Expand Down
29 changes: 29 additions & 0 deletions kernels/test/op_sum_test.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,35 @@ class OpSumOutTest : public OperatorTest {
}
};

TEST_F(OpSumOutTest, BFloat16GenericPathAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// Reducing dim=0 of {512, 1} is not the last dim, so the generic path is
// taken. Without fp32 accumulation the sum saturates at ~256 instead of
// 512. 512 = 2^9 is exactly representable in BFloat16.
constexpr int N = 512;
Tensor x = tf.ones({N, 1});
Tensor out = tf.zeros({1});
int64_t dim = 0;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, BFloat16LargeDimAccumulatesInFloat) {
TensorFactory<ScalarType::BFloat16> tf;
// N=512, all-ones input: without fp32 accumulation the sum saturates at
// ~256 in BFloat16 instead of 512.
constexpr int N = 512;
Tensor x = tf.ones({1, N});
Tensor out = tf.zeros({1});
int64_t dim = 1;
op_sum_intlist_out(
x, ArrayRef<int64_t>{&dim, 1}, /*keepdim=*/false, /*dtype=*/{}, out);
Tensor expected = tf.full({1}, static_cast<float>(N));
EXPECT_TENSOR_CLOSE(out, expected);
}

TEST_F(OpSumOutTest, InvalidDimensionListDies) {
ET_SKIP_IF(
torch::executor::testing::SupportedFeatures::get()->is_aten,
Expand Down
Loading