2026/07/13 26.7k words

Transformer System #numinens#transformer#lora#peft#fine-tuning#low-rank#svd#gradients#training#tensor-shapes

M6.3 - LoRA Foundations: Low-Rank Updates, Gradients, and Target Modules

M6.3 - LoRA Foundations: Low-Rank Updates, Gradients, and Target Modules

0x00 Overview: From Full Fine-Tuning To A Structured Update

Suppose one pretrained model must support a legal-document workflow, a customer-support assistant, and a code-review tool. All three applications can reuse the base model’s language and reasoning capabilities, but they do not need exactly the same behavior. Each has its own data, output contract, vocabulary emphasis, and failure policy.

The direct solution is to copy the pretrained checkpoint and fully fine-tune one copy per task. That gives every task the freedom to change any selected weight. It also turns adaptation into a model-sized lifecycle:

one base model
    -> full task-A training state -> full task-A checkpoint
    -> full task-B training state -> full task-B checkpoint
    -> full task-C training state -> full task-C checkpoint

The cost is not only the number of floating-point operations in one optimizer step. Each task may own model-sized gradients, optimizer moments, distributed training state, checkpoints, evaluation artifacts, and deployment copies. When a base revision changes, every derived model also needs an explicit compatibility and migration decision.

Parameter-efficient fine-tuning changes the ownership question:

Can one frozen base support many downstream behaviors by storing and optimizing only a small task-specific object?

Full fine-tuning and PEFT create different per-task artifacts

0x00A Adaptation Changes A Function, Not Merely A File

Pretraining produces a general-purpose parameter set from a broad data distribution. A downstream task asks for a related function under a new data distribution. Fine-tuning learns a parameter change:

This notation hides several physically different objects:

base state:
    theta_0 and the exact model architecture it belongs to

optimization state:
    trainable parameters, gradients, moments, activations, and communication

task artifact:
    the values and metadata required to reconstruct the adapted function

serving state:
    loaded base weights, active adaptation, caches, and runtime buffers

Full fine-tuning permits to be dense over the selected parameters. That is the least restrictive adaptation hypothesis, but its trainable and task-specific state scales with the selected base surface. PEFT is not one algorithm that simply makes this tensor smaller. It is a family of ways to restrict where or how adaptation enters the model.

0x00B PEFT Methods Change Different Contracts

Four broad PEFT families are useful because they alter different execution paths:

FamilyTraining interventionForward/inference consequenceTypical artifact
additiveinsert learned modules or sequence-side statesextra module, prompt, or prefix path remains activeadded module or learned prompt state
selectiveunfreeze a chosen subset of existing parametersoriginal graph, but selected base tensors differsparse subset or derived checkpoint
reparameterizedoptimize a compact parameterization of a weight changelive branch or algebraically transformed weightfactors plus reconstruction metadata
hybridcombine several interventionsmore than one execution/state contractcomposite artifact

PEFT placements across prompt, attention, and feed-forward paths

Source-local visual from A22 - LoRA, based on the PEFT survey cited there. Gray components are frozen and pink components are learned. The diagram contrasts sequence-side prompt state, low-rank attention updates, and bottleneck adapters in the feed-forward path. These methods can have similar trainable-parameter counts while changing different tensors and runtime paths.

A bottleneck adapter inserts a new nonlinear module. Prompt or prefix tuning changes sequence-side state. Selective tuning changes chosen base tensors. LoRA is reparameterized PEFT: it leaves the pretrained matrix frozen and expresses its task update through a constrained factorization. Calling all of these methods “adapters” without naming the changed state is too imprecise for implementation or deployment.

0x00C Why Low Rank Is A Hypothesis About The Update

For one pretrained linear weight , full fine-tuning can learn an unrestricted dense update with degrees of freedom. LoRA instead searches inside the family:

with . The inner width limits the update to at most independent directions. This is a structural claim about the task update, not a claim that the pretrained weight is low rank or that task knowledge occupies a few individual neurons.

The intuition is that pretraining has already built a rich representation. Some downstream tasks may need to steer or recombine that representation along far fewer directions than would be needed to learn it from scratch. The LoRA paper reports empirical evidence consistent with low-rank adaptation updates in its studied settings. It does not prove that every layer, task, model, or domain shift has the same useful rank.

An analogy is a patch against a large codebase: many task variants can share one upstream tree and store only their change. The analogy breaks in two places. A textual patch is often sparse in coordinates, whereas is generally a dense matrix update. And version control does not optimize the patch through gradient descent. LoRA constrains the update by rank, not by the number of nonzero entries.

0x00D Training, Checkpointing, And Serving Are Different Phases

A complete LoRA lifecycle is:

construct:
    load an exact base revision
    discover target modules
    inject A/B factors and freeze the intended base parameters

train:
    run the base path and adapter path
    store gradients and optimizer state only for trainable adaptation state

save:
    write adapter tensors plus configuration and base-model identity

load:
    recreate the same target-module transformation
    validate keys, shapes, orientation, scaling, and base revision

serve:
    keep a live adapter branch for switching
    or derive a merged weight when the representation and operating mode permit it

This separation prevents three common overclaims:

  1. fewer trainable parameters do not mean the base weights vanish from training or inference memory;
  2. a small checkpoint does not mean the adapter can be loaded onto any shape-compatible base;
  3. an algebraically mergeable update does not mean every quantized, sharded, multi-adapter runtime can merge and unmerge it without additional constraints.

0x00E What This Article Must Establish

LoRA applies the structured update to each selected matrix:

The base matrix remains present and participates in every forward pass; only the low-rank branch is trainable in vanilla LoRA. That compact equation creates several questions that require separate derivations:

LoRA forward and backward tensor paths separating the frozen base projection, low-rank update branch, trainable factor gradients, and frozen base weight
geometry:
    what does rank r permit the update to express?

execution:
    in what order do X, A, and B multiply?

optimization:
    which gradients exist when one factor starts at zero?

state:
    what remains frozen, trainable, checkpointed, or mergeable?

placement:
    which Transformer matrices receive an adapter?

artifact:
    how does a library inject, save, reload, activate, and merge it?

The Transformer Series A22 article establishes fine-tuning, PEFT, matrix rank, and SVD before presenting LoRA. M6.3 preserves that learning path, then extends it with explicit tensor axes, forward/backward derivations, optimizer ownership, target-module consequences, framework injection, checkpoint keys, and failure ledgers.

0x00F The Problem-Response Lineage

LoRA is easier to understand when it is placed in the sequence of problems that adaptation methods tried to solve. The sequence is not a winner’s history in which every new method replaces the previous one. Each method moves task-specific state to a different location and therefore changes a different training, artifact, or runtime contract.

Response to task adaptationTask-specific stateProblem it addressesContract it leaves behind
full fine-tuningselected base weights and model-sized optimizer statemaximize freedom to alter the pretrained functionone derived weight state per task, with full compatibility and release obligations
bottleneck adaptersadded nonlinear modules inside blocksreuse a frozen backbone while learning compact task modulesan extra inference path and adapter architecture must remain present
intrinsic-dimension reparameterizationa small coordinate vector projected into a larger update spacetest whether useful fine-tuning directions occupy fewer degrees of freedoma measurement protocol and projection map, not a low-rank matrix theorem
prefix tuninglearned layer-wise attention prefix statesteer generation while freezing model weightsadditional K/V-like sequence state changes attention length and cache identity
prompt tuninglearned input embeddingscondition a frozen model with a minimal sequence-side artifactextra input positions remain part of the request
LoRAlow-rank factors for selected weight deltasmake weight-space adaptation trainable and compact without inserting a nonlinear bottleneckbase identity, target modules, factor orientation, scaling, and merge state must be preserved

The chronology matters because several phrases that sound similar describe different claims:

motivation:
    useful adaptation may occupy a much smaller search space than all base parameters

operator:
    one selected dense update is parameterized as s * B * A

artifact:
    factors, configuration, target names, and base revision reconstruct the adaptation

runtime:
    the update is executed as a live branch or represented in a compatible merged weight

The intrinsic-dimension study supplies evidence about a low-dimensional optimization space. It does not prove that each useful matrix update has low matrix rank. Prefix-Tuning, Prompt Tuning, and the Houlsby bottleneck adapter show that a frozen base can be adapted by changing sequence-side or module-side state. LoRA v2 makes a different move: it freezes the pretrained matrix and learns a low-rank weight delta for selected modules.

The paper establishes the operator and reports its experiments. The pinned Microsoft loralib repository is separate implementation evidence for injected modules and merge behavior. The pinned PEFT v0.19.1 LoRA reference source is later framework evidence for target discovery, named adapters, configuration, and state handling. M6.3 will compare those evidence surfaces rather than treating a current library interface as if it were part of the 2021 paper.

0x01 Prerequisites: Fine-Tuning, PEFT, Rank, And Factorization

0x01A Pretraining, Fine-Tuning, And Adaptation State

These stages optimize different objects under different constraints:

StageStarting pointTypical trainable objectOutput artifact
pretrainingrandom or partially initialized modelmost model parametersgeneral base checkpoint
full fine-tuningpretrained checkpointmost or all selected base parametersanother full checkpoint
PEFTpretrained checkpointsmall added or selected parameter setbase reference plus adaptation state

For one linear layer , full fine-tuning makes the full matrix trainable. Even if only the final weight delta is saved, training commonly carries weight-shaped state:

base or trainable weight: [d_out,d_in]
gradient:                 [d_out,d_in]
optimizer moments:        one or more [d_out,d_in] tensors

PEFT does not make forward activations or backpropagation through the model disappear. It primarily reduces which parameter tensors require gradients, optimizer state, and task-specific storage.

0x01B PEFT Is A Family, Not A Synonym For LoRA

Different PEFT methods modify different parts of the model contract:

FamilyWhat becomes trainableRepresentative idea
additivenew modules or learned statesadapters, prompt/prefix tuning
selectivea subset of existing parametersbiases or selected layers
reparameterizeda compact parameterization of a weight changeLoRA
hybridmore than one of the aboveprompt plus adapter, LoRA plus other modules

PEFT method families from the Transformer Series source corpus

LoRA is reparameterized PEFT. At inference it can remain as a live side branch or, under compatible conditions, be algebraically merged into the base matrix. Prompt tuning changes sequence-side state; bottleneck adapters add new nonlinear modules; selective tuning changes chosen existing parameters. Their parameter counts may all be small, but their execution and deployment contracts are not interchangeable.

0x01C Matrix Rank Measures Independent Directions

The rank of a matrix is the dimension of its row space, equivalently its column space. Consider:

The second row is twice the first, so there is only one independent row direction and .

Now consider:

The first two rows are independent, while the third satisfies:

so , not 3. Rank counts independent directions, not nonzero entries or the number of visibly different rows.

For a product:

linear algebra gives:

The inner width is therefore both a parameter budget and an expressivity constraint. It is not merely a compression ratio. Increasing enlarges the family of updates that can be represented, but also increases trainable state and adapter compute.

0x01D Low-Rank Factorization Is A Two-Stage Map

For one input row :

The first factor extracts adapter coordinates from the input. The second maps those coordinates into output-space directions. The update can communicate through only intermediate channels:

x:         [d_in]
    A
adapter u: [r]
    B
delta y:   [d_out]

This geometric reading will later make the forward order, gradient shapes, and target-module choice easier to diagnose.

0x01E SVD Supplies Vocabulary, Not The Vanilla LoRA Algorithm

Singular value decomposition writes a matrix as:

and a truncated SVD keeps the strongest singular directions. By the Eckart-Young-Mirsky theorem, that truncation is a best rank- approximation under the spectral and Frobenius norms. This explains why low-rank structure can capture a large fraction of some matrices’ variation.

Vanilla LoRA does not compute the SVD of an unknown future full-fine-tuning update and then store it. It directly optimizes and through task loss. The empirical claim in the LoRA paper is narrower: useful adaptation updates often exhibit low intrinsic rank or rank-deficient structure, making a low-rank constraint effective in the studied settings. That is motivation, not a theorem that every task or target module has the same optimal rank.

From elementary rank to truncated SVD and direct LoRA optimization

0x01F Target Modules Define What Behavior May Change

A Transformer contains many linear weights:

attention:
    W_Q, W_K, W_V, W_O

MLP:
    gate/up/down projections

other possible targets:
    embeddings, output head, modality projectors

Applying rank 8 to q_proj is a different adaptation hypothesis from applying rank 8 to every attention and MLP projection. The rank is local to a factor pair; the total budget is the sum across target modules. Target choice also affects runtime and cache compatibility: adapting K/V projections can alter cached key/value state, while adapting only later output-side projections has different consequences.

A22 low-rank perturbations of Q, K, and V before attention

Source-local visual: A22 - LoRA, citing Low-Rank Adaptation for Foundation Models: A Comprehensive Review v2. The Chinese annotations state that low-rank factors perturb the attention projections and thereby change token dynamics. The important implementation reading is narrower: adapting W_Q, W_K, or W_V inserts three distinct low-rank branches, and K/V-targeted branches change the state that can be cached.

This picture should not be read as an instruction to target all three projections. The original LoRA paper’s language-model experiments concentrated on attention projections and found target choice to be empirical. A reproducible adapter must therefore record the exact module set, not only its rank.

0x01G Reading Map For The LoRA Sequence

M6.3:
    vanilla LoRA geometry, forward/backward, initialization,
    target modules, optimizer state, and checkpoint contract

M6.4:
    variants that change base precision, rank policy, scaling,
    optimization, initialization, or weight parameterization

M6.5:
    live multi-adapter kernels, residency, batching,
    hotswap, scheduling, and request/KV correctness

0x01H Claim Status And Scope

ClaimStatusEvidenceSafe interpretation
LoRA freezes pretrained weights and learns low-rank update factors.Source-local and externally verifiedA22; LoRA paperThe trainable update is smaller; the base model still exists.
A product of rank- factors has rank at most .Mathematical factLinear algebraLoRA restricts the update subspace, not necessarily the rank of the frozen weight.
Standard zero-, random- initialization begins as a no-op.Source-local and externally verifiedA22; LoRA paperThe model function is preserved at step zero, but the two factors do not receive identical first-step gradients.
Low-rank adaptation is supported by empirical rank-deficiency observations.Externally verified, but not universalLoRA paperThe evidence motivates the constraint; it does not prove every task has the same useful rank.
Target-module choice changes the adaptation hypothesis.Source-local and implementation factA22; PEFT implementationsRank alone does not define the adapter budget or what behavior can change.
Merged LoRA can remove the live branch from inference.Mathematical fact and externally verifiedLoRA paper; PEFT docsMergeability depends on the adapter form and deployment representation; runtime serving is owned by M6.5.

Benchmark gains and memory ratios in papers are source-reported observations under particular models, tasks, optimizers, and hardware. They are not universal LoRA constants.

0x01I Owner And Evidence Contract

The address m6-3-lora-qlora-adapter-serving keeps its historical wording. The route is historical metadata, not the current ownership boundary. The article title and body own vanilla LoRA only.

RelationThis article’s contract
ownsfrozen-base delta vocabulary; vanilla LoRA factors, forward/backward paths, initialization, scaling, dropout, target injection, adapter-only checkpoint, and algebraic merge
consumesordinary linear and block identity from M3; training-state accounting from M4; ordinary K/V lifecycle vocabulary from M5.1
defers to M6.4QLoRA, AdaLoRA, rsLoRA, LoRA+, LoRA-FA, PiSSA, LoftQ, DoRA, and other mechanisms that change the vanilla contract
defers to M6.5multi-adapter residency, segmented kernels, batching, hotswap, admission, eviction, request identity, and serving rollback
defers to M6.7intrinsic-dimension experiments, subspace reconstruction, principal angles, factor geometry, kernel-regime arguments, and theorem scope
defers to M6.9learned routing, shared factors, adapter composition, and continual adaptation
defers to M8generic quantized-number formats, calibration, kernels, and deployment representation

Evidence is also typed:

A22:
    source-local teaching lineage, terminology, and figure context

primary papers:
    claims made by the original method or analysis under a pinned revision

official code:
    behavior visible in a pinned implementation revision

Transformer System derivation:
    tensor, gradient, state, and failure consequences reconstructed here

Loop 1 freezes those sources and all 53 A22 figure dispositions. It does not execute a model, reproduce a benchmark, prove a production serving path, or allow later framework behavior to rewrite the historical paper contract.

0x01J Freeze Identity Before Algebra

The equation W' = W + sBA is not yet an executable contract. It does not say whether the stored base weight uses [d_out,d_in] or [d_in,d_out], which adapter owns the factors, where dropout acts, which dtype performs each multiplication, whether two module paths refer to one object, or whether a merged tensor is the canonical base artifact. Those omissions can produce shape-valid but semantically different implementations.

Loop 2 therefore freezes a small linear-layer teaching fixture before later loops derive or execute it:

State classFrozen identityTeaching valueWhy it is not interchangeable
modelbase model and revisionbase-demo@revision-17shape compatibility does not prove semantic base compatibility
targetcanonical module objectmodule:q_proj:0a path string is a lookup result, not the module object itself
pathcanonical and alias pathstwo paths resolve to module:q_proj:0injecting both paths independently would duplicate one logical target
base parameterparameter and storageparameter:W0, storage:W0a parameter identity and its backing storage identity answer different alias questions
adapteradapter name and artifactlegal, adapter:legal:v1rank, scale, factors, and activation state are adapter-local
factorsstored axesA:[2,6], B:[4,2]swapping names or orientations can remain matrix-compatible under another convention
scalingalpha, rank, rule, value8, 2, alpha_over_r, 4.0the effective scalar cannot reconstruct the original rank policy by itself
dropoutsite and modebranch input only; p=0.25 in trainingdropping the base input changes a different function
dtypestorage, branch compute, accumulation, outputbf16, bf16, fp32, bf16one label such as “mixed precision” does not define cast or reduction behavior
mergecanonical and derived statelive unmerged base plus distinct derived merged storageoutput equivalence does not make base and merged artifacts the same object
LoRA state identity manifest separating module paths, canonical module and base storage, adapter-local factors and scale, runtime temporaries, and a derived merged representation

Stored Axes And Runtime Axes

The fixture follows the PyTorch linear storage convention used by this article:

stored:
    W0:   [d_out, d_in] = [4, 6]
    bias: [d_out]       = [4]
    A:    [r, d_in]     = [2, 6]
    B:    [d_out, r]    = [4, 2]

runtime for T = 3 rows:
    X:         [T, d_in]  = [3, 6]
    X_branch:  [T, d_in]  = [3, 6]
    U:         [T, r]     = [3, 2]
    V:         [T, d_out] = [3, 4]
    Y_base:    [T, d_out] = [3, 4]
    Y:         [T, d_out] = [3, 4]

Stored orientation and runtime multiplication are related but not identical vocabularies. A:[r,d_in] is stored once; the row-major runtime operation consumes A.T:[d_in,r]. A manifest that records only the runtime matmul shape cannot reconstruct the checkpoint orientation.

Module Aliases And Storage Ties Are Different

The fixture intentionally includes two alias cases:

module-object alias:
    two Python paths -> one module object -> one W0 parameter -> one storage

storage tie:
    embedding module and output-head module are different objects
    but their weight parameters refer to one tied storage group

An injector must canonicalize the first case by module-object identity, create one adapter slot, and rebind every alias path to the same wrapped object. It must not infer that all shared storage means one module object. Conversely, wrapping or replacing one member of a tied-weight group must not silently break an intended storage tie. Loop 6 will execute those graph-rewrite cases; Loop 2 only freezes the identities that the rewrite must preserve.

Scaling, Dropout, And Dtype Are State

The fixture records rank=2, alpha=8, and scaling_rule=alpha_over_r, which yields an effective scale of 4.0. Storing only 4.0 loses the rule needed to compare a changed rank or reload a method with another scaling convention.

Dropout belongs to the adapter branch input:

base path:       X -> W0
training branch: dropout(X, mask:forward-unique) -> A -> B -> scale
evaluation:      identity(X) -> A -> B -> scale

The base branch never consumes the adapter dropout mask. The mask is an ephemeral per-forward object, not checkpoint state. Evaluation must replace the stochastic operator with identity before live-branch/merged equivalence is tested.

The dtype contract is similarly directional:

base/factor storage:     bfloat16
branch input compute:    bfloat16
matmul accumulation:     float32 in this teaching contract
branch add/output:       restored to bfloat16
merge construction:     float32 work, then explicit target representation

This is not a claim that every LoRA kernel uses fp32 accumulation. It is a declared fixture policy that prevents later ledgers from treating storage bytes, compute dtype, accumulator dtype, and exported dtype as one number.

Live, Disabled, And Merged Are Representation States

The baseline state is LIVE_UNMERGED: W0 is frozen, adapter legal is active, and the branch executes separately. DISABLED_BASE_ONLY keeps the same base artifact but omits the branch. MERGED_DERIVED constructs a distinct runtime representation from the base digest, adapter digest, scale rule, and target dtype.

The derived merged storage must not be mislabeled as the immutable base artifact. An in-place implementation may temporarily mutate a runtime tensor and later unmerge it, as the pinned loralib and PEFT implementations demonstrate, but reproducible artifact identity still requires the original base revision and an explicit merge record. Numerical equality after merge/unmerge is a later executable claim, not something established by naming a state merged.

The Loop 2 oracle validates this manifest and rejects one-field substitutions for axes, factor ownership, scale, dropout location, dtype role, alias identity, checkpoint metadata, runtime shapes, trainable state, and merge provenance. It derives 20 trainable factor scalars and 40 bf16 adapter bytes for the fixture: 2 * 6 + 4 * 2 = 20, then 20 * 2 bytes = 40 bytes. This is a source-to-state reconciliation, not model execution or a memory benchmark.

0x02 Start From The Dense Fine-Tuning Update

Consider one frozen linear transformation:

with:

x: [d_in]
W: [d_out, d_in]
y: [d_out]

Full fine-tuning learns a new dense matrix:

where both and have shape:

If every entry of is independently trainable, the adaptation surface has:

parameters.

For a square 4096 by 4096 projection:

P_dense_update = 4096 * 4096
               = 16,777,216 parameters

at bf16:
M_dense_update = 16,777,216 * 2 bytes
               = 32 MiB

That 32 MiB is only one weight-shaped copy. Training also needs some combination of gradients, optimizer moments, master weights, activation storage, and distributed-training metadata. The exact multiplier depends on optimizer and precision policy, but the central scaling problem is already visible: dense adaptation state grows like .

0x02A Factor The Update, Not The Frozen Weight

LoRA replaces the arbitrary dense update by:

with:

and:

where:

The parameter count becomes:

For the same 4096 by 4096 projection with :

A: [8, 4096]      -> 32,768 parameters
B: [4096, 8]      -> 32,768 parameters

P_LoRA             -> 65,536 parameters
bf16 adapter bytes -> 128 KiB

The ratio for this matrix is:

For a square matrix:

With and :

or about 0.39 percent of the dense update parameter count.

This is the parameter-efficiency argument. It does not say the base matrix is compressed, and it does not say the task update is exactly representable at rank 8.

Original LoRA reparameterization with a frozen base and trainable low-rank branch

Source: LoRA v2, Figure 1. The original diagram freezes (W), initializes one low-rank factor to zero, and trains only the two low-rank factors. Matrix names and multiplication orientation vary across implementations, so the surrounding tensor equations remain the authoritative convention for this article.

0x02B What Rank Constrains

For compatible matrices:

Therefore LoRA restricts to rank at most .

There are two useful geometric readings.

First, maps the input row into adapter coordinates:

Second, maps those coordinates into output-space directions:

So:

A:
    decides which combinations of input channels matter

B:
    decides which output directions those combinations can change

r:
    bounds the width of the communication channel between them

The columns of span at most output directions. The rows of define at most input-sensitive coordinates. This is stronger and more useful than saying only that the adapter is “small.”

0x02C SVD Is Motivation, Not The Training Algorithm

Any matrix has a singular value decomposition:

The best rank- approximation under the Frobenius norm keeps the leading singular triplets:

One possible factorization is:

This explains why two factors can represent a rank- matrix. Vanilla LoRA does not normally compute the dense task update first and then perform this SVD. It learns and directly by gradient descent while remains frozen.

That distinction matters:

truncated SVD:
    starts from a known dense matrix and approximates it

LoRA training:
    searches directly inside a low-rank parameterization
    without ever materializing the full dense Delta W

It is also incorrect to infer that the pretrained must itself be low rank. The hypothesis concerns useful downstream updates, not the full information content of the frozen base weight.

0x03 Tensor-Level Forward Pass

Use a batch of token rows:

X: [B, S, d_in]

Most linear kernels treat the leading dimensions as a collection of rows. Flatten:

T = B * S
X2: [T, d_in]

The base projection is:

with:

W:      [d_out, d_in]
W.T:    [d_in, d_out]
Y_base: [T, d_out]

The LoRA branch applies two smaller projections:

with:

A:   [r, d_in]
A.T: [d_in, r]
U:   [T, r]

B:   [d_out, r]
B.T: [r, d_out]
V:   [T, d_out]

The final output is:

where vanilla LoRA commonly uses:

Renderer-safe form:

U = X2 @ A.T
V = U @ B.T
Y = X2 @ W.T + (alpha / r) * V

The tensor path is:

[T, d_in]
    |
    | base: @ W.T
    v
[T, d_out] ----------------------+
                                     +--> add --> [T, d_out]
[T, d_in]                            |
    |                                |
    | adapter: @ A.T                 |
    v                                |
[T, r]                               |
    |                                |
    | @ B.T                          |
    v                                |
[T, d_out] -- scale alpha/r --------+

0x03A Why The Order Matters

The efficient branch computes:

It does not normally materialize:

during every training forward pass.

The two costs are different.

Live low-rank branch:

Materialize and use a dense update:

Materializing can be useful for a one-time merge after training, but doing it each step would throw away the main computational structure of the parameterization.

0x03B A Shape-Checked Module

class LoRALinear(nn.Module):
    def __init__(self, base, rank, alpha, dropout_p=0.0):
        super().__init__()
        self.base = base
        self.rank = rank
        self.scale = alpha / rank
        self.dropout = nn.Dropout(dropout_p)

        d_out, d_in = base.weight.shape
        self.A = nn.Parameter(torch.empty(rank, d_in))
        self.B = nn.Parameter(torch.zeros(d_out, rank))

        nn.init.kaiming_uniform_(self.A, a=math.sqrt(5))
        self.base.weight.requires_grad_(False)
        if self.base.bias is not None:
            self.base.bias.requires_grad_(False)

    def forward(self, x):
        assert x.shape[-1] == self.A.shape[1]
        base_y = self.base(x)
        low_rank = F.linear(self.dropout(x), self.A)
        delta_y = F.linear(low_rank, self.B)
        assert delta_y.shape == base_y.shape
        return base_y + self.scale * delta_y

The implementation uses the same row-major linear convention as PyTorch:

F.linear(x, weight):
    output = x @ weight.T

Confusing mathematical notation with framework row tensors is a common source of transposition bugs.

0x03C Dtype And Accumulation

The base weight, adapter factors, input activation, and accumulation dtype do not have to match.

A runtime may hold:

base storage:
    quantized or low precision

adapter storage:
    bf16 or fp16

matmul accumulation:
    fp32 or hardware-specific mixed precision

output:
    model activation dtype

The mathematically simple addition still needs a defined cast policy. Silent promotion can increase memory; premature down-casting can reduce numerical accuracy; unsupported mixed dtypes can force a slower kernel path. M6.4 owns quantized-base variants, but the basic tensor contract begins here.

0x03D Derive The Row Kernel From The Column Equation

The compact paper equation usually treats one activation as a column vector. With the Loop 2 storage convention,

where:

x:   [d_in]
A:   [r, d_in]
Ax:  [r]
B:   [d_out, r]
BAx: [d_out]
W0x: [d_out]
b:   [d_out]

Framework linear kernels usually receive a row batch instead. Transpose one column result:

Stacking row vectors into gives:

This derivation explains every transpose in the implementation. F.linear(X, A) consumes stored A:[r,d_in] and computes X @ A.T; the framework is not using a different factorization.

The rank restriction also follows from an image-space argument, not merely a memorized inequality. Every column of is applied to one column of , so every output column lies in the column space of :

Therefore:

Applying the same argument to also gives . Conversely, if a dense update has rank , a thin rank factorization with C:[d_out,q] and R:[q,d_in] can be zero-padded to B:[d_out,r] and A:[r,d_in]. Thus rank at most is both necessary and sufficient for exact representation by this factor shape. It does not say gradient descent will find that factorization or that rank is sufficient for a task.

Parameter counting now follows from stored entries rather than from the dense product:

so:

No update tensor is trainable or checkpointed in the live representation. A later merged weight is a derived representation, not an additional trainable parameter in this accounting.

0x03E Separate Recurring Work From One-Time Merge Work

Using one multiply and one add per inner-product term, the two live branch matmuls require:

and:

Therefore:

Scaling and adding the branch are separate elementwise work:

The dense base projection remains:

The logical live schedule also produces two branch intermediates:

An implementation may fuse scale/add or avoid materializing all of , but that is a physical-kernel optimization over the same logical operator. It must not be used to erase the rank-width intermediate from the derivation.

Merging has a different amortization boundary. Constructing once costs approximately:

followed by scalar additions into the chosen merged representation. Paying this on every training forward would replace a rank-structured recurring path with dense update construction. Paying it once for a compatible deployment can remove the live branch from each later forward.

For the Loop 2 teaching fixture:

T=3, d_in=6, d_out=4, r=2

stored adapter parameters:
    2*6 + 4*2 = 20

live branch matmul FLOPs:
    2*3*(6*2 + 2*4) = 120

dense update-use FLOPs:
    2*3*6*4 = 144

one-time B@A construction FLOPs:
    2*4*2*6 = 96

logical branch intermediates:
    U = 3*2 = 6 elements
    V = 3*4 = 12 elements

The tiny fixture is chosen for exact inspection, not to demonstrate speed. Its rank is not very small relative to the matrix dimensions. For the earlier square d=4096,r=8 example, the branch-to-dense-update matmul ratio is before elementwise work and kernel effects.

0x03F Prove Live-To-Merged Equivalence, Then State Its Limits

Define the derived merged weight in exact arithmetic:

Its row-batch forward is:

The third line uses transpose distributivity, , and matrix associativity. The bias is unchanged because vanilla LoRA in this fixture adapts only the weight.

The Loop 3 exact-integer oracle uses the frozen Loop 2 shapes with scale=4 and obtains:

U = X @ A.T
  = [[0, 8], [2, 2], [7, -3]]

V = U @ B.T
  = [[0, 8, -8, 8], [2, 2, 0, -2], [7, -3, 10, -17]]

Y_live = Y_merged
       = [[4, 34, -26, 30],
          [14, 7, 2, 0],
          [24, -5, 45, -75]]

That equality is an algebraic contract under the fixture. A deployment claim needs more conditions:

ConditionWhy it is requiredWhat breaks it
linear additive branchassociativity can fold the update into one weightnonlinear bottleneck or input-dependent factors
evaluation-mode dropoutthe branch must be deterministicsampled training dropout mask
fixed adapter and scaleone derived weight represents one fixed updateper-request adapter selection or token-dependent routing
identical base, target, and biasthe merged tensor must modify the same operatorwrong base revision, alias, target, or bias policy
explicit merge dtype, addition, cast, and storage must be definedquantization, overflow, or implicit promotion
preserved artifact lineagerollback needs the immutable base plus adapter metadatatreating the merged copy as the canonical base

In finite precision, different accumulation orders and casts mean the live and merged paths need not be bitwise identical even when they implement the same real-valued expression. The later executable oracle must therefore declare tolerances and representations; it may not replace this algebraic proof with an unqualified allclose call.

0x04 Backward Pass: Which Factor Learns First?

Let:

and:

Because is frozen, the trainable gradients are for and .

For :

Shape check:

G.T: [d_out, T]
U:   [T, r]
dB:  [d_out, r]

For the intermediate :

Shape check:

G:   [T, d_out]
B:   [d_out, r]
dU:  [T, r]

For :

Shape check:

dU.T: [r, T]
X2:   [T, d_in]
dA:   [r, d_in]

The adapter contribution to the input gradient is:

The base branch also contributes:

Freezing means not storing or applying . It does not stop gradients from flowing through the base projection into earlier activations.

0x04A Derive Every Gradient From One Differential

The formulas above are easy to memorize and easy to transpose incorrectly. Derive them once from the row-batch graph instead.

Let denote the input used by the adapter branch. In evaluation mode, or when branch dropout is disabled, . Define:

For a scalar loss, the upstream gradient is . The differential identity:

turns each local differential into a gradient with the same storage axes as its tensor.

Holding fixed while varying gives:

and therefore:

Matching this to yields:

Holding fixed while varying gives:

so cyclic permutation inside the trace yields:

Now vary through :

Substituting the already-derived gives:

hence:

The branch-input adjoint is:

while the frozen base path still contributes:

The Loop 3 matrices make this trace executable. Freeze:

The exact-integer oracle then derives:

U =
[[0, 8],
 [2, 2],
 [7, -3]]

dB = s * G.T @ U =
[[56, 8],
 [16, -16],
 [20, 44],
 [-48, 32]]

dU = s * G @ B =
[[12, -12],
 [-12, 16],
 [28, -12]]

dA = dU.T @ X =
[[68, 12, -52, 60, 76, -68],
 [-36, -8, 44, -8, -64, 44]]

dX_adapter = dU @ A =
[[12, -24, -24, 36, -12, 12],
 [-12, 32, 28, -40, 16, -12],
 [28, -24, -40, 68, -12, 28]]

dX_base = G @ W =
[[5, -3, 3, -3, 1, 3],
 [-3, 5, -1, 4, -1, 0],
 [6, -5, 2, -2, 5, 1]]

With retained from the forward pass, the four adapter-backward matrix multiplications cost:

For , , , and , that is matrix-multiplication FLOPs. Recomputing adds FLOPs. The base input adjoint still costs FLOPs, but a frozen does not require materializing the parameter gradient . These are local operator ledgers, not an end-to-end training-speed claim.

0x04B Branch Dropout, Frozen Bias, And Gradient Ownership

Training-mode LoRA commonly drops only the branch input:

where is the sampled Bernoulli mask under inverted-dropout semantics. The base projection still consumes the undropped .

The total input gradient is therefore:

For the elementwise mask above:

This establishes three distinct contracts:

StateMathematical derivativeStored trainable gradientOptimizer update
frozen base weight existsomitted when is frozennone
frozen base bias existsomitted when is frozennone
adapter factors derived aboverequiredyes
branch input activation adjoint, not a parameter gradientpropagated through dropout

For the exact fixture:

Reporting this local derivative does not make the bias trainable. Likewise, evaluation-mode merge equivalence does not merge a sampled training dropout mask into one fixed weight.

0x04C Zero- Initialization Is Asymmetric

The usual initialization is:

A:
    random

B:
    zero

At step zero:

so:

The pretrained function is preserved exactly up to dtype and implementation behavior.

But inspect the first gradients.

Since :

Meanwhile:

which is generally nonzero because is random.

Therefore:

first optimizer step:
    B can move
    A receives zero gradient

later steps:
    once B is nonzero, both factors can learn

This is an implementation-level consequence of the no-op initialization. It is also one motivation for initialization variants such as PiSSA and gradient-aligned methods, which M6.4 compares directly.

0x04D Four Initialization Regimes Have Different First Gradients

A zero initial update is not enough to characterize learnability. The factor that is zero determines which factor can move first.

Initial Initial at step zeroBranch First-order consequence
random/nonzerozerozerozerogenerally nonzerozerostandard LoRA: moves first
zerorandom/nonzerozerogenerally nonzerozerozeroreversed asymmetry: moves first
zerozerozerozerozerozerodead first-order start
nonzerononzeronot guaranteed zerogenerally nonzerogenerally nonzerogenerally nonzeropretrained function is not guaranteed to be preserved

The final row is deliberately qualified. Two nonzero factors can multiply to zero in a degenerate construction, but nonzero/nonzero initialization does not provide the structural no-op guarantee supplied by one zero factor.

Four LoRA initialization regimes showing whether the initial update, factor gradients, and adapter input gradient are zero, including the dead both-zero case

For the frozen Loop 3 tensors and the same , the oracle does not merely label these cases. It substitutes zero matrices of the exact stored shapes and proves:

random A, zero B:
    dA = 0
    dB = [[56, 8], [16, -16], [20, 44], [-48, 32]]

zero A, random B:
    dA = [[68, 12, -52, 60, 76, -68],
          [-36, -8, 44, -8, -64, 44]]
    dB = 0

zero A, zero B:
    dA = 0
    dB = 0

0x04E The First Plain-SGD Update Carries The Scale Twice

The scale appears once in the factor gradient and once again when the changed factor is mapped back into .

Under the standard , initialization and plain SGD with learning rate :

because , while:

The first nonzero dense update represented by the factors is therefore:

For the exact fixture with , the coefficient tensors are:

B1 =
[[-56, -8],
 [-16, 16],
 [-20, -44],
 [48, -32]]

DeltaW1 = s * B1 @ A0 =
[[-224, -64, 192, -416, -32, -224],
 [-64, 128, 128, -192, 64, -64],
 [-80, -352, -96, 16, -176, -80],
 [192, -256, -320, 512, -128, 192]]

This statement is narrowly scoped to the first plain-SGD step with fixed , zero , unchanged , and no clipping, regularization, momentum, adaptive preconditioning, loss scaling, or mixed-precision overflow. It is not a general claim that optimization behavior scales quadratically with . Loop 5 owns that sensitivity analysis.

0x04F Gradient Pseudocode

def lora_backward_shapes(X2, X_branch, W, A, B, grad_y, scale, dropout_backward):
    # X2:     [T, d_in]
    # X_branch is dropout(X2) for the adapter branch only.
    # W:      [d_out, d_in], frozen but still used for grad_X_base.
    # A:      [r, d_in]
    # B:      [d_out, r]
    # grad_y: [T, d_out]

    U = X_branch @ A.T
    grad_B = scale * grad_y.T @ U

    grad_U = scale * grad_y @ B
    grad_A = grad_U.T @ X_branch

    grad_X_branch = grad_U @ A
    grad_X_adapter = dropout_backward(grad_X_branch)
    grad_X_base = grad_y @ W
    grad_X = grad_X_base + grad_X_adapter

    local_grad_bias = grad_y.sum(axis=0)  # derivative exists even if bias is frozen

    assert grad_B.shape == B.shape
    assert grad_A.shape == A.shape
    assert grad_X_adapter.shape == X2.shape
    assert grad_X.shape == X2.shape
    return grad_A, grad_B, grad_X, local_grad_bias

The pseudocode exposes the local derivatives but intentionally does not allocate grad_W or an optimizer-visible base-bias gradient. Loss scaling, distributed reductions, saved-tensor policy, and fused-kernel details remain separate implementation decisions.

0x05 Rank, Scale, Dropout, Precision, And Conditioning Need Separate Experiments

The equation

contains several controls that are often collapsed into one phrase such as “LoRA rank 16.” That phrase does not determine the executed operator. Changing changes factor shapes, parameter state, logical work, and the maximum rank of . Under vanilla scaling, it can also change . Changing with fixed factors changes the forward branch and all branch adjoints. Dropout changes the training operator but not the evaluation operator. Dtype and cast placement can make two real-arithmetic-equivalent paths numerically different. Finally, many pairs represent the same dense update while having very different factor magnitudes and gradients.

This section therefore uses one-axis contracts rather than a single benchmark table:

rank sweep:
    state which factors are truncated, padded, or retrained
    state whether alpha or effective scale is held fixed

alpha sweep:
    hold rank, factors, input, upstream gradient, and optimizer rule fixed

dropout sweep:
    hold the deterministic factors fixed and identify the sampled mask

precision sweep:
    hold the real-valued graph fixed and move only declared cast boundaries

conditioning sweep:
    preserve BA exactly while changing the factor coordinates

The checked-in Loop 5 oracle applies those controls to the exact Loop 3/4 fixture. It is a local algebra and representation experiment, not a training-quality result.

Five controlled LoRA sensitivity experiments separating rank, alpha, dropout, precision cast boundaries, and equivalent-factor conditioning

0x05A Rank Changes Four Ledgers, Not One Number

For one adapted matrix,

and the two live-branch matrix multiplications require

logical FLOPs under the convention that one multiply-add counts as two FLOPs. The representation ceiling follows from

These are three different consequences of : stored adapter state, recurring branch work, and an upper bound on dense-update rank. A fourth consequence appears through .

The exact fixture has , , and :

Factor constructionParamsForward matmul FLOPsActual
keep only factor coordinate 114410601
Loop 3 baseline284201202
append one zero row/column, hold 3124301802

The padded rank-3 factors represent exactly the same as the rank-2 factors. They still allocate 50% more adapter parameters and logical branch matmul work. The unused coordinate does not become useful merely because the declared rank increased. Conversely, the rank-1 truncation changes the represented dense update; it is not a cost-only perturbation.

Now hold while padding from to . The factors still represent the same unscaled , but vanilla scaling changes from to , so the branch output changes. That experiment varies rank and effective scale. Holding instead requires changing from 8 to 12. A fair rank study must state which of those contracts it uses and whether the added directions are initialized, inherited, or retrained.

This also explains why rank-stabilized scaling is not a footnote inside vanilla LoRA. It changes the rank-to-scale rule itself. M6.4 owns rsLoRA, adaptive rank allocation, and methods that change the baseline training policy.

0x05B Alpha Scales The Local Graph, But Not All Training Dynamics

Hold , , , , and fixed. Define the unscaled branch

Then

and the Loop 4 adjoints become

For that frozen local graph, multiplying by multiplies the branch output and those three adjoints by ; their squared Frobenius norms multiply by . LoRA v2 defines the vanilla scale and gives a narrower optimizer-conditioned motivation for it. The fixture below isolates the algebra before any optimizer claim is made:

branch checksum
175861,929959
2142,3447,7163,836
4289,37630,86415,344

This is not a universal loss-curve law. In an actual optimization run, changing changes logits and therefore usually changes ; clipping, weight decay, loss scaling, momentum, adaptive moments, and finite precision can all alter the update. The result from Loop 4 remains narrower still: with random , zero , fixed , and one plain-SGD step,

The extra comes from converting the factor update back into the scaled dense delta. It does not justify treating as an optimizer-independent branch-strength knob.

0x05C Inverted Dropout Preserves The Mean, Not The Realization Or Variance

Let independently and use inverted dropout on one branch-input row :

Because ,

With

linearity gives

That expectation does not make one sampled training forward equal to evaluation. The component variance is

and independence yields the output covariance

For the fixture’s first row, , , and all masks, exact enumeration gives

and

The off-diagonal terms appear because the same dropped input coordinates feed multiple output coordinates. At , the stochastic branch reduces to the deterministic branch. As , the inverted-dropout multiplier and variance diverge; that limit is not a valid way to disable the adapter.

The base path still consumes the undropped :

The sampled mask is ephemeral forward state needed by backward. It is not adapter checkpoint state, and it cannot be folded into one deterministic merged weight. Evaluation disables dropout before live/merged equivalence is tested.

0x05D Equivalent Dense Updates Can Have Unequal Factor Conditioning

The factorization of one dense delta is not unique. For any invertible

define

Then

The forward function and merged dense update are unchanged in exact arithmetic, but the factor-coordinate gradients are not. From the same ,

The fixture applies the integer shear

Every row below has the same , the same branch output, and the same merge result:

014930,8649,376
1152178,67211,728
4102129479,76058,144

Thus same BA does not mean same optimizer trajectory. A shared scalar learning rate acts on factor coordinates, not directly on , and finite-range dtypes see the factor magnitudes before the product cancels them. This is a parameterization symmetry in the forward function, not an optimization symmetry. M6.7 owns the broader factor-geometry and training-dynamics theory; this section records the implementation consequence needed by vanilla LoRA.

0x05E Precision Is A Cast Schedule, Not One Dtype Label

A reproducible LoRA configuration must distinguish at least:

RoleObject or operationWhy it matters
storage, , values can be rounded, underflowed, or overflowed before execution
branch inputcast of after dropoutcontrols values entering the low-rank path
matmul accumulation and reductionscontrols cancellation and reduction error
scale/addmultiplication by and addition to the base outputchanging order can change rounding
merge construction and addition into a dense materialization has its own compute and storage dtypes
outputcast after base-plus-branch adda small branch can disappear next to a large base value

One binary16 teaching probe exposes why cast placement is part of the operator. Let

The real branch value is

the smallest positive binary16 subnormal. If is first rounded to binary16, it becomes zero, and multiplying the rounded value by 2 cannot recover it. If the product and scale remain in a wider compute dtype and only is cast, survives. Both paths may be described casually as “fp16 LoRA,” but they are not the same cast schedule.

Even a surviving branch is lost when added to a binary16 base output near 1 and then rounded, because it is far below the local spacing around 1. The correct lesson is not that one fixed dtype always fails. It is that storage, accumulation, scale/add, merge, and output casts must be specified separately, and live/merged parity needs declared tolerances and reference precision. Generic quantized-number theory belongs to M8.1; quantized-base LoRA variants belong to M6.4.

0x05F Sensitivity Pseudocode Keeps The Axes Separate

def vanilla_lora_sensitivity_case(X, A, B, G, alpha, rank, dropout, casts):
    assert A.shape[0] == rank
    assert B.shape[1] == rank
    scale = alpha / rank

    X_branch, mask = dropout.forward(X)       # base X is unchanged
    U = casts.matmul_accum(X_branch, A.T)
    V = casts.matmul_accum(U, B.T)
    delta_y = casts.scale_and_output(V, scale)

    grad_B = scale * G.T @ U
    grad_U = scale * G @ B
    grad_A = grad_U.T @ X_branch
    grad_X_branch = grad_U @ A
    grad_X_adapter = dropout.backward(grad_X_branch, mask)

    return {
        "delta_y": delta_y,
        "grad_A": grad_A,
        "grad_B": grad_B,
        "grad_X_adapter": grad_X_adapter,
        "state_elements": rank * (A.shape[1] + B.shape[0]),
        "logical_forward_flops": 2 * X.shape[0] * rank * (A.shape[1] + B.shape[0]),
    }

The function returns mechanism observables rather than a quality score. A rank experiment must construct new factors explicitly; an alpha experiment reuses the same factors; a dropout experiment enumerates or records masks; a precision experiment changes casts; and a conditioning experiment transforms while asserting .

0x05G Merge Is An Algebraic Equivalence With Conditions

Live LoRA branch and merged-weight execution

For a fixed adapter and compatible real-arithmetic graph:

because:

That is why a standard LoRA branch can be merged.

The equivalence assumes:

the adapter is input-independent
the same scale applies to every row
dropout is disabled
the live and merged paths declare compatible cast and accumulation schedules
no request needs a different adapter for the same base weight

The algebra proves equality over real arithmetic. It does not prove bitwise equality after different low-precision operation orders. Conditional adapters, quantized merge paths, adapter composition, and multi-tenant serving introduce additional constraints. M6.5 owns those runtime cases.

0x06 Target Modules Are A Graph-Rewrite Contract

Applying rank 8 everywhere is not the same experiment as applying rank 8 to two projections. But even a list such as q_proj,v_proj is still incomplete. A production injector must answer four different questions:

selection identity:
    which path strings match the target rule?

object identity:
    do two matched paths resolve to the same module object?

packed-slice identity:
    does one module store Q, K, and V as row slices of one matrix?

parameter/storage identity:
    do distinct modules or Parameters share one underlying storage?

Those identities can coincide, but they are not interchangeable. A path is a lookup route. A module object is one callable node. A Parameter is an autograd leaf. A storage is the memory backing one or more tensor views. A packed slice is a semantic interval inside one parameter. The injector needs all of them before it mutates the graph.

LoRA injection graph separating exact target paths, a shared packed QKV module object, unequal Q K V row slices, and distinct embedding and output-head modules tied to one base storage

For a decoder block, possible target surfaces include:

attention:
    q_proj, k_proj, v_proj, o_proj
    or semantic slices inside one packed qkv_proj

MLP:
    gate_proj, up_proj, down_proj
    or slices inside a fused gate/up projection

other:
    embedding table, output head, modality projector
    selected expert matrices

Each target changes both the parameter ledger and what transformations can move.

Target surfaceDirectly changesTypical consequence
Q projectionquery subspace and attention scoreschanges which cached keys are selected
K projectionkey representation written during prefill/decodechanges future attention compatibility
V projectioncontent retrieved after attention weightingchanges routed information without directly changing score geometry
O projectionrecombination of head outputschanges how attention output enters residual stream
MLP gate/upnonlinear feature expansion and gatingcan add much more adapter state than Q/V-only
MLP downprojection back to residual widthchanges how expanded features update residual stream
embedding/headtoken input or output interfacecan be vocabulary-sized and may need tying rules
MoE expertsexpert-local transformationsadds routing and placement questions from M6.2

This is why “LoRA rank 8” is not a complete configuration. The target matcher, module graph, slice boundaries, alias groups, tie groups, factor orientation, and merge policy are part of the adaptation hypothesis.

0x06A A Path Match Is Not Yet An Injection Plan

Suppose the graph snapshot contains these occurrences:

blocks.0.self_attn.qkv_proj   -> module:qkv:0
blocks.0.cross_attn.qkv_proj  -> module:qkv:0
blocks.0.self_attn.not_qkv_proj -> module:near-match:0
embed_tokens                 -> module:embedding:0 -> storage:vocab:0
lm_head                      -> module:lm-head:0   -> storage:vocab:0

The first two paths are aliases of one module object. The near-match is a different object whose name merely contains qkv_proj. The final two paths are distinct module objects and distinct parameter identities backed by one tied storage group.

An exact-path target rule for the two QKV paths and lm_head must therefore produce:

matched path occurrences:       3
unique target module objects:   2
wrappers to construct:          2
near-match targets:             0

Counting path matches as unique modules would wrap module:qkv:0 twice. Matching by substring would also capture not_qkv_proj. Deduplicating everything by storage would incorrectly collapse the embedding and output-head roles. The Loop 6 fixture makes each mistake observable.

Frameworks expose different match surfaces. The pinned PEFT v0.19.1 injection source snapshots named_modules(), checks target rules, resolves parent/target/name, and performs compatibility checks before replacement. That is framework evidence for one version, not proof that every injector discovers aliases or tied roles identically. Loop 8 will execute the pinned framework surface; this loop defines the graph invariant independently of it.

0x06B Packed QKV Requires A Slice Manifest

A packed projection stores several semantic operators on one output axis. For GQA-like widths, write:

The query width can differ from the key/value widths:

Let and be fixed binary row-embedding matrices that place a slice-local matrix back into the packed output axis. Adapting Q and V, but not K, gives:

For a row batch :

The K interval is zero by construction. Its rows are unchanged because neither placement matrix writes there, not because the K output happened to be numerically small.

The parameter ledger is slice-local:

The dense packed update obeys:

Separate Q and V factors are therefore not merely an implementation of one monolithic rank- packed branch. They impose separate slice supports, ranks, and scales. A monolithic factor can couple its rank coordinates across Q/K/V rows; slice-local factors cannot write outside their declared intervals.

The exact fixture uses:

T=2, d_in=4
d_q=4, d_k=2, d_v=2
Q: r_q=2, s_q=2
V: r_v=1, s_v=3

P_Q = 2*(4+4) = 16
P_V = 1*(4+2) =  6
P_packed adapter = 22 parameters

Its branch output is:

Delta Y_qkv =
[[ 8, -6, 2, -20, 0, 0,  30, -15],
 [-4, 12, 8,  28, 0, 0, -24,  12]]
                       ^^^^^^
                       K slice remains zero

Adding that branch to the frozen packed projection gives exactly the same output as forming the selectively row-padded dense update and using a derived merged weight. The dense update has actual rank 3 in this fixture, equal to ; the inequality does not promise equality for arbitrary factors.

The pinned loralib.MergedLinear is useful implementation evidence: it builds an enabled-row mask, computes grouped low-rank updates, zero-pads disabled rows, and uses the same selective update for live and merged execution. Its contract divides out_features into equal chunks according to enable_lora. That is appropriate for its declared layout; it is not a general unequal-width GQA slice parser. Architecture-aware code must carry explicit start/stop intervals or use separate projections.

0x06C Aliases Must Share One Wrapper And One Adapter Slot

Let and be two graph paths resolving to the same module object:

The rewrite must construct one wrapped object and rebind both parents:

Wrapping each path independently creates two adapter parameter sets for one logical base operator and may destroy the alias relation. In the exact fixture, the packed object needs 22 adapter parameters. A path-wise injector would count it twice:

correct unique state:
    packed object once + lm_head once
    22 + 10 = 32 parameters

naive path state:
    packed alias path 1 + packed alias path 2 + lm_head
    22 + 22 + 10 = 54 parameters

This is not only an accounting error. If the two wrappers receive different optimizer states or adapter names, the originally tied call sites can execute different functions.

0x06D Tied Storage Makes Merge A Role-Sensitive Operation

Weight tying is a different graph relation. Let the input embedding and output head use one base matrix :

where selects vocabulary rows and contains hidden states. A live head-only LoRA branch intends:

If the head adapter is merged in place into the shared canonical storage, both roles change:

That is not the same graph. The input embedding receives an update even though it was not targeted. A safe injector therefore preserves the tied base storage during live execution and rejects an automatic canonical in-place merge when only one role in the tie group is targeted.

A deployment may instead create an explicit role-local derived head tensor, deliberately adapt every compatible role in the tie group, or keep the live branch. Each choice changes artifact identity or model semantics and needs evaluation. M2.2 owns the full embedding/head tying derivation; M6.3 owns the adapter injection and merge consequence.

For the Loop 6 head fixture:

V=6, d=4, r=1, s=2
head adapter state = 1*(4+6) = 10 parameters

Z_base =
[[1, 0, -1,  2, 1, 1],
 [0, 2,  1, -1, 2, 0]]

Delta Z_head =
[[ 8, 0, -8, 16,  8, -16],
 [-4, 0,  4, -8, -4,   8]]

The live branch changes only the output role and leaves storage:vocab:0 tied. The oracle rejects canonical in-place merge because embed_tokens is an untargeted consumer of that storage.

0x06E Injection Should Be A Preflighted Transaction

Graph mutation should happen after discovery and validation, not while target matching is still incomplete:

def plan_lora_injection(model, target_spec, adapter_name):
    # Snapshot occurrences before replacement changes traversal.
    occurrences = snapshot_paths_objects_parameters_and_storage(model)

    matched = exact_match(occurrences, target_spec.paths)
    require_all_requested_paths_matched(matched, target_spec)

    object_groups = group_by_module_object_identity(matched)
    tie_groups = discover_parameter_and_storage_ties(occurrences)

    plans = []
    for object_id, aliases in object_groups.items():
        base = aliases[0].module
        slices = resolve_semantic_slices(base, target_spec.slice_manifest)
        validate_nonoverlap_full_bounds_and_factor_shapes(base, slices)
        validate_tied_role_merge_policy(base, tie_groups, target_spec)
        plans.append(build_wrapper_plan(object_id, aliases, slices, adapter_name))

    audit_unique_parameter_and_checkpoint_ledgers(plans)
    return plans


def commit_lora_injection(model, plans):
    wrappers = {}
    for plan in plans:
        wrapper = wrappers.setdefault(plan.object_id, build_wrapper_once(plan))
        for parent, child_name in plan.parent_slots:
            setattr(parent, child_name, wrapper)

    freeze_declared_base_parameters(model)
    register_adapter_namespace(model, plans)
    assert_all_aliases_rebound_to_one_wrapper(model, plans)
    assert_tied_base_storage_policy(model, plans)
    assert_trainable_parameters_equal_manifest(model, plans)

The split between plan and commit matters. If an unmatched path, overlapping slice, incompatible rank, or unsafe tie is discovered after half the graph has already been replaced, rollback itself becomes part of correctness. A preflight plan can fail before mutation.

The checked-in standard-library oracle does not import PyTorch or PEFT. It executes the immutable graph and matrix fixture, verifies 69 one-field counterexamples, and proves only the declared teaching contract. Loop 8 will trace actual pinned framework symbols; Loop 10 will integrate forward, gradient, injection, checkpoint, and merge oracles.

0x06F Parameter Ledger Across A Block

Assume:

d_model = 4096
d_ff = 14336
r = 8
layers = 32
adapter dtype = bf16

For one square attention projection:

P = 8 * (4096 + 4096)
  = 65,536

For Q and V in every layer:

P_qv = 2 * 32 * 65,536
     = 4,194,304 parameters

M_qv_bf16 = 4,194,304 * 2 bytes
           = 8 MiB

Now include the three MLP projections.

For gate or up:

P_gate = 8 * (4096 + 14336)
       = 147,456

The down projection has reversed input/output dimensions but the same count:

P_down = 8 * (14336 + 4096)
       = 147,456

Three MLP adapters per layer:

P_mlp_per_layer = 3 * 147,456
                = 442,368

P_mlp_32_layers = 14,155,776
M_mlp_bf16      = 27 MiB

The MLP adaptation surface alone is more than three times the Q/V-only adapter state in this example. The exact model may use fused projections, GQA-specific shapes, tied weights, or different MLP widths; the ledger must follow the actual checkpoint and deduplicate module-object aliases.

0x06G Attention Cache Consequence

Changing Q and changing K/V are not runtime-equivalent.

For a request whose prefix K/V was computed without an adapter:

Q-only adaptation:
    existing base K/V may still describe the same K/V projection
    queries change against that cache

K/V adaptation:
    cached rows should reflect adapted K/V projections
    reusing base-only K/V can be semantically wrong

The exact cache-reuse contract depends on when the adapter becomes active and which projections it changes. M5 owns KV state; M6.5 owns adapter-aware serving. M6.3 establishes why target-module metadata is part of that contract.

0x06H Injection Failure Lab

FailureShape checks pass?Semantic breakRequired check
substring matcher captures not_qkv_projoftenunintended operator receives trainable stateexact or explicitly declared regex match set
two paths wrap one module object twiceoftenduplicate adapters and broken alias behaviorgroup by object identity; rebind every parent slot
equal-chunk packed mask used for unequal GQA widthspossiblyQ/K/V rows are assigned to the wrong semantic sliceexplicit start/stop slice manifest
Q/V slice update writes K rowsyessupposedly untargeted K projection changeszero-delta assertion on the K interval
path count used as parameter countyesstate, optimizer, and checkpoint ledgers overcount aliasescount unique adapter parameter identities
head-only delta merged into tied vocabulary storageyesinput embeddings change unexpectedlytie-group-aware merge admission
graph mutated before all targets validateyesfailure leaves a partially injected modelpreflight plan, then commit transaction
wrapper created with wrong storage orientationsometimestransposed or misplaced dense updateorientation and factor-shape contract

0x07 Training State, Checkpoint, Resume, And Merge Are Different Lifecycles

Freezing the base changes parameter-gradient ownership. It does not sever the computation graph. Activations and input gradients are still needed wherever a trainable adapter earlier in the graph depends on them, and the frozen weights remain resident for the forward and backward operators. A22 makes this distinction in its resource discussion; the retained diagram below comes from that explanation and ultimately from the Ladder Side-Tuning paper, whose comparison motivates a different side-network design precisely because ordinary PEFT still backpropagates through the backbone path.

Frozen-backbone adapter and prompt-tuning forward/backward paths retained from A22

The more subtle mistake happens after the backward pass: one training run can produce three artifacts with different purposes and state sets.

LoRA lifecycle separating live unmerged training state, a portable adapter, an exact-resume bundle, and a derived merged release with rollback lineage
portable adapter:
    enough to reconstruct and execute the adapted function

exact-resume bundle:
    enough to continue the same optimization and data trajectory

merged release:
    a derived deployment representation with evaluation and rollback lineage

They overlap, but none is a substitute for the other two.

0x07A Derive The State Ledger By Lifetime And Owner

Let be the set of unique trainable adapter parameter identities after the alias reconciliation in 0x06, and let

Counting matched paths would count the same adapter twice when two paths alias one wrapped object. Training state follows parameter identity, not path occurrence.

For Adam-like optimization, separate persistent state from the gradient buffer that is live only between backward and zeroing:

and therefore

This is not the whole training peak. It excludes the frozen base, activations, saved dropout masks, input gradients, allocator fragmentation, communication buckets, and temporary workspaces. Those belong in separate ledgers because their lifetimes and scaling variables differ.

For the Loop 6 graph, unique factor scalars while naive path counting gives 54. With bf16 parameters and gradients, fp32 first/second moments, and an fp32 master copy:

StateBytes per scalarUnique Naive path count
adapter parameter264 B108 B
live gradient264 B108 B
first moment 4128 B216 B
second moment 4128 B216 B
master parameter4128 B216 B
persistent subtotal-448 B756 B
subtotal while gradients are live-512 B864 B

The alias error overstates this tiny fixture by 352 bytes. At model scale the same identity mistake corrupts optimizer allocation, checkpoint size, and distributed ownership rather than merely a report.

If no master copy is used, subtract bytes. If the parameter itself is stored in fp32, do not silently count a second fp32 master copy. Quantized optimizers, factored states, offload, and sharding replace the byte roles; they do not justify one universal multiplier.

For the earlier 4,194,304-parameter Q/V example, bf16 parameter/gradient plus two fp32 moments is about 48 MiB while gradients are live. Adding an fp32 master copy makes the same subtotal about 64 MiB. The frozen base and activation peak remain separate and can still dominate the job.

0x07B AdamW State Belongs To Factor Coordinates

Flatten the unique trainable factors into one conceptual vector . For gradient , Adam-style moments are

Starting from zero moments, their bias-corrected forms are

An AdamW-style decoupled update can then be written as

The AdamW paper motivates decoupling weight decay from the adaptive loss-gradient update. Here the important checkpoint consequence is simpler: , , the step , the learning-rate schedule position, and any master copy are state. Reloading only and preserves the current function but resets the optimizer trajectory.

These moments belong to the factor coordinates. Two gauge-related pairs and can represent the same while having different factor gradients and moments, as 0x05D showed. A checkpoint cannot replace factor optimizer state with one optimizer state for the dense product without changing the optimization problem.

0x07C The Training Step Has A Canonical Save Boundary

def lora_train_step(model, batch, optimizer, scheduler, scaler=None):
    optimizer.zero_grad(set_to_none=True)

    # Frozen base parameters remain in the differentiable graph.
    logits = model(
        input_ids=batch.input_ids,
        attention_mask=batch.attention_mask,
    ).logits
    loss = cross_entropy(logits[:, :-1], batch.input_ids[:, 1:])

    if scaler is None:
        loss.backward()
        clip_grad_norm_(model.unique_adapter_parameters(), max_norm=1.0)
        optimizer.step()
        step_applied = True
    else:
        scaler.scale(loss).backward()
        scaler.unscale_(optimizer)
        clip_grad_norm_(model.unique_adapter_parameters(), max_norm=1.0)
        step_applied = scaler_step_with_explicit_overflow_result(scaler, optimizer)
        scaler.update()

    if step_applied:
        scheduler.step()

    # Canonical boundary: all state transitions for step t are complete,
    # and the next sample/dropout draw has not happened.
    optimizer.zero_grad(set_to_none=True)
    return loss.detach()

The scaler update and scheduler order are part of the run contract. If overflow causes the optimizer step to be skipped, a step-counted scheduler should not silently advance as though parameters changed. A different scheduler convention may step at another point; the checkpoint must record the convention rather than infer it from a scalar learning rate.

The ownership assertions from 0x06 still apply:

trainable_ids = {id(p) for p in model.unique_adapter_parameters()}
optimizer_ids = {
    id(p)
    for group in optimizer.param_groups
    for p in group["params"]
}

assert optimizer_ids == trainable_ids
assert no_frozen_base_parameter_has_grad(model)
assert every_alias_resolves_to_one_adapter_slot(model)

A model can look frozen while an optimizer created earlier still holds stale base parameters. Conversely, deduplicating by path can omit or duplicate parameter identities. requires_grad flags, optimizer groups, and graph aliases must reconcile to the same set.

0x07D Step-Boundary And Mid-Accumulation Checkpoints Differ

At the canonical boundary above, gradients are empty and the next stochastic event has not occurred. A resume bundle does not need gradient buffers for that policy. With gradient accumulation over microbatches, a mid-accumulation checkpoint is different:

Exact continuation from that point needs , the microstep , loss-scaling state, and the data/RNG positions after microbatch . Discarding and restarting the accumulation window changes the next optimizer update. Saving only at optimizer-step boundaries is simpler, but it can repeat work after a failure. The policy belongs in the manifest.

The PyTorch AMP recipe explicitly includes GradScaler.state_dict() for bitwise-accurate AMP resume and recommends saving either before a new iteration or after the scaler update. That is official framework guidance, not a claim that every hardware/software environment can replay bitwise.

0x07E Three Artifact ABIs

ArtifactMust carryIntentionally omitsValid question it answers
portable adapterexact base identity/revision, graph and target manifest, adapter config, factor tensors, declared additional modulesoptimizer, scheduler, data cursor, transient RNG statecan this adapted function be reconstructed?
exact-resume bundleportable adapter plus optimizer states/steps, scheduler, scaler, global/microstep, RNG streams, sampler/data state, accumulation state, determinism environmenta derived deployment merge unless separately recordedcan this training trajectory continue from the boundary?
merged releasederived model weights, base+adapter lineage, merge/cast manifest, evaluation record, rollback targetfactor optimizer state and training-control statecan this tested representation be deployed and rolled back?

Official PEFT checkpoint documentation describes adapter weights and adapter configuration as the portable PEFT artifact and notes that the original base remains required. Official PyTorch checkpoint guidance separately includes model and optimizer state for resuming training. Combining those two facts does not make a PEFT adapter directory an exact-resume bundle by itself.

0x07F Exact Resume Restores Control State Before The Next Draw

def save_step_boundary(run):
    assert run.representation == "live_unmerged"
    assert run.accumulation.micro_step == 0
    assert all(p.grad is None for p in run.unique_adapter_parameters())

    return atomic_save({
        "portable_adapter": export_adapter_and_graph_abi(run),
        "optimizer_state": run.optimizer.state_dict(),
        "scheduler_state": run.scheduler.state_dict(),
        "global_step": run.global_step,
        "grad_scaler_state": optional_state_dict(run.scaler),
        "rng_state": capture_all_declared_rng_streams(run),
        "sampler_state": run.sampler.state_dict(),
        "accumulation_state": run.accumulation.state_dict(),
        "software_and_determinism_manifest": environment_manifest(run),
    })


def resume_step_boundary(bundle):
    model = load_exact_base(bundle.base_identity)
    model = inject_from_graph_manifest(model, bundle.graph_manifest)
    load_adapter_values(model, bundle.portable_adapter)

    optimizer = build_optimizer(model.unique_adapter_parameters())
    scheduler = build_scheduler(optimizer)  # construct before state loading
    optimizer.load_state_dict(bundle.optimizer_state)
    scheduler.load_state_dict(bundle.scheduler_state)
    load_optional_scaler_state(bundle.grad_scaler_state)

    restore_accumulation(bundle.accumulation_state)
    restore_sampler(bundle.sampler_state)
    restore_rng_streams(bundle.rng_state)    # before next sample/dropout draw
    assert_reconciled_trainable_and_optimizer_ids(model, optimizer)
    return model, optimizer, scheduler

The base and injection graph are recreated before optimizer values because optimizer slots must resolve to the intended parameter identities. Distributed checkpoints may encode names or sharded identities rather than process-local object IDs, but they still need an explicit mapping.

Exact replay also has a bounded claim. PyTorch’s reproducibility note warns that complete reproducibility is not guaranteed across releases, platforms, or CPU/GPU execution. An article may prove state-complete resume for one declared environment; it should not promote that to universal cross-platform bitwise identity.

0x07G An Interrupted AdamW Fixture Makes Missing State Observable

The checked-in standard-library oracle uses four representative adapter scalars, three AdamW steps, an explicit learning-rate sequence, a stateful sample cursor, and a deterministic dropout RNG. It interrupts after step 1, serializes the complete boundary state, restores it, and runs steps 2 and 3.

Stepsampledropout masklearning rateresulting parameter vector
12[0,0,1,1]0.1[0.990000, -1.980000, 0.592561, 2.870990]
20[1,0,1,1]0.05[0.942366, -1.970100, 0.589598, 2.811167]
33[1,1,1,1]0.025[0.947991, -1.986362, 0.572161, 2.796129]

The uninterrupted and interrupted/resumed final states agree exactly under the oracle’s decimal arithmetic, including parameters, both moments, optimizer and scheduler steps, scaler tracker, RNG state, and sample cursor. The fixture is deliberately narrow: it does not import PyTorch or PEFT, execute AMP/distributed checkpointing, benchmark memory, validate task quality, or prove cross-release replay.

0x07H Merge Is A Release Branch, Not A Training Checkpoint

For compatible evaluation-mode linear LoRA, 0x03 proved

That algebra says how to derive a representation. It does not transfer the factor-coordinate optimizer states into a meaningful optimizer state for . Nor does it make the derived tensor the canonical base.

live training state
    immutable base + adapter + optimizer/control state

derive release candidate
    copy base -> apply admitted merge -> record casts and checksums

validate candidate
    parity tolerances + task/safety/runtime evaluation

deploy or reject
    retain immutable base, adapter, manifest, and previous release target

The tied-head case from 0x06 remains decisive: a head-only delta cannot be merged in place into storage also consumed by an untargeted embedding role. A role-local derived tensor may be valid, but it is a new artifact with its own lineage. Quantized merge and multi-adapter composition introduce further conditions owned by M6.4/M6.5.

Unmerge is also not an archival strategy. Subtracting a low-precision materialized delta can accumulate rounding error, and a framework may discard adapter machinery after merge_and_unload. Rollback should load an immutable prior artifact, not assume arithmetic reversal reconstructs it bit for bit.

0x07I Distributed Ownership Changes Placement, Not Logical Identity

Replicated data parallelism can all-reduce only adapter gradients, but every rank may still hold the frozen base. ZeRO/FSDP-style strategies may shard adapter parameters, gradients, and optimizer states. The global ledger remains over unique logical adapter identities:

only when each logical parameter element has one owner in that accounting. Replicated copies must not be summed as new model parameters; shards must not be mistaken for complete tensors.

PyTorch’s Distributed Checkpoint documentation describes in-place loading, per-rank checkpoint files, and load-time resharding support. Those are versioned framework capabilities. This Loop 7 oracle records only the framework-independent requirements: a stable parameter namespace, one logical value per unique adapter parameter, a save ownership rule, and an explicit resharding plan when topology changes.

0x07J Training-State Failure Lab

FailureWhat was savedFirst observable divergenceRequired repair
adapter tensors only, then “resume”current moments and bias-correction step resetlabel it warm-start or restore optimizer state
scheduler cursor omittedoptimizer moments preservednext learning rate differssave/load scheduler state and ordering convention
dropout RNG restored after forwardnominal RNG bytesnext mask already consumed from wrong staterestore every RNG stream before next stochastic event
sampler cursor omittedmodel/optimizer completenext training example differs or repeatscheckpoint sampler/data-pipeline state
AMP scaler omittedmodel/optimizer completeoverflow/step-skip trajectory can differsave scaler at a valid boundary
mid-accumulation gradients discarded microsteps already consumednext optimizer update omits save gradient/accumulation state or checkpoint only at step boundaries
optimizer groups count aliased pathssame adapter object appears twiceduplicate or conflicting state ownershipreconcile unique parameter identities
merged release reused as resume basedense derived weight onlyfactor optimizer state has no matching coordinatesresume from immutable base + live adapter bundle
arithmetic unmerge used as rollbackmerged low-precision tensorrecovered base drifts after subtractionload immutable rollback artifact
topology changed without resharding planrank-local shardsmissing/duplicated optimizer valuesstable names plus framework-supported resharding

0x07K From LoraConfig To A Changed Module Graph

The equation does not explain how a real model acquires hundreds of factor pairs. A PEFT library must interpret configuration, discover graph owners, choose a backend wrapper, create named state, change trainability, serialize only the selected artifact, and later reconstruct that graph.

This section is tied to PEFT v0.19.1 at commit ba6a1906.... It is official implementation evidence for that revision, not a redefinition of the 2021 LoRA paper and not a promise about later PEFT releases.

Source surfacePinned symbolContract observed hereWhat it does not prove
public mapping helperget_peft_modelrecords base identity metadata and returns a generic or task-specific wrapper while mutating the supplied graphthat every base model or task wrapper follows one identical forward API
low-level mapping helperinject_adapter_in_modelinstantiates the mapped tuner and returns the mutated base model without PeftModelprompt-method support or portable weight population by itself
wrapper lifecyclePeftModel.__init__, save_pretrained, from_pretrainedmaps PEFT type to tuner, saves selected adapters, and reconstructs a wrapper plus adapterexact optimizer resume, which remains a Loop 7 bundle
generic tuner injectionBaseTuner.inject_adaptersnapshots named modules, checks targets, invokes method-specific replacement, and marks trainable statecomplete storage-alias preservation for arbitrary graphs
LoRA model rewrite_create_and_replace, _create_new_moduleupdates an existing LoRA wrapper or chooses the first matching backend dispatcherequivalent semantics across quantized/custom/TP backends without testing them
LoRA layer state and operatorupdate_layer, Linear.forward, get_delta_weight, mergeowns named A/B/dropout/scaling slots, active forward additions, and merge materializationmixed-request serving or variant behavior owned by M6.4/M6.5
adapter state translationget_peft_model_state_dict, set_peft_model_state_dictfilters adapter state, removes a local adapter name on save, and inserts the requested name on loadsemantic compatibility from tensor shapes alone

The public wrapper workflow is conceptually:

from peft import LoraConfig, get_peft_model

config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules=["q_proj", "v_proj"],
    bias="none",
)

model = get_peft_model(
    base_model,
    config,
    adapter_name="legal",
)

The pinned call path is more specific than the public snippet:

get_peft_model
  -> PeftModel.__init__
  -> PEFT_TYPE_TO_TUNER_MAPPING[LORA]
  -> LoraModel / BaseTuner.__init__
  -> BaseTuner.inject_adapter
  -> LoraModel._create_and_replace for each match
       existing LoraLayer -> update_layer(new adapter name)
       plain base module  -> _create_new_module -> first backend match
  -> _mark_only_adapters_as_trainable

The low-level entry starts at the same tuner mapping but returns peft_model.model, not the PeftModel wrapper. It also explicitly rejects prompt learning and unsupported PEFT types. Wrapper choice, graph mutation, and weight loading are therefore separate operations even when a high-level API composes them.

0x07K1 Discovery Is A Snapshot, Then A Mutation

The pinned BaseTuner.inject_adapter first materializes list(model.named_modules()). It uses that snapshot to configure targets, discover existing adapter prefixes, match modules or state-dict-derived owners, and obtain parent/target/child triples before invoking method-specific replacement. This avoids iterating a live module generator while the graph changes.

The durable teaching algorithm is:

def pinned_injection_shape(model, config, adapter_name, optional_state_keys=None):
    named_modules = list(model.named_modules())
    validate_new_adapter_config(config)
    inspect_tied_modules(model, config)
    configure_default_or_all_linear_targets(config, model)

    matches = []
    for path, module in named_modules:
        decision = match_target(path, module, config, optional_state_keys)
        if decision.excluded:
            continue
        if decision.matched:
            parent, target, child_name = get_submodules(model, path)
            matches.append((path, parent, target, child_name))

    if no_valid_match(matches, config):
        raise ValueError("target contract did not match")

    for path, parent, target, child_name in matches:
        lora_model_create_or_update(
            config, adapter_name, target, child_name, parent, path
        )

    mark_only_declared_adapter_state_trainable(model, config)
    return matches

The source attempts several checks before replacement, but it is still not a transaction over every framework-independent identity from Loop 6. Custom module hooks, parameter targeting, lazy/meta initialization, tied modules, and backend-specific wrappers complicate the mutation boundary. A production preflight should compare the expected graph manifest against observed owners before permitting any irreversible change.

The diagnostic targeted_module_names also needs careful interpretation. It appends names as adapters are injected. In the Loop 8 two-adapter run, it is:

[q_proj, v_proj, q_proj, v_proj]

That is useful injection history, but the graph still contains only two target wrappers. Canonical graph accounting should deduplicate by module identity and then inspect the named slots owned by each wrapper.

Pinned PEFT v0.19.1 call chain from public or low-level entry through target discovery, LoRA layer state, portable key rewriting, reload, and merge and unload

PEFT LoRA configuration, module injection, state dictionary, and reload lifecycle

0x07L The Injected Layer Owns Named Adapter Slots

LoraModel._create_and_replace has two materially different branches. If a target is already a LoraLayer, the method calls update_layer and adds a slot. Otherwise it calls _create_new_module; that method evaluates custom, quantized, tensor-parallel, and default dispatchers in a fixed order and accepts the first non-None wrapper. Backend dispatch is therefore part of the runtime contract, not an implementation footnote.

For an ordinary linear target, the transformed object has this state:

LoRA-wrapped linear
    base_layer.weight                 frozen

    lora_A["legal"].weight           [r, d_in]
    lora_B["legal"].weight           [d_out, r]
    dropout["legal"]
    scaling["legal"]

    lora_A["support"].weight         optional second adapter
    lora_B["support"].weight
    ...

Module dictionaries are important. The rank, scaling, factors, and activation state belong to an adapter name, not just to the base layer. Adding a second adapter should create another slot instead of overwriting the first pair.

The pinned update_layer validates positive rank, chooses dropout or identity, creates

and records per-adapter scale

The second branch is mentioned here only to make the source conditional visible. M6.4 owns rsLoRA’s changed scaling contract.

The pinned linear forward has four control paths:

Runtime stateForward behaviorOwner
adapters disabledunmerge if necessary, then execute baseM6.3 control state
adapter_names supplied per examplemixed-batch helperM6.5
layer already mergedexecute materialized baseM6.3 representation state
live unmerged adaptersbase result plus every active adapter deltaM6.3 vanilla operator

The last path is the tensor path derived in 0x03:

def peft_linear_forward(layer, x, adapter_name):
    y = layer.base_layer(x)

    if layer.adapters_disabled or adapter_name is None:
        return y

    x_adapter = x.to(layer.lora_A[adapter_name].weight.dtype)
    z = layer.lora_A[adapter_name](layer.dropout[adapter_name](x_adapter))
    delta = layer.lora_B[adapter_name](z)
    return y + layer.scaling[adapter_name] * delta

The actual pinned source loops over all active_adapters, casts the input to each adapter A dtype, accumulates each delta into the base result, and casts the final result back to the original base-result dtype. Production implementations handle more cases: mixed-adapter batches, variants, quantized layers, tensor-parallel hooks, and specialized merge behavior. M6.4/M6.5 own those branches. The M6.3 invariant is narrower: the injected vanilla object must implement the same axis convention and scaling contract as its saved factors.

0x07L1 The Tiny Fixture Executes The Exact Operator

The Loop 8 runtime fixture uses two base matrices, two rank-one LoRA paths, and input

The frozen paths are

so the frozen sum is . With , , and :

Therefore

After adding a second adapter called support and reselecting legal, both wrappers own legal and support A/B slots, but only the four legal factor tensors have requires_grad=True. This executes the slot distinction rather than inferring it from key names.

After injection, inspect the graph and optimizer surface rather than trusting the configuration:

expected = {name for name, _ in model.named_modules() if is_expected_target(name)}
actual = set(model.targeted_module_names)
assert actual == expected

trainable = {
    name for name, parameter in model.named_parameters()
    if parameter.requires_grad
}
assert trainable
assert all(is_adapter_parameter(name) for name in trainable)

Some configurations intentionally save or train additional modules, such as an output head. In that case the last assertion must admit the declared modules_to_save; it should not be weakened into accepting arbitrary trainable base tensors.

0x07M Adapter-Only State Dict And Reload

A normal model.state_dict() can include the complete transformed model. A portable PEFT artifact should select adapter-owned values and save the configuration needed to recreate their owners.

Official PEFT checkpoint documentation describes two central files for a LoRA adapter:

adapter_model.safetensors (or adapter_model.bin):
    adapter tensors and declared additional saved modules

adapter_config.json:
    PEFT type, rank/scaling/target metadata,
    base-model identity, and reconstruction options

Runtime parameter names may contain an adapter slot such as:

...q_proj.lora_A.legal.weight
...q_proj.lora_B.legal.weight

The pinned get_peft_model_state_dict first selects PEFT-owned values according to method and bias policy, includes declared auxiliary modules and selected embeddings when required, and then removes the local adapter name from adapter-owned keys. PeftModel.save_pretrained writes each selected non-default adapter under its own subdirectory, clones storage aliases that safetensors cannot encode, makes tensors contiguous, writes the tensor artifact, and writes configuration with inference-mode metadata.

The Loop 8 run makes the namespace transformation explicit:

StageExample keyMeaning
live graphbase_model.model.q_proj.lora_A.legal.weightfactor belongs to local slot legal
portable artifactbase_model.model.q_proj.lora_A.weightlocal slot name removed
reloaded graphbase_model.model.q_proj.lora_A.renamed.weightsame portable factor inserted into requested local slot renamed

The portable directory contained exactly:

README.md
legal/adapter_config.json
legal/adapter_model.safetensors

The subdirectory is a consequence of saving a non-default selected adapter in this pinned version. It is not a universal storage layout for every PEFT release or hosting system.

The high-level reload path is:

base = load_exact_base(base_id, revision=base_revision)
model = PeftModel.from_pretrained(
    base,
    adapter_path,
    adapter_name="legal",
    is_trainable=False,
)

The lower-level injection API separates graph construction from weight population:

model = inject_adapter_in_model(config, base, adapter_name="legal")
outcome = set_peft_model_state_dict(model, adapter_state, adapter_name="legal")

assert not outcome.unexpected_keys
assert no_required_adapter_keys_missing(outcome.missing_keys)

PeftModel.from_pretrained loads configuration, constructs the wrapper and tuner graph, then delegates to load_adapter. The pinned set_peft_model_state_dict reinserts the requested adapter name, applies method-specific translations, optionally filters size mismatches, and calls model.load_state_dict(..., strict=False). A non-strict load result is evidence to inspect, not permission to ignore arbitrary missing keys.

This distinction matters for debugging. Successfully creating the low-rank modules does not prove their trained values were loaded. Conversely, loading a dictionary into shape-compatible factors does not prove the target modules, scaling convention, fan-in/fan-out orientation, or base revision are correct.

Treat the adapter as a typed artifact:

adapter ABI =
    base identity and revision
    model architecture
    target-module paths
    factor shapes and orientation
    rank/alpha/scaling convention
    dtype and quantization assumptions
    optional saved modules
    tensor values

The small tensor file is only one component of that ABI.

0x07M1 Reload And Merge Reconcile Different Representations

The real PEFT run loaded the legal artifact under local name renamed. The live and reloaded outputs agreed exactly in Torch fp32:

live output:     [ 1.8150000572, -4.3450002670 ]
reloaded output: [ 1.8150000572, -4.3450002670 ]
max |live - reloaded| = 0

For vanilla Linear, the pinned get_delta_weight computes

safe_merge=True clones the base weight, applies that delta, checks finiteness, and then records the merged adapter. merge_and_unload walks wrappers, merges admitted adapters, replaces each wrapper with its base layer, and removes peft_config from the returned graph. The fixture observed:

reloaded output: [ 1.8150000572, -4.3450002670 ]
merged output:   [ 1.8150001764, -4.3449997902 ]
max |reloaded - merged| = 4.76837158203125e-7
merged state keys containing "lora_" = 0

This is tolerance-level representation parity for one tiny fp32 torch.nn.Linear graph. It does not prove bitwise merge parity, quantized merge, tied-role safety, cross-release loading, or archival rollback. Loop 6’s tied-storage preflight and Loop 7’s immutable rollback lineage remain stronger admission requirements than a successful framework call.

0x07N What Loop 8 Actually Executed

source:
    PEFT v0.19.1 tag
    commit ba6a19060d6ab54a87538a6e77e3e4d5a907375b
    8 exact PEFT files, 20 AST symbol ranges

runtime:
    Python 3.11.15
    PEFT 0.19.1 from the exact checkout
    Torch 2.13.0, Transformers 5.14.1, safetensors 0.8.0
    two ordinary fp32 torch.nn.Linear targets
    two named adapter slots; one active/trainable
    selected save, renamed reload, safe merge and unload

not executed:
    a Transformers architecture
    quantized/custom/tensor-parallel backend dispatch
    distributed or sharded state
    mixed-adapter request batches
    training quality or convergence
    production serving or cross-release compatibility

The checked oracle verifies the exact Git revision and all eight file digests before parsing the 20 source symbols. The runtime imports PEFT from that checkout, not from an unrelated site package. Its report and 49 one-field invalid contracts are article-local evidence; they are not paper benchmarks.

0x07O PEFT Source-To-Runtime Failure Lab

FailureSame-looking surfaceFirst decisive checkRepair owner
wrapper helper and low-level injection conflatedboth mutate the base graphreturned object type and prompt-method support differcaller/API contract
second adapter overwrites firstsame target pathinspect A/B dictionary keys and object identitiesinjection implementation
diagnostic target list counted as graph ownersrepeated q_proj/v_proj stringsdeduplicate module objects, then inspect slotsgraph manifest
broad backend dispatcher selectedwrapper still has A/B namesrecord concrete wrapper class and dispatcher orderruntime integration
adapter saved under wrong namespacetensor shapes still matchcompare live, portable, and reload key mapsartifact ABI
non-strict load silently acceptedload call returnsclassify missing/unexpected/mismatched keys against required ownersloader/admission
active adapter differs after reloadall factors residentinspect active list and trainable parameter IDsruntime control state
merged graph treated as resume checkpointoutput parity may passoptimizer/factor coordinates and graph identity are goneLoop 7
quantized or tied merge generalized from fp32 fixturealgebra looks identicalbackend/storage-role admission was not executedM6.4/M6.5

0x07P One End-To-End Oracle Must Preserve Every Intermediate Contract

The focused oracles above answer deliberately narrow questions. Loop 3 proves one linear operator in exact arithmetic. Loop 4 proves its local adjoints. Loop 6 proves graph injection, aliases, packed slices, and tied-storage admission. Loop 7 proves checkpoint and resume state. Loop 8 follows one real PEFT release through source, named slots, portable save, reload, and merge. None of those results alone proves that one unchanged adapter identity survives the complete sequence.

The end-to-end owner question is stricter:

Can one immutable base and target manifest pass through injection, live forward, backward, adapter-only save, renamed reload, dense merge, and rollback while preserving the correct identity and parity predicate at every boundary?

The checked-in Loop 10 fixture answers that question for one exact teaching graph. It has two independent targets, one true module alias, and one untargeted control:

input X: [T=2, d_in=3]
graph output: q_proj(X) + v_proj(X)

q_proj:
    d_out=2, rank=2, alpha=4, scale=2
    canonical path: model.layers.0.self_attn.q_proj
    alias path:     model.layers.0.attn_q_alias

v_proj:
    d_out=2, rank=1, alpha=3, scale=3
    canonical path: model.layers.0.self_attn.v_proj

k_proj:
    untargeted control; no adapter state

The ranks and scales differ intentionally. A loader that silently applies the query configuration to both targets can still create shape-valid tensors, but it will not implement this graph.

Six-checkpoint LoRA transaction from immutable manifest through graph injection, exact forward and backward, portable save and renamed reload, derived dense merge, and artifact-based rollback

Transformer System executable teaching fixture. The diagram records six different parity domains. It is not a paper benchmark, production-framework trace, or finite-precision merge result.

0x07Q Snapshot, Match, Deduplicate, Then Rebind

The graph contains four path occurrences but only three module objects. The canonical query path and attn_q_alias refer to the same query object. Injection therefore follows a transaction, not an in-place name loop:

snapshot = list(named_module_occurrences(base))

matched = exact_match(
    snapshot,
    ["model.layers.0.self_attn.q_proj",
     "model.layers.0.self_attn.v_proj"],
)
assert no_unmatched_targets(matched)

unique_objects = deduplicate_by_module_object(matched)
assert len(unique_objects) == 2

wrappers = preflight_and_construct(unique_objects, adapter_slot="legal")
rebind_every_alias_path(snapshot, wrappers)

assert wrapper_at("q_proj") is wrapper_at("attn_q_alias")
assert not has_adapter("k_proj")

The exact plan reconciles as follows:

Graph countValueWhy it is not another count
path occurrences4includes a query alias and the untargeted key control
requested paths2canonical query and value targets
unique target objects2query alias does not create a third object
wrappers constructed2one wrapper per target object
paths rebound3both query paths plus the value path

Matching by suffix would weaken the contract: a near-match added by a later architecture could become silently eligible. Wrapping every matched path would break alias identity and double the query adapter state. Rebinding only the canonical path would leave the alias executing the old base object. Loop 10 rejects all three substitutions before any output parity check.

0x07R Compose The Two Live Branches Before Comparing A Merge

Let the target set be . Each target owns its own frozen matrix, bias, rank, factors, and scale. The graph output is

This is not one adapter applied twice. The query target uses ; the value target uses . With

the exact intermediates are:

TargetBase output Low-rank coordinate Scaled branch Live target output
query[[0,-3],[7,4]][[2,1],[-3,2]][[4,2],[-6,4]][[4,-1],[1,8]]
value[[4,5],[-6,1]][[-2],[4]][[-12,6],[24,-12]][[-8,11],[18,-11]]

The sum and scalar teaching loss are therefore

The untargeted key projection is present in the graph manifest but not in this teaching output. That makes it a graph-mutation control, not a hidden third term in the loss.

0x07S Backward Parity Must Cover Every Factor And The Shared Input

For each target , define . Differentiating the composed graph gives

Both branches also contribute to the same input adjoint:

The exact fixture derives

dA_q = [[  2,   0, 10],
        [ -4, -10, 10]]

dB_q = [[ -8, 10],
        [-14,  0]]

dA_v = [[12, 15, 15]]
dB_v = [[18],
        [24]]

dX   = [[13, -14,  5],
        [16,  -3, 10]]

An implementation can produce the right live output while still attaching one factor to the wrong target, transposing one saved factor, or dropping one branch from . The oracle therefore perturbs , , , , and independently. For direction it compares the manual contraction with the exact centered probe

The five results are [-18, 6, 33, 66, 12], and all five agree exactly. This remains a local exact-integer derivative oracle. It does not reproduce optimizer dynamics, stochastic dropout, convergence, or task quality.

0x07T Portable Tensor Identity Is Not Local Namespace Identity

The training graph stores both targets under local slot legal. A portable adapter removes that local slot from tensor keys while retaining the target/rank/alpha/orientation manifest and immutable base digest:

local training key:
    base_model.model.layers.0.self_attn.q_proj.lora_A.legal.weight

portable key:
    base_model.model.layers.0.self_attn.q_proj.lora_A.weight

reloaded local key:
    base_model.model.layers.0.self_attn.q_proj.lora_A.renamed.weight

The artifact contains four factor tensors: A and B for query, then A and B for value. It does not contain base weights or exact-resume optimizer state. Its manifest includes:

base digest:  fd7709aa03cf...
target paths: q_proj, v_proj
ranks:        q=2, v=1
alphas:       q=4, v=3
orientation:  out_in

Consequently, three digests are expected to differ:

local legal state:    d219b4132191...
portable tensor state:f4f7e0faf8df...
local renamed state:  937d4341e628...

The differing local-state digests are not corruption; the key namespace changed by contract. The tensor payload and manifest must still reconstruct the same two target operators. The reloaded graph produces the exact same Y as the live training graph. A partial load, target swap, rank reuse, or non-strict missing key is a failed checkpoint even if every tensor that did load has a plausible shape.

0x07U Merge Creates A New Release; It Does Not Rewrite History

Only after live and reload parity pass does the oracle construct a separate dense release:

For the fixture,

The merged graph reproduces each target output and the final sum exactly. Its release state has zero lora_ keys and digest 4211d4c1c912.... The immutable base artifact remains fd7709aa03cf... before and after the transaction.

That gives six explicit parity checkpoints:

CheckpointCompared identityExact resultWhat a pass does not prove
P0 manifestbase, target, alias, axis, slot, dtypefrozenforward correctness
P1 live forwardinjected graph versus explicit branch referenceequalgradient correctness
P2 backwardfour factor adjoints and shared input versus directional probes5/5 equaloptimizer trajectory
P3 portable reloadlegal save versus renamed graphoutput equalcross-release compatibility
P4 dense mergereloaded live graph versus separate merged releaseoutput equal; zero LoRA keysquantized/tied merge safety
P5 rollbackpost-transaction base versus immutable base digestequalarithmetic unmerge safety

Rollback is therefore an artifact operation:

def rollback(release_record):
    base = load_immutable_base(release_record.base_revision)
    adapter = load_portable_adapter(release_record.adapter_revision)
    return inject_after_full_admission(base, adapter)

It is not merged_weight -= cast(delta). Subtraction may not reconstruct a low-precision base bit for bit, and a merged graph no longer owns the optimizer, slot, or wrapper state needed for exact training resume.

The standard-library oracle rejects 59 one-field substitutions spanning axes, paths, aliases, factors, target-specific scales, gradient probes, artifact fields, namespace translation, merge representation, rollback, and claim scope. It does not replace the Loop 8 real-PEFT run: Loop 8 proves one pinned framework path, while Loop 10 proves the entire semantic transaction under exact arithmetic. Quantized and tied-storage merge admission remains M6.4/M6.5 work; mixed-request adapter scheduling remains M6.5; distributed state remains a separate deployment contract.

0x08 Why Low Rank Can Work, And Why It Can Fail

The LoRA paper reports empirical rank-deficiency in adaptation updates and finds that useful task adaptation can often be represented with ranks much smaller than the matrix width. Earlier intrinsic-dimension work also motivates the idea that successful optimization may live in a lower-dimensional subspace than the raw parameter count suggests.

The paper does more than report a rank ablation. It compares the leading singular subspaces learned with rank 8 and rank 64 for query and value updates in one GPT-3 layer. The strongest directions overlap substantially even though the larger adapter has many more available directions:

LoRA rank-8 and rank-64 adaptation subspace similarity

Source: LoRA v2, Figure 3. The heatmaps compare normalized subspace similarity for rank-8 and rank-64 query/value adaptation updates in the reported 48th GPT-3 layer. The result supports a low-dimensional-update hypothesis for that experiment; it does not establish one universal rank for every layer, model, task, seed, or target matrix.

This evidence should be read at the level of the effective update subspace, not as a claim that the raw factors are uniquely identifiable. The later gauge-non-uniqueness section shows why two pairs can represent the same dense update .

The careful claim is:

Many observed downstream adaptations admit effective low-dimensional update structure.

The claim is not:

Every downstream task, layer, projection, and training regime has the same intrinsic rank.

Several factors can increase the required adaptation surface:

large domain shift
new vocabulary or output interface
multimodal projector changes
long or diverse training corpus
continued pretraining rather than narrow task tuning
strict factual or behavioral retention constraints
few target modules
small base model with less reusable representation

Rank also interacts with target coverage. A small rank on many modules can outperform a larger rank on a narrow surface, or the reverse, depending on where the needed update lives.

0x08A Gauge Non-Uniqueness

The factorization is not unique. For any invertible:

we have:

The dense update is unchanged while the factor values differ.

Consequences:

factor norms are not a unique description of Delta W
optimizer dynamics depend on the chosen parameterization
regularizing A and B separately is not equivalent to regularizing BA
merging adapters should compare effective updates, not raw factors alone

This helps explain why variants that alter initialization, scaling, or per-factor learning rates can change training even though the final representational family still consists of low-rank updates.

0x09 Alternative Adaptation Mechanisms

LoRA is one PEFT design, not the definition of PEFT. The methods below all reduce some task-specific state, but they do not reduce the same state and do not leave the same executable model behind. A comparison that reports only a trainable-parameter percentage erases the distinctions that matter in training and deployment.

The owner question for this section is therefore:

If two methods train the same number of scalars, do they own the same backward path, inference graph, sequence state, cache identity, and release artifact?

The answer is no. We will derive that answer from the original paper contracts rather than rank methods by one benchmark table.

0x09A Four State Locations, Four Runtime Contracts

The book’s prefix- and prompt-tuning figures are useful because they ask where the learned state enters. The exact answer matters more than the shared label “parameter efficient.” The following reconstruction separates four mechanisms that are often drawn with the same colored blocks:

Prompt tuning, prefix tuning, bottleneck adapters, and LoRA placed at their exact input, attention-state, block-module, and weight-update locations

Reconstructed from the comparison idea in book Figures 9.5 and 9.6. The state locations and runtime ledgers are a corrected Transformer System interpretation grounded in the original Prefix-Tuning v1, Prompt Tuning v2, Houlsby adapter v2, and LoRA v2 papers. The book figures are conceptual references, not implementation specifications.

The first comparison axis is where task state enters:

Method familyTask-specific objectEntry pointPersistent inference consequence
full fine-tuningselected dense base parametersexisting model weightstask owns a transformed base checkpoint
prompt tuninglearned embedding rowsinput embedding sequencevirtual positions traverse the frozen stack
prefix tuninglayer-wise continuous activationsattention context at adapted layersmaterialized prefix state extends attention context
bottleneck adapternonlinear down/up moduleblock residual pathserial per-token module remains live
IA3multiplicative channel vectorsK, V, and FFN activationslive per-request scales or a single-task fold
BitFitselected existing biasesnamed base-model parametersno new operator, but task owns selected base values
LoRAlow-rank weight updateselected linear mapslive two-matmul branch or compatible dense merge

This location is not cosmetic. It determines which tensors require gradients, which dimensions belong in the artifact ABI, and whether serving changes sequence length, cache contents, or graph topology.

0x09B One Parameter Count Is Not A Fair Contract

For method , separate at least six ledgers:

where:

  • counts directly optimized parameters, including training-only reparameterizers;
  • counts task-specific state needed by the chosen release representation;
  • is gradient and optimizer state, not merely model weights;
  • records added, replaced, or folded operators;
  • records added input positions or layer-wise attention state;
  • records how far activation gradients must traverse.

Two methods can have equal and disagree on every other coordinate. Prefix-Tuning is a direct example: its training MLP can dominate , but the paper discards that MLP after materializing the effective prefix. LoRA has the opposite deployment option: its trained factors can remain live or be represented as a dense merged delta. Full fine-tuning adds no operator at inference, yet it owns the largest task-specific change to the base checkpoint.

This is also why paper percentages are not automatically comparable. They may include or exclude a task head, task-specific layer normalization, a training-only reparameterizer, tied parameters, or frozen resident state. The denominator may be BERT, T5, GPT-2, or another architecture. This article uses paper-reported values only as claims about the paper’s own experiment and derives its teaching ledger from one explicit fixture later in this section.

0x09C Full Fine-Tuning: No New Graph, Full Task Weight Ownership

Let the pretrained model own unique parameter vector . Full fine-tuning optimizes the selected dense coordinates directly:

If every base coordinate is trainable, then:

For an Adam-like optimizer, every trainable dense coordinate can also own a gradient, first moment, and second moment. The important comparison is not a fixed bytes-per-parameter slogan, because weight, gradient, master-weight, and optimizer dtypes are implementation choices. It is the ownership relation:

full fine-tuning task state:
    dense task weights
    + dense gradients while live
    + optimizer state for selected dense parameters
    + scheduler / scaler / RNG / data-progress state for exact resume

Inference does not gain a new adapter branch. The task-specific function is already represented by ordinary model weights. The operational price is that the task owns a full transformed checkpoint, or a dense delta plus an exact reconstruction contract. Serving many tasks therefore means loading, swapping, or sharing at a much coarser boundary than a small adapter artifact.

Full fine-tuning is the unrestricted update baseline, not the automatically strongest or fairest experimental baseline. Its result depends on optimizer, learning rate, regularization, data size, and checkpoint selection just as PEFT does.

0x09D Prompt Tuning: Input Positions Become Trainable State

The Prompt Tuning paper studies frozen T5 encoder-decoder models. It learns input-side embeddings of width :

Let be the learned prompt length and the model width. Prompt tuning learns:

and concatenates it once, at the embedding boundary:

The frozen Transformer then processes those positions normally. In a decoder-only model they acquire ordinary hidden states and per-layer K/V rows just like other preceding positions. In the encoder-decoder setting studied by the Prompt Tuning paper, the learned prompt is prepended to the encoder input. The architectural placement therefore determines which attention path grows; “adds decoder-cache rows” is not a model-independent statement.

# One input-side insertion; P_prompt is the only trainable tensor.
h = concat([P_prompt, token_embedding(input_ids)], dim="sequence")
for block in frozen_blocks:
    h = block(h)

Parameter state is . Runtime sequence work grows because the prompt positions traverse the frozen stack. They are not vocabulary IDs and need not be nearest to any readable word embedding.

For a causal decoder teaching profile with real positions, the token-only causal attention matrix contains:

When virtual prompt positions become ordinary preceding positions, the stack computes query rows:

Subtracting the baseline gives:

The first term is real-token attention to prompt keys. The second is the prompt positions’ own causal work. More importantly, those positions also traverse every projection, normalization, and FFN in the frozen stack. Counting only task parameters therefore misses the dominant runtime path.

Training is similarly not shallow. Although only receives parameter gradients, the loss gradient must propagate from the output through the frozen stack to the embedding-boundary prompt leaf. Frozen base weights avoid dense parameter-gradient and optimizer ownership; they do not erase activation-gradient propagation.

The paper also shows that prompt length, initialization, model scale, and the frozen model’s pretraining objective interact. Its default T5 experiment used class-label-informed initialization and a longer prompt, while large models were more robust to initialization and length. These are source-reported experimental findings, not a universal rule that a particular prompt length transfers to a causal decoder.

0x09E Prefix-Tuning: Layer-Wise Attention State And A Training Reparameterizer

The original Prefix-Tuning paper defines each prefix position’s state as a concatenation of activations across Transformer layers. For a decoder-serving implementation that materializes per-layer attention state, the effective K/V view is:

and attention consumes:

This K/V notation is a decoder-serving view of the paper’s more general layer-activation formulation. For BART, the paper introduces prefixes in the relevant encoder and decoder stacks; exact tensors therefore depend on architecture and implementation.

Directly optimizing the full prefix activations was unstable in the paper’s experiments. Training instead used a smaller learned table and an MLP reparameterization:

The reparameterization network is training state, not required serving state: after training, the generated effective prefix can be stored and the MLP discarded. At inference, each adapted layer attends over additional prefix positions, but those positions are not ordinary input embeddings propagated through every lower block.

# Effective prefix state may be materialized once per task/request.
prefix_state = prefix_mlp(prefix_table)  # training path
for layer, block in enumerate(frozen_blocks):
    k, v = block.project_kv(h)
    k = concat([prefix_state[layer].k, k], dim="sequence")
    v = concat([prefix_state[layer].v, v], dim="sequence")
    h = block.attend_and_mlp(h, k, v)

Let the materialized prefix width per position be:

for a decoder-only K/V teaching representation. Then direct deployed prefix state is:

Suppose training uses a table and a two-layer MLP with hidden width and output width . Including biases:

The final two parenthesized terms can be much larger than the deployed prefix. That is not a contradiction: the MLP is an optimization coordinate system, while the materialized prefix is the release representation.

For causal token queries, a direct K/V prefix adds no prefix-query rows. It adds keys to each token query:

This differs from prompt tuning by exactly the prompt positions’ own triangular work in this decoder profile:

That equation still understates the difference because prompt positions traverse full blocks, whereas a materialized K/V prefix enters at each attention layer. BART introduces prefixes in architecture-specific encoder and decoder paths, so a single decoder-only K/V count must not be copied into every Prefix-Tuning implementation.

0x09F Bottleneck Adapters: Nonlinear Capacity And Placement Variants

The Houlsby design inserts serial adapters after both the attention projection and feed-forward sublayer in each Transformer block. With bottleneck width :

where:

Including down- and up-projection biases, one insertion owns:

With insertions per block and blocks:

The Houlsby paper uses and also learns new task-specific layer-normalization parameters and an output layer. Calling only “the Houlsby task state” therefore omits paper-owned state. For rows, the two dense adapter projections add approximately:

before bias, activation, residual, and launch costs. There is no sequence-length or direct attention-context growth, but the nonlinear down-activation-up path remains serial. In general it cannot be folded into one existing dense weight because:

is not a fixed linear map when is nonlinear.

Placement is part of the method. Houlsby’s BERT graph places two adapters after sublayer projections and before the outer skip addition and layer normalization. A pre-norm decoder has a different residual schedule; copying the name “Houlsby adapter” without freezing the insertion edge is not an implementation specification. Later adapter frameworks support different placements and compositions, so benchmark rows labeled only “adapter” can describe different graphs.

Near-identity initialization is also a training contract. The adapter’s internal residual lets small projection outputs begin near the frozen function. The Houlsby paper reports instability when initialization departs too far from that regime. LoRA’s zero- initialization and adapter near-identity initialization share a purpose, but their first-gradient algebra and runtime graphs are not the same.

0x09G IA3 And BitFit: Multiplicative Versus Selective Existing State

IA3 modifies activations rather than adding sequence positions or a nonlinear bottleneck. The T-Few paper introduces one learned vector for attention keys, one for values, and one for the FFN intermediate activation in each Transformer block:

where:

For an -block encoder, the paper’s direct count is:

For its encoder-decoder decoder, both self-attention and cross-attention receive key and value scales:

The vectors initialize to ones, making the inserted operation the identity. They can vary by request in a mixed-task batch because each row can select task-specific scales. For a single fixed task, the paper observes that the scales can be absorbed into adjacent matrices. In column notation, for example:

so the scaled output channels can be represented by . The FFN intermediate scale can similarly be absorbed into the input-channel dimension of . Folding is therefore a representation choice with the same base-identity and dtype caveats as any release-time transform; it is not a free mixed-task serving solution.

The primary paper’s Figure 1 pins the exact K, V, and FFN insertion points. Its right half shows the broader T-Few loss recipe; those losses are not part of the IA3 operator contract discussed here.

IA3 key, value, and FFN activation scales alongside the broader T-Few loss recipe, reproduced from the primary paper's Figure 1

Source: Few-Shot Parameter-Efficient Fine-Tuning Is Better and Cheaper than In-Context Learning, Figure 1, released with the paper under its linked Creative Commons license. The left panel verifies IA3 placement; the right panel is retained to avoid presenting a cropped paper figure as the original composition. T-Few’s additional losses are out of scope for this operator comparison.

BitFit takes another route: it changes selected bias vectors already present in the base graph. The original paper studies BERT-family masked-language-model encoders and trains the task classifier plus all biases or a selected bias subset. Its basic operator remains:

No new matrix multiplication or sequence state is required. The task artifact instead depends on exact base parameter names and shapes. The paper reports that query and middle-MLP biases formed a useful reduced subset in its BERT experiments. That is a source-reported architecture-specific result, not a command to tune equivalently named tensors in every decoder. Some modern linear layers omit biases, and normalization families differ, so BitFit’s parameter budget must be counted from the actual graph.

0x09H LoRA Revisited: A Weight-Space Update With Two Release Forms

LoRA attaches trainable factors to selected linear maps:

It does not increase sequence length and does not create a new layer-wise prefix state. Targeting a K/V projection changes the values and compatibility of the resulting cache, not its row count or tensor shape. A live adapter adds two linear operations at each targeted map; a compatible deployment can instead merge into . That merge path is the decisive difference from a nonlinear bottleneck adapter.

This does not make LoRA uniformly cheaper. A live mixed-adapter batch needs an adapter-aware kernel or segmentation by adapter identity. A merged adapter removes the branch but gives up cheap per-request selection unless separate merged model instances are resident. The detailed scheduler and residency consequences belong to M6.5; this article owns only the operator and artifact distinction.

0x09I One Decoder Fixture Separates State From Work

To compare the contracts without mixing paper architectures, freeze this Transformer System teaching model:

decoder blocks L = 2
model width d = 4
SwiGLU width d_ff = 8
query heads H_q = 2
KV heads H_kv = 2
head width d_h = 2
vocabulary V = 16
real input positions T = 3
prompt / prefix length m = 2
adapter bottleneck r_a = 2
LoRA rank r = 1, targeting q_proj and v_proj
prefix reparameterizer k = 2, hidden width h_p = 6
two affine LayerNorms per block plus one final affine LayerNorm
token embedding and output projection share one storage

Every attention and SwiGLU projection includes a bias. The base unique-parameter count is:

The four terms inside the block are Q/K/V/O, SwiGLU gate and up projections, the down projection, and two LayerNorm gamma/beta pairs. Substitution gives:

The resulting state ledger is:

Method representationDirectly trainable task scalarsDeployed task scalarsWhat the count includes
full fine-tuningall unique base parameters; tied output storage counted once
prompt tuninglearned input embeddings
prefix tuning table + MLP during training; materialized K/V state at release
Houlsby-style adapter adapter parameters + task-specific LayerNorm parameters; task head excluded
IA3, decoder-only self-attention profile live or folded with
BitFit, all fixture biasesprojection and LayerNorm biases in this graph; no task head
LoRA on Q and V live, or zero separate scalars after dense merge targets, each

The table does not claim equal accuracy, equal expressive power, or equal paper settings. It demonstrates that task-scalar count itself is representation-dependent. Prefix training state and prefix release state differ. Merged LoRA has no separate factor artifact but changes the dense base artifact. Full fine-tuning and BitFit modify existing state rather than append modules.

Now compare recurring work for the same input:

MethodAdded recurring work in the fixtureSequence / K/V consequence
full fine-tuningno new inference operatorsame shape, task-specific values
prompt tuningfull frozen-block work for extra positions; extra causal score pairs per layertwo ordinary preceding positions and their per-layer K/V rows
prefix tuning extra score pairs per layer plus value aggregationtwo direct K/V prefix rows per layer; no prefix query rows
Houlsby adapter extra projection MACs before activation/bias/launch costsno extra sequence rows
IA3 live elementwise multiplies, or no added operator after a valid single-task foldno extra rows; K/V values and FFN activations change
BitFitno new operator relative to the biased base graphno extra rows; existing projected values change
LoRA Q/V live branch MACs, or no added operator after mergeno extra rows; cached K/V values depend on adapter identity

For prompt tuning, . For direct prefix K/V, . For live LoRA, four targeted maps each execute MACs, giving . For two adapter insertions in each of two blocks, MACs. These are logical operator counts, not latency predictions.

Paper-contract ledger comparing task state, backward path, runtime operator, sequence or KV consequence, and release representation for full fine-tuning, prompt tuning, prefix tuning, bottleneck adapters, IA3, BitFit, and LoRA

Transformer System teaching ledger. Counts are replayed by the Loop 9 oracle for the frozen decoder fixture; the visual does not compare method quality.

0x09J Training And Backward Paths Are Not Ranked By Trainable Scalars

For a layer whose output feeds the loss, backward still propagates:

even when is frozen and is not stored. The closest trainable leaf to the model input determines how much of that activation-gradient chain remains necessary:

MethodBase weight gradientsActivation-gradient pathDistinct training consequence
full fine-tuningselected dense base weightsfull selected graphdense gradient and optimizer state
prompt tuningnone for frozen baseoutput to input prompt across the full stackprompt is an input-boundary leaf
prefix tuningnone for frozen basefrom outputs to layer-wise prefix insertionsreparameterizer also receives prefix gradients
adapternone for frozen basethrough downstream blocks to every inserted modulenonlinear adapter activations are live for backward
IA3none for frozen basethrough downstream blocks to scale vectorselementwise scales still depend on backbone activations
BitFitselected bias gradientsthrough graph to each selected biasexisting parameter subset owns optimizer state
LoRAfactor gradients onlythrough downstream blocks and local low-rank branchesbase weight-gradient buffers are omitted, not activation gradients

This is the distinction A22’s resource section and retained Ladder Side-Tuning figure introduce earlier in 0x07. LST changes the graph itself by training a separate side network so backbone backward can be avoided. Ordinary in-backbone PEFT should not inherit that property merely because it updates few scalars.

Training recipes also differ beyond the operator:

  • Prompt Tuning’s paper studies prompt initialization, prompt length, optimizer settings, T5 scale, and LM adaptation of the frozen model.
  • Prefix-Tuning uses an MLP reparameterizer because direct prefix optimization was unstable in its experiments, then discards the MLP after training.
  • Houlsby adapters rely on near-identity initialization and train task-specific normalization and output state in addition to bottleneck modules.
  • IA3 initializes scales to one and T-Few combines the method with pretraining of IA3 state and additional task losses; those recipe components are not part of the bare IA3 operator.
  • BitFit trains a named bias subset and task head in BERT-family models.
  • LoRA initializes one factor so the initial dense update is zero and exposes rank, scaling, target, dropout, and merge choices derived earlier in this article.

An empirical comparison is valid only after these recipe differences are declared.

0x09K Artifact, Cache, And Serving Identity

Each method needs a different release manifest:

MethodMinimum task-artifact identityCache / request identity consequence
full fine-tuningexact transformed checkpoint or dense delta, base revision, tokenizer/configall generated state belongs to that task model revision
prompt tuningprompt tensor, , , input side, insertion order, base revisionprompt-conditioned hidden/K/V rows belong to prompt identity
prefix tuningmaterialized vs reparameterized format, layer/stage map, , K/V head geometry, dtype, base revisioneach layer’s prefix state and dependent token K/V belong to prefix identity
bottleneck adapterinsertion edges, pre/post-norm schedule, width, activation, task norms/head, base revisiondownstream hidden state and later-layer K/V depend on adapter identity
IA3K/V/FFN scale targets, channel axes, live vs folded representation, base revisionscaled K/V and dependent hidden state belong to IA3 identity
BitFitexact bias parameter paths, shapes, values, task head, base revisionprojected and downstream state belongs to BitFit identity
LoRAfactor orientation, rank, scale, target paths/slices, live vs merged state, base revisionK/V-targeted adapters directly alter cached rows; all targets alter model identity

A cached prefix cannot be keyed only by token IDs once task-specific state changes the model function. Even when an adapter does not change K/V shape, it can change K/V values directly or through earlier hidden states. Reusing cache across mismatched prompt, prefix, adapter, IA3, BitFit, LoRA, or full-model identities silently conditions future decoding on another function.

Mixed-task batching also follows the state location:

for request in batch:
    contract = resolve_adaptation_contract(request.task_id)
    validate_base_and_cache_identity(contract)

    if contract.kind == "prompt":
        prepend_virtual_embeddings(request, contract.prompt)
    elif contract.kind == "prefix":
        bind_layer_prefix_state(request, contract.materialized_prefix)
    elif contract.kind in {"adapter", "lora", "ia3"}:
        bind_request_specific_module_or_scale_state(request, contract)
    elif contract.kind in {"bitfit", "full_ft"}:
        route_to_compatible_weight_instance(request, contract.model_revision)

This pseudocode states identity, not a universal kernel. Prompt lengths may require padding or grouping; prefixes require layer-state plumbing; live LoRA, adapters, and IA3 require request-aware operators or batch segmentation; folded or dense-updated methods generally select a compatible weight instance. M6.5 owns the scheduler, residency, and fragmentation analysis.

0x09L Paper-By-Contract Comparison

The source papers answer different experimental questions on different architectures:

Primary paperFrozen model and task contextDirectly trained statePaper-specific detail that must survive retellingClaim that must not be generalized
Houlsby adapters v2BERT transfer over classification and QA taskstwo bottleneck adapters per layer, task LayerNorms, output layerpost-sublayer placement and near-identity initializationone adapter parameter percentage is not every adapter architecture
Prefix-Tuning v1GPT-2 table-to-text and BART summarizationsmall prefix table plus MLP reparameterizerall-layer prefix activations; discard reparameterizer after materializationdecoder-only K/V layout is not the only prefix layout
Prompt Tuning v2frozen T5 encoder-decoder modelsinput prompt embeddingsprompt length, initialization, scale, and frozen-model pretraining interactT5 source-reported quality does not establish causal-decoder cost or quality
LoRA v2RoBERTa, DeBERTa, GPT-2, and GPT-3 experimentslow-rank factors on selected weightsmergeable linear delta and target/rank choicesno-latency claim assumes a valid merge representation
BitFit v2BERT-family masked-LM encodersbiases or selected biases plus task classifierexisting-state selection is architecture namedquery/MLP bias subset is not universal across decoder graphs
IA3 / T-Few v2T0/T5 encoder-decoder few-shot tasksK, V, and FFN scaling vectorsseparate self/cross-attention counts, one initialization, mixed-task scales, optional foldT-Few losses and IA3 operator are not the same contract
LST v2T5 and CLIP-T5 transferseparate ladder side networkchanges the backward topology to avoid backbone backpropagationordinary LoRA/adapter/prompt training does not inherit LST memory behavior

Benchmark claims stay attached to those rows. This article does not use one paper’s score to rank methods under another paper’s model, data, objective, or parameter budget.

0x09M Alternatives Failure Lab

FailureBroken contractObservable consequence
equal trainable scalars treated as equal costtraining, runtime, and sequence ledgers collapsedmethod choice reverses under latency or memory measurement
prompt and prefix used as synonymsinput embeddings confused with all-layer statewrong parameter count, mask, cache shape, and artifact layout
prefix reparameterizer shipped as mandatory inference graphtraining and release representations conflatedavoidable serving state and compute
direct prefix K/V counted as ordinary prompt tokensprefix query rows inventedattention work is overcounted and masks are wrong
Houlsby count includes only once per blockbiases, second insertion, task norms/head omittedtask artifact and optimizer state are understated
adapter graph copied from post-norm BERT into a pre-norm decoder by nameinsertion edge omitteda different function is trained
IA3 called three vectors for every architectureencoder, decoder, and cross-attention structure ignoredtarget channels and parameter count are wrong
IA3 fold used while retaining per-request mixed-task selectionrepresentation and batching contracts conflictrequests need incompatible weights
BitFit budget inferred without graph enumerationabsent or extra biases ignoredcheckpoint paths fail or parameter count drifts
LoRA called latency-free while kept livemerge precondition omittedextra branch work and adapter-aware batching remain
frozen weights interpreted as no backbone backwardactivation-gradient path omittedtraining memory and time are understated
cache keyed only by tokensadaptation identity omittedlater decode uses state produced by a different function

Choosing an adaptation method now becomes a contract decision: what transformation family is needed, what training path is affordable, what task artifact can be released, what request identity the runtime can select, and what cache state remains valid. Popularity and parameter percentage are insufficient selectors.

0x0A Failure Lab

FailureBroken assumptionObservable symptom
LoRA called model compressionbase residency omitteddeployment memory is underestimated
assumed low rank because is factorizedwrong object analyzedmisleading theory and target selection
framework linear orientation ignoredrow/column convention mismatchshape error or transposed update
materialized every training steplow-rank execution path discardedunnecessary dense compute and memory
zero- initialization described as symmetric learningfirst-step gradient path ignoredincorrect optimizer analysis
rank reported without alpha/scalingtraining contract incompleteirreproducible rank comparison
declared rank treated as actual factor directions can be dependent or zerocapacity is overstated and padded-rank experiments are misread
alpha held fixed but rank study called scale-controlled changes with rankbranch and gradient magnitude changes are attributed only to capacity
dropout expectation treated as one deterministic forwardmean preservation confused with realization equalitystochastic variance and mask state disappear from the explanation
equal treated as equal optimization statefactor-coordinate gauge ignoredequivalent functions follow different factor-gradient trajectories
one dtype label used without cast boundariesstorage, accumulation, scale/add, and output precision collapsedlive/merged parity and underflow failures cannot be reproduced
rank reported without target modulesadaptation budget incompleteparameter and quality comparisons do not transfer
target path counted as module identityaliases are wrapped independentlyduplicate adapter state and broken shared behavior
substring match used as an undeclared target rulename near-match is treated as semantic identityunintended modules become trainable
equal packed chunks assumed for unequal GQA slicesstorage layout is inferred from namesQ/K/V deltas land on wrong rows
only one path in an object-alias group is reboundparent slots no longer resolve to one modulecall sites execute different wrappers
tied head delta merged into an untargeted embedding storagerole identity is collapsed into storage identitytoken embeddings change during head-only adaptation
replacement begins before graph preflight closesmutation and validation are interleavedfailed injection leaves a partially rewritten model
base weights removed from memory ledgerfrozen confused with absenttraining OOM despite small optimizer state
adapter state dict lacks base revisionsemantic compatibility uncheckedadapter loads but produces invalid behavior
portable adapter mislabeled exact-resume checkpointoptimizer and control state omittedresumed trajectory diverges immediately
scheduler advances after an AMP-skipped optimizer stepstep counters use different eventslearning-rate schedule drifts from parameter updates
RNG or sampler restored after the next drawsave values are correct but restore order is wrongnext mask or sample differs
path-local optimizer slots created for one aliased parameterpath occurrence confused with parameter identityduplicate or conflicting moments
merged release relabeled as canonical basederivation lineage discardedrollback and later adapter compatibility become ambiguous
base K/V reused after K/V projections changetarget-module state ignoredgeneration conditions on incompatible cache

0x0A1 A Failure Record Needs Five Coordinates

A three-column prose table is useful for recognition, but it is not yet an operational recovery contract. A replayable failure record needs five coordinates:

failure id and category:
    stable identity for the alarm class

invariant:
    the exact semantic statement that must remain true

detector:
    executable assertion, digest, tensor oracle, or lifecycle replay

consequence:
    the first observable state or value that becomes invalid

recovery owner:
    the article or release gate allowed to repair the contract

The owner field prevents a common integration error. M6.5 may change how live adapters are scheduled, but it may not silently redefine M6.3 factor orientation, path matching, checkpoint ABI, or merge semantics. M8 may define quantized deployment representations, but it may not promote a quantized merge to an exact vanilla-LoRA identity without a representation-specific proof.

0x0A2 Same Shape Does Not Mean Same Semantics

The most dangerous substitutions in this article preserve tensor shapes:

Shape-compatible substitutionDetectorFirst invalid stateRecovery owner
transpose and conventions consistently in metadata but not executionexact row/column forward oraclelive outputM6.3
normalize a selected target with another target’s scaletarget-local scale assertionbranch output and all local adjointsM6.3
wrap both paths of one aliased module independentlyobject-identity and unique-parameter ledgeradapter state and optimizer ownershipM6.3
split a packed GQA projection into equal thirdsexplicit row-slice manifestQ/K/V row ownershipM6.3
load portable factor tensors into a near-matching base revisionbase/config/target digest admissionmodel and cache identityM6.3, then M6.5 at admission
commit a merged dense artifact as if it were the immutable baselineage and representation-state assertionrollback and later compatibilityrelease gate
compare prompt, prefix, adapter, IA3, BitFit, and LoRA by scalar count aloneseven-method paper-contract oraclemethod-selection claimrelease gate

Output equality at one point is also insufficient. The Loop 10 transaction therefore checks object identity, live values, factor and input derivatives, portable namespace identity, merged representation identity, and rollback lineage at six different checkpoints. A later checkpoint cannot stand in for an earlier one.

0x0A3 Failures Belong To Different Alarms

The checked failure manifest groups 30 records from Loops 2-10 into five categories. Each category has a different first detector:

CategoryRepresentative invariantsFirst detectorTypical recovery
identity and statepath, object, parameter, storage, adapter slot, representation stateimmutable manifest and digest reconciliationrebuild the M6.3 injection or artifact plan
algebra and gradientorientation, rank, scale, dropout, cast schedule, local adjointsexact tensor and directional-derivative oraclerepair the M6.3 operator or derivation
graph and targetexact path, alias group, packed slices, tied storage, transactional rebindsnapshot-plan-commit graph oracleabort mutation and regenerate a complete plan
lifecycle and runtimeoptimizer/RNG order, portable namespace, cache identity, merged lineageinterrupted resume, renamed reload, and merge parityrestore canonical artifacts; M6.5 owns request/runtime recovery
evidence and scopepaper revision, code revision, toy/runtime distinction, source-reported resultsource, figure, URL, and claim-firewall auditrelease gate blocks publication or narrows the claim
Typed LoRA failure map connecting identity, algebra, graph, lifecycle, and evidence invariants to detectors, consequences, and canonical recovery owners

Transformer System static closure map. It records failure ownership and publication inventory; it is not browser-layout or production-runtime evidence.

Loop 11 closes the static publication surface rather than sampling it:

source records:
    3 local + 11 revision-pinned primary + 18 official/project = 32

article external URLs:
    29, all covered by the source manifest

local owner routes:
    5 normalized routes, all resolved in src/content/posts

A22 figures:
    53 source figures, each retained, replaced, or deferred exactly once

article image routes:
    33, all present, attributed, digest-pinned, and nonempty in alternative text

negative executable surface:
    9 invalid-fixture families
    566 one-field mutation cases across Loops 2-10

typed recovery surface:
    30 records covering every mechanism loop from 2 through 10

The 32 source records do not prove that every external endpoint will remain available. The 33 static image routes do not prove that text is legible in a browser. SVG roles, titles, descriptions, view boxes, file existence, digests, and alternatives can be checked without rendering; MathJax layout, responsive selection, horizontal overflow, theme contrast, console errors, and local-route acceptance require a browser. Loop 12 owns normalized desktop/mobile and light/dark browser evidence; the completed report below closes that narrower publication claim.

0x0A5 Final Formula, Rendering, Accessibility, And Boundary Closure

The final audit reconciles the source article, generated route, and browser as three different surfaces:

SurfaceExact closureWhat it does not prove
source189 source-ordered display formulas, 38 tables, 98 fenced code blocks, 33 image routes, and five normalized local post routesformal correctness of every derivation or future availability of external URLs
generated routeevery source display formula becomes one display MathJax source; all inline and display sources render without a MathJax errorcompatibility with every renderer or assistive technology
responsive images11 picture elements select their mobile source below the declared breakpoint and their desktop source above it; the remaining 11 images remain single-sourceperceptual quality under every zoom level, font override, or color-vision condition
four browser profilesdesktop/mobile and light/dark profiles load every image and local route, preserve heading targets, contain article-owned overflow, and produce no article console exceptionproduction adapter execution, GPU behavior, quality, latency, or live third-party endpoint health
boundariesconcrete M3.4, M5.1, M6.4, M6.5, M6.7, M6.9, and M8.1 owners resolve without long exact prose duplicationthat those neighboring articles are already complete

Browser acceptance does not promote a teaching oracle into framework or production evidence. It proves that this built M6.3 snapshot exposes the declared formulas, links, tables, alternatives, and responsive figures without article-local publication defects under the four normalized profiles. The source manifest still owns claim provenance, the executable fixtures still own their narrow tensor and lifecycle contracts, and later articles still own variant and serving behavior.

0x0B Boundary To M6.4 And M6.5

M6.3 ends with the vanilla mechanism fully specified.

M6.4 asks how major variants alter that mechanism:

QLoRA:
    base storage and training-memory path

AdaLoRA:
    rank budget allocation over modules and time

rsLoRA:
    rank-dependent scaling

LoRA+:
    separate learning rates for A and B

PiSSA and LoftQ:
    initialization around pretrained or quantized weights

DoRA:
    magnitude/direction parameterization

M6.5 then treats the adapter as a serving object:

loaded or evicted
merged or live
selected per request
executed in heterogeneous batches
co-resident with request KV
observed under latency and memory SLOs

0x0C Publish Gate

CheckStatus
Fine-tuning, PEFT families, rank, factorization, SVD, and target modules are established as prerequisitespass
Dense update and low-rank update parameter counts are derivedpass
Rank is explained geometrically and algebraicallypass
SVD motivation is separated from direct LoRA trainingpass
Forward tensors are traced with explicit shapespass
Backward gradients for , , and are derivedpass
Zero- first-step asymmetry is explainedpass
Rank, alpha, dropout, precision, and factor conditioning are separated into controlled contractspass
Target modules are compared by behavioral and state consequencepass
Path, object, packed-slice, parameter, and storage identities are separatedpass
Unequal packed Q/K/V adaptation is derived and K-slice zero-delta is executed exactlypass
Object aliases share one wrapper and tied-storage merge admission is role-awarepass
Injection is split into preflight planning and graph commitpass
A realistic multi-layer parameter ledger is includedpass
Training and optimizer state are separated from frozen base residencypass
Persistent adapter, gradient, optimizer-moment, master-copy, activation, and runtime ledgers are not collapsedpass
AdamW factor-coordinate moments and bias-correction state are derivedpass
Step-boundary and mid-accumulation checkpoint contracts are separatedpass
Portable adapter, exact-resume bundle, and merged release ABIs are distinctpass
Exact resume restores optimizer, scheduler, scaler, RNG, sampler, and counters before the next stochastic eventpass
Interrupted and uninterrupted teaching runs reconcile exactly under the declared oraclepass
Merge is a derived release path with evaluation and immutable rollback lineagepass
Distributed placement is separated from unique logical adapter identitypass
Adapter checkpoint metadata is explicitpass
PEFT target discovery and module replacement are traced from configuration to injected factorspass
Runtime adapter slots are separated from portable checkpoint keyspass
Reload validates graph reconstruction and tensor population as separate operationspass
Low-rank evidence is qualified rather than universalizedpass
Competing PEFT mechanisms are comparedpass
Prompt, prefix, bottleneck-adapter, and LoRA state locations are separated at tensor and runtime levelpass
Full fine-tuning, Prompt Tuning, Prefix-Tuning, Houlsby adapters, IA3, BitFit, LoRA, and LST remain distinct paper contractspass
Trainable, deployed, optimizer, runtime-graph, sequence-state, and backward-path ledgers are not collapsedpass
Prompt and direct-prefix causal attention work are derived from different query-row contractspass
Prefix training reparameterizer state is separated from materialized release statepass
Houlsby module, bias, placement, task-normalization, initialization, and runtime contracts are explicitpass
IA3 encoder, encoder-decoder, mixed-task live, and single-task folded forms are separatedpass
BitFit bias selection is counted from an explicit graph rather than generalized from BERT namespass
The seven-method decoder teaching ledger reconciles through the Loop 9 oraclepass
Every adaptation artifact participates in model and cache identitypass
QLoRA/DoRA variants and serving runtime have separate owner articlespass
Thirty typed failures bind invariants to detectors, consequences, and recovery ownerspass
All 32 sources, 29 external URLs, five local routes, 53 A22 figure dispositions, and 33 image routes reconcile staticallypass
Nine invalid-fixture families contain 566 one-field failures across Loops 2-10pass
Static Loop 11 closure remains distinct from normalized browser evidencepass
All 189 display formulas reconcile through the source registry and generated MathJax surfacepass
Desktop/mobile and light/dark profiles preserve images, local links, headings, tables, themes, and article-local overflow containmentpass
Browser closure is not promoted to live external availability, production runtime, quality, or latency evidencepass

0xFF References