CS336 Lecture 1: Overview and Tokenization

课程导读 这门课的讲义基于 notebook 组织,不同章节通过函数模块化展开。 开场老师提出了一个很尖锐的问题:研究者正在与底层技术逐步脱节。他给了一个时间线: 八年前,研究者会自己补充数据并训练模型; 六年前,研究者还会下载模型后进行 fine-tune; 现在,很多人直接向闭源模型(GPT-4/Claude/Gemini)提问。 这虽然有些夸张,但确实反映了趋势:模型能力提升后,很多基础环节被“封装”了。老师强调: “Full understanding of this technology is necessary for fundamental research” 随着模型规模持续增大,训练中的计算压力正在从 Attention 逐步转向 FFN。 课程收获 Mechanics:系统如何工作(如 Transformer 结构、GPU 并行方式) Mindset:如何最大化利用硬件与内存,如何看待 scaling laws Intuition:哪些数据和建模决策更可能带来更好结果 老师也指出一个常见误解:只要堆算力,模型就会自动变好。他给出的观点是:准确率 = 效率 × 资源。 在数据和资源固定时,效率决定了上限。 老师将近年的 LLM 工作大致分成两类: 闭源路线:以 GPT 系列为代表,最早系统性拥抱 Scale,但细节封闭。 开放权重路线:以 Qwen 等 open-weight 模型为代表,拥抱 Scale 的同时提升开放度,但常见情况是只公开部分训练细节,数据与失败案例仍不完整。 Tokenization 为什么需要 tokenization?因为 LLM 在 token 序列上建模概率分布,我们需要把原始字符串编码为 token,并保证可逆解码。 🔗 Tokenization 可视化工具: tiktokenizer.vercel.app 常见 tokenization 方法: ...

April 3, 2026 · 1 min

NanoGPT

所有之前:这篇是关于NanoGPT项目的核心代码的总结和理解,我会持续把主流技术加入到这个项目中,并对核心代码进行解释。完整项目代码需要查询NanoGPT. 模型架构 Base 最基础的模型架构是一个Decoder-only的Transformer模型,代码实现参考CodeByMyself,在CodeByMyself的基础上,使用了更现代化的Rope位置编码和SwiGLU激活函数, 并且加入了Moe用于和FFN对比,可在配置文件中选择是否使用Moe。部分代码如下: # swiglu class ffn_swiglu(nn.Module): def __init__(self, d_model, d_hidden, dropout=0.1): super().__init__() #第一个 Linear 输出 2 * d_hidden self.w_gate_up = nn.Linear(d_model, 2 * d_hidden) #同时生成 gate 和 up self.w_down = nn.Linear(d_hidden, d_model) self.dropout = nn.Dropout(dropout) def forward(self, x): # x: [B, L, d_model] gate_up = self.w_gate_up(x) # [B, L, 2 * d_hidden] gate, up = gate_up.chunk(2, dim=-1) # each: [B, L, d_hidden] swiglu_out = F.silu(gate) * up # [B, L, d_hidden] out = self.w_down(self.dropout(swiglu_out)) # [B, L, d_model] return out # moe class moe(nn.Module): def __init__(self,n_expert,d_model,top_k=2,dropout=0.1): super().__init__() self.d_model = d_model self.n_expert = n_expert self.top_k = top_k self.dropout = dropout self.gate = nn.Linear(d_model,n_expert,bias=False) self.softmax = nn.Softmax(dim=-1) # self.experts = nn.ModuleList([ffn(self.d_model,self.d_model*4,dropout) for _ in range(n_expert)]) relu激活的expert self.experts = nn.ModuleList([ffn_swiglu(self.d_model,self.d_model*2,dropout) for _ in range(n_expert)]) def forward(self,x): b,t,d = x.shape assert d == self.d_model,f"输入维度和moe设置维度不匹配" x_flat = x.view(-1,d) N = x_flat.shape[0] gate_logits = self.gate(x_flat) topk_weights,topk_indices = torch.topk(gate_logits,self.top_k,dim=-1) topk_weights= self.softmax(topk_weights) out = torch.zeros_like(x_flat) for i,expert in enumerate(self.experts): mask = (topk_indices == i) if not mask.any(): continue token_indices,expert_pos = torch.where(mask) select_x = x_flat[token_indices] expert_out = expert(select_x) weights = topk_weights[token_indices, expert_pos] out.index_add_(0, token_indices, expert_out * weights.unsqueeze(1)) return out.view(b,t,d) def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0): """ 预计算旋转角度的复数表示(cos + i*sin) """ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) t = torch.arange(end, device=freqs.device) freqs = torch.outer(t, freqs).float() # [end, dim//2] freqs_cos = freqs.cos() freqs_sin = freqs.sin() return freqs_cos, freqs_sin # 分开存储更便于后续操作,不使用torch.complex类型,因为部分系统不支持 SwiGLU激活函数的实现是通过将线性层的输出分成两部分,一部分作为gate,另一部分作为up,然后使用SILU函数对gate进行激活,并与up相乘得到最终的输出。 ...

November 24, 2025 · 2 min

CodeByMySelf-Transformer

所有之前:CodeByMySelf 系列之 Transformer 的实现记录,只有标题没有内容的是TODO,针对经典算法的变体和改进算法在其他文章。 Transformer 下面是一个最基本的Transformer实现,主要包含以下几个部分: 基本模块 多头注意力模块 Token和位置嵌入模块 LayerNorm 前馈神经网络FFN $EncoderLayer -> Encoder$ $DecoderLayer -> Decoder$ $Encoder+Decoder -> Transformer$ 多头注意力MHA 多头注意力机制的公式如下: $$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)W^O $$其中,每一个头的计算分别为: $$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V $$对于图像数据和序列数据,多头注意力机制的实现是不同,但是核心原理是相同的。对于序列数据,每个位置对应一个token;对于图像数据,每个位置对应一个patch块或者一个像素块,如果是$patch$块,需要展开到一维向量。这里实现的为序列数据的多头注意力机制,初始$shape$为$(batch,time,d_model)$ import torch from torch import nn import torch.functional as F import math class MultiHeadAttention(nn.Module): def __init__(self,d_model,n_head): super(MultiHeadAttention, self).__init__() assert d_model % n_head == 0 self.d_model = d_model self.n_head = n_head self.w_k = nn.Linear(d_model,d_model) self.w_q = nn.Linear(d_model,d_model) self.w_v = nn.Linear(d_model,d_model) self.w_conbine = nn.Linear(d_model,d_model,bias=False) # 对应W^O self.softmax = nn.Softmax(dim=-1) def forward(self,q,k,v,mask=None): batch,time,dimension = q.shape() # q,k,v shape: (batch,time,d_model) n_d = self.d_model // self.n_head q,k,v = self.w_q(q),self.w_k(k),self.w_v(v) print(q.shape, batch, time, self.n_head, n_d) q = q.view(batch,time,self.n_head,n_d).transpose(1,2) # self.n_head * n_d = d_model k = k.view(batch,time,self.n_head,n_d).transpose(1,2) # shape: (batch,n_head,time,n_d) v = v.view(batch,time,self.n_head,n_d).transpose(1,2) # compute attention by the formula score = q @ k.transpose(-2,-1) / math.sqrt(n_d) # (batch,n_head,time,time) if mask is not None: score = score.masked_fill(mask==0,float('-inf')) attn = self.softmax(score) @ v # (batch,n_head,time,n_d) attn = attn.transpose(1,2).contiguous().view(batch,time,dimension) # (batch,time,dimension) attn = self.w_conbine(attn) # (batch,time,dimension) return attn d_model = 512 n_head = 8 mha = MultiHeadAttention(d_model,n_head) x = torch.randn(2,10,d_model) y = mha(x,x,x) # self-multi-head-attention print(f"shape: {y.shape}\n输出:{y}") # torch.Size([2, 10, 512]) Token和位置嵌入模块 Token Embedding 是将离散的词(token)转换为连续的向量表示的过程。通常通过查找表(如 nn.Embedding)将每个词的索引映射为一个高维向量,使模型能够处理和学习词之间的语义关系。 公式表示: ...

September 24, 2025 · 5 min