Skip to content

feat(tensilelite): build a solution record in C++ - #13573

Open
jaopaulolc wants to merge 7 commits into
developfrom
users/jolabega/jit-upstream-00b-solution-state
Open

jaopaulolc wants to merge 7 commits into
developfrom
users/jolabega/jit-upstream-00b-solution-state

Conversation

@jaopaulolc

@jaopaulolc jaopaulolc commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

JIRA ID

  • AIHPBLAS-4548
  • AIHPBLAS-5160

Motivation

solutionFromState builds a ContractionSolution in C++ from the state Solution.FromOriginalState reads. This pull request is the builder, its tests and fixtures, and the CMake wiring. It is based on develop; the MessagePack writer it was stacked on has merged.

Technical Details

TensileLite::solutionFromState reads one MessagePack map and returns a ContractionSolution: name, kernel name, problem type, predicates, size mapping, custom kernel, internal-argument support, and the other fields LoadLibraryFile stores. It does not build originalSolution.

The record follows the Python builder for the fields that had drifted:

  • PersistentLoopArgsVersion 0, 1, and 2. Version 2 requires StreamK with a static grid, a ClusterDim axis above 1, and KernArgsVersion 3.
  • Mixed MX operands (FP8, BF8, FP6, BF6, FP4) take DataTypeA and DataTypeB as the compute inputs when both MAC inputs are still the DataType default.
  • Int4 A records ScaleBlockSizeA (an Or when several of 32, 64, and 128 are listed), ScaleZeroPointA, and Int4EncodingA.
  • StochasticRounding, CustomKernelSizePredicate (SizeGreaterThan, SizeLessThan, SizeEqual), and the size-mapping fields the performance model reads: WavefrontSize, 1LDSBuffer, SourceSwap, UnrollMajorLDSA/B, enableLDSTrA/B, BufferLoad, and BufferStore. Absent BufferLoad and BufferStore stay true. Absent UnrollMajorLDS* stay -1.
  • A bad Ideals key is std::runtime_error naming the key.

WorkgroupMappingXCC is stored with a compute-unit count of 0. Building the record does not query a GPU. A gfx950 DeviceNames entry such as Device 75a0 becomes a PCI chip-id predicate.

test_solution_state_catalog.py compares the hand-copied data-type names and build chip ids with rocisa.enum.DataTypeEnum and GFX_CHIP_IDS. The chip ids live in Tensile/Architectures.hpp, so other C++ code can use the same list.

The C++ record is checked against the Python builder in two ways. SolutionRecord.MatchesThePythonBuilder compares the record lines, and SolutionRecord.PredicateValuesMatchThePythonBuilder compares the problem, task and hardware predicates, values included, with the {type, index, value} form of the checked-in *.predicates.yaml. SolutionRecord.FixturesAreComplete fails when a fixture file is missing. The fixtures under tensilelite/Tests/unit/test_data/solution_records are ten derived states: three plain fp16 kernels (gfx90a, gfx942, gfx950) and gfx942 stochastic rounding, gfx942 StreamK fp8, gfx950 MX, gfx950 MX with free blocks, gfx1151 int4, gfx1250 StreamK cluster, and gfx1250 StreamK cluster with ReuseAcrossPersistent and an occupancy probe. python tensilelite/Tests/unit/test_solution_state_record.py regenerates them, and derive_solution_records.py derives states from library logic files and benchmark configs. Changing, for example, a predicate value, the A and B types of TypesEqual, MXBlockFreeA, occupancyProbe or the int4 encoding in SolutionState.cpp makes at least one of these tests fail.

The record tests read their fixtures through TestData::Instance(), from the data directory next to the test binary. A build step copies the fixtures there on every build, so a binary run on another machine from an installed copy finds them. A missing fixture fails with its path.

Test Plan

Host build, gfx950, Release.

  • tensilelite-tests --gtest_filter='SolutionState.*:SolutionRecord.*'
  • pytest tensilelite/Tests/unit/test_solution_state_record.py tensilelite/Tests/unit/test_solution_state_catalog.py

Test Result

On 4806b97c35a, on a gfx950 host build (Release, build-13573): tensilelite-tests ran 854 tests, 848 passed, 6 were skipped (they need a device feature this host lacks) and none failed. That includes 15 SolutionState and 4 SolutionRecord gtests. pytest on test_solution_state_record.py and test_solution_state_catalog.py passed 48 tests. The tensilelite-tests binary and its data directory also passed when copied to another directory and run from there.

The gtests are part of tensilelite-tests, which the TheRock Test tensilelite job runs on gfx94X. The pytests run in the TensileLite coverage / Linux lane, in the TheRock Test tensilelite job, and in the Math CI preliminary and tensilelite-unit-codecov jobs. Check results for this head are on the pull request.

Submission Checklist

Risk level

Medium. A record built here is what a later library write stores. The new cases are the ones the Python builder already accepts: cluster StreamK, mixed MX operands, int4 group scales, stochastic rounding, exact-size bounds, and the size-mapping fields. Rollback is reverting this pull request.

@therock-pr-bot

therock-pr-bot Bot commented Oct 13, 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

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

Copy link
Copy Markdown

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

@talumbau
talumbau requested review from Alex-Vasile, davidd-amd and newling and removed request for newling October 13, 2026 21:10
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 6cb295f to 75035b4 Compare October 13, 2026 22:21
@jaopaulolc jaopaulolc added the rocm:gemm algos Label to identify PRs of the GEMM Algorithms team. label Oct 13, 2026
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #13573      +/-   ##
===========================================
- Coverage    78.09%   78.09%   -0.00%     
===========================================
  Files         3005     3005              
  Lines       521953   522055     +102     
  Branches     81452    81485      +33     
===========================================
+ Hits        407579   407656      +77     
- Misses       85459    85475      +16     
- Partials     28915    28924       +9     
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 53.89% <ø> (+<0.01%) ⬆️
TensileLite-Unit 85.08% <ø> (+0.01%) ⬆️
hipBLAS 90.62% <ø> (ø) Carriedforward from fa9e0a5
hipBLASLt 70.07% <ø> (-<0.01%) ⬇️
hipCUB 82.68% <ø> (ø) Carriedforward from fa9e0a5
hipDNN 87.34% <ø> (ø) Carriedforward from fa9e0a5
hipFFT 42.65% <ø> (ø) Carriedforward from fa9e0a5
hipRAND 76.12% <ø> (ø) Carriedforward from fa9e0a5
hipSOLVER 69.67% <ø> (ø) Carriedforward from fa9e0a5
hipSPARSE 87.03% <ø> (ø) Carriedforward from fa9e0a5
rocALUTION 82.89% <ø> (ø) Carriedforward from fa9e0a5
rocBLAS 69.20% <ø> (ø) Carriedforward from fa9e0a5
rocFFT 46.49% <ø> (ø) Carriedforward from fa9e0a5
rocRAND 57.32% <ø> (ø) Carriedforward from fa9e0a5
rocSOLVER 76.66% <ø> (ø) Carriedforward from fa9e0a5
rocSPARSE 83.61% <ø> (ø) Carriedforward from fa9e0a5
rocThrust 93.15% <ø> (ø) Carriedforward from fa9e0a5

*This pull request uses carry forward flags. Click here to find out more.
see 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from c5dae21 to 0b08d00 Compare October 15, 2026 22:52
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 70bc297 to fd9cdb2 Compare October 16, 2026 17:42
@jaopaulolc
jaopaulolc requested review from Alex-Vasile and talumbau and removed request for Alex-Vasile, davidd-amd and talumbau October 22, 2026 20:14
@jaopaulolc
jaopaulolc requested review from Alex-Vasile and removed request for Alex-Vasile November 4, 2026 16:36
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 15f1e26 to 2832364 Compare November 4, 2026 18:38
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 2832364 to 1e2d140 Compare November 5, 2026 14:30
Base automatically changed from users/jolabega/jit-upstream-00-msgpack-writer to develop November 5, 2026 21:57
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 1e2d140 to 0d1cffb Compare November 5, 2026 21:57
@jaopaulolc
jaopaulolc requested review from Alex-Vasile and removed request for Alex-Vasile November 5, 2026 21:58
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 0d1cffb to 1fc8739 Compare November 6, 2026 15:15

@ThanHenderson ThanHenderson 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.

Approving, but consider addressing my concerns with the architecture IDs. Could be a follow up though too.

@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 1fc8739 to 533ce1a Compare November 6, 2026 15:26

@Alex-Vasile Alex-Vasile 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.

Thanks for the fixes since the last round. The gaps I raised in the builder are closed in the code. I am requesting changes for three reasons, each with an inline comment:

  1. The two new SolutionRecord gtests fail in the TheRock Test tensilelite job (gfx94X, job 128031777598: 840 of 852 gtests pass, these 2 fail).
  2. SolutionState.SizeMappingOptionalsFollowTheState reads freed memory.
  3. The record check compares predicate type names only, so it does not yet catch most drift between the Python and C++ builders.

The description also still cites ec66cfb1f05 with 14 tests, says the automated checks are pending, and says the PR is based on the MessagePack writer branch. Could it be refreshed with the final commit, the current test counts (15 SolutionState gtests, 2 SolutionRecord gtests, 16 pytests) and which lane ran each?

"${CMAKE_CURRENT_SOURCE_DIR}/SolutionRecord_test.cpp"
)
target_compile_definitions(tensilelite-tests PRIVATE
"TENSILELITE_SOLUTION_RECORD_DIR=\"${SOLUTION_STATE_RECORD_DIR}\""

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.

These two definitions put the build-tree path into the tensilelite-tests binary, and the TheRock Test tensilelite job runs that binary on a different runner from the one that built it. In job 128031777598 the path /__w/rocm-libraries/rocm-libraries/build/math-libs/BLAS/hipBLASLt/build/tensilelite/tests/data/solution-state-catalog does not exist, so CatalogMatchesSolutionState compares the C++ lists with {} and MatchesThePythonBuilder stops at ASSERT_FALSE(bytes.empty()) for all three fixtures. Those are the 2 failures in that job. Math CI passes the same tests only because it builds and runs in one tree.

The other tests in this directory find their data through TestData::Instance(), which resolves <directory of the test binary>/data (tests/TestData.cpp:119). The rocm_install(DIRECTORY "${TEST_DATA_DIR}/" ...) step copies that directory next to the installed binary, so files placed under TEST_DATA_DIR are found there. Could these tests use the same lookup instead of a compile-time path?

Also, fileLines() in SolutionRecord_test.cpp returns an empty list when a file is missing, which is why the catalog failure reads as "lists differ". Could it fail with the missing path instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4806b97c35a. The compile-time path definitions are gone. SolutionRecord_test.cpp now finds the fixtures through TestData::Instance(), in the data directory next to the test binary, like the neighboring tests. A build target, tensilelite-solution-state-fixtures, copies the fixtures into TEST_DATA_DIR on every build, so the rocm_install(DIRECTORY ...) step carries them next to an installed binary. The readers no longer return an empty list for a missing file: they fail with the path, for example cannot open test fixture <dir>/data/solution-state/catalog/catalog_chip_ids.txt. I checked this the way the TheRock job runs it: I copied the tensilelite-tests binary and its data directory to another directory and ran SolutionRecord.* and SolutionState.* from there, and they passed; with the catalog file deleted from the copy, the test failed with the message above.

StateOptions options;
options.oneLdsBuffer = true;
options.bufferLoadOff = true;
auto const& size = build(options)->sizeMapping;

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.

build(options) returns a shared_ptr<ContractionSolution>, and ->sizeMapping is a member of the object it points to. The temporary shared_ptr is destroyed at the end of this statement, so size refers to freed memory on lines 730-733. Binding a reference to a member does not keep the temporary alive. An AddressSanitizer build of this test (a local run, not CI) reports a heap-use-after-free at line 730. CI passes because the freed memory still holds the old values.

Could the result be kept in a named variable (auto solution = build(options);) and read through solution->sizeMapping? Line 692 is fine, because build(options)->problemType.stochasticRounding is read inside one statement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4806b97c35a. SizeMappingOptionalsFollowTheState now keeps auto solution = build(options); in a named variable and takes size from solution->sizeMapping. I went through SolutionState_test.cpp and SolutionRecord_test.cpp for other references or views bound to a member of a temporary returned by value and found none left. I reproduced your report with an AddressSanitizer run of the old test (heap-use-after-free at line 730); the same run on the fixed test, and on the SolutionRecord and SolutionState tests, reports nothing.

add("problemType.stochasticRounding", jsonBool(problem.stochasticRounding));

std::vector<std::string> tags;
collect(*solution.problemPredicate, tags);

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 compares predicate type names only: collect() (line 117) pushes predicate.type(), so no value inside a predicate is compared in C++. The values are in the checked-in *.predicates.yaml files, but only test_solution_state_record.py reads them. The three fixtures are also one fp16 kernel on three ISAs (MacroTile0 = MacroTile1 = DepthU = 16, GlobalReadVectorWidthA = GlobalReadVectorWidthB = 4), so swapped A/B or M/N values would give the same output.

In a local copy of SolutionState.cpp, each of these changes left SolutionRecord.MatchesThePythonBuilder and SolutionState.* passing: GlobalSplitUCheckMinK 32 changed to 64; the A and B types in TypesEqual swapped; the ReuseAcrossPersistent block deleted; the MXBlockFreeA predicate dropped; occupancyProbe ignored; Int4EncodingA::UnsignedBias8 read as Signed. That is the kind of divergence this check was added to catch, and the last four are the same class of gap that came up in the earlier threads.

Could the C++ side compare predicate values as well, for example by writing the predicates in the {type, index, value} form that predicates.yaml uses, and could the fixtures include a kernel with different tile sizes and at least one non-default feature (MX, int4, StreamK, ReuseAcrossPersistent or an occupancy probe)? The C++ builder and the Python builder are both maintained, so this check is the only thing that fails when they differ.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 4806b97c35a. The C++ side now compares predicate values, not only type names: SolutionRecord.PredicateValuesMatchThePythonBuilder projects the problem, task and hardware predicate trees into the {type, index, value} form of the checked-in *.predicates.yaml and compares them as a deep comparison, reporting the path of the first mismatch. SolutionRecord.FixturesAreComplete checks every fixture has all its files, and the record test discovers fixtures from the data directory instead of a fixed list. The fixtures are now ten derived states: the three plain fp16 kernels, plus gfx942 stochastic rounding, gfx942 StreamK fp8, gfx950 MX, gfx950 MX with free blocks, gfx1151 int4 (several scale block sizes, UnsignedBias8), gfx1250 StreamK cluster, and gfx1250 StreamK cluster with ReuseAcrossPersistent and an occupancy probe; they have different tile sizes, depth and vector widths, so swapped A/B or M/N values show up.

I applied your six mutations one at a time to SolutionState.cpp; each now fails at least one test: GlobalSplitUCheckMinK 32 to 64 and swapped A/B types in TypesEqual and Int4EncodingA::UnsignedBias8 read as Signed fail PredicateValuesMatchThePythonBuilder; dropping the ReuseAcrossPersistent block and dropping MXBlockFreeA fail both MatchesThePythonBuilder and PredicateValuesMatchThePythonBuilder; ignoring occupancyProbe fails MatchesThePythonBuilder. The stricter comparison also found real differences, which are fixed: the MX predicate order in the C++ builder differed from Python, and the test projection was missing customKernel.args padding and the occupancyProbe line. I also refreshed the description with the final commit and the test counts.

TheRock runs this test from build/share/hipblaslt/tensilelite, which has
no src/msgpack. Look up the checkout that produced that copy.
solutionFromState used the test constructor, which freezes a compute-unit
count of 0 and never queries the GPU, so every XCC kernel was rejected.
Building the record still leaves the compute-unit count at 0. Evaluating
the check reads the device, so a frozen count of 0 cannot reject every kernel.
solutionFromState and FromOriginalState are kept by a checked-in record of
the three plain kernels. A Python-side field change fails the pytest until
that record is regenerated, and the regenerated lines fail the gtest until
the C++ builder matches them.
…g walk

The installed pytest tree has no SolutionState.cpp, and the plain-kernel
YAML was collected as a tensilelite config. The catalog now compares Python
to a checked-in list, and the fixtures live under test_data so findConfigs
skips them.
Read MX block-free and occupancy-probe fields, align the
ReuseAcrossPersistent predicates with develop, and make the catalog test
compare exported tables while loading fixture data from the test data dir.
@jaopaulolc
jaopaulolc force-pushed the users/jolabega/jit-upstream-00b-solution-state branch from 533ce1a to 4806b97 Compare November 6, 2026 19:28

This branch has not been deployed

No deployments
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.

4 participants