Skip to content

SDXL & SD1 LoRA training ​

Train a Stable Diffusion LoRA on your own image dataset using AI Toolkit. This page covers the two classic SD ecosystems:

ecosystemOutput LoRA usable withDefault priceNative resolution
sdxlSDXL image generation500 Buzz1024²
sd1SD1 image generation500 Buzz512²

Both are the cheapest training ecosystems on the platform — good first pick when iterating on dataset choice or hyperparameters before stepping up to a more expensive model family. SDXL is the better default for new fine-tunes; SD1 is mostly useful when you specifically need a SD 1.5 LoRA (e.g. to deploy onto an existing SD 1.5 product).

The request shape ​

Every training request is a single training step with an engine and ecosystem selector:

json
{
  "$type": "training",
  "input": {
    "engine":    "ai-toolkit",
    "ecosystem": "sdxl"          // sdxl | sd1
  }
}

The input shape is shared with the other AI Toolkit ecosystems — see Common parameters below. The fields documented here are the SD-family-specific ones (model, minSnrGamma, triggerWord, default text-encoder behavior).

Long-running step

Training takes minutes to hours depending on dataset size and steps. Always submit with wait=0 and follow up with polling or a webhook — see Results & webhooks. The <RecipeRun> widgets below preview cost via whatif=true so you can see the price without kicking off an actual training run.

Prerequisites ​

  • A Civitai orchestration token (Quick start → Prerequisites)

  • A training-data zip that sourceUrl can address. sourceUrl is an AIR, so the accepted forms are:

    • A Hugging Face file AIR — urn:air:other:other:huggingface:{repo}@{revision}/{path}, as the examples below use. Pin a commit sha rather than main so the reference can't move.
    • A Civitai storage AIR or URL — R2 (urn:air:other:other:civitai-r2:{bucket}@{key}, or the equivalent *.r2.cloudflarestorage.com URL), Backblaze B2, or DigitalOcean Spaces
    • No hosted zip? Upload each image through POST /v2/consumer/blobs and pass them as trainingData.items instead — see Training data as blobs.

    A URL on any other host is rejected: nothing maps it to an AIR.

  • An accurate count of images in the zip — used for batch sizing

SDXL ​

Stable Diffusion XL trains at 1024² and produces a LoRA usable with any SDXL checkpoint. The base checkpoint defaults to urn:air:sdxl:checkpoint:civitai:101055@128078 (Juggernaut XL); override model to train on top of a different SDXL checkpoint.

http
POST https://orchestration.civitai.com/v2/consumer/workflows?wait=0
Authorization: Bearer <your-token>
Content-Type: application/json

{
  "tags": ["training"],
  "steps": [{
    "$type": "training",
    "priority": "normal",
    "retries": 2,
    "input": {
      "engine": "ai-toolkit",
      "ecosystem": "sdxl",
      "model": "urn:air:sdxl:checkpoint:civitai:101055@128078",
      "steps": 2000,
      "lr": 0.0005,
      "textEncoderLr": 5e-5,
      "trainTextEncoder": true,
      "lrScheduler": "cosine",
      "optimizerType": "adafactor",
      "networkDim": 32,
      "networkAlpha": 32,
      "noiseOffset": 0.1,
      "minSnrGamma": 5,
      "triggerWord": "cat",
      "trainingData": {
        "type": "zip",
        "sourceUrl": "urn:air:other:other:huggingface:datasets/Civitai/orchestration-samples@e86d025874700507615ae8ef74937c319ea41dfe/sample-training-dataset.zip",
        "count": 15
      },
      "samples": {
        "prompts": [
          "catz no humans, cat, whiskers, animal focus, looking at viewer, animal, solo, yellow eyes",
          "catz no humans, candle, pokemon (creature), blurry, animal focus, solo, food, bird, standing",
          "catz no humans, food, noodles, bowl, cat, green eyes, tongue, food focus, ramen, chopsticks"
        ]
      }
    }
  }]
}
POST/v2/consumer/workflows
Set your Civitai API token via the Token button in the navbar to enable Try It.
Request body — edit to customize (e.g. swap the image URL or prompt)
Valid JSON

SDXL-specific fields:

FieldDefaultRange / valuesNotes
modelurn:air:sdxl:checkpoint:civitai:101055@128078Any SDXL checkpoint AIRThe base checkpoint your LoRA is trained on top of.
minSnrGamma50–20Min-SNR-γ stabilization. 5 is a good default; lower values can speed up convergence on simple subjects.
triggerWord(none)stringOptional prompt token that activates the trained LoRA. Recommended for character / style LoRAs.
trainTextEncodertrueboolSDXL benefits noticeably from text-encoder training. Disable to halve memory cost at the price of prompt-following quality.
textEncoderLr5e-50–1Only used when trainTextEncoder: true.
optimizerTypeadafactorsee enumadafactor is the SDXL house default; adamw8bit works too if you have memory headroom.

SD1 ​

Stable Diffusion 1.5 trains at 512² and produces a LoRA usable with any SD1.5 checkpoint. Base checkpoint defaults to urn:air:sd1:checkpoint:civitai:127227@139180. Same shape as SDXL with two differences: optimizerType defaults to adamw8bit (not adafactor), and noiseOffset defaults to 0 (not 0.1).

http
POST https://orchestration.civitai.com/v2/consumer/workflows?wait=0
Authorization: Bearer <your-token>
Content-Type: application/json

{
  "tags": ["training"],
  "steps": [{
    "$type": "training",
    "input": {
      "engine": "ai-toolkit",
      "ecosystem": "sd1",
      "model": "urn:air:sd1:checkpoint:civitai:127227@139180",
      "steps": 4000,
      "lr": 0.0005,
      "textEncoderLr": 5e-5,
      "trainTextEncoder": true,
      "lrScheduler": "cosine",
      "optimizerType": "adamw8bit",
      "networkDim": 32,
      "networkAlpha": 16,
      "noiseOffset": 0.1,
      "minSnrGamma": 5,
      "trainingData": {
        "type": "zip",
        "sourceUrl": "urn:air:other:other:huggingface:datasets/Civitai/orchestration-samples@e86d025874700507615ae8ef74937c319ea41dfe/sample-training-dataset.zip",
        "count": 15
      },
      "samples": {
        "prompts": [
          "no humans, cat, whiskers, animal focus, looking at viewer, animal, solo, yellow eyes",
          "no humans, food, tiger, cake, animal focus, food focus, whiskers, black background",
          "no humans, fruit, food, bug, black background, wings, food focus, orange (fruit), antennae"
        ]
      }
    }
  }]
}
POST/v2/consumer/workflows
Set your Civitai API token via the Token button in the navbar to enable Try It.
Request body — edit to customize (e.g. swap the image URL or prompt)
Valid JSON

SD1-specific fields are a subset of the SDXL list — same model / minSnrGamma / triggerWord / trainTextEncoder / textEncoderLr, with these defaults:

FieldSD1 defaultSDXL default
optimizerTypeadamw8bitadafactor
noiseOffset00.1

Common parameters ​

These apply to every AI Toolkit training input regardless of ecosystem. Defaults shown are the post-ApplyDefaults values for SDXL; some ecosystems override individual defaults (the SD1 / SDXL differences above are the main examples).

FieldRequiredDefaultNotes
engine✅—Always ai-toolkit for these recipes.
ecosystem✅—sdxl or sd1 for this page.
steps20001–10000. Total training steps. Primary driver of training length and pricing (see Cost).
epochs101–20. Number of saved checkpoints delivered, each separately downloadable. Each adds 10 Buzz of storage (see Cost).
batchSize1Defaults to 1. Raise it up to the ecosystem maximum (4 for both SDXL and SD1) to train faster at the cost of more GPU memory; a larger batch needs fewer steps. Values above the max are clamped.
continueFrom(none)A previously-trained urn:air:sdxl:lora:... / urn:air:sd1:lora:... AIR to resume from (see Continue training). Must be a LoRA of the same ecosystem.
lr0.00010–1. UNet learning rate. 0.0005 is typical for character/style LoRAs on SDXL.
textEncoderLr—Only used when trainTextEncoder: true. SDXL/SD1 default to 5e-5.
trainTextEncodertrue (SDXL/SD1)Train CLIP alongside the diffuser.
lrSchedulercosineconstant, constant_with_warmup, cosine, linear, step.
optimizerTypeadafactor (SDXL) / adamw8bit (SD1)adamw, adamw8bit, adam8bit, lion, lion8bit, adafactor, adagrad, prodigy, prodigy8bit, automagic.
networkDim321–256. LoRA rank. Higher = more capacity, larger LoRA file.
networkAlphamatches networkDim1–256. Scales the effective learning rate via alpha / dim.
noiseOffset0.1 (SDXL) / 0 (SD1)0–1. Adds noise during training to improve dark/bright sample handling.
flipAugmentationfalseRandom horizontal flips. Useful for symmetric subjects.
shuffleTokensfalseRandomize caption-tag order.
keepTokens00–10. When shuffleTokens: true, keep the first N tokens fixed.
triggerWord(none)Activation token recommended for character/style LoRAs.
trainingData.type✅—zip or blobs (see Training data as blobs).
trainingData.sourceUrl✅ (zip)—Hugging Face file AIR, or a Civitai R2/B2/Spaces AIR or URL.
trainingData.count✅ (zip)—Number of images in the zip.
trainingData.items[]✅ (blobs)—Up to 1000 { air, caption? } objects referencing consumer-uploaded blobs.
samples.prompts[][]Up to a handful of preview prompts rendered at each saved checkpoint with the trained LoRA at strength 1.0. Empty entries are skipped.
samples.negativePrompt(none)Applied to all sample prompts.
samples.cfgScale(ecosystem default)Overrides the CFG / guidance scale used when rendering the preview samples.
samples.strength1.0Trained-LoRA weight applied in the preview samples.

Training data as blobs ​

If you don't have your dataset hosted as a zip, upload each image individually through UploadConsumerBlob (POST /v2/consumer/blobs) and pass the returned blobs as trainingData.items. Every image goes through per-asset moderation on upload — which is why the blob endpoint accepts images but not zips.

http
POST https://orchestration.civitai.com/v2/consumer/blobs
Authorization: Bearer <your-token>
Content-Type: image/jpeg

< ./cat-01.jpg

The response's id is the blob key. Reference it in any of three equivalent forms — the bare key, the blob URL from the response, or the AIR urn urn:air:other:other:orchestrator:blob@<id>:

json
{
  "$type": "training",
  "input": {
    "engine": "ai-toolkit",
    "ecosystem": "sdxl",
    "steps": 2000,
    "trainingData": {
      "type": "blobs",
      "items": [
        { "air": "urn:air:other:other:orchestrator:blob@<id1>" },
        { "air": "<id2>", "caption": "catz sitting on a windowsill, looking at viewer" },
        { "air": "<id3>", "caption": "catz curled up asleep on a red blanket" }
      ]
    }
  }
}

Rules and behavior:

  • items are objects with a required air and an optional caption (max 1024 characters). Captions serve the same role as the .txt sidecar files in a training zip; items without one fall back to the trigger word / default caption.
  • At most 1000 items, each referenced blob at most once. Any media type the blob endpoint accepts works (jpg, png, webp, mp4, webm, mp3, wav) — use images for the SD family, videos for video ecosystems like WAN/LTX, audio for ACE-Step.
  • There is no count field — the item count is items.length.
  • Uploaded blobs are retained for ~30 days (each re-upload or training submission refreshes the clock), so upload and train within the same session or at least the same month.
  • Item order matters for training-run dedup: resubmitting the same set in a different order is treated as a new training run.

Continue training / train further ​

To resume from a LoRA you already trained instead of starting from the base checkpoint, set continueFrom to that LoRA's AIR. The new run starts from those weights and the new epochs build on top:

json
{
  "$type": "training",
  "input": {
    "engine": "ai-toolkit",
    "ecosystem": "sdxl",
    "continueFrom": "urn:air:sdxl:lora:civitai:<id>@<version>",
    "steps": 1000
  }
}

continueFrom must point at a LoRA of the same ecosystem as the model being trained (an SDXL LoRA for ecosystem: "sdxl", an SD1 LoRA for ecosystem: "sd1"). A mismatched ecosystem is rejected.

Reading the result ​

Submitting with wait=0 returns immediately with status: processing. Poll GetWorkflow (or use a webhook — see Results & webhooks) until the step settles. The step reports an epochs[] array with one entry per epoch — the number of epochs is derived from steps — where model.available tells you whether that checkpoint has been saved yet. Each available entry contains the trained LoRA blob and any sample images:

json
{
  "status": "succeeded",
  "steps": [{
    "name": "0",
    "$type": "training",
    "status": "succeeded",
    "output": {
      "moderationStatus": "Approved",
      "epochs": [
        {
          "epochNumber": 1,
          "model": { "id": "blob_...", "url": "https://.../epoch_1.safetensors" },
          "samples": [
            { "id": "blob_...", "url": "https://.../sample_0.jpeg" },
            { "id": "blob_...", "url": "https://.../sample_1.jpeg" }
          ]
        },
        { "epochNumber": 2, "model": { "...": "..." }, "samples": [] }
      ]
    }
  }]
}

The blob URLs are signed and expire — refetch the workflow or call GetBlob for a fresh URL when downloading the trained LoRA.

moderationStatus reflects safety review of the dataset: Approved is the green-light case. Rejected means the run was halted because the dataset failed moderation.

Live progress & logs ​

Training runs for minutes to hours, so the output also lets you watch it while it runs:

epochs[].traceUrl — opt in with "trace": "events" (or "logs") on the input and each epoch gets a live, tail-able stream. epochs[] always lists every epoch of the run: a pending or training epoch has model.available: false and no samples yet, and its traceUrl is where to watch it (epochs served from an existing checkpoint have no trace):

json
"epochs": [
  { "epochNumber": 1, "model": { "id": "…", "available": true, "url": "https://…" }, "samples": [ … ],
    "traceUrl": "https://orchestration.civitai.com/v2/consumer/streaming-blobs/training-...-epoch-1-trace.ndjson" },
  { "epochNumber": 2, "model": { "id": "…", "available": false }, "samples": [],
    "traceUrl": "https://orchestration.civitai.com/v2/consumer/streaming-blobs/training-...-epoch-2-trace.ndjson" }
]

Only treat an epoch as downloadable when model.available is true. GET a trace with a streaming client (curl -N, fetch + ReadableStream); it stays open while that epoch trains and ends when its job does, so follow the run by moving to the next epoch's trace as each one closes. The two modes differ only in what is written:

  • "logs" — plain text (text/plain): one line per line of the trainer's console output, nothing else. The same shape as customComfy's trace: "logs".
  • "events" — NDJSON (application/x-ndjson): each line is one JSON object with a unix-millisecond t, a type and the epoch:
typeFieldsNotes
attemptjobId, claimIdFirst line of every upload; a second one means the job was retried on a new worker
phasephaseWhat the worker is doing: loading_base_model, creating_session, copying_previous_epoch, running, uploading, …
logmessageOne line of the trainer's console output (progress-bar redraws are filtered out)
stepstep, maxSteps, epochStepsRemaining, secondsPerStepSampled about every 2 s, so consecutive lines may skip steps; secondsPerStep is the average since training started and null until a few steps are in
epochcheckpointPathThe checkpoint for this epoch was written; the upload follows
errormessageThe job failed — the reason, since you'd otherwise only see status: failed

To render an ETA from the stream: nextEpochIn ≈ epochStepsRemaining × secondsPerStep and remaining ≈ (maxSteps − step) × secondsPerStep.

A few things to expect: a trace URL answers 404 until its worker has written the first line (retry with backoff rather than treating it as final); if the worker falls behind on upload it drops the oldest queued lines, so resync on the next newline instead of assuming a gap-free sequence; a few log lines can go missing if the worker has to re-attach to the trainer's log stream; and a job retried on another worker continues into the same stream after a fresh attempt line.

Runtime ​

Training is highly variable and depends on steps. Always use wait=0.

EcosystemPer-step wall time (15 imgs, default settings)Typical full run
sdxl~0.3–0.5 s5–15 min for 2000 steps
sd1~0.1–0.2 s3–10 min for 4000 steps (SD1 is much faster per step)

Queue time on busy days can dominate — use the workflow's events[] to see when the step actually started.

Cost ​

Training is billed per step plus a flat per-epoch storage surcharge, with a price floor:

price = steps × costPerStep + epochs × 10        (rounded)
costPerStep = 0.20   (sdxl and sd1)
floor: never less than 80% of the default-configuration price

epochs is the number of saved checkpoints delivered (default 10, range 1–20); each adds 10 Buzz of storage. The default run is 2000 steps / 10 epochs, which lands the price on the familiar number:

2000 × 0.20 + 10 × 10 = 400 + 100 = 500 Buzz

The floor is 80% of the default price (400 Buzz for both SDXL and SD1) — lowering steps or epochs can save at most 20%.

Sample-prompt rendering is billed separately at standard SDXL / SD1 image-generation rates. Use whatif=true (the default for the Preview cost button on the <RecipeRun> widgets above) to see the exact pre-flight charge before committing.

ConfigurationBuzz
SDXL / SD1, default (steps: 2000, epochs: 10)500 + samples
SDXL / SD1, steps: 1000, epochs: 10400 + samples (floor)
SDXL / SD1, steps: 2000, epochs: 20600 + samples

Troubleshooting ​

SymptomLikely causeFix
400 with "steps out of range"steps outside 1–10000The hard cap is 10000. For longer SD1 runs (rare), submit multiple training workflows and chain them.
400 with "model not found"model URN points at a checkpoint that isn't the right ecosystem (e.g. an SD1 model on ecosystem: "sdxl")Use a urn:air:sdxl:checkpoint:... AIR for SDXL; urn:air:sd1:checkpoint:... for SD1.
400 with "uses a resource which could not be resolved"The zip behind sourceUrl is gone, private, or the signed URL expiredRe-upload it, or point sourceUrl at an AIR pinned to an immutable revision.
400 with "count mismatch"trainingData.count doesn't match the actual image count in the zipInspect the zip contents and update count.
Step failed, output moderationStatus: "Rejected"Dataset failed automated content moderationReplace flagged images and resubmit. Don't retry the same dataset.
Trained LoRA looks under-trainedToo few steps for the datasetRaise steps; or increase lr.
Trained LoRA overfits / memorizesToo many steps, dim too high, or alpha = dim with high lrLower steps, drop networkDim to 16–24, or set networkAlpha to half of networkDim.

Civitai Developer Documentation