-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlora-qlora-visual.html
More file actions
325 lines (302 loc) · 15.1 KB
/
Copy pathlora-qlora-visual.html
File metadata and controls
325 lines (302 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LoRA & QLoRA · 看图就懂</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600..800&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
--paper:#E9EDF4;
--paper-2:#F4F6FA;
--ink:#101638;
--ink-soft:#4A5478;
--frost:#5C79C4; /* 冻住的权重 */
--frost-pale:#B9C7E6;
--spark:#F0A020; /* 可训练的部分 */
--spark-deep:#C97407;
--line:rgba(16,22,56,.14);
--disp:"Bricolage Grotesque","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans SC",sans-serif;
--body:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans SC",system-ui,sans-serif;
--mono:"DM Mono","SF Mono",ui-monospace,monospace;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
margin:0;background:var(--paper);color:var(--ink);font-family:var(--body);
-webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* ---------- 版式 ---------- */
.panel{
min-height:88vh;padding:9vh 6vw;display:flex;flex-direction:column;
justify-content:center;align-items:center;text-align:center;gap:2.2rem;
border-bottom:1px solid var(--line);position:relative;
}
.panel.dark{background:var(--ink);color:var(--paper-2);border-bottom-color:rgba(255,255,255,.12)}
.panel.dark .cap{color:#A9B6DA}
.panel.dark .tag{border-color:rgba(255,255,255,.28);color:#A9B6DA}
.tag{
font-family:var(--mono);font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
color:var(--ink-soft);border:1px solid var(--line);border-radius:999px;padding:.35rem .9rem;
}
h1,h2{font-family:var(--disp);font-weight:800;line-height:1.02;margin:0;letter-spacing:-.02em}
h1{font-size:clamp(3rem,13vw,8.5rem)}
h2{font-size:clamp(1.9rem,6.2vw,4rem);max-width:16ch}
.cap{
font-size:clamp(1rem,2.4vw,1.35rem);line-height:1.7;color:var(--ink-soft);
max-width:34ch;margin:0;
}
.stage{width:min(880px,100%)}
svg{width:100%;height:auto;display:block;overflow:visible}
.lbl{font-family:var(--mono);font-size:13px;fill:var(--ink-soft)}
.lbl-b{font-family:var(--mono);font-size:15px;font-weight:500;fill:var(--ink)}
/* ---------- 首屏 ---------- */
#hero{min-height:100vh;gap:1.6rem;justify-content:center}
#hero .sub{font-size:clamp(.95rem,2.4vw,1.25rem);color:var(--ink-soft);max-width:30ch}
.amp{color:var(--frost);font-weight:600}
.scroll-hint{
position:absolute;bottom:3.5vh;font-family:var(--mono);font-size:.7rem;letter-spacing:.2em;
color:var(--ink-soft);animation:bob 2.4s ease-in-out infinite;
}
@keyframes bob{0%,100%{transform:translateY(0);opacity:.5}50%{transform:translateY(6px);opacity:1}}
/* ---------- 权重方格 ---------- */
.grid{
display:grid;grid-template-columns:repeat(28,1fr);gap:3px;width:min(760px,100%);
margin-inline:auto;
}
.grid .cell{aspect-ratio:1;border-radius:2px;background:var(--frost);transition:background .5s ease,transform .5s ease}
.grid.hot .cell{background:var(--spark);animation:tick 1.6s ease-in-out infinite}
@keyframes tick{0%,100%{opacity:.45}50%{opacity:1}}
.grid.frozen .cell{background:var(--frost-pale)}
/* ---------- 内存条 ---------- */
.bars{width:min(720px,100%);display:flex;flex-direction:column;gap:1.6rem;margin-inline:auto}
.bar-row{display:grid;grid-template-columns:7.5rem 1fr;align-items:center;gap:1rem;text-align:left}
.bar-name{font-family:var(--mono);font-size:.85rem;color:#A9B6DA}
.bar-track{position:relative;height:2.4rem}
.bar-fill{
height:100%;border-radius:4px;width:0;transition:width 1.1s cubic-bezier(.2,.7,.2,1);
display:flex;align-items:center;justify-content:flex-end;padding-right:.7rem;
font-family:var(--mono);font-size:.85rem;color:var(--ink);white-space:nowrap;
}
.bar-fill.f1{background:#8794C4}
.bar-fill.f2{background:var(--frost)}
.bar-fill.f3{background:var(--spark)}
.bar-fill.tiny{justify-content:flex-start;padding-left:calc(100% + .7rem);color:var(--spark)}
/* ---------- 便利贴 ---------- */
.sticky-wrap{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:1.6rem}
.base-box{
width:min(300px,72vw);aspect-ratio:1.35;border-radius:10px;background:var(--frost-pale);
border:2px solid var(--frost);display:flex;flex-direction:column;align-items:center;
justify-content:center;gap:.3rem;
}
.base-box b{font-family:var(--disp);font-size:1.5rem}
.base-box span{font-family:var(--mono);font-size:.8rem;color:var(--ink-soft)}
.stickies{display:flex;flex-direction:column;gap:.9rem}
.sticky{
background:var(--spark);border-radius:4px;padding:.85rem 1.3rem;text-align:left;
box-shadow:3px 4px 0 rgba(16,22,56,.16);min-width:190px;
}
.sticky b{display:block;font-size:1.05rem}
.sticky span{font-family:var(--mono);font-size:.72rem;color:var(--spark-deep)}
/* ---------- 图例 / 术语 ---------- */
.legend{display:flex;gap:1.6rem;flex-wrap:wrap;justify-content:center;font-family:var(--mono);font-size:.78rem;color:var(--ink-soft)}
.legend i{display:inline-block;width:.85rem;height:.85rem;border-radius:2px;vertical-align:-1px;margin-right:.45rem}
.terms{width:min(680px,100%);display:grid;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:6px;overflow:hidden}
.term{background:var(--paper-2);padding:1.1rem 1.3rem;display:grid;grid-template-columns:9rem 1fr;gap:1rem;text-align:left;font-size:.95rem}
.term b{font-family:var(--mono);font-weight:500;color:var(--frost)}
.term span{color:var(--ink-soft);line-height:1.6}
footer{padding:4rem 6vw 5rem;text-align:center;font-family:var(--mono);font-size:.75rem;color:var(--ink-soft)}
/* ---------- 入场 ---------- */
.reveal{opacity:0;transform:translateY(22px);transition:opacity .7s ease,transform .7s cubic-bezier(.2,.7,.2,1)}
.reveal.in{opacity:1;transform:none}
@media (max-width:640px){
.grid{grid-template-columns:repeat(18,1fr)}
.term{grid-template-columns:1fr;gap:.3rem}
.bar-row{grid-template-columns:1fr;gap:.4rem}
}
@media (prefers-reduced-motion:reduce){
*{animation:none!important;transition-duration:.01ms!important}
.reveal{opacity:1;transform:none}
}
</style>
</head>
<body>
<!-- 0 · 首屏 -->
<section class="panel" id="hero">
<div class="tag">大模型微调 · 图解</div>
<h1>LoRA<br><span class="amp">&</span> QLoRA</h1>
<p class="sub">怎么用一张显卡,教会一个几百亿参数的大模型做新事情。</p>
<div class="stage"><div class="grid" id="heroGrid" aria-hidden="true"></div></div>
<div class="scroll-hint">往下滑</div>
</section>
<!-- 1 · 模型是什么 -->
<section class="panel">
<div class="tag reveal">第一步</div>
<h2 class="reveal">大模型,其实就是一大堆数字</h2>
<div class="stage reveal"><div class="grid" id="g1" aria-hidden="true"></div></div>
<p class="cap reveal">每个小方格 = 一个「权重」。<br>大模型有 <b>650 亿</b> 个这样的方格。</p>
</section>
<!-- 2 · 全量微调 -->
<section class="panel">
<div class="tag reveal">问题</div>
<h2 class="reveal">想教它新东西?<br>就得改这些数字</h2>
<div class="stage reveal"><div class="grid hot" id="g2" aria-hidden="true"></div></div>
<p class="cap reveal">全部一起改,叫「全量微调」。<br>需要 <b>780GB</b> 显存起步 —— 装不下。</p>
</section>
<!-- 3 · LoRA -->
<section class="panel">
<div class="tag reveal">解法一</div>
<h2 class="reveal">LoRA:冻住大的,<br>只加两块小的</h2>
<div class="stage reveal">
<svg viewBox="0 0 880 300" role="img" aria-label="冻结的大矩阵,加上两块小矩阵 A 和 B">
<!-- 大矩阵 W -->
<rect x="10" y="40" width="220" height="220" rx="6" fill="#B9C7E6" stroke="#5C79C4" stroke-width="2"/>
<text x="120" y="150" text-anchor="middle" font-size="46" fill="#5C79C4">❄</text>
<text x="120" y="196" text-anchor="middle" class="lbl-b" fill="#3C558F">不动</text>
<text x="120" y="286" text-anchor="middle" class="lbl">原来的大矩阵 4096×4096</text>
<!-- + -->
<text x="272" y="165" text-anchor="middle" font-size="38" fill="#4A5478">+</text>
<!-- A -->
<rect x="318" y="40" width="26" height="220" rx="4" fill="#F0A020"/>
<text x="331" y="286" text-anchor="middle" class="lbl">A · 4096×8</text>
<!-- × -->
<text x="386" y="165" text-anchor="middle" font-size="30" fill="#4A5478">×</text>
<!-- B -->
<rect x="424" y="137" width="220" height="26" rx="4" fill="#F0A020"/>
<text x="534" y="286" text-anchor="middle" class="lbl">B · 8×4096</text>
<!-- 括号 -->
<path d="M318 305 L318 316 L644 316 L644 305" fill="none" stroke="#C97407" stroke-width="2"/>
<text x="481" y="338" text-anchor="middle" class="lbl-b" fill="#C97407">只训练这两块</text>
<!-- 数字对比 -->
<line x1="700" y1="40" x2="700" y2="260" stroke="rgba(16,22,56,.14)" stroke-width="1"/>
<text x="790" y="106" text-anchor="middle" class="lbl">要改的数字</text>
<text x="790" y="150" text-anchor="middle" font-family="DM Mono, monospace" font-size="30" fill="#5C79C4" text-decoration="line-through">1677 万</text>
<text x="790" y="182" text-anchor="middle" font-size="20" fill="#4A5478">↓</text>
<text x="790" y="218" text-anchor="middle" font-family="DM Mono, monospace" font-size="34" font-weight="500" fill="#C97407">6.5 万</text>
</svg>
</div>
<p class="cap reveal">一条又高又窄 × 一条又矮又宽,<br>拼起来正好盖住大矩阵 —— 但数字少了 99%。</p>
</section>
<!-- 4 · 量化 -->
<section class="panel dark">
<div class="tag reveal">解法二</div>
<h2 class="reveal">Q = 量化:<br>把每个数字压小</h2>
<div class="stage reveal">
<svg viewBox="0 0 880 260" role="img" aria-label="16 位精度的平滑渐变,压缩成 4 位的 16 个色阶">
<defs>
<linearGradient id="smooth" x1="0" x2="1">
<stop offset="0%" stop-color="#0E1330"/><stop offset="100%" stop-color="#F0A020"/>
</linearGradient>
</defs>
<text x="0" y="22" class="lbl" fill="#A9B6DA">原始 · 16 位</text>
<rect x="0" y="38" width="880" height="58" rx="4" fill="url(#smooth)"/>
<text x="880" y="22" text-anchor="end" class="lbl" fill="#A9B6DA">130 GB</text>
<text x="440" y="140" text-anchor="middle" font-size="26" fill="#A9B6DA">↓</text>
<text x="0" y="182" class="lbl" fill="#F0A020">压缩后 · 4 位 · 只留 16 个档位</text>
<g id="steps"></g>
<text x="880" y="182" text-anchor="end" font-family="DM Mono, monospace" font-size="15" fill="#F0A020">33 GB</text>
</svg>
</div>
<p class="cap reveal">像把高清图存成小图:<br>细节少一点,体积只剩 <b>1/4</b>。</p>
</section>
<!-- 5 · QLoRA -->
<section class="panel dark">
<div class="tag reveal">合起来</div>
<h2 class="reveal">QLoRA = 压缩过的大模型 + 小挂件</h2>
<div class="stage reveal">
<div class="bars" id="bars">
<div class="bar-row"><div class="bar-name">全量微调</div>
<div class="bar-track"><div class="bar-fill f1" data-w="100">780 GB+</div></div></div>
<div class="bar-row"><div class="bar-name">LoRA</div>
<div class="bar-track"><div class="bar-fill f2" data-w="26">约 200 GB</div></div></div>
<div class="bar-row"><div class="bar-name">QLoRA</div>
<div class="bar-track"><div class="bar-fill f3 tiny" data-w="6">48 GB · 一张显卡</div></div></div>
</div>
</div>
<p class="cap reveal">同样一个 650 亿参数的模型,<br>从「一整个机房」变成「一张卡」。</p>
</section>
<!-- 6 · 便利贴 -->
<section class="panel">
<div class="tag reveal">好处</div>
<h2 class="reveal">一个底座,<br>换不同的「便利贴」</h2>
<div class="stage reveal">
<div class="sticky-wrap">
<div class="base-box"><b>底座大模型</b><span>130 GB · 永远不变</span></div>
<div class="stickies">
<div class="sticky"><b>客服语气</b><span>+ 60 MB</span></div>
<div class="sticky"><b>法律文书</b><span>+ 60 MB</span></div>
<div class="sticky"><b>写代码</b><span>+ 60 MB</span></div>
</div>
</div>
</div>
<p class="cap reveal">训练出来的成果只有几十 MB,<br>想换技能,撕下来换一张就行。</p>
<div class="legend reveal"><span><i style="background:var(--frost-pale);outline:1px solid var(--frost)"></i>冻住 · 不训练</span><span><i style="background:var(--spark)"></i>可训练 · 很小</span></div>
</section>
<!-- 7 · 术语 -->
<section class="panel">
<div class="tag reveal">三个词</div>
<div class="stage reveal">
<div class="terms">
<div class="term"><b>微调 Fine-tune</b><span>拿一个练好的大模型,再教它一点新本事。</span></div>
<div class="term"><b>LoRA</b><span>大模型冻住不动,旁边挂两块小矩阵,只训练它们。</span></div>
<div class="term"><b>QLoRA</b><span>先把冻住的大模型压成 4 位,再做 LoRA。省到能塞进一张显卡。</span></div>
</div>
</div>
<p class="cap reveal">压缩会丢一点点精度,<br>但训练出来的效果,基本追平不压缩的做法。</p>
</section>
<footer>数字为常见量级示意 · 实际占用随模型与设置浮动</footer>
<script>
(function(){
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* 生成权重方格 */
function fill(id, n){
var el = document.getElementById(id); if(!el) return;
var f = document.createDocumentFragment();
for(var i=0;i<n;i++){
var d = document.createElement('div');
d.className = 'cell';
d.style.opacity = (0.3 + Math.random()*0.7).toFixed(2);
if(!reduce) d.style.animationDelay = (Math.random()*1.6).toFixed(2)+'s';
f.appendChild(d);
}
el.appendChild(f);
}
fill('heroGrid', 28*7);
fill('g1', 28*10);
fill('g2', 28*10);
/* 4 位色阶 */
var steps = document.getElementById('steps');
if(steps){
var w = 880/16;
for(var i=0;i<16;i++){
var r = document.createElementNS('http://www.w3.org/2000/svg','rect');
r.setAttribute('x', i*w + 1.5); r.setAttribute('y', 196);
r.setAttribute('width', w-3); r.setAttribute('height', 44);
r.setAttribute('rx', 3);
var t = i/15;
r.setAttribute('fill','rgb('+Math.round(14+226*t)+','+Math.round(19+141*t)+','+Math.round(48-16*t)+')');
steps.appendChild(r);
}
}
/* 滚动入场 */
var io = new IntersectionObserver(function(es){
es.forEach(function(e){
if(!e.isIntersecting) return;
e.target.classList.add('in');
io.unobserve(e.target);
if(e.target.id === 'bars'){
e.target.querySelectorAll('.bar-fill').forEach(function(b,i){
setTimeout(function(){ b.style.width = b.dataset.w + '%'; }, 120*i);
});
}
});
}, {threshold:.18});
document.querySelectorAll('.reveal').forEach(function(el){ io.observe(el); });
var bars = document.getElementById('bars');
if(bars) io.observe(bars);
})();
</script>
</body>
</html>