tensorflow / tensorflow/tensorflow
tf.sigmoid on bfloat16 is non-monotonic and not correctly rounded in eager/graph on CPU; XLA returns the correctly rounded values
@Kayyuri is already working on this.
Since Aug 17, 2026.
- Dominant language
- C++
- Stars
- 200k
- Forks
- 76.9k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 433
Description
Issue type
Bug
Have you reproduced the bug with TensorFlow Nightly?
Yes
Source
binary
TensorFlow version
tf-nightly 2.22.0-dev20260531 (v1.12.1-140696-g1f5a6a050b4)
Custom code
Yes
OS platform and distribution
Linux Ubuntu 20.04.4 LTS
Mobile device
N/A
Python version
Python 3.11.15
Bazel version
N/A
GCC/compiler version
N/A
CUDA/cuDNN version
N/A. Reproduced on CPU (eager CPU kernel vs XLA CPU JIT).
GPU model and memory
N/A. Reproduced on CPU.
Current behavior?
For bfloat16 inputs on CPU, tf.sigmoid in eager mode (and non-XLA tf.function, which matches eager) returns values that are neither correctly rounded nor monotonic. The same function compiled with jit_compile=True returns the correctly rounded bfloat16 values.
The exact values are sigmoid(5.75) = 0.9968171… and sigmoid(6.0) = 0.9975274…. In bfloat16, the two neighboring representable values are 0.99609375 (= 1 − 2⁻⁸, bits 0x3F7F) and 1.0 (bits 0x3F80). Both exact values are closer to 0.99609375, so the correctly rounded bfloat16 result is 0.99609375 in both cases — which is what XLA returns.
The eager/graph kernel shows two problems:
- Not correctly rounded:
sigmoid(6.0)returns 1.0 (error > 0.5 ulp), andsigmoid(5.75)returns 0.9921875 (≈ 1.9 ulp below the exact value). - Non-monotonic:
sigmoid(5.5)returns 0.99609375 whilesigmoid(5.75)returns the smaller value 0.9921875, although sigmoid is strictly increasing. Monotonicity violations can silently break code that relies on order preservation (thresholding, top-k / argmax over probabilities, calibration).
The behavior is unchanged with TF_ENABLE_ONEDNN_OPTS=0, and also reproduces on TensorFlow 2.20. Because the divergence is deterministic, any bfloat16 model containing sigmoid can produce stable output mismatches between eager/graph and jit_compile=True; this was found through differential testing where the difference propagates through a subsequent bfloat16 matmul.
Standalone code to reproduce the issue
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
import tensorflow as tf
print("TensorFlow:", tf.__version__)
for v in [5.5, 5.75, 6.0, 6.25]:
x = tf.constant(v, dtype=tf.bfloat16)
eager = float(tf.sigmoid(x).numpy())
graph = float(tf.function(tf.sigmoid)(x).numpy())
xla = float(tf.function(tf.sigmoid, jit_compile=True)(x).numpy())
print(f"sigmoid({v}): eager={eager} graph={graph} xla={xla}")
Relevant log output
TensorFlow: 2.22.0-dev20260531
sigmoid(5.5): eager=0.99609375 graph=0.99609375 xla=0.99609375
sigmoid(5.75): eager=0.9921875 graph=0.9921875 xla=0.99609375
sigmoid(6.0): eager=1.0 graph=1.0 xla=0.99609375
sigmoid(6.25): eager=1.0 graph=1.0 xla=1.0
Reference values:
exact sigmoid(5.75) = 0.9968171… -> correctly rounded bfloat16: 0.99609375
exact sigmoid(6.0) = 0.9975274… -> correctly rounded bfloat16: 0.99609375
bfloat16 neighbors near 1: 0.99609375 (0x3F7F), 1.0 (0x3F80)
Same output with TF_ENABLE_ONEDNN_OPTS=0.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.