Repository navigation
Conversation
|
Thanks for contributing to TorchTPU, @Kaap10! 🔥 To ensure smooth code reviews, we kindly ask authors and reviewers to follow the process documented in https://github.com/google-pytorch/torch_tpu/blob/main/CONTRIBUTING.md#code-reviews. Happy hacking! |
| int64_t minlength_val = minlength.guard_int(__FILE__, __LINE__); | ||
|
|
||
| TT_CHECK_THROW(minlength_val >= 0, error::kInvalidArgument) | ||
| << "minlength should be >= 0"; |
There was a problem hiding this comment.
Error formatting: "expected non-negative minlength, got " << minlength_val.
There was a problem hiding this comment.
Updated the error message to "expected non-negative minlength, got " << minlength_val; and updated the unit test in tests/errors_test.py.
| << "minlength should be >= 0"; | ||
|
|
||
| TT_CHECK_THROW(self.dim() == 1, error::kInvalidArgument) | ||
| << "unexpected dimension of input tensor: " |
There was a problem hiding this comment.
Error formatting: "expected one-dimensional input tensor, got " << self.dim() << "-dimensional"
There was a problem hiding this comment.
Updated the error message to "expected one-dimensional input tensor, got " << self.dim() << "-dimensional"; and updated the unit test in tests/errors_test.py.
| // properly size the output tensor of that computation. | ||
| at::Scalar max = self.max().cpu().item(); | ||
| if (weights_) { | ||
| TT_CHECK_THROW( |
There was a problem hiding this comment.
Split into two checks and return errors specific to the error case:
- "expected one-dimensional weights, got " << weights_->dim() << "-dimensional"
- "expected input and weights lengths to match, got " << self.size(0) << " and " << weights_->size(0);
There was a problem hiding this comment.
Split this into two separate checks:
weights_->dim() == 1with"expected one-dimensional weights, got " << weights_->dim() << "-dimensional"weights_->size(0) == self.size(0)with"expected input and weights lengths to match, got " << self.size(0) << " and " << weights_->size(0);
Also added dedicated unit tests for both cases in tests/errors_test.py and updated tests/error_coverage_data.yaml.
| // these values in order to validate non-negativity, to pass the number | ||
| // of bins to the actual bincount computation, and to properly size the | ||
| // output tensor of that computation. | ||
| auto [min_t, max_t] = at::aminmax(self); |
There was a problem hiding this comment.
Avoid redispatching through at:: operations; this requires a fairly expensive roundtrip through the torch dispatcher.
Slightly better would be call into AtenAMinMaxOut directly.
As an advanced option, we could consider:
- Computing the minum and maximum as a single DeviceBufferRef of shape int64_t[2]
- Creating an int64_t[2] receiver and using CopyDeviceBufferRefToCpuPtr to copy directly into it
This would avoid creating any CPU-side tensors at all and would only require one synchronization instead of the current 2.
There was a problem hiding this comment.
Implemented your advanced option:
- Using
BuildFusedAMinMaxShloto compute both min and max, casting them toI64, and concatenating them into a singleDeviceBufferRefof shape[2]. - Creating an
int64_t min_max_vals[2]stack receiver and copying directly into it viaCopyDeviceBufferRefToCpuPtr.
This avoids any at:: dispatcher roundtrip, eliminates CPU tensor allocations, and requires only 1 device-to-host synchronization.
| int64_t max_val = max_t.cpu().item().toLong(); | ||
|
|
||
| TT_CHECK_THROW(min_val >= 0, error::kInvalidArgument) | ||
| << "bincount only supports 1-d non-negative integral inputs"; |
There was a problem hiding this comment.
"expected non-negative inputs, but got a minimum value of " << min_val
You don't need to specify "bincount"; the TT_KERNEL wrapper will already prepend with "bincount()" in any error messaging.
There was a problem hiding this comment.
Updated to "expected non-negative inputs, but got a minimum value of " << min_val; and removed the explicit "bincount" prefix so it relies on the TT_KERNEL wrapper.
…e error formatting Address review comments from @benawilson on google-pytorch#6199: 1. Standardize error message formatting across validations: - 'expected non-negative minlength, got ' << minlength_val - 'expected one-dimensional input tensor, got ' << self.dim() << '-dimensional' - 'expected non-negative inputs, but got a minimum value of ' << min_val (relying on TT_KERNEL prefix rather than repeating op name) 2. Split weights validation into two distinct checks: - 'expected one-dimensional weights, got ' << weights_->dim() << '-dimensional' - 'expected input and weights lengths to match, got ' << self.size(0) << ' and ' << weights_->size(0) 3. Optimize fused aminmax to avoid redispatching through PyTorch dispatcher: - Construct single DeviceBufferRef of shape int64_t[2] directly via BuildFusedAMinMaxShlo + Concatenate - Copy directly to CPU stack receiver via CopyDeviceBufferRefToCpuPtr - Eliminates all ATen CPU tensor allocations and reduces host synchronizations to exactly 1 4. Add dependencies in csrc/ops/bincount/BUILD and wrap to 80 columns. 5. Update tests/errors_test.py and tests/error_coverage_data.yaml.
|
Thanks for the review @benawilson, |
| with et.assert_raises_message( | ||
| RuntimeError, | ||
| tpu="""bincount(): unexpected dimension of input tensor: [2, 2, 2]""", | ||
| tpu=( |
There was a problem hiding this comment.
Style: we use a """-string without linebreak for the expected message s.t. it's easier to update and more readable.
The same below.
There was a problem hiding this comment.
Done, updated all tpu= expected messages to use single-line """-strings without linebreaks.
| RuntimeError, | ||
| tpu="""bincount(): unexpected dimension of input tensor: [2, 2, 2]""", | ||
| tpu=( | ||
| "bincount(): expected one-dimensional input tensor, got" |
There was a problem hiding this comment.
Style: for consistency, please say 1D and 3D instead.
There was a problem hiding this comment.
Updated to 1D and 3D (and 1D/2D for weights) in both C++ and tests.
|
|
||
| with et.assert_raises_message( | ||
| RuntimeError, | ||
| tpu="""bincount(): expected non-negative minlength, got -1""", |
There was a problem hiding this comment.
Nit: expected minlength to be >= 0, got -1 is easier to read.
There was a problem hiding this comment.
Updated phrasing to "expected minlength to be >= 0, got " << minlength_val;.
|
|
||
| def test_bincount_non_integral_input(self): | ||
| t = torch.tensor([1.0, 2.0], device=et.device(), dtype=torch.float32) | ||
| err_type = RuntimeError if et.is_on_tpu() else NotImplementedError |
There was a problem hiding this comment.
Please raise NotImplementedError instead to match GPU behavior.
There was a problem hiding this comment.
Updated to throw error::kPythonNotImplementedError (raising NotImplementedError) with diagnostic "expected integral inputs, got " << ToString(self.scalar_type());, matching GPU behavior.
| with et.assert_raises_message( | ||
| err_type, | ||
| tpu=( | ||
| "bincount(): bincount only supports 1-d non-negative integral" |
There was a problem hiding this comment.
The message should focus on what is wrong - don't let the reader guess which part of the constraint is violated.
expected integral inputs, got float32
There was a problem hiding this comment.
Done: updated expected message to "bincount(): expected integral inputs, got float32" in test (and formatted dynamically via ToString(self.scalar_type()) in C++).
| with et.assert_raises_message( | ||
| RuntimeError, | ||
| tpu=( | ||
| "bincount(): expected input and weights lengths to match, got 2 and" |
There was a problem hiding this comment.
Updated delimiter to " vs " (got 2 vs 5).
| with et.assert_raises_message( | ||
| RuntimeError, | ||
| tpu=( | ||
| "bincount(): expected non-negative inputs, but got a minimum value" |
There was a problem hiding this comment.
For consistency, say got instead of but got.
expected inputs to be >= 0, got a minimum value of -1
There was a problem hiding this comment.
Done: updated expected message to "expected inputs to be >= 0, got a minimum value of -1" in test (and dynamically using min_val in C++).
…NotImplementedError Address review comments from @zhanyong-wan on google-pytorch#6199: 1. Standardize dimension nomenclature to '1D', '2D', '3D'. 2. Improve minlength error message to 'expected minlength to be >= 0, got '. 3. Raise NotImplementedError on non-integral inputs to match GPU behavior, and specify the violating dtype ('expected integral inputs, got <dtype>'). 4. Format weights length mismatch delimiter with 'vs' ('got 2 vs 5'). 5. Standardize non-negativity error phrasing to 'expected inputs to be >= 0, got a minimum value of '. 6. Format tests in tests/errors_test.py using single-line triple-quoted strings.
…n bincount Fix compilation and linting issues identified by presubmit CI on google-pytorch#6199: 1. In bincount_aten_kernels.cc: - Fix mlir::stablehlo::Reshape call signature to 2 arguments (op, shape). - Match MlirUnaryOpBuilder parameter signature (mlir::MlirOp input). - Pass self directly as const at::Tensor& to DispatchOp<1>. - Provide concrete lvalue variables for DispatchOpOptions<1> reference members (min_max_dims and min_max_dtype). 2. In BUILD: - Restore standard single-line load statement adhering to buildifier rules.
|
Pushed an update to fix the CI build and lint failures:
All linters and format checks are now passing locally. |
|
Please take a look at the build failures. Thanks. |
…ch.bincount ## Description Fixes google-pytorch#6107 According to the PyTorch specification, `torch.bincount` counts the frequency of values in a 1-D non-negative integral tensor. Previously in `csrc/ops/bincount/bincount_aten_kernels.cc`: 1. Negative input values were silently clamped to 0 and added into bin 0 via `stablehlo::Scatter`, causing silent data corruption instead of raising an error. Additionally, when all elements were negative and `minlength=0`, `BuildBinCount` constructed `Clamp(min=0, max=-1)`, which violates the StableHLO specification (§ClampOp: `min <= max`). 2. Negative `minlength` was not validated upfront and could lead to invalid tensor allocations. 3. Non-integral (floating-point or boolean) input dtypes were not validated upfront, leading to unhandled MLIR/StableHLO scatter verification errors (resolving internal bug `b/544855480`). 4. Multi-dimensional or length-mismatched `weights` were not validated upfront. This change: 1. Validates `minlength >= 0` prior to the empty tensor shortcut, raising `RuntimeError: bincount(): minlength should be >= 0`. 2. Validates integral input types using `IsInteger(self)`, raising `RuntimeError: bincount(): bincount only supports 1-d non-negative integral inputs`. 3. Validates weights rank and length (`weights_->dim() == 1 && weights_->size(0) == self.size(0)`), raising `RuntimeError: bincount(): weights should be 1-d and have the same length as input`. 4. Replaces `self.max().cpu().item()` with fused `at::aminmax(self)`, executing `BuildFusedAMinMaxShlo` (`AtenAminmaxOut`) to retrieve both minimum and maximum in a single TPU reduction dispatch without extra host synchronization penalty. 5. Validates non-negativity (`min_val >= 0`), raising `RuntimeError: bincount(): bincount only supports 1-d non-negative integral inputs` and eliminating silent data corruption in bin 0. 6. Adds 5 targeted error unit tests in `tests/errors_test.py` and updates `tests/error_coverage_data.yaml`. ## Test Plan - Verified C++ code formatting: `uvx clang-format --dry-run --Werror csrc/ops/bincount/bincount_aten_kernels.cc` passes with 0 errors. - Verified Python code formatting and linting: `ruff check tests/errors_test.py` passes with 0 errors. - Verified line length constraints: all modified lines strictly adhere to <= 80 columns. - Verified error macro coverage tracking: verified with `tests/error_coverage_lib.py` that all 5 `TT_CHECK_THROW` macros in `bincount_aten_kernels.cc` are accounted for in `tests/error_coverage_data.yaml` with 0 remaining uncovered macros. - Added comprehensive error tests in `tests/errors_test.py`: - `test_bincount_negative_minlength` - `test_bincount_non_integral_input` - `test_bincount_weights_dimension_mismatch` - `test_bincount_weights_length_mismatch` - `test_bincount_negative_inputs`
…e error formatting Address review comments from @benawilson on google-pytorch#6199: 1. Standardize error message formatting across validations: - 'expected non-negative minlength, got ' << minlength_val - 'expected one-dimensional input tensor, got ' << self.dim() << '-dimensional' - 'expected non-negative inputs, but got a minimum value of ' << min_val (relying on TT_KERNEL prefix rather than repeating op name) 2. Split weights validation into two distinct checks: - 'expected one-dimensional weights, got ' << weights_->dim() << '-dimensional' - 'expected input and weights lengths to match, got ' << self.size(0) << ' and ' << weights_->size(0) 3. Optimize fused aminmax to avoid redispatching through PyTorch dispatcher: - Construct single DeviceBufferRef of shape int64_t[2] directly via BuildFusedAMinMaxShlo + Concatenate - Copy directly to CPU stack receiver via CopyDeviceBufferRefToCpuPtr - Eliminates all ATen CPU tensor allocations and reduces host synchronizations to exactly 1 4. Add dependencies in csrc/ops/bincount/BUILD and wrap to 80 columns. 5. Update tests/errors_test.py and tests/error_coverage_data.yaml.
…NotImplementedError Address review comments from @zhanyong-wan on google-pytorch#6199: 1. Standardize dimension nomenclature to '1D', '2D', '3D'. 2. Improve minlength error message to 'expected minlength to be >= 0, got '. 3. Raise NotImplementedError on non-integral inputs to match GPU behavior, and specify the violating dtype ('expected integral inputs, got <dtype>'). 4. Format weights length mismatch delimiter with 'vs' ('got 2 vs 5'). 5. Standardize non-negativity error phrasing to 'expected inputs to be >= 0, got a minimum value of '. 6. Format tests in tests/errors_test.py using single-line triple-quoted strings.
…n bincount Fix compilation and linting issues identified by presubmit CI on google-pytorch#6199: 1. In bincount_aten_kernels.cc: - Fix mlir::stablehlo::Reshape call signature to 2 arguments (op, shape). - Match MlirUnaryOpBuilder parameter signature (mlir::MlirOp input). - Pass self directly as const at::Tensor& to DispatchOp<1>. - Provide concrete lvalue variables for DispatchOpOptions<1> reference members (min_max_dims and min_max_dtype). 2. In BUILD: - Restore standard single-line load statement adhering to buildifier rules.
…target in bincount
Fix build and linter failures from previous commit:
1. In bincount_aten_kernels.cc:
- Construct DispatchOpOptions<1> explicitly with
.op_param_cache_keys = OpParamCacheKeys::Empty() to satisfy
uninitialized member requirement in C++20 designated initializers.
- Bind out_dims to concrete absl::Span<const int64_t> lvalue to
prevent binding a reference member to a temporary.
- Include <array> header.
2. In BUILD:
- Canonicalize //csrc/ops/a_min_max:a_min_max to //csrc/ops/a_min_max
per buildifier rules.
dba7e34 to
bb06dd7
Compare
Fixed and rebased onto latest main. |
|
@benawilson is working on test failures detected when importing this PR into Google's internal build. |
Description
Fixes #6107
According to the PyTorch specification,
torch.bincountcounts the frequency of values in a 1-D non-negative integral tensor.Previously in
csrc/ops/bincount/bincount_aten_kernels.cc:stablehlo::Scatter, causing silent data corruption instead of raising an error. Additionally, when all elements were negative andminlength=0,BuildBinCountconstructedClamp(min=0, max=-1), which violates the StableHLO specification (§ClampOp:min <= max).minlengthwas not validated upfront and could lead to invalid tensor allocations.b/544855480).weightswere not validated upfront.This change:
minlength >= 0prior to the empty tensor shortcut, raisingRuntimeError: bincount(): expected non-negative minlength, got <val>.RuntimeError: bincount(): expected one-dimensional input tensor, got <dim>-dimensional.IsInteger(self), raisingRuntimeError: bincount(): bincount only supports 1-d non-negative integral inputs.weights_->dim() == 1, raisingRuntimeError: bincount(): expected one-dimensional weights, got <dim>-dimensional.weights_->size(0) == self.size(0), raisingRuntimeError: bincount(): expected input and weights lengths to match, got <len1> and <len2>.DeviceBufferRefof shapeint64_t[2]usingBuildFusedAMinMaxShloandstablehlo::Concatenate.int64_t[2]CPU stack receiver viaCopyDeviceBufferRefToCpuPtr.min_val >= 0), raisingRuntimeError: bincount(): expected non-negative inputs, but got a minimum value of <val>, completely eliminating silent data corruption.tests/errors_test.pyand tracks all 6 error macros intests/error_coverage_data.yaml.Test Plan
uvx clang-format --dry-run --Werror csrc/ops/bincount/bincount_aten_kernels.ccpasses with 0 errors.ruff check tests/errors_test.pypasses with 0 errors.python ci/tools/check_directory_layout.pypasses.tests/error_coverage_lib.pythat all 6TT_CHECK_THROWmacros inbincount_aten_kernels.ccare covered with 0 remaining uncovered macros intests/error_coverage_data.yaml.tests/errors_test.py:test_bincount_rank_too_hightest_bincount_negative_minlengthtest_bincount_non_integral_inputtest_bincount_weights_dimension_mismatchtest_bincount_weights_length_mismatchtest_bincount_negative_inputs