Text-to-Image
Diffusers
Safetensors
English
Chinese
QwenImage21Pipeline
sdnq
int4
uint4
image-generation
image-editing
apple-silicon
8-bit precision
Instructions to use ixim/Image21-INT4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use ixim/Image21-INT4 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("ixim/Image21-INT4", 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
File size: 7,953 Bytes
9116984 | 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 | """Compare paired BF16 and INT4 runs without ranking them."""
import argparse
import csv
import html
import json
import math
import os
import statistics
from pathlib import Path
import numpy as np
from PIL import Image
from scripts.integrity import sha256, validate_pair
from scripts.provenance import validate_roles
def load_records(root):
rows = [json.loads(line) for line in (Path(root) / 'records.jsonl').read_text(encoding='utf-8').splitlines() if line]
records = {}
for row in rows:
key = (row['case_id'], row['seed'])
if key in records:
raise ValueError(f'Duplicate record: {key}')
if sha256(Path(root) / row['image']) != row['image_sha256']:
raise ValueError(f'Image hash mismatch: {key}')
records[key] = row
if not records:
raise ValueError('Empty benchmark run')
return records
def compare_pixels(left, right):
with Image.open(left) as image_a, Image.open(right) as image_b:
if image_a.size != image_b.size:
raise ValueError('Image dimensions differ')
image_a, image_b = image_a.convert('RGBA'), image_b.convert('RGBA')
array_a = np.asarray(image_a, dtype=np.float32) / 255
array_b = np.asarray(image_b, dtype=np.float32) / 255
composite_a = array_a[..., :3] * array_a[..., 3:] + 1 - array_a[..., 3:]
composite_b = array_b[..., :3] * array_b[..., 3:] + 1 - array_b[..., 3:]
mse = float(np.mean((composite_a - composite_b) ** 2))
return {'rgb_mae_white': float(np.mean(np.abs(composite_a - composite_b))),
'rgb_psnr_white_db': None if mse == 0 else -10 * math.log10(mse),
'alpha_mae': float(np.mean(np.abs(array_a[..., 3] - array_b[..., 3])))}
def summary_from(rows, baseline_env, quantized_env, sample):
return {'pairs': len(rows), 'cases': len({row['case_id'] for row in rows}),
'bf16_mean_seconds': statistics.mean(row['bf16_seconds'] for row in rows),
'int4_mean_seconds': statistics.mean(row['int4_seconds'] for row in rows),
'bf16_max_allocated_gib': max(row['bf16_peak_allocated_gib'] for row in rows),
'int4_max_allocated_gib': max(row['int4_peak_allocated_gib'] for row in rows),
'width': sample['width'], 'height': sample['height'], 'steps': sample['steps'],
'warmup': baseline_env['warmup'],
'bf16_weight_bytes': sum(item['size'] for item in baseline_env['model_identity']['files']
if item['path'].endswith('.safetensors')),
'int4_weight_bytes': sum(item['size'] for item in quantized_env['model_identity']['files']
if item['path'].endswith('.safetensors'))}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--baseline', default='artifacts/eval/bf16')
parser.add_argument('--quantized', default='artifacts/eval/int4')
parser.add_argument('--output', default='artifacts/eval')
args = parser.parse_args()
base, quant, out = Path(args.baseline), Path(args.quantized), Path(args.output)
baseline, quantized = load_records(base), load_records(quant)
if baseline.keys() != quantized.keys():
raise ValueError('Benchmark cases or seeds do not match')
baseline_env = json.loads((base / 'environment.json').read_text(encoding='utf-8'))
quantized_env = json.loads((quant / 'environment.json').read_text(encoding='utf-8'))
validate_roles(baseline_env['model_identity'], quantized_env['model_identity'])
for key in ('benchmark_sha256', 'runtime_helper_sha256', 'device_helper_sha256'):
if baseline_env.get(key) != quantized_env.get(key):
raise ValueError(f'Benchmark implementation mismatch: {key}')
for key in ('gpu', 'cuda', 'packages', 'offload', 'warmup', 'generator_device', 'cases_sha256'):
if baseline_env[key] != quantized_env[key]:
raise ValueError(f'Runtime mismatch: {key}')
rows = []
for key in baseline:
left, right = baseline[key], quantized[key]
validate_pair(left, right)
rows.append({'case_id': key[0], 'seed': key[1],
'bf16_seconds': left['seconds'], 'int4_seconds': right['seconds'],
'bf16_peak_allocated_gib': left['peak_allocated_bytes'] / 2**30,
'int4_peak_allocated_gib': right['peak_allocated_bytes'] / 2**30,
**compare_pixels(base / left['image'], quant / right['image'])})
summary = summary_from(rows, baseline_env, quantized_env, next(iter(baseline.values())))
out.mkdir(parents=True, exist_ok=True)
(out / 'summary.json').write_text(json.dumps(summary, indent=2), encoding='utf-8')
with (out / 'comparison.csv').open('w', encoding='utf-8', newline='') as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
lines = ['# Informal release evaluation: BF16 / INT4', '',
'This community comparison is reference material. It is not an official evaluation.', '',
f'{len(rows)} paired outputs on {baseline_env["gpu"]}; {summary["width"]}×{summary["height"]}, '
f'{summary["steps"]} steps, offload={baseline_env["offload"]}, CFG=1, KV cache enabled.', '',
('Warmup excluded. ' if baseline_env['warmup'] else 'No warmup performed. ') +
'Pixel metrics measure drift. They are not a semantic quality score. '
'The suite is small and does not establish a ranking.', '',
'| Case | Seed | BF16 s | INT4 s | BF16 peak GiB | INT4 peak GiB | RGB MAE |',
'|---|---:|---:|---:|---:|---:|---:|']
gallery = ['<!doctype html><meta charset="utf-8"><title>BF16 / INT4</title>',
'<style>body{font:16px system-ui;margin:32px;max-width:1500px}'
'.pair{display:grid;grid-template-columns:1fr 1fr;gap:20px}img{width:100%;background:'
'repeating-conic-gradient(#ddd 0 25%,#fff 0 50%) 0/24px 24px}pre{white-space:pre-wrap}</style>',
'<h1>BF16 / INT4</h1><p>Left: BF16. Right: saved INT4. Same settings and seeds.</p>']
for row in rows:
lines.append(f'| {row["case_id"]} | {row["seed"]} | {row["bf16_seconds"]:.2f} | '
f'{row["int4_seconds"]:.2f} | {row["bf16_peak_allocated_gib"]:.2f} | '
f'{row["int4_peak_allocated_gib"]:.2f} | {row["rgb_mae_white"]:.4f} |')
key = (row['case_id'], row['seed'])
left = Path(os.path.relpath(base / baseline[key]['image'], out)).as_posix()
right = Path(os.path.relpath(quant / quantized[key]['image'], out)).as_posix()
gallery.append(f'<section><h2>{html.escape(row["case_id"])} seed {row["seed"]}</h2>'
f'<pre>{html.escape(baseline[key]["prompt"])}</pre><div class="pair">'
f'<a href="{html.escape(left, quote=True)}"><img src="{html.escape(left, quote=True)}" alt="BF16"></a>'
f'<a href="{html.escape(right, quote=True)}"><img src="{html.escape(right, quote=True)}" alt="INT4"></a></div></section>')
lines.extend(['', '## Summary', '',
f'Mean latency: BF16 {summary["bf16_mean_seconds"]:.2f}s; INT4 {summary["int4_mean_seconds"]:.2f}s.',
f'Maximum allocated CUDA memory: BF16 {summary["bf16_max_allocated_gib"]:.2f} GiB; '
f'INT4 {summary["int4_max_allocated_gib"]:.2f} GiB.', '',
'Raw records: comparison.csv, bf16/records.jsonl, int4/records.jsonl.',
'Visual notes belong in qualitative.md and are written after inspecting the images.', ''])
(out / 'report.md').write_text('\n'.join(lines) + '\n', encoding='utf-8')
(out / 'comparison.html').write_text('\n'.join(gallery), encoding='utf-8')
print(json.dumps(summary, indent=2))
if __name__ == '__main__':
main()
|