LoRA Adapters
LoRA (Low-Rank Adaptation) fine-tunes a model by training small rank-r matrices on top of frozen base weights, so you adapt large models at a fraction of the parameters and memory. AiDotNet exposes LoRA through the AiModelBuilder facade with ConfigureLoRA(...), and QLoRA (quantized base + LoRA) by pairing it with ConfigureQuantization(...).
LoRA Fine-Tuning
Build the base model, attach a LoRA configuration, and BuildAsync() wraps the dense layers with adapters and trains only the low-rank matrices.
using AiDotNet;
using AiDotNet.Data.Loaders;
using AiDotNet.Enums;
using AiDotNet.LoRA;
using AiDotNet.NeuralNetworks;
using AiDotNet.Tensors.LinearAlgebra;
var model = new NeuralNetwork<double>(new NeuralNetworkArchitecture<double>(
inputFeatures: 32, numClasses: 4, complexity: NetworkComplexity.Simple));
// rank = adapter capacity; alpha = scaling; freeze the base weights.
var loraConfig = new DefaultLoRAConfiguration<double>(rank: 8, alpha: 16, freezeBaseLayer: true);
var rng = new Random(42);
var trainX = new Tensor<double>(new[] { 32, 32 });
var trainY = new Tensor<double>(new[] { 32, 4 });
for (int i = 0; i < 32; i++)
{
for (int j = 0; j < 32; j++) trainX[new[] { i, j }] = rng.NextDouble();
trainY[new[] { i, i % 4 }] = 1.0;
}
var result = await new AiModelBuilder<double, Tensor<double>, Tensor<double>>()
.ConfigureModel(model)
.ConfigureLoRA(loraConfig)
.ConfigureDataLoader(DataLoaders.FromTensors(trainX, trainY))
.BuildAsync();
Console.WriteLine($"Fine-tuned with LoRA (rank {loraConfig.Rank}, alpha {loraConfig.Alpha}).");
Configuration
| Parameter | Meaning |
|---|---|
rank |
Rank r of the adapter matrices — higher = more capacity, more parameters |
alpha |
Scaling factor applied to the adapter output |
freezeBaseLayer |
Freeze the base weights and train only the adapters (the LoRA default) |
Typical settings: rank 4–16, alpha = 2 × rank.
QLoRA (Quantized Base + LoRA)
QLoRA shrinks memory by quantizing the frozen base weights, then trains LoRA adapters on top. Add ConfigureQuantization(...) alongside ConfigureLoRA(...).
using AiDotNet;
using AiDotNet.Data.Loaders;
using AiDotNet.Deployment.Configuration;
using AiDotNet.Enums;
using AiDotNet.LoRA;
using AiDotNet.NeuralNetworks;
using AiDotNet.Tensors.LinearAlgebra;
var model = new NeuralNetwork<double>(new NeuralNetworkArchitecture<double>(
inputFeatures: 32, numClasses: 4, complexity: NetworkComplexity.Simple));
var loraConfig = new DefaultLoRAConfiguration<double>(rank: 8, alpha: 16, freezeBaseLayer: true);
var quantConfig = new QuantizationConfig { Mode = QuantizationMode.Int8 };
var rng = new Random(7);
var trainX = new Tensor<double>(new[] { 32, 32 });
var trainY = new Tensor<double>(new[] { 32, 4 });
for (int i = 0; i < 32; i++)
{
for (int j = 0; j < 32; j++) trainX[new[] { i, j }] = rng.NextDouble();
trainY[new[] { i, i % 4 }] = 1.0;
}
var result = await new AiModelBuilder<double, Tensor<double>, Tensor<double>>()
.ConfigureModel(model)
.ConfigureQuantization(quantConfig) // quantize the frozen base
.ConfigureLoRA(loraConfig) // train the adapters
.ConfigureDataLoader(DataLoaders.FromTensors(trainX, trainY))
.BuildAsync();
Console.WriteLine("Fine-tuned with QLoRA (Int8 base + LoRA adapters).");
Best Practices
- Freeze the base: keep
freezeBaseLayer: true— that is what makes LoRA parameter-efficient. - Start small:
rank: 8is a strong default; raise it only if the model underfits. - Scale alpha with rank:
alpha≈ 2 ×rankis a common starting point. - Use QLoRA for large models: quantize the base with
ConfigureQuantizationto cut memory. - Validate: compare
result.Evaluation.TrainingSetvsresult.Evaluation.ValidationSetfor overfitting.
PEFT Variants (DoRA, LoKr, VeRA, …)
DefaultLoRAConfiguration takes an optional loraAdapter — pass a specific adapter (DoRA, LoKr, LoHa, VeRA, AdaLoRA, QLoRA, MoRA, PiSSA, …) and ConfigureLoRA applies that variant instead of plain LoRA.
using AiDotNet;
using AiDotNet.Data.Loaders;
using AiDotNet.Enums;
using AiDotNet.LoRA;
using AiDotNet.LoRA.Adapters;
using AiDotNet.NeuralNetworks;
using AiDotNet.Tensors.LinearAlgebra;
var model = new NeuralNetwork<double>(new NeuralNetworkArchitecture<double>(
inputFeatures: 32, numClasses: 4, complexity: NetworkComplexity.Simple));
// DoRA here — swap for LoKrAdapter, LoHaAdapter, VeRAAdapter, AdaLoRAAdapter, etc.
var adapter = new DoRAAdapter<double>(null, rank: 8, alpha: 8, freezeBaseLayer: true);
var loraConfig = new DefaultLoRAConfiguration<double>(rank: 8, alpha: 8, loraAdapter: adapter);
var trainX = new Tensor<double>(new[] { 32, 32 });
var trainY = new Tensor<double>(new[] { 32, 4 });
for (int i = 0; i < 32; i++) { trainX[new[] { i, 0 }] = i / 32.0; trainY[new[] { i, i % 4 }] = 1.0; }
var result = await new AiModelBuilder<double, Tensor<double>, Tensor<double>>()
.ConfigureModel(model)
.ConfigureLoRA(loraConfig)
.ConfigureDataLoader(DataLoaders.FromTensors(trainX, trainY))
.BuildAsync();
Console.WriteLine("Fine-tuned with a DoRA adapter through ConfigureLoRA.");
AiDotNet ships ~25 adapter variants in AiDotNet.LoRA.Adapters (DoRA, LoKr, LoHa, VeRA, AdaLoRA, QLoRA, MoRA, PiSSA, DyLoRA, LongLoRA, ReLoRA, GLoRA, and more), plus ChainLoRAAdapter / MultiLoRAAdapter for composing several.
Notes
The facade exposes LoRA and QLoRA directly (ConfigureLoRA, ConfigureQuantization) and every adapter variant via DefaultLoRAConfiguration’s loraAdapter parameter. Prefix/prompt tuning and runtime adapter hot-swapping are the remaining methods configured outside the single facade call.