Factory.ai

Open-Source Wikis

/

Stable Diffusion WebUI

/

Built-in extensions

/

Lora extension

AUTOMATIC1111/stable-diffusion-webui

Lora extension

Active contributors: AUTOMATIC1111, Kohaku-Blueleaf, w-e-w

Purpose

Implements the <lora:name:weight> prompt syntax, the Lora card browser, and the runtime patching that applies LoRA / LyCORIS weights to the loaded checkpoint. This is the largest extension in the repository (~2,600 LoC across 16 files in extensions-builtin/Lora/).

Directory layout

extensions-builtin/Lora/
├── extra_networks_lora.py        # ExtraNetwork subclass: parse activations, call networks.load_networks
├── networks.py                   # the heart: load_network, network_apply_weights, etc. (~730 lines)
├── network.py                    # base Network module (per-applied-LoRA state)
├── network_lora.py               # plain LoRA strategy
├── network_hada.py               # LoHa
├── network_lokr.py               # LoKr
├── network_ia3.py                # IA3
├── network_glora.py              # GLoRA
├── network_oft.py                # OFT
├── network_full.py               # Full diff weights
├── network_norm.py               # Norm-layer fine-tunes
├── lora.py                       # legacy entry point preserved for backward compat
├── lora_logger.py                # disk cache + verbose logging
├── lora_patches.py               # monkey-patches torch nn.Linear/Conv2d to support backup-and-replace cleanly
├── lyco_helpers.py               # LyCORIS-specific math
├── preload.py                    # registers --lora-dir CLI flag
├── ui_extra_networks_lora.py     # the Lora browser tab
├── ui_edit_user_metadata.py      # the per-card metadata edit dialog
└── scripts/                      # Script subclasses for boot registration

Key abstractions

Type File Description
extra_network_lora extra_networks_lora.py The ExtraNetwork instance registered into the framework. Receives parsed <lora:foo:0.7> arguments.
Network network.py A loaded LoRA — owns the per-layer modules, multipliers, and the original weights for restore.
NetworkOnDisk networks.py Filesystem record: filename, alias, hash, metadata (from sidecar JSON or built-in metadata).
NetworkModule (subclasses) the network_*.py files Per-strategy implementation; each declares the keys it expects and the forward() math.
loaded_networks networks.py global The list of currently-applied Network objects — used by deactivate() to restore weights.
available_networks networks.py global The full registry by canonical name; populated at boot and on /sdapi/v1/refresh-loras.
available_network_aliases same Map of every alternative name (legacy filename, alias from sidecar JSON) to the canonical entry.
network_apply_weights(self) networks.py Applies all currently loaded networks to a single nn.Module.
network_restore_weights_from_backup(self) networks.py Reverses the patch by restoring weight.original / weight.bias_original.

How a Lora is applied

sequenceDiagram
    participant Pipe as processing.process_images_inner
    participant EN as extra_networks
    participant ENL as extra_network_lora
    participant Loader as networks.load_networks
    participant Patcher as networks.network_apply_weights
    participant UNet
    participant TextEnc

    Pipe->>EN: parse_prompts(prompts)
    Pipe->>EN: activate(p, network_data)
    EN->>ENL: activate(p, [(['foo','0.7'], {})])
    ENL->>Loader: load_networks(['foo'], te=[1], unet=[0.7], dyn=[None])
    Loader->>Loader: read .safetensors, dispatch to NetworkModule
    Loader->>Loader: append to loaded_networks
    Pipe->>UNet: forward(...)
    UNet->>Patcher: hook on each Linear/Conv2d.forward
    Patcher->>UNet: weight + sum(network_modules[name].calc_updown(weight))
    Pipe->>EN: deactivate(p)
    EN->>ENL: deactivate(p)
    ENL->>Loader: load_networks([])
    Loader->>Patcher: network_restore_weights_from_backup

The patching is eager but cached: when a Network is activated, every relevant module's weight is mutated in place and the original weight is stashed as weight.original. Subsequent forward passes see the patched weights directly — no per-step wrapping. Deactivation restores from backup. The performance work in v1.10 (Prevent unnecessary extra networks bias backup) reduced the cost of restore by skipping bias backup when the bias isn't actually changed.

LyCORIS / multi-strategy dispatch

When a LoRA file is loaded, networks.load_network() peeks at the keys in the state dict:

Keys present Strategy File
lora_up.weight + lora_down.weight (+ optional alpha) Plain LoRA network_lora.py
hada_w1_a, hada_w1_b, hada_w2_a, hada_w2_b LoHa network_hada.py
lokr_w1, lokr_w2 (or lokr_w1_a/b, lokr_w2_a/b) LoKr network_lokr.py
on_input IA3 network_ia3.py
a1, a2, b1, b2 GLoRA network_glora.py
oft_blocks OFT network_oft.py
w_norm, b_norm Norm network_norm.py
Diff-shape weights Full network_full.py

Each strategy's NetworkModule.calc_updown() returns the delta weight (and optionally bias) to add. The patcher sums them — multiple LoRAs can stack on the same layer.

Per-block weights, dynamic ranks, sub-prompts

The Lora syntax accepts named arguments:

Token Effect
<lora:foo:0.7> Weight 0.7 applied to both UNet and text encoder
<lora:foo:te=1.0:unet=0.5> Different weights per branch
<lora:foo:lbw=A:0.8> Per-block weights (A = IN-..., etc.)
<lora:foo:dyn=64> Dynamic LoRA dim (truncate the rank to 64)
<lora:foo:hr=0.5> Different weight during the hires fix pass

Parsing happens in extra_network_lora.py. The named dict from ExtraNetworkParams carries these to load_networks.

Caching and hashing

LoRA files are SHA-256 hashed via modules/hashes.py; the hash and metadata appear in the saved infotext as Lora hashes: so the recipe can be reproduced. cache.json next to config.json keeps the hashes between runs. The "Add Lora hashes to infotext" option controls whether they're written.

The on-disk metadata is read from one of three places, in order:

  1. The .safetensors file's own __metadata__ field.
  2. A sibling <basename>.json (user-edited via the UI).
  3. A sibling <basename>.preview.png for the thumbnail.

User-edits in the Edit metadata dialog write to the sibling .json. Changes propagate to all visible filename aliases.

UI

ui_extra_networks_lora.py builds the Lora browser tab. The card layout is HTML rendered by create_html_for_item — the JavaScript (javascript/extraNetworks.js) handles search, sort, drag-and-drop into the prompt, and click-to-insert.

ui_edit_user_metadata.py is the modal dialog where the user edits per-card description, activation text, preview image, and tags. It's a gr.Modal opened by clicking the "i" icon on a card.

Integration points

  • extra_network_lora is registered through extra_networks.register_extra_network in the script's on_app_started callback.
  • network_apply_weights is called as a forward-pre hook on every patched module — that's why the patches mention lora_patches.py, which adds the necessary __lora_patched_forward plumbing on nn.Linear/nn.Conv2d.
  • Cleanup is via extra_networks.deactivate(p) at the end of process_images_inner — see systems/processing.md.
  • The /sdapi/v1/refresh-loras API endpoint forces a rescan and is used by extensions that download Loras at runtime.

Entry points for modification

  • Add a new strategy — drop a network_<name>.py next to the others and add the dispatch case in load_network(). Tests are not required (none exist for any existing strategy).
  • Tweak weights at apply timeNetwork.calc_updown is the right place. Avoid editing the patcher itself; it's hot.
  • Change UIui_extra_networks_lora.py is the only file that needs touching for new card metadata fields. Update ui_edit_user_metadata.py if the field is editable.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Lora extension – Stable Diffusion WebUI wiki | Factory