unslothai/unsloth · error · ValueError

No captioned images found. Provide a metadata.jsonl / captio

Error message

No captioned images found. Provide a metadata.jsonl / captions.jsonl, per-image .txt captions, or an instance prompt.

What it means

After scanning metadata.jsonl/captions.jsonl, per-image .txt sidecars, and the dreambooth instance_prompt fallback, zero image/caption pairs were produced. Every image lacked a caption from every source, or the directory had no recognized image files at all, so the trainer refuses to start with an empty dataset.

Source

Thrown at studio/backend/core/training/diffusion_train_common.py:1451

        # 3. dreambooth instance prompt for any image still without a caption.
        if not caption and instance_prompt:
            caption = instance_prompt
        if caption:
            if verify_images:
                # Reject a corrupt/truncated image now via a cheap PIL header probe: otherwise it passes filename-only discovery, the start route frees the GPU models, and the trainer crashes in Image.open.
                try:
                    from PIL import Image
                    with Image.open(img) as _probe:
                        _probe.verify()
                except Exception as e:  # noqa: BLE001 -- corrupt/zero-byte/truncated file
                    raise ValueError(
                        f"Image cannot be decoded: {img.name} ({e}). Remove or replace the "
                        f"corrupt or zero-byte file before training."
                    ) from e
            pairs.append((str(img), caption))

    if not pairs:
        raise ValueError(
            "No captioned images found. Provide a metadata.jsonl / captions.jsonl, per-image "
            ".txt captions, or an instance prompt."
        )
    return pairs


# Families whose trainer has no checkpoint/resume support yet. The shared DiffusionLoraConfig
# carries save_steps / resume_from_checkpoint for every family, so a loop that implements
# neither has to say so rather than ignore them.
CHECKPOINTLESS_FAMILIES: frozenset[str] = frozenset({"minimax-h3"})

# Families whose forward covers ONE packed sequence, so the batch axis is a pure replication
# axis and a second clip cannot join it: the layout, the rotary grid and the row timesteps are
# set by that clip's geometry and its caption's length. Kept beside the refusal it explains.
SINGLE_SEQUENCE_FAMILIES: frozenset[str] = frozenset({"minimax-h3"})

# Families whose trainer loads its base through ``ModularPipeline.from_pretrained``. Their local
# layout is ``modular_model_index.json`` and no ``model_index.json``, so the conventional shape

View on GitHub (pinned to 203007d190)

Solutions

  1. Add a metadata.jsonl (or captions.jsonl) with {"file_name": "img.png", "prompt": "..."} records.
  2. Or add a per-image caption sidecar: img.png + img.txt in the same directory.
  3. Or set an instance_prompt on the run so every uncaptioned image falls back to it.
  4. Verify image extensions are within the recognized set and metadata file_name values exactly match the image filenames.

Example fix

# before: data_dir has only images
# after: add captions.jsonl in data_dir
{"file_name": "img_0001.png", "prompt": "a photo of sks dog"}
{"file_name": "img_0002.png", "prompt": "a photo of sks dog at the beach"}
Defensive patterns

Strategy: validation

Validate before calling

IMAGE_EXTS = {'.png', '.jpg', '.jpeg', '.webp', '.bmp'}
def dataset_is_captionable(root: Path, instance_prompt=None) -> bool:
    imgs = [p for p in root.iterdir() if p.is_file() and p.suffix.lower() in IMAGE_EXTS]
    if not imgs:
        return False
    has_meta = (root / 'metadata.jsonl').is_file() or (root / 'captions.jsonl').is_file()
    has_sidecars = any(p.with_suffix('.txt').is_file() for p in imgs)
    return bool(has_meta or has_sidecars or instance_prompt)

Try / catch

try:
    pairs = build_caption_pairs(...)
except ValueError as e:
    if 'No captioned images' in str(e):
        prompt_user_for_captions_or_instance_prompt()

Prevention

When it happens

Trigger: A data_dir with images but no metadata.jsonl, no sidecar .txt files, and no instance_prompt; or a data_dir whose files use an extension outside _IMAGE_EXTS; or a metadata.jsonl whose keys don't match the images (bad-upload records are skipped, not fatal).

Common situations: User uploads bare images expecting automatic captioning; sidecar files named image-caption.txt instead of image.txt; metadata.jsonl file_name fields with wrong paths/extensions; forgetting to set instance_prompt for dreambooth-style datasets.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/55cec5ea07630d03. Report an issue: GitHub.