简明Pytorch分布式训练 — DistributedDataParallel 实践

上一次的Pytorch单机多卡训练主要介绍了Pytorch里分布式训练的基本原理,DP和DDP的大致过程,以及二者的区别,并分别写了一个小样作为参考。小样毕竟还是忽略了很多细节和工程实践时的一些处理方式的。实践出真知,今天(简单)写一个实际可用的 DDP 训练中样,检验一下自己对 DDP 的了解程度,也作为以后分布式训练的模板,逐渐完善。

文章目录

  • DDP 流程回顾
    • 一些前置
  • DDP 实践
    • 训练前
    • 训练时
    • 启动脚本
  • 更上一层楼
  • 结语
  • Reference

DDP 流程回顾

Pytorch单机多卡训练中主要从DDP的原理层面进行了介绍,这次从实践的角度来看看。

试问把大象装进冰箱需要几步?三步,先打开冰箱,再把大象装进去,最后关上冰箱门。那实践中DDP又分为几步呢?

  1. 开始训练/预测前的准备工作。
  2. 开始训练/验证!

看,DDP比把大象装进冰箱简单多了!

一些前置

虽然DDP很简单,但是得先了解一些前置知识。有一些DDP相关的函数是必须要了解的1,主要是和分布式通信相关的函数:

  • torch.distributed.init_process_group2。初始化分布式进程组。由于是单机多卡看的分布式,因此其实是将任务分布在多个进程中。
  • torch.distributed.barrier()3。用于进程同步。每个进程进入这个函数后都会被阻塞,当所有进程都进入这个函数后,阻塞解除,继续执行后续的代码。
  • torch.distributed.all_gather4。收集不同进程中的tensor。这个用到的比较多的,但有一点不太好理解。解释一下:某些变量是不同进程中都会有的,比如loss,如果要把各个进程中计算得到的loss汇总到一起,就需要进程间通信,把loss收集起来,all_gather干的就是收集不同进程中的某个tensor的事儿。
  • local_rank。节点上device的标识。在单机多卡的模式下,每个device的local_rank是唯一的,一般我们都在0号设备上操作。该参数不需要手动传参,在执行DDP时会自动设置该参数。当为非DDP时,该参数值为-1。
  • torch.nn.parallel.DistributedDataParallel5。在初始化好的分布式环境中,创建分布式的模型,负责。

好了,你已经具备DDP实践的主要前置知识了,一起开启DDP实践吧!

DDP 实践

训练前

训练前的准备工作,主要包含:

  • 初始化分布式进程组,使后续各个进程间能够通信。
  • 准备好数据和模型。
torch.cuda.set_device(args.local_rank)
device = torch.device("cuda", args.local_rank)
torch.distributed.init_process_group(backend="nccl", timeout=datetime.timedelta(seconds=3600))
args.device = deviceX, Y = make_classification(n_samples=25000, n_features=N_DIM, n_classes=2, random_state=args.seed)  # 每个进程都会拥有这样一份数据集
X = torch.tensor(X).float()
Y = torch.tensor(Y).float()train_X, train_Y = X[:20000], Y[:20000]
val_X, val_Y = X[20000:], Y[20000:]
train_dataset = SimpleDataset(train_X, train_Y)
val_dataset = SimpleDataset(val_X, val_Y)model = SimpleModel(N_DIM)
if args.local_rank not in (-1, 0):torch.distributed.barrier()
else:# 只需要在 0 号设备上加载预训练好的参数即可,后续调用DDP时会自动复制到其他卡上if args.ckpt_path is not None:model.load_state_dict(torch.load(args.ckpt_path))torch.distributed.barrier()

训练时

与单卡训练不同,DDP训练主要有三个改动地方:

  • 创建数据加载器时,需要创建特定的采样器,以保证每个进程使用的数据是不重复的
  • 创建分布式的模型。
  • 收集一些辅助信息时,例如loss、prediction等,需要all_gather将不同进程中的数据收集到一起。
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank], output_device=args.local_rank, find_unused_parameters=False)train_sampler = RandomSampler(train_dataset) if args.local_rank == -1 else DistributedSampler(train_dataset)
train_dataloader = DataLoader(train_dataset, sampler=train_sampler, batch_size=args.batch_size)...def gather_tensors(target):"""target should be a tensor"""target_list = [torch.ones_like(target) for _ in range(torch.distributed.get_world_size())]torch.distributed.all_gather(target_list, target)ret = torch.hstack(target_list)return retfor epoch in train_iterator:if args.local_rank != -1:train_dataloader.sampler.set_epoch(epoch)  # 必须设置的!确保每一轮的随机种子是固定的!model.zero_grad()model.train()pbar = tqdm(train_dataloader, desc="Training", disable=args.local_rank not in [-1, 0])step_loss, step_label, step_pred = [], [], []for step, batch in enumerate(pbar):data, label = batchdata, label = data.to(args.device), label.to(args.device)prediction = model(data)loss = loss_func(prediction, label.unsqueeze(1))optimizer.zero_grad()loss.backward()optimizer.step()global_step += 1step_loss.append(loss.item())step_label.extend(label.squeeze().cpu().tolist())step_pred.extend(prediction.squeeze().detach().cpu().tolist())if step % args.logging_step == 0:if torch.distributed.is_initialized():  # 兼容非 DDP 的模式step_loss = torch.tensor(step_loss).to(args.device)step_label = torch.tensor(np.array(step_label)).to(args.device)step_pred = torch.tensor(np.array(step_pred)).to(args.device)step_loss = gather_tensors(step_loss)step_label = gather_tensors(step_label).squeeze().cpu().numpy()step_pred = gather_tensors(step_pred).squeeze().cpu().numpy()

单卡训练时,收集一些辅助信息是很方便的,因为所有的数据都在同一进程中,所以不需要同步操作。在DDP模式下,每个进程使用不同的数据训练,为了记录训练过程的信息以及计算指标等情况,需要把所有数据的结果收集起来,这就需要用到all_gather了。

启动脚本

一种常用的DDP启动方式:

#! /usr/bin/env bash
node_num=4# DDP 模式
CUDA_VISIBLE_DEVICES="0,1,2,3" python -m torch.distributed.launch --nproc_per_node=$node_num --master_addr 127.0.0.2 --master_port 29502 ddp-demo.py \
--do_train \
--do_test \
--epochs 3 \
--batch_size 128 \
--lr 5e-6 \
--output_dir ./test_model \
--seed 2024

python -m torch.distributed.launch即把torch.distributed.launch这个模块作为一个脚本运行,后面接的 --nproc_per_node=$node_num --master_addr 127.0.0.2 --master_port 29502 ddp-demo.py都作为这个模块的参数,关于该模块的参数以及含义,可以参考:https://github.com/pytorch/pytorch/blob/main/torch/distributed/launch.py。

更上一层楼

果然,DDP训练比把大象装进冰箱要简单多了。

当然,这主要归功于Pytorch封装的好,还有很多细节是值得我们深究的。以下是一些我觉得可以进一步探索的:

  • 分布式训练时不同的后端之间的区别,比如gloompinccl。主要是加深对多进程通信的了解。
  • torch.nn.parallel.DistributedDataParallel内部的细节,比如梯度的同步过程及涉及到的算法等。
  • DistributedSampler是如何实现不同device读取不同的数据的。这一块其实原理很简单,源码也不多。
  • 多机多卡的实践6

结语

单卡或DP训练相比,DDP启动多个进程,每个进程读取对应的数据,每个step单独训练,能够大大提高显卡的利用率和降低训练时间。

初次接触DDP还是很容易让人茫然的,如果有过多进程的经验的会更好理解其中的一些概念以及分布式训练的流程。

目前写的这个demo虽然比较简单,但常用的内容都涉及到了,后续可以在这个版本上继续完善。

附完整代码:

import os
import time
import random
import logging
import argparse
import datetime
from tqdm import tqdm, trangeimport numpy as np
from sklearn import metrics
from sklearn.datasets import make_classificationimport torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
from torch.utils.data.distributed import DistributedSamplerimport torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDPN_DIM = 200
logging.basicConfig(format='[%(asctime)s] %(levelname)s - %(name)s - %(message)s',datefmt='%m-%d %H:%M:%S',level=logging.INFO)
logger = logging.getLogger("DDP")class SimpleModel(nn.Module):def __init__(self, input_dim):super(SimpleModel, self).__init__()self.fc = nn.Linear(input_dim, 1)def forward(self, x):return torch.sigmoid(self.fc(x))class SimpleDataset(Dataset):def __init__(self, data, target):self.data = dataself.target = targetdef __len__(self):return len(self.data)def __getitem__(self, idx):return self.data[idx], self.target[idx]def set_seed(args):seed = args.seedrandom.seed(seed)np.random.seed(seed)torch.manual_seed(seed)if args.n_gpu > 0:torch.cuda.manual_seed_all(seed)def parse_args():parser = argparse.ArgumentParser()## DDP:从外部得到local_rank参数。从外面得到local_rank参数,在调用DDP的时候,其会自动给出这个参数parser.add_argument("--local_rank", default=-1, type=int)parser.add_argument("--no_cuda", action="store_true", help="Whether to cuda.")parser.add_argument("--seed", default=2024, type=int)parser.add_argument("--ckpt_path", default="./ddp-outputs/checkpoint-100/model.pt", type=str)parser.add_argument("--output_dir", default="./ddp-outputs/", type=str)parser.add_argument("--epochs", default=3, type=int)parser.add_argument("--lr", default=1e-5, type=float)parser.add_argument("--batch_size", default=2, type=int)parser.add_argument("--val_step", default=50, type=int)parser.add_argument("--save_step", default=10, type=int)parser.add_argument("--logging_step", default=2, type=int)parser.add_argument("--do_train", action="store_true", help="Whether to run training.")parser.add_argument("--do_test", action="store_true", help="Whether to run eval on the test set.")args = parser.parse_args()return argsdef gather_tensors(target):"""target should be a tensor"""target_list = [torch.ones_like(target) for _ in range(torch.distributed.get_world_size())]torch.distributed.all_gather(target_list, target)# ret = torch.cat(target_list, 0)ret = torch.hstack(target_list)return retdef train(args, model, train_dataset, val_dataset=None):if args.n_gpu > 1:model = torch.nn.DataParallel(model)model = model.to(args.device)if args.local_rank != -1:model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank], output_device=args.local_rank, find_unused_parameters=False)logger.info(f"After DDP, Rank = {args.local_rank}, weight = {model.module.fc.weight.data[0, :10]}")# 采样器train_sampler = RandomSampler(train_dataset) if args.local_rank == -1 else DistributedSampler(train_dataset)train_dataloader = DataLoader(train_dataset, sampler=train_sampler, batch_size=args.batch_size)optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=args.lr)loss_func = nn.BCELoss().to(args.local_rank)total_steps = len(train_dataloader) * args.epochsglobal_step = 0train_iterator = trange(0, int(args.epochs), desc="Epoch", disable=args.local_rank not in [-1, 0])for epoch in train_iterator: #range(args.epochs):  # train_iterator:if args.local_rank != -1:train_dataloader.sampler.set_epoch(epoch)if args.local_rank in (-1, 0):logger.info('*' * 25 + f" Epoch {epoch + 1} / {args.epochs} " + '*'* 25)model.zero_grad()model.train()pbar = tqdm(train_dataloader, desc="Training", disable=args.local_rank not in [-1, 0])step_loss, step_label, step_pred = [], [], []for step, batch in enumerate(pbar):data, label = batchdata, label = data.to(args.device), label.to(args.device)prediction = model(data)loss = loss_func(prediction, label.unsqueeze(1))optimizer.zero_grad()loss.backward()optimizer.step()global_step += 1time.sleep(0.1)step_loss.append(loss.item())step_label.extend(label.squeeze().cpu().tolist())step_pred.extend(prediction.squeeze().detach().cpu().tolist())if step % args.logging_step == 0:if torch.distributed.is_initialized():  # 兼容非 DDP 的模式step_loss = torch.tensor(step_loss).to(args.device)step_label = torch.tensor(np.array(step_label)).to(args.device)step_pred = torch.tensor(np.array(step_pred)).to(args.device)step_loss = gather_tensors(step_loss)step_label = gather_tensors(step_label).squeeze().cpu().numpy()step_pred = gather_tensors(step_pred).squeeze().cpu().numpy()if args.local_rank in (-1, 0):# logger.info(f"Gathered loss = {step_loss}")# logger.info(f"Gathered label = {step_label}")# logger.info(f"Label shape = {step_label.shape}, Pred = {step_pred}, {prediction.shape}")if not all(step_label == 1) and not all(step_label == 0):auc = metrics.roc_auc_score(step_label, step_pred)# logger.info(f"Step AUC = {auc:.5f}")pbar.set_description(f"loss={step_loss.mean():>.5f}, auc={auc:.5f}")step_loss, step_label, step_pred = [], [], []if step % args.val_step == 0 and val_dataset is not None:test_val(args, model, val_dataset)if global_step % args.save_step == 0 and global_step > 0 and args.local_rank in [-1, 0]:save_path = os.path.join(args.output_dir, f"checkpoint-{global_step}")if not os.path.exists(save_path):os.makedirs(save_path)model_to_save = model.module if hasattr(model, 'module') else modeltorch.save(model_to_save.state_dict(), os.path.join(save_path, "model.pt"))torch.save(optimizer.state_dict(), os.path.join(save_path, "optimizer.pt"))def test_val(args, model, dataset):# 采样器test_sampler = RandomSampler(dataset) if args.local_rank == -1 else DistributedSampler(dataset)test_dataloader = DataLoader(dataset, sampler=test_sampler, batch_size=args.batch_size)ground_truth = []prediction = []pbar = tqdm(test_dataloader, desc="Testing", disable=args.local_rank not in [-1, 0])with torch.no_grad():for step, batch in enumerate(pbar):data, label = batchdata, label = data.to(args.device), label.to(args.device)pred = model(data)time.sleep(0.05)ground_truth.extend(label.cpu().squeeze().tolist())prediction.extend(pred.cpu().squeeze().tolist())ground_truth = torch.tensor(ground_truth).to(args.device)prediction = torch.tensor(prediction).to(args.device)if args.local_rank != -1:  # 等待所有进程预测完,再进行后续的操作torch.distributed.barrier()if torch.distributed.is_initialized():  # 由于每张卡上都会有 prediction 和 ground_truth,为了汇集所有卡上的信息,需要 torch.distributed.all_gatherground_truth = gather_tensors(ground_truth)prediction = gather_tensors(prediction)if args.local_rank in [-1, 0]:print(f"GT : {ground_truth.shape}\tPred : {prediction.shape}")auc = metrics.roc_auc_score(ground_truth.detach().cpu(), prediction.detach().cpu())logger.info(f"Test Auc: {auc:.5f}")def main(args):if args.local_rank == -1 or args.no_cuda:device = torch.device("cuda" if torch.cuda.is_available() and not args.no_cuda else "cpu")args.n_gpu = torch.cuda.device_count()else:torch.cuda.set_device(args.local_rank)device = torch.device("cuda", args.local_rank)torch.distributed.init_process_group(backend="nccl", timeout=datetime.timedelta(seconds=3600))args.n_gpu = 1args.device = deviceif args.local_rank in (-1, 0):logger.info('*' * 66)for k, v in vars(args).items():print(f"########## {k:>20}:\t{v}")logger.info('*' * 66)set_seed(args)model = SimpleModel(N_DIM)if args.local_rank not in (-1, 0):logger.info(f"Barrier in {args.local_rank}")torch.distributed.barrier()  # barrier 用于进程同步。每个进程进入这个函数后都会被阻塞,当所有进程都进入这个函数后,阻塞解除。else:if args.ckpt_path is not None:logger.info(f"Load pretrained model in {args.local_rank}")model.load_state_dict(torch.load(args.ckpt_path))logger.info(f"Barrier in {args.local_rank}")torch.distributed.barrier()logger.info(f"After barrier in {args.local_rank}")logger.info(f"Before DDP, Rank = {args.local_rank}, weight = {model.fc.weight.data[0, :10]}")X, Y = make_classification(n_samples=25000, n_features=N_DIM, n_classes=2, random_state=args.seed)  # 每个进程都会拥有这样一份数据集X = torch.tensor(X).float()Y = torch.tensor(Y).float()# 通过这个可以看到,在不同进程上的 X 的 id 是不同的,但是内容是一致的logger.info(f"Rank = {args.local_rank}, id(X) = {id(X)}, Y[:20] = {Y[:20]}")  train_X, train_Y = X[:20000], Y[:20000]val_X, val_Y = X[20000:], Y[20000:]train_dataset = SimpleDataset(train_X, train_Y)val_dataset = SimpleDataset(val_X, val_Y)if args.do_train:train(args, model, train_dataset, val_dataset)if args.do_test:test_val(args, model, val_dataset)if __name__ == "__main__":args = parse_args()main(args)

在这里插入图片描述

Reference


  1. DISTRIBUTED COMMUNICATION PACKAGE - TORCH.DISTRIBUTED. ↩︎

  2. 官方文档:torch.distributed.init_process_group. ↩︎

  3. 官方文档:torch.distributed.barrier. ↩︎

  4. 官方文档:torch.distributed.all_gather. ↩︎

  5. 官方文档-torch.nn.parallel.DistributedDataParallel. ↩︎

  6. Multi Node PyTorch Distributed Training Guide For People In A Hurry. ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/782091.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解数据结构(1):复杂度详解

文章主题:复杂度详解🌱所属专栏:深入理解数据结构📘作者简介:更新有关深入理解数据结构知识的博主一枚,记录分享自己对数据结构的深入解读。😄个人主页:[₽]的个人主页🔥…

【微服务】OpenFeign+Sentinel集中处理远程调用异常

文章目录 1.微服务基本环境调整1.对10004模块的application.yml调整2.启动nacos以及一个消费者两个提供者3.测试1.输入http://localhost:8848/nacos/index.html 来查看注册情况2.浏览器访问 http://localhost:81/member/nacos/consumer/get/13.结果 2.使用OpenFeign实现微服务模…

2024年北京通信展|北京国际信息通信展览会|北京PT展

2024年北京通信展|北京国际信息通信展览会|北京PT展 2024年中国国际信息通信展览会(PTEXPO),是由工业和信息化部主办的ICT行业盛会,自1990年创办以来,已成功举办31届,是反映信息通信行业发展最新成果的重要…

【Java数据结构】关于栈的操作出栈,压栈,中缀表达式,后缀表达式,逆波兰表达式详解

🔥个人主页:努力学编程’ 🔥内容管理:java数据结构 上一篇文章我们讲过了java数据结构的链表,对于链表我们使用了它的一些基本操作,完成了扑克牌小游戏的操作,如果你感兴趣的话,点…

MATLAB 自定义均值滤波 (53)

MATLAB 自定义均值滤波 (53) 一、算法介绍二、算法实现1.原理2.代码一、算法介绍 均值滤波,是一种常见的点云平滑算法,改善原始点云的数据质量问题,MATLAB自带的工具似乎不太友好,这里提供自定义实现的点云均值滤波算法,具体效果如下所示: 均值滤波前: 均值滤波后:…

Pycharm选择使用Anaconda环境中的Pytorch 失败解决办法之一

前几日想要复现一篇论文,结果给配的台式机完全禁不住,老是报溢出,慢都没事,溢出就很难受了,因此想用自己笔记本的GPU来训练。 安装以后遇到一个问题: Anaconda里创建了环境,安装好了对应pytor…

RPC--远程调用

通信调用 程序A(加密) 程序B 内存共享 (本地RPC) 发送窗口信息 (本地RPC) --长度有限制 串口通讯 com口 --浏览器不开串口... 通讯管道(防止多开) (本地RPC) --对我们不可见. 网络 TCP/IP (远程RPC) --good! 浏览器(, ws) <--- 任意语言开发的软件 --任意语言控制浏览器. 注…

Linux基础知识

文章目录 一、入门命令&#xff1a;1.find 条件 要查找的文件满足的条件&#xff08;从当前目录开始查找&#xff09;&#xff1a;2.locate 文件名&#xff1a;3.lear CTRL L &#xff08;清除终端窗口&#xff09;与cat&#xff08;打印输出文件内容&#xff09;&#xff1a…

网络性能提升10%,ZStack Edge 云原生超融合基于第四代英特尔®至强®可扩展处理器解决方案发布

随着业务模式的逐渐转变、业务架构逐渐变得复杂&#xff0c;同时容器技术的兴起和逐渐成熟&#xff0c;使得Kubernetes、微服务等新潮技术逐步应用于业务应用系统上。 为了充分释放性能、为业务系统提供更高效的运行环境&#xff0c;ZStack Edge 云原生超融合采用了第四代英特尔…

ROS传感器图像转换

ros通过摄像头来获得图片&#xff0c;传感器数据类型为sensor_msgs中的Image&#xff0c;具体的数据类型组成&#xff1a; sensor_msgs/Image Documentationhttp://docs.ros.org/en/api/sensor_msgs/html/msg/Image.html但是我们一般使用opencv对图像进行处理&#xff0c;所以…

elementui 导航菜单栏和Breadcrumb 面包屑关联

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 文章目录 系列文章目录前言一、elementui 导航菜单栏和Breadcrumb 面包屑怎么关联&#xff1f;二、实现效果三、实现步骤1.本项目演示布局2.添加面包屑2.实现breadcrumbName方法3.监听方法4.路由指配5.路由配置…

全国青少年软件编程(Python)等级考试三级考试真题2023年12月——持续更新.....

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;三级&#xff09; 分数&#xff1a;100 题数&#xff1a;38 一、单选题(共25题&#xff0c;共50分) 1.一个非零的二进制正整数&#xff0c;在其末尾添加两个“0”&#xff0c;则该新数将是原数的&#xf…

具备实时数据更新能力的大语言模型——Larimar

ChatGPT、Claude.ai等大模型产品就像“图书馆”一样为我们生成各种各样的内容。但是想更新这个图书馆里的知识却不太方便&#xff0c;经常需要漫长、费时的预训练、蒸馏才能完成。 研究人员提出了一种具有情景记忆控制的大语言模型Larimar&#xff0c;这是一种类似人脑"海…

love 2d win 下超简单安装方式,学习Lua 中文编程 刚需!!

一、下载love 2d 参考&#xff1a;【Love2d从青铜到王者】第一篇:Love2d入门以及安装教程 或直接下载&#xff1a; 64位&#xff0c;现在一般电脑都可以用。 64-bit zipped 32位&#xff0c;很复古的电脑都可以用。 32-bit zipped 二、解压 下载好了之后&#xff0c;解压到…

css3之动画animation

动画animation 一.优点二.定义和使用三.动画序列和解释四.常见属性及解释五.简写&#xff08;名字和时间不能省略&#xff09;&#xff08;持续时间在何时开始的时间前&#xff09;&#xff08;简写中无animation-play-state)六.例子1.大数据热点图2.奔跑的熊大&#xff08;一个…

vitess执行计划缓存 测试

打开执行计划器缓存&#xff1a; sysbench /usr/local/share/sysbench/oltp_write_only.lua --mysql-host127.0.0.1 --mysql-port15306 --mysql-userroot --mysql-password --mysql-dbcustomer --report-interval10 100s sysbench /usr/local/share/sysbench/oltp_read_only.l…

vlanif三层交换机实现不同网络通信

实验目的&#xff1a;通过三层交换机实现不同 网络通信&#xff0c;之前都是路由器进行不同网络转发 拓扑图 内容&#xff1a;左边vlan10&#xff0c;右边vlan20 lsw1接口通过所有vlan lsw2网路vlan10 lsw3网络vlan20 问题点&#xff1a;开始只是配置了最上面LSW1的交换机…

三台电机的顺启逆停

1&#xff0c;开启按钮输入信号是 电机一开始启动&#xff0c;5秒回电机2启动 &#xff0c;在5秒电机三启动 关闭按钮输入时电机3关闭 &#xff0c;5秒后电机2关闭 最后电机一关闭 2&#xff0c;思路开启按钮按下接通电机1 并且接通定时器T0 定时器T0 到时候接通电机2 并且开…

Predict the Next “X” ,第四范式发布先知AIOS 5.0

今天&#xff0c;第四范式发布了先知AIOS 5.0&#xff0c;一款全新的行业大模型平台。 大语言模型的原理是根据历史单词去不断预测下一个单词&#xff0c;换一句常见的话&#xff1a;Predict the Next “Word”。 当前对于行业大模型的普遍认知就是沿用这种逻辑&#xff0c;用大…

聊聊多版本并发控制(MVCC)

多版本并发控制&#xff08;MVCC&#xff09; MVCC一直是数据库部分的高频面试题&#xff0c;这篇文章来聊聊MVCC是什么&#xff0c;以及一些底层原理的实现。 当前读和快照读&#xff1a; 当前读&#xff1a;读取的是事务最新的版本&#xff0c;读取的过程中其他并发事务不…