Zero-shot evolutionary architecture search for LoRA via MOEA/D and Gradient Projection Score.
Project description
EvoLoRA-MOEAD
Zero-shot evolutionary architecture search for Low-Rank Adaptation (LoRA).
Acknowledgment & Secondary Creation: This repository is an independent engineering implementation and secondary creation based on the theoretical framework proposed by Wu, Neri, and Feng (2026). All necessary original theoretical credit belongs to the authors. DOI: 10.1142/S0129065726500255
evolo-moead searches for performant LoRA adapter configurations —
rank r, scaling factor alpha, and insertion sites target_modules —
before any fine-tuning begins. The search is training-free: it ranks
candidate architectures by the Gradient Projection Score (GPS), a
singular-value statistic of the calibration gradients, and explores the
trade-off surface with the MOEA/D multi-objective evolutionary
algorithm. The recommended configuration is the knee point of the
discovered Pareto front and is returned as a ready-to-use
peft.LoraConfig.
Why
Choosing LoRA hyperparameters by trial-and-error is expensive: every candidate normally requires a full fine-tuning run. EvoLoRA-MOEAD replaces that loop with a single forward/backward pass per candidate and a principled, multi-objective ranking that balances three competing goals:
| Objective | Direction | Meaning |
|---|---|---|
| MeanGPS | maximise | Average rank-r gradient energy across LoRA layers — a proxy for adapter capacity. |
| StdGPS | minimise | Inter-layer dispersion of that energy — penalises unstable, lopsided adapters. |
| TrainableParams | minimise | Adapter parameter budget — honours the parameter-efficiency constraint. |
Internally the optimiser minimises the vector [-MeanGPS, StdGPS, TP].
Installation
# From PyPI (core search + HuggingFace evaluator)
pip install evolo-moead
# From source, editable, with development and example extras
git clone https://github.com/your-org/EvoLoRA-MOEAD.git
cd EvoLoRA-MOEAD
pip install -e ".[dev,examples]"
Core dependencies: torch>=2.0.0, transformers, peft, pymoo,
numpy. Python 3.9+.
Quick start
from evolo_moead import EvoLoRATuner
# `model` is any transformers.PreTrainedModel;
# `calibration_loader` yields dict batches containing `labels`.
tuner = EvoLoRATuner(model, calibration_loader)
best_config = tuner.search_best_config(n_gen=50) # -> peft.LoraConfig
# Feed the result straight into PEFT for the real fine-tuning run.
from peft import get_peft_model
peft_model = get_peft_model(model, best_config)
That is the entire surface. Every evolutionary and linear-algebra
detail is hidden behind search_best_config.
Running the bundled example
# CPU-only, no model download — verifies the installation in seconds
python examples/quick_start.py --backend mock
# Real model path (requires the `examples` extra and network access)
python examples/quick_start.py --backend hf --n-gen 20
API reference
EvoLoRATuner(model, calibration_dataloader=None, search_space=None, num_calibration_batches=2, task_type=None, evaluator=None)
| Argument | Default | Description |
|---|---|---|
model |
— | Base model to search adapters for. |
calibration_dataloader |
None |
Loader of labelled dict batches. Required unless evaluator is given. |
search_space |
None |
Override of the default discrete space (see below). |
num_calibration_batches |
2 |
Batches drawn per candidate evaluation. |
task_type |
None |
peft task type (e.g. "SEQ_CLS", "CAUSAL_LM"). |
evaluator |
None |
Pre-built evaluator; supply a MockEvaluator for CPU-only demos. |
search_best_config(n_gen=50, n_partitions=12, seed=42, **moead_kwargs) -> peft.LoraConfig
Runs the full search and returns the knee-point configuration.
n_partitions controls the Das-Dennis reference-direction density
(for three objectives, n_dirs = (p+1)(p+2)/2, so p=12 yields 91
directions). Extra keyword arguments — n_neighbors,
prob_neighbor_mating — are forwarded to the MOEA/D driver.
Default search space
{
"ranks": [4, 8, 16, 32, 64],
"alphas": [8, 16, 32, 64],
"target_modules": [["q", "v"], ["q", "k", "v", "o"], "all-linear"],
}
Override it to match the module-naming convention of the host model.
For BERT-style models, for instance, the attention projections are
named query / key / value:
search_space = {
"ranks": [2, 4, 8],
"alphas": [8, 16],
"target_modules": [["query", "value"], ["query", "key", "value"]],
}
tuner = EvoLoRATuner(model, loader, search_space=search_space, task_type="SEQ_CLS")
How it works
EvoLoRATuner.search_best_config
│
▼
LoRASearchProblem ──uses──► BaseEvaluator
(pymoo, 3-obj) ├── HuggingFaceEvaluator (real gradients)
│ └── MockEvaluator (closed-form surrogate)
▼
run_moead ──► MOEADResult ──► deduplicate_front
│ │
▼ ▼
Das-Dennis ref. dirs find_knee_point (cosine curvature)
│
▼
peft.LoraConfig
Gradient Projection Score
For a layer gradient G ∈ ℝ^{d_out × d_in} with singular values
σ₁ ≥ σ₂ ≥ …, the rank-r GPS is the Frobenius norm of the optimal
rank-r projection:
GPS(G, r) = sqrt( Σ_{i=1}^{r} σ_i² )
Tensors of rank > 2 (convolution kernels, embeddings) are unfolded to
2D with the leading axis as the output dimension. Zero, NaN, and
Inf inputs are sanitised, and a CPU float64 fallback guards against
non-convergent GPU SVD kernels, so a single degenerate layer never
aborts the search.
Knee-point selection
The Pareto front is Min-Max normalised per objective (to prevent the high-magnitude parameter axis from dominating) and sorted along the parameter-count axis. The point whose neighbouring difference vectors subtend the largest angle is returned as the recommended trade-off. Degenerate fronts (fewer than three distinct points) fall back to the minimum-parameter solution.
Memory discipline (HuggingFaceEvaluator)
Each candidate evaluation mounts a temporary adapter with
peft.get_peft_model, runs a short forward/backward pass, aggregates
per-layer GPS, then unconditionally cleans up in a finally block:
zero_grad(set_to_none=True)on the wrapped model,peft_model.unload()to revert every module replacement,gc.collect(), thentorch.cuda.empty_cache()andtorch.cuda.ipc_collect()when CUDA is present.
This guarantees the host model returns to its original parameter and memory state after every call, so the search loop runs at constant memory.
Project layout
EvoLoRA-MOEAD/
├── pyproject.toml
├── src/
│ └── evolo_moead/
│ ├── __init__.py # exposes EvoLoRATuner
│ ├── api.py # high-level orchestrator
│ ├── core/
│ │ ├── gps_metric.py # SVD gradient projection score
│ │ └── geometry.py # Pareto knee-point extraction
│ ├── evaluator/
│ │ ├── base.py # evaluator ABC + MockEvaluator
│ │ └── hf_evaluator.py # HuggingFace gradient evaluator
│ └── search/
│ ├── problem.py # pymoo multi-objective problem
│ └── optimizer.py # MOEA/D driver
├── examples/
│ └── quick_start.py
└── README.md
Citation
This package is an independent engineering implementation and secondary creation based on the method described in Zero-Shot Evolutionary Architecture Search for Low-Rank Adaptation (Wu et al., 2026). Cite the original work when applying this engineering implementation in research. DOI: 10.1142/S0129065726500255
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file evolo_moead-0.1.0.tar.gz.
File metadata
- Download URL: evolo_moead-0.1.0.tar.gz
- Upload date:
- Size: 26.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e4ebfb17b66d830205a8bb64b0a529d01584ac8faddd5d65e9e99e752a9c26d
|
|
| MD5 |
52778156b007fb6a1771e5a97a43f20f
|
|
| BLAKE2b-256 |
8dedac5d05ff9bb35d21915e9e681422477e1bcc93ab542912dabb0ae393629f
|
File details
Details for the file evolo_moead-0.1.0-py3-none-any.whl.
File metadata
- Download URL: evolo_moead-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78fb99a63e54009aab8d8ae4588094e5f0f71e22af08e2ddd7b455f088ab0889
|
|
| MD5 |
5b01c8f69f74b98452286db6b91f6a22
|
|
| BLAKE2b-256 |
21b8172c3a227b5a7fd90c95dc006dbeccaf1a6a1787ad68a8d8bb2ef48dec58
|