Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 93 additions & 54 deletions mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include <llvm/ADT/STLExtras.h>
#include <llvm/ADT/SmallVector.h>
#include <llvm/ADT/TypeSwitch.h>
#include <llvm/Support/Debug.h>
#include <llvm/Support/Allocator.h>
#include <llvm/Support/ErrorHandling.h>
#include <mlir/Dialect/Func/IR/FuncOps.h>
#include <mlir/IR/Block.h>
Expand All @@ -38,6 +38,7 @@
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <numeric>
#include <optional>
#include <queue>
Expand Down Expand Up @@ -70,6 +71,8 @@
*/
enum class Direction : std::uint8_t { Forward, Backward };

struct LayoutInfo;

/**
* @brief A qubit layout that maps program and hardware indices without
* storing Values. Used for efficient memory usage when Value tracking isn't
Expand Down Expand Up @@ -191,18 +194,6 @@
return programToHardware_.size();
}

void dump() {
llvm::dbgs() << "prog= ";
for (std::size_t i = 0; i < nqubits(); ++i) {
llvm::dbgs() << i << " ";
}
llvm::dbgs() << "\nhw= ";
for (std::size_t i = 0; i < nqubits(); ++i) {
llvm::dbgs() << programToHardware_[i] << ' ';
}
llvm::dbgs() << '\n';
}

protected:
/**
* @brief Maps a program qubit index to its hardware index.
Expand All @@ -215,10 +206,43 @@
SmallVector<IndexType> hardwareToProgram_;

private:
friend struct MappingPass::LayoutInfo;

Layout() = default;
explicit Layout(const std::size_t nqubits)
: programToHardware_(nqubits), hardwareToProgram_(nqubits) {}
};

/**
* @brief Required to use Layout as a key for LLVM maps and sets.
*/
class LayoutInfo {
using Info = DenseMapInfo<SmallVector<IndexType>>;

public:
static Layout getEmptyKey() {
Layout l;
l.programToHardware_ = Info::getEmptyKey();
l.hardwareToProgram_ = Info::getEmptyKey();
return l;
}

static Layout getTombstoneKey() {
Layout l;
l.programToHardware_ = Info::getTombstoneKey();
l.hardwareToProgram_ = Info::getTombstoneKey();
return l;
}

static unsigned getHashValue(const Layout& l) {
return Info::getHashValue(l.programToHardware_);
}

static bool isEqual(const Layout& a, const Layout& b) {
return Info::isEqual(a.programToHardware_, b.programToHardware_);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
MatthiasReumann marked this conversation as resolved.
};

/**
* @brief Parameters influencing the behavior of the A* search algorithm.
*/
Expand All @@ -240,30 +264,34 @@
* @brief Describes a node in the A* search graph.
*/
struct Node {
SmallVector<IndexGate> sequence;
struct ComparePointer {
bool operator()(const Node* lhs, const Node* rhs) const {
return lhs->f > rhs->f;
}
};

Layout layout;
IndexGate swap;
Node* parent;
std::size_t depth;
float f;

/**
* @brief Construct a root node with the given layout. Initialize the
* sequence with an empty vector and set the cost to zero.
*/
explicit Node(Layout layout) : layout(std::move(layout)), f(0) {}
explicit Node(Layout layout)
: layout(std::move(layout)), parent(nullptr), depth(0), f(0) {}

/**
* @brief Construct a non-root node from its parent node. Apply the given
* swap to the layout of the parent node and evaluate the cost.
* swap to the layout of the parent node.
*/
Node(const Node& parent, IndexGate swap, ArrayRef<Layer> layers,
Node(Node* parent, IndexGate swap, ArrayRef<Layer> layers,
const Architecture& arch, const Parameters& params)
: sequence(parent.sequence), layout(parent.layout), f(0) {
// Apply node-specific swap to given layout.
: layout(parent->layout), swap(swap), parent(parent),
depth(parent->depth + 1), f(0) {
layout.swap(swap.first, swap.second);

// Add swap to sequence.
sequence.emplace_back(swap);

// Evaluate cost function.
f = g(params.alpha) + h(layers, arch, params); // NOLINT
}

Expand All @@ -279,12 +307,6 @@
});
}

/**
* @returns true iff. the costs of this node are higher than the one of @p
* rhs.
*/
[[nodiscard]] bool operator>(const Node& rhs) const { return f > rhs.f; }

private:
/**
* @brief Calculate the path cost for the A* search algorithm.
Expand All @@ -293,7 +315,7 @@
* SWAPs.
*/
[[nodiscard]] float g(float alpha) const {
return alpha * static_cast<float>(sequence.size());
return alpha * static_cast<float>(depth);
}

/**
Expand All @@ -319,8 +341,6 @@
}
};

using MinQueue = std::priority_queue<Node, std::vector<Node>, std::greater<>>;

struct [[nodiscard]] TrialResult {
explicit TrialResult(Layout layout) : layout(std::move(layout)) {}

Expand Down Expand Up @@ -383,8 +403,7 @@
public:
using MappingPassBase::MappingPassBase;

protected:
void runOnOperation() override {

Check warning on line 406 in mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

View workflow job for this annotation

GitHub Actions / 🇨‌ Lint / 🚨 Lint

mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp:406:8 [misc-override-with-different-visibility]

visibility of function 'runOnOperation' is changed from protected in class 'Pass' to public
std::mt19937_64 rng{this->seed};
IRRewriter rewriter(&getContext());

Expand Down Expand Up @@ -555,9 +574,9 @@
* @brief Perform A* search to find a sequence of SWAPs that makes the
* two-qubit operations inside the first layer (the front) executable.
* @details
* The iteration budget is then b^{3}, which corresponds to
* exhausting all paths of length up to b^{2} in a search tree with branching
* factor b. A hard cap prevents impractical runtimes on larger architectures.
* The iteration budget is b^{3} node expansions, i.e. roughly a depth-3
* search in a tree with branching factor b. A hard cap prevents impractical
* runtimes on larger architectures.
*
* The branching factor b of the A* search is the product of the
* architecture's maximum qubit degree and the maximum number of two-qubit
Expand All @@ -575,43 +594,63 @@
const std::size_t b = arch.maxDegree() * ((arch.nqubits() + 1) / 2);
const std::size_t budget = std::min(b * b * b, cap);

Node root(layout);
if (root.isGoal(layers.front(), arch)) {
return SmallVector<IndexGate>{};
}
llvm::SpecificBumpPtrAllocator<Node> arena;
std::priority_queue<Node*, std::vector<Node*>, Node::ComparePointer>
frontier;
frontier.emplace(std::construct_at(arena.Allocate(), layout));

MinQueue frontier{};
frontier.emplace(root);
DenseMap<Layout, std::size_t, LayoutInfo> bestDepth;
DenseSet<IndexGate> expansionSet;

std::size_t i = 0;
while (!frontier.empty() && i < budget) {
Node curr = frontier.top();
Node* curr = frontier.top();
frontier.pop();

if (curr.isGoal(layers.front(), arch)) {
return curr.sequence;
// Multiple sequences of SWAPs can lead to the same layout and the same
// layout creates the same child-nodes. Thus, if we've seen a layout
// already at a lower depth don't reexpand the current node (and hence
// recreate the same child nodes).

const auto [it, inserted] =
bestDepth.try_emplace(curr->layout, curr->depth);
if (!inserted) {
const auto otherDepth = it->getSecond();
if (curr->depth >= otherDepth) {
++i;
continue;
}

it->second = curr->depth;
}

// Given a layout, create child-nodes for each possible SWAP between
// two neighbouring hardware qubits.
// If the currently visited node is a goal node, reconstruct the sequence
// of SWAPs from this node to the root.

expansionSet.clear();
if (!curr.sequence.empty()) {
expansionSet.insert(curr.sequence.back());
if (curr->isGoal(layers.front(), arch)) {
SmallVector<IndexGate> seq;
Comment thread
burgholzer marked this conversation as resolved.
Outdated
for (Node* n = curr; n->parent != nullptr; n = n->parent) {
seq.push_back(n->swap);
Comment thread
burgholzer marked this conversation as resolved.
Outdated
}
Comment thread
MatthiasReumann marked this conversation as resolved.
return to_vector(reverse(seq));
Comment thread
burgholzer marked this conversation as resolved.
Outdated
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Given a layout, create child-nodes for each possible SWAP
// between two neighbouring hardware qubits.

expansionSet.clear();
for (const IndexGate& gate : layers.front()) {
for (const auto prog : {gate.first, gate.second}) {
const auto hw0 = curr.layout.getHardwareIndex(prog);
const auto hw0 = curr->layout.getHardwareIndex(prog);
for (const auto hw1 : arch.neighboursOf(hw0)) {
/// Ensure consistent hashing/comparison.
// Ensure consistent hashing/comparison.
const IndexGate swap = std::minmax(hw0, hw1);
if (!expansionSet.insert(swap).second) {
continue;
}

frontier.emplace(curr, swap, layers, arch, params);
frontier.emplace(std::construct_at(arena.Allocate(), curr, swap,
layers, arch, params));
Comment thread
MatthiasReumann marked this conversation as resolved.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class MappingPassTest : public testing::Test,
.alpha = 1,
.lambda = 0.85,
.niterations = 2,
.ntrials = 8,
.ntrials = 16,
.seed = 1337}));
pm.addPass(createQCOToQC());
auto res = pm.run(*moduleOp);
Expand Down
Loading