sa3 LoRA Manager#91
Open
JakeHartnell wants to merge 9 commits into
Open
Conversation
The registry read side: lsdj/loras.py resolves a client-supplied
<base>/<slug> adapter name to its on-disk directory (Rust owns the write
side), /api/generate accepts a validated {name, strength} lora object at
the trust boundary, and sa3.generate rides it as --lora/--lora-strength
on the pinned fork's CLI (ADR-0028).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
loras.rs imports adapters from a HuggingFace repo id or a local path on the shared install thread (model://progress family "lora"), validates them at the ADR-0028 trust boundary — pickle refused before any read, safetensors JSON header only, base inferred from the disjoint DiT layer widths with an explicit choice for rank-only -xs shapes — and places them under sa3-loras/<base>/<slug>/ with an import manifest. Adapters ride model_status, get an in-app delete (small, re-downloadable, never iCloud-managed), and a registry watcher mirrors watch_models. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A third model-manager section lists adapters by base with import (HF repo id or the native file picker, with an explicit-base override for shape-anonymous -xs adapters) and in-app delete. The media explorer's generate/samples forms and each deck's pad panel gain a base-matched adapter picker + strength control that ride the /api/generate lora field; a stale choice (deleted adapter, base switch) honestly falls back to none. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Import/cancel/refusal, audible adapter effect + strength scaling, the ADR-0028 bit-exact strength-0 bypass via fixed seed, registry restart/ delete/hand-drop lifecycle, and the wrong-base 422 — the facts only a real machine with the warmed SA3 checkout can verify. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The import row crammed the repo field beside the base picker and two buttons in the narrow drawer; it now wraps, with the field taking the whole first line. Pasted huggingface.co/hf.co URLs (scheme, /tree/… suffix, query, fragment) normalize to the canonical owner/name id in both the webview (so the progress label is canonical) and the shell (so any caller can paste a link). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The adapter list/import moves out of the Model library umbrella into a standalone LoraLibrary component with its own top-level settings heading, so custom sound worlds don't hide inside the weights installer. It keeps the manager's snapshot/progress contract — one install at a time shell-side, so another family's download still gates the import buttons, while only lora events surface as this section's label and errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The single adapter+strength pickers become the LoraRack: every base-matched adapter renders as a toggle chip, and a chip clicked into the stack grows a small trim knob (0-2 in quarter steps, double-click parks ×1, ×0 dims the slot — the bit-exact bypass, now visible). The rack replaces the two Selects in all three generate surfaces, taking the deck accent on the decks and master in the Media Explorer; slots stay in library order because the merge is order-independent (ADR-0028). Knob grows a 's' (28 px) dial size and an ariaLabel so the visible label can be the live ×value readout. /api/generate's `lora` object becomes `loras`: up to MAX_LORA_STACK (4) entries, duplicates refused, per-entry strength defaulting to 1.0 so the dirs/strengths argv lists stay aligned. sa3.generate emits one --lora list plus one --lora-strength value per adapter. Per-adapter strength needs a small fork change — the pinned CLI takes multiple --lora but a single global strength. docs/sa3-lora-stack.patch carries it (single value still broadcasts, so existing invocations are untouched); apply + push + sa3-pin.json bump before hardware checks, per the new checklist section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-0028's revert condition arrived: our merge-at-load contribution merged upstream 2026-07-14 (Stability-AI/stable-audio-3#57), and #65 superseded the fork's flag shape with per-adapter --lora groups (PATH strength=S [steps=RANGE]). The pin moves to upstream main (0385302); the model manager offers the switch as a normal update (repo drift already counts as updatable). sa3.generate now emits one --lora group per adapter with its strength= option instead of parallel dir/strength lists, verified against upstream's parse_lora_spec grammar. docs/sa3-lora-stack.patch is gone — upstream already does what it patched in, better — and the checklist's fork gate becomes "run the in-app SA3 update". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stacked slot grew downward (chip over an inset trim well), costing ~90px of the Media Explorer tray whenever anything was stacked — height that belongs to the library list. The slot is now a strip: trim knob beside the chip behind a hairline divider, the ×value readout beside the dial (the knob lies flat via the rack's trim styles, with a floor width on the readout so a drag from ×1 to ×0.25 doesn't shuffle the neighbouring slots), and the silkscreen label inline. Same grammar — chip toggles, knob trims, ×0 dims — at control height. The rack also owns its empty state now: it renders nothing without adapters, so the call sites drop their length guards (the deck keeps only its magenta gate). Its cap likewise counts only present slots, so a stale stack entry can't hold a slot hostage after a delete. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brxs
self-requested a review
July 18, 2026 16:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings custom Stable Audio 3 finetunes into LSDJ end-to-end (ADR-0028, built on
the #44 spike): import them, manage them, and play them — up to four at once,
each with its own strength — from every generate surface. Along the way, our
LoRA runtime contribution merged upstream, so this PR also retires the fork.
Importing & managing adapters
or pasted URL (
huggingface.co/…andhf.co/…forms normalize to thecanonical id), or a local
.safetensorsvia the native file picker..safetensorsonly —pickle (
.ckpt/.pt/.pth/.bin) is refused before any read, and only thesafetensors JSON header is ever parsed. Both conventions are read (SA3-native
metadata and PEFT
adapter_config.json).fan-in widths are disjoint, measured from the runtime); rank-only
-xsadapters fall back to the PEFT config or an explicit base choice.
sa3-loras/<base>/<slug>/in the app data dir,watched live (hand-dropped folders appear, deletes propagate), surviving
restarts with nothing else to persist. Install/cancel/progress and delete
match the Add an in-app model manager: download Magenta + Stable Audio models from a settings overlay #43 model manager, on the same single-install pipeline.
Playing them — the LoRA rack
Every base-matched adapter renders as a toggle chip in a one-line rack strip
(Generate tab, Samples tab, and both deck columns — deck-accented on the decks).
Clicking a chip into the stack grows a trim knob beside it: ×0–×2 in quarter
steps, double-click parks ×1, and ×0 is the bit-exact bypass, shown as a
dimmed slot. Up to four adapters stack per generation; slots stay in library
order because the merge is order-independent. Trims are remembered per adapter
while toggling; a deleted adapter silently drops from the next request.
/api/generategainsloras: [{name, strength}, …]— validated at the trustboundary (installed-adapter resolution, base-vs-kind match, strength 0–4 bounds,
cap 4, duplicate refusal) and emitted as one
--lora <dir> strength=<s>groupper adapter.
Back on upstream
Our MLX merge-at-load runtime landed as Stability-AI/stable-audio-3#57, and #65
extended it with the per-adapter
strength=syntax this PR emits — sosa3-pin.jsonreverts from the fork to upstream, per ADR-0028's stated exit.The model manager offers the switch as a normal SA3 update.
Testing
parse_lora_specgrammar), and the full/api/generatecontract incl.boundary/NaN/duplicate/cap rejections.
normalization (needs
cargo clippy/cargo teston CI or a Mac — the sandboxcan't build the shell).
docs/issue-66-checklist.md— Maqam import, audible effect,seed-fixed ×0 hash equality, lifecycle, wrong-base 422, and the new
multi-adapter section (run the in-app SA3 update first).