Skip to content

feat(hiptensor): add bf16 support for elementwise permute - #14406

Merged
evedovelli merged 4 commits into
developfrom
users/evedovel/hiptensor/permute-bf16
Oct 29, 2026
Merged

evedovelli merged 4 commits into
developfrom
users/evedovel/hiptensor/permute-bf16

Conversation

@evedovelli

@evedovelli evedovelli commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

hiptensorPermute supports f16, f32 and f64 but rejects bf16, even though the binary elementwise op already registers bf16 and the CK ops used by permute (Scale, PassThrough, HiptensorUnaryOp) all have bhalf_t overloads. The CPU reference already registers bf16 permute for ranks 2-6, so only the device path, type validation and tests were missing.

While adding this, I found that the readVal(void const*, hiptensorComputeDescriptor_t) overload has no HIPTENSOR_COMPUTE_DESC_16BF case and returns 0. A bf16 scalar would have been silently treated as 0 on both the GPU and CPU-reference sides, so GPU-vs-CPU tests could not catch it. The same bug already affects two shipped APIs:

  • hiptensorElementwiseBinaryExecute with a bf16 scalar type (alpha, gamma).
  • hiptensorReduce with a bf16 compute type (alpha, beta). Its existing bf16-compute test cases were comparing all-zero outputs.

The PR also fills test-coverage gaps for f64 permute (from #13081) that turned up during review.

ISSUE ID : AIHIPTENS-296

Technical Details

  1. Type table (hiptensor_elementwise_permute.cpp): adds {R_16BF, R_16BF} and {R_16BF, R_32F}. Also fixes the table comment, whose columns are typeA, typeScalar.
  2. Output type validation (hiptensor_elementwise_permute.cpp): hiptensorPermute now also requires the output tensor's data type to match the input's, as the binary op already does for C and D. Previously a mismatch (e.g. bf16 in, f32 out) passed validation and failed later with HIPTENSOR_STATUS_INTERNAL_ERROR ("Selected kernel is unable to solve the problem"). It now returns HIPTENSOR_STATUS_NOT_SUPPORTED.
  3. Instances: 10 new files, elementwise_permute_solution_rank{2..6}_bhalf{,_noop}_instances.cpp, registering ck::Tuple<ck::bhalf_t> with the existing CkPermutationUnaryCombinedOp and CkPermutationPassThroughCombinedOp. No new op struct is needed: CK Scale<bhalf_t, bhalf_t> multiplies in float with a float scale.
  4. Kernel parameters: in GetInstances, ranks 2-4 reuse the double branches (256/64/64/4/4, vector width 4 with 2 and 1 as fallbacks). The matching fallback hashes are added in instance_params.cpp. Untuned, as for f64. Ranks 5-6 use the existing type-generic branch. The tuned selection tables have no R_16BF keys, so lookup falls back to the default parameters, which are registered.
  5. readVal fix (data_types_impl.hpp): adds the HIPTENSOR_COMPUTE_DESC_16BF case. This overload is used by the elementwise ops (permute, binary, trinary) and by reduction (hiptensorReduce and its CPU reference). Contraction uses the ScalarData specialization and is not affected.
  6. Tests
    • elementwise_test.cpp / elementwise_resource.cpp:

      • bf16 in the type check.
      • checkDevice, gated on isF16Supported() as in reduction.
      • Alpha storage for a bf16 compute type.
      • Element printing; this also adds an FP64 branch, since doubles were being printed as _Float16.
      • Validation via compareEqualLaunchKernel<hip_bfloat16>.
      • Random fill.
    • util_test: readValComputeDescTest (fails before the fix).

    • elementwise_mode_test: PermutationWithMismatchedOutputTypeReturnsNotSupported covers item 2.

    • elementwise_cpu_test: new permute cases that compare the CPU reference against hard-coded expected values:

      • CompareBF16ResultWithReference: bf16 scalar of 2.1. Also a regression test for the readVal fix.
      • CompareBF16WithF32ScalarResultWithReference: bf16 data with an f32 scalar.
      • CompareF64ResultWithReference: expected values and alpha kept in double, with a 1e-12 tolerance, so a float-precision regression (about 1e-8) fails. It is skipped with GTEST_SKIP() on GPUs without FP64. Without that, creating the f64 descriptor returns ARCH_MISMATCH and ctest would skip the whole binary there.

      The shared helper now uses explicit casts so it compiles for hip_bfloat16; f32 and f16 are unchanged.

    • test/utils.hpp: compareEqual no longer truncates double values to float. This also tightens the f64 comparison in reduction_cpu_impl_test, which still passes.

    • Permutation configs, ranks 2-6:

      Category Entries added
      validation/quick, emulation/quick [16BF,16BF]
      validation/standard, validation/comprehensive [16BF,16BF], [16BF,32F]
      emulation/full [64F,64F], [16BF,16BF], [16BF,32F]
      bench [16BF,16BF], [16BF,32F], [64F,64F] (f64 last, see below)
      • validation/full stays empty because it inherits the lower tiers.
      • emulation/full gets its own entries because ffm-full does not inherit from ffm-quick.
      • Elementwise code coverage reads emulation/quick, which has bf16; test/02_elementwise/configs/code_coverage/ is not used for elementwise.
  7. Docs: permutation rows in the API reference data type table and the installation test descriptions. CHANGELOG.md, under 2.6.0:
    • Added: bf16 permute.
    • Changed: permute output-type validation.
    • Resolved issues: bf16 scalar read as 0 in hiptensorElementwiseBinaryExecute and hiptensorReduce.
  8. TESTING.md:
    • Elementwise type coverage per op.
    • Four previously undocumented test binaries (batched_contraction_test, elementwise_mode_test, elementwise_broadcast_test, elementwise_output_strides_test).
    • Where coverage configs come from.
    • An updated new-data-type checklist (emulation/full, bench, CPU-reference case).
    • New known gaps.

Test Plan

Built with -DGPU_TARGETS=<arch> and run on real hardware:

  • ctest -R 'elementwise|util_test|reduction' (28 tests).
  • elementwise_cpu_test, elementwise_mode_test and reduction_cpu_impl_test directly.
  • The permute binaries with the emulation/full and bench configs (bench with validation off, as in CI).
  • The reduction binaries with their default (validation/standard) configs, which include bf16 compute.
  • Counts of bf16 and f64 rows per rank, to confirm the cases actually ran rather than being skipped.

Test Result

Final head (all fixes), plus the reduction suite:

Arch ctest elementwise_cpu_test elementwise_mode_test Reduction suite
gfx950 (MI350X) 28/28 9/9 12/12 pass, incl. bf16
gfx90a (MI210) 28/28 9/9 12/12 pass, incl. bf16
gfx908 (MI100) 28/28 8 pass, f64 skipped 12/12 pass (f64 skipped)
gfx1100 28/28 8 pass, f64 skipped 12/12 pass (f64 skipped)

Permute data-type coverage from the earlier rounds:

Arch bf16 permute (emulation/full, bench) f64 permute (emulation/full, bench)
gfx942 (MI300X) pass, ranks 2-6 pass, ranks 2-6
gfx950 pass, ranks 2-6 pass, ranks 2-6
gfx90a pass, ranks 2-6 emulation/full pass; bench ranks 2 and 4 run out of memory*
gfx908 emulation/full pass; bench ranks 3, 5, 6* n/a
gfx1100 emulation/full pass; bench ranks 3, 5* n/a
gfx1201 standard config: 60/180/1152/2880/5760 bf16 cases per alpha type (ranks 2-6), all passing n/a

gfx942 is not validated at the final head. On the only gfx942 node available, rank1_reduction_test passed 48/48 and then hung in the GPU driver during process exit (uninterruptible, synchronize_srcu). That is a node issue, not a test failure, but the remaining reduction ranks and the review fixes did not run on gfx942.

* Bench out-of-memory failures (documented in TESTING.md Known Gaps):

  • gfx908 (32 GB) and gfx1100 (24 GB) run out of memory on the large f32 bench shapes, before any bf16 entry. This is pre-existing.
  • gfx90a (64 GB) runs out of memory only on the largest f64 shapes. f64 is listed last in the bench configs, so every other type completes first.

Known gaps / follow-ups (not addressed here)

  • The elementwise binary test harness has no bf16 support, so the binary bf16 path (accepted by the API) still has no end-to-end test. The readVal part of the binary fix is covered by util_test.
  • bf16 permute uses the untuned f64 kernel set for ranks 2-4. f16 gets tuned, wider-vector kernels for the same 2-byte element. A follow-up could key bf16 lookups and instances to the f16 tuning; the new bf16 bench entries give a baseline.
  • The per-type validation blocks in PermutationTest::RunKernel are near-identical copies; a templated helper would remove the duplication.

Submission Checklist

@therock-pr-bot

therock-pr-bot Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

@Ryker0627 Ryker0627 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a well-executed PR. The registration and test-config coverage are complete, and the documentation is accurate. You can ignore the two minor comments.

@evedovelli
evedovelli added this pull request to stack #14658 October 29, 2026 15:07
@evedovelli
evedovelli merged commit 44bf947 into develop Oct 29, 2026
89 checks passed
@evedovelli
evedovelli deleted the users/evedovel/hiptensor/permute-bf16 branch October 29, 2026 15:11
evedovelli added a commit that referenced this pull request Oct 29, 2026
…ary (#14564)

## Motivation

Elementwise binary accepts six data type combinations: f16, bf16, f32
and f64, with f16 and bf16 also allowed an f32 scalar. Trinary only
accepted f16, f32 and f64 with a same-type scalar. Binary's bf16 and
f16-with-f32-scalar paths were also accepted by the API but never tested
end to end; no test config used them. This PR gives trinary the same
support as binary and tests all six combinations for both ops. Stacked
on the elementwise-test refactor PR; retarget to `develop` after it
merges.

ISSUE ID : AIHIPTENS-589

## Technical Details

  **Library**
- `hiptensor_elementwise_trinary.cpp`: the type table adds
`{16F,16F,16F,32F}`, `{16BF,16BF,16BF,16BF}` and `{16BF,16BF,16BF,32F}`
(typeA, typeB, typeC, typeScalar), matching binary.
- `hiptensorElementwiseTrinaryExecute` now returns
`HIPTENSOR_STATUS_NOT_SUPPORTED` when D's data type differs from C's, as
binary already does. Previously the mismatch failed later with
`HIPTENSOR_STATUS_INTERNAL_ERROR`.
- `elementwise_trinary_solution_rank{2..6}_half_instances.cpp`: register
`ck::Tuple<bhalf_t, bhalf_t, bhalf_t>` alongside half, as the binary
half files already do.
- No new op code: trinary uses the same `HiptensorBinaryOp` and
`UnaryCombinedOp` that binary bf16 already compiles with.
- The trinary kernel parameters and fallback hashes are type-generic.
    - The CPU reference already registered bf16 trinary.
- Trinary scalars are read with `readVal<float>`, which handles a bf16
compute descriptor since #14406.

  **Tests**
- Binary and trinary harnesses: `checkDevice` accepts bf16 (gated on
`isF16Supported()`). Scalar writes, comparison and printing already go
through the type-dispatch helper from the refactor PR.
  - Configs, binary and trinary, ranks 2-6:

    | Category | Entries added |
    |---|---|
    | `validation/quick`, `emulation/quick` | `[16BF,16BF]` |
| `validation/standard`, `emulation/full`, `bench` | `[16F,32F]`,
`[16BF,16BF]`, `[16BF,32F]` |

`validation/comprehensive` and `validation/full` stay empty for these
ops, as before.
- `elementwise_cpu_test`: three new cases each for binary and trinary:
bf16, bf16 with an f32 scalar, and f16 with an f32 scalar. The shared
helpers use explicit casts so they compile for `hip_bfloat16`; f32 and
f16 values are unchanged.
- `elementwise_mode_test`:
`TrinaryWithMismatchedOutputTypeReturnsNotSupported`. It uses f16 for D
so it runs on GPUs without FP64.

  **Docs**
- `api-reference.rst`: the old "Element-wise Operations" rows (`f16 /
f32 / -` etc.) are replaced by one "Element-wise (Permutation, binary,
trinary)" group. All three ops now support the same six combinations. A
new note says that for element-wise ops all tensors share one data type
and the compute type is the scalar type.
- `installation.rst`: binary and trinary test descriptions include
bfloat16.
- `CHANGELOG.md` (2.6.0): Added (trinary bf16, and f16 with an f32
scalar); Changed (trinary output-type check).
- `TESTING.md`: binary, trinary, CPU-reference and mode-test rows
updated; the "binary BF16 untested" known gap removed.

## Test Plan

Built with `-DGPU_TARGETS=<arch>` and run on real hardware:
  - `ctest -R 'elementwise|util_test'` (21 tests).
  - `elementwise_cpu_test` and `elementwise_mode_test` directly.
- Binary and trinary binaries, ranks 2-6, default
(`validation/standard`) config, counting validated cases per type pair.
- Rank 3 with the `emulation/full` and `bench` configs (bench with
validation off).

## Test Result

| Arch | binary + trinary, all type pairs, ranks 2-6 |
`elementwise_cpu_test` | `elementwise_mode_test` | ctest |
  |---|---|---|---|---|
  | gfx942 (MI300A) | 6 pairs pass | 15/15 | 13/13 | 21/21 |
  | gfx950 (MI350X) | 6 pairs pass | 15/15 | 13/13 | 21/21 |
  | gfx90a (MI210) | 6 pairs pass | 15/15 | 13/13 | 21/21 |
| gfx908 (MI100) | 5 pairs pass (no FP64) | 14, FP64 skipped | 13/13 |
21/21 |
| gfx1201 | 5 pairs pass (no FP64) | 14, FP64 skipped | 13/13 | 21/21 |

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.
@assistant-librarian

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants