Repository navigation
Fix XLA DenseBincount negative input validation for runtime tensors - #122706
copybara-service[bot] merged 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request adds support for handling runtime negative inputs in the XLA dense_bincount operator by mapping negative indices to an out-of-bounds value (output_size) so they are silently ignored by XLA Scatter, along with adding corresponding unit tests. The review feedback points out that also zeroing out the updates for these negative values is redundant and introduces unnecessary performance overhead, suggesting the removal of these redundant operations and updating the comments accordingly.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Hi @gbaned, gentle nudge on this one. The CLA check is passing and the branch is mergeable with no conflicts. This fixes a real bug where |
dmiltr3
left a comment
There was a problem hiding this comment.
Correctness & Safety
The approach of using output_size (Out of Bounds - OOB) instead of clamping to 0 is excellent. It correctly avoids overwriting valid data (e.g., bin 0) in cases where binary_output=True (Scatter Assign semantics). Since XLA Scatter drops OOB updates, setting negative indices to a positive OOB value like output_size is a standard and robust way to ignore them.
Redundancy in Updates Zeroing
The PR description notes step 3 (zeroing updates) as a "belt-and-suspenders safety measure".
- If
Scatterstrictly adheres to XLA semantics and drops OOB indices (which includesoutput_size), zeroing updates is redundant because the update value is never applied. - Zeroing updates introduces minor dynamic overhead (
Mul,ConvertElementType). - Caution: If there were a backend bug where OOB indices were accidentally clamped or routed to a valid bin (e.g., bin 0), zeroing out updates could actually make the bug worse for
ScatterAssign(Assign 0 overwriting valid data) than leaving it as-is (Assign 1 or weight). - Recommendation: Consider removing the updates zeroing step if performance is critical or if you are confident in standard OOB dropping behavior. The explicit conversion of negative indices to positive OOB (
output_size) is sufficient and safer than leaving them negative (which might cause UB or buffer overflow on non-compliant backends).
Consistent Positive OOB
Using output_size is preferred over relying on negative indices being treated as OOB, as some backends or low-level arithmetic might handle signed negative indices inconsistently or interpret them as huge positive indices wrapping around.
Unit Tests
To verify this behavior, the following tests can be added to standard suite (e.g., bincount_op_test.py). These tests verify that runtime negative inputs are ignored gracefully.
def testNegativeInputRuntimeIgnored(self):
with self.session():
with self.test_scope():
@def_function.function(jit_compile=True)
def f(arr):
return gen_math_ops.dense_bincount(
input=arr, size=3, weights=[]
)
# Pass as argument to make it a runtime tensor
arr = array_ops.constant([0, -1, 2])
result = f(arr)
# Expected: 0 -> bin 0, 2 -> bin 2. -1 ignored.
self.assertAllEqual([1, 0, 1], result)
def testNegativeInputRuntimeIgnoredBinary(self):
with self.session():
with self.test_scope():
@def_function.function(jit_compile=True)
def f(arr):
return gen_math_ops.dense_bincount(
input=arr, size=3, weights=[], binary_output=True
)
arr = array_ops.constant([0, -1, 2])
result = f(arr)
self.assertAllEqual([1, 0, 1], result)
def testNegativeInputRuntime2d(self):
with self.session():
with self.test_scope():
@def_function.function(jit_compile=True)
def f(arr):
return gen_math_ops.dense_bincount(
input=arr, size=3, weights=[]
)
# 2D input: [[0, -1, 2], [1, 2, -1]]
arr = array_ops.constant([[0, -1, 2], [1, 2, -1]])
result = f(arr)
# Expected output shape: [2, 3]
# Row 0: 0 -> bin 0, 2 -> bin 2. -1 ignored. -> [1, 0, 1]
# Row 1: 1 -> bin 1, 2 -> bin 2. -1 ignored. -> [0, 1, 1]
self.assertAllEqual([[1, 0, 1], [0, 1, 1]], result)|
Thanks @dmiltr3 for the thorough review. You're right — the updates zeroing is redundant since XLA Scatter drops OOB indices, and it adds unnecessary dynamic overhead. Removed both zeroing blocks (rank-1 and rank-2 paths) in the latest push. The OOB approach ( |
dmiltr3
left a comment
There was a problem hiding this comment.
We have tested the changes internally and they look good.
Verification Results
- CPU Tests: Passed.
- GPU Tests: Passed (verified with CUDA backend).
- A100 Tests: Passed.
Logic Review
The approach of mapping negative indices in runtime tensors to output_size (out of bounds) using xla::Select successfully prevents corruption and ensures that negative values are ignored, matching the behavior of the non-XLA kernels.
Removing the redundant updates zeroing for negative indices (in your 4th commit) is also correct as XLA Scatter handles out-of-bounds updates silently.
|
Heads up on the Update (Jul 20): the Current failure (run 29689318004)
All bincount targets pass on Windows in the same run: Repo-wide reproductionEvery other recent PR I checked also fails
This PR's diff only touches Original note (Jul 16, since fixed on master): the failure at that time was a master-side link regression:
|
|
@dmiltr3 Following up on the approval — would it be worth adding a weighted-bincount runtime-negative test (non-empty |
dmiltr3
left a comment
There was a problem hiding this comment.
Code Quality & Style
Please ensure your code follows standard formatting rules. Running clang-format on your C++ changes (bincount_op.cc) will help maintain consistency.
Implementation Details
Your approach of masking negative indices to output_size (out of bounds) so that XLA Scatter silently ignores them is valid and works correctly for both 1D and 2D inputs.
However, running these tests exposed a couple of issues that were addressed:
-
DenseBincountShape Inference: The shape function intensorflow/core/ops/math_ops.ccwas missing a fallback for unknown rank inputs. This caused a crash (Check failed: output(i).IsSet()) during tracing of your new tests. Added a fallback to set output toUnknownShape()for unknown rank inputs.// In tensorflow/core/ops/math_ops.cc under REGISTER_OP("DenseBincount") // ... if (c->Rank(c->input(0)) == 1 || c->Rank(c->input(0)) == 0) { c->set_output(0, c->MakeShape({size_val})); } else if (c->Rank(c->input(0)) == 2) { c->set_output(0, c->MakeShape({c->Dim(c->input(0), 0), size_val})); + } else { + c->set_output(0, c->UnknownShape()); } return absl::OkStatus(); });
-
Test Wrappers: In
bincount_op_test.py, the new tests requiredwith self.session():wrappers to avoidtest_session not supportederrors underXLATestCase.# In tensorflow/compiler/tests/bincount_op_test.py # ... def testNegativeInputRuntimeIgnored(self): - with self.test_scope(): + with self.session(): + with self.test_scope(): @def_function.function( jit_compile=True, input_signature=[tensor_spec.TensorSpec([5], dtypes.int32)]) def f(arr): return gen_math_ops.dense_bincount( input=arr, size=5, weights=[] )
(Please apply this
with self.session():wrapper to all three new tests:testNegativeInputRuntimeIgnored,testNegativeInputRuntimeIgnoredBinary, andtestNegativeInputRuntime2d)
Please include these fixes in your GitHub PR to ensure tests pass successfully.
PR #121118 added compile-time constant validation for negative inputs in the XLA DenseBincount kernel, but only for the case where the input is a compile-time constant. For runtime (non-constant) tensors -- the common case in @tf.function(jit_compile=True) where inputs come from placeholders, variables, or upstream ops -- negative values were passed directly to xla::Scatter, silently producing wrong results instead of raising InvalidArgumentError as eager mode does. Since XLA cannot raise runtime errors from within a compiled computation, map negative indices to output_size (out of bounds) via xla::Select so that XLA Scatter silently ignores them. Using OOB indices instead of clamping to 0 avoids a subtle bug with binary_output=True: when scatter assigns (rather than adds), clamping negative values to index 0 would overwrite legitimate bin 0 values with 0. OOB indices are simply dropped by XLA Scatter, preserving correct results. The existing compile-time constant validation from #121118 is preserved unchanged -- OP_REQUIRES raises InvalidArgumentError at compile time for constant negative inputs. Also add an UnknownShape() fallback to the DenseBincount shape function in math_ops.cc for unknown-rank inputs, which previously crashed with 'Check failed: output(i).IsSet()' when tracing the new runtime-negative tests. Adds three XLA tests covering runtime negative inputs (1D, 1D with binary_output, and 2D), all wrapped in with self.session(): per XLATestCase requirements. Closes #117960
|
Thanks for the review. I addressed all requested changes and rebased/squashed into a single clean commit:
Changes:
I also ran additional cross-architecture validation in Colab (TF 2.20.0), including weighted-path coverage, since existing in-repo tests primarily exercise
The OOB-index behavior correctly drops negative indices and their corresponding weights, so no additional masking logic is required. This closes the runtime negative-input gap identified in #121118 and resolves #117960. Ready for merge once CI is green. (The Windows failure appears to be an unrelated master branch issue: .) |
|
Thanks @dmiltr3 for the thorough reviews and approval. The PR is rebased, squashed to a single commit, and all checks are green (the Windows x86 failure is a known infra issue unrelated to this change). Ready to merge when you have a moment. |
Summary
PR #121118 added compile-time constant validation for negative inputs in the XLA
DenseBincountkernel. However, it only handles the case where the input is a compile-time constant (e.g.,tf.constant([0, -1, 2])). For runtime (non-constant) tensors — the common case in@tf.function(jit_compile=True)where inputs come from placeholders, variables, or upstream ops — negative values are still passed directly toxla::Scatter, silently producing wrong results instead of raisingInvalidArgumentErroras eager mode does.This PR fixes the runtime case that #121118 left unfixed.
Approach
For runtime (non-constant) inputs, since XLA cannot raise runtime errors from within a compiled computation, we:
nonneg_mask = xla::Ge(input, 0)— true where input >= 0output_size(out of bounds) viaxla::Selectso that XLA Scatter silently ignores them (verified: XLA Scatter drops out-of-bounds updates)Using OOB indices instead of clamping to 0 avoids a subtle bug with
binary_output=True: when scatter assigns (rather than adds), clamping negative values to index 0 would overwrite legitimate bin 0 values with 0. OOB indices are simply dropped by XLA Scatter, preserving correct results.Compile-time constant inputs
The existing validation from #121118 is preserved unchanged —
OP_REQUIRESraisesInvalidArgumentErrorat compile time for constant negative inputs.Test plan
testNegativeInputRuntimeIgnored: 1D runtime input with negatives → negative values ignored, correct counts for non-negative valuestestNegativeInputRuntimeIgnoredBinary:binary_output=Truewith runtime negatives → negative values ignored, no bin 0 overwritetestNegativeInputRuntime2d: 2D runtime input with negatives → per-row bincount correct, negatives ignoredCloses #117960