人工智能大模型AI 技能/插件分布式训练微调深度学习【免费下载链接】ml-engineeringMachine Learning Engineering Open Book项目地址https://gitcode.com/gh_mirrors/ml/ml-engineering点击查看免费下载在机器学习工程中用全尺寸模型、全量 Tokenizer 和完整数据集去调试代码是最常见也最浪费时间的做法——程序启动慢、迭代周期长、调试器在多进程并行场景下失效问题定位的难度被成倍放大。本指南来自 ml-engineering 仓库 的调试章节系统讲解如何基于 Hugging Facetransformers生态快速构造微型随机模型 微型 Tokenizer 微型数据集让调试与开发进入秒级启动的快速迭代循环。读完本文你将掌握四类实战技能任意模型配置的维度裁剪、四种 Tokenizer 词表收缩方案、微型模型与微型 Tokenizer 的组合装配以及微型数据集的两种制作路线直接截取与合成生成。为什么调试时坚持使用小载荷调试时使用全尺寸模型和全尺寸 Tokenizer本质上是一种低效的开发方式。不仅解决问题更难等待程序重启、等待跑到目标代码行所耗费的时间会累积成巨大的时间黑洞严重消耗动力和生产力。解决方案非常简单除非你在测试模型的质量否则永远使用一个微型随机模型并配合一个尽可能小的 Tokenizer。这个原则背后有三个硬核理由等待时间被压缩模型越小加载越快、每次迭代跑到目标断点的时间越短调试循环的周转速度呈数量级提升。资源门槛极低大模型往往需要昂贵的大规模资源而一个微型随机模型保证可以塞进最便宜的单张最小消费级 GPU。手头没有 GPU 时甚至可以用免费的 Colab 完成开发。调试器兼容性任何调试器都能轻松处理单进程但如果模型太大、必须走模型并行化等需要多进程的路线绝大多数调试器会直接失效或无法给出你需要的完整信息。理想的开发环境是单进程 微型模型。由此得出更新后的 ML 开发信条模型越大最终产品生成质量越好模型越小最终产品开始训练的速度越快。注最新研究表明更大并非总是更好但这一表述足够传达本方法的核心意义。当代码已经能正常跑通时再切换到真实模型去验证生成质量。即便如此也建议先尝试能产出合格结果的最小模型只有确认生成基本正确后才用最大的模型做最终验收。制作一个微型模型考虑到transformers生态的流行度与简洁的 API 设计以下讲解以 Hugging Facetransformers模型为例但同样的原理适用于任何其他建模库。制作微型模型的流程极其简单TLDR 四步拉取全尺寸模型的 config 对象缩小 hidden size 以及少数几个贡献了模型主体参数量的维度用收缩后的 config 创建模型保存模型完成。必须牢记这样生成的是一个随机初始化的模型不要指望它的输出有任何质量可言——它存在的唯一目的是让代码快速跑通。以 google/mt5-small 为例将其转换为微型随机版本from transformers import MT5Config, MT5ForConditionalGeneration mname_from google/mt5-small mname_very_small mt5-tiny-random config MT5Config.from_pretrained(mname_from) config.update(dict( d_model64, d_ff256, )) print(new config, config) very_small_model MT5ForConditionalGeneration(config) print(fnum of params {very_small_model.num_parameters()}) very_small_model.save_pretrained(mname_very_small)整个过程不需要任何 GPU——即便是 BLOOM-176B 这种 1760 亿参数的巨兽也能做同样的事因为你从未真正加载原始模型权重只加载了它的 config 对象。如果想让模型更小可以在修改 config 前先打印原始参数挑选更多可收缩的维度。例如用更少的层数会让模型更小、更好调试config.update(dict( d_model64, d_ff256, d_kv8, num_layers8, num_decoder_layers8, num_heads4, relative_attention_num_buckets32, ))两类必须联动收缩的配置陷阱陷阱一编码器-解码器模型的层数要成对收缩。MT5 这类 encoder-decoder 模型同时拥有num_layers和num_decoder_layers两个计数。num_decoder_layers只在未设置时才回退到num_layers而从真实 checkpoint 加载的 config 早已显式设置过它——所以只改num_layers会导致解码器仍保持全深度。其他 encoder-decoder 架构的命名不同BART、Marian、Pegasus、Whisper 将这对参数命名为encoder_layers与decoder_layers但规则一致两层计数必须一起收缩。原始 google/mt5-small 模型文件约 1.2GB经过上述维度收缩再加上后续小节介绍的词表收缩可降到 126MB。陷阱二嵌套 config 与交错层类型。对于多层嵌套的 config必须逐层分别更新。以 IDEFICS 为例其 config 包含 1 个主对象和 2 个嵌套对象config config.perceiver_config config.vision_config收缩时需要同时更新主 config 与子 configconfig.update(dict( hidden_size64, intermediate_size37, num_hidden_layers5, num_attention_heads4, max_position_embeddings64, max_sequence_length64, )) # 子对象需要直接更新无法从顶层 dict 间接设置 config.vision_config.update(dict(embed_dim64))完整可运行的脚本见 idefics-make-tiny-model.py该脚本没有做词表收缩仅用于演示嵌套 config 的更新方式。另一个相关陷阱交错块架构的模型会把块类型写成逐层对应的layer_types列表。例如 Qwen/Qwen-AgentWorld-35B-A3B 在num_hidden_layers: 40之外还携带 40 个linear_attention/full_attention条目二者必须联动裁剪和上面的嵌套场景一样两个键都位于text_config之下。若不同步收缩会有两种失败方式保存收缩后的 config 再重新加载会直接拒绝StrictDataclassClassValidationError: Class validation error for validator validate_layer_type: ValueError: num_hidden_layers (2) must be equal to the number of layer_types (40)只在内存中修改 config 对象则不会报错——模型会悄悄地从未裁剪的列表前 2 个条目构建你的 2 层。由于该模型的 full attention 块首次出现在索引 3你会得到两个 linear-attention 层而想保留的块一个都没有。正确做法是同步裁剪列表config.text_config.update(dict( num_hidden_layers4, layer_typesconfig.text_config.layer_types[:4], ))如果直接编辑config.json也可以删除layer_types条目让 config 按新的深度从full_attention_interval重新生成模式。无论哪种方式请选择能覆盖完整一个模式周期period的深度——该模型用 4 而非 2——否则你想保留的块变体会消失。用半精度进一步减半体积接下来可以再让模型小一半——按目标需求在保存前转为 fp16 或 bf16very_small_model.half() # 转为 fp16 #very_small_model.bfloat16() # 转为 bf16 very_small_model.save_pretrained(mname_very_small)这一步将模型文件压到 64MB。到这一步你的程序启动已经快得多了但还有最后一招可以让模型真正微型化。我们到目前为止还没有收缩词表维度——64×250khidden × vocab依然巨大。诚然 250k 词表并不典型一般模型的词表在 30k50k但即便 30k 对真正微型的目标来说依然太大。词表大小由 Tokenizer 决定因此下一步就是收缩 Tokenizer。制作一个微型 TokenizerToken 收缩的难度因底层 Tokenizer 而异——从相对简单到相当复杂。下面的几个配方来自 Hugging Face 几位出色的 Tokenizer 专家作者在其基础上做了适配。如果你是第一次阅读可以直接跳到 用微型 Tokenizer 制作微型模型 一节需要时再回来看细节。Anthony Moi 版本基于 tokenizers 库直接裁剪Anthony Moi 的方案是直接读取 fast tokenizer 的 JSON 结构保留前 N 个词条并同步裁剪 BPE mergesimport json from transformers import AutoTokenizer from tokenizers import Tokenizer vocab_keep_items 5000 mname microsoft/deberta-base tokenizer AutoTokenizer.from_pretrained(mname, use_fastTrue) assert tokenizer.is_fast, This only works for fast tokenizers. tokenizer_json json.loads(tokenizer._tokenizer.to_str()) vocab tokenizer_json[model][vocab] if tokenizer_json[model][type] BPE: new_vocab { token: i for token, i in vocab.items() if i vocab_keep_items } merges tokenizer_json[model][merges] new_merges [] for i in range(len(merges)): a, b merges[i].split() new_token .join((a, b)) if a in new_vocab and b in new_vocab and new_token in new_vocab: new_merges.append(merges[i]) tokenizer_json[model][merges] new_merges elif tokenizer_json[model][type] Unigram: new_vocab vocab[:vocab_keep_items] elif tokenizer_json[model][type] WordPiece or tokenizer_json[model][type] WordLevel: new_vocab { token: i for token, i in vocab.items() if i vocab_keep_items } else: raise ValueError(fdont know how to handle {tokenizer_json[model][type]}) tokenizer_json[model][vocab] new_vocab tokenizer._tokenizer Tokenizer.from_str(json.dumps(tokenizer_json)) tokenizer.save_pretrained(.)注意其中的细节BPE 类型的模型除了裁剪vocab还必须同步过滤merges——只有当合并对的两个子词和合并结果都在新词表中时才保留该 merge否则合并规则会引用不存在的 token。Unigram 类型则是直接切片保留前 N 个条目。实践中发现一个坑gpt2 在词表最末尾藏着特殊 token|endoftext|简单按 ID 裁剪会把它丢掉导致代码崩溃。修补方式是把特殊 token 放回去if gpt2 in mname: new_vocab { token: i for token, i in vocab.items() if i vocab_keep_items-1 } new_vocab[|endoftext|] vocab_keep_items-1 else: new_vocab { token: i for token, i in vocab.items() if i vocab_keep_items }Lysandre Debut 版本train_new_from_iterator 重新训练Lysandre Debut 的方案是调用 fast tokenizer 自带的train_new_from_iterator在给定语料上重新训练一个小词表from transformers import AutoTokenizer mname microsoft/deberta-base # 或任何拥有 fast tokenizer 的 checkpoint vocab_keep_items 5000 tokenizer AutoTokenizer.from_pretrained(mname) assert tokenizer.is_fast, This only works for fast tokenizers. tokenizer.save_pretrained(big-tokenizer) # 应是一个 list of texts 的生成器 training_corpus [ [This is the first sentence., This is the second one.], [This sentence (contains #) over symbols and numbers 12 3., But not this one.], ] new_tokenizer tokenizer.train_new_from_iterator(training_corpus, vocab_sizevocab_keep_items) new_tokenizer.save_pretrained(small-tokenizer)这个方法需要训练语料作者想到一个取巧的办法用原 Tokenizer 自己的词表作为训练语料从而免去准备真实语料的麻烦from transformers import AutoTokenizer mname microsoft/deberta-base vocab_keep_items 5000 tokenizer AutoTokenizer.from_pretrained(mname) assert tokenizer.is_fast, This only works for fast tokenizers. vocab tokenizer.get_vocab() training_corpus [ vocab.keys() ] # 应是一个 list of texts 的生成器 new_tokenizer tokenizer.train_new_from_iterator(training_corpus, vocab_sizevocab_keep_items) new_tokenizer.save_pretrained(small-tokenizer)这个方案几乎完美唯一缺陷是新词表丢失了每个词/字符的频率信息——而大多数 Tokenizer 正是依赖频率构建词表的。如果你确实需要频率信息可以让每个 key 出现len(vocab) - ID次来模拟training_corpus [ (k for i in range(vocab_len-v)) for k,v in vocab.items() ]代价是脚本运行时间会大幅拉长。但对于微型模型测试用途的需求而言频率完全无关紧要。Hack 文件法直接截断 tokenizer.json有些 Tokenizer 可以直接在文件层面手工截断。例如将 Llama2 的 Tokenizer 收缩到 3k 条目# 收缩原始 vocab 以保持小体积只需足以切分任意单词的量字母符号 # ElectraTokenizerFast 完全由 tokenizer.json 定义其中包含 vocab 和 id # 所以我们只需明智地截断它 import subprocess import shlex from transformers import LlamaTokenizerFast mname meta-llama/Llama-2-7b-hf vocab_keep_items 3000 tokenizer_fast LlamaTokenizerFast.from_pretrained(mname) tmp_dir f/tmp/{mname} tokenizer_fast.save_pretrained(tmp_dir) # 收缩 tokenizer.jsonvocab.txt 会在 save_pretrained 时自动收缩 # perl -0777 -pi -e s|(2999).*|$1},merges: []}}|msg tokenizer.json # 0-indexed所以是 vocab_keep_items-1 closing_pat },merges: []}} cmd (fperl -0777 -pi -e s|({vocab_keep_items-1}).*|$1{closing_pat}|msg {tmp_dir}/tokenizer.json) result subprocess.run(shlex.split(cmd), capture_outputTrue, textTrue) # 用修改后的 tokenizer 重新加载 tokenizer_fast_tiny LlamaTokenizerFast.from_pretrained(tmp_dir) tokenizer_fast_tiny.save_pretrained(.)这里用perl -0777slurp 整个文件配合正则把第vocab_keep_items-1个词条之后的所有内容其余词表与全部 merges整体替换为空。再次强调结果只适用于功能测试不适用于质量工作。SentencePiece 词表收缩SentencePiece 类的 Tokenizer如 XLMRoberta需要另一种做法——直接操作 SentencePiece 的 protobuf 模型git clone https://github.com/google/sentencepiece然后收缩词表# 规避 fast tokenizer 的 protobuf 问题同时也快得多 os.environ[PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION] python from transformers import XLMRobertaTokenizerFast mname xlm-roberta-base # 收缩原始 vocab 以保持小体积 vocab_keep_items 5000 tmp_dir f/tmp/{mname} vocab_orig_path f{tmp_dir}/sentencepiece.bpe.model # 文件名可能不同 vocab_short_path f{tmp_dir}/spiece-short.model # HACK需要 sentencepiece 源码来获取 sentencepiece_model_pb2因为安装包并不带它 sys.path.append(../sentencepiece/python/src/sentencepiece) import sentencepiece_model_pb2 as model tokenizer_orig XLMRobertaTokenizerFast.from_pretrained(mname) tokenizer_orig.save_pretrained(tmp_dir) with open(vocab_orig_path, rb) as f: data f.read() # 改编自 ceshine 博客的方法 m model.ModelProto() m.ParseFromString(data) print(fShrinking vocab from original {len(m.pieces)} dict items) for i in range(len(m.pieces) - vocab_keep_items): _ m.pieces.pop() print(fnew dict {len(m.pieces)}) with open(vocab_short_path, wb) as f: f.write(m.SerializeToString()) m None tokenizer_fast_tiny XLMRobertaTokenizerFast(vocab_filevocab_short_path) tokenizer_fast_tiny.save_pretrained(.)两个关键点其一通过PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATIONpython规避 fast tokenizer 的 protobuf 兼容问题其二sentencepiece_model_pb2不会随 pip 安装包分发必须从克隆的 sentencepiece 源码目录导入。用微型 Tokenizer 制作微型模型词表可以收缩到 Tokenizer 允许的下限——至少需要覆盖目标字母表与特殊字符通常 3k5k 个 token 绰绰有余。有时还能更小毕竟原始 ASCII 字符集也只有 128 个字符。延续前面的 MT5 示例把上一节的 Tokenizer 收缩代码合并进来就得到完整的 mt5-make-tiny-model.py。运行后最终模型文件只有3.34MB该脚本还包含一段验证逻辑确认修改后的模型能与收缩后的 Tokenizer 配合工作——输出内容会是垃圾文本但目的只是验证新模型和新 Tokenizer 功能正常config.update(dict( vocab_sizekeep_items12, d_model64, d_ff256, d_kv8, num_layers8, num_decoder_layers8, num_heads4, relative_attention_num_buckets32, )) ... very_small_model MT5ForConditionalGeneration(config) very_small_model.resize_token_embeddings(len(tokenizer)) ... very_small_model.half() # 使其更小 very_small_model.save_pretrained(mname_very_small) config.save_pretrained(mname_very_small) tokenizer.save_pretrained(mname_very_small)注意这里config.update里还设置了vocab_sizekeep_items12收缩后的词表数 额外特殊 token 数并用resize_token_embeddings(len(tokenizer))让嵌入层与微型词表对齐——这正是把模型和 Tokenizer 装配成一体时的关键一步。另一个极端示例是 fsmt-make-super-tiny-model.py它不依赖任何现有 checkpoint而是从零手写一个微型词表l、o、w、low、lowest 等约 20 个 token 及其 merges再据此构建模型全部文件只有约 60KB。相比保留完整词表 merges 文件、只压缩层数与嵌入维度的路线约 3MB从零构建词表可以做到真正的超微型。两个实用建议把构建脚本和模型存放在一起这样日后可以快速修复问题或生成类似版本的模型。优先寻找现成的微型模型因为transformers官方测试也需要微型模型几乎每种架构都能在 hf-internal-testing 找到对应的微型版本。如果找到的微型模型维度不满足需求也可以在此基础上改造——既然是随机模型本质上只关乎维度是否正确。例如找到的微型模型只有 2 层但你想要 8 层直接用更大的维度重新保存即可。制作一个微型数据集与模型和 Tokenizer 同理为你经常使用的数据集准备一个微型版本能大幅提速。当然这对质量测试无益但非常适合让程序瞬间启动。一个重要的前提说明如果你使用已预建索引的 Arrow 文件数据集微型数据集带来的收益没有微型模型那么显著Arrow 本身已经极快。但如果你希望迭代器在 10 步内跑完一个 epoch与其改代码截断数据集不如直接换一个微型数据集。制作微型数据集的流程因原数据集的 builder 而异不同 builder 差异很大但核心概念非常简单克隆完整数据集的 git 仓库将完整数据 tarball 替换为只含少量样本的微型 tarball把微型结果发布为 Hub 无需自定义脚本即可加载的数据文件Parquet、Arrow、JSON/JSONL、CSV或图像/文本文件夹外加一张 dataset card——完成。注意自datasets4.0 起Hub 托管的 Python 加载脚本不再被load_dataset执行。任何 builder/unpacker.py文件应保留在仓库中作为数据集制作过程的文档、并用于本地再生成但远程用户加载的必须是数据文件本身。路线一直接截取原始数据仓库中的 c4-en-10k.py、oscar-en-10k.py、openwebtext-10k.py 都属于这条路线取原始 tarball抓取前 10k 条记录重新打包成小 tarballbuilder 脚本其余部分基本保持不变。以 c4-en-10k.py 为例其_URL指向一个 c4-en-10k 的压缩包_split_generators解压后交给_generate_examples逐行读取 JSONL 并产出{text: ...}记录——整个结构与官方 C4 builder 完全一致只是数据源换成了微型 tarball。路线二合成数据集可控制任意规模对于更复杂的多模态数据作者采用解包 → 挑选代表样本 → 脚本合成任意规模的路线general-pmd-synthetic-testing.py 与配套解包器 general-pmd-ds-unpack.pycm4-synthetic-testing.py 与配套解包器 m4-ds-unpack.py。这些是更复杂的例子每条样本不仅是纯文本还包含多条文本与图片。解包器负责把每条复杂的多字段记录展开成独立的子目录让你可以在文件系统层面自由增删图片、改短文本等。可以看到连图片也被缩小到 32×32 的微型尺寸——微型化原则被贯彻到所有不破坏目标代码需求的维度上。随后主脚本基于这套目录结构构建任意期望长度的数据集。general-pmd-synthetic-testing.py 的_generate_examples展示了合成的核心逻辑把几条种子记录按需循环复用repeat类型直接重复产出相同记录unique类型则把记录索引号注入文本尾部确保每条记录在文本上唯一。脚本头部注释完整记录了数据集仓库的部署指令# prep dataset repo # 创建数据集仓库 general-pmd-synthetic-testing git clone 数据集仓库地址 cd general-pmd-synthetic-testing # 挑选几条种子记录长短文本各有一些、带图与不带图各有一些、每种类型多几个变体 python general-pmd-ds-unpack.py --dataset_name_or_path \ general_pmd/image/localized_narratives__ADE20k/train/00000-00002 --ids 1-10 --target_path data cd data # 压缩到最大 32x32保持宽高比 mogrify -format jpg -resize 32x32\ */*jpg # 将一条记录调整为既无图也无文本 cd 1 rm image.jpg text.txt touch image.null text.null cd - cd .. # 打 tarball tar -cvzf data.tar.gz data # 完善数据集仓库 echo This dataset is designed to be used in testing. Its derived from general-pmd/localized_narratives__ADE20k \ dataset README.md # 测试数据集——必须能从纯数据文件加载无需远程加载脚本 cd .. python -c from datasets import load_dataset; print(load_dataset(general-pmd-synthetic-testing))与模型一样建议始终把构建脚本与数据集存放在一起方便日后修复或制作相似版本。另外类似微型模型在 hf-internal-testing 下也能找到大量现成的微型数据集。结论在 ML 领域我们拥有数据集、模型和 Tokenizer 三大工件它们各自都可以被微型化从而在极低的资源需求下实现超高速开发。如果你来自其他行业也可以把本章的思想迁移到你自己领域的工作产物上——核心方法论是普适的在保证不破坏被测代码前提条件的前提下把每个维度都压缩到最小用小载荷换取秒级迭代的开发体验。本章脚本备份为了防止本章引用的原始脚本失效或 Hub 不可用仓库在 debug/tiny-scripts 中提供了全部脚本的本地备份mt5-make-tiny-model.py微型 MT5 模型 SentencePiece 词表收缩完整示例idefics-make-tiny-model.py嵌套 config 收缩示例fsmt-make-super-tiny-model.py从零构建超微型词表与模型c4-en-10k.py、oscar-en-10k.py、openwebtext-10k.py直接截取式微型数据集general-pmd-synthetic-testing.py、general-pmd-ds-unpack.py、cm4-synthetic-testing.py、m4-ds-unpack.py多模态合成数据集全套脚本。本章的完整技术原文收录于 调试 PyTorch 程序与之配套的还有debug目录下的 分布式挂起排查、NCCL 性能排查、下溢与上溢分析 等实战笔记可作延伸阅读。赞分享人工智能大模型AI 技能/插件分布式训练微调深度学习【免费下载链接】ml-engineeringMachine Learning Engineering Open Book项目地址https://gitcode.com/gh_mirrors/ml/ml-engineering点击查看免费下载相关推荐Machine Learning Engineering Open Book用 Tiny 模型、Tokenizer 与数据集加速调试与开发Machine Learning Engineering Open Book用 Tiny 模型、Tokenizer 与数据集加速调试与开发 导读 本篇文章基人工智能大模型AI 技能/插件分布式训练微调深度学习Made-With-ML模型开发SciBERT微调与评估体系Made With ML模型开发SciBERT微调与评估体系 文章详细介绍了Made With ML项目中基于SciBERT预训练模型的文本分类系统开发全过程机器学习教程NLPMLOps后端ML-For-Beginners迁移学习预训练模型微调ML For Beginners迁移学习预训练模型微调 你是否还在为训练高性能机器学习模型而烦恼数据不足、算力不够、训练时间过长本文将带你探索迁移学习T教程机器学习人工智能上一篇DNMP自定义服务开发添加新服务的完整教程下一篇无内容可仿写文章内容为空时的创作提示创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?