Skip to content

⚡ Thunderbolt: Softmax — 8x Unrolling + Single-FMA Range Reduction#64

Open
bugparty wants to merge 1 commit into
mainfrom
thunderbolt-softmax-8x-unroll-48235933610456447
Open

⚡ Thunderbolt: Softmax — 8x Unrolling + Single-FMA Range Reduction#64
bugparty wants to merge 1 commit into
mainfrom
thunderbolt-softmax-8x-unroll-48235933610456447

Conversation

@bugparty

@bugparty bugparty commented Jun 26, 2026

Copy link
Copy Markdown
Owner

💡 What:
Introduced softmax_v6 using 8x unrolling and a faster exp256_ps_v3 approximation that leverages a single-FMA range reduction instead of splitting ln(2) for exact precision. Added the new kernel to the benchmark suite (SoftmaxV6Benchmark) and verified numerical correctness (test_softmax_v6).

🎯 Why:
softmax_v5 heavily relied on the exact precision split for ln(2) in exp256. However, because Softmax relies on relative differences ($e^{x_i - \max}$) rather than exact IEEE outputs of individual $e^x$, we can safely sacrifice minor precision during range reduction without exceeding ML numerical tolerance (verified 1e-4f). This simplification reduces register pressure and instruction count, allowing us to safely increase unrolling to 8x to perfectly match execution port throughput and hide instruction latency.

🏗️ How:

  1. Created exp256_ps_v3 which replaces the two-step ln(2) subtraction with a single _mm256_fnmadd_ps.
  2. Created softmax_v6 which implements an 8x unroll (processing 64 elements per loop iteration) for finding the max, computing $e^x$ and the sum, and the final normalization pass.

📊 Impact:
Measured on AVX2:

  • N=1048576 (Fixed Memory): 4.36 GFLOP/s vs softmax_v5's 3.81 GFLOP/s (~14.4% throughput gain).
  • Reduced register pressure and lower FMA latency chain.

🖥️ Tested on:
GNU C++ 13.3.0, Ubuntu 22.04 (Docker), AVX2/FMA target.

🔬 How to reproduce:

cd build && make -j$(nproc) ml_kernel_bench
DISABLE_CPU_BINDING=1 ./ml_kernels/ml_kernel_bench --filter "softmax_v5" > v5.log &
DISABLE_CPU_BINDING=1 ./ml_kernels/ml_kernel_bench --filter "softmax_v6" > v6.log &
wait
cat v5.log v6.log

PR created automatically by Jules for task 48235933610456447 started by @bugparty

Summary by CodeRabbit

  • New Features

    • Added a new high-throughput softmax variant and a related benchmark entry.
    • Expanded softmax handling to better support larger inputs with wider unrolling.
  • Bug Fixes

    • Improved softmax performance while preserving accurate output normalization.
    • Added coverage to verify the new softmax path matches the reference result closely.
  • Documentation

    • Added notes on the latest softmax tuning approach and benchmark findings.

Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added a new AVX2 softmax variant with a new exponential helper, benchmark registration, correctness coverage, and a documentation note about the range-reduction and unrolling approach.

Changes

AVX2 softmax v6

Layer / File(s) Summary
Range-reduction exponential helper
ml_kernels/include/ml_kernels/softmax.h
exp256_ps_v3 clamps input values, derives an exponent shift from log2-space, and evaluates the polynomial approximation with FMA-style operations.
8-way softmax kernel
ml_kernels/include/ml_kernels/softmax.h
softmax_v6 uses 8-way unrolling for max reduction, exponent accumulation, and output normalization, then falls back to scalar handling for the tail.
Benchmark and validation wiring
ml_kernels/src/kernel_bench.cpp, ml_kernels/src/test_naive_ops.cpp, .jules/thunderbolt.md
Registers softmax_v6 in the benchmark suite, adds a correctness test against softmax_naive, updates main() to run it, and adds the matching documentation note.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I hopped through vectors, bright and fast,
and found a softmax built to last.
With 8x hops and FMA cheer,
my carrot count came out quite near.
exp went zoom, the sums aligned—
a rabbit grin in code I mined. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately highlights the main change: a new softmax kernel with 8x unrolling and single-FMA range reduction.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch thunderbolt-softmax-8x-unroll-48235933610456447

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
ml_kernels/src/kernel_bench.cpp (1)

337-341: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the new function-body braces onto their own lines.

The new name() and run() definitions use same-line opening braces, which doesn't match the repository's C/C++ function style. As per coding guidelines, "Keep braces on their own lines for function bodies".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ml_kernels/src/kernel_bench.cpp` around lines 337 - 341, The new softmax_v6
benchmark methods in the unnamed class use same-line function-body braces, which
violates the project’s C/C++ style. Update the `name()` and `run()` overrides in
`kernel_bench.cpp` so the opening braces for these function bodies are on their
own lines, matching the surrounding function formatting used elsewhere in this
file.

Source: Coding guidelines

ml_kernels/src/test_naive_ops.cpp (2)

184-184: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the new function-body braces onto their own lines.

test_softmax_v6 and main keep the opening brace on the signature line, which doesn't match the repository's C/C++ function style. As per coding guidelines, "Keep braces on their own lines for function bodies".

Also applies to: 219-219

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ml_kernels/src/test_naive_ops.cpp` at line 184, The function-body braces for
test_softmax_v6 and main are attached to the signature line, which does not
match the C/C++ style used here. Move each opening brace onto its own line for
those function definitions, following the same brace placement pattern used
elsewhere in this file.

Source: Coding guidelines


184-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a 1-7 element tail case for softmax_v6.

This fixture has 72 values, so it covers the 64-wide unrolled body and the 8-lane vector remainder, but it never reaches the scalar cleanup loop in softmax_v6. A 65-71 element case would exercise the new tail path as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ml_kernels/src/test_naive_ops.cpp` around lines 184 - 214, The softmax_v6
test only covers the 64-wide unrolled path plus the 8-lane remainder, so it
misses the scalar cleanup tail. Update test_softmax_v6 to include a separate
65-71 element input case that forces the final scalar loop in
ml_kernels::softmax_v6. Compare that result against softmax_naive the same way
as the existing assertions, so all execution paths are validated.
ml_kernels/include/ml_kernels/softmax.h (1)

505-505: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the new function-body braces onto their own lines.

exp256_ps_v3 and softmax_v6 keep the opening brace on the signature line, which doesn't match the repository's C/C++ function style. As per coding guidelines, "Keep braces on their own lines for function bodies".

Also applies to: 539-539

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ml_kernels/include/ml_kernels/softmax.h` at line 505, The function-body
opening braces in exp256_ps_v3 and softmax_v6 should follow the repository’s
C/C++ style and be placed on their own lines instead of staying on the signature
lines. Update the function declarations in softmax.h so each opening brace is
moved to its own line for these two functions, matching the existing brace style
used elsewhere in the file.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ml_kernels/src/kernel_bench.cpp`:
- Around line 335-344: SoftmaxV6Benchmark is still using the inherited
SoftmaxBenchmark::verify() tolerance, which is stricter than the published
softmax_v6 kernel contract. Override verify() in SoftmaxV6Benchmark and use the
kernel’s expected 1e-4f tolerance so the benchmark matches the registered
softmax_v6 behavior.

---

Nitpick comments:
In `@ml_kernels/include/ml_kernels/softmax.h`:
- Line 505: The function-body opening braces in exp256_ps_v3 and softmax_v6
should follow the repository’s C/C++ style and be placed on their own lines
instead of staying on the signature lines. Update the function declarations in
softmax.h so each opening brace is moved to its own line for these two
functions, matching the existing brace style used elsewhere in the file.

In `@ml_kernels/src/kernel_bench.cpp`:
- Around line 337-341: The new softmax_v6 benchmark methods in the unnamed class
use same-line function-body braces, which violates the project’s C/C++ style.
Update the `name()` and `run()` overrides in `kernel_bench.cpp` so the opening
braces for these function bodies are on their own lines, matching the
surrounding function formatting used elsewhere in this file.

In `@ml_kernels/src/test_naive_ops.cpp`:
- Line 184: The function-body braces for test_softmax_v6 and main are attached
to the signature line, which does not match the C/C++ style used here. Move each
opening brace onto its own line for those function definitions, following the
same brace placement pattern used elsewhere in this file.
- Around line 184-214: The softmax_v6 test only covers the 64-wide unrolled path
plus the 8-lane remainder, so it misses the scalar cleanup tail. Update
test_softmax_v6 to include a separate 65-71 element input case that forces the
final scalar loop in ml_kernels::softmax_v6. Compare that result against
softmax_naive the same way as the existing assertions, so all execution paths
are validated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 307a9fe9-20c4-4dbd-95dc-a0d0b307d6ad

📥 Commits

Reviewing files that changed from the base of the PR and between acca01e and 7ce9661.

📒 Files selected for processing (4)
  • .jules/thunderbolt.md
  • ml_kernels/include/ml_kernels/softmax.h
  • ml_kernels/src/kernel_bench.cpp
  • ml_kernels/src/test_naive_ops.cpp

Comment on lines +335 to +344
class SoftmaxV6Benchmark : public SoftmaxBenchmark {
public:
const char *name() const override { return "softmax_v6"; }

void run() override {
ml_kernels::softmax_v6(inputs_[current_idx_].data(), outputs_[current_idx_].data(), inputs_[0].size());
current_idx_ = (current_idx_ + 1) % pool_size_;
}
};
REGISTER_BENCHMARK(SoftmaxV6Benchmark);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align SoftmaxV6Benchmark::verify() with the kernel's published tolerance.

This subclass inherits SoftmaxBenchmark::verify(), which still hard-codes 1e-5f, while the new kernel is documented and tested at 1e-4f. That makes the benchmark contract stricter than the implementation you're registering here.

Suggested localized fix
 class SoftmaxV6Benchmark : public SoftmaxBenchmark {
 public:
     const char *name() const override { return "softmax_v6"; }
 
     void run() override {
         ml_kernels::softmax_v6(inputs_[current_idx_].data(), outputs_[current_idx_].data(), inputs_[0].size());
         current_idx_ = (current_idx_ + 1) % pool_size_;
     }
+
+    bool verify() override {
+        current_idx_ = 0;
+        run();
+        constexpr float tol = 1e-4f;
+        for (std::size_t i = 0; i < outputs_[0].size(); ++i) {
+            if (std::fabs(outputs_[0][i] - output_ref_[i]) > tol) {
+                return false;
+            }
+        }
+        return true;
+    }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class SoftmaxV6Benchmark : public SoftmaxBenchmark {
public:
const char *name() const override { return "softmax_v6"; }
void run() override {
ml_kernels::softmax_v6(inputs_[current_idx_].data(), outputs_[current_idx_].data(), inputs_[0].size());
current_idx_ = (current_idx_ + 1) % pool_size_;
}
};
REGISTER_BENCHMARK(SoftmaxV6Benchmark);
class SoftmaxV6Benchmark : public SoftmaxBenchmark {
public:
const char *name() const override { return "softmax_v6"; }
void run() override {
ml_kernels::softmax_v6(inputs_[current_idx_].data(), outputs_[current_idx_].data(), inputs_[0].size());
current_idx_ = (current_idx_ + 1) % pool_size_;
}
bool verify() override {
current_idx_ = 0;
run();
constexpr float tol = 1e-4f;
for (std::size_t i = 0; i < outputs_[0].size(); ++i) {
if (std::fabs(outputs_[0][i] - output_ref_[i]) > tol) {
return false;
}
}
return true;
}
};
REGISTER_BENCHMARK(SoftmaxV6Benchmark);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ml_kernels/src/kernel_bench.cpp` around lines 335 - 344, SoftmaxV6Benchmark
is still using the inherited SoftmaxBenchmark::verify() tolerance, which is
stricter than the published softmax_v6 kernel contract. Override verify() in
SoftmaxV6Benchmark and use the kernel’s expected 1e-4f tolerance so the
benchmark matches the registered softmax_v6 behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant