Kempner
kempner是一种计算机集群的概念,这边暂时不太阐述
剩下的就是虚拟环境——运行环境配置
跟着文件中一步步来就好
conda create -y -n LLM python=3.10 ipython
conda activate LLM
conda install -y pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
git clone https://github.com/allenai/LLM.git
cd LLM
pip install -e .
# 提前下载下游评估数据代码
Pre-download all the downstream evals. In a Python shell:
from olmo.eval.downstream import *
tokenizer = Tokenizer.from_file("tokenizers/allenai_eleuther-ai-gpt-neox-20b-pii-special.json")
for x in label_to_task_map.values():
x(tokenizer=tokenizer)
LUMI
LUMI是一种云计算资源,可以在上面部署你的程序,文件介绍了使用方法,这里不做阐述
官网介绍
欧洲高性能计算联合企业(EuroHPC JU)正在汇集欧洲资源,开发用于处理大数据的顶级超大规模超级计算机,其基础是具有竞争力的欧洲技术。
泛欧超大规模前超级计算机之一 LUMI 位于芬兰卡亚尼的 CSC 数据中心。
该超级计算机由 LUMI 财团托管。LUMI(大型统一现代基础设施)联盟国家包括芬兰、比利时、捷克共和国、丹麦、爱沙尼亚、冰岛、挪威、波兰、瑞典和瑞士。
LUMI 是世界上最著名的科学仪器之一,寿命为 2021-2027 年。
LUMI 提供多种计算分区和计算能力。所有容量都以三种单位计算:GPU(图形处理单元)小时数、CPU(中央处理器)小时数和存储小时数。LUMI 上的所有项目都需要以这三种单位的组合来申请和配置资源。LUMI 的潜在用户有两种申请资源的途径。
Notes
这里主要是介绍如何训练OLMo,包含了更多的技术启动细节
配置
安装torch以及相关依赖
pip install -e .
启动
我们的训练脚本是scripts/train.py,应通过 torchrun 或 Slurm(见下文)启动,因为它仅支持分布式训练(在GPU上)。训练脚本的第一个参数是指向训练配置文件的路径。然后,它接受任意数量的可选参数,这些参数可以使用点表示法覆盖配置文件中的值。例如,要更改学习率,您可以传递 --optimizer.learning_rate=0.0001。
scripts/train.py为训练文件,启动即可以开始训练
参数第一个是配置,后面可以跟其他需要更改的训练参数
这个部分提到了Cirrascale,这是一个云计算服务,如果本地运行则使用传统使用方法就好
torchrun --nproc-per-node=8 scripts/train.py configs/c4-tiny.yaml \
--save_folder=/tmp/
继续训练
以启动方式开始训练即可,后面跟上–load_path=/path/to/checkpoint_directory
保存形式一般是分片式权重
-
分片检查点(Sharded Checkpoints): 在分片检查点中,模型的权重被拆分为多个部分,每个部分独立保存在不同的文件中。这种方式有助于分布式训练,其中不同的处理器或设备可以独立保存和加载它们负责的权重部分。分片检查点的优势在于在多GPU或多设备训练时更高效,因为每个设备只需处理自己负责的权重片段。
-
非分片检查点(Unsharded Checkpoints): 在非分片检查点中,整个模型的权重被保存在一个单独的文件中。这种方式相对简单,适用于单GPU或单设备训练。加载非分片检查点可能在某些情况下会更慢,因为整个模型权重必须一次性加载。
scripts/unshard.sh文件提供两者的转换
官方给出了他们训练时的权重追踪曲线:https://wandb.ai/ai2-llm还有训练时的权重gs://ai2-olmo/<wandb_run_path>
加载
1.直接加载训练的checkpoint
例子中是他们训练效果较好的ckpt
from olmo import Olmo, Tokenizer
checkpoint = "gs://ai2-olmo/ai2-llm/c4-small/euox4j8q/step73000-unsharded"
model = Olmo.from_checkpoint(checkpoint, device="cuda")
tokenizer = Tokenizer.from_checkpoint(checkpoint)
生成文本代码
步骤:1.encode输入 2.转化为tensor并且添加batch维度 3.forward 4.得到生成的tensor并且转化为list
5.decoder到文字
# Prepare inputs.
# Note: we don't want the EOS token added to the end of the input, hence
# the `add_special_tokens=False`.
input_ids = tokenizer.encode("I'm a large language model, ", add_special_tokens=False)
# `model.generate()` expects a batch.
input_tensor = torch.tensor(input_ids).unsqueeze(0)
# Run beam search.
outputs = model.generate(input_tensor, max_steps=3, beam_size=3)
# The output token IDs are shape (batch_size, beam_size, max_steps)
best_generation = outputs.token_ids[0][0].tolist()
print(tokenizer.decode(best_generation))
RELEASE_PROCESS
帮助开发者管理github发布
Safetensors
提供转化方法,safetensors_util.py 把.pt转成.safetensors格式,由于优化器和模型是分开的,要转换两次。
当两者同时存在时,程序会进行检查,safetensors格式优先加载
.safetensors比.pt加载更快的解释
OLMo使用 torch.save() 保存非分片检查点,该函数使用 Python 标准库中的 pickle 将状态字典写入文件。问题在于 pickle 运行速度较慢,单线程运行,并且强制我们在读取任何部分之前先读取整个文件。对于 65B 模型,检查点大约为 700GB,加载需要很多分钟,因此这是一个显著的问题。此外,在运行时使用 8 个 GPU 的机器上,我们无法并行加载模型 8 次,因为会耗尽内存。
Huggingface 的 safetensors 格式解决了这些问题。Safetensors 提供了一种以使在加载时张量的数据将从磁盘内存映射的方式存储状态字典。这意味着在实际访问张量之前,张量实际上并不会加载。在同一台机器上的多个进程加载相同的 safetensors 文件时(这正是当 OLMo从检查点加载模型时发生的情况),数据将仅读取一次。
不幸的是,safetensors 还不够远。如果你有一个符合类型 Dict[str, Tensor] 的状态字典(即将字符串映射到张量的Python字典),那么 safetensors 可以执行其任务。这对于模型权重有效,但对于优化器状态无效。因此,我们在 safetensors_util.py 上添加了一层,将 OLMo 需要的模型和优化器状态的数据类型映射到 safetensors 需要执行其操作的数据类型。
TRAINLOG
官方的训练日志和心得,帮助大家踩坑,提供优化建议,这里由于刚初学,不进行分析,只进行翻译,图片会在后续做到对应部分进行补充和对比
实验日志
2023年07月12日
在过去的一周中,我们一直在追踪一个问题,即我们的损失曲线看起来像这样:[屏幕截图链接]。
我们的MosaicML同事建议我们的数据可能没有正确混合,但我们仔细审查了代码并未发现问题。然而,在耗尽所有其他可能性之后,我们无法找到更多线索,因此决定尝试绘制随时间变化的批次组成图。结果,批次组成确实发生了显著变化:
在这个图中,橙色表示来自Common Crawl的内容,绿色表示来自The Stack的内容,即代码。正如您所看到的,代码的比例随时间显著变化,如果将这两个图形叠加在一起,您会发现更多的代码意味着更低的损失。因此,显然我们的洗牌确实存在问题。
在构建批次时,我们将所有内容连接成一个巨大的实例(样本)数组,然后对数组进行洗牌。我们使用torch.randperm()进行洗牌。长话短说,结果表明torch.randperm()的洗牌效果不佳。当您在时间轴上绘制最终进入我们的批次的实例的索引时,会看到一个非常明显的模式:
虽然找出为什么会发生这种情况可能很有趣,但我们将其留给PyTorch团队来解决,并重新实现了我们的洗牌代码以使用NumPy。现在曲线看起来像这样:
漂亮而随机!
2023年04月26日
大量的节点数意味着较高的故障率。昨天的运行在凌晨3点耗尽了内存,所以我们没有完成希望完成的批次。今天的问题:
- 与我们用于检查点的超快速网络驱动器似乎有问题。尽管昨天还正常工作,但今天写入检查点一直失败。我们暂时切换到一个较慢的驱动器进行检查点,以取得一些进展。
- 有时节点之间的Slingshot互联在“Cassini Event Queue overflow”消息中失败。解决方法是通过设置环境变量FI_CXI_DEFAULT_CQ_SIZE为131072(或其他较大的数字)来设置较大的事件队列大小。
- 启动作业时,许多节点无法启动。至少有两个导致这种情况的独立问题。RCCL或设备驱动程序的底层故障未得到正确记录。相反,它们只是打印到stdout和stderr。我们修改了启动脚本,以便在启动期间出现错误时仍然告诉我们哪个节点报告了哪个错误。使用这个,我们可以在启动时排除一个节点在启动期间发生错误时。这是一个繁琐的过程。启动64个节点的作业通常会花费30分钟或更长时间,因为由于这些故障而导致。
- 尽管所有这些问题,模型现在正在正常训练,截至撰写本文时,我们已经训练了70亿个令牌。我们甚至有了第一次适当的损失峰值!
2023年04月25日
有关检查点的问题已解决,我们正在进行一次大规模的训练运行。我们利用这个机会来跟踪速度与节点数的关系。
结果相当不错。我们失去了大约20%的效率以弥补通信开销,这是可以接受的。使用64个节点,我们不再需要执行渐变累积,因
此可能这就是64个节点的配置实际上比32个节点的配置更快的原因。
2023年04月24日
我们发现Torch 2存在越来越多的问题。我们必须使用Torch 2,因为使我们的硬件正常工作的一些驱动程序仅适用于Torch 2,但在其当前状态下,它似乎确实是半成品。问题的复杂性在于我们试图使用MosaicML的Composer来运行训练,但Torch 2目前尚未正式受Composer支持。为了避免在两个不稳定的软件之上堆叠,我们决定编写自己的训练器而不是依赖于Composer。
虽然我们正在追踪围绕torch.compile()和随机数的一些较小的问题,但主要问题是检查点。将使用Torch的FSDP写入磁盘的模型编写起来出奇地困难。
2023年04月18日
尽管从科学角度来看不是绝对必要,但我们认为训练中等大小的模型达到300B令牌可能是个好主意,以摸清系统的状况,确保我们走在正确的轨道上。我们没有办法在此运行之前完成数据,因此我们只是在C4上进行训练。但为了使这个运行作为基准有些合理,我们想要有一个相对合理的验证集。我们选择了最近发布的Red Pajama数据,因为它与我们想要使用我们自己的数据进行的工作非常接近。数据团队正在努力中。
2023年04月17日
我们最初的模型设置和大小来自MosaicML。我们发现它们比它们所说的要小得多,因此我们回到PaLM论文并采用了那些设置。我们的大多数架构选择都遵循PaLM论文,因此使用模型的维度也是有道理的。
由于新的大小并不完全符合1B/7B/70B的方案,因此我们现在称它们为“小”、“中”和“大”。新的大小如下:
- extra-tiny-debug:16M参数,16169216-h参数,3291392个非嵌入参数。
- tiny:288M参数,288706560-h参数,237195264个非嵌入参数。
- small:1B参数,1051439104-h参数,948416512个非嵌入参数。
- medium:7.8B参数,7791353856-h参数,7585308672个非嵌入参数。
- large:60.8B参数,60818014208-h参数,60405923840个非嵌入参数。
2023年04月13日
我们一直在尝试使用支持任意注意偏置的FlashAttention的triton实现,这将允许我们使用ALiBi。不幸的是,目前看来这可能不是一个可行的选项。这个特定的实现仅在使用CUDA特定后端的旧版本triton上工作。因此,它无法在AMD GPU上运行。
当HazyResearch/flash-attention有更新时,我们将再次查看这一点。
与此同时,我们第一次运行了70B模型!32个节点,256个GPU。由于最新的PyTorch存在一些问题,阻止我们以最高速度运行,但性能仍然不错。我们只运行了六个批次,因为这只是为了验证设置。在解决一些明显的性能问题后,我们将进行更长时间的运行。
一个有趣的发现是即使只有32个节点,节点经常不干净地启动。我们看到GPU处于不一致的状态,一些节点在约会期间没有出现,而且只有未诊断的挂起
。为了更好地了解情况,我们开始使用基于py-spy的一些工具来诊断整个集群中的挂起进程。
2023年04月12日
今天我们使Slingshot互连正常工作。LUMI集群使用这种类型的互连,将GPU与每秒800GBit/s的聚合连接在一起。对于大规模分布式训练作业,这种互连的速度绝对至关重要。例如,仅在两个节点(16个GPU)上,1.2B模型的速度从每秒7500个令牌/秒/GPU提高到每秒9500个令牌/秒/GPU。这是速度的巨大增长!
在细节上,使其正常工作主要涉及使用正确的库,并确保它们在正确的时间对正确的进程可用。这涉及设置正确的环境变量、设置正确的标志和一般的低级Linux操作。这不是训练大型语言模型的性感部分。
2023年04月11日
PyTorch 2.0带来了一个新功能:torch.compile()。如果您正确设置模型,它承诺可以实现大幅度的加速。我们打算利用这一点,并在NVidia硬件上使其工作,但在AMD硬件上使其工作要困难一些。在AMD工程师的帮助下,我们在今天解决了这个问题,并立即看到1.2B模型的速度提高了15%!15%的提速不容易,所以这是一个巨大的成功。
2023年04月10日
今天我们第一次在LUMI上运行了7B模型。这在8个节点、64个GPU上运行。我们还没有采取许多使其快速的技巧,但我们看到每秒1200个令牌/秒/GPU。这相当不错!
这花了很长时间才能使其工作,主要是由于一个与7B配置无关的问题。ALiBi注意使用一些用于其计算的常量静态张量。我们不希望为每个批次重新计算这些值,因此我们将它们保存在torch的“缓冲区”中,这类似于参数,除了它们不接收梯度更新。这些缓冲区以bf16格式存在,并包含大量-inf,因此从一开始就正在探索许多边缘情况。此外,在FSDP的上下文中,torch缓冲区的定义很差,FSDP对其执行的一些操作导致NaN。解决方案是以一种不受FSDP干扰的方式存储这些张量。
2023年04月06日
LUMI集群已经结束维护!这比预期提前一天。作为维护的一部分,LUMI软件已经更新,这破坏了我们的启动脚本。在这些计算节点上,拓扑结构的特点是每个GPU都与8个CPU有一个快速的连接,并且与其他CPU有一个稍慢的连接。因此,我们使用“CPU绑定”来确保将正确的CPU与正确的GPU配对。这一系统的部分已经崩溃,因此目前我们正在运行时没有运行它。我们从未对其影响进行基准测试,因此目前尚不清楚它实际上有多重要。从CPU到GPU的带宽并不是最大的瓶颈,因此这可能不是问题。
2023年04月03日
我们添加了一个选项,可以在PaLM架构中解耦MLP和Attention计算。也就是说,在每个transformer块内,我们计算MLP(LN(x)) + Attention(LN(x)),而不是MLP(LN(x + Attention(LN(x))))(忽略一些跳过连接
)。这允许增加吞吐量,因为我们可以将单独的前馈和注意输入投影融合到一个单独的线性层中。我们还尝试将输出投影融合到一个单独的线性层中,但这没有帮助,可能是由于连接前馈和注意激活的拼接开销。
2023年04月02日
第一次训练运行!我们对C4的约70B令牌进行了300M模型的训练。这个模型的目的是为了为其他LLM团队提供我们格式中的一些不完全随机的东西,以便他们可以测试他们的评估和推断代码。
这在AMD的集群上仅运行在一个节点上。在AMD硬件上,我们仍然缺少Flash Attention,而且我们无法在运行之前及时使torch.compile()正常工作。这两者都有望提供显着的加速。这个训练运行使用了对编译模型最优的模型设置,尽管不能编译,因为我们希望它成为下游评估的一个代表性模型。
2023年03月28日
由于集群正在维护中,所以我们只是排队一些我们想要运行的功能。我们还利用LUMI的停机时间来构建一个更好的日志记录功能。在运行数千个节点的集群时,很难获得有意义的日志。我们将日志发送到第三方日志提供商logz.io。这很基础,但能够胜任任务。
2023年03月14日
这是一些足够严肃的实验的第一天,值得在这里提到。实验全部是在1.3B模型上进行的,微批次大小逐渐增加,只是为了摸清系统的状况。所有运行都在小型分区上进行,这只是为了调试。我每次只保留一个节点。运行时间限制为15分钟,这对于性能到稳定性来说太短,但足以获得一个大致的概念。结果如下:
- WandB集成有效。
- 使用slurm启动节点有效。我们让slurm启动所有内容,但有一种说法认为slurm应该只在每个节点上启动一个进程,然后您可以使用torch.distributed在节点上分布。我不确定这对我们有什么好处,而且它是混合中的一个额外组件,所以我们没有以这种方式做。
- 自动重启有效。一个运行被杀死并自动重新启动。重新启动有效是很好的,但在不到45分钟的运行时间内我们就已经在一个节点上进行了采样,这让人有些担忧。