⚡️ Add MLIR mapping performance benchmark - #1955
Conversation
Add deterministic frontier- and routing-heavy workloads plus a paired A/B runner that records raw timings, build metadata, robust summaries, and bootstrap confidence intervals. Document correctness and route-quality validation limits separately from timing. Assisted-by: GPT-5.6 via Codex
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@MatthiasReumann FYI: this is what GPT-5.6-sol came up with when asked for a benchmarking suite for the mapping pass. |
MatthiasReumann
left a comment
There was a problem hiding this comment.
Thanks @burgholzer! Looks really promising 👀
I've left two comments - nothing dramatic - with some additional ideas.
| const auto start = std::chrono::steady_clock::now(); | ||
| const auto result = pm.run(module.get()); | ||
| const auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>( | ||
| std::chrono::steady_clock::now() - start); | ||
| if (failed(result)) { | ||
| llvm::errs() << "mapping failed\n"; | ||
| return 1; | ||
| } | ||
| if (failed(verify(module.get()))) { | ||
| llvm::errs() << "mapped module verification failed\n"; | ||
| return 1; | ||
| } | ||
| std::cout << elapsed.count() << '\n'; | ||
| return 0; |
There was a problem hiding this comment.
Instead of wrapping pm.run we could also use MLIR's pass instrumentation. In my local benchmarks I implemented it as follows.
struct PassExecutionStats : public mlir::PassInstrumentation {
PassExecutionStats(Statistics &stats) : stats_(&stats) {}
void runBeforePass([[maybe_unused]] mlir::Pass *pass,
mlir::Operation *op) override {
nswapsPrev_ = countSwaps(op);
t0_ = std::chrono::steady_clock::now();
}
void runAfterPass([[maybe_unused]] mlir::Pass *pass,
mlir::Operation *op) override {
const auto t1 = std::chrono::steady_clock::now();
const auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0_).count();
const auto nswapsAfter = countSwaps(op);
stats_->emplace("time", duration);
stats_->emplace("nswaps", nswapsAfter - nswapsPrev_);
}
private:
std::size_t countSwaps(mlir::Operation *op) {
std::size_t cnt{0};
op->walk([&cnt](mlir::qco::SWAPOp) { ++cnt; });
return cnt;
}
Statistics *stats_;
std::chrono::steady_clock::time_point t0_;
std::size_t nswapsPrev_;
};int main() {
...
pm.addInstrumentation(
std::make_unique<support::PassExecutionStats>(stats));
}Not sure if this brings any real benefit, but I think mlir-timing is also implemented this way.
| return couplingSet; | ||
| } | ||
|
|
||
| OwningOpRef<ModuleOp> makeProgram(MLIRContext& context) { |
There was a problem hiding this comment.
I really like this idea! Eventually it would be really awesome if we could simply use mqt-bench in Python to generate QASM circuits and run the mapping pass one those. Maybe this is even possible today - Codex could probably implement this with relative ease!
There was a problem hiding this comment.
If this was possible, we could also think about using this setup as a test-framework instead of the current unit-tests.
🤖 AI text below 🤖
Summary
Motivation
Follow-up to #1930. The local harness used during that review was useful for separating genuine mapping-pass improvements from behavior changes. Publishing it provides a shared starting point for iterating on representative workloads, measurement methodology, and performance-regression evaluation.
The benchmark is opt-in through
BUILD_MQT_CORE_BENCHMARKSand does not change production behavior.Validation
mqt-core-mlir-mapping-evalin release mode with MLIR 22.1.3Draft iteration points