Repository navigation
fix(tensilelite): filter unsupported StreamK benchmark kernels #11872
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -104,6 +104,20 @@ namespace TensileLite | |
| return false; | ||
| } | ||
|
|
||
| // The all-solutions benchmark path selects kernels by index instead | ||
| // of going through a SolutionLibrary search. Apply the dynamic | ||
| // StreamK topology predicate explicitly so this path has the same | ||
| // support boundary as normal library selection. In particular, a | ||
| // gfx942 MI300A reports six XCDs while these kernels bake eight | ||
| // per-XCD queues; launching one is invalid, but static StreamK and | ||
| // non-StreamK solutions in the same config remain runnable. | ||
|
davidd-amd marked this conversation as resolved.
Resolved
|
||
| if(!solution.streamKDynamicQueueSupported(problem, *m_hardware)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new call can throw an exception at a call site that is not protected.
TENSILE_ASSERT_EXC(hipAMDGPU != nullptr);
TENSILE_ASSERT_EXC(hipAMDGPU->analyticalHardware != nullptr); // std::runtime_error
The conditions are narrow: an origami-unknown architecture, an SK5 solution, and AUTO mode or Suggested change in if(!streamKDynamicQueueUnsupported(hardware))
return true;
// Unknown hardware: streamK5EffectiveDynamic() needs analyticalHardware and
// throws without it. The policy for unknown hardware is UNSUPPORTED.
auto const* hipAMDGPU = dynamic_cast<hip::HipAMDGPU const*>(&hardware);
if(hipAMDGPU == nullptr || hipAMDGPU->analyticalHardware == nullptr)
{
warnStreamKDynamicQueueUnsupportedOnce(hardware);
return false;
}
const bool dynamicQueue = ...Why this is correct. The exception is possible only when |
||
| { | ||
| if(isReportValid) | ||
| m_reporter->report(ResultKey::Validation, "UNSUPPORTED_XCD_TOPOLOGY"); | ||
| return false; | ||
| } | ||
|
|
||
| // Test if the persistent kernel is eligible for the current hw and solution | ||
| problem.checkPersistentKernelEligibility(solution, *m_hardware); | ||
| Task task(*m_hardware, problem, solution); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,8 @@ | |
| #include <gtest/gtest.h> | ||
| #include <limits> | ||
| #include <memory> | ||
| #include <sstream> | ||
| #include <utility> | ||
|
|
||
| #include <hip/hip_runtime.h> | ||
|
|
||
|
|
@@ -19,6 +21,8 @@ | |
| #include <origami/streamk.hpp> | ||
|
|
||
| #include "FallbackTestUtils.hpp" | ||
| #include "LogReporter.hpp" | ||
| #include "SolutionIterator.hpp" | ||
|
|
||
| using namespace TensileLite; | ||
| using namespace TensileLite::testing; | ||
|
|
@@ -900,6 +904,34 @@ namespace | |
| makeGfx942HardwareWithXcd(numXCD)); | ||
| return device; | ||
| } | ||
|
|
||
| class TestSolutionIterator : public Client::SolutionIterator | ||
| { | ||
| public: | ||
| explicit TestSolutionIterator(std::shared_ptr<Hardware> hardware) | ||
| : SolutionIterator(nullptr, std::move(hardware), false) | ||
| { | ||
| } | ||
|
|
||
| bool accepts(ContractionSolution& solution, | ||
| ContractionProblemGemm& problem, | ||
| bool reportResult) | ||
| { | ||
| return checkSolution(solution, problem, reportResult); | ||
| } | ||
|
|
||
| void postProblem() override {} | ||
| void preSolution(ContractionSolution* const) override {} | ||
| void postSolution() override {} | ||
| bool moreSolutionsInProblem() const override | ||
| { | ||
| return false; | ||
| } | ||
| std::shared_ptr<ContractionSolution> getSolution() override | ||
| { | ||
| return nullptr; | ||
| } | ||
| }; | ||
| } // namespace | ||
|
|
||
| TEST(StreamKDynamicQueueXcdGateTest, RejectsMi300aSixXcd) | ||
|
|
@@ -1000,6 +1032,34 @@ TEST(StreamKDynamicQueueXcdGateTest, KeepsNonDynamicQueueSolutionsOnMi300a) | |
| << "SK3-static solution must remain selectable on MI300A"; | ||
| } | ||
|
|
||
| TEST(StreamKDynamicQueueXcdGateTest, ClientIteratorFiltersOnlyUnsupportedDynamicQueue) | ||
| { | ||
| auto mi300a = std::make_shared<hip::HipAMDGPU>(makeGfx942DeviceWithXcd(6)); | ||
| TestSolutionIterator iterator(mi300a); | ||
| auto problem = makeGemmProblem(512, 512, 512); | ||
| std::ostringstream reportOutput; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The stream is destroyed before the object that points to it.
There is no fault today, because the destructors are empty and nothing writes to the stream at destruction. But the fault becomes real if a person adds a flush operation to the destructor, and an ASAN build can show it now. Suggested change — declare the stream first: auto mi300a = std::make_shared<hip::HipAMDGPU>(makeGfx942DeviceWithXcd(6));
std::ostringstream reportOutput; // moved up
TestSolutionIterator iterator(mi300a);
auto problem = makeGemmProblem(512, 512, 512);Why this works. The destruction order becomes If you take the change in the comment below, this problem also goes away, because that change removes the stream and the Separate remark on |
||
| auto reporter = std::make_shared<Client::LogReporter>( | ||
| Client::LogLevel::Terse, | ||
| std::initializer_list<std::string>{Client::ResultKey::Validation}, | ||
| reportOutput, | ||
| false, | ||
| false); | ||
| iterator.setReporter(reporter); | ||
|
|
||
| ContractionSolution dynamicSolution; | ||
| initEquality512Solution(dynamicSolution, 4); | ||
| EXPECT_FALSE(iterator.accepts(dynamicSolution, problem, true)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two assertions check the same result, and the
Two results:
This matters because The PR description says the test "verifies both reporting and non-reporting checks". The report path is not checked. Suggested change — record the values with a small double on the pure virtual hook class RecordingReporter : public Client::ResultReporter
{
public:
std::vector<std::pair<std::string, std::string>> reported;
void reportValue_string(std::string const& key, std::string const& value) override
{
reported.emplace_back(key, value);
}
// stub the other pure virtual functions
};auto reporter = std::make_shared<RecordingReporter>();
iterator.setReporter(reporter);
EXPECT_FALSE(iterator.accepts(dynamicSolution, problem, /*reportResult=*/true));
ASSERT_EQ(reporter->reported.size(), 1u);
EXPECT_EQ(reporter->reported[0].second, "UNSUPPORTED_XCD_TOPOLOGY");
reporter->reported.clear();
EXPECT_FALSE(iterator.accepts(dynamicSolution, problem, /*reportResult=*/false));
EXPECT_TRUE(reporter->reported.empty());Why this works. The full row path is difficult to drive, because |
||
| << "The explicit all-solutions client path must not launch an eight-queue " | ||
| "dynamic kernel on a six-XCD MI300A"; | ||
| EXPECT_FALSE(iterator.accepts(dynamicSolution, problem, false)) | ||
| << "Topology filtering must also apply during non-reporting prediction checks"; | ||
|
|
||
| ContractionSolution staticSolution; | ||
| initEquality512Solution(staticSolution, 3); | ||
| EXPECT_TRUE(iterator.accepts(staticSolution, problem, true)) | ||
| << "The topology guard must retain static StreamK coverage on MI300A"; | ||
| } | ||
|
|
||
| // =========================================================================== | ||
| // StreamKFlagBound -- the bound getSKGrid puts on a Stream-K grid. | ||
| // | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order of the checks is not the same as the
softwarePredicatethat this comment names.SolutionLibrary.hpp:91uses the opposite order. It checks the asserts first and the topology rule third:return (*solutions.problemPredicate)(problem) && (*solutions.taskPredicate)(task) && solutions.streamKDynamicQueueSupported(problem, hardware) && solutions.uniformSummationOrderSupported(problem, hardware);Here the new block is before the
problemPredicate/taskPredicateblock at line 124. There are two results:UNSUPPORTED_XCD_TOPOLOGY. The library reportsDID_NOT_SATISFY_ASSERTS. In a tuning log the user reads that the GPU part is wrong, but the real cause is a macro-tile or assert mismatch. This also uses the singlewarnStreamKDynamicQueueUnsupportedOncemessage on a solution that was never usable.Suggested change — move the new block below the assert block:
Why this is correct and safe. The two checks are independent.
streamKDynamicQueueSupportedreads onlysizeMapping.streamK, the XCD counts, and the problem sizes. It does not readm_eligibleForPK.softwarePredicateproves this: it calls the predicate but never callscheckPersistentKernelEligibility(line 122 here is the only call site in the tree). Therefore the set of accepted solutions stays the same. Only the report value for a doubly-failing solution changes, and it changes to the library value. The new order is also less expensive.Also: please make this comment shorter. The same MI300A text is in
ContractionSolution.hpp:728, which this PR also edits, and again in thestreamKDynamicQueueSupportedRefmirror inCuCount_test.cpp. Three copies become different with time. One line that points to the canonical text is enough.