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

Open
#123,104 1 comment 0 reactions 1 assignee View on GitHub

@Kayyuri is already working on this.

Since Aug 17, 2026.

2.21.0 awaiting PR merge comp:xla type:bug
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:

  1. Not correctly rounded: sigmoid(6.0) returns 1.0 (error > 0.5 ulp), and sigmoid(5.75) returns 0.9921875 (≈ 1.9 ulp below the exact value).
  2. Non-monotonic: sigmoid(5.5) returns 0.99609375 while sigmoid(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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.