kimhyunwoo commited on
Commit
cd2c1bd
ยท
verified ยท
1 Parent(s): fa5bd25

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +329 -19
index.html CHANGED
@@ -1,19 +1,329 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Transformer Latent Space Visualizer</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ 'neon-blue': '#00f3ff',
14
+ 'neon-purple': '#bc13fe',
15
+ 'neon-green': '#0aff00',
16
+ },
17
+ fontFamily: {
18
+ mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
19
+ }
20
+ }
21
+ }
22
+ }
23
+ </script>
24
+ <style>
25
+ body { margin: 0; background-color: #050505; color: white; overflow: hidden; }
26
+ .glass-panel {
27
+ background: rgba(10, 10, 20, 0.7);
28
+ backdrop-filter: blur(12px);
29
+ border: 1px solid rgba(255, 255, 255, 0.1);
30
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
31
+ }
32
+ canvas { outline: none; }
33
+ </style>
34
+
35
+ <!-- Import Maps for React & Three.js -->
36
+ <script type="importmap">
37
+ {
38
+ "imports": {
39
+ "react": "https://esm.sh/react@18.2.0",
40
+ "react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
41
+ "three": "https://esm.sh/three@0.160.0",
42
+ "@react-three/fiber": "https://esm.sh/@react-three/fiber@8.15.12?external=react,react-dom,three",
43
+ "@react-three/drei": "https://esm.sh/@react-three/drei@9.96.1?external=react,react-dom,three,@react-three/fiber",
44
+ "lucide-react": "https://esm.sh/lucide-react@0.303.0?external=react"
45
+ }
46
+ }
47
+ </script>
48
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
49
+ </head>
50
+ <body>
51
+ <div id="root"></div>
52
+
53
+ <script type="text/babel" data-type="module">
54
+ import React, { useState, useEffect, useRef, useMemo } from 'react';
55
+ import { createRoot } from 'react-dom/client';
56
+ import { Canvas, useFrame } from '@react-three/fiber';
57
+ import { Text, OrbitControls, Stars, Trail, Line } from '@react-three/drei';
58
+ import * as THREE from 'three';
59
+ import { Play, SkipForward, RefreshCw, Layers, BrainCircuit, Share2 } from 'lucide-react';
60
+
61
+ // --- MOCK DATA ENGINE ---
62
+ // ์‹ค์ œ ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋ฉด ๋„ˆ๋ฌด ๋ฌด๊ฑฐ์šฐ๋ฏ€๋กœ, ๋…ผ๋ฌธ ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ•œ "๊ฐ€์ƒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ์ดํ„ฐ"๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
63
+
64
+ const SAMPLE_TEXT = "The cat sat on the mat";
65
+ const TOKENS = SAMPLE_TEXT.split(" ");
66
+ const TOTAL_LAYERS = 6;
67
+
68
+ // ๊ฐ ํ† ํฐ์˜ 3D ์ขŒํ‘œ๋ฅผ ๋ ˆ์ด์–ด๋ณ„๋กœ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)
69
+ const generateLatentTrajectory = () => {
70
+ const trajectory = [];
71
+ // ์ดˆ๊ธฐ ์ž„๋ฒ ๋”ฉ (๋ฌด์ž‘์œ„ ๋ถ„์‚ฐ)
72
+ let currentPositions = TOKENS.map((_, i) => ({
73
+ x: (i - TOKENS.length/2) * 1.5, // ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์น˜ํ•˜๋˜
74
+ y: Math.random() * 2 - 1,
75
+ z: Math.random() * 2 - 1
76
+ }));
77
+ trajectory.push(JSON.parse(JSON.stringify(currentPositions)));
78
+
79
+ for (let l = 0; l < TOTAL_LAYERS; l++) {
80
+ // 1. Attention Step: ํ† ํฐ๋“ค์ด ์„œ๋กœ(์˜๋ฏธ์ ์œผ๋กœ ๋น„์Šทํ•œ ์• ๋“ค๋ผ๋ฆฌ) ๋ญ‰์นจ
81
+ // "The"(0)์™€ "the"(4)๊ฐ€ ๊ฐ€๊นŒ์›Œ์ง€๊ณ , "cat"(1)๊ณผ "mat"(5)๊ฐ€ ๊ฐ€๊นŒ์›Œ์ง€๋Š” ์‹
82
+ const attnPositions = currentPositions.map((pos, i) => {
83
+ let target = { ...pos };
84
+ if (i === 1) target.x += (currentPositions[5].x - pos.x) * 0.3; // cat -> mat
85
+ if (i === 5) target.x += (currentPositions[1].x - pos.x) * 0.3; // mat -> cat
86
+ if (i === 3) { // 'on' connects cat and mat
87
+ target.y += 1;
88
+ target.z += (currentPositions[1].z - pos.z) * 0.2;
89
+ }
90
+ // ๋…ธ์ด์ฆˆ ์ถ”๊ฐ€ (Latent space์˜ ๋ณต์žก์„ฑ ํ‘œํ˜„)
91
+ target.x += (Math.random() - 0.5) * 0.5;
92
+ target.y += (Math.random() - 0.5) * 0.5;
93
+ target.z += (Math.random() - 0.5) * 0.5;
94
+ return target;
95
+ });
96
+ trajectory.push(JSON.parse(JSON.stringify(attnPositions)));
97
+
98
+ // 2. MLP Step: ๋น„์„ ํ˜• ๋ณ€ํ™˜ (๊ณต๊ฐ„์„ ๋น„ํ‹€๊ฑฐ๋‚˜ ํ™•์žฅ)
99
+ const mlpPositions = attnPositions.map(pos => ({
100
+ x: pos.x * 1.1,
101
+ y: pos.y + Math.sin(pos.x) * 0.5, // ๋น„์„ ํ˜•์„ฑ
102
+ z: pos.z * 1.1
103
+ }));
104
+ trajectory.push(JSON.parse(JSON.stringify(mlpPositions)));
105
+ currentPositions = mlpPositions;
106
+ }
107
+ return trajectory;
108
+ };
109
+
110
+ const TRAJECTORY_DATA = generateLatentTrajectory();
111
+
112
+ // --- COMPONENTS ---
113
+
114
+ const TokenNode = ({ position, label, color, isActive, idx }) => {
115
+ const mesh = useRef();
116
+
117
+ useFrame((state) => {
118
+ if(mesh.current) {
119
+ // ๋ถ€๋“œ๋Ÿฌ์šด ์ด๋™ (Lerp)
120
+ mesh.current.position.lerp(new THREE.Vector3(position.x, position.y, position.z), 0.1);
121
+ // ํ™œ์„ฑ ์ƒํƒœ์ผ ๋•Œ ๋‘ฅ๋‘ฅ ๋– ๋‹ค๋‹˜
122
+ if(isActive) {
123
+ mesh.current.position.y += Math.sin(state.clock.elapsedTime * 2 + idx) * 0.002;
124
+ }
125
+ }
126
+ });
127
+
128
+ return (
129
+ <group>
130
+ {/* ๊ถค์ (Trail) ํšจ๊ณผ: Latent Geometry ์‹œ๊ฐํ™”์˜ ํ•ต์‹ฌ */}
131
+ <Trail width={0.2} length={8} color={new THREE.Color(color)} attenuation={(t) => t * t}>
132
+ <mesh ref={mesh} position={[position.x, position.y, position.z]}>
133
+ <sphereGeometry args={[0.3, 32, 32]} />
134
+ <meshStandardMaterial color={color} emissive={color} emissiveIntensity={isActive ? 2 : 0.5} toneMapped={false} />
135
+ </mesh>
136
+ </Trail>
137
+ {/* ํ…์ŠคํŠธ ๋ผ๋ฒจ */}
138
+ <Text position={[position.x, position.y + 0.5, position.z]} fontSize={0.3} color="white" anchorX="center" anchorY="middle">
139
+ {label}
140
+ </Text>
141
+ </group>
142
+ );
143
+ };
144
+
145
+ const AttentionLines = ({ positions, opacity }) => {
146
+ // ๋ชจ๋“  ํ† ํฐ ์Œ์„ ์—ฐ๊ฒฐํ•˜๋Š” ์„  (Attention Matrix ์‹œ๊ฐํ™”)
147
+ const lines = [];
148
+ for (let i = 0; i < positions.length; i++) {
149
+ for (let j = i + 1; j < positions.length; j++) {
150
+ const dist = Math.sqrt(
151
+ Math.pow(positions[i].x - positions[j].x, 2) +
152
+ Math.pow(positions[i].y - positions[j].y, 2) +
153
+ Math.pow(positions[i].z - positions[j].z, 2)
154
+ );
155
+ // ๊ฑฐ๋ฆฌ๊ฐ€ ๊ฐ€๊นŒ์šธ์ˆ˜๋ก(์—ฐ๊ด€์„ฑ์ด ๋†’์„์ˆ˜๋ก) ์ง„ํ•˜๊ฒŒ
156
+ const intensity = Math.max(0, 1 - dist / 3) * opacity;
157
+ if (intensity > 0.01) {
158
+ lines.push(
159
+ <Line
160
+ key={`${i}-${j}`}
161
+ points={[[positions[i].x, positions[i].y, positions[i].z], [positions[j].x, positions[j].y, positions[j].z]]}
162
+ color="#00f3ff"
163
+ transparent
164
+ opacity={intensity * 0.5}
165
+ lineWidth={1}
166
+ />
167
+ );
168
+ }
169
+ }
170
+ }
171
+ return <group>{lines}</group>;
172
+ };
173
+
174
+ const Scene = ({ step, trajectory }) => {
175
+ // ํ˜„์žฌ ์Šคํ…(0 ~ 12)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜„์žฌ ์œ„์น˜ ๊ณ„์‚ฐ
176
+ // trajectory ๋ฐฐ์—ด: [์ดˆ๊ธฐ, L1_Attn, L1_MLP, L2_Attn, L2_MLP, ...]
177
+ const currentPosIndex = Math.min(step, trajectory.length - 1);
178
+ const currentPositions = trajectory[currentPosIndex];
179
+
180
+ // ์ง์ˆ˜ ์Šคํ…์€ Attention ์งํ›„, ํ™€์ˆ˜ ์Šคํ…์€ MLP ์งํ›„๋ผ๊ณ  ๊ฐ€์ • (์‹œ๊ฐ์  ์—ฐ์ถœ)
181
+ const isAttentionPhase = step % 2 === 1;
182
+
183
+ return (
184
+ <group>
185
+ <ambientLight intensity={0.5} />
186
+ <pointLight position={[10, 10, 10]} intensity={1} color="#bc13fe" />
187
+ <pointLight position={[-10, -10, -10]} intensity={1} color="#00f3ff" />
188
+
189
+ {/* ํ† ํฐ๋“ค */}
190
+ {TOKENS.map((token, i) => (
191
+ <TokenNode
192
+ key={i}
193
+ idx={i}
194
+ position={currentPositions[i]}
195
+ label={token}
196
+ color={`hsl(${i * 60}, 80%, 60%)`}
197
+ isActive={true}
198
+ />
199
+ ))}
200
+
201
+ {/* Attention ์‹œ๊ฐํ™” (Attention ๋‹จ๊ณ„์ผ ๋•Œ๋งŒ ํ‘œ์‹œ) */}
202
+ <AttentionLines positions={currentPositions} opacity={isAttentionPhase ? 1 : 0} />
203
+
204
+ {/* ๋ฐฐ๊ฒฝ ๊ทธ๋ฆฌ๋“œ (Latent Space ๊ธฐ์ค€๏ฟฝ๏ฟฝ๏ฟฝ) */}
205
+ <gridHelper args={[20, 20, 0x333333, 0x111111]} position={[0, -2, 0]} />
206
+ <Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
207
+ <OrbitControls autoRotate autoRotateSpeed={0.5} />
208
+ </group>
209
+ );
210
+ };
211
+
212
+ // --- MAIN APP ---
213
+
214
+ const App = () => {
215
+ const [step, setStep] = useState(0);
216
+ const [isAuto, setIsAuto] = useState(false);
217
+
218
+ const totalSteps = TRAJECTORY_DATA.length;
219
+ const currentLayer = Math.floor((step - 1) / 2) + 1;
220
+ const phase = step === 0 ? "Embedding" : (step % 2 === 1 ? "Self-Attention" : "Feed Forward (MLP)");
221
+
222
+ useEffect(() => {
223
+ let interval;
224
+ if (isAuto) {
225
+ interval = setInterval(() => {
226
+ setStep(s => (s + 1) % totalSteps);
227
+ }, 1500);
228
+ }
229
+ return () => clearInterval(interval);
230
+ }, [isAuto, totalSteps]);
231
+
232
+ const getPhaseDescription = () => {
233
+ if (step === 0) return "ํ…์ŠคํŠธ ํ† ํฐ์ด ๊ณ ์ฐจ์› ๋ฒกํ„ฐ ๊ณต๊ฐ„(Latent Space)์— ๋งคํ•‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";
234
+ if (step % 2 === 1) return "ํ† ํฐ๋“ค์ด ์„œ๋กœ '์ฃผ๋ชฉ(Attention)'ํ•˜๋ฉฐ ๋ฌธ๋งฅ ์ •๋ณด๋ฅผ ๊ตํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์˜๋ฏธ๊ฐ€ ๊ด€๋ จ๋œ ํ† ํฐ๋ผ๋ฆฌ ๊ณต๊ฐ„์ƒ์—์„œ ์„œ๋กœ๋ฅผ ๋‹น๊น๋‹ˆ๋‹ค.";
235
+ return "๊ฐ ํ† ํฐ์ด ๊ฐœ๋ณ„์ ์œผ๋กœ ์ฒ˜๋ฆฌ(MLP)๋˜์–ด ์ •๋ณด๋ฅผ ๊ฐ€๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ž ์žฌ ๊ณต๊ฐ„ ๋‚ด์—์„œ ๊ธฐํ•˜ํ•™์  ๋ณ€ํ™˜์ด ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค.";
236
+ };
237
+
238
+ return (
239
+ <div className="w-full h-screen relative font-mono">
240
+ <Canvas camera={{ position: [0, 2, 8], fov: 50 }}>
241
+ <color attach="background" args={['#050505']} />
242
+ <fog attach="fog" args={['#050505', 5, 20]} />
243
+ <Scene step={step} trajectory={TRAJECTORY_DATA} />
244
+ </Canvas>
245
+
246
+ {/* UI Overlay */}
247
+ <div className="absolute top-0 left-0 w-full p-6 pointer-events-none flex justify-between items-start">
248
+ <div className="glass-panel p-6 rounded-xl max-w-md pointer-events-auto">
249
+ <h1 className="text-2xl font-bold text-neon-blue mb-2 flex items-center gap-2">
250
+ <BrainCircuit size={24} /> Transformer Internals
251
+ </h1>
252
+ <div className="space-y-4">
253
+ <div>
254
+ <div className="flex items-center justify-between text-xs text-gray-400 uppercase tracking-widest mb-1">
255
+ <span>Current State</span>
256
+ <span className="text-neon-green">Step {step} / {totalSteps - 1}</span>
257
+ </div>
258
+ <div className="text-xl text-white font-bold mb-1 flex items-center gap-2">
259
+ {step > 0 && <span className="text-neon-purple">Layer {currentLayer}</span>}
260
+ <span>{phase}</span>
261
+ </div>
262
+ <div className="h-1 w-full bg-gray-800 rounded-full overflow-hidden">
263
+ <div
264
+ className="h-full bg-neon-blue transition-all duration-500 ease-out"
265
+ style={{ width: `${(step / (totalSteps - 1)) * 100}%` }}
266
+ />
267
+ </div>
268
+ </div>
269
+ <p className="text-sm text-gray-300 leading-relaxed border-l-2 border-gray-600 pl-3">
270
+ {getPhaseDescription()}
271
+ </p>
272
+ </div>
273
+ </div>
274
+ </div>
275
+
276
+ <div className="absolute bottom-0 left-0 w-full p-6 flex flex-col items-center pointer-events-none">
277
+ <div className="glass-panel px-6 py-4 rounded-full flex items-center gap-6 pointer-events-auto mb-4">
278
+ <button
279
+ onClick={() => setStep(0)}
280
+ className="text-gray-400 hover:text-white transition-colors flex flex-col items-center gap-1"
281
+ >
282
+ <RefreshCw size={20} />
283
+ <span className="text-[10px]">RESET</span>
284
+ </button>
285
+
286
+ <button
287
+ onClick={() => setIsAuto(!isAuto)}
288
+ className={`w-12 h-12 rounded-full flex items-center justify-center border transition-all ${isAuto ? 'bg-neon-blue text-black border-neon-blue shadow-[0_0_15px_#00f3ff]' : 'border-white text-white hover:bg-white/10'}`}
289
+ >
290
+ {isAuto ? <div className="w-3 h-3 bg-black rounded-sm" /> : <Play size={20} fill="currentColor" className="ml-1" />}
291
+ </button>
292
+
293
+ <button
294
+ onClick={() => { setIsAuto(false); setStep(s => Math.min(s + 1, totalSteps - 1)); }}
295
+ className="text-gray-400 hover:text-white transition-colors flex flex-col items-center gap-1"
296
+ >
297
+ <SkipForward size={20} />
298
+ <span className="text-[10px]">NEXT</span>
299
+ </button>
300
+ </div>
301
+ <div className="text-xs text-gray-500 font-mono bg-black/50 px-3 py-1 rounded">
302
+ Based on "Visualizing LLM Latent Space Geometry" (2025)
303
+ </div>
304
+ </div>
305
+
306
+ {/* Side Indicators */}
307
+ <div className="absolute right-6 top-1/2 -translate-y-1/2 flex flex-col gap-4 pointer-events-none">
308
+ <div className={`glass-panel p-3 rounded-lg transition-all duration-300 ${phase === 'Embedding' ? 'border-neon-blue scale-110' : 'opacity-50'}`}>
309
+ <div className="text-xs text-gray-400">INPUT</div>
310
+ <div className="font-bold text-white">Embedding</div>
311
+ </div>
312
+ <div className={`glass-panel p-3 rounded-lg transition-all duration-300 ${phase === 'Self-Attention' ? 'border-neon-blue scale-110 shadow-[0_0_15px_rgba(0,243,255,0.3)]' : 'opacity-50'}`}>
313
+ <div className="text-xs text-gray-400">MIXING</div>
314
+ <div className="font-bold text-white flex items-center gap-2"><Share2 size={14}/> Attention</div>
315
+ </div>
316
+ <div className={`glass-panel p-3 rounded-lg transition-all duration-300 ${phase === 'Feed Forward (MLP)' ? 'border-neon-blue scale-110 shadow-[0_0_15px_rgba(0,243,255,0.3)]' : 'opacity-50'}`}>
317
+ <div className="text-xs text-gray-400">PROCESSING</div>
318
+ <div className="font-bold text-white flex items-center gap-2"><Layers size={14}/> MLP</div>
319
+ </div>
320
+ </div>
321
+ </div>
322
+ );
323
+ };
324
+
325
+ const root = createRoot(document.getElementById('root'));
326
+ root.render(<App />);
327
+ </script>
328
+ </body>
329
+ </html>