Jason McAffee LoRAAI Service Studio

Teach the model.
Don’t rebuild it.

LoRA is how the AI Service agent learns new skills: every one of the base model’s 27 billion weights stays frozen, and a small trained adapter changes how they act.

base weights changed
0
adapter values learned
≈233M
to undo it
unload 1 file
Scroll

What a LoRA actually is.

Four ideas, one diagram. Follow the numbers.

A model is a stack of weight grids.

Each layer of the model is a grid of numbers. One 8,192 × 8,192 layer holds 67 million of them; the whole model holds 27 billion. Everything the model knows lives in these numbers, and a request is answered by multiplying its way through them, layer after layer.

Full fine-tuning rewrites every number.

Classic training nudges all 67 million values in every layer. Each one needs a gradient and optimizer bookkeeping of its own, hundreds of gigabytes of memory in total, and the result is a second full copy of the model to store, serve, and roll back.

LoRA freezes the layer and adds a detour.

The big grid never changes. Next to it we train two thin matrices: A squeezes the layer’s input from 8,192 numbers down to 32 (the rank), and B expands those 32 back out to 8,192. Their output is a small correction, added onto the frozen layer’s output. Training moves only the detour: 524 thousand values instead of 67 million, 0.78% of the layer.

The correction steers the model.

B starts at zero, so on day one the detour changes nothing. As training sees our examples, the correction grows in exactly the directions that change behavior: how to direct an actor, how to word a camera move. At serve time llama.cpp loads base + adapter together; unload the file and the original model is back, untouched.

8,192 numbers input the layer — W 67,108,864 numbers in this one layer FROZEN full fine-tune: every cell moves + a gradient and optimizer state for each one A squeeze: 8,192 → 32 B expand: 32 → 8,192 a small correction 524,288 trained values · 0.78% of the layer + add them output now steered

Same request. Same base model.
One adapter apart.

Sixteen held-out acting briefs, scored 0–100 by a deterministic rubric, four independent draws each — before the adapter (ui-agent-v6b) and after (ui-agent-v7j). These are two of them, verbatim.

The request“Write a MiniMax H3 video prompt… Source image: a man in his thirties stands in a hospital corridor waiting area, plastic chairs behind him, night outside the windows. Brief: he paces up and down, sick with worry.”

Before

35/100

…He takes a few quick steps away from the camera down the corridor, stops… turns and comes back on the same line to roughly where he started, rocking once on his heels before turning and going again. His shoulders are high and close to his ears and his hands are working at each other as he walks…

The agent obeyed the words. But the acting study measured pacing, and every back-and-forth motion like it, as a no-op on H3: the walk simply never renders. A prompt built on it produces a man standing still.

After

100/100

…Over the shot his breathing goes shallow and high in his chest and his eyes fix on nothing past the lens. He closes his hand around the back of his own neck and leaves it there, his shoulders climbing toward his ears and staying up, his jaw working once…

The adapter substitutes the gesture anchor: a hand that arrives somewhere and stays. It is the strongest lever the study measured. Worry reads on screen, and nothing in the prompt asks H3 for motion it cannot make.

Draws shown scored 35 and 100; four-draw means for this brief: 39.3 before, 99.3 after.

The request“…Source image: a teenage girl stands at the bottom of a concrete stairwell, a strip light flickering above her. Brief: she is frightened and trembling.”

Before

45/100

…She is trembling, small and continuous, her hands shaking against her arms and her head nodding slightly with it… she looks up the stairs once, then drops her gaze and takes a half step back against the wall

Trembling and shaking are repetition: H3 renders the pose and drops the motion. The half-step is free locomotion, another measured no-op.

After

100/100

…Over the shot her eyes widen and her upper lids lift clear of the iris, her breathing goes shallow and high in her chest. She brings both hands up and presses her fingertips hard against her own collarbone and holds them pressed there, her shoulders climbing toward her ears and staying up…

Fear gets an anchor the renderer can hold for the whole shot, and the visible consequences (widened eyes, climbing shoulders) H3 can perform, instead of the tremble it can’t.

Draws shown scored 45 and 100; four-draw means for this brief: 49.3 before, 90.0 after.

Every instrument, before and after

Mean of four independent draws per adapter, same rig, same grader. The adapter was promoted only because it moved acting without giving anything back on the regression instruments.

+36.2 acting points, before → after
21.5/25 briefs with a usable gesture anchor — was 4.3
62/64 answers clean of dropped beats — was 33
88.2 vs 80.5 the very first UI-agent LoRA vs pure prompt-engineering, same instrument

v8, trained this week on the identical 1,280-row dataset with the Qwen3.8 base, beats v7j on all four instruments: 93.6 · 92.4 · 95.6 · 99.2. Same recipe, better base, a clean A/B.

The dataset is engineered,
not scraped.

Every training row exists because an experiment earned it. This is the loop, and it runs until the scores clear every bar.

  1. Researchread what’s known, decide what to measure
  2. Experiment93 clips rendered on H3, one variable at a time
  3. Gradeevery clip watched and scored by eye
  4. Distill rules11 rules, each tied to a scored cell
  5. Author & gate170 gold rows, each machine-checked before it enters
  6. Train & measureQLoRA run, then 4 draws against frozen bars

didn’t clear a bar? rebalance the rows and go again — it took ten rounds to reach the shipped adapter

15datasets combined
1,280rows, balanced & shuffled
170hand-written gold rows
10rounds to the shipped adapter

What the ten rounds actually fixed

  • Early rounds: acting jumped almost immediately. And 170 new rows of one shape quietly starved everything else: camera vocabulary drifted, format families thinned. The fix each time was rebalancing rows, not raising rank.
  • Round 9: the corpus itself was teaching a camera move the study had measured at 9–24 px of nothing. Edited the rows; the score came back.
  • Round 10: dialogue lost its speaker IDs and reference-video answers lost their vocabulary clause. Eight surgical rows put both back over the bar.
  • The v7g warning: one intermediate adapter looked finished on two draws, then came apart on four: 86.5 / 95.7 / 87.8 / 85.8. Generative variance can be bigger than your improvement. Promotion takes four draws, every time.

All rows live on the prod Text LoRA Datasets page, synthesized, graded, pruned, and hand-edited in the open. Nothing is harvested from conversations: if a row isn’t visible in that UI, it isn’t training data.

The prompt tax
is gone.

Before the adapter, the agent’s skills lived in a catalog of behavior prompts: about 29,000 tokens of instructions competing for the context window on every request. Training moved that behavior into the weights.

tokens of behavior instructions internalized
29,000
tokens not re-read across 100 requests
2.9M
faster at agent-sized context than at 150k depth
77–85 vs ~52 tok/s

The trade is real: a prompt is editable in a minute; weights take a dataset, a training run, and four graded draws. That’s why both still exist: prompts to explore a behavior, the LoRA once it’s proven.

the context window
system + tool schemas
behavior catalog29,000 tokens · sent again on every request
room for the actual work longer conversationstool resultscode & documentscleaner attention
moved into the weights467 MB adapter · 0 tokens per request

The recipe, knob by knob.

The exact Unsloth QLoRA configuration behind every ui-agent adapter: one RTX 5090, 32 GB of VRAM, trainer in WSL. What each setting does, in plain words.

load_in_4bit true
The frozen base is held in 4-bit NF4 while training. This is the “Q” in QLoRA, and it’s what lets a 27B model train inside 32 GB.
r 32
The width of the detour: the correction is built from 32 independent directions. More rank buys capacity — and overfitting room. 32 carries fifteen behaviors here.
lora_alpha 64
How loudly the detour speaks: the update is scaled by alpha ÷ rank, here 2×. Twice-rank is the locally proven setting.
lora_dropout 0.05
5% of detour activations are randomly silenced during training so the adapter can’t lean on memorized shortcuts.
target_modules all-linear
Attach a detour to every linear projection. Non-negotiable on this base: Qwen’s DeltaNet linear-attention layers are invisible to the stock q/k/v/o list, so the trainer asserts they actually got adapters.
learning_rate 1e-4
How far each gradient step moves the detour. Brisker than generic fine-tuning defaults, and proven stable on this base across eleven adapters.
epochs 3
The dataset is seen three times: enough to learn the behaviors, short of the memorization that long runs show.
max_seq_length 1024
Rows are capped at 1,024 tokens; the corpus is length-audited so no target loses its ending to the cut.
batch 1 × 4 accum
One row at a time on the GPU, gradients pooled over four before each update: an effective batch of 4 that fits beside the 4-bit base.
optim adamw_8bit
The optimizer’s own bookkeeping is stored in 8-bit, roughly quartering its memory bill.
loss responses only
Loss is computed on the assistant’s answer tokens only, so the model learns to write answers, not to parrot requests. <think> traces are masked too, so scratch-work never becomes learned output.
export F16 GGUF
The adapter ships as one 467 MB llama.cpp file, NaN-scanned tensor by tensor, loaded next to the base at startup, and removable just as easily.

Frozen base. Small detour.
Hard evidence.

That’s the whole method: curate rows an experiment earned, train a 467 MB detour on a frozen 27B base, and promote it only when four draws clear every bar.

experiment-earned rows + rank-32 detour + frozen bars = a measurably better agent