Skip to content

NF4 Flux params in diffusers #9165

Description

@sayakpaul

@SunMarc

Since the Flux params are quite huge (if we include the text encoder2, autoencoder, and the diffusion model itself) -- it totals to more than 30GB.

https://huggingface.co/lllyasviel/flux1-dev-bnb-nf4 ships a single safetensors file that has the diffusion model in NF4.

Now, I was able to get this converted and load it into our FluxTransformer2DModel, but I am not seeing any size (state dict size) benefits. I am seeing the size benefits (yay!). But loading seems to be not working yet. What am I missing? Will appreciate feedback.

Here is a detailed rundown of what I have done so far.

convert_nf4_flux.py
"""Utilities adapted from* https://github.com/huggingface/transformers/blob/main/src/transformers/quantizers/quantizer_bnb_4bit.py* https://github.com/huggingface/transformers/blob/main/src/transformers/integrations/bitsandbytes.py"""importtorchimportbitsandbytesasbnbfromtransformers.quantizers.quantizers_utilsimportget_module_from_nameimporttorch.nnasnnfromaccelerateimportinit_empty_weightsdef_replace_with_bnb_linear(
model,
method="nf4",
has_been_replaced=False,
):
""" Private method that wraps the recursion for module replacement. Returns the converted model and a boolean that indicates if the conversion has been successfull or not. """forname, moduleinmodel.named_children():
ifisinstance(module, nn.Linear):
withinit_empty_weights():
in_features=module.in_featuresout_features=module.out_featuresifmethod=="llm_int8":
model._modules[name] =bnb.nn.Linear8bitLt(
in_features,
out_features,
module.biasisnotNone,
has_fp16_weights=False,
threshold=6.0,
)
has_been_replaced=Trueelse:
model._modules[name] =bnb.nn.Linear4bit(
in_features,
out_features,
module.biasisnotNone,
compute_dtype=torch.bfloat16,
compress_statistics=False,
quant_type="nf4",
)
has_been_replaced=True# Store the module class in case we need to transpose the weight latermodel._modules[name].source_cls=type(module)
# Force requires grad to False to avoid unexpected errorsmodel._modules[name].requires_grad_(False)
iflen(list(module.children())) >0:
_, has_been_replaced=_replace_with_bnb_linear(
module,
has_been_replaced=has_been_replaced,
)
# Remove the last key for recursionreturnmodel, has_been_replaceddefcheck_quantized_param(
model,
param_name: str,
) ->bool:
module, tensor_name=get_module_from_name(model, param_name)
ifisinstance(module._parameters.get(tensor_name, None), bnb.nn.Params4bit):
# Add here check for loaded components' dtypes once serialization is implementedreturnTrueelifisinstance(module, bnb.nn.Linear4bit) andtensor_name=="bias":
# bias could be loaded by regular set_module_tensor_to_device() from accelerate,# but it would wrongly use uninitialized weight there.returnTrueelse:
returnFalsedefcreate_quantized_param(
model,
param_value: "torch.Tensor",
param_name: str,
target_device: "torch.device",
state_dict=None,
unexpected_keys=None,
pre_quantized=False
):
module, tensor_name=get_module_from_name(model, param_name)
iftensor_namenotinmodule._parameters:
raiseValueError(f"{module} does not have a parameter or a buffer named {tensor_name}.")
old_value=getattr(module, tensor_name)
iftensor_name=="bias":
ifparam_valueisNone:
new_value=old_value.to(target_device)
else:
new_value=param_value.to(target_device)
new_value=torch.nn.Parameter(new_value, requires_grad=old_value.requires_grad)
module._parameters[tensor_name] =new_valuereturnifnotisinstance(module._parameters[tensor_name], bnb.nn.Params4bit):
raiseValueError("this function only loads `Linear4bit components`")
if (
old_value.device==torch.device("meta")
andtarget_devicenotin ["meta", torch.device("meta")]
andparam_valueisNone
):
raiseValueError(f"{tensor_name} is on the meta device, we need a `value` to put in on {target_device}.")
ifpre_quantized:
if (param_name+".quant_state.bitsandbytes__fp4"notinstate_dict) and (
param_name+".quant_state.bitsandbytes__nf4"notinstate_dict
):
raiseValueError(
f"Supplied state dict for {param_name} does not contain `bitsandbytes__*` and possibly other `quantized_stats` components."
)
quantized_stats= {}
fork, vinstate_dict.items():
# `startswith` to counter for edge cases where `param_name`# substring can be present in multiple places in the `state_dict`ifparam_name+"."inkandk.startswith(param_name):
quantized_stats[k] =vifunexpected_keysisnotNoneandkinunexpected_keys:
unexpected_keys.remove(k)
new_value=bnb.nn.Params4bit.from_prequantized(
data=param_value,
quantized_stats=quantized_stats,
requires_grad=False,
device=target_device,
)
else:
new_value=param_value.to("cpu")
kwargs=old_value.__dict__new_value=bnb.nn.Params4bit(new_value, requires_grad=False, **kwargs).to(target_device)
module._parameters[tensor_name] =new_value
generate.py
fromhuggingface_hubimporthf_hub_downloadfromaccelerate.utilsimportset_module_tensor_to_device, compute_module_sizesfromaccelerateimportinit_empty_weightsfromdiffusers.loaders.single_file_utilsimportconvert_flux_transformer_checkpoint_to_diffusersfromconvert_nf4_fluximport_replace_with_bnb_linear, create_quantized_param, check_quantized_paramfromdiffusersimportFluxTransformer2DModel, FluxPipelineimportsafetensors.torchimportgcimporttorchdtype=torch.bfloat16ckpt_path=hf_hub_download("black-forest-labs/flux.1-dev", filename="flux1-dev.safetensors")
original_state_dict=safetensors.torch.load_file(ckpt_path)
converted_state_dict=convert_flux_transformer_checkpoint_to_diffusers(original_state_dict)
deloriginal_state_dictgc.collect()
withinit_empty_weights():
config=FluxTransformer2DModel.load_config("black-forest-labs/flux.1-dev", subfolder="transformer")
model=FluxTransformer2DModel.from_config(config).to(dtype)
_replace_with_bnb_linear(model, "nf4")
forparam_name, paraminconverted_state_dict.items():
param=param.to(dtype)
ifnotcheck_quantized_param(model, param_name):
set_module_tensor_to_device(model, param_name, device=0, value=param)
else:
create_quantized_param(model, param, param_name, target_device=0)
delconverted_state_dictgc.collect()
print(compute_module_sizes(model)[""] /1024/1204)
pipe=FluxPipeline.from_pretrained("black-forest-labs/flux.1-dev", transformer=model, torch_dtype=dtype)
pipe.enable_model_cpu_offload()
prompt="A mystic cat with a sign that says hello world!"image=pipe(prompt, guidance_scale=3.5, num_inference_steps=50, generator=torch.manual_seed(0)).images[0]
image.save("flux-nf4-dev.png")
model.push_to_hub("sayakpaul/flux.1-dev-nf4")

The image generates just fine. But not sure why we're not seeing any size benefit here.

image

But the loading seems broken (generated image is noise). Advise? I have uploaded the NF4 serialized state dict here: https://huggingface.co/sayakpaul/flux.1-dev-nf4

Loading script is below:

load_from_nf4_and_generate.py
"""Some bits are from https://github.com/huggingface/transformers/blob/main/src/transformers/modeling_utils.py"""fromhuggingface_hubimporthf_hub_downloadfromaccelerate.utilsimportset_module_tensor_to_device, compute_module_sizesfromaccelerateimportinit_empty_weightsfromconvert_nf4_fluximport_replace_with_bnb_linear, create_quantized_param, check_quantized_paramfromdiffusersimportFluxTransformer2DModel, FluxPipelineimportsafetensors.torchimportgcimporttorchdtype=torch.bfloat16is_torch_e4m3fn_available=hasattr(torch, "float8_e4m3fn")
ckpt_path=hf_hub_download("sayakpaul/flux.1-dev-nf4", filename="diffusion_pytorch_model.safetensors")
original_state_dict=safetensors.torch.load_file(ckpt_path)
withinit_empty_weights():
config=FluxTransformer2DModel.load_config("sayakpaul/flux.1-dev-nf4")
model=FluxTransformer2DModel.from_config(config).to(dtype)
expected_state_dict_keys=list(model.state_dict().keys())
_replace_with_bnb_linear(model, "nf4")
forparam_name, paraminoriginal_state_dict.items():
ifparam_namenotinexpected_state_dict_keys:
continueis_param_float8_e4m3fn=is_torch_e4m3fn_availableandparam.dtype==torch.float8_e4m3fniftorch.is_floating_point(param) andnotis_param_float8_e4m3fn:
param=param.to(dtype)
ifnotcheck_quantized_param(model, param_name):
set_module_tensor_to_device(model, param_name, device=0, value=param)
else:
create_quantized_param(
model, param, param_name, target_device=0, state_dict=original_state_dict, pre_quantized=True
)
deloriginal_state_dictgc.collect()
print(compute_module_sizes(model)[""] /1024/1204)
pipe=FluxPipeline.from_pretrained("black-forest-labs/flux.1-dev", transformer=model, torch_dtype=dtype)
pipe.enable_model_cpu_offload()
prompt="A mystic cat with a sign that says hello world!"image=pipe(prompt, guidance_scale=3.5, num_inference_steps=50, generator=torch.manual_seed(0)).images[0]
image.save("flux-nf4-dev-loaded.png")

NF4 serialization and loading is working fine!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions