Instructions to use kingjones777/Ming-Image-0.1-Design-ROCm-INT8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use kingjones777/Ming-Image-0.1-Design-ROCm-INT8 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("kingjones777/Ming-Image-0.1-Design-ROCm-INT8", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
| #!/usr/bin/env python3 | |
| """Unified Hugging Face inference entry point for Ming image checkpoints.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| import sys | |
| from pathlib import Path | |
| from typing import Iterable, List | |
| from inference_profile import ( | |
| VALID_TASKS, | |
| load_checkpoint_capabilities, | |
| resolve_model_directory, | |
| ) | |
| from mllm_device_map import ( | |
| build_mllm_device_plan, | |
| load_mllm_num_hidden_layers, | |
| validate_loaded_layer_devices, | |
| ) | |
| CODE_DIRECTORY = Path(__file__).resolve().parent | |
| TASK_RESOLUTION_BUCKETS = { | |
| "text-to-image": (1024, 2048), | |
| "image-edit": (1024,), | |
| "layer-decompose": (512, 1024), | |
| } | |
| TASK_DEFAULT_RESOLUTIONS = { | |
| "text-to-image": 2048, | |
| "image-edit": 1024, | |
| "layer-decompose": 1024, | |
| } | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser( | |
| description=( | |
| "Run text-to-image, image editing, or layer decomposition with an " | |
| "explicit checkpoint capability profile." | |
| ) | |
| ) | |
| parser.add_argument("--model", required=True, help="Local model directory or HF Hub repo ID") | |
| parser.add_argument("--task", required=True, choices=sorted(VALID_TASKS)) | |
| parser.add_argument("--prompt", help="Generation/edit prompt; optional for layer decomposition") | |
| parser.add_argument("--input-image", type=Path, help="Required for edit and layer decomposition") | |
| parser.add_argument("--num-layers", type=int, default=1) | |
| parser.add_argument("--output-dir", type=Path, default=Path("outputs")) | |
| parser.add_argument( | |
| "--resolution", | |
| type=int, | |
| help=( | |
| "Requested resolution bucket. Defaults: text-to-image 2048, " | |
| "image-edit 1024, layer-decompose 1024. Requests snap to the " | |
| "nearest bucket supported by the selected task." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "--steps", | |
| type=int, | |
| help="Override the checkpoint-family default (generation: 12, layers: 12)", | |
| ) | |
| parser.add_argument("--seed", type=int, default=42) | |
| parser.add_argument( | |
| "--cfg", | |
| type=float, | |
| help="Override the checkpoint-family default (generation: 1.0, layers: 2.0)", | |
| ) | |
| parser.add_argument("--dtype", choices=("bfloat16", "float16", "float32"), default="bfloat16") | |
| parser.add_argument( | |
| "--attn-implementation", | |
| choices=("sdpa", "flash_attention_2", "eager"), | |
| # The BailingMoeV2 LLM only implements eager and flash_attention_2 | |
| # attention classes; selecting "sdpa" fails closed at load time. | |
| default="eager", | |
| ) | |
| parser.add_argument("--device", default="cuda:0") | |
| parser.add_argument( | |
| "--device-map", | |
| choices=("balanced", "none", "auto"), | |
| default="balanced", | |
| help="balanced reserves GPU 0 for fixed image modules and shards the MLLM", | |
| ) | |
| parser.add_argument( | |
| "--num-gpus", | |
| type=int, | |
| default=1, | |
| help="required visible GPU count for balanced placement (any positive integer)", | |
| ) | |
| parser.add_argument( | |
| "--processor", | |
| help="Optional processor data directory; defaults to <checkpoint>/mllm", | |
| ) | |
| parser.add_argument("--revision", help="HF Hub model revision") | |
| parser.add_argument("--cache-dir", type=Path) | |
| parser.add_argument("--local-files-only", action="store_true") | |
| parser.add_argument( | |
| "--validate-only", | |
| action="store_true", | |
| help="Validate model profile and task arguments without loading weights", | |
| ) | |
| parser.add_argument( | |
| "--attention-bf16-reduction", | |
| action="store_true", | |
| help=( | |
| "Let PyTorch's math attention kernel (the only SDPA kernel that runs on ROCm gfx1151) " | |
| "stay in bf16 instead of upcasting to fp32: faster, less precise." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "--release-mllm-after-conditioning", | |
| action="store_true", | |
| help=( | |
| "Free the MLLM, vision tower and connector as soon as the conditioning is computed, " | |
| "before the diffusion steps. Lowers peak memory; one image per process." | |
| ), | |
| ) | |
| return parser.parse_args() | |
| def parse_num_layers(text: str) -> int: | |
| decompose_match = re.search( | |
| r"decompose this image into\s+(\d+)\s+layers?", text.lower() | |
| ) | |
| if decompose_match: | |
| return int(decompose_match.group(1)) | |
| for line in text.splitlines(): | |
| s = line.strip().lower() | |
| if s.startswith("number of layers:"): | |
| try: | |
| return int(s.split(":", 1)[1].strip()) | |
| except ValueError: | |
| pass | |
| return 5 | |
| def resolve_task_resolution(task: str, requested: int | None) -> int: | |
| """Resolve a user request to the nearest supported task-level bucket.""" | |
| try: | |
| buckets = TASK_RESOLUTION_BUCKETS[task] | |
| except KeyError as exc: | |
| raise ValueError(f"unsupported task for resolution policy: {task!r}") from exc | |
| if requested is None: | |
| return TASK_DEFAULT_RESOLUTIONS[task] | |
| if isinstance(requested, bool) or not isinstance(requested, int) or requested <= 0: | |
| raise ValueError("--resolution must be a positive integer") | |
| return min(buckets, key=lambda value: (abs(value - requested), value)) | |
| def _load_prompt(prompt: str) -> str: | |
| prompt_path = Path(prompt) | |
| try: | |
| is_file = prompt_path.is_file() | |
| except OSError: | |
| # Long literal prompts can be invalid filesystem paths. In that case, | |
| # keep treating the argument as prompt text. | |
| return prompt | |
| if is_file: | |
| return prompt_path.read_text(encoding="utf-8") | |
| return prompt | |
| def _dtype(name: str): | |
| import torch | |
| return { | |
| "bfloat16": torch.bfloat16, | |
| "float16": torch.float16, | |
| "float32": torch.float32, | |
| }[name] | |
| def _build_messages(task: str, prompt: str, input_image: Path | None): | |
| content = [] | |
| if input_image is not None: | |
| content.append({"type": "image", "image": str(input_image)}) | |
| content.append({"type": "text", "text": prompt}) | |
| return [{"role": "HUMAN", "content": content}] | |
| def _normalize_outputs(output) -> List[Image.Image]: | |
| from PIL import Image | |
| if isinstance(output, Image.Image): | |
| return [output] | |
| if isinstance(output, (list, tuple)) and all( | |
| isinstance(item, Image.Image) for item in output | |
| ): | |
| return list(output) | |
| raise TypeError(f"model returned unsupported output type: {type(output)!r}") | |
| def _model_input_device(model): | |
| try: | |
| return model.device | |
| except AttributeError: | |
| return next(parameter.device for parameter in model.parameters() if not parameter.is_meta) | |
| def _validate_balanced_placement(model, plan, torch) -> None: | |
| layer_devices = [] | |
| for index, layer in enumerate(model.model.model.layers): | |
| devices = {parameter.device for parameter in layer.parameters()} | |
| if len(devices) != 1: | |
| raise RuntimeError(f"MLLM layer {index} spans devices {sorted(map(str, devices))}") | |
| device = devices.pop() | |
| if device.type != "cuda" or device.index is None: | |
| raise RuntimeError(f"MLLM layer {index} loaded on {device}, expected CUDA") | |
| layer_devices.append(device.index) | |
| validate_loaded_layer_devices(layer_devices, plan) | |
| fixed_modules = { | |
| "vision": model.vision, | |
| "linear_proj": model.linear_proj, | |
| "connector": getattr(model, "connector", None), | |
| "proj_in": getattr(model, "proj_in", None), | |
| "proj_out": getattr(model, "proj_out", None), | |
| "proj_directvlm": getattr(model, "proj_directvlm", None), | |
| "query_tokens": getattr(model, "query_tokens_dict", None), | |
| "diffusion": getattr(model, "diffusion_loss", None), | |
| } | |
| expected = {torch.device("cuda:0")} | |
| for name, module in fixed_modules.items(): | |
| if module is None: | |
| continue | |
| devices = {parameter.device for parameter in module.parameters()} | |
| if devices and devices != expected: | |
| raise RuntimeError( | |
| f"fixed module {name} loaded on {sorted(map(str, devices))}, expected cuda:0" | |
| ) | |
| def _save_outputs( | |
| images: Iterable, output_dir: Path, task: str | |
| ) -> List[Path]: | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| prefix = "layer" if task == "layer-decompose" else "image" | |
| skip_first = task == "layer-decompose" | |
| paths = [] | |
| for index, image in enumerate(images): | |
| if skip_first and index == 0: | |
| continue | |
| output_path = output_dir / f"{prefix}_{index:02d}.png" | |
| image.save(output_path) | |
| paths.append(output_path) | |
| return paths | |
| def main() -> None: | |
| args = parse_args() | |
| model_directory = resolve_model_directory( | |
| args.model, | |
| revision=args.revision, | |
| cache_dir=args.cache_dir, | |
| local_files_only=args.local_files_only, | |
| ) | |
| profile = load_checkpoint_capabilities(model_directory) | |
| has_reference_image = args.input_image is not None | |
| profile.validate_task( | |
| args.task, | |
| has_reference_image=has_reference_image, | |
| num_layers=args.num_layers, | |
| ) | |
| effective_resolution = resolve_task_resolution(args.task, args.resolution) | |
| if args.resolution is not None and args.resolution != effective_resolution: | |
| print( | |
| f"resolution {args.resolution} snapped to {effective_resolution} " | |
| f"for task {args.task}", | |
| file=sys.stderr, | |
| ) | |
| sampling = profile.resolve_sampling_parameters(steps=args.steps, cfg=args.cfg) | |
| if args.input_image is not None and not args.input_image.is_file(): | |
| raise FileNotFoundError(f"input image does not exist: {args.input_image}") | |
| if args.task != "layer-decompose" and not args.prompt: | |
| raise ValueError(f"--prompt is required for {args.task}") | |
| if args.prompt is not None: | |
| prompt = _load_prompt(args.prompt) | |
| else: | |
| prompt = f"Decompose this image into {args.num_layers} layers." | |
| num_layers = parse_num_layers(prompt) if args.task == "layer-decompose" else args.num_layers | |
| if args.validate_only: | |
| print( | |
| json.dumps( | |
| { | |
| "model": str(model_directory), | |
| "task": args.task, | |
| "profile": profile.__dict__, | |
| "sampling": sampling.__dict__, | |
| "resolution": { | |
| "requested": args.resolution, | |
| "effective": effective_resolution, | |
| }, | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| return | |
| model, processor = load_model_and_processor(model_directory, args) | |
| images = run_generation( | |
| model, | |
| processor, | |
| profile, | |
| task=args.task, | |
| prompt=prompt, | |
| input_image=args.input_image, | |
| resolution=effective_resolution, | |
| sampling=sampling, | |
| seed=args.seed, | |
| num_layers=num_layers, | |
| dtype=_dtype(args.dtype), | |
| ) | |
| output_paths = _save_outputs(images, args.output_dir, args.task) | |
| print(json.dumps({"outputs": [str(path.resolve()) for path in output_paths]}, indent=2)) | |
| def load_model_and_processor(model_directory: Path, args): | |
| """Load the model and processor once; shared by the CLI and batch tools.""" | |
| import torch | |
| from modeling_bailingmm2 import BailingMM2NativeForConditionalGeneration | |
| from processing_bailingmm2 import load_bailingmm2_processor | |
| # Processor/tokenizer data lives in the package's mllm/ component; the | |
| # Python implementations stay in this repository (AutoProcessor would | |
| # require them inside the data directory). --processor overrides the data | |
| # directory only. | |
| processor_directory = ( | |
| Path(args.processor).expanduser().resolve() | |
| if args.processor | |
| else model_directory / "mllm" | |
| ) | |
| processor = load_bailingmm2_processor(processor_directory) | |
| if getattr(args, "attention_bf16_reduction", False): | |
| # The math SDPA kernel upcasts bf16 inputs to fp32 by default; this keeps it in bf16. | |
| torch.backends.cuda.allow_fp16_bf16_reduction_math_sdp(True) | |
| dtype = _dtype(args.dtype) | |
| load_kwargs = { | |
| "torch_dtype": dtype, | |
| "attn_implementation": args.attn_implementation, | |
| "load_image_gen": True, | |
| "image_gen_device": args.device, | |
| } | |
| device_plan = None | |
| if args.device_map == "balanced": | |
| visible_gpus = torch.cuda.device_count() | |
| if visible_gpus != args.num_gpus: | |
| raise RuntimeError( | |
| f"balanced placement requires {args.num_gpus} visible GPUs, got {visible_gpus}; " | |
| "set CUDA_VISIBLE_DEVICES before starting Python" | |
| ) | |
| num_hidden_layers = load_mllm_num_hidden_layers(model_directory / "mllm") | |
| device_plan = build_mllm_device_plan(num_hidden_layers, visible_gpus) | |
| load_kwargs["device_map"] = device_plan.device_map | |
| print( | |
| f"MLLM device plan: {visible_gpus} GPUs, layer_counts={device_plan.layer_counts}" | |
| ) | |
| elif args.device_map == "auto": | |
| load_kwargs["device_map"] = "auto" | |
| model = BailingMM2NativeForConditionalGeneration.from_pretrained( | |
| str(model_directory), **load_kwargs | |
| ) | |
| if args.device_map == "none": | |
| model = model.to(device=args.device, dtype=dtype) | |
| elif device_plan is not None: | |
| _validate_balanced_placement(model, device_plan, torch) | |
| if getattr(args, "release_mllm_after_conditioning", False): | |
| _release_mllm_before_sampling(model) | |
| return model, processor | |
| def _release_mllm_before_sampling(model) -> None: | |
| """Free the MLLM-side modules once the conditioning exists (--release-mllm-after-conditioning). | |
| Wraps the diffusion sampler: by the time it is called the conditioning tensors are computed, | |
| so the language model, vision tower and connector are moved to the meta device (releasing | |
| their memory) before the diffusion steps start. The model cannot generate again afterwards. | |
| """ | |
| import gc | |
| import torch | |
| original_sample = model.diffusion_loss.sample | |
| def sample_after_release(*args, **kwargs): | |
| for name in ("model", "vision", "linear_proj", "connector"): | |
| module = getattr(model, name, None) | |
| if module is not None: | |
| module.to("meta") | |
| gc.collect() | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| return original_sample(*args, **kwargs) | |
| model.diffusion_loss.sample = sample_after_release | |
| def run_generation( | |
| model, | |
| processor, | |
| profile, | |
| *, | |
| task: str, | |
| prompt: str, | |
| input_image, | |
| resolution: int, | |
| sampling, | |
| seed: int, | |
| num_layers: int, | |
| dtype, | |
| ) -> List: | |
| """Run one inference with an already-loaded model and processor.""" | |
| import torch | |
| from PIL import Image | |
| resolution = resolve_task_resolution(task, resolution) | |
| messages = _build_messages(task, prompt, input_image) | |
| text = processor.apply_chat_template(messages, add_generation_prompt=True) | |
| image_inputs, video_inputs, _ = processor.process_vision_info(messages) | |
| reference_image = None | |
| if input_image is not None: | |
| reference_mode = "RGB" if profile.vae_input_channels == 3 else "RGBA" | |
| reference_image = Image.open(input_image).convert(reference_mode) | |
| inputs = processor( | |
| text=[text], | |
| images=image_inputs, | |
| videos=video_inputs, | |
| return_tensors="pt", | |
| image_gen_highres=resolution, | |
| image_gen_ref_images=reference_image, | |
| image_gen_input_channels=profile.vae_input_channels, | |
| ) | |
| input_device = _model_input_device(model) | |
| inputs = inputs.to(input_device) | |
| for key, value in inputs.items(): | |
| if isinstance(value, torch.Tensor) and torch.is_floating_point(value): | |
| inputs[key] = value.to(dtype=dtype) | |
| output = model.generate( | |
| **inputs, | |
| image_gen=True, | |
| image_gen_task=task, | |
| image_gen_seed=seed, | |
| image_gen_steps=sampling.steps, | |
| image_gen_cfg=sampling.cfg, | |
| num_frames_per_prompt=num_layers+1 if task == "layer-decompose" else num_layers, | |
| ) | |
| images = _normalize_outputs(output) | |
| expected_outputs = num_layers + 1 if task == "layer-decompose" else 1 | |
| if len(images) != expected_outputs: | |
| raise RuntimeError( | |
| f"expected {expected_outputs} output image(s), got {len(images)}" | |
| ) | |
| return images | |
| if __name__ == "__main__": | |
| main() | |