File size: 7,181 Bytes
d58fd10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env python
"""MageFlow command-line inference.

Two console scripts share this module:

  * ``mage-flow``      — text-to-image generation
  * ``mage-flow-edit`` — instruction-based image editing

Both are BATCHED: pass several prompts and they are packed into a single
transformer forward per denoise step. Sample ``i`` uses seed ``--seed + i``.

Text-to-image (multiple prompts = a batch)::

    mage-flow --prompt "a cat holding a sign that says hello" "a red ferrari" \
        --model_path microsoft/Mage-Flow-4B --steps 30 --cfg 5.0 --out ./outputs

``--model_path`` accepts a local repo dir OR a Hugging Face Hub repo id (e.g.
``microsoft/Mage-Flow-4B``), downloaded and cached automatically on first use.

Mixed resolutions — give one ``--height``/``--width`` per prompt (they are packed
into a single forward per step regardless of shape)::

    mage-flow --prompt "a tall waterfall" "a wide desert panorama" \
        --height 2048 512 --width 512 2048 \
        --model_path microsoft/Mage-Flow-4B --out ./outputs

Image editing (one ``--ref`` entry per prompt; comma-separate paths for
multi-image edit)::

    mage-flow-edit \
        --prompt "把背景改为城市街道" "把这两张图融合在一起" \
        --ref hydrant.png "scene.png,object.png" \
        --model_path /path/Mage-Flow-Edit-4B-Base --out ./outputs
"""
import argparse
import os

from mage_flow import MageFlowPipeline


def _add_common_args(p):
    p.add_argument("--model_path", required=True,
                   help="local diffusers-style repo dir OR a Hugging Face Hub repo id "
                        "(e.g. microsoft/Mage-Flow-4B); HF ids are downloaded and cached "
                        "automatically on first use")
    p.add_argument("--neg_prompt", default=None,
                   help="negative prompt applied to every sample (default: a single space)")
    p.add_argument("--steps", type=int, default=30)
    p.add_argument("--cfg", type=float, default=5.0)
    p.add_argument("--seed", type=int, default=42, help="base seed; sample i uses seed + i")
    p.add_argument("--static_shift", type=float, default=None,
                   help="override scheduler shift (default: repo scheduler_config.json, 6.0)")
    p.add_argument("--device", default="cuda")
    p.add_argument("--out", default="./outputs")


def _neg_list(neg_prompt, n):
    return [neg_prompt] * n if neg_prompt is not None else None


def _size_list(vals, n, name, parser):
    """Broadcast one size value to all prompts, or use a per-prompt list."""
    if len(vals) == 1:
        return vals * n
    if len(vals) == n:
        return vals
    parser.error(f"--{name} expects 1 value (applied to all) or {n} values "
                 f"(one per prompt); got {len(vals)}")


def main():
    """``mage-flow`` — batched text-to-image generation."""
    p = argparse.ArgumentParser(
        prog="mage-flow", description="MageFlow text-to-image generation.",
        formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("--prompt", nargs="+", required=True,
                   help="one or more prompts; multiple prompts are batched")
    p.add_argument("--height", type=int, nargs="+", default=[1024],
                   help="output height (multiple of 16); one value applied to all "
                        "prompts, or one value per prompt for mixed resolutions")
    p.add_argument("--width", type=int, nargs="+", default=[1024],
                   help="output width (multiple of 16); one value applied to all "
                        "prompts, or one value per prompt for mixed resolutions")
    p.add_argument("--prompt_template", default="mage-flow")
    _add_common_args(p)
    args = p.parse_args()

    os.makedirs(args.out, exist_ok=True)
    pipe = MageFlowPipeline.from_pretrained(args.model_path, args.device)
    n = len(args.prompt)
    imgs = pipe.generate(
        args.prompt,
        neg_prompts=_neg_list(args.neg_prompt, n),
        seeds=[args.seed + i for i in range(n)],
        heights=_size_list(args.height, n, "height", p),
        widths=_size_list(args.width, n, "width", p),
        steps=args.steps, cfg=args.cfg, static_shift=args.static_shift,
        prompt_template=args.prompt_template,
    )
    for i, im in enumerate(imgs):
        path = os.path.join(args.out, f"gen_{i:03d}.png")
        im.save(path)
        print(f"saved {path}")


def main_edit():
    """``mage-flow-edit`` — batched instruction-based image editing."""
    p = argparse.ArgumentParser(
        prog="mage-flow-edit", description="MageFlow instruction-based image editing.",
        formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("--prompt", nargs="+", required=True,
                   help="one or more edit instructions; multiple prompts are batched")
    p.add_argument("--ref", nargs="+", required=True,
                   help="reference image(s) for each prompt, positionally aligned with "
                        "--prompt; comma-separate paths for multi-image edit "
                        "(e.g. --ref a.png 'b.png,c.png')")
    p.add_argument("--max_size", type=int, default=None,
                   help="longest side of the output (short side by aspect ratio). "
                        "Default: keep each source image's own resolution")
    p.add_argument("--height", type=int, default=None,
                   help="explicit output height (use with --width; overrides --max_size)")
    p.add_argument("--width", type=int, default=None,
                   help="explicit output width (use with --height; overrides --max_size)")
    p.add_argument("--vl_cond_long_edge", type=int, default=384,
                   help="cap the long edge of the reference image fed to the VL text "
                        "encoder (matches training preprocessing; the VAE path keeps the "
                        "full output resolution). 0 or negative disables the cap")
    p.add_argument("--prompt_template", default="mage-flow-edit")
    _add_common_args(p)
    args = p.parse_args()

    if len(args.ref) != len(args.prompt):
        p.error(f"--ref count ({len(args.ref)}) must match --prompt count ({len(args.prompt)})")

    os.makedirs(args.out, exist_ok=True)
    pipe = MageFlowPipeline.from_pretrained(args.model_path, args.device)
    n = len(args.prompt)
    # Each --ref token is one prompt's reference(s); commas split multi-image refs.
    ref_images = [[s.strip() for s in r.split(",") if s.strip()] for r in args.ref]

    size_kw = {}
    if args.height and args.width:
        size_kw = {"heights": [args.height] * n, "widths": [args.width] * n}

    outs = pipe.edit(
        args.prompt, ref_images,
        neg_prompts=_neg_list(args.neg_prompt, n),
        seeds=[args.seed + i for i in range(n)],
        max_size=args.max_size, steps=args.steps, cfg=args.cfg,
        static_shift=args.static_shift, prompt_template=args.prompt_template,
        vl_cond_long_edge=args.vl_cond_long_edge,
        **size_kw,
    )
    for i, im in enumerate(outs):
        path = os.path.join(args.out, f"edit_{i:03d}.png")
        im.save(path)
        print(f"saved {path}")


if __name__ == "__main__":
    main()