Spaces:
Running
Running
github-actions[bot]
Deploy from GitHub 39b3777315c11d9c8bcd39ad7bf034f2a88a7379 (filtered: code + Dockerfile + README + NOTICES only)
2e175db | """ | |
| Generate synthetic images with Flux.1-schnell. | |
| Flux.1-schnell is released by Black Forest Labs under Apache 2.0. Outputs are | |
| not restricted, so we can use them as commercial training data. | |
| Usage | |
| ----- | |
| python scripts/dataset/generate_flux_synthetic.py \ | |
| --out data/raw/ai_generated \ | |
| --count 50000 \ | |
| --prompts scripts/dataset/prompts.txt | |
| Prompts file format | |
| ------------------- | |
| One complete prompt per line. Blank lines and lines starting with `#` are | |
| ignored. Diversity is critical: a model trained on monotonous prompts learns | |
| to detect prompt style, not AI-generation artefacts. Aim for the prompts | |
| file to span the full distribution of subjects, settings, lighting, and | |
| camera styles a regular person might upload — including mundane and | |
| imperfect ones (snapshots, blurry shots, boring objects), not just | |
| gallery-worthy compositions. | |
| If `--prompts` is omitted, a tiny built-in fallback set is used. That set | |
| is only sufficient for smoke tests, not for a real training run. | |
| Hardware | |
| -------- | |
| Flux.1-schnell needs a GPU with ~12-16 GB VRAM. Runs comfortably on: | |
| • RunPod / Lambda Labs / Vast.ai spot instances (RTX 3090 / 4090 / A10G) | |
| • ~3-5 seconds per image at 4 inference steps on an A10G | |
| • 50k images ≈ 60-80 GPU-hours ≈ $25-50 at typical spot rates | |
| Idempotency | |
| ----------- | |
| Each generated file is named by a hash of (prompt, seed) so re-runs skip | |
| already-generated images. Crash-resume works automatically. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import random | |
| from pathlib import Path | |
| from generation_utils import ( | |
| APPROVED_GENERATORS, | |
| choose_prompt, | |
| image_dimensions, | |
| load_prompts, | |
| manifest_row, | |
| next_seed, | |
| sha256_file, | |
| stable_image_key, | |
| ) | |
| # Fallback used only when --prompts is not provided. Intentionally tiny — | |
| # enough for a smoke test, not for a real training run. | |
| FALLBACK_PROMPTS = [ | |
| "a photo of a dog in a park, golden hour", | |
| "candid photograph of a person walking on a busy street", | |
| "studio lighting portrait of two friends talking", | |
| "a wide-angle shot of a kitchen with a child playing", | |
| ] | |
| FLUX_SPEC = APPROVED_GENERATORS["flux.1-schnell"] | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--out", type=Path, required=True, help="Output directory") | |
| parser.add_argument("--count", type=int, default=50_000) | |
| parser.add_argument( | |
| "--prompts", | |
| type=Path, | |
| default=None, | |
| help="Optional file with one prompt template per line", | |
| ) | |
| parser.add_argument("--seed", type=int, default=0) | |
| parser.add_argument("--steps", type=int, default=4, help="Flux schnell uses 4 steps") | |
| args = parser.parse_args() | |
| args.out.mkdir(parents=True, exist_ok=True) | |
| manifest_path = args.out.parent / "ai_generated_manifest.csv" | |
| if args.prompts is not None: | |
| prompts = load_prompts(args.prompts) | |
| print(f"Loaded {len(prompts)} prompts from {args.prompts}") | |
| else: | |
| prompts = load_prompts(None, fallback_prompts=FALLBACK_PROMPTS) | |
| print( | |
| f"WARNING: --prompts not given; using {len(prompts)} built-in " | |
| "fallback prompts (smoke-test only, not enough diversity for " | |
| "a real training run)" | |
| ) | |
| rng = random.Random(args.seed) | |
| # Lazy import — diffusers + torch + accelerate are heavy. | |
| print("Loading Flux.1-schnell pipeline (~24 GB download on first run)...") | |
| import torch | |
| from diffusers import FluxPipeline | |
| pipe = FluxPipeline.from_pretrained( | |
| "black-forest-labs/FLUX.1-schnell", | |
| torch_dtype=torch.bfloat16, | |
| ) | |
| pipe.to("cuda") | |
| rows: list[dict] = [] | |
| for i in range(args.count): | |
| prompt = choose_prompt(rng, prompts) | |
| seed = next_seed(rng) | |
| # Filename is content-addressable so re-runs are idempotent. | |
| key = stable_image_key(prompt, seed) | |
| dst = args.out / f"{key}.png" | |
| if not dst.exists(): | |
| generator = torch.Generator("cuda").manual_seed(seed) | |
| image = pipe( | |
| prompt, | |
| guidance_scale=0.0, # Flux schnell ignores guidance | |
| num_inference_steps=args.steps, | |
| generator=generator, | |
| ).images[0] | |
| image.save(dst, format="PNG") | |
| width, height = image_dimensions(dst) | |
| rows.append(manifest_row( | |
| # as_posix() — keep manifests portable across OS boundaries. | |
| path=dst.relative_to(args.out.parent.parent).as_posix(), | |
| cls="ai_generated", | |
| spec=FLUX_SPEC, | |
| sha256=sha256_file(dst), | |
| prompt=prompt, | |
| seed=seed, | |
| width=width, | |
| height=height, | |
| generation_params={ | |
| "steps": args.steps, | |
| "guidance_scale": 0.0, | |
| "pipeline": FLUX_SPEC.pipeline, | |
| }, | |
| )) | |
| if (i + 1) % 100 == 0: | |
| print(f" generated {i + 1}/{args.count}") | |
| with manifest_path.open("w", newline="") as fh: | |
| writer = csv.DictWriter( | |
| fh, | |
| fieldnames=[ | |
| "path", "class", "source", "license", "license_url", | |
| "sha256", "generator", "model_family", "model_id", "prompt", | |
| "seed", "width", "height", "generation_params_json", | |
| ], | |
| ) | |
| writer.writeheader() | |
| writer.writerows(rows) | |
| print(f"Done. {len(rows)} images. Manifest fragment: {manifest_path}") | |
| if __name__ == "__main__": | |
| main() | |