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 registrationKey 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_backupThe 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:
- The
.safetensorsfile's own__metadata__field. - A sibling
<basename>.json(user-edited via the UI). - A sibling
<basename>.preview.pngfor 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_lorais registered throughextra_networks.register_extra_networkin the script'son_app_startedcallback.network_apply_weightsis called as a forward-pre hook on every patched module — that's why the patches mentionlora_patches.py, which adds the necessary__lora_patched_forwardplumbing onnn.Linear/nn.Conv2d.- Cleanup is via
extra_networks.deactivate(p)at the end ofprocess_images_inner— see systems/processing.md. - The
/sdapi/v1/refresh-lorasAPI 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>.pynext to the others and add the dispatch case inload_network(). Tests are not required (none exist for any existing strategy). - Tweak weights at apply time —
Network.calc_updownis the right place. Avoid editing the patcher itself; it's hot. - Change UI —
ui_extra_networks_lora.pyis the only file that needs touching for new card metadata fields. Updateui_edit_user_metadata.pyif the field is editable.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.
Previous
Built-in extensions
Next
Other built-in extensions