LoRA Factorization Viz
A teaching visualisation of Low-Rank Adaptation. Three matrix blocks sit side-by-side: the full d × d weight-update ΔW on the left, then =, then the factorization B (d × r) · A (r × d) on the right. Drag the rank slider and watch B and A shrink — the parameter-count readout shows the same story numerically: d² parameters collapse to 2 d r.
LoRA weight-update factorization visualisation.LoRA factorization: d=64, r=8. Full update has 4.1K parameters; LoRA uses 1.0K, a 75.0% reduction.
LoRA factorizationd=64 · r=8
8
- Full ΔW
- 4.1Kparams
- LoRA (B·A)
- 1.0Kparams
- Savings
- 75.0%
Customize
Shape
64
8
Detail
Installation
npx shadcn@latest add https://craftbits.dev/r/lora-factorization-viz.jsonUsage
import { LoRaFactorizationViz } from "@craft-bits/core";
<LoRaFactorizationViz d={64} defaultR={8} />Drive the rank from outside the component:
const [r, setR] = useState(8);
<LoRaFactorizationViz d={64} r={r} onRChange={setR} />Hide the full ΔW block to focus on the factorization alone:
<LoRaFactorizationViz d={64} defaultR={4} showFullMatrix={false} />Drop the parameter-count readout for a leaner figure:
<LoRaFactorizationViz d={64} defaultR={8} showParameterCount={false} />Understanding the component
- Three blocks, one equation. Left is
ΔW (d × d)— the full weight update, drawn as a speckled muted matrix so it reads as "every one ofd²entries is in play." Right is the factorization:B (d × r)accent-tinted in the top half, then a multiplication dot, thenA (r × d)accent-muted underneath. An=between them makes the identity explicit. - Thin dimension animates with
r. The width ofBand the height ofAare both bound to the rank — sliderfrom1up to⌊d / 2⌋and the two thin matrices fill out smoothly. The fullΔWblock stays the same size, so the gap between "how much we'd train without LoRA" and "how much we actually train" is visible at a glance. d² → 2 d rparameter math. Below the diagram, three stats render side-by-side: the full countd², the LoRA count2 d r, and the savings as a percentage. Withd = 64andr = 8that's4,096→1,024— a 75% reduction. Withd = 4,096andr = 8it's16.8M→65K— a 99.6% reduction.- Slider clamps to
⌊d / 2⌋. LoRA is only interesting whenr ≪ d. Pastd / 2the factorization stops being a compression — the slider stops there so the visualisation never lies. - Controlled + uncontrolled. Pass
r+onRChangeto drive the rank from a parent; omit them and the component owns its own state viadefaultR. - Reduced motion. When
prefers-reduced-motion: reduceis set, the spring onBandA's thin dimension collapses toduration: 0— the matrices snap to their new size instead of springing.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
d | number | 64 | Full model dimension. ΔW is rendered as d × d. |
r | number | — | Controlled rank. Pair with onRChange. Clamped to [1, ⌊d/2⌋]. |
defaultR | number | 8 | Uncontrolled initial rank. |
onRChange | (r) => void | — | Fires when the rank changes. |
showFullMatrix | boolean | true | Render the full ΔW block alongside the factorization. |
showParameterCount | boolean | true | Render the Full: d² / LoRA: 2 d r / Savings % readout. |
transition | Transition | SPRINGS.smooth | Spring for the size animation on B and A. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The root is
role="figure"witharia-labeldescribing the current dimensions and parameter counts. - A visually-hidden
aria-live="polite"summary mirrors the same line so screen readers re-announce as the rank slider moves. - The rank slider is a native
<input type="range">viaLabeledSlider— keyboard arrows, screen-reader value announcements, and:focus-visiblerings come for free. - Color is never the only signal — every matrix carries a text label (
ΔW,B,A) plus a dimension annotation (d × d,d × r,r × d), and the parameter-count panel renders the numbers infont-variant-numeric: tabular-nums. prefers-reduced-motion: reducecollapses every spring to an instant swap.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/LoRaFactorizationViz.tsx). The source was a lesson-mode widget with a phase system (observe→compressing→insight), narration strings, a breathing pulse, and a hard-codedD = 4096model dimension. The library extract is the pure factorization primitive — three matrix blocks, a rank slider, and a parameter-count readout. The phase / narration scaffolding lives in the consuming lesson, not the primitive.