Instructions to use darask0/MOSS-Image_4-step_adapter_lora with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use darask0/MOSS-Image_4-step_adapter_lora with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("OpenMOSS-Team/MOSS-Image-SFT", dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("darask0/MOSS-Image_4-step_adapter_lora") 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
Download comfyui/rapid-moss/nodes.py from darask0/MOSS-Image_4-step_adapter_lora: direct link, hf CLI and curl.
- Browser
- Download file 14.3 kB
-
https://huggingface.co/darask0/MOSS-Image_4-step_adapter_lora/resolve/main/comfyui/rapid-moss/nodes.py
- Command line
-
hf download hf://darask0/MOSS-Image_4-step_adapter_lora/comfyui/rapid-moss/nodes.py
-
curl -L -o nodes.py https://huggingface.co/darask0/MOSS-Image_4-step_adapter_lora/resolve/main/comfyui/rapid-moss/nodes.py
14.3 kB
| """Few-step MOSS-Image adapters (rapid-moss DMAD 4-step, lodestones 8-step DMD) for ComfyUI. | |
| Works on native ComfyUI MOSS-Image models and the OpenMOSS HF loader. | |
| An adapter is a folder in models/rapid_moss/ holding lora.safetensors, time_embedding.safetensors and | |
| dataspace.safetensors. The node returns a patched MODEL plus the data-space SAMPLER and SIGMAS for | |
| SamplerCustomAdvanced (use BasicGuider: no CFG, no negative prompt). | |
| """ | |
| import inspect | |
| import math | |
| import os | |
| import comfy.patcher_extension | |
| import comfy.samplers | |
| import folder_paths | |
| import torch | |
| from safetensors.torch import load_file | |
| from torch.nn import functional as F | |
| FOLDER = 'rapid_moss' | |
| folder_paths.add_model_folder_path(FOLDER, os.path.join(folder_paths.models_dir, FOLDER)) | |
| FILES = ('lora.safetensors', 'time_embedding.safetensors', 'dataspace.safetensors') | |
| PROJ = ('q', 'k', 'v', 'o', 'ffn_up_gate', 'ffn_down') | |
| TIME = ('time_embedding.0.weight', 'time_embedding.0.bias', 'time_embedding.2.weight', 'time_embedding.2.bias', | |
| 'shared_block_modulation.bias', 'video_head_modulation.bias') | |
| DM = 'diffusion_model.' | |
| WRAPPER_KEY = 'rapid_moss' | |
| def _adapter_dirs(): | |
| found = {} | |
| for root in folder_paths.get_folder_paths(FOLDER): | |
| if os.path.isdir(root): | |
| for name in sorted(os.listdir(root)): | |
| path = os.path.join(root, name) | |
| if name not in found and all(os.path.isfile(os.path.join(path, f)) for f in FILES): | |
| found[name] = path | |
| return found | |
| class _DeviceCopy: | |
| """Tensors kept on the CPU, copied once to the device that first needs them.""" | |
| def __init__(self, *tensors): | |
| self.cpu = tensors | |
| self.device = None | |
| self.copies = None | |
| def get(self, device): | |
| if device != self.device: | |
| self.copies = [t.to(device) for t in self.cpu] | |
| self.device = device | |
| return self.copies | |
| # ---- OpenMOSS HF loader: patch the modules of the diffusers transformer ---- | |
| class _FP32Linear(torch.nn.Module): | |
| """fp32 linear outside ComfyUI's weight management. `weight` is a plain attribute so the MOSS | |
| forward (which casts its input to `.weight.dtype`) runs this path in fp32.""" | |
| def __init__(self, weight, bias): | |
| super().__init__() | |
| self.weight = weight | |
| self.tensors = _DeviceCopy(weight, bias) | |
| def forward(self, x): | |
| weight, bias = self.tensors.get(x.device) | |
| return F.linear(x.float(), weight, bias) | |
| def _lora_forward(module, down, up, scale): | |
| """Unmerged fp32 LoRA: the delta is below bf16 rounding, so it must not be merged into the weight.""" | |
| class_forward = type(module).forward | |
| tensors = _DeviceCopy(down, up) | |
| def forward(x, *args, **kwargs): | |
| out = class_forward(module, x, *args, **kwargs) | |
| d, u = tensors.get(x.device) | |
| return out + (((x.float() @ d.t()) @ u.t()) * scale).to(out.dtype) | |
| return forward | |
| def _patch_hf(patched, lora, time_path, strength): | |
| patched.add_object_patch(DM + 'time_embedding', torch.nn.Sequential( | |
| _FP32Linear(time_path['time_embedding.0.weight'].float(), time_path['time_embedding.0.bias'].float()), | |
| torch.nn.SiLU(), | |
| _FP32Linear(time_path['time_embedding.2.weight'].float(), time_path['time_embedding.2.bias'].float()))) | |
| for name in ('shared_block_modulation', 'video_head_modulation'): | |
| weight = patched.get_model_object(DM + name).weight.detach().to('cpu', torch.float32) | |
| patched.add_object_patch(DM + name, _FP32Linear(weight, time_path[name + '.bias'].float())) | |
| for i in range(len(patched.get_model_object(DM + 'layers'))): | |
| for proj in PROJ: | |
| target = f'{DM}layers.{i}.proj.{proj}' | |
| down = lora[target + '.lora_down.weight'].float() | |
| up = lora[target + '.lora_up.weight'].float() | |
| scale = strength * float(lora[target + '.alpha']) / down.shape[0] | |
| patched.add_object_patch(target + '.forward', _lora_forward(patched.get_model_object(target), down, up, scale)) | |
| # ---- native ComfyUI MOSS-Image: the whole forward as a DIFFUSION_MODEL wrapper ---- | |
| def _timestep_embedding(timestep, dim=256): | |
| """The native sinusoidal embedding (timestep * 1000, cos | sin) in float64, never rounded to bf16.""" | |
| half = dim // 2 | |
| freqs = torch.exp(-math.log(10000) * torch.arange(half, dtype=torch.float64, device=timestep.device) / half) | |
| phase = (timestep.double() * 1000.0)[:, None] * freqs | |
| return torch.cat((phase.cos(), phase.sin()), dim=-1).float() | |
| def _fp32_linear(module, x, bias): | |
| """The base modulation weight in fp32 with the adapter's bias (ComfyUI casts or loads the weight).""" | |
| import comfy.ops | |
| weight, base_bias, stream = comfy.ops.cast_bias_weight(module, x, dtype=torch.float32, offloadable=True) | |
| out = F.linear(x, weight, bias) | |
| comfy.ops.uncast_bias_weight(module, weight, base_bias, stream) | |
| return out | |
| def _lin(module, x, lora): | |
| out = module(x) | |
| down, up = lora | |
| return out + F.linear(F.linear(x.to(down.dtype), down), up).to(out.dtype) | |
| class _NativeForward: | |
| """MOSS-Image forward with the adapter: fp32 time path, modulation and residual stream, unmerged LoRA | |
| (as rapid-moss trains and samples it). ComfyUI's memory compiler (comfy-aimdo) records the allocations of | |
| each block and replays them; a tensor allocated in one block and freed in a later one aborts it | |
| ("aimdo memory compile error"). So, like the native blocks, every block updates a residual allocated before | |
| the compiled region in place, and the adapter tensors reach the device before that region.""" | |
| def __init__(self, lora, time_path, strength, num_layers, lora_dtype=torch.bfloat16): | |
| self.time = _DeviceCopy(*(time_path[k].float() for k in TIME)) | |
| self.lora = [] | |
| for i in range(num_layers): | |
| tensors = [] | |
| for proj in PROJ: | |
| prefix = f'{DM}layers.{i}.proj.{proj}' | |
| down = lora[prefix + '.lora_down.weight'] | |
| scale = strength * float(lora[prefix + '.alpha']) / down.shape[0] | |
| tensors += [down.to(lora_dtype), (lora[prefix + '.lora_up.weight'].float() * scale).to(lora_dtype)] | |
| self.lora.append(_DeviceCopy(*tensors)) | |
| def __call__(self, executor, x, timestep, context, transformer_options={}, **kwargs): | |
| import comfy.model_prefetch | |
| from comfy.ldm.moss_image.model import modulated_rms_norm | |
| dm = executor.class_obj | |
| B, _, H, W = x.shape | |
| dtype, device = x.dtype, x.device | |
| transformer_options = transformer_options.copy() | |
| if 'transformer_options' in inspect.signature(dm.rope).parameters: | |
| pe = dm.rope(H, W, context.shape[1], device, transformer_options) | |
| else: | |
| pe = dm.rope(H, W, context.shape[1], device) | |
| t0_w, t0_b, t2_w, t2_b, mod_bias, head_bias = self.time.get(device) | |
| lora = [layer.get(device) for layer in self.lora] | |
| vec = F.linear(F.silu(F.linear(_timestep_embedding(timestep), t0_w, t0_b)), t2_w, t2_b) | |
| mod = _fp32_linear(dm.shared_block_modulation, vec, mod_bias).unflatten(-1, (6, -1)).unsqueeze(2).unbind(1) | |
| head_shift, head_scale = _fp32_linear(dm.video_head_modulation, vec, head_bias).unflatten(-1, (2, -1)).unsqueeze(2).unbind(1) | |
| txt = dm.text_embedder(context, pe[:, H * W:], transformer_options) | |
| # fp32 residual, allocated outside the memory compiler region; the blocks update it in place | |
| x = torch.cat((dm.video_embedder(x.flatten(2).transpose(1, 2)), txt), dim=1).float() | |
| del txt | |
| blocks_replace = transformer_options.get('patches_replace', {}).get('dit', {}) | |
| transformer_options['total_blocks'] = len(dm.layers) | |
| transformer_options['block_type'] = 'single' | |
| prefetch_queue = comfy.model_prefetch.make_prefetch_queue(list(dm.layers), device, transformer_options) | |
| comfy.model_prefetch.malloc_graph_begin(device) | |
| for i, block in enumerate(dm.layers): | |
| comfy.model_prefetch.prefetch_queue_pop(prefetch_queue, device, block, dtype, malloc_scope='block') | |
| transformer_options['block_index'] = i | |
| if ('single_block', i) in blocks_replace: | |
| def block_wrap(args, block=block, layer=lora[i]): | |
| return {'img': self.block(block, layer, args['img'], args['mod'], args['pe'], dtype, args['transformer_options'])} | |
| x = blocks_replace[('single_block', i)]({'img': x, 'vec': vec, 'mod': mod, 'pe': pe, 'transformer_options': transformer_options}, | |
| {'original_block': block_wrap})['img'] | |
| else: | |
| self.block(block, lora[i], x, mod, pe, dtype, transformer_options) | |
| comfy.model_prefetch.prefetch_queue_pop(prefetch_queue, device, None, malloc_scope='block') | |
| comfy.model_prefetch.malloc_graph_end() | |
| y = modulated_rms_norm(x[:, :H * W], dm.final_norm_video, head_scale, head_shift).to(dtype) | |
| return dm.final_linear_video(y).transpose(1, 2).reshape(B, dm.latent_channels, H, W) | |
| def block(block, lora, x, mod, pe, dtype, transformer_options): | |
| from comfy.ldm.moss_image.model import attention, modulated_rms_norm, situ | |
| q_lora, k_lora, v_lora, o_lora, up_lora, down_lora = zip(lora[0::2], lora[1::2]) | |
| shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = mod | |
| y = modulated_rms_norm(x, block.norm1, scale_a, shift_a).to(dtype) | |
| if block.fused_qkv: | |
| q, k, v = block.proj['qkv'](y).chunk(3, dim=-1) | |
| else: | |
| q, k, v = block.proj['q'](y), block.proj['k'](y), block.proj['v'](y) | |
| y_l = y.to(q_lora[0].dtype) | |
| q, k, v = (out + F.linear(F.linear(y_l, down), up).to(out.dtype) for out, (down, up) in zip((q, k, v), (q_lora, k_lora, v_lora))) | |
| out = attention(q, k, v, block.q_norm, block.k_norm, pe, block.num_heads, block.comfy_attention, transformer_options) | |
| del q, k, v, y_l | |
| x.addcmul_(_lin(block.proj['o'], out, o_lora), gate_a) | |
| del out | |
| y = modulated_rms_norm(x, block.norm2, scale_f, shift_f).to(dtype) | |
| hidden, gate = _lin(block.proj['ffn_up_gate'], y, up_lora).chunk(2, dim=-1) | |
| x.addcmul_(_lin(block.proj['ffn_down'], situ(hidden, gate), down_lora), gate_f) | |
| return x | |
| def _is_native(patched): | |
| try: | |
| from comfy.ldm.moss_image.model import MOSSImage | |
| except ImportError: | |
| return False | |
| return isinstance(patched.get_model_object(DM[:-1]), MOSSImage) | |
| def sample_dataspace(model, x, sigmas, extra_args=None, callback=None, disable=None, Q=None, codes=None): | |
| """x0 = 0; for r: x = z + Q x0, v = net(x, code_r), x0 = z - v (one noise z, no schedule, no CFG).""" | |
| extra_args = {} if extra_args is None else extra_args | |
| z = x.float() # sigmas[0] == 1, so the sampler receives pure noise | |
| mix = Q.to(z.device, torch.float32) | |
| x0 = torch.zeros_like(z) | |
| s_in = z.new_ones([z.shape[0]]) | |
| for r in range(len(sigmas) - 1): | |
| x_in = z + torch.einsum('oc,bchw->bohw', mix, x0) | |
| s = float(codes[r]) / 1000.0 # flow model: timestep = sigma * 1000 = code | |
| denoised = model(x_in, s * s_in, **extra_args).float() # = x_in - s * v | |
| x0 = z - (x_in - denoised) / s | |
| if callback is not None: | |
| callback({'x': x_in, 'i': r, 'sigma': sigmas[r], 'sigma_hat': sigmas[r], 'denoised': x0}) | |
| return x0 | |
| class RapidMOSSAdapter: | |
| def INPUT_TYPES(cls): | |
| names = list(_adapter_dirs()) or ['(no adapter folder in models/rapid_moss)'] | |
| return {'required': { | |
| 'model': ('MODEL',), | |
| 'adapter': (names,), | |
| 'steps': ('INT', {'default': 4, 'min': 1, 'max': 8, | |
| 'tooltip': 'Network evaluations; up to the adapter step-code count (4 for rapid-moss, 8 for lodestones).'}), | |
| 'lora_strength': ('FLOAT', {'default': 1.0, 'min': 0.0, 'max': 2.0, 'step': 0.05}), | |
| }} | |
| RETURN_TYPES = ('MODEL', 'SAMPLER', 'SIGMAS') | |
| RETURN_NAMES = ('model', 'sampler', 'sigmas') | |
| FUNCTION = 'apply' | |
| CATEGORY = 'MOSS-Image/rapid-moss' | |
| DESCRIPTION = ('Few-step MOSS-Image: patches the MOSS-Image MODEL with a data-space adapter (unmerged LoRA, ' | |
| 'fp32 time path) and returns the matching SAMPLER and SIGMAS for SamplerCustomAdvanced + BasicGuider.') | |
| def apply(self, model, adapter, steps, lora_strength): | |
| dirs = _adapter_dirs() | |
| if adapter not in dirs: | |
| raise FileNotFoundError(f'Put {", ".join(FILES)} in a folder under models/{FOLDER}/ (got {adapter!r}).') | |
| path = dirs[adapter] | |
| lora = load_file(os.path.join(path, 'lora.safetensors')) | |
| time_path = load_file(os.path.join(path, 'time_embedding.safetensors')) | |
| data = load_file(os.path.join(path, 'dataspace.safetensors')) | |
| codes = data['step_codes'].float() | |
| if steps > len(codes): | |
| raise ValueError(f'{adapter} has {len(codes)} step codes; steps must be <= {len(codes)}.') | |
| patched = model.clone() | |
| base = patched.get_model_object(DM + 'shared_block_modulation') | |
| if base.weight.dtype not in (torch.bfloat16, torch.float16, torch.float32): | |
| raise ValueError('Load MOSS-Image with weight_dtype default/bf16/fp32; fp8 weights are not supported here.') | |
| if _is_native(patched): | |
| patched.remove_wrappers_with_key(comfy.patcher_extension.WrappersMP.DIFFUSION_MODEL, WRAPPER_KEY) | |
| patched.add_wrapper_with_key(comfy.patcher_extension.WrappersMP.DIFFUSION_MODEL, WRAPPER_KEY, _NativeForward( | |
| lora, time_path, lora_strength, len(patched.get_model_object(DM + 'layers')))) | |
| else: | |
| _patch_hf(patched, lora, time_path, lora_strength) | |
| sampler = comfy.samplers.KSAMPLER(sample_dataspace, extra_options={'Q': data['latent_mix.Q'].float(), 'codes': codes}) | |
| sigmas = torch.cat((codes[:steps] / 1000.0, torch.zeros(1))) | |
| return (patched, sampler, sigmas) | |
| NODE_CLASS_MAPPINGS = {'RapidMOSSAdapter': RapidMOSSAdapter} | |
| NODE_DISPLAY_NAME_MAPPINGS = {'RapidMOSSAdapter': 'MOSS-Image Few-Step Adapter (rapid-moss)'} | |