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

Add a how-to for catalyst-compiling "Symmetry-invariant quantum machine learning force fields" #1222

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

paul0403
Copy link

@paul0403 paul0403 commented Sep 19, 2024

The how-to contains the full code listing, and some primitive tutorial words.

Before submitting

Please complete the following checklist when submitting a PR:

  • Ensure that your tutorial executes correctly, and conforms to the
    guidelines specified in the README.

  • Remember to do a grammar check of the content you include.

  • All tutorials conform to
    PEP8 standards.
    To auto format files, simply pip install black, and then
    run black -l 100 path/to/file.py.

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


Title: How to Quantum Just-In-Time Compile "Symmetry-invariant quantum machine learning force fields" with Catalyst

Summary:
As part of Catalyst's work on identifying 10 demos to compile with catalyst, we convert "Symmetry-invariant quantum machine learning force fields", a machine learning workflow that calls a training step function repeatedly and thus can have significant performance boosts with catalyst compilation.

Note: the original demo is from an external user, so we should NOT merge this until we have reached out to them. Update: we have obtained their permission.

Relevant references:

Possible Drawbacks:
Because lightning does not support differentiation through QubitUnitary, we have to resort to finite difference method for gradients, which brings a big performance degradation.

Related GitHub Issues:
[sc-72938]


If you are writing a demonstration, please answer these questions to facilitate the marketing process.

  • GOALS — Why are we working on this now?

    Promote Catalyst by demonstrating the performance improvements it offers by QJIT compiling a relatively complex end-to-end quantum workflow.

  • AUDIENCE — Who is this for?

    Chemistry and quantum machine learning researchers.

  • KEYWORDS — What words should be included in the marketing post?

    • Quantum machine learning
    • Catalyst
    • QJIT
  • Which of the following types of documentation is most similar to your file?
    (more details here)

  • Tutorial
  • Demo
  • How-to

…ne learning force fields"

The how-to contains the full code listing, and some primitive tutorial words.
@paul0403 paul0403 requested review from josh146, a team and jay-selby September 19, 2024 15:26
Copy link

👋 Hey, looks like you've updated some demos!

🐘 Don't forget to update the dateOfLastModification in the associated metadata files so your changes are reflected in Glass Onion (search and recommendations).

Please hide this comment once the field(s) are updated. Thanks!

@paul0403
Copy link
Author

paul0403 commented Sep 19, 2024

We should reach out to the original author before deciding whether this should be an additional section to the original demo or a separate new demo.

Until then I won't add too many documentation/metadata.json boilerplate, just because it might not be necessary. @josh146

Copy link
Contributor

@joeycarter joeycarter left a comment

Choose a reason for hiding this comment

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

Thanks @paul0403! It would be worth running black on the file to fix up some formatting, but otherwise looks good to me! 🎉

@dime10
Copy link
Contributor

dime10 commented Sep 20, 2024

@paul0403 It looks like a meta data file is needed so we can look at the built version :)

Copy link
Contributor

@isaacdevlugt isaacdevlugt left a comment

Choose a reason for hiding this comment

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

Thanks @paul0403! This looks fine to me 😄. I made a few suggestions.

demonstrations/tutorial_eqnn_force_field.py Outdated Show resolved Hide resolved
demonstrations/tutorial_eqnn_force_field.py Outdated Show resolved Hide resolved
demonstrations/tutorial_eqnn_force_field.py Outdated Show resolved Hide resolved
demonstrations/tutorial_eqnn_force_field.py Outdated Show resolved Hide resolved
demonstrations/tutorial_eqnn_force_field.py Outdated Show resolved Hide resolved
@paul0403
Copy link
Author

@isaacdevlugt given that this change is quite small, should we make any updates to the metadata?

@isaacdevlugt
Copy link
Contributor

@paul0403 oh yes this is a good point! You'll want to change the "Updated on:" field in the metadata.

@alvaro-at-xanadu do you have any other recommendations for metadata changes, or is the updated date sufficient? Cliff notes for this PR: we replaced jit with qjit because it works now, and we got written approval from Oriel (previous resident) to do so.

@paul0403
Copy link
Author

You'll want to change the "Updated on:" field in the metadata.

Done

@alvaro-at-xanadu
Copy link
Collaborator

@alvaro-at-xanadu do you have any other recommendations for metadata changes, or is the updated date sufficient? Cliff notes for this PR: we replaced jit with qjit because it works now, and we got written approval from Oriel (previous resident) to do so.

@isaacdevlugt This should be all that's needed!

@paul0403
Copy link
Author

@isaacdevlugt I don't think there's any leftover work here, so I think we should deploy it and mark it as done.

Not sure about CI though.

loss, grads = jax.value_and_grad(cost, argnums=0)(net_params, loss_data)

loss = cost(net_params, loss_data)
grads = catalyst.grad(cost, method="fd", h=1e-13, argnums=0)(net_params, loss_data)
Copy link
Author

@paul0403 paul0403 Dec 18, 2024

Choose a reason for hiding this comment

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

When changing to catalyst, it was discovered that QubitUnitary cannot be differentiated:

catalyst.utils.exceptions.DifferentiableCompileError: QubitUnitary is non-differentiable on 'lightning.qubit' device

To make the demo work, I had to manually change gradient method to finite difference. This causes significant performance degradation.

Possible paths forward:

  1. Find another demo to convert
  2. Still convert this demo, but either (a) make lightning work with differentiating through qubit unitary, or (b) decompose qubit unitary into "standard" gateset, although unsure whether this decomposition itself can be jitted or not, or (c) decrease the batch size and the number of traning steps

@josh146
Copy link
Member

josh146 commented Dec 18, 2024

@paul0403 @isaacdevlugt I recommend not merging this demo if it is using finite-difference or a reduction in the timesteps that deviates from the current demo.

@paul0403 you should try option (b):

QubitUnitary.compute_decomposition(U, wires=...)

assuming the unitary matrix is small enough to support decomposition.

@paul0403
Copy link
Author

paul0403 commented Dec 19, 2024

@paul0403 @isaacdevlugt I recommend not merging this demo if it is using finite-difference or a reduction in the timesteps that deviates from the current demo.

@paul0403 you should try option (b):

QubitUnitary.compute_decomposition(U, wires=...)

assuming the unitary matrix is small enough to support decomposition.

The problem is that decomposition essentially makes the circuit (hence the IR) different for different unitaries. I strongly suspect this decomposition itself is not jittable, as catalyst itself handles decomposition at the frontend, so essentially the decomposition would make qjit recompile every single run.

Edit: actually @mudit2812 and I found out that this method will actually fail with infinite recursion and/or a custom DecompositionUndefined error unless the matrix is 2-by-2 or 4-by-4: https://github.com/PennyLaneAI/pennylane/blob/3ff71de1472f1c8ec1c1f8d339a2fbc2d10b1c5e/pennylane/ops/qubit/matrix_ops.py#L189

Copy link

Thank you for opening this pull request.

You can find the built site at this link.

Deployment Info:

  • Pull Request ID: 1222
  • Deployment SHA: 1823aa8c2c667fc72bd10d99ddb87dccf28101ca
    (The Deployment SHA refers to the latest commit hash the docs were built from)

Note: It may take several minutes for updates to this pull request to be reflected on the deployed site.

@paul0403
Copy link
Author

@paul0403 @isaacdevlugt I recommend not merging this demo if it is using finite-difference or a reduction in the timesteps that deviates from the current demo.

@paul0403 you should try option (b):

QubitUnitary.compute_decomposition(U, wires=...)

assuming the unitary matrix is small enough to support decomposition.

So I tried this:

    #qml.QubitUnitary(U, wires=wires, id="U")
    decomp = qml.QubitUnitary.compute_decomposition(U, wires=wires)#, id="U")
    for op in decomp:
        qml.apply(decomp)

This raises the following warning and error:

/home/paul.wang/.local/lib/python3.10/site-packages/pennylane/ops/op_math/decompositions/two_qubit_unitary.py:621: RuntimeWarning: The two-qubit decomposition may not be differentiable when the input unitary depends on trainable parameters.
  _check_differentiability_warning(U)

pennylane.queuing.QueuingError: [RZ(Traced<ShapedArray(float64[])>with<DynamicJaxprTrace(level=3/1)>, wires=[0]), RY(Traced<ShapedArray(float64[])>with<DynamicJaxprTrace(level=3/1)>, wires=[0]), RZ(Traced<ShapedArray(float64[])>with<DynamicJaxprTrace(level=3/1)>, wires=[0])] encountered in AnnotatedQueue and is not an object that can be processed into a QuantumScript. Queues should contain Operator or MeasurementProcess objects only.

I am unsure what this error means, as it seems like the queue contains operator only.

@dime10
Copy link
Contributor

dime10 commented Dec 19, 2024

@paul0403 @isaacdevlugt I recommend not merging this demo if it is using finite-difference or a reduction in the timesteps that deviates from the current demo.
@paul0403 you should try option (b):

QubitUnitary.compute_decomposition(U, wires=...)

assuming the unitary matrix is small enough to support decomposition.

So I tried this:

    #qml.QubitUnitary(U, wires=wires, id="U")
    decomp = qml.QubitUnitary.compute_decomposition(U, wires=wires)#, id="U")
    for op in decomp:
        qml.apply(decomp)

This raises the following warning and error:

/home/paul.wang/.local/lib/python3.10/site-packages/pennylane/ops/op_math/decompositions/two_qubit_unitary.py:621: RuntimeWarning: The two-qubit decomposition may not be differentiable when the input unitary depends on trainable parameters.
  _check_differentiability_warning(U)

pennylane.queuing.QueuingError: [RZ(Traced<ShapedArray(float64[])>with<DynamicJaxprTrace(level=3/1)>, wires=[0]), RY(Traced<ShapedArray(float64[])>with<DynamicJaxprTrace(level=3/1)>, wires=[0]), RZ(Traced<ShapedArray(float64[])>with<DynamicJaxprTrace(level=3/1)>, wires=[0])] encountered in AnnotatedQueue and is not an object that can be processed into a QuantumScript. Queues should contain Operator or MeasurementProcess objects only.

I am unsure what this error means, as it seems like the queue contains operator only.

The differentiability warning is certainly a concern, since that is a requirement for this demo.

The actual error is probably because of this typo though:

    decomp = qml.QubitUnitary.compute_decomposition(U, wires=wires)#, id="U")
    for op in decomp:
        qml.apply(op)  # was decomp

@paul0403
Copy link
Author

paul0403 commented Dec 19, 2024

The actual error is probably because of this typo though:

    decomp = qml.QubitUnitary.compute_decomposition(U, wires=wires)#, id="U")
    for op in decomp:
        qml.apply(op)  # was decomp

OH! Good catch 💦

However after fixing this we get an even more mysterious jax error now:

  File "/home/paul.wang/catalyst_new/catalyst/frontend/catalyst/jax_primitives.py", line 517, in _grad_lowering
    attr = ir.DenseElementsAttr.get(nparray, type=const_type)
ValueError: ndarray is not C-contiguous

Also, this approach likely just does not work at all, as seen by this factored out example:

import pennylane as qml
import catalyst
from catalyst import qjit
import jax
from jax import numpy as jnp

dev = qml.device("lightning.qubit", wires=1)

@qjit(keep_intermediate=False)
@qml.qnode(dev)
def circuit(x):
	U = jnp.array([[1,0],[0,x]])
	decomp = qml.QubitUnitary.compute_decomposition(U, wires=0)
	for op in decomp:
		qml.apply(op)
	return qml.probs()

print(circuit(1.0))  # fine, [1. 0.]

def f(x):
	probs = circuit(x)
	return probs[0] + probs[1]

print(catalyst.grad(f, argnums=0)(1.0))
catalyst.utils.exceptions.CompileError: catalyst-cli failed with error code 1: Failed to run pipeline: MLIRToLLVMDialect
Compilation failed:
deriv_circuit:112:12: error: failed to legalize operation 'memref.load' that was explicitly marked illegal
      %1 = stablehlo.reshape %0 : (tensor<1x1x1xcomplex<f64>>) -> tensor<1xcomplex<f64>>
           ^
deriv_circuit:35:13: note: called from
      %14 = call @det(%13) : (tensor<1x2x2xcomplex<f64>>) -> tensor<1xcomplex<f64>>
            ^
deriv_circuit:112:12: note: see current operation: %213 = "memref.load"(%202, %204, %207, %210) <{nontemporal = false}> : (memref<1x1x1xcomplex<f64>, strided<[4, 2, 1]>>, index, index, index) -> complex<f64>
      %1 = stablehlo.reshape %0 : (tensor<1x1x1xcomplex<f64>>) -> tensor<1xcomplex<f64>>
           ^
While processing 'MemrefToLLVMWithTBAAPass' pass Failed to lower MLIR module

@dime10
Copy link
Contributor

dime10 commented Dec 19, 2024

However after fixing this we get an even more mysterious jax error now:

File "/home/paul.wang/catalyst_new/catalyst/frontend/catalyst/jax_primitives.py", line 517, in _grad_lowering
attr = ir.DenseElementsAttr.get(nparray, type=const_type)
ValueError: ndarray is not C-contiguous

Weird, it's just a data layout issue though which can probably fixed relatively easily.

Also, this approach likely just does not work at all, as seen by this factored out example:

This one seems worse, must be a gap in our stablehlo support 🤔

@paul0403
Copy link
Author

I opened an issue so we can formally tackle the underlying problem in the future:
PennyLaneAI/catalyst#1393

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants