LoRA and aLoRA adapters
Off-the-shelf language models sometimes fail on domain-specific tasks — particularly requirement validation over proprietary terminology or specialized classification schemes not well-represented in general training data. Mellea lets you train a LoRA or aLoRA adapter on your own labeled dataset and use it as a requirement validator in any Mellea program.
Prerequisites: pip install "mellea[cli,hf]". Training requires a GPU or
Apple Silicon Mac with sufficient VRAM for the chosen base model. Uploading requires a
Hugging Face account.
Backend note: Custom-trained adapters can only be loaded into
LocalHFBackend. They do not work with Ollama, OpenAI, or other remote backends.Granite Switch models ship with pre-trained adapter functions embedded in the model weights, which can be used via
OpenAIBackendwithload_embedded_adapters=True. See Adapter functions for details.
LoRA vs aLoRA
Both adapter types fine-tune a base model on your data. The difference is inference cost:
| LoRA | aLoRA | |
|---|---|---|
| Inference overhead | Processes full context each call | Activated at a single token — minimal overhead |
| Best for | General fine-tuning | Fast inner-loop checks, requirement validation |
| Training time | Similar | Similar |
For requirement validation in Mellea (short binary checks inside a generation loop),
aLoRA is the better choice. Use --adapter lora if you need a more general fine-tune
and can absorb the inference cost.
Data format
Training data is a .jsonl file with one JSON object per line. Each object must have:
item— the input text to classifylabel— the string classification label
{"item": "Observed black soot on intake. Seal seems compromised under thermal load.", "label": "piston_rings"}
{"item": "Rotor misalignment caused torsion on connecting rod. High vibration at 3100 RPM.", "label": "connecting_rod"}
{"item": "Combustion misfire traced to a cracked mini-carburetor flange.", "label": "mini_carburetor"}
{"item": "Stembolt makes a whistling sound and does not complete the sealing process.", "label": "no_failure"}
Labels can be any strings. The adapter learns to predict the label from the item text.
Train an adapter
m alora train data.jsonl \
--basemodel ibm-granite/granite-3.2-8b-instruct \
--outfile ./checkpoints/my_adapter \
--adapter alora \
--epochs 6 \
--learning-rate 6e-6 \
--batch-size 2 \
--max-length 1024 \
--grad-accum 4
The trained adapter weights are saved to ./checkpoints/my_adapter/.
Parameters
| Flag | Type | Default | Description |
|---|---|---|---|
datafile | str | required | Path to .jsonl training file |
--basemodel | str | required | Hugging Face model ID or local path |
--outfile | str | required | Directory to save adapter weights |
--adapter | str | alora | Adapter type: alora or lora |
--device | str | auto | Device: auto, cpu, cuda, or mps |
--epochs | int | 6 | Number of training epochs |
--learning-rate | float | 6e-6 | Learning rate |
--batch-size | int | 2 | Per-device batch size |
--max-length | int | 1024 | Max tokenized sequence length |
--grad-accum | int | 4 | Gradient accumulation steps |
--promptfile | str | None | JSON file overriding the invocation prompt |
The default invocation prompt is <|start_of_role|>check_requirement<|end_of_role|>.
Provide --promptfile only if your adapter needs a different prompt format. The file
must contain {"invocation_prompt": "..."}.
Upload to Hugging Face
huggingface-cli login # one-time setup
m alora upload ./checkpoints/my_adapter \
--name your-org/my-adapter
This creates the Hugging Face repository if it does not exist and uploads the adapter
weights. Requires HF_TOKEN set or a prior huggingface-cli login.
Warning: Before uploading to a public repository, review whether your training data includes proprietary, confidential, or personal information. Language models can memorize details from small domain-specific datasets.
If you intend to use the adapter as a Mellea adapter function (so that it can be loaded by
model ID rather than local path), pass --intrinsic and provide an io.yaml file:
m alora upload ./checkpoints/my_adapter \
--name your-org/my-adapter \
--intrinsic \
--io-yaml ./io.yaml
Use the adapter in Mellea
Load the trained adapter into a LocalHFBackend using CustomIntrinsicAdapter.
Note:
CustomIntrinsicAdapteris deprecated in favor of theAdapter/WeightsBindingmodel, but is still the only working way to load a locally-trained custom adapter —Adapter's weight-loading is not yet implemented. This example will move toAdapteronce that lands; see #1144.
from mellea.backends.huggingface import LocalHFBackend
from mellea.backends.adapters.adapter import CustomIntrinsicAdapter
from mellea.stdlib.context import ChatContext
from mellea import MelleaSession
from mellea.stdlib.requirements import ALoraRequirement
backend = LocalHFBackend(model_id="ibm-granite/granite-3.2-8b-instruct")
adapter = CustomIntrinsicAdapter(
model_id="your-org/my-adapter", # HF repo ID or local checkpoint path
base_model_name="granite-3.2-8b-instruct",
intrinsic_name="custom-failure-check",
)
backend.add_adapter(adapter)
m = MelleaSession(backend, ctx=ChatContext())
failure_check = ALoraRequirement(
"The failure mode must not be 'no_failure'.",
intrinsic_name="custom-failure-check",
)
result = m.instruct(
"Write a triage summary based on this technician note: {{note}}",
user_variables={"note": "High vibration at 3100 RPM, connecting rod suspected."},
requirements=[failure_check],
)
print(str(result))
# Output will vary — LLM responses depend on model and temperature.
Note:
CustomIntrinsicAdapteremits an advisoryUserWarningbecause custom capability names are not part of Mellea's built-in capability registry. The adapter is still registered for routing.
ALoraRequirement routes validation through the adapter with the matching
intrinsic_name. Create CustomIntrinsicAdapter before the requirement: its
compatibility shim registers the custom name, which lets ALoraRequirement
resolve it. The adapter runs at the check_requirement prompt position. Its
io.yaml must transform the output into the
{"requirement_check": {"score": <float>}} response schema; label-only
adapter output is not compatible with ALoraRequirement.
How automatic routing works
When an adapter is loaded via backend.add_adapter(), Mellea automatically routes
req() validation calls through it rather than falling back to LLM-as-a-judge. The
rule is: use the most specific available method. In practice this means the aLoRA
adapter is preferred whenever one is loaded, with three exceptions:
backend.default_to_constraint_checking_alorais set toFalse— the adapter is loaded but routing is suppressed for the entire backend instance.- The requirement uses the
LLMaJRequirementsubtype explicitly — the caller is asking for LLM-as-a-judge regardless of what adapters are loaded. - The adapter is unavailable (e.g. cannot be loaded) — Mellea falls back to
LLM-as-a-judge automatically. This is the only fallback case: if the
adapter runs but its output fails schema validation,
validate()does not fall back. Instead it surfaces the schema error onValidationResult.errorand fails the check closed (bool(result)isFalse), so callers can tell an unparsable adapter response apart from an ordinary "requirement not met".
If you want to force the adapter path even when using generate_from_context
directly (bypassing the normal validate() call), use ALoraRequirement from
mellea.stdlib.requirements — this bypasses default_to_constraint_checking_alora,
but still requires a matching adapter to actually be registered. If none is found,
Mellea logs a warning and falls back to regular generation rather than erroring.
Disable adapter validation
To run without adapter validation (for benchmarking or debugging):
backend.default_to_constraint_checking_alora = False
Set it back to True to re-enable. This flag is per-backend instance and does not
affect other sessions.
See also: Adapter functions | The Requirements System | Write Custom Verifiers | CLI Reference