从零手搓大模型前置知识(附录一)PyTorch 基础,从张量到训练循环

从零手搓大模型前置知识(附录一)PyTorch 基础,从张量到训练循环

这一部分是本系列的 PyTorch 入门补充。它不是直接讲大模型,而是帮你补齐后面手搓 LLM 必须用到的 PyTorch 基础:

text 复制代码
tensor -> 自动求导 -> 神经网络模块 -> Dataset/DataLoader -> 训练循环 -> 保存和加载模型

如果你看从零手搓大模型系列正文时对 torch.tensornn.ModuleDataLoaderloss.backward() 感到陌生,建议先把这篇学完。

1. PyTorch 是什么

PyTorch 是一个深度学习框架。对我们来说,它主要提供三类能力:

  1. 用 tensor 表示数据和模型参数。
  2. 自动计算梯度。
  3. 用 GPU 加速训练。

导入 PyTorch:

python 复制代码
import torch

检查 GPU 是否可用:

python 复制代码
print(torch.cuda.is_available())

如果输出 True,说明当前环境可以使用 NVIDIA GPU。输出 False 也没关系,附录一和前几章代码大多可以在 CPU 上跑。

2. Tensor:PyTorch 的基本数据结构

tensor 可以理解成"支持自动求导和 GPU 加速的多维数组"。

常见形式:

text 复制代码
0D tensor: scalar,标量
1D tensor: vector,向量
2D tensor: matrix,矩阵
3D+ tensor: 更高维张量

示例:

python 复制代码
import torch
import numpy as np

tensor0d = torch.tensor(1)

tensor1d = torch.tensor([1, 2, 3])

tensor2d = torch.tensor([[1, 2],
                         [3, 4]])

tensor3d_1 = torch.tensor([[[1, 2], [3, 4]],
                           [[5, 6], [7, 8]]])

这些就是后面 LLM 里所有数据的基础。

比如第1章里:

text 复制代码
token IDs:        2D tensor
token embeddings: 3D tensor

第2章里:

text 复制代码
attention scores: 2D 或 4D tensor

3. 从 NumPy 转成 tensor

NumPy 和 PyTorch 的关系:

python 复制代码
ary3d = np.array([[[1, 2], [3, 4]],
                  [[5, 6], [7, 8]]])

tensor3d_2 = torch.tensor(ary3d)
tensor3d_3 = torch.from_numpy(ary3d)

区别很重要:

text 复制代码
torch.tensor(ary):      会复制一份数据
torch.from_numpy(ary):  和 NumPy 数组共享内存

共享内存意味着,如果你改了原来的 NumPy 数组,对应的 tensor 也可能跟着变。

初学时记住一个简单建议:

text 复制代码
想要安全复制,用 torch.tensor(...)
想要省内存共享,用 torch.from_numpy(...)

4. Tensor 的数据类型 dtype

示例:

python 复制代码
tensor1d = torch.tensor([1, 2, 3])
print(tensor1d.dtype)

整数列表默认通常会得到:

text 复制代码
torch.int64

浮点列表:

python 复制代码
floatvec = torch.tensor([1.0, 2.0, 3.0])

通常会得到:

text 复制代码
torch.float32

也可以手动转换:

python 复制代码
floatvec = tensor1d.to(torch.float32)

为什么 dtype 重要?

因为神经网络的权重和输入通常是浮点数,例如 float32float16bfloat16。而分类标签常常是整数,例如 int64

5. 常见 tensor 操作

创建一个二维 tensor:

python 复制代码
tensor2d = torch.tensor([[1, 2, 3],
                         [4, 5, 6]])

查看形状:

python 复制代码
tensor2d.shape

这里形状是:

text 复制代码
(2, 3)

表示 2 行 3 列。

改变形状:

python 复制代码
tensor2d.reshape(3, 2)

也可以用:

python 复制代码
tensor2d.view(3, 2)

初学时可以先把 reshapeview 都理解为"改形状"。更细的区别以后再看。

转置:

python 复制代码
tensor2d.T

矩阵乘法:

python 复制代码
tensor2d.matmul(tensor2d.T)

更常见写法:

python 复制代码
tensor2d @ tensor2d.T

这在第2章 attention 里会大量出现:

python 复制代码
attn_scores = queries @ keys.T

所以 @ 一定要熟悉,它就是矩阵乘法。

6. 把模型看成计算图

用一个最小神经元示例:

python 复制代码
import torch.nn.functional as F

y = torch.tensor([1.0])  # true label
x1 = torch.tensor([1.1]) # input feature
w1 = torch.tensor([2.2]) # weight parameter
b = torch.tensor([0.0])  # bias unit

z = x1 * w1 + b
a = torch.sigmoid(z)

loss = F.binary_cross_entropy(a, y)
print(loss)

流程是:

text 复制代码
输入 x1
  -> 乘权重 w1,加偏置 b
  -> 得到 z
  -> sigmoid 激活得到 a
  -> 和真实标签 y 计算 loss

这就是一个计算图。

神经网络再复杂,本质也是很多这样的计算节点连起来。

7. 自动求导 autograd

训练神经网络时,我们要知道:

text 复制代码
loss 对每个参数的梯度是多少

PyTorch 可以自动计算。

示例:

python 复制代码
import torch.nn.functional as F
from torch.autograd import grad

y = torch.tensor([1.0])
x1 = torch.tensor([1.1])
w1 = torch.tensor([2.2], requires_grad=True)
b = torch.tensor([0.0], requires_grad=True)

z = x1 * w1 + b
a = torch.sigmoid(z)

loss = F.binary_cross_entropy(a, y)

grad_L_w1 = grad(loss, w1, retain_graph=True)
grad_L_b = grad(loss, b, retain_graph=True)

print(grad_L_w1)
print(grad_L_b)

关键是:

python 复制代码
requires_grad=True

它告诉 PyTorch:

text 复制代码
这个 tensor 是需要训练的参数,请记录它参与的计算,并能对它求梯度。

后面训练循环里更常见的是:

python 复制代码
loss.backward()

它会自动沿着计算图反向传播,把所有可训练参数的梯度算出来。

8. 用 nn.Module 定义神经网络

定义一个小型多层神经网络:

python 复制代码
class NeuralNetwork(torch.nn.Module):
    def __init__(self, num_inputs, num_outputs):
        super().__init__()

        self.layers = torch.nn.Sequential(
            torch.nn.Linear(num_inputs, 30),
            torch.nn.ReLU(),

            torch.nn.Linear(30, 20),
            torch.nn.ReLU(),

            torch.nn.Linear(20, num_outputs),
        )

    def forward(self, x):
        logits = self.layers(x)
        return logits

几个重点:

torch.nn.Module 是所有 PyTorch 模型的基类。

__init__ 里定义层:

python 复制代码
torch.nn.Linear(...)
torch.nn.ReLU()

forward 里定义数据怎么流过模型:

python 复制代码
logits = self.layers(x)

这和后面 GPT 模型是同一套路:

text 复制代码
定义模块 -> 写 forward -> 输入 tensor -> 输出 logits

9. logits 是什么

模型最后输出:

python 复制代码
logits = model(x)

logits 是还没有经过 softmax 的原始分数。

比如二分类时可能输出:

text 复制代码
[2.1, -0.8]

第一个类别分数更高,就预测类别 0。

训练时 F.cross_entropy 可以直接吃 logits,不需要你先手动 softmax。

10. Dataset:定义数据集

notebook 先准备玩具训练数据:

python 复制代码
X_train = torch.tensor([
    [-1.2, 3.1],
    [-0.9, 2.9],
    [-0.5, 2.6],
    [2.3, -1.1],
    [2.7, -1.5]
])

y_train = torch.tensor([0, 0, 0, 1, 1])

然后定义 Dataset:

python 复制代码
from torch.utils.data import Dataset


class ToyDataset(Dataset):
    def __init__(self, X, y):
        self.features = X
        self.labels = y

    def __getitem__(self, index):
        one_x = self.features[index]
        one_y = self.labels[index]
        return one_x, one_y

    def __len__(self):
        return self.labels.shape[0]

Dataset 必须实现两个方法:

text 复制代码
__len__: 返回数据集大小
__getitem__: 根据 index 返回一条样本

第1章里的 GPTDatasetV1 也是同样思想,只不过返回的是:

text 复制代码
input_ids, target_ids

11. DataLoader:批量取数据

定义 DataLoader:

python 复制代码
from torch.utils.data import DataLoader

torch.manual_seed(123)

train_loader = DataLoader(
    dataset=train_ds,
    batch_size=2,
    shuffle=True,
    num_workers=0
)

参数解释:

  • dataset:数据集对象。
  • batch_size:每次取几条样本。
  • shuffle:每轮训练是否打乱。
  • num_workers:加载数据的子进程数,Windows 初学阶段用 0 更稳。

遍历:

python 复制代码
for idx, (x, y) in enumerate(train_loader):
    print(idx, x, y)

训练模型时,我们不是一次只喂一条样本,而是一批一批喂。

这就是 batch training。

12. 典型训练循环

这是这部分最重要的代码:

python 复制代码
import torch.nn.functional as F

torch.manual_seed(123)
model = NeuralNetwork(num_inputs=2, num_outputs=2)
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)

num_epochs = 3

for epoch in range(num_epochs):

    model.train()
    for batch_idx, (features, labels) in enumerate(train_loader):

        logits = model(features)

        loss = F.cross_entropy(logits, labels)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        print(f"Epoch: {epoch+1:03d}/{num_epochs:03d}"
              f" | Batch {batch_idx+1:03d}/{len(train_loader):03d}"
              f" | Train/Val Loss: {loss:.2f}")

    model.eval()

训练循环可以背成固定模板:

text 复制代码
1. model.train()
2. 前向传播:logits = model(features)
3. 计算损失:loss = loss_fn(logits, labels)
4. 清空旧梯度:optimizer.zero_grad()
5. 反向传播:loss.backward()
6. 更新参数:optimizer.step()
7. model.eval()

为什么要 zero_grad

因为 PyTorch 默认会累积梯度。如果不清空,上一个 batch 的梯度会混进来。

为什么 backward 后要 step

backward 只负责算梯度,step 才真正更新参数。

zero_grad() 只负责清理历史垃圾梯度,不产生新梯度;

loss.backward() 才是生成当前批次梯度的唯一步骤;

13. model.train() 和 model.eval()

训练时:

python 复制代码
model.train()

评估时:

python 复制代码
model.eval()

它们会影响 dropout、batch norm 等层的行为。

虽然这个小模型里影响不明显,但后面大模型里一定要养成习惯。

14. 计算准确率

定义:

python 复制代码
def compute_accuracy(model, dataloader):

    model.eval()
    correct = 0.0
    total_examples = 0

    for idx, (features, labels) in enumerate(dataloader):

        with torch.no_grad():
            logits = model(features)

        predictions = torch.argmax(logits, dim=1)
        compare = labels == predictions
        correct += torch.sum(compare)
        total_examples += len(compare)

    return (correct / total_examples).item()

关键点:

python 复制代码
with torch.no_grad():

评估时不需要求梯度,可以省内存、省计算。

预测类别:

python 复制代码
predictions = torch.argmax(logits, dim=1)

意思是对每个样本,取 logits 最大的类别。

15. 保存和加载模型

保存:

python 复制代码
torch.save(model.state_dict(), "model.pth")

加载:

python 复制代码
model = NeuralNetwork(2, 2)
model.load_state_dict(torch.load("model.pth", weights_only=True))

注意:加载时模型结构必须和保存时一样。

state_dict 保存的是参数,不是完整 Python 类定义。

所以你要先创建同样结构的模型:

python 复制代码
model = NeuralNetwork(2, 2)

再把权重加载进去。

16.本章 和 LLM 的关系

本章对后面章节的帮助非常直接:

第 1 章会用:

text 复制代码
Dataset
DataLoader
tensor shape

第 2 章会用:

text 复制代码
矩阵乘法
transpose
softmax
nn.Module

第 3 章会用:

text 复制代码
nn.Module
nn.Linear
forward
Sequential 思维

第 4 章训练 GPT 会完整用到:

text 复制代码
loss.backward()
optimizer.step()
model.train()
model.eval()
torch.no_grad()
state_dict

所以这部分不是"附录可看可不看",而是后面手搓大模型的 PyTorch 地基。

17. 建议

按这个顺序学:

  1. 先跑 tensor 部分,熟悉 shape、dtype、reshape、transpose、矩阵乘法。
  2. 再跑自动求导,理解 requires_grad=Trueloss.backward()
  3. 然后看 NeuralNetwork,理解 nn.Moduleforward
  4. 再看 Dataset/DataLoader,理解数据怎么按 batch 送进模型。
  5. 最后认真看训练循环,把那 6 步背熟。

如果只记一个训练模板,记这个:

python 复制代码
model.train()
for features, labels in train_loader:
    logits = model(features)
    loss = F.cross_entropy(logits, labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

后面训练 GPT,本质上也是这个套路,只是模型更大、数据更复杂。

相关推荐
涛声依旧-底层原理研究所2 小时前
Agent 长任务可靠性设计:实现暂停、恢复、续跑与崩溃重启的完整方案
人工智能·python·系统架构
AC赳赳老秦2 小时前
防火墙规则批量配置实战:OpenClaw 自动生成模板、批量下发与合规性校验全解析
java·开发语言·人工智能·python·github·php·openclaw
8Qi82 小时前
HelloAgents:RAG——让 Agent 学会检索知识
人工智能·llm·agent·ai编程·vibecoding
小小编程路2 小时前
如何优化while循环的性能?
python
触底反弹2 小时前
🔥 从点积到 Transformer:我终于搞懂大模型是怎么"猜"出下一个词的了
人工智能·机器学习·架构
Token炼金师2 小时前
算力显存通信的三角博弈:DP/TP/PP/SP、ZeRO、混合精度与稳定性 —— 训练优化四件套
人工智能·深度学习·dp·sp·pp·zero·tp
无糖可可果2 小时前
MCP(Model Context Protocol)学习分享:从理论到实践
人工智能
RFID科技的魅力2 小时前
RFID资产管理系统选型避坑指南:从需求梳理到落地验证
大数据·人工智能·物联网·rfid
ai产品老杨3 小时前
【边云协同视频分析项目实战记录】多站点AI视频分析平台部署手册
人工智能·音视频
北鹤M3 小时前
如何将模特导入AI实现电商智能换装,主流工具体验分享
人工智能·aigc