Skip to content
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

Draft of dependency graph generation algorithm #1848

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a447bd9
Add dependency analysis code
atgeller Jun 24, 2024
c465360
Formatting
atgeller Jun 24, 2024
0943b5a
Use lifetimes computed from dependency graph to map to physical qubits.
atgeller Jun 27, 2024
31a7d64
Use lifetimes to determine when it is safe to reuse qubits, and reord…
atgeller Jul 3, 2024
49edf9e
Insert into new block and whack old block after instead of erasing in…
atgeller Jul 3, 2024
7a31f75
Missed an erase
atgeller Jul 3, 2024
615ee75
Forgot to actually erase old block
atgeller Jul 3, 2024
7a752d9
Some cleanup and fixes
atgeller Jul 10, 2024
96fa396
Merge branch 'main' into dependency_analysis
atgeller Jul 10, 2024
3565142
Fix up scheduling to address bugs, includes two test cases for these …
atgeller Jul 11, 2024
fc5c485
Merge branch 'dependency_analysis' of github.com:atgeller/cuda-quantu…
atgeller Jul 11, 2024
0212dde
Add filecheck commands to tests
atgeller Jul 11, 2024
07050d7
Add assertions and validation functions, and tests for errors. Also i…
atgeller Jul 13, 2024
d959274
More assertions in place
atgeller Jul 15, 2024
2884c07
Add support for classical values
atgeller Jul 16, 2024
4b3136b
Merge branch 'main' into dependency_analysis
atgeller Jul 16, 2024
a9d4941
Fixes several bugs (mostly related to classical values) and adds test…
atgeller Jul 19, 2024
0f5f20a
Merge branch 'dependency_analysis' of github.com:atgeller/cuda-quantu…
atgeller Jul 19, 2024
68a056e
Some cleanup, remove unused functions
atgeller Jul 19, 2024
9a9f826
Remove unnecessary TODO
atgeller Jul 19, 2024
9d5d12d
Improve error messages, add missing test files
atgeller Jul 22, 2024
6526b5e
Improve errors a bit
atgeller Jul 24, 2024
24843c1
Fix performance issue with finding nodes at a given cycle
atgeller Jul 24, 2024
a61ac20
Ensure that classical operations after quantum values are generated
atgeller Jul 24, 2024
0d7160a
Preliminary addition of qubit management passes to JIT pipeline
atgeller Jul 24, 2024
622b8b2
Update pass descriptions
atgeller Jul 24, 2024
f4e93bc
Add qubit management pipeline
atgeller Jul 24, 2024
ffc4c4c
Merge remote-tracking branch 'upstream/main' into dependency_analysis
atgeller Jul 24, 2024
32ecc13
Add to yaml configs, add options to disable qubit management and to d…
atgeller Jul 24, 2024
02b45c9
Add targettests for qubit management
atgeller Jul 24, 2024
ffad80d
Fix bug counting number of virtual qubits
atgeller Jul 25, 2024
da4f915
Update how qubit-management is added to qir-base pipeline
atgeller Jul 29, 2024
6c0ff58
Forgot to remove debug code
atgeller Jul 30, 2024
3de2006
Run qubit management before mapping
atgeller Aug 2, 2024
c61600e
Impose ordering on assigning physical qubits
atgeller Aug 2, 2024
c11779c
Fix various issues in dependency tests
atgeller Aug 6, 2024
8c8f054
Fix bug with codegen for classical values
atgeller Aug 6, 2024
775239f
Avoid reliance on statistic variables to avoid multithreading issues
atgeller Aug 6, 2024
3f01e51
Missed a test file
atgeller Aug 6, 2024
ab3c9c4
Small fix to qubit management pipeline
atgeller Aug 7, 2024
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
9 changes: 9 additions & 0 deletions include/cudaq/Optimizer/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ def ApplySpecialization : Pass<"apply-op-specialization", "mlir::ModuleOp"> {
];
}

def DependencyAnalysis : Pass<"dep-analysis", "mlir::func::FuncOp"> {
let summary = "Generates qubit dependency graphs based on quake code.";
let description = [{
Work in progress.
}];

let dependentDialects = ["quake::QuakeDialect"];
}

def PySynthCallableBlockArgs : Pass<"py-synth-callable-block-args", "mlir::func::FuncOp"> {
let summary = "Synthesize / Inline cc.callable_func on function block arguments.";
let description = [{
Expand Down
1 change: 1 addition & 0 deletions lib/Optimizer/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ add_cudaq_library(OptTransforms
LowerUnwind.cpp
Mapping.cpp
MemToReg.cpp
DependencyAnalysis.cpp
MultiControlDecomposition.cpp
ObserveAnsatz.cpp
PruneCtrlRelations.cpp
Expand Down
230 changes: 230 additions & 0 deletions lib/Optimizer/Transforms/DependencyAnalysis.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*******************************************************************************
* Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

#include "cudaq/Frontend/nvqpp/AttributeNames.h"
#include "cudaq/Optimizer/Dialect/CC/CCDialect.h"
#include "cudaq/Optimizer/Dialect/Quake/QuakeDialect.h"
#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h"
#include "cudaq/Optimizer/Transforms/Passes.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Threading.h"
#include "mlir/InitAllDialects.h"
#include "mlir/Rewrite/FrozenRewritePatternSet.h"
#include "mlir/Transforms/DialectConversion.h"

using namespace mlir;

//===----------------------------------------------------------------------===//
// Generated logic
//===----------------------------------------------------------------------===//
namespace cudaq::opt {
#define GEN_PASS_DEF_DEPENDENCYANALYSIS
#include "cudaq/Optimizer/Transforms/Passes.h.inc"
} // namespace cudaq::opt

namespace {
class DependencyNode {
protected:
SmallVector<DependencyNode *> successors;
SmallVector<DependencyNode *> dependencies;
// TODO: should probably be a set of qids here
size_t qindex;
Operation *associated;

// Print with tab index to should depth in graph
void printSub(int tabIndex) {
for (int i = 0; i < tabIndex; i++) {
llvm::outs() << "\t";
}
llvm::outs() << "qid: " << qindex << " -- ";
if (isJoin())
llvm::outs() << "join\n";
else if (associated != nullptr)
associated->dump();

for (auto dependency : dependencies) {
dependency->printSub(tabIndex + 1);
}
}

public:
DependencyNode(size_t index, Operation *op, DependencyNode *from)
: successors(), dependencies(), qindex(index), associated(op) {
if (from) {
addSuccessor(from);
}
};

DependencyNode(DependencyNode *from)
: successors({from}), dependencies(), qindex(from->qindex),
associated(nullptr) {
addSuccessor(from);
};

inline void addSuccessor(DependencyNode *other) {
atgeller marked this conversation as resolved.
Show resolved Hide resolved
successors.push_back(other);
other->dependencies.push_back(this);
}

inline bool isJoin() {
// TODO: Is second part ever not true?
return associated == nullptr && dependencies.size() > 0;
}

void print() { printSub(0); }
};

inline bool isMeasureOp(Operation *op) {
return dyn_cast<quake::MxOp>(*op) || dyn_cast<quake::MyOp>(*op) ||
dyn_cast<quake::MzOp>(*op);
}

inline bool isBeginOp(Operation *op) {
return dyn_cast<quake::UnwrapOp>(*op) || dyn_cast<quake::ExtractRefOp>(*op) ||
dyn_cast<quake::NullWireOp>(*op);
}

class DependencyAnalysis {
private:
SmallVector<DependencyNode *> perOp;
DenseMap<BlockArgument, DependencyNode *> map;

inline DependencyNode *getDNodeId(Operation *op, int res_index) {
if (op->hasAttr("dnodeids")) {
auto ids = op->getAttr("dnodeids").cast<DenseI32ArrayAttr>();
if (ids[res_index] != -1) {
auto dnode = perOp[ids[res_index]];
return dnode;
}
}

return nullptr;
}

public:
DependencyAnalysis() : perOp(), map(){};

DependencyNode *handleDependencyOp(Operation *op, DependencyNode *next,
int res_index) {
// Reached end of graph (beginning of circuit)
if (isBeginOp(op))
return nullptr;

// If we've already visited this operation, return memoized dnode
auto dnodeid = getDNodeId(op, res_index);
if (dnodeid) {
dnodeid->addSuccessor(next);
return dnodeid;
}

// Lookup qid for result
auto qid = op->getAttrOfType<DenseI32ArrayAttr>("qids")[res_index];

// Construct new dnode
DependencyNode *newNode = new DependencyNode(qid, op, next);

// TODO: Only one dnodeid per op with qid sets?
// Dnodeid for the relevant result is the next slot of the dnode vector
SmallVector<int32_t> ids(op->getNumResults(), -1);
ids[res_index] = perOp.size();

// Add dnodeid attribute
OpBuilder builder(op);
op->setAttr("dnodeids",
builder.getDenseI32ArrayAttr({ids.begin(), ids.end()}));
perOp.push_back(newNode);

// Recursively visit children
for (auto operand : op->getOperands()) {
handleDependencyValue(operand, newNode);
}

return newNode;
}

DependencyNode *handleDependencyArg(BlockArgument arg, DependencyNode *next) {
// If we've already handled this block argument, return memoized value
if (auto prev = map.lookup(arg)) {
prev->addSuccessor(next);
return prev;
}

auto block = arg.getParentBlock();
DependencyNode *newNode = next;
// TODO: better way to check for multiple predecessors?
// TODO: get single or get unique?
// If join point, insert join node
if (!block->getSinglePredecessor()) {
newNode = new DependencyNode(next);
map.insert({arg, newNode});
}

// Look up operands from all branch instructions that can jump
// to the parent block and recursively visit them
for (auto predecessor : block->getPredecessors()) {
if (auto branch =
dyn_cast<BranchOpInterface>(predecessor->getTerminator())) {
unsigned numSuccs = branch->getNumSuccessors();
for (unsigned i = 0; i < numSuccs; ++i) {
if (block && branch->getSuccessor(i) != block)
continue;
auto brArgs = branch.getSuccessorOperands(i).getForwardedOperands();
auto operand = brArgs[arg.getArgNumber()];
handleDependencyValue(operand, newNode);
}
}
}

return newNode;
}

DependencyNode *handleDependencyValue(Value v, DependencyNode *next) {
// Block arguments do not have associated operations,
// but may require inserting joins, so they are handled specially
if (auto arg = dyn_cast<BlockArgument>(v))
return handleDependencyArg(arg, next);

auto defOp = v.getDefiningOp();
if (defOp) {
// Find the value amongst those returned by the operation
int i = 0;
for (auto res : defOp->getResults()) {
if (res == v)
break;
i++;
}

return handleDependencyOp(defOp, next, i);
}

// TODO: FAIL
llvm::outs() << "UNKNOWN VALUE\n";
v.dump();
return nullptr;
}
};

struct DependencyAnalysisPass
: public cudaq::opt::impl::DependencyAnalysisBase<DependencyAnalysisPass> {
using DependencyAnalysisBase::DependencyAnalysisBase;

void runOnOperation() override {
auto func = getOperation();

DependencyAnalysis engine;

func.walk([&](quake::MzOp mop) {
// TODO: Making assumption wire is second measure result
auto graph = engine.handleDependencyValue(mop.getResult(1), nullptr);
if (graph)
graph->print();
});
}
};

} // namespace
59 changes: 59 additions & 0 deletions test/Quake/dependency.qke
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// ========================================================================== //
// Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. //
// All rights reserved. //
// //
// This source code and the accompanying materials are made available under //
// the terms of the Apache License 2.0 which accompanies this distribution. //
// ========================================================================== //

// RUN: cudaq-opt --expand-controls-veqs %s | FileCheck %s

// func.func @test() {
// %0 = quake.alloca !quake.veq<2>
// %1 = quake.extract_ref %0[0] : (!quake.veq<2>) -> !quake.ref
// %2 = quake.unwrap %1 : (!quake.ref) -> !quake.wire {qid = 0 : index}
// %3 = quake.extract_ref %0[1] : (!quake.veq<2>) -> !quake.ref
// %4 = quake.unwrap %3 : (!quake.ref) -> !quake.wire {qid = 1 : index}
// %5 = quake.h %2 : (!quake.wire) -> !quake.wire {qid = 0 : index}
// %measOut, %wires = quake.mz %5 : (!quake.wire) -> (!quake.measure, !quake.wire) {qids = array<i32: -1, 0>}
// %6 = quake.discriminate %measOut : (!quake.measure) -> i1 {qid = 0 : index}
// cf.cond_br %6, ^bb1(%4, %wires : !quake.wire, !quake.wire), ^bb2(%4, %wires : !quake.wire, !quake.wire)
// ^bb1(%7: !quake.wire, %8: !quake.wire): // pred: ^bb0
// %9 = quake.h %7 : (!quake.wire) -> !quake.wire {qid = 1 : index}
// %10:2 = quake.x [%9] %8 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) {qids = array<i32: 1, 0>}
// quake.wrap %10#0 to %3 : !quake.wire, !quake.ref
// cf.br ^bb3(%10#1 : !quake.wire)
// ^bb2(%11: !quake.wire, %12: !quake.wire): // pred: ^bb0
// %13 = quake.h %11 : (!quake.wire) -> !quake.wire {qid = 1 : index}
// %measOut_0, %wires_1 = quake.mz %13 : (!quake.wire) -> (!quake.measure, !quake.wire) {qids = array<i32: -1, 1>}
// quake.wrap %wires_1 to %3 : !quake.wire, !quake.ref {qid = 1 : index}
// cf.br ^bb3(%12 : !quake.wire)
// ^bb3(%14: !quake.wire): // 2 preds: ^bb1, ^bb2
// %measOut_2, %wires_3 = quake.mz %14 : (!quake.wire) -> (!quake.measure, !quake.wire) {qids = array<i32: -1, 0>}
// quake.wrap %wires_3 to %1 : !quake.wire, !quake.ref {qid = 0 : index}
// quake.dealloc %0 : !quake.veq<2>
// return
// }

func.func @test() {
%0 = quake.null_wire {qid = 0 : index}
%1 = quake.null_wire {qid = 1 : index}
%2 = quake.h %0 : (!quake.wire) -> !quake.wire {qids = array<i32: 0>}
%measOut, %wires = quake.mz %2 : (!quake.wire) -> (!quake.measure, !quake.wire) {qids = array<i32: -1, 0>}
%3 = quake.discriminate %measOut : (!quake.measure) -> i1 {qids = array<i32: 0>}
cf.cond_br %3, ^bb1(%1, %wires : !quake.wire, !quake.wire), ^bb2(%1, %wires : !quake.wire, !quake.wire)
^bb1(%4: !quake.wire, %5: !quake.wire): // pred: ^bb0
%6 = quake.h %4 : (!quake.wire) -> !quake.wire {qids = array<i32: 1>}
%7:2 = quake.x [%6] %5 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) {qids = array<i32: 1, 0>}
quake.sink %7#0 : !quake.wire
cf.br ^bb3(%7#1 : !quake.wire)
^bb2(%8: !quake.wire, %9: !quake.wire): // pred: ^bb0
%10 = quake.h %8 : (!quake.wire) -> !quake.wire {qids = array<i32: 1>}
%measOut_0, %wires_1 = quake.mz %10 : (!quake.wire) -> (!quake.measure, !quake.wire) {qids = array<i32: -1, 1>}
quake.sink %wires_1 : !quake.wire
cf.br ^bb3(%9 : !quake.wire)
^bb3(%11: !quake.wire): // 2 preds: ^bb1, ^bb2
%measOut_2, %wires_2 = quake.mz %11 : (!quake.wire) -> (!quake.measure, !quake.wire) {qids = array<i32: -1, 0>}
quake.sink %wires_2 : !quake.wire
return
}
Loading