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

(LK-C-4) Add controlled QubitUnitary support to Lightning Kokkos #955

Open
wants to merge 99 commits into
base: lk-control-base
Choose a base branch
from

Conversation

josephleekl
Copy link
Contributor

@josephleekl josephleekl commented Oct 21, 2024

Before submitting

Please complete the following checklist when submitting a PR:

  • All new features must include a unit test.
    If you've fixed a bug or added code that should be tested, add a test to the
    tests directory!

  • All new functions and code must be clearly commented and documented.
    If you do make documentation changes, make sure that the docs build and
    render correctly by running make docs.

  • Ensure that the test suite passes, by running make test.

  • Add a new entry to the .github/CHANGELOG.md file, summarizing the
    change, and including a link back to the PR.

  • Ensure that code is properly formatted by running make format.

When all the above are checked, delete everything above the dashed
line and fill in the pull request template.


Context:

Description of the Change:
This PR adds support for controlled QubitUnitary for Lightning Kokkos. There are specialized implementations for 1/2/3-qubits (applyNC1/2/3QubitOpFunctor) and a general case implementation (NCMultiQubitOpFunctor). These functors are defined in MatrixGateFunctors.hpp. These are called by applyControlledMatrix or applyOperation/applyNCMultiQubitOp in StateVectorKokkos.hpp

Benefits:
Performance benchmarks for gates are shown here: https://www.notion.so/xanaduai/Lightning-Kokkos-Native-Controlled-Operation-Gate-Benchmarks-12ebc6bd17648017a2dcd237748b24fe

Possible Drawbacks:

Related GitHub Issues:

[sc-76776]

Copy link
Contributor

Hello. You may have forgotten to update the changelog!
Please edit .github/CHANGELOG.md with:

  • A one-to-two sentence description of the change. You may include a small working example for new features.
  • A link back to this PR.
  • Your name (or GitHub username) in the contributors section.

@josephleekl josephleekl changed the title Lk control matrix nq Add controlled N-qubit matrix support to Lightning Kokkos Oct 21, 2024
@josephleekl josephleekl force-pushed the lk-control-gate-NQ-multiRZ branch 2 times, most recently from 42f9cd3 to 23f398c Compare October 21, 2024 21:01
@josephleekl josephleekl added ci:build_wheels Activate wheel building. ci:use-gpu-runner Enable usage of GPU runner for this Pull Request urgent Mark a pull request as high priority labels Oct 21, 2024
@josephleekl josephleekl marked this pull request as ready for review October 21, 2024 21:40
Copy link

codecov bot commented Oct 21, 2024

Codecov Report

Attention: Patch coverage is 99.32584% with 3 lines in your changes missing coverage. Please review.

Please upload report for BASE (lk-control-base@7ea9296). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...nylane_lightning/lightning_kokkos/_state_vector.py 0.00% 3 Missing ⚠️
Additional details and impacted files
@@                Coverage Diff                 @@
##             lk-control-base     #955   +/-   ##
==================================================
  Coverage                   ?   97.05%           
==================================================
  Files                      ?      221           
  Lines                      ?    34895           
  Branches                   ?        0           
==================================================
  Hits                       ?    33866           
  Misses                     ?     1029           
  Partials                   ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -104,6 +111,71 @@ template <class Precision> struct multiQubitOpFunctor {
}
};

template <class Precision> struct NCMultiQubitOpFunctor {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This construction is similar to multiQubitOpFunctor

@@ -142,6 +214,51 @@ template <class PrecisionT> struct apply1QubitOpFunctor {
arr(i1) = matrix(0B10) * v0 + matrix(0B11) * v1;
}
};

template <class PrecisionT> struct applyNC1QubitOpFunctor {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

and this with apply1QubitOpFunctor etc.

Base automatically changed from lk-control-gate-NQ-multiRZ to lk-control-base November 13, 2024 21:36
Copy link
Member

@multiphaseCFD multiphaseCFD left a comment

Choose a reason for hiding this comment

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

Thanks @josephleekl ! A couple of Q.


auto &&num_qubits = this->getNumQubits();
std::size_t two2N =
std::exp2(num_qubits - wires.size() - controlled_wires.size());
Copy link
Member

Choose a reason for hiding this comment

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

What about using Pennylane::Util::exp2?

Copy link
Member

Choose a reason for hiding this comment

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

can we mark two2N as a const?

Copy link
Member

Choose a reason for hiding this comment

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

I believe that's the idea behind Util::exp2. Could you ensure that we use this exp2 instead of std::exp2. I think you used it in a few other places in your previous PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I'll update to Util::exp2

KokkosVector matrix_trans("matrix_trans", matrix.size());

if (inverse) {
Kokkos::MDRangePolicy<DoubleLoopRank> policy_2d({0, 0}, {dim, dim});
Copy link
Member

Choose a reason for hiding this comment

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

do we want to use shmem to optimize the transpose here?

break;
default:
std::size_t scratch_size = ScratchViewComplex::shmem_size(dim) +
ScratchViewSizeT::shmem_size(dim);
Copy link
Member

Choose a reason for hiding this comment

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

any concern on the shmem usage here?

Copy link
Member

@maliasadi maliasadi left a comment

Choose a reason for hiding this comment

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

Nice work @josephleekl! 🤩


auto &&num_qubits = this->getNumQubits();
std::size_t two2N =
std::exp2(num_qubits - wires.size() - controlled_wires.size());
Copy link
Member

Choose a reason for hiding this comment

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

I believe that's the idea behind Util::exp2. Could you ensure that we use this exp2 instead of std::exp2. I think you used it in a few other places in your previous PRs.

Kokkos::MDRangePolicy<DoubleLoopRank> policy_2d({0, 0}, {dim, dim});
Kokkos::parallel_for(
policy_2d,
KOKKOS_LAMBDA(const std::size_t i, const std::size_t j) {
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to specify const for size_t! Could you ensure that this is fixes everywhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we need to specify const for size_t?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think our cpp-dev-guide doesn't specify using const for primitive data type for parameter passed by values, but some guides like google C++ guide suggests not? I am happy to take it all out to keep it consistent?

const std::vector<std::size_t> &wires, bool inverse = false) {
PL_ABORT_IF(wires.empty(), "Number of wires must be larger than 0");
std::size_t n = static_cast<std::size_t>(1U) << wires.size();
KokkosVector matrix_(matrix, n * n);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
KokkosVector matrix_(matrix, n * n);
KokkosVector matrix_(matrix, exp2(n, 2));

bool inverse = false) {
PL_ABORT_IF(wires.empty(), "Number of wires must be larger than 0");
std::size_t n = static_cast<std::size_t>(1U) << wires.size();
std::size_t n2 = n * n;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
std::size_t n2 = n * n;
std::size_t n2 {exp2(n, 2)};

Comment on lines +487 to +489
PL_ABORT_IF(gate_matrix.empty(),
std::string("Operation does not exist for ") + opName +
std::string(" and no matrix provided."));
Copy link
Member

Choose a reason for hiding this comment

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

Where do you test this?

ComplexT *matrix, const std::vector<std::size_t> &controlled_wires,
const std::vector<bool> &controlled_values,
const std::vector<std::size_t> &wires, bool inverse = false) {
PL_ABORT_IF(wires.empty(), "Number of wires must be larger than 0");
Copy link
Member

Choose a reason for hiding this comment

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

Where have you tested this abort?

Comment on lines +679 to +682
PL_ABORT_IF(wires.empty(), "Number of wires must be larger than 0");
PL_ABORT_IF(matrix.size() != exp2(2 * wires.size()),
"The size of matrix does not match with the given "
"number of wires");
Copy link
Member

Choose a reason for hiding this comment

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

Where have you tested these aborts?

control_wires,
control_values,
target_wires,
False,
Copy link
Member

Choose a reason for hiding this comment

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

Could you also add a code comment about this inv == False for the future reference? (this is a missing feature that we'll tackle later)

Suggested change
False,
inv,

@@ -129,6 +129,7 @@
"C(DoubleExcitationMinus)",
"C(DoubleExcitationPlus)",
"C(MultiRZ)",
"C(QubitUnitary)",
Copy link
Member

Choose a reason for hiding this comment

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

What about ControlledQubitUnitary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's already in L72

Copy link
Contributor

@AmintorDusko AmintorDusko left a comment

Choose a reason for hiding this comment

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

Some comments, and questions. Most of it I did some time ago and forgot to submit 😓.
I couldn't track down all comments so, please disregard any outdated comment. I may delete some after submitting.

Comment on lines +532 to +549
case 1:
Kokkos::parallel_for(two2N, applyNC1QubitOpFunctor<fp_t>(
*data_, num_qubits, matrix_trans,
controlled_wires, controlled_values,
wires));
break;
case 2:
Kokkos::parallel_for(two2N, applyNC2QubitOpFunctor<fp_t>(
*data_, num_qubits, matrix_trans,
controlled_wires, controlled_values,
wires));
break;
case 3:
Kokkos::parallel_for(two2N, applyNC3QubitOpFunctor<fp_t>(
*data_, num_qubits, matrix_trans,
controlled_wires, controlled_values,
wires));
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there something we can do to reduce the code here? Maybe determine the apply function in a case-like structure and reuse the Kokkos::parallel_for call. I mean, because all functions have the same signature, right? No need to lose sleep about this though. 😅

Comment on lines 616 to 640
inline void applyControlledMatrix(
ComplexT *matrix, const std::vector<std::size_t> &controlled_wires,
const std::vector<bool> &controlled_values,
const std::vector<std::size_t> &wires, bool inverse = false) {
PL_ABORT_IF(wires.empty(), "Number of wires must be larger than 0");
std::size_t n = static_cast<std::size_t>(1U) << wires.size();
KokkosVector matrix_(matrix, n * n);
applyNCMultiQubitOp(matrix_, controlled_wires, controlled_values, wires,
inverse);
}

inline void
applyControlledMatrix(const ComplexT *matrix,
const std::vector<std::size_t> &controlled_wires,
const std::vector<bool> &controlled_values,
const std::vector<std::size_t> &wires,
bool inverse = false) {
PL_ABORT_IF(wires.empty(), "Number of wires must be larger than 0");
std::size_t n = static_cast<std::size_t>(1U) << wires.size();
std::size_t n2 = n * n;
KokkosVector matrix_("matrix_", n2);
Kokkos::deep_copy(matrix_, UnmanagedConstComplexHostView(matrix, n2));
applyNCMultiQubitOp(matrix_, controlled_wires, controlled_values, wires,
inverse);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In the constant matrix overload, you can probably copy the constant matrix to a matrix_ and call the non-constant matrix overload. Reusing all logic. What are your thoughts?

Comment on lines 157 to 159
for (std::size_t i = 0; i < dim; i++) {
coeffs_in(i) = arr(indices(i) + offset);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for (std::size_t i = 0; i < dim; i++) {
coeffs_in(i) = arr(indices(i) + offset);
}
std::transform(indices.begin(), indices.end(), coeffs_in.begin(), [&](std::size_t index) { return arr(index + offset)};

right? (please check)

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't remember if we can do this in a KOKKOS_INLINE_FUNCTION.

Kokkos::MDRangePolicy<DoubleLoopRank> policy_2d({0, 0}, {dim, dim});
Kokkos::parallel_for(
policy_2d,
KOKKOS_LAMBDA(const std::size_t i, const std::size_t j) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we need to specify const for size_t?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci:build_wheels Activate wheel building. ci:use-gpu-runner Enable usage of GPU runner for this Pull Request urgent Mark a pull request as high priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants