nx_genlora.nx
buildroot/runtime/nx_genlora.nx
about
nx_genlora.nx -- apply LoRA adapters to a base weight. Sovereign, model-agnostic LoRA support.
W' = W + (alpha / rank) * multiplier * (up @ down)
read out of sd.cpp's lora.hpp, not guessed: `scale_value = alpha / rank; scale_value *= multiplier`.
With no `.alpha` tensor, alpha defaults to rank, so the scale is the multiplier alone.
⚠TWO THINGS VARY INDEPENDENTLY, AND BOTH MUST BE HANDLED:
1. THE NAMING CONVENTION.
base model.diffusion_model.layers.0.attention.qkv.weight
kohya lora_unet_layers_0_attention_qkv.lora_down.weight (flattened, underscores)
peft diffusion_model.layers.0.attention.to_q.lora_A.weight (dotted, A=down B=up)
2. WHETHER THE ADAPTER TRAINED THE **FUSED** MODULE OR THE UNFUSED ONES. This is the part that
bites. The Z-Image checkpoint stores ONE fused `attention.qkv.weight` [11520, 3840]. The
kohya adapter here matches it directly. But all NINE peft adapters in the same folder train
`to_q` / `to_k` / `to_v` SEPARATELY -- there is no `attention.qkv` key in any of them.
A name-only lookup therefore finds nothing, folds nothing, and returns "this adapter does
not cover this layer" for EVERY layer of EVERY peft adapter, with no error anywhere.
★★★★★ AN ADAPTER THAT MATCHES NO KEYS IS INDISTINGUISHABLE FROM ONE THAT IS WORKING WEAKLY.
⇒ a fused base weight must be folded SLICE-WISE from the unfused adapter modules:
to_q -> rows [0, out/3) to_k -> [out/3, 2out/3) to_v -> [2out/3, out)
★ MEASURED CAVEAT worth more than the code: on Z-Image layer 0 the kohya adapter moves the
activation by only 0.86% while the oracle's own Q8_0 error is 0.6% median. Fitting the scale by
least squares against the oracle recovered 0.1725 where alpha/rank is 0.1667 -- consistent, but
INSIDE THE NOISE. ★★★★★ YOU CANNOT VALIDATE AN ADAPTER ON A LAYER WHERE THE ADAPTER BARELY ACTS;
grade against an EXACT reference (nx_gen_lora_verify does).
WHY FOLD RATHER THAN APPLY AT RUNTIME: sd.cpp keeps the adapter separate and pays
2*rank*(in+out)*tokens every step. Folding pays 2*rank*in*out ONCE and leaves the hot matmul
byte-identical to the un-adapted path -- so an adapter costs nothing per step and cannot slow
down or perturb the kernel.
license_tier: ORIGINAL
dependencies 8 imports · 2 importers
imports: nx_syscalls.nxnx_le.nxnx_f32.nxnx_f32_div.nxnx_f32_cvt.nxnx_f16.nxnx_strconv.nxnx_genweights.nx
imported by: nx_gen_lora_repr.nxnx_gen_lora_verify.nx
structs
| none |
consts
| 45 | const LORA_MAGIC_1024: i64 = 1024 |
| 49 | const LORA_ABSENT: i64 = 0 |
| 50 | const LORA_APPLIED: i64 = 1 |
| 51 | const LORA_BAD_SHAPE: i64 = 0 - 10 |
| 52 | const LORA_BAD_PAIR: i64 = 0 - 11 |
| 53 | const LORA_BAD_TYPE: i64 = 0 - 12 |
functions
| 55 | func _lo_len(s: *u8) -> i64 |
| 60 | func _lo_app(dst: *u8, at: i64, src: *u8) -> i64 |
| 67 | func _lo_ends(s: *u8, suf: *u8) -> i64 |
| 77 | func _lo_retail(stem: *u8, nold: i64, rep: *u8) -> i64 |
| 82 | func nx_lora_key_kohya(out: *u8, base: *u8) -> i64 |
| 104 | func nx_lora_key_peft(out: *u8, base: *u8) -> i64 |
| 119 | func _lo_probe(lw: *i64, stem: *u8, suffix: *u8, nm: *u8, inf: *i64) -> i64 |
| 129 | func nx_lora_fold_slice(lw: *i64, stem: *u8, dn_suf: *u8, up_suf: *u8, W: *u8, called by 1: nx_lora_fold calls 9: sys_mmap_lo_probenx_gw_dim1nx_gw_dim0nx_gw_to_f32_packednx_f32_div+3 |
| 196 | func nx_lora_fold(lw: *i64, base: *u8, W: *u8, in_dim: i64, out_dim: i64, mult_f32: i64) -> i64 called by 2: mainmain calls 6: sys_mmapnx_lora_key_kohyanx_lora_fold_slicenx_lora_key_peft_lo_ends_lo_retail |