-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
259 lines (250 loc) · 17.4 KB
/
index.html
File metadata and controls
259 lines (250 loc) · 17.4 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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LLM Interview Hot 100 - 大模型面试手撕代码</title>
<meta name="description" content="LLM 时代的 Hot 100,大模型面试手撕代码必备题库,社区投票驱动的真实面试热度排行">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔥</text></svg>">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<style>
:root { --primary: #6366f1; --fire: #ef4444; --success: #22c55e; --bg: #f8fafc; --bg-card: #fff; --text: #1e293b; --text-muted: #64748b; --border: #e2e8f0; --gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%); }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
.lang-switch { position: fixed; top: 10px; right: 20px; z-index: 100; background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px; padding: 5px 15px; font-size: 0.9rem; }
.lang-switch a { text-decoration: none; }
.hero { min-height: 40vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px 20px; background: linear-gradient(180deg, #f8fafc 0%, #e0e7ff 100%); }
.logo { font-size: 3rem; margin-bottom: 10px; }
h1 { font-size: 2.5rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.tagline { font-size: 1.2rem; color: var(--text-muted); margin: 10px 0 20px; }
.highlight { color: var(--fire); font-weight: 600; }
.stats { display: flex; gap: 30px; margin-top: 20px; flex-wrap: wrap; justify-content: center; }
.stat { text-align: center; }
.stat-number { font-size: 1.8rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.stat-label { color: var(--text-muted); font-size: 0.85rem; }
.container { max-width: 1100px; margin: 0 auto; padding: 30px 20px; }
.section-title { text-align: center; font-size: 1.8rem; margin-bottom: 30px; }
.category-section { margin-bottom: 30px; background: var(--bg-card); border-radius: 12px; border: 1px solid var(--border); overflow: hidden; }
.category-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: #f8fafc; border-bottom: 1px solid var(--border); flex-wrap: wrap; gap: 10px; }
.category-title { font-size: 1.2rem; font-weight: 700; display: flex; align-items: center; gap: 8px; }
.category-links { display: flex; gap: 15px; }
.category-links a { color: var(--primary); text-decoration: none; font-size: 0.85rem; }
.topic-list { list-style: none; }
.topic-item { display: flex; align-items: center; padding: 12px 20px; border-bottom: 1px solid var(--border); gap: 15px; }
.topic-item:last-child { border-bottom: none; }
.topic-item:hover { background: #f8fafc; }
.topic-rank { width: 30px; font-weight: 700; color: var(--text-muted); text-align: center; }
.topic-rank.gold { color: #f59e0b; }
.topic-rank.silver { color: #9ca3af; }
.topic-rank.bronze { color: #d97706; }
.topic-info { flex: 1; min-width: 0; }
.topic-name { font-weight: 600; color: var(--text); text-decoration: none; display: block; }
.topic-name:hover { color: var(--primary); }
.topic-desc { font-size: 0.8rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topic-votes { display: flex; align-items: center; gap: 8px; }
.vote-count { color: var(--fire); font-weight: 600; min-width: 40px; text-align: center; }
.vote-btn { width: 36px; height: 36px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-card); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 1.1rem; transition: all 0.2s; }
.vote-btn:hover { border-color: var(--primary); background: #f0f0ff; }
.vote-btn.voted { background: var(--primary); border-color: var(--primary); color: white; }
.hot-badge { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; margin-left: 8px; }
.hot-3 { background: rgba(239,68,68,0.15); color: #dc2626; }
.hot-2 { background: rgba(251,146,60,0.15); color: #ea580c; }
.toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: #333; color: white; padding: 12px 24px; border-radius: 8px; font-size: 0.9rem; z-index: 1000; opacity: 0; transition: opacity 0.3s; }
.toast.show { opacity: 1; }
footer { padding: 30px 20px; text-align: center; border-top: 1px solid var(--border); }
footer p { color: var(--text-muted); margin-bottom: 8px; }
footer a { color: var(--primary); text-decoration: none; }
@media (max-width: 768px) { h1 { font-size: 1.8rem; } .stats { gap: 15px; } .topic-desc { display: none; } }
</style>
</head>
<body>
<div class="lang-switch"><a href="index.html" style="color:#6366f1;">中文</a> | <a href="index_en.html" style="color:#64748b;">EN</a></div>
<section class="hero">
<div class="logo">🔥</div>
<h1>LLM Interview Hot 100</h1>
<p class="tagline">LLM 时代的 <span class="highlight">Hot 100</span></p>
<p style="color: var(--text-muted);">大模型面试手撕代码 · 点击投票 · 实时热度排行</p>
<div class="stats">
<div class="stat"><div class="stat-number">63+</div><div class="stat-label">精选题目</div></div>
<div class="stat"><div class="stat-number">11</div><div class="stat-label">核心模块</div></div>
<div class="stat"><div class="stat-number" id="total-votes">—</div><div class="stat-label">社区投票</div></div>
<div class="stat"><div class="stat-number" id="user-votes">0</div><div class="stat-label">我的投票</div></div>
</div>
</section>
<section class="container">
<h2 class="section-title">📚 题目清单 <span style="font-size:0.6em;color:var(--text-muted);">(点击 👍 投票,按票数排序)</span></h2>
<div id="categories-container"><p style="text-align:center;color:var(--text-muted);">加载中...</p></div>
</section>
<footer>
<p><strong>#LLMHot100</strong> - LLM 时代的 Hot 100</p>
<p><a href="https://github.com/cdhx/LLM-Code-Hot-100">GitHub</a></p>
</footer>
<div class="toast" id="toast"></div>
<script>
const GITHUB_BASE='https://github.com/cdhx/LLM-Code-Hot-100/blob/main/';
const SUPABASE_URL='https://qxlhugrwofxnssqggbac.supabase.co';
const SUPABASE_KEY='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF4bGh1Z3J3b2Z4bnNzcWdnYmFjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQ0Nzg4OTYsImV4cCI6MjA5MDA1NDg5Nn0.Y0SaWTANFQkd-V6JP_8i_7Mk0JIEWV4FeZmGUxot9qQ';
let supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
const categories = [
{name:'LLM 基础',icon:'📖',doc:'docs/00-llm-basics.md',topics:[
{id:'gradient',name:'梯度与反向传播',anchor:'#梯度与反向传播',desc:'链式法则手推,深度学习根基'},
{id:'linear-reg',name:'线性回归',anchor:'#线性回归',desc:'y = Wx + b'},
{id:'logistic-reg',name:'逻辑回归',anchor:'#逻辑回归',desc:'sigmoid(Wx + b)'},
{id:'softmax-reg',name:'Softmax 回归',anchor:'#回归-vs-分类',desc:'多分类,LLM 输出层'},
{id:'mlp',name:'MLP 多层感知机',anchor:'#mlp-多层感知机',desc:'万能近似器'},
{id:'activations',name:'激活函数',anchor:'#激活函数',desc:'ReLU/GELU/SiLU'}]},
{name:'Attention 机制',icon:'��',doc:'docs/01-attention.md',topics:[
{id:'sdpa',name:'Scaled Dot-Product Attention',anchor:'#scaled-dot-product-attention',desc:'softmax(QK^T/√d)V'},
{id:'mha',name:'Multi-Head Attention',anchor:'#multi-head-attention',desc:'多头并行注意力'},
{id:'causal-mask',name:'Causal Mask',anchor:'#causal-mask',desc:'下三角掩码'},
{id:'gqa',name:'Grouped Query Attention',anchor:'#grouped-query-attention-gqa',desc:'GQA,LLaMA2 主流'},
{id:'mqa',name:'Multi-Query Attention',anchor:'#multi-query-attention-mqa',desc:'MQA'},
{id:'flash-attn',name:'Flash Attention',anchor:'#flash-attention-原理',desc:'分块计算,内存 O(N)'},
{id:'kv-cache-attn',name:'KV Cache',anchor:'#kv-cache',desc:'缓存历史 KV'},
{id:'cross-attn',name:'Cross Attention',anchor:'#cross-attention',desc:'Q来自decoder'}]},
{name:'归一化层',icon:'📏',doc:'docs/02-normalization.md',topics:[
{id:'layernorm',name:'Layer Normalization',anchor:'#layer-normalization',desc:'Transformer 标配'},
{id:'rmsnorm',name:'RMS Normalization',anchor:'#rms-normalization',desc:'LLaMA 用'},
{id:'batchnorm',name:'Batch Normalization',anchor:'#batch-normalization',desc:'CNN 常用'},
{id:'prenorm',name:'Pre-Norm vs Post-Norm',anchor:'#pre-norm-vs-post-norm',desc:'训练稳定性'}]},
{name:'位置编码',icon:'📍',doc:'docs/03-position-encoding.md',topics:[
{id:'sinusoidal',name:'Sinusoidal PE',anchor:'#sinusoidal-position-encoding',desc:'sin/cos 固定编码'},
{id:'learnable-pe',name:'Learnable PE',anchor:'#learnable-position-encoding',desc:'可学习嵌入'},
{id:'rope',name:'RoPE 旋转位置编码',anchor:'#rotary-position-embedding-rope',desc:'LLM 主流'},
{id:'alibi',name:'ALiBi',anchor:'#alibi',desc:'线性偏置'}]},
{name:'采样策略',icon:'��',doc:'docs/04-sampling.md',topics:[
{id:'greedy',name:'Greedy Decoding',anchor:'#greedy-decoding',desc:'argmax'},
{id:'temperature',name:'Temperature Sampling',anchor:'#temperature-sampling',desc:'控制随机性'},
{id:'topk',name:'Top-k Sampling',anchor:'#top-k-sampling',desc:'从 top-k 采样'},
{id:'topp',name:'Top-p Sampling',anchor:'#top-p-nucleus-sampling',desc:'累积概率截断'},
{id:'beam',name:'Beam Search',anchor:'#beam-search',desc:'保留 k 个最优'}]},
{name:'损失函数',icon:'📉',doc:'docs/05-loss-functions.md',topics:[
{id:'ce-loss',name:'Cross Entropy Loss',anchor:'#cross-entropy-loss',desc:'分类标配'},
{id:'lm-loss',name:'Language Model Loss',anchor:'#language-model-loss',desc:'next token'},
{id:'kl-div',name:'KL Divergence',anchor:'#kl-divergence',desc:'分布差异'},
{id:'mse-loss',name:'MSE Loss',anchor:'#mse-loss',desc:'回归'},
{id:'focal-loss',name:'Focal Loss',anchor:'#focal-loss',desc:'类别不平衡'},
{id:'sft-loss',name:'SFT Loss',anchor:'#sft-loss',desc:'只算 response'},
{id:'rm-loss',name:'Reward Model Loss',anchor:'#reward-model-loss',desc:'偏好学习'},
{id:'contrastive',name:'Contrastive Loss',anchor:'#contrastive-loss',desc:'对比学习'}]},
{name:'优化器',icon:'⚡',doc:'docs/06-optimizers.md',topics:[
{id:'sgd',name:'SGD',anchor:'#sgd',desc:'最基础'},
{id:'momentum',name:'SGD + Momentum',anchor:'#sgd-with-momentum',desc:'加动量'},
{id:'adam',name:'Adam',anchor:'#adam',desc:'自适应学习率'},
{id:'adamw',name:'AdamW',anchor:'#adamw',desc:'LLM 标配'},
{id:'lr-schedule',name:'学习率调度',anchor:'#learning-rate-scheduler',desc:'Warmup + Cosine'}]},
{name:'强化学习 (RLHF)',icon:'🎮',doc:'docs/07-reinforcement-learning.md',topics:[
{id:'reinforce',name:'REINFORCE',anchor:'#reinforce',desc:'策略梯度'},
{id:'gae',name:'GAE',anchor:'#gae',desc:'优势估计'},
{id:'ppo',name:'PPO',anchor:'#ppo',desc:'RLHF 核心'},
{id:'ppo-clip',name:'PPO-Clip',anchor:'#ppo',desc:'clip 版本'},
{id:'dpo',name:'DPO',anchor:'#dpo',desc:'直接偏好优化'},
{id:'grpo',name:'GRPO',anchor:'#grpo',desc:'DeepSeek 用'},
{id:'kl-penalty',name:'KL 惩罚',anchor:'#ppo',desc:'防止偏离'},
{id:'reward-shaping',name:'Reward Shaping',anchor:'#ppo',desc:'奖励工程'}]},
{name:'高效训练',icon:'🚀',doc:'docs/08-efficient-training.md',topics:[
{id:'lora',name:'LoRA',anchor:'#lora',desc:'低秩分解'},
{id:'qlora',name:'QLoRA',anchor:'#lora',desc:'LoRA + 4bit'},
{id:'grad-ckpt',name:'Gradient Checkpointing',anchor:'#gradient-checkpointing',desc:'时间换空间'},
{id:'mixed-precision',name:'Mixed Precision',anchor:'#mixed-precision-training',desc:'FP16/BF16'},
{id:'grad-accum',name:'Gradient Accumulation',anchor:'#gradient-accumulation',desc:'模拟大 batch'}]},
{name:'推理优化',icon:'⚡',doc:'docs/09-inference-optimization.md',topics:[
{id:'kv-cache',name:'KV Cache',anchor:'#kv-cache',desc:'加速自回归'},
{id:'paged-attn',name:'Paged Attention',anchor:'#pagedattention',desc:'vLLM 核心'},
{id:'spec-decode',name:'Speculative Decoding',anchor:'#speculative-decoding',desc:'投机解码'},
{id:'cont-batch',name:'Continuous Batching',anchor:'#continuous-batching',desc:'动态 batch'},
{id:'quantization',name:'Quantization',anchor:'#kv-cache',desc:'INT8/INT4'}]},
{name:'Transformer 架构',icon:'🏗️',doc:'docs/10-transformer-architecture.md',topics:[
{id:'encoder-only',name:'Encoder-Only (BERT)',anchor:'#transformer-概览',desc:'双向注意力'},
{id:'decoder-only',name:'Decoder-Only (GPT)',anchor:'#gpt-style-decoder-only',desc:'LLM 主流'},
{id:'enc-dec',name:'Encoder-Decoder (T5)',anchor:'#transformer-概览',desc:'seq2seq'},
{id:'ffn',name:'FFN',anchor:'#feed-forward-network',desc:'两层 MLP'},
{id:'swiglu',name:'SwiGLU',anchor:'#feed-forward-network',desc:'门控 FFN'}]}
];
let votes = {};
let userVotes = [];
function getUserId() {
let id = localStorage.getItem('llm-hot-100-user-id');
if (!id) { id = 'user_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('llm-hot-100-user-id', id); }
return id;
}
const userId = getUserId();
async function loadVotes() {
votes = JSON.parse(localStorage.getItem('llm-hot-100-votes') || '{}');
userVotes = JSON.parse(localStorage.getItem('llm-hot-100-user-votes') || '[]');
render();
try {
const { data: voteCounts } = await supabaseClient.from('votes').select('topic_id');
if (voteCounts) { votes = {}; voteCounts.forEach(v => { votes[v.topic_id] = (votes[v.topic_id] || 0) + 1; }); }
const { data: userVoteData } = await supabaseClient.from('votes').select('topic_id').eq('user_id', userId);
userVotes = userVoteData ? userVoteData.map(v => v.topic_id) : [];
render();
} catch (e) { console.error(e); }
}
async function vote(topicId) {
const isVoted = userVotes.includes(topicId);
try {
if (isVoted) {
await supabaseClient.from('votes').delete().eq('user_id', userId).eq('topic_id', topicId);
userVotes = userVotes.filter(id => id !== topicId);
votes[topicId] = Math.max(0, (votes[topicId] || 0) - 1);
} else {
await supabaseClient.from('votes').insert({ user_id: userId, topic_id: topicId });
userVotes.push(topicId);
votes[topicId] = (votes[topicId] || 0) + 1;
}
showToast(isVoted ? '已取消投票' : '投票成功!🎉');
} catch (e) {
console.error(e);
showToast('投票失败,请重试');
return;
}
localStorage.setItem('llm-hot-100-votes', JSON.stringify(votes));
localStorage.setItem('llm-hot-100-user-votes', JSON.stringify(userVotes));
render();
}
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2000);
}
function render() {
const totalVotes = Object.values(votes).reduce((a, b) => a + b, 0);
document.getElementById('total-votes').textContent = totalVotes || '—';
document.getElementById('user-votes').textContent = userVotes.length;
document.getElementById('categories-container').innerHTML = categories.map(cat => {
const sorted = [...cat.topics].sort((a, b) => (votes[b.id] || 0) - (votes[a.id] || 0));
return `<div class="category-section">
<div class="category-header">
<div class="category-title"><span>${cat.icon}</span><span>${cat.name}</span></div>
<div class="category-links">
<a href="${GITHUB_BASE}${cat.doc}" target="_blank">📄 查看</a>
</div>
</div>
<ul class="topic-list">${sorted.map((t, i) => {
const v = votes[t.id] || 0;
const r = i === 0 ? 'gold' : i === 1 ? 'silver' : i === 2 ? 'bronze' : '';
const badge = v >= 10 ? '<span class="hot-badge hot-3">🔥必考</span>' : v >= 5 ? '<span class="hot-badge hot-2">🔥高频</span>' : '';
const isVoted = userVotes.includes(t.id);
return `<li class="topic-item">
<span class="topic-rank ${r}">${i + 1}</span>
<div class="topic-info">
<a href="${GITHUB_BASE}${cat.doc}${t.anchor}" class="topic-name" target="_blank">${t.name}</a>${badge}
<div class="topic-desc">${t.desc}</div>
</div>
<div class="topic-votes">
<span class="vote-count">${v > 0 ? '🔥' + v : ''}</span>
<button class="vote-btn ${isVoted ? 'voted' : ''}" onclick="vote('${t.id}')" title="${isVoted ? '取消投票' : '投票'}">
${isVoted ? '✓' : '👍'}
</button>
</div>
</li>`;
}).join('')}</ul>
</div>`;
}).join('');
}
loadVotes();
</script>
</body>
</html>