Skip to main content

Fast, differentiable quantum-machine-learning in pure Apple MLX — GPU statevector simulation with native autodiff.

Project description

mlx-quantum

CI

Fast, differentiable quantum machine learning in pure Apple MLX.

Statevector simulation runs on the Metal GPU and is differentiable end-to-end through MLX autodiff — so a quantum layer trains like any other mlx.nn module, with no custom gradient code. Forward values and gradients match Qiskit to ~1e-6 (float32), and it is ~100–400× faster end-to-end than driving Qiskit's EstimatorQNN from Python. See Validation for the evidence.

Install

uv add mlx-quantum
# or
pip install mlx-quantum

Requires Python ≥ 3.13 and Apple Silicon. The library depends only on MLX and NumPy; Qiskit is optional and used solely to cross-validate/benchmark.

Quickstart

QuantumLayer is a trainable mlx.nn.Module. Drop it into a model and train:

import mlx.core as mx
import mlx.nn as nn
from mlx_quantum import QuantumLayer

class HybridMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.pre = nn.Linear(8, 4)
        self.qnn = QuantumLayer(num_qubits=4, reps=2)   # trainable quantum layer
        self.post = nn.Linear(4, 3)

    def __call__(self, x):
        x = mx.tanh(self.pre(x)) * mx.pi                # encode into rotation angles
        return self.post(self.qnn(x))

The quantum weights are ordinary MLX parameters — nn.value_and_grad and any optimizer update them automatically:

loss_and_grad = nn.value_and_grad(model, loss_fn)
loss, grads = loss_and_grad(model, x, y)
optimizer.update(model, grads)

Building custom circuits

QuantumLayer runs a hardware-efficient ansatz, but the simulator primitives are public — build any circuit as a plain differentiable function:

import mlx.core as mx
from mlx_quantum import zero_state, apply_1q, apply_2q, expval_all_z, H, ry, CX

def circuit(x, weights):            # x: (batch, n) angles, weights: (n,)
    n = x.shape[1]
    state = zero_state(x.shape[0], n)
    for q in range(n):
        state = apply_1q(state, H, q)
    for q in range(n):
        state = apply_1q(state, ry(x[:, q]), q)          # per-sample encoding
    for q in range(n):
        state = apply_1q(state, ry(weights[q]), q)        # trainable
    for q in range(n - 1):
        state = apply_2q(state, CX, q, q + 1)
    return expval_all_z(state)       # <Z> per qubit, shape (batch, n)

grads = mx.grad(lambda w: mx.sum(circuit(x, w)))(weights)  # just works

Gates provided: H, X, Y, Z, rx, ry, rz, CX, CZ. Add your own — a single-qubit gate is any (2, 2) complex mx.array; a two-qubit gate is a (2, 2, 2, 2) tensor [out0, out1, in0, in1].

How it works

A statevector is a complex mx.array of shape (batch,) + (2,) * num_qubits; qubit ordering is little-endian to match Qiskit exactly (qubit 0 is the fastest index, so flattening reproduces Qiskit's amplitude order). Gates are applied as einsum contractions, so the entire simulation is differentiable and GPU-resident. Because there is no custom vjp and no NumPy round-trip, mx.grad differentiates the circuit directly — including through complex amplitudes.

Two MLX specifics the implementation works around: the initial state is built as a constant (not an in-place assignment, which compiles to an unsupported complex GPU scatter), and gates are contractions rather than take/gather (whose backward is also a scatter).

Examples

uv run python examples/simple_mlp.py                             # hybrid MLP training
uv run --extra examples python examples/benchmark_vs_qiskit.py   # quick speed + accuracy vs Qiskit

Validation

Two tracks — is it correct, and is the speed claim fair? All measurements are noiseless statevector, float32. Regenerate with uv run --extra examples python benchmarks/validate.py (details in benchmarks/).

Correctness. Forward values and gradients are compared against Qiskit (Statevector and ReverseEstimatorGradient) over 142 random circuits covering every gate (H, X, Y, Z, rx, ry, rz, CX, CZ); per-circuit error stays at ~1e-6. (The batch-summed gradient error on the accuracy plot climbs to ~1e-5 by 8 qubits — that is float32 accumulation from summing 128 terms into one number, still ≥5 significant figures, not a modelling error.) Gates are checked for unitarity, the state norm is checked after every layer, and an asymmetric circuit pins the little-endian qubit order to Qiskit's.

accuracy error distribution

Performance. Two honest baselines. End-to-end vs EstimatorQNN driven from Python (~100–400×), and kernel-level vs Aer's compiled statevector estimator (~1.7–3×, forward only) — so the win is not just deleted orchestration. Wall-time is shown until MLX hits the memory cliff (~22–26 qubits, single statevector).

speedup wall time

Trains identically. Same circuit, init, data, and optimizer (SGD): the MLX layer (mx.grad) and the Qiskit QNN (qnn.backward) produce the same loss curve to ~1e-7 — same training, just faster.

training overlay

Tests

uv run pytest

Covers gate/statevector correctness, gate unitarity and norm preservation, the little-endian convention, layer training, a finite-difference gradient check, and forward + weight-gradient + input-gradient parity with Qiskit across a random gate sweep (Qiskit-dependent tests skip automatically if Qiskit is absent).

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mlx_quantum-0.1.0.tar.gz (222.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mlx_quantum-0.1.0-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

Details for the file mlx_quantum-0.1.0.tar.gz.

File metadata

  • Download URL: mlx_quantum-0.1.0.tar.gz
  • Upload date:
  • Size: 222.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mlx_quantum-0.1.0.tar.gz
Algorithm Hash digest
SHA256 73cc525f4366965cd59257d80bd976794581606d93deb095f52a6e1aed78275e
MD5 7909f843ad56c3f26fb06673e0acd655
BLAKE2b-256 26a80ddae1e9350add3eb85490d78145bb114ae9a9102955676c5f00cbdcb67a

See more details on using hashes here.

File details

Details for the file mlx_quantum-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mlx_quantum-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mlx_quantum-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a952d16f73dd995a116306717287a4e1f9f9a0602271e3ff0cc629609ecdc790
MD5 b1fc3f00918f34bead31b32fe77c8c7d
BLAKE2b-256 ebf21b61e327a0d6d427d4838dce5ab956058f70b13e4798eb28480a7d5ad9eb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page