This guide covers training custom LoRAs using the Civitai training API for both image and video generation models.
Overview#
The Civitai training integration supports:
- Image LoRAs: Character, style, and object training on SD1.5/SDXL/Flux
- Video LoRAs: Motion training on Wan and Hunyuan video models
- Training Presets: Pre-configured hyperparameters for common use cases
- Progress Monitoring: Real-time loss tracking and sample generation
- Cost Estimation: Accurate training cost predictions
Quick Start#
Basic Training Example#
typescript
import {
LoraTrainingProvider,
createLoraTrainingProvider,
} from '@oshun/civitai-training';
const training = createLoraTrainingProvider(process.env.CIVITAI_API_KEY);
// Train a character LoRA
const job = await training.trainCharacterLoRA(
'my-character', // name
images, // dataset images
'mycharacter', // trigger word
{ epochs: 15 } // optional customization
);
console.log('Training started:', job.id);
console.log('Estimated cost:', job.cost, 'Buzz');
Dataset Preparation#
Image Dataset#
typescript
import type { DatasetImage, DatasetConfig } from '@oshun/civitai-training';
// Prepare images
const images: DatasetImage[] = [
{
image: 'https://example.com/image1.jpg', // URL or base64
caption: 'mycharacter, woman, portrait, looking at camera',
repeats: 5, // higher repeats for important images
},
{
image: 'https://example.com/image2.jpg',
caption: 'mycharacter, woman, full body, standing',
repeats: 3,
},
// Add 10-50 images for best results
];
// Dataset configuration
const dataset: DatasetConfig = {
name: 'my-character-dataset',
images,
triggerWord: 'mycharacter',
classWord: 'woman', // for regularization
captionMode: 'caption', // 'tag', 'caption', or 'auto'
autoCaption: false, // use AI captioning
resolution: 1024, // training resolution
bucketResolutionSteps: 64, // aspect ratio bucketing
enableCropping: true,
flipProbability: 0.5, // horizontal flip augmentation
};
Video Dataset#
typescript
import type { DatasetVideo } from '@oshun/civitai-training';
const videos: DatasetVideo[] = [
{
video: 'https://example.com/clip1.mp4',
caption: 'smooth camera pan, cinematic motion',
startTime: 0,
endTime: 4, // seconds
},
{
video: 'https://example.com/clip2.mp4',
caption: 'zooming in, dynamic movement',
},
// Add 10+ clips for motion LoRAs
];
Dataset Guidelines#
| Training Type | Min Images | Recommended | Notes |
|---|---|---|---|
| Character | 10 | 20-50 | Varied poses, angles, lighting |
| Style | 20 | 50-100 | Consistent style across subjects |
| Object | 15 | 30-60 | Multiple angles and contexts |
| Motion (Video) | 10 clips | 30+ clips | 2-5 second clips |
Caption Modes#
| Mode | Description | Best For |
|---|---|---|
tag |
Comma-separated tags | Anime, stylized content |
caption |
Natural language descriptions | Realistic, detailed content |
auto |
AI-generated captions | Large datasets |
Training Presets#
Available Presets#
| Preset | Base Model | Use Case | Rank | Epochs |
|---|---|---|---|---|
character-sdxl |
SDXL | Character/person training | 128 | 15 |
style-sdxl |
SDXL | Art style training | 32 | 10 |
object-sdxl |
SDXL | Object/product training | 64 | 20 |
character-flux |
Flux Dev | Character on Flux | 64 | 10 |
style-flux |
Flux Dev | Style on Flux | 16 | 8 |
motion-wan |
Wan 2.1 | Video motion training | 64 | 20 |
character-hunyuan |
Hunyuan Video | Character for video | 64 | 15 |
fast-test |
SDXL | Quick testing | 8 | 3 |
Using Presets#
typescript
// Train with preset
const job = await training.startTrainingWithPreset(
'character-sdxl', // preset name
dataset, // dataset config
'my-character-lora', // training name
{
// Optional: override specific settings
epochs: 20,
learningRate: {
unetLR: 0.00005,
},
}
);
Preset Configurations#
typescript
// Character SDXL preset configuration
const characterSDXL = {
network: { rank: 128, alpha: 64, dropout: 0.05 },
learningRate: {
unetLR: 0.00007,
textEncoderLR: 0.000035,
scheduler: 'cosine_with_warmup',
warmup: 0.1,
},
optimizer: 'AdamW8bit',
batchSize: 2,
epochs: 15,
mixedPrecision: 'bf16',
gradientCheckpointing: true,
xformers: true,
};
// Style SDXL preset configuration
const styleSDXL = {
network: { rank: 32, alpha: 16, dropout: 0.1 },
learningRate: {
unetLR: 0.0001,
textEncoderLR: 0.00005,
scheduler: 'cosine',
warmup: 0.05,
},
optimizer: 'AdamW8bit',
batchSize: 4,
epochs: 10,
mixedPrecision: 'bf16',
};
Custom Training Configuration#
Full Configuration Example#
typescript
import type {
TrainingConfig,
TrainingHyperparams,
} from '@oshun/civitai-training';
const hyperparams: TrainingHyperparams = {
network: {
rank: 64, // Network dimension (4-256)
alpha: 32, // Scaling factor (usually rank or rank/2)
dropout: 0.05, // Dropout for regularization
convRank: 32, // For LoCon (optional)
convAlpha: 16, // For LoCon (optional)
},
learningRate: {
unetLR: 0.0001, // U-Net learning rate
textEncoderLR: 0.00005, // Text encoder learning rate
scheduler: 'cosine_with_warmup',
warmup: 0.1, // 10% warmup steps
minLR: 0.00001, // Minimum LR at end
numCycles: 1, // For cosine_with_restarts
},
optimizer: 'AdamW8bit', // Memory-efficient optimizer
batchSize: 2,
epochs: 15,
maxSteps: undefined, // Override epochs with fixed steps
gradientAccumulationSteps: 1,
mixedPrecision: 'bf16', // bf16 for newer GPUs, fp16 otherwise
gradientCheckpointing: true,
xformers: true,
seed: 42,
cacheLatents: true,
noiseOffset: 0.1, // For better blacks/whites
priorPreservationWeight: 1.0, // For DreamBooth
};
const config: TrainingConfig = {
name: 'my-custom-lora',
baseModel: 'sdxl',
trainingType: 'lora',
dataset,
hyperparams,
outputPath: '/trained-loras/',
callbackUrl: 'https://api.myapp.com/webhooks/training',
saveEveryNEpochs: 5,
sampleEveryNEpochs: 5,
samplePrompts: [
'mycharacter, portrait, high quality',
'mycharacter, full body, standing',
],
};
const job = await training.startTraining(config);
Network Parameters#
| Parameter | Range | Description |
|---|---|---|
rank |
4-256 | Network dimension (higher = more capacity) |
alpha |
1-rank | Scaling factor (common: rank/2 or rank) |
dropout |
0-0.3 | Regularization (0.05-0.1 recommended) |
convRank |
4-64 | Convolutional rank for LoCon |
Learning Rate Guidelines#
| Base Model | U-Net LR | Text Encoder LR |
|---|---|---|
| SD 1.5 | 1e-4 - 5e-4 | 5e-5 - 2e-4 |
| SDXL | 5e-5 - 1e-4 | 2.5e-5 - 5e-5 |
| Flux | 5e-5 - 2e-4 | 2.5e-5 - 1e-4 |
Schedulers#
| Scheduler | Description | Use Case |
|---|---|---|
constant |
Fixed learning rate | Short training |
constant_with_warmup |
Warmup then constant | General purpose |
linear |
Linear decay | Long training |
cosine |
Cosine decay | Most recommended |
cosine_with_restarts |
Periodic restarts | Very long training |
polynomial |
Polynomial decay | Fine-tuning |
Optimizers#
| Optimizer | VRAM | Speed | Description |
|---|---|---|---|
AdamW |
High | Fast | Standard optimizer |
AdamW8bit |
Low | Fast | Memory-efficient (recommended) |
SGD |
Low | Medium | Simple, stable |
Lion |
Medium | Fast | Newer, efficient |
Prodigy |
Medium | Fast | Adaptive LR |
DAdaptAdam |
Medium | Medium | Self-tuning LR |
Supported Base Models#
Image Models#
| Model ID | Name | Min VRAM | Notes |
|---|---|---|---|
sd15 |
SD 1.5 | 6GB | Fast, broad compatibility |
sd21 |
SD 2.1 | 8GB | Improved quality |
sdxl |
SDXL 1.0 | 12GB | High quality, recommended |
flux-dev |
Flux Dev | 24GB | Best quality |
flux-schnell |
Flux Schnell | 20GB | Faster Flux |
sd35-medium |
SD 3.5 Medium | 16GB | Latest SD |
sd35-large |
SD 3.5 Large | 24GB | Best SD 3.5 |
pony-v6 |
Pony V6 | 12GB | Anime/stylized |
Video Models#
| Model ID | Name | Min VRAM | Notes |
|---|---|---|---|
hunyuan-video |
Hunyuan Video | 24GB | Character LoRAs |
wan-2.1 |
Wan 2.1 | 24GB | Motion LoRAs |
wan-2.2 |
Wan 2.2 | 24GB | Improved Wan |
Training Helpers#
Character LoRA#
typescript
// Quick character training
const job = await training.trainCharacterLoRA('john-doe', images, 'johndoe', {
epochs: 20,
network: { rank: 128 },
});
Style LoRA#
typescript
// Style training with lower rank
const job = await training.trainStyleLoRA(
'watercolor-style',
images,
'watercolor',
{
network: { rank: 32 },
}
);
Motion LoRA#
typescript
// Video motion training
const job = await training.trainMotionLoRA(
'smooth-pan',
videoClips,
'smoothpan',
{
epochs: 25,
}
);
Hunyuan Character LoRA#
typescript
// Character for Hunyuan video
const job = await training.trainHunyuanLoRA(
'my-character-hunyuan',
images,
'mychar'
);
Progress Monitoring#
Job Status#
typescript
// Get job by ID
const job = training.getJob(jobId);
console.log('Status:', job.status);
console.log('Progress:', job.progress?.progress * 100, '%');
console.log('Current epoch:', job.progress?.currentEpoch);
console.log('Current step:', job.progress?.currentStep);
console.log('Loss:', job.progress?.loss.currentLoss);
Job Status Flow#
text
queued → preparing → training → completed
→ failed
→ cancelled
Event Handling#
typescript
// Listen for training events
training.on('training:started', ({ job }) => {
console.log('Training started:', job.id);
});
training.on('training:progress', ({ jobId, progress }) => {
console.log(`${jobId}: ${progress.progress * 100}%`);
console.log(`Loss: ${progress.loss.currentLoss}`);
console.log(`ETA: ${progress.estimatedTimeRemaining}s`);
});
training.on('training:sample', ({ jobId, sample }) => {
console.log(`Sample generated at step ${sample.step}:`);
console.log(`URL: ${sample.imageUrl}`);
});
training.on('training:checkpoint', ({ jobId, checkpoint }) => {
console.log(`Checkpoint saved at epoch ${checkpoint.epoch}`);
console.log(`Download: ${checkpoint.downloadUrl}`);
});
training.on('training:completed', ({ job }) => {
console.log('Training completed!');
console.log('LoRA URL:', job.loraUrl);
});
training.on('training:failed', ({ jobId, error }) => {
console.error('Training failed:', error);
});
Loss Metrics#
typescript
const progress = job.progress;
// Loss tracking
console.log('Current loss:', progress.loss.currentLoss);
console.log('Average loss:', progress.loss.averageLoss);
console.log('Best loss:', progress.loss.bestLoss);
console.log('Best loss step:', progress.loss.bestLossStep);
// Loss history
for (const point of progress.loss.lossHistory) {
console.log(`Step ${point.step}: ${point.loss}`);
}
Waiting for Completion#
typescript
// Wait with default timeout
const completedJob = await training.waitForTraining(jobId);
console.log('LoRA URL:', completedJob.loraUrl);
// Wait with custom timeout (2 hours)
const job = await training.waitForTraining(jobId, 7200000);
Cost Estimation#
Calculating Training Cost#
typescript
import {
estimateTrainingCost,
calculateTotalSteps,
} from '@oshun/civitai-training';
// Calculate total steps
const totalSteps = calculateTotalSteps(
50, // imageCount
5, // repeats per image
2, // batchSize
15 // epochs
);
// (50 * 5) / 2 * 15 = 1875 steps
// Estimate cost
const cost = estimateTrainingCost('sdxl', totalSteps);
console.log('Estimated cost:', cost, 'Buzz');
Cost Per 1000 Steps#
| Base Model | Cost/1000 Steps |
|---|---|
| SD 1.5 | 50 Buzz |
| SD 2.1 | 50 Buzz |
| SDXL | 100 Buzz |
| Flux Dev | 200 Buzz |
| Flux Schnell | 150 Buzz |
| SD 3.5 Medium | 120 Buzz |
| SD 3.5 Large | 180 Buzz |
| Pony V6 | 100 Buzz |
| Hunyuan Video | 300 Buzz |
| Wan 2.1 | 300 Buzz |
| Wan 2.2 | 350 Buzz |
Time Estimation#
typescript
import { estimateTrainingTime } from '@oshun/civitai-training';
// Estimate time (default 0.5 steps/second)
const timeSeconds = estimateTrainingTime(1875);
console.log('Estimated time:', timeSeconds / 60, 'minutes');
// With custom speed
const timeWithSpeed = estimateTrainingTime(1875, 0.8);
Validation#
Dataset Validation#
typescript
import { validateDataset } from '@oshun/civitai-training';
const validation = validateDataset(dataset);
if (!validation.valid) {
console.error('Dataset errors:', validation.errors);
// [
// 'Minimum 4 images required for effective training',
// 'Dataset must contain at least one image or video'
// ]
}
Hyperparameter Validation#
typescript
import { validateHyperparams } from '@oshun/civitai-training';
const validation = validateHyperparams(hyperparams);
if (!validation.valid) {
console.error('Errors:', validation.errors);
}
if (validation.warnings.length > 0) {
console.warn('Warnings:', validation.warnings);
// [
// 'Alpha is typically equal to or less than rank',
// 'More than 50 epochs may lead to overfitting'
// ]
}
Optimal Learning Rate#
typescript
import { calculateOptimalLR } from '@oshun/civitai-training';
// Calculate optimal LR based on rank
const optimalLR = calculateOptimalLR(
128, // rank
0.0001 // base LR
);
// Scales based on rank^-0.84
Job Management#
Cancelling Training#
typescript
await training.cancelJob(jobId);
Getting All Jobs#
typescript
const allJobs = training.getAllJobs();
for (const job of allJobs) {
console.log(`${job.id}: ${job.status}`);
if (job.progress) {
console.log(` Progress: ${job.progress.progress * 100}%`);
}
}
Downloading Results#
typescript
// Download completed LoRA
const { url, filename } = await training.downloadLoRA(jobId);
console.log('Download URL:', url);
console.log('Filename:', filename);
Best Practices#
Dataset Preparation#
- Quality over quantity: 20 high-quality images > 100 poor ones
- Variety: Include different poses, angles, lighting, backgrounds
- Consistency: Keep style/subject consistent within dataset
- Resolution: Use images at or above training resolution
- Captions: Detailed captions improve quality significantly
Hyperparameter Tuning#
- Start with presets: Use presets as baseline
- Rank selection: Higher rank = more capacity but risk of overfitting
- Learning rate: Start low, increase if underfitting
- Epochs: Monitor loss, stop if it increases
- Batch size: Larger batches = more stable, but need more VRAM
Training Monitoring#
- Watch the loss curve: Should decrease smoothly
- Check samples: Visual quality is ultimate measure
- Look for overfitting: Loss goes up or samples look identical
- Save checkpoints: Keep intermediate checkpoints
Common Issues#
| Issue | Symptoms | Solution |
|---|---|---|
| Overfitting | Samples look identical to training images | Reduce epochs, increase dropout |
| Underfitting | LoRA has no effect | Increase rank, LR, or epochs |
| Catastrophic forgetting | Model loses base capabilities | Lower LR, use regularization |
| Mode collapse | Limited variety in outputs | Increase dataset variety |
Error Handling#
Error Codes#
| Code | Description |
|---|---|
AUTH_FAILED |
Invalid API key |
INSUFFICIENT_BUZZ |
Not enough Buzz balance |
INVALID_DATASET |
Dataset validation failed |
INVALID_CONFIG |
Invalid hyperparameters |
TRAINING_FAILED |
Training failed on server |
JOB_NOT_FOUND |
Job doesn't exist |
TIMEOUT |
Training timed out |
Error Handling Example#
typescript
import { LoraTrainingError } from '@oshun/civitai-training';
try {
const job = await training.startTraining(config);
const completed = await training.waitForTraining(job.id);
} catch (error) {
if (error instanceof LoraTrainingError) {
switch (error.code) {
case 'INSUFFICIENT_BUZZ':
console.error('Not enough Buzz. Please top up.');
break;
case 'INVALID_DATASET':
console.error('Dataset issues:', error.details);
break;
case 'TRAINING_FAILED':
console.error('Training failed:', error.message);
break;
default:
console.error('Error:', error.message);
}
}
}
Lifecycle Management#
Shutdown#
typescript
// Graceful shutdown - cancels running jobs
await training.shutdown();
TypeScript Types#
typescript
import type {
// Dataset Types
DatasetImage,
DatasetVideo,
DatasetConfig,
DatasetStats,
// Training Configuration
TrainingConfig,
TrainingHyperparams,
NetworkParams,
LearningRateParams,
// Base Models
TrainingBaseModel,
TrainingType,
TrainingPreset,
// Job Types
TrainingJob,
TrainingStatus,
TrainingProgress,
// Progress Types
LossMetrics,
TrainingSample,
TrainingCheckpoint,
// Configuration
LoraTrainingConfig,
OptimizerType,
LRScheduler,
CaptionMode,
} from '@oshun/civitai-training';
Related Documentation#
- API Documentation - Generation API reference
- Model Management - Model discovery and downloads
- Best Practices - Optimization and cost management