portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all
, '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

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum - #20089

Closed
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax
Closed

portable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sum#20089
vacu9708 wants to merge 2 commits into
pytorch:mainfrom
vacu9708:accumulate_in_fp32_in_softmax

Conversation

@vacu9708

@vacu9708vacu9708 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Motivation

softmax, log_softmax, mean, and sum all accumulate their reduction in the input dtype. For BFloat16, that sum saturates around 256. Once it gets there, adding 1.0 rounds away and the total gets stuck. A uniform softmax over 512 elements in BFloat16 gives ~1/256 per output instead of 1/512.

Why FP32 accumulation is needed

BFloat16 has the same exponent width as Float32, so it has a similar range. However, it has far fewer fraction bits, which makes its representable spacing much coarser as values grow.

TypeExponent bitsFraction bitsPractical effect
BFloat1687Similar range to Float32, but coarse spacing
Float32823Similar range, much finer spacing

For BFloat16, the gap between consecutive representable values (i.e, the smallest step size) increases at each power-of-two range:

RangeBFloat16 step sizeRepresentable examples
[128, 256)1128, 129, 130, ..., 255
[256, 512)2256, 258, 260, ..., 510

As a result, once a BFloat16 running sum reaches 256, adding 1.0 no longer changes the value:

OperationExact resultBFloat16 resultReason
256 + 1257256257 is not representable and rounds back to 256 (according to IEEE 754; round-to-nearest-even)

This directly affects all four ops for large inputs. For a softmax over 512 zeros, each exp(0) contributes 1.0, so the denominator should be 512. If the BFloat16 accumulation gets stuck at 256, the output becomes approximately 1/256 instead of the correct 1/512.

CaseExpected denominatorBFloat16 accumulated denominatorOutput
Correct accumulation5125121/512
BFloat16 accumulation512~256~1/256

ATen accumulates reductions in float for Half/BFloat16 (via acc_type). This PR does the same, following the pattern already established in op_grid_sampler_2d (#19117).

Tests

$ cmake --build cmake-out --target portable_kernels_test -j$(nproc)
[100%] Built target portable_kernels_test
# Post-fix — new tests:
[ OK ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ OK ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
# Pre-fix (reverted op files):
[ FAILED ] OpSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpLogSoftmaxOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpMeanOutTest.BFloat16LargeDimAccumulatesInFloat
[ FAILED ] OpSumOutTest.BFloat16LargeDimAccumulatesInFloat
$ lintrunner op_softmax.cpp op_log_softmax.cpp op_mean.cpp op_sum.cpp \
op_softmax_test.cpp op_log_softmax_test.cpp op_mean_test.cpp op_sum_test.cpp
ok No lint issues.

cc @larryliu0820@manuelcandales

@pytorch-bot

pytorch-botBot commented Jun 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20089

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 12 Awaiting Approval

As of commit 98d2f39 with merge base 01b3568 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 7, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

vacu9708and others added 2 commits June 8, 2026 10:41
…tmax
Problem:
Softmax and log_softmax accumulated exp(x - max) in the tensor dtype.
For BFloat16, the running sum saturates around 256 — adding 1.0 stops
changing the total — so a uniform softmax over N=512 elements outputs
~1/256 instead of 1/512.
Changes:
Accumulate the exp-sum in float for Half/BFloat16 by threading an ACC
type through the map-reduce calls. Loads and stores remain in the tensor
dtype.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem:
The fast-path and generic reduction loops in mean.out and sum.IntList_out
accumulated the running sum in the tensor dtype. For BFloat16, the sum
saturates around 256, so a mean over N=512 all-ones elements gives 0.5
instead of 1.0, and summing 512 all-ones elements gives 256 instead of
512.
Changes:
Accumulate in float for Half/BFloat16 by promoting the loop accumulator
to ACC in both the fast path and the generic path. The final result is
cast back to the tensor dtype on store.
Continues the fp32-accumulation work in pytorch#19117.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vacu9708
vacu9708force-pushed the accumulate_in_fp32_in_softmax branch from 7777b92 to 98d2f39CompareJune 8, 2026 02:03
@vacu9708vacu9708 changed the title portable: accumulate in fp32 for Half/BFloat16 in softmax and log_softmaxportable: accumulate in fp32 for Half/BFloat16 in softmax, log_softmax, mean, and sumJun 8, 2026
@vacu9708vacu9708 closed this Jun 8, 2026
@vacu9708

Copy link
Copy Markdown
ContributorAuthor

I am opening the PR again with a more appropriate branch name

@vacu9708
vacu9708 deleted the accumulate_in_fp32_in_softmax branch June 8, 2026 04:07
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@vacu9708@nil-is-all