Text-to-Image
Diffusers
Safetensors
QwenImage21Pipeline
image-generation
image-editing
bitsandbytes
int8
qwen-image-2.1-turbo
8-bit precision
Instructions to use ixim/Image21-Turbo-INT8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use ixim/Image21-Turbo-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("ixim/Image21-Turbo-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
File size: 5,114 Bytes
a5e8975 | 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 | """Device-only offload workaround for the pinned bitsandbytes 0.50.2 stack.
Parent Module.to() recursively invokes _apply(), not each child's to().
Linear8bitLt.to() moves auxiliary tensors, but this path is bypassed by a
parent move. Keep CB aliases and SCB/outlier state colocated with the weight.
This is instance-local; checkpoint tensors and installed packages are unchanged.
"""
from types import MethodType
import inspect
from weakref import WeakSet
import torch
def check_sampling_support(config):
"""Fail before loading weights when an old runtime would ignore a saved grid."""
if config.get('sample_sigmas') is not None:
from diffusers import QwenImage21Pipeline
if 'sample_sigmas' not in inspect.signature(QwenImage21Pipeline.__init__).parameters:
raise RuntimeError('This checkpoint requires Diffusers sampling-grid support (PR #14950)')
def release_prefix_cache_before_decode(pipe):
"""Release finished denoising prefix tensors before the full-resolution VAE.
The pinned upstream pipeline holds its local KV containers through decode.
Their tensors are no longer read after the final transformer call. Clearing
them at decode entry preserves the denoising computation and latent input.
Weak references avoid retaining caches on latent-only calls or failures.
"""
if getattr(pipe, '_prefix_cache_release', False):
return
caches = WeakSet()
telemetry = {'calls': 0, 'tensor_bytes': 0}
def capture(_module, _args, kwargs):
cache = kwargs.get('kv_cache')
if cache is not None:
caches.add(cache)
handle = pipe.transformer.register_forward_pre_hook(capture, with_kwargs=True)
original = pipe.vae.decode
def decode(self, *args, **kwargs):
released = 0
for cache in list(caches):
for layer in cache.layer_caches:
released += sum(t.numel() * t.element_size() for t in (layer.k, layer.v)
if isinstance(t, torch.Tensor))
layer.k = None
layer.v = None
caches.clear()
telemetry.update(calls=telemetry['calls'] + 1, tensor_bytes=released)
return original(*args, **kwargs)
pipe.vae.decode = MethodType(decode, pipe.vae)
pipe._prefix_cache_release_hook = handle
pipe._prefix_cache_release = True
pipe._prefix_cache_telemetry = telemetry
def patch_int8_device_moves(model):
from bitsandbytes.nn import Linear8bitLt
for layer in model.modules():
if not isinstance(layer, Linear8bitLt) or getattr(layer, '_int8_move_patched', False):
continue
if layer.state.has_fp16_weights:
raise ValueError('Offload helper only supports inference INT8 weights')
original = layer._apply
def apply(self, fn, recurse=True, _original=original):
result = _original(fn, recurse=recurse)
device = self.weight.device
# Parameter auxiliaries are used before init_8bit_state(); state
# auxiliaries are used afterwards. Never duplicate the CB storage.
if self.weight.CB is not None:
self.weight.CB = self.weight.data
if self.weight.SCB is not None:
self.weight.SCB = self.weight.SCB.to(device)
for name, value in vars(self.state).items():
if isinstance(value, torch.Tensor):
setattr(self.state, name, self.weight.data if name == 'CB' else value.to(device))
return result
layer._apply = MethodType(apply, layer)
layer._int8_move_patched = True
def enable_int8_cpu_offload(pipe):
"""Install auxiliary-tensor movement before standard model CPU offload."""
for name in ('text_encoder', 'transformer'):
patch_int8_device_moves(getattr(pipe, name))
pipe.enable_model_cpu_offload()
def load_int8_pipeline(model, local_files_only=False):
"""Load/offload components sequentially to avoid a combined CUDA load peak."""
from diffusers import QwenImage21Pipeline, QwenImage21Transformer2DModel
from transformers import Qwen3VLForConditionalGeneration
check_sampling_support(QwenImage21Pipeline.load_config(model, local_files_only=local_files_only))
components = {}
for name, cls in [('transformer', QwenImage21Transformer2DModel),
('text_encoder', Qwen3VLForConditionalGeneration)]:
component = cls.from_pretrained(model, subfolder=name, dtype=torch.bfloat16,
local_files_only=local_files_only)
if not getattr(component, 'is_loaded_in_8bit', False):
raise ValueError('Expected saved bitsandbytes INT8 component: '+name)
patch_int8_device_moves(component)
component.to('cpu')
torch.cuda.empty_cache()
components[name] = component
pipe = QwenImage21Pipeline.from_pretrained(model, dtype=torch.bfloat16,
local_files_only=local_files_only, **components)
enable_int8_cpu_offload(pipe)
release_prefix_cache_before_decode(pipe)
return pipe
|