来开发者空间手搓一个迷你版大模型

举报
deli007 发表于 2025/12/17 09:17:41 2025/12/17
【摘要】    曾几何时,也想自己训练一个大模型,即使不能像deepseek一样一鸣惊人,也希望能把大模型从0到1的训练过程弄清楚。于是首先想到的是transformer论文《Attention is all you need》,其主要技术架构图如下:    看到这张图,不得不放弃了,不仅是因为步骤太多,关键是有几步理解起来就很困难,编码实现更困难,比如多头注意力。不过好在我们有开发者空间 vibe ...

   曾几何时,也想自己训练一个大模型,即使不能像deepseek一样一鸣惊人,也希望能把大模型从0到1的训练过程弄清楚。于是首先想到的是transformer论文《Attention is all you need》,其主要技术架构图如下:


8b13632762d0f703918f2cb080a2463d269759ee6f1c.jpg

    看到这张图,不得不放弃了,不仅是因为步骤太多,关键是有几步理解起来就很困难,编码实现更困难,比如多头注意力。不过好在我们有开发者空间 vibe coding 的辅助,让AI帮我们把这个图转化为代码。打开VS code,搜索安装 huawei developer space插件,创建 CDE并点击开机连接上,就可以在云开发环境里编程了。


    然后在vs code里搜索安装kilo或cline等vibe coding 开源插件,在开发者空间首页的权益栏目里领取MaaS token给插件配置上api key,就可以无限使用AI 辅助编程了。


     给kilo一段提示词,让其用pytorch生成一个大模型训练的简单示例代码,我们在本文末尾再详细解释代码,这里先直接运行看看结果。

import torch
import torch.nn as nn
import torch.optim as optim

# -----------------------------
# 1. 超参数设置(极小规模)
# -----------------------------
vocab_size = 100  # 词表大小(我们用数字代替单词)
d_model = 32  # 词向量维度(原版 Transformer 是 512+,这里缩小)
n_heads = 4  # 注意力头数(必须能整除 d_model)
n_layers = 2  # Transformer 层数
max_seq_len = 16  # 最大序列长度
batch_size = 8
learning_rate = 0.001
epochs = 100

# -----------------------------
# 2. 构造极小的训练数据(玩具级)
# -----------------------------
# 假设我们的“语言”只有数字 0~99,构造简单模式:如 [1,2,3,...] → 预测下一个
data = []
for i in range(100):
    seq = list(range(i, min(i + max_seq_len + 1, 100)))  # 生成递增序列
    if len(seq) > 1:
        data.append(seq)

# 将数据转为张量,并截断到统一长度
from torch.nn.utils.rnn import pad_sequence

data = [torch.tensor(s[:max_seq_len + 1]) for s in data if len(s) > 1]
data = pad_sequence(data, batch_first=True, padding_value=0)
# data shape: (num_samples, max_seq_len+1)

# 创建输入和目标(标准语言模型:输入是前 T 个词,目标是后 T 个词)
inputs = data[:, :-1]  # shape: (N, T)
targets = data[:, 1:]  # shape: (N, T)

# 构建 DataLoader(极简,直接切片)
dataset_size = inputs.size(0)
train_loader = [(inputs[i:i + batch_size], targets[i:i + batch_size])
                for i in range(0, dataset_size, batch_size)]


# -----------------------------
# 3. 定义极简 Transformer 解码器层
# -----------------------------
class MiniGPT(nn.Module):
    def __init__(self):
        super().__init__()
        self.token_embedding = nn.Embedding(vocab_size, d_model)
        self.pos_embedding = nn.Embedding(max_seq_len, d_model)

        # 使用多头注意力 + 前馈网络堆叠 n_layers 次
        self.layers = nn.ModuleList([
            # Use EncoderLayer for decoder-only self-attention (no external memory)
            nn.TransformerEncoderLayer(
                d_model=d_model,
                nhead=n_heads,
                dim_feedforward=d_model * 2,  # 小型前馈网络
                dropout=0.1,
                batch_first=True
            ) for _ in range(n_layers)
        ])
        self.lm_head = nn.Linear(d_model, vocab_size)  # 输出词表 logits

    def forward(self, x):
        # x: (batch, seq_len)
        B, T = x.shape
        token_emb = self.token_embedding(x)  # (B, T, d_model)
        # make sure position indices are on the same device as token_emb
        pos_idx = torch.arange(T, device=token_emb.device)
        pos_emb = self.pos_embedding(pos_idx).unsqueeze(0).expand(B, T, -1)  # (B, T, d_model)
        x = token_emb + pos_emb  # (B, T, d_model)

        # 因为是 decoder-only,memory=None,只做 self-attention
        # Each TransformerEncoderLayer performs self-attention (no external memory)
        for layer in self.layers:
            x = layer(x)

        logits = self.lm_head(x)  # (B, T, vocab_size)
        return logits


# -----------------------------
# 4. 初始化模型、损失函数、优化器
# -----------------------------
model = MiniGPT()
criterion = nn.CrossEntropyLoss(ignore_index=0)  # 忽略填充的 0
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# -----------------------------
# 5. 训练循环
# -----------------------------
model.train()
for epoch in range(epochs):
    total_loss = 0
    for x, y in train_loader:
        optimizer.zero_grad()

        # 前向传播
        logits = model(x)  # (B, T, vocab_size)

        # 计算损失:将 logits 和 target 展平
        B, T, V = logits.shape
        # use reshape instead of view to support non-contiguous tensors
        loss = criterion(logits.reshape(B * T, V), y.reshape(B * T))

        # 反向传播
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    if epoch % 20 == 0:
        print(f"Epoch {epoch}, Loss: {total_loss / len(train_loader):.4f}")

print("✅ 极简 LLM 训练完成!")

# -----------------------------
# 6. 简单推理示例(生成下一个词)
# -----------------------------
model.eval()
with torch.no_grad():
    prompt = torch.tensor([[1, 2, 3]])  # 输入 "1 2 3"
    logits = model(prompt)
    next_token = logits[0, -1].argmax().item()  # 取最后一个位置预测
    print(f"输入: [1, 2, 3] → 预测下一个词: {next_token}")

    我们先运行一下它,从结果来看,这段代码训练了100轮,生成了一个minigpt大模型,然后用其预测[1,2,3] 的下一个词,能正确输出4. 至此,用开发者空间成功训练出了一个大模型,接下来我们剖析一下大模型训练过程。


   首先看文件头,比较好理解。引入了pytorch框架,它已经封装了很多机器学习、深度学习、神经网络、大模型、transformer原理操作,这对于非AI研究类人员来说是非常好的,它造好了武器,我们扣扳机开枪就行。从超参数设置里可以看到,这是一个100个词表的数据集,可谓是一个超级mini数据集,真实数据集可能要几十T大小。其它层数、注意力头数、批次大小、学习率、学习轮次等参数,都是训练大模型不可缺少的。如果对这些参数不理解,也很简单,让编程助手不断的添加更详细的注释就行。

import torch
import torch.nn as nn
import torch.optim as optim

# -----------------------------
# 1. 超参数设置(极小规模)
# -----------------------------
vocab_size = 100  # 词表大小(我们用数字代替单词)
d_model = 32  # 词向量维度(原版 Transformer 是 512+,这里缩小)
n_heads = 4  # 注意力头数(必须能整除 d_model)
n_layers = 2  # Transformer 层数
max_seq_len = 16  # 最大序列长度
batch_size = 8
learning_rate = 0.001
epochs = 100

   

   再看其构造出来的数据集,用的方法也很简单,就是生成一堆连续的数字,例如 [1,2,3,4,5...], [2,3,4,5,6.....],真实大模型的数据集可能是1万本小说里的一段段文字,但原理其实是一样的,大模型就是从这一个个样本中找规律(这些词在向量空间里的相邻关系)。


# -----------------------------
# 2. 构造极小的训练数据(玩具级)
# -----------------------------pip
# 假设我们的“语言”只有数字 0~99,构造简单模式:如 [1,2,3,...] → 预测下一个
data = []
for i in range(100):
    seq = list(range(i, min(i + max_seq_len + 1, 100)))  # 生成递增序列
    if len(seq) > 1:
        data.append(seq)

# 将数据转为张量,并截断到统一长度
from torch.nn.utils.rnn import pad_sequence

data = [torch.tensor(s[:max_seq_len + 1]) for s in data if len(s) > 1]
data = pad_sequence(data, batch_first=True, padding_value=0)
# data shape: (num_samples, max_seq_len+1)

# 创建输入和目标(标准语言模型:输入是前 T 个词,目标是后 T 个词)
inputs = data[:, :-1]  # shape: (N, T)
targets = data[:, 1:]  # shape: (N, T)

# 构建 DataLoader(极简,直接切片)
dataset_size = inputs.size(0)
train_loader = [(inputs[i:i + batch_size], targets[i:i + batch_size])
                for i in range(0, dataset_size, batch_size)]



   接下来是transformer技术架构图的代码实现,首先是对输入进行词嵌入(token embedding)和位置嵌入(pos embedding),然后进行encoding编码,再进行前向传播,与技术架构图所画的流程是一样的。


# -----------------------------
# 3. 定义极简 Transformer 解码器层
# -----------------------------
class MiniGPT(nn.Module):
    def __init__(self):
        super().__init__()
        self.token_embedding = nn.Embedding(vocab_size, d_model)
        self.pos_embedding = nn.Embedding(max_seq_len, d_model)

        # 使用多头注意力 + 前馈网络堆叠 n_layers 次
        self.layers = nn.ModuleList([
            # Use EncoderLayer for decoder-only self-attention (no external memory)
            nn.TransformerEncoderLayer(
                d_model=d_model,
                nhead=n_heads,
                dim_feedforward=d_model * 2,  # 小型前馈网络
                dropout=0.1,
                batch_first=True
            ) for _ in range(n_layers)
        ])
        self.lm_head = nn.Linear(d_model, vocab_size)  # 输出词表 logits

    def forward(self, x):
        # x: (batch, seq_len)
        B, T = x.shape
        token_emb = self.token_embedding(x)  # (B, T, d_model)
        # make sure position indices are on the same device as token_emb
        pos_idx = torch.arange(T, device=token_emb.device)
        pos_emb = self.pos_embedding(pos_idx).unsqueeze(0).expand(B, T, -1)  # (B, T, d_model)
        x = token_emb + pos_emb  # (B, T, d_model)

        # 因为是 decoder-only,memory=None,只做 self-attention
        # Each TransformerEncoderLayer performs self-attention (no external memory)
        for layer in self.layers:
            x = layer(x)

        logits = self.lm_head(x)  # (B, T, vocab_size)
        return logits

   

    接下来设置好损失函数和优化器,开始训练。训练过程就是不停的计算损失函数,把损失情况反向传播给优化器,优化器按梯度下降的方向去修改优化权重参数,随着训练轮次加多,可以看到损失函数越来越小,这表明模型的权重参数已经越来越好了。


# -----------------------------
# 4. 初始化模型、损失函数、优化器
# -----------------------------
model = MiniGPT()
criterion = nn.CrossEntropyLoss(ignore_index=0)  # 忽略填充的 0
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# -----------------------------
# 5. 训练循环
# -----------------------------
model.train()
for epoch in range(epochs):
    total_loss = 0
    for x, y in train_loader:
        optimizer.zero_grad()

        # 前向传播
        logits = model(x)  # (B, T, vocab_size)

        # 计算损失:将 logits 和 target 展平
        B, T, V = logits.shape
        # use reshape instead of view to support non-contiguous tensors
        loss = criterion(logits.reshape(B * T, V), y.reshape(B * T))

        # 反向传播
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    if epoch % 20 == 0:
        print(f"Epoch {epoch}, Loss: {total_loss / len(train_loader):.4f}")

print("✅ 极简 LLM 训练完成!")


   最后我们看一下大模型推理过程,先把模型设置为评估模式 model.eval(),然后构造一个提示词[1,2,3],把这个提示词给到大模型,获得大模型返回的 next token


# -----------------------------
# 6. 简单推理示例(生成下一个词)
# -----------------------------
model.eval()
with torch.no_grad():
    prompt = torch.tensor([[1, 2, 3]])  # 输入 "1 2 3"
    logits = model(prompt)
    next_token = logits[0, -1].argmax().item()  # 取最后一个位置预测
    print(f"输入: [1, 2, 3] → 预测下一个词: {next_token}")

   

    再次回顾一下大模型的训练和推理输出结果,可以看到随着训练轮次增加,损失loss越来越小,预测[1,2,3],能正确返回4,至此,一个迷你版的大模型诞生了!


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。