Metadata-Version: 2.5
Name: quantum-adapter
Version: 0.1.0
Summary: Fine-tune Hugging Face language models with adapters generated by quantum circuits
Project-URL: Homepage, https://github.com/aishwaryawambule/quantum-adapter
Project-URL: Issues, https://github.com/aishwaryawambule/quantum-adapter/issues
Author-email: aishwaryawambule <aishwaryawambule@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: fine-tuning,llm,lora,peft,qiskit,quantum,transformers
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Requires-Dist: huggingface-hub>=0.25
Requires-Dist: safetensors>=0.4
Requires-Dist: torch>=2.3
Requires-Dist: transformers>=4.40
Provides-Extra: qiskit
Requires-Dist: qiskit>=1.0; extra == 'qiskit'
Provides-Extra: trainer
Requires-Dist: accelerate>=0.26; extra == 'trainer'
Description-Content-Type: text/markdown

# quantum-adapter

Fine-tune Hugging Face language models with small adapters whose weights are generated by simulated quantum circuits.

Status: beta (0.1.0). Training runs on a classical simulator; see [Limits](#limits).

## How it works

Fine-tuning normally changes a model's weights directly. Adapter methods such as LoRA leave the model alone and learn a small correction instead. quantum-adapter does the same, but the correction is produced by a quantum circuit, so only the circuit's few angles are trained.

```
1. Base model        weights W, frozen (never changed)
                          |
2. Small correction  y = x W  +  x dW        (dW is small and learned)
                          |
3. Circuit makes dW  angles --> [ circuit ] --> state --> dW
                       (the only trained numbers)
                          |
4. Merge             W' = W + dW   -> a plain model, no extra cost at inference
```

At the start of training the correction is exactly zero, so the model behaves like the original. More detail, with pictures: [docs/how-it-works.md](docs/how-it-works.md). Every public class, function and argument: [docs/api.md](docs/api.md).

## Install

```bash
pip install quantum-adapter            # core
pip install "quantum-adapter[qiskit]"  # adds circuit export to Qiskit
pip install "quantum-adapter[trainer]" # adds accelerate, needed by QuantumTrainer
```

Requires Python 3.10 or newer.

## Train on your own data (no code)

Any text classification task works: reviews, support tickets, spam, intents, topics. Put your data in a CSV file with a header row:

```
text,label
"The delivery was late again",complaint
"Thanks, it arrived early!",praise
```

Then train and use it:

```bash
quantum-adapter train --csv my_data.csv --out my-adapter                # GPT-2 by default
quantum-adapter train --csv my_data.csv --out my-adapter --model roberta-base
quantum-adapter predict --adapter my-adapter "Where is my parcel?"        # prints label, confidence, text
```

`train` holds back 10% of the rows to report validation accuracy after each epoch and keeps the best epoch, and picks the layers to adapt for GPT-2, Llama/Mistral/Qwen, BERT/RoBERTa, DistilBERT and T5 models (`--target-modules` for others). Labels can be any text. The defaults (6 epochs, learning rate 1e-2) follow the training tips below. `quantum-adapter train --help` lists every option. Accuracy has so far been measured on one task (SST-2); see [Results](#results).

## Quick start

```python
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from quantum_adapter import QuantumAdapterConfig, QuantumModel, get_quantum_model

tok = AutoTokenizer.from_pretrained("gpt2")
tok.pad_token = tok.eos_token                     # GPT-2 has no pad token; batches of more than one need one
base = AutoModelForSequenceClassification.from_pretrained("gpt2", num_labels=2)
base.config.pad_token_id = tok.pad_token_id       # so the classifier finds the last real token of each row

config = QuantumAdapterConfig(
    target_modules=["c_attn"],   # layer names (or a regex) to adapt
    method="amplitude",          # "amplitude" | "unitary" | "tensor"
    rank=4,                      # amplitude / unitary
    modules_to_save=["score"],   # extra layers to train and save, e.g. the classification head
)
model = get_quantum_model(base, config)   # freezes the base model and adds adapters
model.print_trainable_parameters()

# ... train with a plain PyTorch loop or the Hugging Face Trainer ...

model.circuit_summary()                   # qubits, dials, gates and parameters per layer, vs LoRA
model.save_pretrained("out")              # adapter_config.json + adapter_model.safetensors
base = AutoModelForSequenceClassification.from_pretrained("gpt2", num_labels=2)
base.config.pad_token_id = tok.pad_token_id
model = QuantumModel.from_pretrained(base, "out")   # load onto the same base model
model.push_to_hub("user/name")            # private repo by default
circuits = model.export_circuits()        # Qiskit circuits (needs the [qiskit] extra)

# Do this last, for deployment: merging bakes the adapters into the weights and removes them,
# so export_circuits() and circuit_summary() raise afterwards. Save and export first.
merged = model.merge_and_unload()         # a plain transformers model
```

A runnable version with a tiny random model (no downloads): `python examples/quickstart.py --tiny`.

## Methods

| Method | Based on | What the circuit produces | Status |
|---|---|---|---|
| `amplitude` | This repo's first experiment; related to quantum-generated adapter weights | The amplitudes of an RY + CNOT circuit are reshaped into LoRA-style factors A and B | default, beta |
| `unitary` | Quantum-PEFT, [arXiv 2503.05431](https://arxiv.org/abs/2503.05431) | Orthonormal factors U and V from RY + CZ circuits, with a trainable diagonal between them: dW = U diag(lambda) V^T | experimental |
| `tensor` | QuanTA, [arXiv 2406.00132](https://arxiv.org/abs/2406.00132) | The hidden size is split into axes; small trainable tensors act on pairs of axes | experimental |

`amplitude` and `unitary` use real quantum gates and can be exported to Qiskit. `tensor` is quantum-inspired (its gates are not unitary), so it has no circuit to export. On GPT-2-sized layers `tensor` trains about as many numbers as LoRA rank 4 (13,024 against 12,288 on GPT-2's `c_attn`), so it cannot meet the release rule of at least 5x fewer parameters than LoRA rank 4. `unitary` was about 3 points behind LoRA at 3 epochs on SST-2. Both are marked experimental (see [docs/benchmarks.md](docs/benchmarks.md)). All three are implemented independently from the papers' descriptions. An experimental method needs `experimental=True` in the config.

## Training with the Hugging Face Trainer

Use `QuantumTrainer`, a `transformers.Trainer` whose checkpoints hold only the adapter. It works with default `TrainingArguments`, including periodic checkpoints and `resume_from_checkpoint`:

```python
from transformers import TrainingArguments
from quantum_adapter import QuantumTrainer

model = get_quantum_model(base, config)
args = TrainingArguments(output_dir="run", learning_rate=1e-2, num_train_epochs=1)
trainer = QuantumTrainer(model=model, args=args, train_dataset=train_ds, eval_dataset=eval_ds)
trainer.train()                              # run/checkpoint-*/ hold adapter_config.json + adapter_model.safetensors
# trainer.train(resume_from_checkpoint=True) # continues from the latest checkpoint
model.save_pretrained("out")
```

Each checkpoint directory can be loaded with `QuantumModel.from_pretrained`. The plain `transformers.Trainer` also trains a `QuantumModel`, but only with `save_strategy="no"`: its checkpoints save the whole base model, and fail outright on models with tied embeddings (such as GPT-2 language models).

Circuit angles usually need a higher learning rate than full fine-tuning (the example above uses 1e-2); pick it on a held-out split.

### Training tips

Quantum adapters learn more slowly than LoRA. In our test (SST-2, GPT-2 small) amplitude at 3 epochs reached 84.8% against 87.1% for LoRA r4, but at 6 epochs it reached 87.3%. So train about 2x longer than you would LoRA (6 epochs where LoRA uses 3) with a learning rate near 1e-2. Depth 20 was worse in one test (3 epochs), and adapting more layers did not help. Details and caveats (one task, n=2000, 3 seeds) are in [docs/experiments.md](docs/experiments.md).

## Results

Results so far are preliminary: one task (SST-2, GPT-2 small). With 6 epochs, `amplitude` reached 87.3% against 87.7% for LoRA r4 with 87 times fewer adapter parameters; at 3 epochs it was 2.4 points behind. In short: LoRA-level accuracy (0.4 points behind on SST-2 at 6 epochs) with about 87x fewer trainable parameters. Does the circuit itself help? At 3 epochs an equal-size classical generator matched it. At 6 epochs `amplitude` beat the same classical generator in all 3 seeds, by 0.8 points on average (86.9% against 86.1%; paired t-test p ≈ 0.01). That is a small edge on one task; the full benchmark (6 epochs, 3 tasks) tests whether it holds. See [docs/benchmarks.md](docs/benchmarks.md) and [docs/experiments.md](docs/experiments.md).

The full release benchmark (SST-2, AG News and E2E text generation, 3 seeds, against LoRA rank 4 and same-size classical controls) runs next; its results, including any losses, will be published in 0.1.1.

### History

The first experiment, run with the standalone script `experiments/sst2_bench.py` before the library existed.

#### First experiment: SST-2 sentiment, GPT-2 small

2,000 training reviews, scored on the 872-review GLUE validation set.
Learning rate picked on a held-out dev split, then 3 seeds at that rate.

| Contender | Trainable params | Accuracy (mean, min–max) | Train time |
|---|---|---|---|
| Head only (no adapter) | 1,536 | 71.7% (65.4–75.1) | 28 s |
| LoRA, rank 4 | 148,992 | **87.6%** (86.5–88.6) | 63 s |
| **Quantum adapter** | **3,228** | **84.8%** (83.6–85.4) | 100 s |
| Same-size classical adapter | 3,228 | 78.1% (75.5–81.5) | 64 s |

- The quantum adapter trains **46× fewer numbers than LoRA** and loses about 3 points.
- With the same number of trainable numbers, it was about 7 points ahead of the classical adapter used in this
  early experiment. The release benchmark's better-built classical control matched the quantum adapter
  (84.7% vs 84.8%) at 3 epochs, so the 7-point gap was mostly a weak control. At 6 epochs a smaller, consistent
  lead of 0.8 points remains (see [Results](#results)).
- Its whole training run took about 1.6× as long as LoRA's (1.5× at 8,000 reviews, below). See
  [Limits](#limits) for why the library's measured per-step cost is lower.

**Limits:** one task, one small data size, 3 seeds, simulator only. Promising, not proven.
Raw numbers: [`results/sst2_n2000.json`](results/sst2_n2000.json).

##### With 8,000 training reviews

| Contender | Trainable params | Accuracy (mean, min–max) | Train time |
|---|---|---|---|
| Head only (no adapter) | 1,536 | 78.9% (76.9–80.6) | 112 s |
| LoRA, rank 4 | 148,992 | **89.8%** (88.5–90.8) | 268 s |
| **Quantum adapter** | **3,228** | **88.2%** (86.9–89.0) | 393 s |
| Same-size classical adapter | 3,228 | 83.3% (81.2–84.5) | 273 s |

Learning rates were reused from the 2,000-review dev sweep (no new sweep at 8,000). At 8,000 reviews the quantum adapter trails LoRA by 1.6 points, which is outside the spec's 1-point target.

Raw numbers: [`results/sst2_n8000.json`](results/sst2_n8000.json), run with `N_TRAIN=8000 REUSE_LR=results/sst2_n2000.json uv run python experiments/sst2_bench.py`.

## Limits

- Training uses a classical simulator of the circuit. There is no speed or accuracy gain from quantum hardware.
- Real hardware is reachable only by exporting the trained circuits to Qiskit (`amplitude` and `unitary`). Training on hardware is not supported.
- Intended for text models up to about 1B parameters. Vision and multimodal models are not supported.
- Training is slower than LoRA. Two figures exist and measure different code:
  - about **1.15x per step** for the library (GPT-2 small, batch 32x64, Apple M-series GPU via MPS: 0.47 s per step for `amplitude` vs 0.41 s for PEFT LoRA rank 4);
  - about **1.5–1.6x end to end** in the experiment tables above, which were run with the standalone script `experiments/sst2_bench.py`: its own circuit simulator and a hand-written LoRA, timed over the whole training run.

  The release benchmark times whole training runs of the library against PEFT LoRA and will replace both figures.
  Merged models run at the base model's speed.
- The `tensor` method needs more parameters than LoRA rank 4 on GPT-2-sized layers: on GPT-2's `c_attn` (768 to 2304) it trains 13,024 numbers per layer against LoRA rank 4's 12,288.
- The simulator holds 2^n numbers per circuit, so memory grows quickly with qubit count (default limit: 20 qubits).

## Citations

If you use this library, please cite the papers the methods come from.

```bibtex
@inproceedings{koike2025quantumpeft,
  title     = {Quantum-PEFT: Ultra Parameter-Efficient Fine-Tuning},
  author    = {Koike-Akino, Toshiaki and Tonin, Francesco and Wu, Yongtao and Wu, Frank Zhengqing and Candogan, Leyla Naz and Cevher, Volkan},
  booktitle = {International Conference on Learning Representations (ICLR)},
  year      = {2025},
  note      = {arXiv:2503.05431}
}

@article{chen2024quanta,
  title   = {QuanTA: Efficient High-Rank Fine-Tuning of LLMs with Quantum-Informed Tensor Adaptation},
  author  = {Chen, Zhuo and Dangovski, Rumen and Loh, Charlotte and Dugan, Owen and Luo, Di and Soljačić, Marin},
  journal = {arXiv preprint arXiv:2406.00132},
  year    = {2024}
}
```

Related work: a quantum circuit that produces adapter weights is also described in [arXiv 2503.12790](https://arxiv.org/abs/2503.12790). No code is available for it, so it is not implemented here.

## Licence

Apache-2.0. See [LICENSE](LICENSE).
