This guide covers optimization strategies, cost management, and production recommendations for using Civitai in the Oshun platform.
Cost Optimization#
Understanding Buzz#
Civitai uses "Buzz" as its credit system. Understanding cost factors helps optimize spending.
Image Generation Cost Factors#
| Factor | Impact | Optimization |
|---|---|---|
| Base Model | SD1=2, SDXL=5, Flux=8 Buzz | Use SD1.5 for prototyping |
| LoRAs/Networks | +0.5-2 Buzz per network | Minimize network count |
| Batch Size | Linear multiplier | Batch similar requests |
| Draft Mode | 40% savings on SDXL | Use for previews |
Cost Optimization Strategies#
typescript
// 1. Use draft mode for previews
const preview = await provider.generateImageWithPreset(
model,
prompt,
'fast' // 12 steps, lower CFG
);
// 2. Batch similar generations
const jobs = await Promise.all([
provider.generateImage({ model, params: { prompt: 'A' }, batchSize: 4 }),
// Instead of 4 separate requests
]);
// 3. Cache generated images
const cacheKey = generateCacheKey(prompt, model, seed);
const cached = await cache.get(cacheKey);
if (!cached) {
const result = await provider.generateImageAndWait(request);
await cache.set(cacheKey, result);
}
// 4. Track and budget costs
const budget = 1000; // Buzz
let spent = 0;
provider.on('cost:recorded', ({ cost }) => {
spent += cost;
if (spent > budget * 0.8) {
console.warn('80% of budget used!');
}
});
Video Generation Cost Management#
typescript
// 1. Start with shorter durations
const testVideo = await provider.generateVideo({
model: 'wan-2.1',
params: {
prompt: 'Test motion',
duration: 2, // Start short
quality: 'draft', // 50% cost
},
});
// 2. Use appropriate models
// ltxv/mochi = 8 Buzz/s, veo3 = 25 Buzz/s
const economicalJob = await provider.generateVideo({
model: 'ltxv', // Most economical
params: { prompt: 'Simple animation', duration: 4 },
});
// 3. Validate prompts before generation
function validateVideoPrompt(prompt: string): boolean {
// Ensure prompt is specific enough
return prompt.length > 20 && prompt.includes('motion');
}
Training Cost Optimization#
typescript
import {
estimateTrainingCost,
calculateTotalSteps,
} from '@oshun/civitai-training';
// 1. Estimate before training
const steps = calculateTotalSteps(images.length, repeats, batchSize, epochs);
const estimatedCost = estimateTrainingCost('sdxl', steps);
if (estimatedCost > budget) {
// Reduce epochs or images
epochs = Math.floor(epochs * (budget / estimatedCost));
}
// 2. Use smaller ranks for styles (less training needed)
const styleConfig = {
network: { rank: 32 }, // Not 128
epochs: 10, // Not 20
};
// 3. Start with fast-test preset
const testJob = await training.startTrainingWithPreset(
'fast-test', // 3 epochs, rank 8
dataset,
'test-run'
);
// Check results before full training
API Optimization#
Rate Limiting Best Practices#
typescript
// 1. Pre-check rate limit state
const rateLimitState = service.getRateLimitState();
if (rateLimitState.remaining < 5) {
// Wait or use cached data
await sleep(rateLimitState.getWaitTimeMs());
}
// 2. Implement request queuing
class RequestQueue {
private queue: Array<() => Promise<void>> = [];
private processing = false;
async add<T>(request: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await request();
resolve(result);
} catch (error) {
reject(error);
}
});
this.processQueue();
});
}
private async processQueue(): Promise<void> {
if (this.processing) return;
this.processing = true;
while (this.queue.length > 0) {
const request = this.queue.shift()!;
await request();
await sleep(1000); // Minimum delay between requests
}
this.processing = false;
}
}
// 3. Use event-based rate limit handling
service.on('rate_limited', async ({ waitTime }) => {
console.log(`Rate limited. Waiting ${waitTime}ms...`);
await sleep(waitTime);
});
Caching Strategies#
typescript
// 1. Model metadata caching
const modelCache = new Map<number, { model: Model; expiresAt: Date }>();
async function getModelCached(modelId: number): Promise<Model> {
const cached = modelCache.get(modelId);
if (cached && cached.expiresAt > new Date()) {
return cached.model;
}
const model = await service.getModel(modelId);
modelCache.set(modelId, {
model,
expiresAt: new Date(Date.now() + 3600000), // 1 hour
});
return model;
}
// 2. Search result caching
const searchCache = new RequestCache<ModelInfo[]>(100, 300);
async function searchModelsCached(
query: ModelSearchQuery
): Promise<ModelInfo[]> {
const key = RequestCache.generateKey('search', query);
let results = searchCache.get(key);
if (!results) {
const response = await service.listModels(query);
results = response.items;
searchCache.set(key, results);
}
return results;
}
// 3. Hash-to-version lookup caching
const hashCache = new Map<string, number>(); // hash -> versionId
Error Recovery#
typescript
// 1. Implement exponential backoff
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (!isRetryable(error)) throw error;
const delay = baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * delay * 0.1;
await sleep(delay + jitter);
}
}
throw lastError!;
}
function isRetryable(error: unknown): boolean {
if (error instanceof CivitaiError) {
return ['RATE_LIMITED', 'TIMEOUT', 'NETWORK_ERROR'].includes(error.code);
}
return false;
}
// 2. Circuit breaker integration
service.on('circuit_open', () => {
// Switch to fallback provider or queue requests
useFallbackProvider();
});
// 3. Graceful degradation
async function generateWithFallback(
request: ImageGenerationRequest
): Promise<JobResult> {
try {
return await provider.generateImageAndWait(request);
} catch (error) {
if (error instanceof CivitaiError && error.code === 'RATE_LIMITED') {
// Try with lower priority
return await fallbackProvider.generateImageAndWait({
...request,
params: { ...request.params, steps: 10 }, // Reduce steps
});
}
throw error;
}
}
Generation Best Practices#
Prompt Engineering#
typescript
// 1. Structure prompts consistently
interface PromptBuilder {
subject: string;
style?: string;
quality?: string[];
negative?: string[];
}
function buildPrompt(builder: PromptBuilder): string {
const parts = [builder.subject];
if (builder.style) {
parts.push(builder.style);
}
if (builder.quality?.length) {
parts.push(...builder.quality);
}
return parts.join(', ');
}
function buildNegativePrompt(builder: PromptBuilder): string {
const defaults = ['blurry', 'low quality', 'artifacts', 'distorted'];
return [...defaults, ...(builder.negative || [])].join(', ');
}
// Usage
const prompt = buildPrompt({
subject: 'A majestic dragon',
style: 'digital art',
quality: ['highly detailed', '8k', 'trending on artstation'],
});
// 2. Model-specific prompts
const modelPromptStyles: Record<string, (base: string) => string> = {
// SDXL responds well to quality tags
sdxl: (base) => `${base}, masterpiece, best quality`,
// Flux prefers natural language
flux: (base) => `A high quality image of ${base}, photorealistic`,
// Pony uses specific tags
pony: (base) => `${base}, score_9, score_8_up`,
};
Seed Management#
typescript
// 1. Track seeds for reproducibility
interface GenerationRecord {
prompt: string;
model: string;
seed: number;
result: string;
}
const generations: GenerationRecord[] = [];
async function generateTracked(
request: ImageGenerationRequest
): Promise<JobResult> {
const seed = request.params.seed ?? Math.floor(Math.random() * 2147483647);
const job = await provider.generateImageAndWait({
...request,
params: { ...request.params, seed },
});
generations.push({
prompt: request.params.prompt,
model: request.model,
seed,
result: job.blobUrl!,
});
return job;
}
// 2. Seed variation for similar results
function generateSeedVariations(baseSeed: number, count: number): number[] {
return Array.from({ length: count }, (_, i) => baseSeed + i);
}
Quality vs Speed Trade-offs#
typescript
// Quality levels with configurations
const qualityConfigs = {
preview: {
steps: 8,
scheduler: 'LCM' as const,
cfgScale: 2,
width: 512,
height: 512,
},
draft: {
steps: 15,
scheduler: 'EulerA' as const,
cfgScale: 5,
width: 768,
height: 768,
},
standard: {
steps: 25,
scheduler: 'DPM2MKarras' as const,
cfgScale: 7,
width: 1024,
height: 1024,
},
high: {
steps: 40,
scheduler: 'DPM2MKarras' as const,
cfgScale: 7,
width: 1024,
height: 1024,
},
ultra: {
steps: 60,
scheduler: 'DPMSDEKarras' as const,
cfgScale: 7,
width: 1024,
height: 1024,
},
};
// Use preview for iterations, standard for final
async function iterativeGeneration(prompt: string): Promise<JobResult> {
// Quick preview
const preview = await provider.generateImage({
model,
params: { prompt, ...qualityConfigs.preview },
});
// If user approves preview, generate high quality
const final = await provider.generateImage({
model,
params: { prompt, ...qualityConfigs.standard, seed: preview.seed },
});
return final;
}
Model Selection#
Choosing the Right Base Model#
| Use Case | Recommended Model | Why |
|---|---|---|
| Photorealistic portraits | Juggernaut XL | Best for faces |
| Anime/illustration | Animagine XL | Trained on anime |
| General purpose | RealVisXL | Versatile |
| Highest quality | Flux Dev | Best overall |
| Fast iteration | SD 1.5 | Low cost, fast |
| Video characters | Hunyuan | Good LoRA support |
| Video motion | Wan 2.1/2.2 | Motion training |
LoRA Selection#
typescript
// 1. Check LoRA compatibility
function isCompatible(
loraBaseModel: string,
checkpointBaseModel: string
): boolean {
const compatibility: Record<string, string[]> = {
'SD 1.5': ['SD 1.5', 'SD 1.4'],
'SDXL 1.0': ['SDXL 1.0', 'SDXL 0.9'],
Flux: ['Flux.1 D', 'Flux.1 S'],
};
return compatibility[checkpointBaseModel]?.includes(loraBaseModel) ?? false;
}
// 2. Stack LoRAs intelligently
function optimizeLoraStack(loras: AdditionalNetwork[]): AdditionalNetwork[] {
// Sort by importance
const sorted = [...loras].sort(
(a, b) => (b.strength ?? 1) - (a.strength ?? 1)
);
// Limit to 3-4 LoRAs for best results
const limited = sorted.slice(0, 4);
// Reduce total strength if > 1.5
const totalStrength = limited.reduce((sum, l) => sum + (l.strength ?? 1), 0);
if (totalStrength > 1.5) {
const scale = 1.5 / totalStrength;
return limited.map((l) => ({ ...l, strength: (l.strength ?? 1) * scale }));
}
return limited;
}
Training Best Practices#
Dataset Quality#
typescript
// 1. Pre-process images
async function preprocessDataset(
images: DatasetImage[]
): Promise<DatasetImage[]> {
return Promise.all(
images.map(async (img) => {
// Ensure consistent resolution
const processed = await resizeImage(img.image, 1024, 1024);
// Generate caption if missing
const caption = img.caption ?? (await generateCaption(processed));
return { ...img, image: processed, caption };
})
);
}
// 2. Validate dataset diversity
function analyzeDatasetDiversity(images: DatasetImage[]): {
issues: string[];
recommendations: string[];
} {
const issues: string[] = [];
const recommendations: string[] = [];
// Check image count
if (images.length < 10) {
issues.push('Dataset too small (< 10 images)');
recommendations.push('Add more images for better results');
}
// Check caption diversity
const uniqueCaptions = new Set(images.map((i) => i.caption)).size;
if (uniqueCaptions < images.length * 0.5) {
issues.push('Low caption diversity');
recommendations.push('Use more varied captions');
}
// Check for repeats balance
const avgRepeats =
images.reduce((sum, i) => sum + (i.repeats ?? 1), 0) / images.length;
if (avgRepeats < 3) {
recommendations.push('Consider increasing repeats for small datasets');
}
return { issues, recommendations };
}
Training Monitoring#
typescript
// 1. Loss monitoring with early stopping
class LossMonitor {
private history: number[] = [];
private bestLoss = Infinity;
private patienceCounter = 0;
constructor(private patience: number = 5) {}
update(loss: number): { shouldStop: boolean; reason?: string } {
this.history.push(loss);
if (loss < this.bestLoss) {
this.bestLoss = loss;
this.patienceCounter = 0;
} else {
this.patienceCounter++;
}
// Check for overfitting
if (this.patienceCounter >= this.patience) {
return { shouldStop: true, reason: 'Loss not improving (overfitting)' };
}
// Check for explosion
if (this.history.length > 10) {
const recent = this.history.slice(-10);
const avgRecent = recent.reduce((a, b) => a + b, 0) / recent.length;
const avgPrevious =
this.history.slice(-20, -10).reduce((a, b) => a + b, 0) / 10;
if (avgRecent > avgPrevious * 1.5) {
return { shouldStop: true, reason: 'Loss exploding' };
}
}
return { shouldStop: false };
}
}
// 2. Progress reporting
training.on('training:progress', ({ jobId, progress }) => {
const eta = progress.estimatedTimeRemaining
? `${Math.round(progress.estimatedTimeRemaining / 60)}min remaining`
: 'calculating...';
console.log(
[
`Job: ${jobId}`,
`Epoch: ${progress.currentEpoch}/${progress.totalEpochs}`,
`Step: ${progress.currentStep}/${progress.totalSteps}`,
`Loss: ${progress.loss.currentLoss.toFixed(4)}`,
`Best: ${progress.loss.bestLoss.toFixed(4)}`,
`ETA: ${eta}`,
].join(' | ')
);
});
Production Deployment#
Health Monitoring#
typescript
interface HealthStatus {
healthy: boolean;
checks: {
api: { status: 'ok' | 'degraded' | 'down'; latency?: number };
rateLimit: { status: 'ok' | 'warning' | 'critical'; remaining: number };
circuitBreaker: { status: 'ok' | 'open'; state: string };
cache: { hitRate: number };
};
}
async function checkHealth(): Promise<HealthStatus> {
const rateLimitState = service.getRateLimitState();
const circuitState = service.getCircuitBreakerState();
const cacheStats = service.getCacheStats();
const checks = {
api: {
status:
circuitState.state === 'open' ? ('down' as const) : ('ok' as const),
},
rateLimit: {
status:
rateLimitState.remaining < 10
? ('critical' as const)
: rateLimitState.remaining < 30
? ('warning' as const)
: ('ok' as const),
remaining: rateLimitState.remaining,
},
circuitBreaker: {
status:
circuitState.state === 'open' ? ('open' as const) : ('ok' as const),
state: circuitState.state,
},
cache: {
hitRate: cacheStats.hits / (cacheStats.hits + cacheStats.misses) || 0,
},
};
return {
healthy:
checks.api.status === 'ok' && checks.circuitBreaker.status === 'ok',
checks,
};
}
Metrics Collection#
typescript
// 1. Request metrics
const metrics = {
requestsTotal: 0,
requestsSuccessful: 0,
requestsFailed: 0,
latencyHistogram: new Array(10).fill(0), // 0-100ms, 100-200ms, etc.
buzzSpent: 0,
};
service.on('request_complete', ({ duration }) => {
metrics.requestsTotal++;
metrics.requestsSuccessful++;
const bucket = Math.min(Math.floor(duration / 100), 9);
metrics.latencyHistogram[bucket]++;
});
service.on('request_error', () => {
metrics.requestsTotal++;
metrics.requestsFailed++;
});
provider.on('cost:recorded', ({ cost }) => {
metrics.buzzSpent += cost;
});
// 2. Prometheus-style metrics export
function exportMetrics(): string {
return [
`civitai_requests_total ${metrics.requestsTotal}`,
`civitai_requests_successful ${metrics.requestsSuccessful}`,
`civitai_requests_failed ${metrics.requestsFailed}`,
`civitai_buzz_spent_total ${metrics.buzzSpent}`,
`civitai_cache_hit_rate ${service.getCacheStats().hits / (service.getCacheStats().hits + service.getCacheStats().misses)}`,
].join('\n');
}
Alerting#
typescript
// 1. Cost alerting
const dailyBudget = 5000; // Buzz
let dailySpent = 0;
provider.on('cost:recorded', ({ cost }) => {
dailySpent += cost;
if (
dailySpent > dailyBudget * 0.8 &&
dailySpent - cost <= dailyBudget * 0.8
) {
sendAlert('warning', '80% of daily Buzz budget used');
}
if (dailySpent > dailyBudget) {
sendAlert('critical', 'Daily Buzz budget exceeded');
}
});
// 2. Error rate alerting
let recentErrors = 0;
let recentRequests = 0;
service.on('request_complete', () => {
recentRequests++;
});
service.on('request_error', () => {
recentRequests++;
recentErrors++;
});
setInterval(() => {
if (recentRequests > 10) {
const errorRate = recentErrors / recentRequests;
if (errorRate > 0.1) {
sendAlert('warning', `High error rate: ${(errorRate * 100).toFixed(1)}%`);
}
}
recentErrors = 0;
recentRequests = 0;
}, 60000);
// 3. Circuit breaker alerting
service.on('circuit_open', ({ endpoint }) => {
sendAlert('critical', `Circuit breaker opened for ${endpoint}`);
});
Security Considerations#
API Key Management#
typescript
// 1. Never hardcode API keys
const apiKey = process.env.CIVITAI_API_KEY;
if (!apiKey) {
throw new Error('CIVITAI_API_KEY environment variable required');
}
// 2. Rotate keys periodically
// Store key metadata
interface KeyMetadata {
createdAt: Date;
lastUsed: Date;
rotatedAt?: Date;
}
// 3. Audit key usage
service.on('request_start', ({ endpoint }) => {
auditLog.log({
action: 'civitai_api_call',
endpoint,
timestamp: new Date(),
keyId: process.env.CIVITAI_KEY_ID,
});
});
Content Safety#
typescript
// 1. Filter content by rating
const service = createCivitAIService({
apiKey,
nsfwEnabled: false, // Disable NSFW content
contentLevel: 'PG13', // Maximum content level
});
// 2. Validate model content ratings
async function validateModelSafety(modelId: number): Promise<boolean> {
const model = await service.getModel(modelId);
if (model.nsfw) {
console.warn(`Model ${modelId} is NSFW`);
return false;
}
return service.isContentLevelAllowed(model.nsfwLevel);
}
// 3. Content moderation for generated images
async function moderateGeneration(result: JobResult): Promise<boolean> {
// Implement content moderation
const isSafe = await contentModerationService.check(result.blobUrl);
return isSafe;
}
Troubleshooting#
Common Issues#
| Issue | Cause | Solution |
|---|---|---|
| Rate limited frequently | Too many requests | Implement request queuing |
| High latency | Large batch sizes | Reduce batch size |
| Generation failures | Model unavailable | Use fallback models |
| Training takes too long | Too many epochs | Reduce epochs, use presets |
| LoRA has no effect | Low strength | Increase LoRA strength |
| Images look identical | Seed reuse | Use random seeds |
Debugging#
typescript
// 1. Enable verbose logging
service.on('request_start', (e) => console.log('Request:', e));
service.on('request_complete', (e) => console.log('Complete:', e));
service.on('request_error', (e) => console.error('Error:', e));
// 2. Dump state for debugging
function debugDump(): void {
console.log('=== Civitai Debug Dump ===');
console.log('Rate Limit:', service.getRateLimitState());
console.log('Circuit:', service.getCircuitBreakerState());
console.log('Cache:', service.getCacheStats());
console.log('Analytics:', service.getAnalytics());
}
// 3. Test connectivity
async function testConnection(): Promise<boolean> {
try {
await service.listTags({ limit: 1 });
return true;
} catch (error) {
console.error('Connection test failed:', error);
return false;
}
}
Related Documentation#
- API Documentation - Generation API reference
- Model Management - Model discovery and downloads
- Training Guide - Training custom LoRAs