Files
2026-07-13 13:22:34 +08:00

114 lines
4.1 KiB
Python

"""
Demo: MLflow Diffusers Adapter Flavor (LoRA)
This script demonstrates the full workflow of logging and loading a diffusion
model LoRA adapter using the native mlflow.diffusers flavor.
No GPU or real model weights required — uses a fake adapter for validation.
"""
import tempfile
from pathlib import Path
import numpy as np
import yaml
from safetensors.numpy import save_file
import mlflow
import mlflow.diffusers
def create_fake_lora_adapter(output_dir: Path) -> Path:
output_dir.mkdir(parents=True, exist_ok=True)
# Simulate LoRA weight matrices (small random tensors)
tensors = {
"unet.down_blocks.0.attentions.0.transformer_blocks.0.attn1.to_q.lora_down.weight": (
np.random.randn(4, 320).astype(np.float32)
),
"unet.down_blocks.0.attentions.0.transformer_blocks.0.attn1.to_q.lora_up.weight": (
np.random.randn(320, 4).astype(np.float32)
),
}
adapter_file = output_dir / "pytorch_lora_weights.safetensors"
save_file(tensors, str(adapter_file))
print(f"Created fake LoRA adapter at: {adapter_file}")
print(f" Adapter size: {adapter_file.stat().st_size} bytes")
return output_dir
def demo_log_and_load():
"""Demonstrate the full log -> load -> inspect cycle."""
with tempfile.TemporaryDirectory() as tmpdir:
# 1. Create fake adapter
adapter_dir = create_fake_lora_adapter(Path(tmpdir) / "my_lora")
# 2. Log the adapter with MLflow
print("\n--- Logging adapter with mlflow.diffusers.log_model() ---")
mlflow.set_experiment("diffusers-adapter-poc")
with mlflow.start_run(run_name="lora-adapter-demo") as run:
model_info = mlflow.diffusers.log_model(
adapter_path=str(adapter_dir),
base_model="black-forest-labs/FLUX.1-dev",
adapter_type="lora",
name="lora_model",
metadata={
"lora_rank": 4,
"training_steps": 1000,
"trigger_word": "sks style",
},
)
print(f" Run ID: {run.info.run_id}")
print(f" Model URI: {model_info.model_uri}")
# 3. Inspect the MLmodel file
print("\n--- MLmodel file contents ---")
model_uri = f"runs:/{run.info.run_id}/lora_model"
local_path = mlflow.artifacts.download_artifacts(model_uri)
mlmodel_path = Path(local_path) / "MLmodel"
with open(mlmodel_path) as f:
mlmodel = yaml.safe_load(f)
print(yaml.dump(mlmodel, default_flow_style=False, indent=2))
# 4. Load the model back
print("--- Loading model back with mlflow.diffusers.load_model() ---")
loaded = mlflow.diffusers.load_model(model_uri)
print(f" Type: {type(loaded).__name__}")
print(f" Base model: {loaded.base_model}")
print(f" Adapter type: {loaded.adapter_type}")
print(f" Adapter path: {loaded.adapter_path}")
print(f" Adapter files: {list(Path(loaded.adapter_path).iterdir())}")
# 5. Verify flavor config from MLmodel
print("\n--- Flavor config ---")
flavor_conf = mlmodel["flavors"]["diffusers"]
print(f" base_model: {flavor_conf['base_model']}")
print(f" adapter_type: {flavor_conf['adapter_type']}")
print(f" adapter_weights: {flavor_conf['adapter_weights']}")
# 6. Show that pyfunc interface is available
print("\n--- Pyfunc model interface ---")
print(" mlflow.pyfunc.load_model() would return a wrapper with predict()")
print(" predict() accepts: DataFrame/dict with 'prompt' column")
print(" predict() returns: list of PNG-encoded image bytes")
print(" (Skipping actual pyfunc load — requires base model download)")
print("\n--- Demo complete! ---")
print(
"The adapter is logged as a first-class MLflow model with full model registry support."
)
print(
"To generate images, call loaded.load_pipeline() on a machine "
"with the base model available."
)
if __name__ == "__main__":
demo_log_and_load()