1. 为什么数据质量过滤是大模型预训练的生死线做过大模型预训练的人都有一个共识模型效果的上限在数据准备阶段就已经被决定了。你后面用多少张卡、跑多少天、调多少超参都只是在逼近这个上限而已。我见过太多团队模型结构抄得一模一样训练代码也大差不差但出来的效果就是差一截最后追根溯源问题全出在数据上。MindSpore 作为国内主流的深度学习框架在 Ascend 昇腾硬件上的大模型预训练支持已经相当成熟。但框架本身只解决了“算得动”的问题“喂什么”这件事框架帮不了你。原始爬取或收集的语料里混杂着大量低质内容广告软文、乱码、重复段落、机器翻译腔、甚至完全无关的模板文本。这些东西如果不清洗直接灌进预训练流程轻则浪费算力重则让模型学会一堆垃圾模式后面微调都救不回来。这篇文章要聊的就是在 MindSpore 框架下做大规模预训练时怎么设计一套可落地、可扩展的数据质量过滤方案。我会从整体架构思路讲到具体过滤规则的实现再到分布式场景下的工程细节最后把我自己踩过的坑和排查经验一并倒出来。适合正在做或准备做预训练数据工程的同行参考也适合对大模型数据流水线感兴趣、想了解工业级做法的人。核心关键词就几个MindSpore、大模型预训练、数据质量过滤。整篇内容围绕这三个词展开不跑偏。2. 数据质量过滤的整体设计思路2.1 过滤不是一道工序而是一条流水线很多人一提“数据过滤”脑子里想的就是写个脚本把不满足条件的行删掉。这个理解太窄了。在工业级预训练场景下数据质量过滤是一条多级串联的流水线每一级负责拦截不同类型的脏数据级与级之间有明确的顺序依赖。为什么强调顺序因为不同过滤手段的成本差异巨大。字符级规则过滤几乎不耗算力但模型打分过滤可能要跑一遍推理。如果你把贵的放在前面等于用高成本去处理那些本来用一行正则就能干掉的数据纯属浪费。我通常把整条流水线分成四层第一层格式规范化。统一编码、去除异常字符、标准化空白符。这一层不做“删”只做“改”把数据整理成后续环节能统一处理的形态。第二层规则过滤。基于统计特征和启发式规则做快速筛选比如长度阈值、重复率、特殊符号占比、语言置信度等。这一层是主力能干掉八成以上的低质数据。第三层模型打分。用轻量级质量判别模型对剩余数据打分区分“通顺自然”和“机器味重”的文本。这一层成本高但精度也高。第四层去重与采样。全局去重避免模型反复看同样的内容再根据领域配比做采样控制最终数据的分布。这个分层设计的核心逻辑是用最低的成本拦截最大量的脏数据把昂贵算力留给真正需要精细判断的样本。2.2 为什么选 MindSpore 来做这件事有人会问数据过滤用 Spark 或者纯 Python 不就行了跟 MindSpore 有什么关系关系很大。第一过滤流水线的最终产物要直接对接 MindSpore 的数据集加载流程。MindSpore 的MindDataset、GeneratorDataset对数据格式有特定要求如果过滤阶段产出的格式和加载阶段对不上中间还得做一次转换平白多一道 IO。直接在 MindSpore 生态里做可以保证格式无缝衔接。第二模型打分环节需要推理能力。质量判别模型本身就是一个神经网络用 MindSpore 训练、用 MindSpore 推理整个链路统一不用在框架之间倒腾权重。而且 MindSpore 在 Ascend 上的推理性能有优势打分环节的吞吐能提上去。第三分布式场景下的一致性。预训练数据量动辄 TB 级别单机根本处理不过来。MindSpore 的数据并行机制可以让过滤任务天然分布到多卡多机上而且和后续训练任务共享同一套分布式配置运维成本低。2.3 过滤力度的平衡过犹不及这里必须说一个容易被忽视的问题过滤太狠数据多样性会受损。我见过一个团队规则写得特别激进长度低于 50 字的全删、含数字的全删、含英文的全删。结果过滤完一看剩下的全是那种四平八稳的新闻通稿模型训出来说话跟机器人念稿一样一点灵气都没有。所以过滤方案设计时心里要有一杆秤每一条规则都要问自己它拦掉的是“垃圾”还是“特色”。短文本不一定是垃圾可能是一条精炼的问答含代码的文本不一定是噪声可能是宝贵的编程语料。规则要精细不能一刀切。我的做法是每条规则上线前先在小批量数据上跑一遍人工抽检被拦截的样本确认拦截理由站得住脚再全量应用。这个习惯帮我避免了好几次“误杀”。3. 核心过滤规则的实现细节3.1 字符级与格式规范化处理这一层是地基看起来简单但细节最多。原始语料最常见的几个问题编码混乱、控制字符残留、HTML 标签没清干净、空白符不规范。编码问题在中文语料里尤其突出。有些数据是 GBK 存的有些是 UTF-8混在一起直接读会报错或者出乱码。我的处理方式是统一转 UTF-8转换失败的样本单独落盘人工排查来源。这里有个经验不要用errorsignore静默丢弃非法字符那样会把好好的文本切得七零八落宁可让转换失败暴露出来。控制字符和零宽字符是另一个坑。像\x00、\u200b这类字符肉眼看不见但会干扰分词和模型训练。用正则统一清理import re def normalize_text(text): # 去除控制字符和零宽字符 text re.sub(r[\x00-\x08\x0b\x0c\x0e-\x1f\x7f], , text) text re.sub(r[\u200b-\u200f\u2028-\u202f\ufeff], , text) # 统一空白符 text re.sub(r\s, , text) # 去除首尾空白 text text.strip() return textHTML 标签清理要小心。简单的[^]正则会误伤数学表达式里的尖括号。如果语料里数学内容多建议用专门的 HTML 解析库处理或者至少把正则写得更严格一些只匹配已知的标签名。注意格式规范化阶段不要做任何“删除样本”的操作只做文本变换。删除决策留给后面的规则层这样出问题时容易回溯。3.2 基于统计特征的规则过滤这一层是过滤流水线的主力核心思路是用可量化的统计指标来判断文本质量。我常用的指标有这么几类长度指标。包括字符数、词数、句子数。太短的文本信息量不足太长的可能是拼接的垃圾。但阈值不能拍脑袋定要看你的数据分布。我的做法是先统计全量数据的长度分布取 5% 分位数和 95% 分位数作为参考再结合业务判断微调。重复率指标。包括字符级重复率和 n-gram 重复率。有些垃圾文本是同一句话反复粘贴字符重复率极高。计算方式很简单def char_repeat_ratio(text): if len(text) 0: return 1.0 return 1.0 - len(set(text)) / len(text)这个值超过 0.5 基本可以判定为高度重复的垃圾。特殊符号占比。广告、乱码文本往往夹杂大量特殊符号。统计非字母、非数字、非中文、非常用标点的字符占比超过阈值就拦截。语言置信度。用轻量级语言识别工具判断文本主要语言和目标语言不符的拦截。中文语料里混英文很正常但如果一段文本 90% 是日文那大概率是爬错了。下面这张表是我在实际项目中用过的一套参考阈值注意这只是起点具体项目要按数据分布调整指标参考阈值拦截理由字符数 20 或 100000信息量不足或异常拼接字符重复率 0.5高度重复的垃圾文本特殊符号占比 0.3广告或乱码平均句长 3 或 200碎片化或未分句数字占比 0.5表格或数值堆砌3.3 基于模型的质量打分过滤规则过滤能干掉大部分脏数据但有些问题规则识别不了。比如机器翻译腔的文本字符层面看完全正常但读起来就是别扭。这种就得靠模型打分。我用的方案是训练一个轻量级文本质量判别器。具体做法人工标注一批高质量和低质量样本用 BERT 级别的模型做二分类训练然后在过滤流水线里对每条数据打分低于阈值的拦截。这个判别器的训练有几个要点正负样本要平衡且贴近真实分布。负样本不能全用乱码要包含机器翻译腔、口水文、模板文这些“看起来正常但质量低”的样本否则判别器学不到真正有用的特征。模型不用太大。BERT-base 级别足够太大反而拖慢过滤吞吐。在 MindSpore 里可以用mindspore.nn.BertModel加载预训练权重接一个分类头就行。打分阈值要调。阈值太高会误杀太低起不到过滤作用。我一般会在验证集上画 P-R 曲线选 F1 最高的点作为初始阈值再根据人工抽检微调。推理阶段用 MindSpore 的model.predict批量打分配合Dataset的batch操作吞吐能做得比较高。如果数据量特别大可以把判别器导出成 MindIR用 Ascend 做推理加速。提示模型打分环节建议保留分数不要只保留二值结果。后续如果发现阈值定得不合适有分数在还能重新划线不用重跑整个流水线。3.4 全局去重与领域采样去重这件事很多人低估了它的重要性。预训练语料里重复内容太多模型会过度拟合这些重复模式泛化能力下降。而且重复数据浪费算力等于花同样的钱训了两遍同样的内容。去重分两个粒度精确去重用哈希做完全匹配。对每条文本算 MD5 或 SimHash相同哈希的只保留一条。这个用集合就能搞定但要注意内存TB 级数据得用分片或者布隆过滤器。近似去重用 MinHash LSH 找相似文本。有些文本只改了几个字哈希对不上但实质内容一样。近似去重能抓出这类。MinHash 的实现可以用datasketch库也可以自己写核心是把文本转成 shingle 集合算最小哈希签名再通过 LSH 分桶找候选对。去重之后是领域采样。预训练数据通常来自多个来源新闻、论坛、百科、代码各领域配比直接影响模型能力。我的做法是给每条数据打上领域标签然后按目标配比做加权采样。配比没有标准答案取决于你的模型要干什么。通用模型一般新闻类占比高一些代码类占比低一些如果要做代码助手那代码语料的比例就得往上提。4. MindSpore 下的工程实现与分布式处理4.1 数据流水线的 MindSpore 对接方式过滤完的数据最终要喂给 MindSpore 训练。这里有个工程细节值得展开过滤产出的格式怎么和 MindSpore 数据集加载对齐。MindSpore 常用的数据加载方式有几种MindDataset读 MindRecord 格式、GeneratorDataset从 Python 生成器读、TFRecordDataset读 TFRecord。预训练场景下我推荐把过滤后的数据转成MindRecord格式原因是它读取效率高、支持随机访问、和 MindSpore 生态结合最紧密。转换流程大致是过滤后的文本先落成 JSONL 或者纯文本然后用mindspore.mindrecord.FileWriter写入 MindRecord。写入时把文本、领域标签、质量分数都作为字段存进去后续训练时可以根据需要取用。import mindspore.mindrecord as ms_mindrecord schema { text: {type: string}, domain: {type: string}, quality_score: {type: float32} } writer ms_mindrecord.FileWriter(train.mindrecord, shard_num16, overwriteTrue) writer.add_schema(schema, pretrain_data) for item in filtered_data: writer.write_raw_data([{ text: item[text], domain: item[domain], quality_score: item[score] }]) writer.commit()shard_num建议设成和训练卡数匹配或者成倍数关系这样每张卡读一个分片IO 不打架。4.2 分布式过滤任务的切分与合并TB 级数据的过滤不可能单机跑。我的做法是把原始数据按文件切分每个分片独立跑过滤流水线最后合并结果。切分粒度要适中太粗了单任务跑太久太细了任务调度开销大。一般每个分片控制在 1-2 GB 比较合适。分布式执行可以用 MindSpore 的mindspore.dataset.distributed相关接口也可以直接用多进程。如果团队有 Spark 环境用 Spark 做规则过滤、用 MindSpore 做模型打分各取所长也是常见组合。合并阶段要注意去重的全局性。如果每个分片各自去重跨分片的重复数据抓不出来。解决办法是分两轮第一轮各分片内部去重第二轮把所有分片的哈希值汇总做全局去重。第二轮只处理哈希数据量小很多单机也能扛。4.3 过滤吞吐的优化经验过滤流水线的吞吐直接决定数据准备周期。我总结几个实测有效的优化点批处理代替逐条处理。模型打分环节尤其明显逐条推理 GPU 利用率极低改成 batch 推理吞吐能翻好几倍。MindSpore 的Dataset.batch()配合model.predict()就能实现。规则过滤用向量化操作。能用 numpy 或者 MindSpore 张量算的别用 Python 循环。比如字符重复率用 numpy 的unique比 Python 的set快得多。IO 和计算重叠。读数据的同时跑过滤别等全部读完再开始算。MindSpore 的Dataset有prefetch和num_parallel_workers参数调好了能显著提升吞吐。中间结果落盘用列式格式。Parquet 比 JSONL 读写都快而且压缩率高。过滤中间结果建议用 Parquet 存最后再转 MindRecord。5. 常见问题与排查技巧实录5.1 过滤后数据量骤降怎么办这是最常见的问题。跑完过滤一看数据只剩原来的 20%第一反应肯定是“是不是规则写错了”。排查思路是逐层统计拦截量。给每一层过滤加计数器看数据是在哪一层大量流失的。如果第一层就掉了一半那多半是格式规范化出了问题比如编码转换失败被误删。如果第二层掉得多就抽检被拦截的样本看规则是不是太激进。我遇到过一次长度阈值设了 50 字结果某批数据是短问答对平均长度就 30 字全被拦了。后来把阈值降到 15 字并针对问答类数据单独放宽数据量就回来了。经验任何过滤规则上线前先在小批量上跑人工抽检被拦截的样本。这个习惯能帮你避免大部分误杀。5.2 模型打分环节速度太慢模型打分是整条流水线最慢的一环。如果发现打分速度跟不上先检查是不是 batch size 太小。很多人写推理代码习惯一条一条来GPU 利用率可能只有 10%。改成 batch 推理batch size 设到显存允许的上限速度能提升一个数量级。如果 batch 调大了还是慢考虑换更小的判别模型。质量判别这个任务BERT-tiny 或者蒸馏后的小模型往往就够用了没必要上大模型。精度掉一两个点速度翻几倍这笔账划算。再不行就上 Ascend 做推理加速把判别器导出成 MindIR用mindspore_lite或者 MindSpore 的 Ascend 后端跑吞吐还能再上一个台阶。5.3 去重后仍有大量相似内容精确去重只能抓完全一样的文本改几个字的抓不出来。这时候要上近似去重。MinHash 的 shingle 大小和签名长度是两个关键参数shingle 太小相似判断太敏感shingle 太大又抓不出近似重复。中文文本我一般用 3-gram 做 shingle签名长度 128 位实测效果比较均衡。还有一个容易忽视的点去重要在过滤之后做。如果先去重再过滤那些被过滤掉的垃圾数据也参与了去重计算浪费算力。而且垃圾数据往往重复率高先去重会把它们合并成一条反而让过滤规则不好判断。5.4 分布式任务某张卡跑得特别慢分布式过滤时偶尔会遇到某张卡或者某个节点明显拖后腿。这种情况八成是数据分片不均衡。有的分片数据量大有的小跑得快的等跑得慢的整体效率被拉低。解决办法是分片时做负载均衡按数据量而不是按文件数来切。如果数据已经切好了可以在任务调度时动态分配谁跑完谁领新分片而不是静态绑定。另一个可能是慢节点本身有问题。检查一下那张卡的 IO 或者网络Ascend 卡之间的通信如果走的是慢链路也会拖慢整体。这个属于硬件运维范畴但排查时不能忽略。下面这张表是我整理的常见问题速查现象可能原因排查方向数据量骤降规则过严或编码问题逐层统计拦截量抽检被拦样本打分速度慢batch 太小或模型太大调大 batch换小模型去重不彻底只做了精确去重加 MinHash 近似去重某卡拖后腿分片不均或硬件问题检查分片负载和节点状态过滤后模型效果差误杀多样性数据放宽规则人工抽检6. 一些踩坑之后的个人体会数据质量过滤这件事技术难度不算高但工程细节和判断经验特别重要。同样的规则参数差一点结果可能天差地别。我最大的体会是不要追求一步到位。过滤方案是迭代出来的先跑一版粗过滤训个小模型看看效果再根据模型表现反推数据哪里有问题回头调过滤规则。这个闭环跑几轮方案就成熟了。还有就是保留原始数据和中间结果。过滤是个不可逆操作一旦删了就找不回来。我习惯把每一层的输入输出都落盘出问题能回溯。存储成本相比重跑一遍过滤的时间成本不值一提。最后分享一个小技巧给过滤流水线加采样开关。全量跑之前先随机采样 1% 的数据跑一遍看看各层拦截比例和最终产出确认没问题再全量。这个习惯帮我省了无数次重跑的时间。