MacOS M2使用MPS后端加速训练

PyTorch 2.6

PyTorch 2.6

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

MPS 后端核心解析

MPS (Metal Performance Shaders) 是 Apple 提供的 GPU 加速框架,PyTorch 从 1.12 版本开始支持 MPS 后端。其主要特点:


1. 硬件适配性## 标题
  • 仅支持 Apple Silicon 芯片(M1/M2 系列)
  • 需要 macOS 12.3 或更高版本
  • 与 CUDA 的编程模型存在显著差异
2. 性能特征
  • 优势场景
    • 中等规模矩阵运算(1024x1024 ~ 4096x4096)
    • 连续内存访问模式
    • FP32 浮点计算
  • 局限场景
    • 核函数启动开销较大(小张量运算效率低)
    • 缺乏 CUDA 的异步流控制机制
    • 显存管理策略较保守
3. 兼容性现状
# 查看 MPS 支持状态
print(torch.backends.mps.is_available())  # 硬件/系统支持
print(torch.backends.mps.is_built())      # PyTorch 编译支持

性能优化实践指南

1. 计算密度提升
  • 推荐配置

    # 典型高效配置
    batch_size = 512       # 不低于 256
    hidden_size = 1024     # 维度保持 2^n
    sequence_length = 64   # 避免碎片化长度
    
  • 规避低效模式

    # 低效示例(小批量+低维度)
    x = torch.randn(16, 128, device="mps")  # 难以充分利用 GPU
    
2. 内存访问优化
# 推荐连续内存布局
x = x.contiguous()  # 强制内存连续

# 避免跨步访问
y = x[:, ::2]       # 产生非连续视图
y_opt = x.contiguous()[:, ::2].contiguous()  # 优化版本
3. 混合精度训练
# MPS 自动混合精度方案
with torch.autocast(device_type="mps", dtype=torch.float16):
    output = model(input)

典型问题诊断

1. 显存管理异常
# 监控显存使用
export PYTORCH_MPS_MEMORY_DEBUG=1
python script.py
2. 核函数缺失
# 捕获未实现操作
try:
    output = model(input)
except RuntimeError as e:
    if "not implemented" in str(e):
        print("存在未支持的算子:", e)
3. 性能分析工具
# 使用 PyTorch Profiler
with torch.profiler.profile(
    activities=[torch.profiler.ProfilerActivity.CPU,
                torch.profiler.ProfilerActivity.MPS]) as prof:
    model(input)
print(prof.key_averages().table())

进阶调试技巧

1. 强制回退机制
# 创建 MPS 回退上下文
class MPSFallback:
    def __enter__(self):
        torch.mps.set_flags(enable_mps_fallback=True)
    
    def __exit__(self, *args):
        torch.mps.set_flags(enable_mps_fallback=False)

with MPSFallback():
    # 此处代码会尝试自动回退到 CPU
    problematic_operation()
2. 内核选择策略
# 强制使用特定内核变体
torch.mps.set_kernel_priority([
    "Metal::conv2d_winograd",
    "Metal::conv2d_implicit_gemm"
])
3. 内存复用优化
# 启用内存池
torch.mps.enable_memory_pool()

# 手动释放缓存
torch.mps.empty_cache()

版本适配建议

PyTorch 版本MPS 成熟度关键特性
1.12实验阶段基础算子支持
2.0生产可用动态图优化
2.1+增强支持分布式训练

性能对比参考

在 M2 Max 芯片上的典型测试结果(ResNet-50 前向传播):

| 设备   | 批次大小 | 时延 (ms) | 能耗 (J) |
|--------|----------|-----------|----------|
| CPU    | 64       | 4200      | 28.5     |
| MPS    | 64       | 620       | 9.8      |
| MPS+AMP| 128      | 530       | 8.2      |

最佳实践总结

  1. 保持张量维度对齐 128 字节边界
  2. 优先使用 view() 而非 reshape()
  3. 避免频繁的 CPU-MPS 数据传输
  4. 对循环结构使用 @torch.jit.script 编译
  5. 定期更新 PyTorch 到最新版本## 标题

使用SkipGram模型训练词向量

#coding:utf8

import torch
import torch.nn as nn
import numpy as np
import math
import time
import random
import os
import re
import matplotlib.pyplot as plt

# 设备选择:优先使用 MPS(Mac M2),其次使用 CUDA(NVIDIA GPU),最后使用 CPU
device = torch.device("mps" if torch.backends.mps.is_available() else
                      "cuda" if torch.cuda.is_available() else "cpu")

"""
基于pytorch的rnn语言模型
"""

class LanguageModel(nn.Module):
    def __init__(self, input_dim, vocab):
        super(LanguageModel, self).__init__()
        self.embedding = nn.Embedding(len(vocab) + 1, input_dim)
        self.layer = nn.RNN(input_dim, input_dim, num_layers=2, batch_first=True)
        self.classify = nn.Linear(input_dim, len(vocab) + 1)
        self.dropout = nn.Dropout(0.1)
        self.loss = nn.functional.cross_entropy

    #自定义交叉熵损失函数
    def cross_entropy(self, pred, target):
        batch_size, classes = pred.shape
        # 数值稳定的 Softmax 计算
        # log_softmax = nn.functional.log_softmax(pred, dim=1)  # 比手动计算更稳定
        # one_hot_target = nn.functional.one_hot(target, num_classes=classes).float()
        # # 计算交叉熵损失,逐元素相乘
        # loss_elements = -one_hot_target * log_softmax
        # batch_loss = loss_elements.sum(dim=1)
        # 直接使用 log_softmax + gather 组合,避免生成完整 one-hot 矩阵
        log_softmax = nn.functional.log_softmax(pred, dim=1)
        # 关键优化:用 gather 代替 one-hot 乘法
        batch_loss = -log_softmax.gather(1, target.unsqueeze(1)).squeeze(1)
        return batch_loss.mean()

    #当输入真实标签,返回loss值;无真实标签,返回预测值
    def forward(self, x, y=None):
        x = self.embedding(x)  #output shape:(batch_size, sen_len, input_dim)
        x, _ = self.layer(x)      #output shape:(batch_size, sen_len, input_dim)
        x = x[:, -1, :]        #output shape:(batch_size, input_dim)
        x = self.dropout(x)
        y_pred = self.classify(x)   #output shape:(batch_size, vocab_size)
        if y is not None:
            return self.cross_entropy(y_pred, y) #[1*vocab_size] []
        else:
            return torch.softmax(y_pred, dim=-1)


#加载字表
def build_vocab(vocab_path):
    vocab = {}
    with open(vocab_path, encoding="utf8") as f:
        for index, line in enumerate(f):
            char = line[:-1]        #去掉结尾换行符
            vocab[char] = index + 1 #留出0位给pad token
        vocab["\n"] = 1
    return vocab

#加载语料
def load_corpus(path):
    return open(path, encoding="utf8").read()

#随机生成一个样本
#从文本中截取随机窗口,前n个字作为输入,最后一个字作为输出
def build_sample(vocab, window_size, corpus):
    start = random.randint(0, len(corpus) - 1 - window_size)
    end = start + window_size
    window = corpus[start:end]
    target = corpus[end]
    # print(window, target)
    x = [vocab.get(word, vocab["<UNK>"]) for word in window]   #将字转换成序号
    y = vocab[target]
    return x, y

#建立数据集
#sample_length 输入需要的样本数量。需要多少生成多少
#vocab 词表
#window_size 样本长度
#corpus 语料字符串
def build_dataset(sample_length, vocab, window_size, corpus):
    dataset_x = []
    dataset_y = []
    for i in range(sample_length):
        x, y = build_sample(vocab, window_size, corpus)
        dataset_x.append(x)
        dataset_y.append(y)
    return torch.LongTensor(dataset_x), torch.LongTensor(dataset_y)

#建立模型
def build_model(vocab, char_dim):
    model = LanguageModel(char_dim, vocab)
    return model



def train(corpus_path, save_weight=True):
    epoch_num = 10        #训练轮数
    batch_size = 1024       #每次训练样本个数
    train_sample = 10240   #每轮训练总共训练的样本总数
    char_dim = 128        #每个字的维度
    window_size = 6       #样本文本长度
    vocab = build_vocab("vocab.txt")       #建立字表
    corpus = load_corpus(corpus_path)     #加载语料
    model = build_model(vocab, char_dim)    #建立模型
    optim = torch.optim.Adam(model.parameters(), lr=0.001)   #建立优化器
    # 预构建完整数据集并一次性转移到MPS
    dataset_x, dataset_y = build_dataset(train_sample, vocab, window_size, corpus)
    dataset_x, dataset_y = dataset_x.to(device), dataset_y.to(device)
    model = model.to(device)
    for epoch in range(epoch_num):
        model.train()
        watch_loss = []
        # 训练时直接切片
        for batch in range(0, train_sample, batch_size):
            x = dataset_x[batch:batch + batch_size]
            y = dataset_y[batch:batch + batch_size]
            optim.zero_grad()    #梯度归零
            loss = model(x, y)   #计算loss
            watch_loss.append(loss.item())
            loss.backward()      #计算梯度
            optim.step()         #更新权重
        print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss)))
    if not save_weight:
        return
    else:
        base_name = os.path.basename(corpus_path).replace("txt", "pth")
        model_path = os.path.join("model", base_name)
        torch.save(model.state_dict(), model_path)
        return

#训练corpus文件夹下的所有语料,根据文件名将训练后的模型放到莫得了文件夹
def train_all():
    for path in os.listdir("corpus"):
        corpus_path = os.path.join("corpus", path)
        train(corpus_path)


if __name__ == "__main__":
    print(torch.backends.mps.is_available())  # 应为 True
    print(torch.backends.mps.is_built())  # 应为 True
    # build_vocab_from_corpus("corpus/all.txt")
    # train("corpus.txt", True)
    start_time = time.time()
    train_all()
    end_time = time.time()
    print(f"执行时间: {end_time - start_time:.6f} 秒")

1.使用mps模式之前,可以判断下,如果其他地方出错则需要debug下。

print(torch.backends.mps.is_available())  # 应为 True
print(torch.backends.mps.is_built())  # 应为 True

2.部分pytorch版本的交叉熵损失函数与mps不兼容,所以需要在损失函数里自定义交叉熵损失函数

3.使用的语料库

由于平台暂时不支持一次性批量上传多份文档就没来得及上传了。

数据链接地址RNN训练语料文件

您可能感兴趣的与本文相关的镜像

PyTorch 2.6

PyTorch 2.6

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值