【在 RX 6750 GRE 10GB 上预训练 GPT:一场 ROCm 生态的踩坑实录

在 RX 6750 GRE 10GB 上预训练 GPT:一场 ROCm 生态的踩坑实录

导言

用一张消费级 A 卡(RX 6750 GRE 10GB)在 Linux + ROCm 环境下成功预训练了一个 50M 参数的 GPT 模型。本文记录了从环境配置、ROCm 兼容性伪装、数据集选型到显存优化的完整过程,以及那些只有亲自踩过才知道的坑。

一、为什么做这件事

大模型训练似乎已经成了 NVIDIA 的专属游戏------A100/H100 一卡难求,消费级 RTX 4090 也要被 CUDA 生态绑定,而且现在NVIDIA的显卡涨价太厉害了。但手边恰好有一张 RX 6750 GRE 10GB(Navi 22, gfx1031),女朋友又送了我一张3T的机械硬盘,能不能用它跑起来?

AMD 的 ROCm 生态近年进步明显,PyTorch 官方也提供了 ROCm wheel。但消费级 RDNA2 显卡并不在 ROCm 的官方支持列表里。这篇文章记录的就是:如何让一张"不被支持"的 A 卡,稳定跑通 GPT 预训练。

二、硬件与环境

组件 配置
GPU AMD Radeon RX 6750 GRE 10GB (Navi 22, gfx1031)
驱动 ROCm 6.x
系统 Ubuntu 22.04
PyTorch 2.x (ROCm 6.1 wheel)
显存 10 GB GDDR6

RX 6750 GRE 的架构是 gfx1031,ROCm 官方仅支持 gfx1030(RX 6700 XT)。直接运行会报 invalid device function。解决方案:环境变量伪装。

bash 复制代码
export HSA_OVERRIDE_GFX_VERSION=10.3.0

这行命令必须在 import torch 之前 执行,让 HIP runtime 把 gfx1031 识别为 gfx1030。绝大多数 kernel 可以兼容运行,但会触发一个无害的警告:

bash 复制代码
UserWarning: Attempting to use hipBLASLt on an unsupported architecture! 
Overriding blas backend to hipblas

三、模型设计:50M 参数的 Mini-GPT

10GB 显存无法支撑标准的 GPT-2 Small(124M),因此设计了一个轻量化架构:

参数 数值
层数 8
注意力头 8
嵌入维度 512
上下文长度 1024
词表大小 50304(GPT-2 BPE)
总参数量 ~51M

在 BF16 + 梯度检查点 + SDPA 的组合下,实际显存占用非常健康:

bash 复制代码
[VRAM] 分配: 0.68 GB | 预留: 1.66 GB | 峰值: 1.44 GB

这意味着:

复制代码
显存余量巨大:10GB 只用了不到 15%
理论上可以上更大的模型:355M(GPT-2 Medium)在 10GB 下也能跑,但 batch_size 必须降到 1
上下文可以更长:当前 1024,实测可以推到 2048 甚至 4096

四、技术栈:把能用的优化全用上

4.1 混合精度(BF16)

python 复制代码
ctx = torch.amp.autocast(device_type="cuda", dtype=torch.bfloat16)

BF16 对 RDNA2 的兼容性比 FP16 更好,且不需要 loss scaling,训练更稳定。

4.2 PyTorch SDPA(替代 FlashAttention)

ROCm 上无法使用 CUDA 版的 FlashAttention-2,但 PyTorch 2.0+ 的 scaled_dot_product_attention 在 ROCm 下会自动调用 MIOpen memory efficient attention,实现类似的 O(n) 显存复杂度。

python 复制代码
y = F.scaled_dot_product_attention(q, k, v, is_causal=True)

4.3 梯度检查点(Gradient Checkpointing)

以时间换空间,只保存每层的输入,反向传播时重算中间激活。对于 8 层模型,显存节省约 60-70%。

python 复制代码
x = x + checkpoint(lambda t: self.attn(self.ln_1(t)), x, use_reentrant=False)

五、数据集:wikitext-2,小而精五、数据集:wikitext-2,小而精

属性 数值
数据集 Salesforce/wikitext / wikitext-2-raw-v1
大小 ~2 MB(极小,秒下)
语言 英文(维基百科)
训练样本 2,332 个(block_size=1024)
验证样本 241 个

六、 训练日志与效果

python 复制代码
============================================================
  设备: AMD Radeon RX 6750 GRE 10GB
  显存: 10.0 GB | 架构: gfx103
============================================================
[!] 显存仅 10.0GB,强制 batch_size=1

[1/5] 加载 tiktoken...
    使用 tiktoken (gpt2)
[2/5] 加载数据集: Salesforce/wikitext/wikitext-2-raw-v1...
    [datasets] 加载成功, 样本数: 36718
    生成 2332 个训练样本
[3/5] 构建模型...
    参数量 : 50.98M
    上下文 : 1024
    层数   : 8 | 头数: 8 | 维度: 512
[4/5] 训练配置:
    微批次     : 1
    梯度累积   : 4
    有效批次   : 4
    混合精度   : bfloat16
    梯度检查点 : True
============================================================
iter      0 | loss 10.9044 | lr 0.00e+00 | 1759ms | mfu 3.7%
iter     10 | loss 10.6042 | lr 6.00e-06 | 11090ms | mfu 0.6%
iter     50 | loss 9.3630 | lr 3.00e-05 | 11116ms | mfu 0.6%
iter    100 | loss 8.3577 | lr 6.00e-05 | 11103ms | mfu 0.6%
iter    150 | loss 6.8392 | lr 9.00e-05 | 11125ms | mfu 0.6%
iter    200 | loss 6.9590 | lr 1.20e-04 | 11036ms | mfu 0.6%
iter    250 | loss 6.4357 | lr 1.50e-04 | 11017ms | mfu 0.6%
iter    300 | loss 6.3136 | lr 1.80e-04 | 11053ms | mfu 0.6%
iter    330 | loss 6.5860 | lr 1.92e-04 | 11024ms | mfu 0.6%

7、 踩坑记录:那些只有跑过才知道的事

7.1 invalid device function

原因:gfx1031 不被 ROCm 原生支持。

修复:export HSA_OVERRIDE_GFX_VERSION=10.3.0,且必须在 import torch 前设置。

7.2 ValueError: not enough values to unpack (expected 2, got 1)

原因:batch_size=1 时,next(dataset) 返回 (1024,) 一维张量,模型 forward 期望 (B, T) 二维。

修复:get_batch 函数使用 torch.stack 强制堆叠,确保 batch 维度始终存在。

7.3 KeyError: 'lm_head.weight'

原因:wte 和 lm_head 共享权重,named_parameters() 中只保留一个 key,但 configure_optimizers 通过 named_modules() 收集到了两个名字。

修复:与 param_dict 取交集,过滤掉因共享权重而不存在的 key。

7.4 Invalid HF URI 'hf://datasets/wikitext'

原因:huggingface_hub >= 1.16 强制要求 namespace/name 格式,拒绝单段 ID。

修复:wikitext → Salesforce/wikitext。

附录:核心代码

python 复制代码
#!/usr/bin/env python3
"""
ROCm GPT-2 预训练脚本 (RX 6750 GRE 10GB, 最终稳定版)
=======================================================
数据集: Salesforce/wikitext (wikitext-2-raw-v1, 英文维基百科, ~2MB)
修复: huggingface_hub >= 1.16 要求 namespace/name 格式

安装:
    pip install torch --index-url https://download.pytorch.org/whl/rocm6.1
    pip install datasets tiktoken numpy tqdm

运行:
    export HSA_OVERRIDE_GFX_VERSION=10.3.0
    python train_gpt2_rocm_stable.py
"""

import os
import sys
import time
import math
import json
from dataclasses import dataclass, asdict
from contextlib import nullcontext

import torch
import torch.nn as nn
from torch.nn import functional as F
from torch.utils.checkpoint import checkpoint

# =====================================================================
# 0. 强制设置 HuggingFace 国内镜像
# =====================================================================
if not os.environ.get("HF_ENDPOINT"):
    os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
    print(f"[HF] 自动设置镜像: {os.environ['HF_ENDPOINT']}")

# =====================================================================
# 1. ROCm 环境自检
# =====================================================================
def rocm_sanity_check():
    if not torch.cuda.is_available():
        raise RuntimeError("CUDA/ROCm 不可用")
    props = torch.cuda.get_device_properties(0)
    total_mem = props.total_memory / 1024**3
    print("=" * 60)
    print(f"  设备: {torch.cuda.get_device_name(0)}")
    print(f"  显存: {total_mem:.1f} GB | 架构: gfx{props.major}{props.minor}")
    print("=" * 60)
    return total_mem

# =====================================================================
# 2. 配置 (~50M 参数小模型)
# =====================================================================
@dataclass
class TrainConfig:
    n_layer: int = 8
    n_head: int = 8
    n_embd: int = 512
    block_size: int = 1024
    vocab_size: int = 50304
    dropout: float = 0.0
    bias: bool = True

    batch_size: int = 2
    gradient_accumulation_steps: int = 4
    max_iters: int = 50000
    learning_rate: float = 6e-4
    weight_decay: float = 0.1
    beta1: float = 0.9
    beta2: float = 0.95
    grad_clip: float = 1.0
    warmup_iters: int = 1000
    lr_decay_iters: int = 50000
    min_lr: float = 6e-5

    use_mixed_precision: bool = True
    use_gradient_checkpointing: bool = True
    use_sdpa: bool = True

    # =====================================================================
    # 关键修复: 数据集名称必须用 namespace/name 格式
    # huggingface_hub >= 1.16 拒绝单段 ID (如 "wikitext")
    # =====================================================================
    dataset: str = "Salesforce/wikitext"      # 修复: 加命名空间
    dataset_config: str = "wikitext-2-raw-v1"  # 子集配置名
    text_field: str = "text"

    eval_interval: int = 500
    eval_iters: int = 100
    log_interval: int = 10
    checkpoint_interval: int = 2500
    sample_interval: int = 1000
    out_dir: str = "out_rocm_gpt2"
    device: str = "cuda"
    dtype: str = "bfloat16"
    seed: int = 1337

# =====================================================================
# 3. 模型定义
# =====================================================================
class CausalSelfAttention(nn.Module):
    def __init__(self, config):
        super().__init__()
        assert config.n_embd % config.n_head == 0
        self.n_head = config.n_head
        self.n_embd = config.n_embd
        self.head_dim = config.n_embd // config.n_head
        self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd, bias=config.bias)
        self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=config.bias)
        self.register_buffer("mask", torch.triu(torch.ones(config.block_size, config.block_size), diagonal=1).bool(), persistent=False)
        self.use_sdpa = config.use_sdpa and hasattr(F, "scaled_dot_product_attention")

    def forward(self, x):
        B, T, C = x.size()
        q, k, v = self.c_attn(x).split(self.n_embd, dim=2)
        q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        if self.use_sdpa:
            y = F.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0, is_causal=True)
        else:
            att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(self.head_dim))
            att = att.masked_fill(self.mask[:, :, :T, :T], float("-inf"))
            att = F.softmax(att, dim=-1)
            y = att @ v
        y = y.transpose(1, 2).contiguous().view(B, T, C)
        return self.c_proj(y)

class MLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.c_fc = nn.Linear(config.n_embd, 4 * config.n_embd, bias=config.bias)
        self.c_proj = nn.Linear(4 * config.n_embd, config.n_embd, bias=config.bias)
    def forward(self, x):
        return self.c_proj(F.gelu(self.c_fc(x)))

class Block(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.ln_1 = nn.LayerNorm(config.n_embd)
        self.attn = CausalSelfAttention(config)
        self.ln_2 = nn.LayerNorm(config.n_embd)
        self.mlp = MLP(config)
        self.use_ckpt = config.use_gradient_checkpointing

    def forward(self, x):
        if self.use_ckpt and self.training:
            x = x + checkpoint(lambda t: self.attn(self.ln_1(t)), x, use_reentrant=False)
            x = x + checkpoint(lambda t: self.mlp(self.ln_2(t)), x, use_reentrant=False)
        else:
            x = x + self.attn(self.ln_1(x))
            x = x + self.mlp(self.ln_2(x))
        return x

class GPT(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.transformer = nn.ModuleDict(dict(
            wte = nn.Embedding(config.vocab_size, config.n_embd),
            wpe = nn.Embedding(config.block_size, config.n_embd),
            drop = nn.Dropout(config.dropout),
            h = nn.ModuleList([Block(config) for _ in range(config.n_layer)]),
            ln_f = nn.LayerNorm(config.n_embd),
        ))
        self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
        self.transformer.wte.weight = self.lm_head.weight
        self.apply(self._init_weights)
        for pn, p in self.named_parameters():
            if pn.endswith("c_proj.weight"):
                torch.nn.init.normal_(p, mean=0.0, std=0.02 / math.sqrt(2 * config.n_layer))

    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)

    def forward(self, idx, targets=None):
        B, T = idx.size()
        assert T <= self.config.block_size
        pos = torch.arange(0, T, dtype=torch.long, device=idx.device).unsqueeze(0)
        x = self.transformer.drop(self.transformer.wte(idx) + self.transformer.wpe(pos))
        for block in self.transformer.h:
            x = block(x)
        x = self.transformer.ln_f(x)
        logits = self.lm_head(x)
        loss = None
        if targets is not None:
            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
        return logits, loss

    def configure_optimizers(self, config):
        decay, no_decay = set(), set()
        whitelist, blacklist = (nn.Linear,), (nn.LayerNorm, nn.Embedding)
        for mn, m in self.named_modules():
            for pn, p in m.named_parameters(recurse=False):
                fpn = f"{mn}.{pn}" if mn else pn
                if pn.endswith('bias'):
                    no_decay.add(fpn)
                elif pn.endswith('weight') and isinstance(m, whitelist):
                    decay.add(fpn)
                elif pn.endswith('weight') and isinstance(m, blacklist):
                    no_decay.add(fpn)
        param_dict = {pn: p for pn, p in self.named_parameters()}
        decay = {pn for pn in decay if pn in param_dict}
        no_decay = {pn for pn in no_decay if pn in param_dict}
        inter = decay & no_decay
        union = decay | no_decay
        assert len(inter) == 0
        assert len(param_dict.keys() - union) == 0
        optim_groups = [
            {"params": [param_dict[pn] for pn in sorted(decay)], "weight_decay": config.weight_decay},
            {"params": [param_dict[pn] for pn in sorted(no_decay)], "weight_decay": 0.0},
        ]
        return torch.optim.AdamW(optim_groups, lr=config.learning_rate, betas=(config.beta1, config.beta2))

    def get_num_params(self, non_embedding=True):
        n = sum(p.numel() for p in self.parameters())
        if non_embedding:
            n -= self.transformer.wpe.weight.numel()
        return n

    def estimate_mfu(self, fwdbwd_per_iter, dt):
        N = self.get_num_params()
        cfg = self.config
        L, H, Q, T = cfg.n_layer, cfg.n_head, cfg.n_embd // cfg.n_head, cfg.block_size
        flops_per_token = 6 * N + 12 * L * H * Q * T
        flops_per_fwdbwd = flops_per_token * T
        flops_per_iter = flops_per_fwdbwd * fwdbwd_per_iter
        return flops_per_iter * (1.0 / dt) / 22.6e12

    @torch.no_grad()
    def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
        for _ in range(max_new_tokens):
            idx_cond = idx if idx.size(1) <= self.config.block_size else idx[:, -self.config.block_size:]
            logits, _ = self(idx_cond)
            logits = logits[:, -1, :] / temperature
            if top_k is not None:
                v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
                logits[logits < v[:, [-1]]] = float("-inf")
            probs = F.softmax(logits, dim=-1)
            idx_next = torch.multinomial(probs, num_samples=1)
            idx = torch.cat((idx, idx_next), dim=1)
        return idx

# =====================================================================
# 4. 数据加载 (datasets + hf-mirror, namespace/name 格式)
# =====================================================================
class WikitextDataset:
    """加载 wikitext 数据集, 使用 namespace/name 格式"""
    def __init__(self, dataset_name, config_name, text_field, tokenizer, block_size, split="train"):
        from datasets import load_dataset
        print(f"    [datasets] 加载 {dataset_name}/{config_name} ({split})...")
        print(f"    [datasets] 镜像: {os.environ.get('HF_ENDPOINT', '默认')}")

        try:
            ds = load_dataset(dataset_name, config_name, split=split, trust_remote_code=False)
            print(f"    [datasets] 加载成功, 样本数: {len(ds)}")
        except Exception as e:
            print(f"\n[!] 加载失败: {e}")
            print("\n    可能原因:")
            print("    1. 未安装 datasets: pip install datasets")
            print("    2. 网络问题, 检查是否能访问 hf-mirror.com")
            print("    3. datasets 版本过旧/过新,尝试: pip install -U datasets huggingface_hub")
            raise

        self.samples = []
        self.tokenizer = tokenizer
        self.block_size = block_size

        buffer = []
        for ex in ds:
            text = ex.get(text_field, "") if isinstance(ex, dict) else getattr(ex, text_field, "")
            if not text or len(text.strip()) < 10:
                continue

            ids = tokenizer.encode(text, allowed_special={"<|endoftext|>"})
            buffer.extend(ids)

            while len(buffer) >= block_size + 1:
                chunk = buffer[:block_size + 1]
                buffer = buffer[block_size + 1:]
                self.samples.append((
                    torch.tensor(chunk[:-1], dtype=torch.long),
                    torch.tensor(chunk[1:], dtype=torch.long)
                ))

        print(f"    生成 {len(self.samples)} 个训练样本")

    def __len__(self):
        return len(self.samples)

    def __getitem__(self, idx):
        return self.samples[idx]

def get_batch(ds, batch_size, device):
    """确保 batch 维度始终存在"""
    ix = torch.randint(0, len(ds), (batch_size,))
    x = torch.stack([ds[i][0] for i in ix])
    y = torch.stack([ds[i][1] for i in ix])
    return x.to(device), y.to(device)

# =====================================================================
# 5. 工具函数
# =====================================================================
def get_lr(it, config):
    if it < config.warmup_iters:
        return config.learning_rate * it / config.warmup_iters
    if it > config.lr_decay_iters:
        return config.min_lr
    decay_ratio = (it - config.warmup_iters) / (config.lr_decay_iters - config.warmup_iters)
    coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))
    return config.min_lr + coeff * (config.learning_rate - config.min_lr)

def print_memory(tag=""):
    alloc = torch.cuda.memory_allocated() / 1024**3
    res = torch.cuda.memory_reserved() / 1024**3
    peak = torch.cuda.max_memory_allocated() / 1024**3
    print(f"  [VRAM {tag}] 分配: {alloc:.2f} GB | 预留: {res:.2f} GB | 峰值: {peak:.2f} GB")

# =====================================================================
# 6. 主训练循环
# =====================================================================
def main():
    config = TrainConfig()
    os.makedirs(config.out_dir, exist_ok=True)

    with open(os.path.join(config.out_dir, "config.json"), "w") as f:
        json.dump(asdict(config), f, indent=2)

    torch.manual_seed(config.seed)
    torch.cuda.manual_seed(config.seed)

    total_mem = rocm_sanity_check()
    device = torch.device(config.device)

    if total_mem < 12 and config.batch_size > 1:
        print(f"[!] 显存仅 {total_mem:.1f}GB,强制 batch_size=1")
        config.batch_size = 1

    # 加载 tokenizer
    print("\n[1/5] 加载 tiktoken...")
    try:
        import tiktoken
        enc = tiktoken.get_encoding("gpt2")
        print("    使用 tiktoken (gpt2)")
    except ImportError:
        print("[!] 未安装 tiktoken: pip install tiktoken")
        sys.exit(1)

    # 加载数据集
    print(f"[2/5] 加载数据集: {config.dataset}/{config.dataset_config}...")
    try:
        train_ds = WikitextDataset(config.dataset, config.dataset_config, config.text_field, enc, config.block_size, "train")
        val_ds = WikitextDataset(config.dataset, config.dataset_config, config.text_field, enc, config.block_size, "validation")
    except Exception as e:
        print(f"\n[!] 加载数据集失败: {e}")
        sys.exit(1)

    # 构建模型
    print("[3/5] 构建模型...")
    model = GPT(config)
    model = model.to(device)
    n_params = model.get_num_params()
    print(f"    参数量 : {n_params / 1e6:.2f}M")
    print(f"    上下文 : {config.block_size}")
    print(f"    层数   : {config.n_layer} | 头数: {config.n_head} | 维度: {config.n_embd}")

    optimizer = model.configure_optimizers(config)

    ptdtype = {"float32": torch.float32, "bfloat16": torch.bfloat16, "float16": torch.float16}[config.dtype]
    ctx = nullcontext() if not config.use_mixed_precision else torch.amp.autocast(device_type="cuda", dtype=ptdtype)

    print(f"[4/5] 训练配置:")
    print(f"    微批次     : {config.batch_size}")
    print(f"    梯度累积   : {config.gradient_accumulation_steps}")
    print(f"    有效批次   : {config.batch_size * config.gradient_accumulation_steps}")
    print(f"    混合精度   : {config.dtype}")
    print(f"    梯度检查点 : {config.use_gradient_checkpointing}")
    print("=" * 60)

    iter_num = 0
    best_val_loss = 1e9
    t0 = time.time()
    model.train()

    X, Y = get_batch(train_ds, config.batch_size, device)

    while iter_num < config.max_iters:
        lr = get_lr(iter_num, config)
        for pg in optimizer.param_groups:
            pg["lr"] = lr

        for micro_step in range(config.gradient_accumulation_steps):
            with ctx:
                logits, loss = model(X, Y)
                loss = loss / config.gradient_accumulation_steps

            loss.backward()
            X, Y = get_batch(train_ds, config.batch_size, device)

        torch.nn.utils.clip_grad_norm_(model.parameters(), config.grad_clip)
        optimizer.step()
        optimizer.zero_grad(set_to_none=True)

        if iter_num % config.log_interval == 0:
            dt = time.time() - t0
            t0 = time.time()
            lossf = loss.item() * config.gradient_accumulation_steps
            mfu = model.estimate_mfu(config.batch_size * config.gradient_accumulation_steps, dt)
            print(f"iter {iter_num:6d} | loss {lossf:.4f} | lr {lr:.2e} | {dt*1000:.0f}ms | mfu {mfu*100:.1f}%")

        if iter_num % (config.log_interval * 10) == 0:
            print_memory(f"iter {iter_num}")

        if iter_num > 0 and iter_num % config.eval_interval == 0:
            model.eval()
            losses = torch.zeros(config.eval_iters)
            with torch.no_grad():
                for k in range(config.eval_iters):
                    xv, yv = get_batch(val_ds, config.batch_size, device)
                    with ctx:
                        _, l = model(xv, yv)
                    losses[k] = l.item()
            model.train()
            val_loss = losses.mean()
            print(f"\n>>> 验证损失: {val_loss:.4f} <<<")

            if val_loss < best_val_loss:
                best_val_loss = val_loss
                ckpt = {"model": model.state_dict(), "optimizer": optimizer.state_dict(),
                        "iter_num": iter_num, "config": asdict(config)}
                torch.save(ckpt, os.path.join(config.out_dir, "ckpt_best.pt"))
                print(f"    [保存] 最佳模型")

        if iter_num > 0 and iter_num % config.sample_interval == 0:
            model.eval()
            prompt = torch.zeros((1, 1), dtype=torch.long, device=device)
            with torch.no_grad():
                with ctx:
                    sample = model.generate(prompt, max_new_tokens=64, top_k=40)[0].tolist()
            text = enc.decode(sample)
            print(f"\n--- 生成样本 (iter {iter_num}) ---")
            print(text[:300])
            print("---\n")
            model.train()

        if iter_num > 0 and iter_num % config.checkpoint_interval == 0:
            ckpt = {"model": model.state_dict(), "optimizer": optimizer.state_dict(),
                    "iter_num": iter_num, "config": asdict(config)}
            torch.save(ckpt, os.path.join(config.out_dir, f"ckpt_{iter_num}.pt"))

        iter_num += 1

    print("\n[5/5] 训练完成!")
    print_memory("最终")
    print(f"最佳验证损失: {best_val_loss:.4f}")

if __name__ == "__main__":
    main()
相关推荐
南方程序猴3 小时前
2026年7月最新国内 Codex 安装教程和使用教程:GPT-5.6 完整指南
人工智能·gpt·ai·ai编程
天天有money18 小时前
API中转站在社媒矩阵里的价值:多平台文案如何统一改写
gpt·线性代数·ai·chatgpt·矩阵
LDZKKJ18 小时前
OpenAI暂停GPT-6训练:AI行业从“竞速“到“刹车“的分水岭
人工智能·gpt·语言模型·chatgpt·transformer
我是大卫20 小时前
图解Transformer:为什么GPT、Claude、DeepSeek都用同一个架构?
gpt·deepseek
神奇霸王龙1 天前
Coze 3.0多Agent协作:5国产基座实测对比
人工智能·python·gpt·ai·ai编程·glm
Black_Rock_br1 天前
闭源双雄 vs 2.8T 开源权重:Kimi K3 / Fable 5 / GPT-5.6 的调用成本与任务适配
人工智能·python·gpt·语言模型·开源
whyfail1 天前
GPT-5.6时代-从Superpowers转向Grill-Me
人工智能·gpt
库拉大叔2 天前
企业级RAG应用:GPT-5.6长上下文检索与推理闭环实测
gpt
神奇霸王龙2 天前
DeepSeek医疗RAG降本60%实战
人工智能·gpt·ai·prompt·音视频·医疗·ai医疗