news 2026/11/3 16:32:34

从NumPy到LLM:程序员手写Transformer入门指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从NumPy到LLM:程序员手写Transformer入门指南

1. 从一行 NumPy 说起:为什么程序员学 LLM 要从数组开始

很多人第一次接触大模型,脑子里全是“注意力机制”“千亿参数”“分布式训练”这些大词,结果打开教程第一行代码就是import numpy as np,瞬间觉得被糊弄了——我都准备搞人工智能了,你让我算数组?我当初也是这么想的,直到自己动手把一个小型 Transformer 从零实现了一遍,才发现 NumPy 根本不是“前菜”,它是整桌菜的后厨。你在大模型里看到的每一个 Token 向量、每一层权重矩阵、每一次前向传播,底层全都是多维数组在搬运数据。Tensor 这个概念听起来高级,说白了就是 NumPy 数组加了自动求导和 GPU 加速,PyTorch 的torch.Tensor和np.ndarray在内存布局和索引方式上几乎是一一对应的。

这篇文章适合谁看?如果你是一个会写 Python、用过 NumPy 做数据分析、但对 LLM 内部到底怎么运转还没有系统认知的程序员,那这篇笔记就是写给你的。我不会一上来就甩一堆数学公式,而是沿着我自己学习的路径,从 NumPy 的数组操作讲到 Transformer 的核心结构,再到大模型的 Token 机制和推理流程,把“一行 NumPy”和“一个大模型”之间的那条线给串起来。核心关键词会自然穿插在每一节里:NumPy、LLM、Transformer、Tensor、Shape,这五个词基本覆盖了从底层计算到上层架构的完整链路。

先说说我为什么坚持从 NumPy 入手。市面上的 LLM 教程大致分两类:一类是纯理论,满屏公式推导,看完不知道代码怎么写;另一类是纯调包,from transformers import AutoModel三行跑通,但模型里面发生了什么完全黑盒。这两类之间缺了一座桥,而 NumPy 就是这座桥最合适的建材。你用 NumPy 手写一遍矩阵乘法、手算一次注意力分数、手动 reshape 一个 Tensor,再去看 PyTorch 的源码,会发现那些看似神秘的 API 不过是对数组操作的封装。这个过程就像学开车先学手动挡,虽然现在都是自动挡,但你知道离合器怎么工作,遇到坡道起步心里不慌。

还有一个现实原因:NumPy 的环境问题几乎是每个新手必经的坑。热搜词里“python安装numpy库的方法”“numpy版本不匹配”“ModuleNotFoundError: No module named 'numpy'”这些高频出现,说明大量人在第一步就卡住了。我见过太多人因为 NumPy 装不上、版本冲突、和 PyTorch 不兼容,直接放弃了整个学习路径。所以这篇笔记也会把这些环境层面的实操细节讲清楚,让你少走弯路。

2. NumPy 基础回顾:从数组创建到 Shape 理解

2.1 安装 NumPy 的正确姿势与版本管理

先把最基础的事情说清楚。安装 NumPy 本身不复杂,但“装对版本”这件事值得单独拎出来讲。最直接的方式是pip install numpy,但如果你后续要装 PyTorch 或 TensorFlow,版本兼容性就成了必须考虑的问题。我的习惯是先用虚拟环境隔离项目,再根据深度学习框架的要求反推 NumPy 版本。

python -m venv llm_env source llm_env/bin/activate # Windows 用 llm_env\Scripts\activate pip install numpy

装完之后验证一下:

import numpy as np print(np.__version__)

如果遇到ModuleNotFoundError: No module named 'numpy',九成是虚拟环境没激活,或者 pip 装到了系统 Python 而不是当前环境。这时候用which python和which pip确认路径是否一致,比盲目重装有效得多。另一个常见问题是 NumPy 版本和 PyTorch 不匹配,比如 PyTorch 2.0 要求 NumPy 1.21 以上,但你环境里是 1.19,导入时就会报二进制兼容错误。解决办法是先装框架再让它自动拉取兼容的 NumPy,或者查框架的官方兼容性表格手动指定版本。

提示:不要用pip install --upgrade numpy无脑升级。很多老项目依赖 NumPy 1.x 的 API,升到 2.x 后np.float_、np.int_这类别名被移除,代码直接跑不起来。升级前先看项目的 requirements。

2.2 从 list 到 ndarray:快在哪里

热搜里有个问题很典型:“numpy 和 list 比快在哪?”我用一个实际测试来说明。假设要计算一百万个数的平方和:

import numpy as np import time data_list = list(range(1000000)) data_array = np.arange(1000000) # list 方式 start = time.time() sum([x * x for x in data_list]) print("list:", time.time() - start) # numpy 方式 start = time.time() np.sum(data_array ** 2) print("numpy:", time.time() - start)

实测下来 NumPy 通常快 20 到 50 倍。原因有两个:一是 NumPy 底层用 C 实现,循环在编译层完成,没有 Python 解释器的开销;二是 ndarray 在内存中是连续存储的同类型数据,CPU 缓存命中率高,而 list 存的是指针数组,每个元素都是独立的 Python 对象,散落在内存各处。这个差异在 LLM 场景下被无限放大,因为大模型里动辄几百万个浮点数做矩阵乘法,用 list 根本不可行。

2.3 Shape 思维:LLM 里最重要的直觉

如果说 NumPy 有哪个概念对学 LLM 帮助最大,我的答案是 Shape。你去看 Transformer 的代码,一半的 bug 都是 Shape 对不上。养成“每做一步操作就打印 shape”的习惯,比背公式有用得多。

a = np.random.randn(2, 3, 4) # 2 个样本,每个 3 行 4 列 print(a.shape) # (2, 3, 4) b = a.reshape(2, 12) print(b.shape) # (2, 12) c = a.transpose(0, 2, 1) print(c.shape) # (2, 4, 3)

在 LLM 里,一个 batch 的输入通常是(batch_size, seq_len),经过 embedding 层变成(batch_size, seq_len, d_model),再进多头注意力会拆成(batch_size, num_heads, seq_len, d_head)。你如果能看着 shape 就知道数据现在处于哪个阶段,调试效率会高一个量级。我自己的做法是在写模型代码时,每个关键步骤后面加一行print(x.shape),跑通之后再删掉,这个笨办法帮我省了无数时间。

3. 从数组到 Tensor:理解大模型的数据载体

3.1 Tensor 到底是什么

Tensor 这个词被翻译成“张量”,听起来很唬人,但你可以先把它理解成“NumPy 数组的升级版”。它比 ndarray 多了两个能力:自动求导和硬件加速。自动求导是训练神经网络的基础,你需要知道每个参数对损失函数的梯度,才能用梯度下降更新参数。硬件加速则是把矩阵运算搬到 GPU 上,用成千上万个核心并行计算。

import torch x = torch.tensor([[1.0, 2.0], [3.0, 4.0]], requires_grad=True) y = x ** 2 z = y.sum() z.backward() print(x.grad) # 输出 dz/dx = 2x

这段代码如果用 NumPy 写,你得手动推导导数并实现。Tensor 把这个过程自动化了,你只需要定义前向计算,反向传播由框架完成。这就是为什么 LLM 的训练框架都建立在 Tensor 之上,而不是直接用 NumPy。

3.2 Tensor 的 Shape 语义在 LLM 中的含义

在 LLM 语境下,Tensor 的每个维度都有明确语义。以最常见的输入为例:

维度含义典型值
batch_size一次处理多少条文本8、16、32
seq_len每条文本有多少个 Token128、512、2048
d_model每个 Token 的向量维度512、768、4096
num_heads注意力头数量8、12、16
d_head每个头的维度d_model / num_heads

当你看到(batch_size, seq_len, d_model)这个 shape,脑子里应该浮现出:一个三维的数据块,第一维是样本,第二维是时间步,第三维是每个时间步的特征向量。Transformer 的所有操作,本质上都是在这个三维块上做变换。理解了这一点,再看那些复杂的架构图,就不会觉得无从下手。

3.3 NumPy 与 Tensor 的互转与踩坑

实际项目中经常需要在 NumPy 和 Tensor 之间转换,比如用 NumPy 做数据预处理,再喂给 PyTorch 模型。转换本身很简单:

import numpy as np import torch np_array = np.random.randn(2, 3) tensor = torch.from_numpy(np_array) back_to_np = tensor.numpy()

但有几个坑要注意。第一,torch.from_numpy创建的 Tensor 和原 NumPy 数组共享内存,改一个另一个也会变。如果你不想共享,用torch.tensor(np_array)会复制一份。第二,NumPy 默认是 float64,而 PyTorch 默认是 float32,转换后如果要做矩阵乘法,dtype 不一致会报错。第三,GPU 上的 Tensor 不能直接转 NumPy,必须先.cpu()再.numpy()。这些细节在调试时经常让人抓狂,但记住之后就是肌肉记忆。

4. Transformer 核心机制拆解:注意力到底在算什么

4.1 用 NumPy 手写一个注意力

Transformer 的核心是自注意力机制,而自注意力的核心是三个矩阵:Query、Query、Value。热搜里有个很形象的比喻:“key 我是谁、query 我在找什么、value 我能提供什么”。这个比喻抓住了本质。我用 NumPy 写一个最简版本,帮你建立直觉。

import numpy as np def softmax(x): exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) return exp_x / np.sum(exp_x, axis=-1, keepdims=True) seq_len = 4 d_model = 8 # 模拟输入:4 个 Token,每个 8 维 x = np.random.randn(seq_len, d_model) # 三个投影矩阵 W_q = np.random.randn(d_model, d_model) W_k = np.random.randn(d_model, d_model) W_v = np.random.randn(d_model, d_model) Q = x @ W_q # (4, 8) K = x @ W_k # (4, 8) V = x @ W_v # (4, 8) # 计算注意力分数 scores = Q @ K.T / np.sqrt(d_model) # (4, 4) attention_weights = softmax(scores) # (4, 4) output = attention_weights @ V # (4, 8)

这段代码不到 20 行,但包含了自注意力的全部核心逻辑。Q @ K.T计算的是每个 Token 和其他所有 Token 的相关性,除以sqrt(d_model)是为了防止点积结果过大导致 softmax 梯度消失,softmax 把分数归一化成概率分布,最后用这些概率对 V 加权求和。你把这个过程理解透了,再看多头注意力、交叉注意力,都只是在这个基础上的变体。

4.2 多头注意力:为什么要拆成多个头

单个注意力头只能捕捉一种模式的相关性,多头注意力让模型同时关注不同类型的关系。实现上就是把 d_model 拆成 num_heads 份,每份独立做注意力,最后拼接起来。

def multi_head_attention(x, num_heads, d_model): seq_len = x.shape[0] d_head = d_model // num_heads Q = x @ np.random.randn(d_model, d_model) K = x @ np.random.randn(d_model, d_model) V = x @ np.random.randn(d_model, d_model) # 拆头:(seq_len, d_model) -> (num_heads, seq_len, d_head) Q = Q.reshape(seq_len, num_heads, d_head).transpose(1, 0, 2) K = K.reshape(seq_len, num_heads, d_head).transpose(1, 0, 2) V = V.reshape(seq_len, num_heads, d_head).transpose(1, 0, 2) scores = Q @ K.transpose(0, 2, 1) / np.sqrt(d_head) weights = softmax(scores) out = weights @ V # (num_heads, seq_len, d_head) # 拼头 out = out.transpose(1, 0, 2).reshape(seq_len, d_model) return out

这里最关键的操作是 reshape 和 transpose。(seq_len, d_model)变成(num_heads, seq_len, d_head)的过程,就是把最后一维切成 num_heads 份。transpose 是为了让矩阵乘法在正确的维度上进行。如果你对 shape 变换不熟练,这段代码会看得非常痛苦。我的建议是拿一张纸,把每一步的 shape 画出来,手动推一遍,比看十遍教程都管用。

4.3 位置编码:Transformer 为什么需要它

自注意力本身是位置无关的,也就是说把输入序列打乱,注意力计算的结果不变。但语言是有顺序的,“我打你”和“你打我”意思完全相反。所以 Transformer 需要额外注入位置信息,这就是位置编码的作用。

def positional_encoding(seq_len, d_model): pe = np.zeros((seq_len, d_model)) for pos in range(seq_len): for i in range(0, d_model, 2): pe[pos, i] = np.sin(pos / (10000 ** (i / d_model))) pe[pos, i + 1] = np.cos(pos / (10000 ** (i / d_model))) return pe

原始 Transformer 用的是正弦余弦编码,后来很多模型改用可学习的位置嵌入。两种方式各有优劣,正弦编码可以外推到比训练时更长的序列,可学习嵌入则更灵活。实际用的时候,你不需要自己实现,框架都封装好了,但知道原理能帮你在遇到位置相关 bug 时快速定位。

5. 从 Transformer 到 LLM:Token、推理与部署

5.1 Token 机制:文本怎么变成数字

LLM 处理的最小单位不是字也不是词,而是 Token。Token 是介于字符和词之间的一个粒度,英文里一个常见词可能是一个 Token,生僻词会被拆成多个子词。中文里一个字可能是一个 Token,也可能多个字组成一个 Token,取决于分词器的词表。

# 以 GPT 系列常用的 BPE 分词器为例(伪代码示意) text = "程序员的 LLM 学习笔记" tokens = tokenizer.encode(text) print(tokens) # [1234, 5678, 9012, ...]

Token 的数量直接决定了计算量和显存占用。Transformer 的注意力计算复杂度是 O(seq_len^2),也就是说序列长度翻倍,计算量变成四倍。这就是为什么长上下文模型需要专门的优化技术,比如稀疏注意力、滑动窗口注意力等。你在实际使用中如果发现模型处理长文本特别慢,根源就在这里。

5.2 LLM 推理流程:一次前向传播发生了什么

当你向 LLM 输入一段话,模型内部大致经历这些步骤:文本先被分词成 Token 序列,每个 Token 查嵌入表变成一个向量,加上位置编码后进入多层 Transformer 块,每层包含多头注意力和前馈网络,最后经过输出层映射到词表维度,用 softmax 得到下一个 Token 的概率分布。

# 简化版推理流程示意 def generate(model, input_ids, max_new_tokens): for _ in range(max_new_tokens): logits = model(input_ids) # (batch, seq_len, vocab_size) next_token_logits = logits[:, -1, :] next_token = np.argmax(next_token_logits, axis=-1) input_ids = np.concatenate([input_ids, next_token[:, None]], axis=1) return input_ids

实际推理时不会用 argmax 这么简单,而是用温度采样、top-k、top-p 等策略来控制生成的多样性。温度越高,输出越随机;温度越低,输出越确定。这些参数你在调用 API 时经常看到,现在应该知道它们作用在哪一步了。

5.3 部署 LLM 的几种路径

训练一个大模型不是个人开发者能轻易做到的,但部署和推理是可行的。常见路径有几种:一是用 Hugging Face 的 transformers 库直接加载开源模型,适合快速验证;二是用 ONNX 导出模型再部署,适合对性能有要求的场景;三是用量化技术把模型压缩到更小的显存占用,比如 4-bit 量化能让 7B 模型在消费级显卡上跑起来。

部署方式适用场景显存要求难度
transformers 直接加载快速验证、研究高低
ONNX 导出生产环境、跨平台中中
量化推理消费级硬件低中高
推理框架专用格式高并发服务中高

我自己的经验是,先用 transformers 把流程跑通,确认模型效果符合预期,再考虑优化部署。不要一上来就追求极致性能,那样容易在环境配置上耗光耐心。

6. 常见问题与排查技巧实录

6.1 NumPy 相关高频问题速查

问题原因解决方法
ModuleNotFoundError: No module named 'numpy'未安装或环境不对激活虚拟环境后 pip install numpy
numpy 版本不匹配框架要求特定版本查框架文档,指定版本安装
矩阵乘法报错shape 不满足广播规则检查并调整维度
内存溢出数组过大用 float32 替代 float64,分批处理
逆矩阵求解失败矩阵奇异检查行列式,用伪逆替代

6.2 Transformer 调试中的典型坑

第一个坑是 shape 对不上。注意力的 Q 和 K 做矩阵乘法时,要求 K 的最后两维转置后能和 Q 对齐。如果你忘了 transpose,报错信息通常很隐晦,只说维度不匹配,不会告诉你该转置哪个。我的做法是在每个矩阵乘法前打印两个操作数的 shape,一眼就能看出问题。

第二个坑是 mask 用错。训练时需要用 causal mask 防止模型看到未来 Token,推理时如果忘了加 mask,生成结果会变得很奇怪。mask 的本质是一个上三角为负无穷的矩阵,加到注意力分数上,softmax 之后未来位置的权重就变成零。

def causal_mask(seq_len): mask = np.triu(np.ones((seq_len, seq_len)), k=1) * -1e9 return mask

第三个坑是位置编码的维度对不上。位置编码的 shape 必须和输入 embedding 的 shape 一致,否则相加时会广播错误。这个错误通常发生在你修改了 d_model 但忘了同步修改位置编码的实现。

6.3 学习路径上的避坑建议

我踩过最大的坑是贪多。一开始同时看三四个教程,每个都讲得不一样,越看越乱。后来我定了一个规矩:只跟一个教程,从头到尾手敲一遍代码,跑通之后再去看别的资料做补充。这个办法看起来慢,实际上最快。

另一个建议是不要跳过 NumPy。很多人觉得 NumPy 太基础,直接上 PyTorch,结果遇到 shape 问题、广播问题、dtype 问题,完全不知道从哪查。NumPy 的报错信息比 PyTorch 清晰得多,在 NumPy 里把数组操作练熟,到了 PyTorch 里就是降维打击。

最后一个建议是动手写。看懂了和写出来是两回事。我自认为注意力机制理解得不错,直到自己从零实现一遍,才发现有那么多细节没注意到,比如 softmax 的数值稳定性、mask 的加法方式、多头拆分的维度顺序。这些细节只有亲手写过才会真正记住。

7. 我个人的学习体会与后续扩展方向

回过头看,从一行 NumPy 到一个大模型,中间的距离没有想象中那么远。NumPy 教会你数组思维和 shape 直觉,Tensor 在此基础上加了自动求导和硬件加速,Transformer 用这些基础组件搭建出注意力机制,LLM 则是在 Transformer 上堆叠层数、扩大参数量、用海量数据训练出来的结果。每一层都是下一层的基础,跳过任何一层都会在后续遇到瓶颈。

我目前还在继续深入的方向有几个:一是把小型 Transformer 的训练流程完整跑一遍,包括数据加载、损失计算、梯度更新、学习率调度;二是研究量化推理的细节,看看 4-bit 量化到底损失了多少精度;三是尝试用 ONNX 把模型导出并部署到一个简单的服务上。这些方向都不需要大规模算力,一台带显卡的普通电脑就能做。

如果你也在学 LLM,我的建议是不要被那些大词吓住。从 NumPy 开始,把数组操作练熟,把 shape 思维建立起来,然后手写一个最简的注意力,再逐步扩展到完整的 Transformer。这个过程可能需要几周时间,但走完之后,你看任何 LLM 相关的论文和代码,都不会再有那种“每个字都认识但连起来看不懂”的感觉。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 16:32:34

联邦卡尔曼滤波在多传感器融合定位中的原理与实践

1. 联邦卡尔曼滤波在多传感器融合定位中的应用价值在导航定位领域,我们常常面临一个核心挑战:如何有效整合来自不同传感器的数据,以获取比单一传感器更准确、更可靠的状态估计。传统单一传感器系统存在明显局限——GNSS在开阔环境下精度高但更…

作者头像 李华
网站建设 2026/11/3 16:32:25

直播推广出价算法:轻量化设计与实战优化

1. 直播推广出价算法背景解析 直播电商在过去三年经历了爆发式增长,数据显示头部平台单场直播GMV已突破十亿量级。在这个背景下,广告主面临的核心痛点在于:如何在有限的预算下,精准触达高转化人群并实现ROI最大化。传统出价策略主…

作者头像 李华
网站建设 2026/11/3 16:31:29

边缘AI芯片选型指南:从场景反推算力与避坑实践

1. 边缘端 AI 算力选型的底层逻辑1.1 为什么“从场景反推芯片”才是正确姿势做边缘 AI 项目,最容易踩的坑就是先看芯片参数表,再想“这玩意儿能干啥”。我见过太多团队拿着某款标称 6 TOPS 的 SoC 做方案,结果模型一量化精度掉得没法看&#…

作者头像 李华
网站建设 2026/11/3 16:31:14

Jev vs LLM:确定性执行引擎如何让Agent响应快200倍

1. 当所有人都在卷大模型参数时,Jev 走了另一条路上周有个做 Agent 开发的朋友甩给我一个链接,说"你看看这个,不用 GPU、不用 API、响应快到离谱"。我第一反应是又是哪个营销号在吹牛。结果自己跑了一遍之后,确实有点意…

作者头像 李华
网站建设 2026/11/3 16:29:21

2026年API测试智能化转型:工具对比与实施指南

1. API测试工具的智能化转型背景2026年的API测试领域正在经历一场前所未有的技术革命。作为一名从业超过十年的测试架构师,我亲眼目睹了这个领域从最初的手工测试到自动化测试,再到如今智能化测试的完整演进历程。这场变革的核心驱动力来自三个方面&…

作者头像 李华
网站建设 2026/11/3 16:27:19

MIPI DSI转LVDS桥接芯片CS5518设计与调试全流程

简介:CS5518规格书面向硬件工程师、显示接口开发者和选型人员,聚焦MIPI DSI转LVDS转换芯片的器件认知与方案评估,可用于手持设备、主板、双面板显示器及汽车显示等场景的前期设计与调试参考。压缩包共1个PDF文件,约4.92MB&#xf…

作者头像 李华