Data-Juicer RayEmptyFormatter 算子深度解析:为 Ray 分布式数据处理链路创建空数据集的完整指南

原创2026-10-03 23:32:49950 阅读
文章标签:人工智能大模型数据工程数据清洗数据增强数据质检

Data-Juicer RayEmptyFormatter 算子深度解析:为 Ray 分布式数据处理链路创建空数据集的完整指南

导读

RayEmptyFormatter 是 Data-Juicer 中专门用于 Ray 执行环境的 formatter(格式化)算子,核心能力是根据指定长度与字段列表,在内存中直接生成一张"全空"的 Ray Dataset,无需任何磁盘文件或外部数据源。它在算子流水线调试、空数据集校验、基于 --generated_dataset_config 的合成数据生成等场景中非常实用。读完本文,你将掌握 RayEmptyFormatter 的参数语义、底层实现原理、与原生 EmptyFormatter 的关键差异,以及如何在 Data-Juicer 配置中把它接入数据集加载链路。

RayEmptyFormatter 是什么

在 Data-Juicer 中,formatter 是数据管线的"入口"组件,负责把各种来源的数据(本地文件、远程仓库、内存对象)统一加载为后续算子可以处理的 Dataset。绝大多数 formatter(如 JsonFormatter、ParquetFormatter、CsvFormatter)都依赖真实文件路径,而 RayEmptyFormatter 是一个特例:它不读取任何文件,而是按用户指定的行数与列名,构造一个元素全为空({})的 Ray 数据集。

该算子的算子类型为 formatter,标签为 cpu,其官方说明为:

The class is used to create empty data for ray.(该类用于为 Ray 创建空数据。)

在 Ray 执行模式下,Data-Juicer 的数据集对象是 ray.data.Dataset,这与默认 Hugging Face datasets 生态的 Dataset 不同。因此仓库在 data_juicer/format/empty_formatter.py 中同时提供了两个对称实现:

  • EmptyFormatter:面向普通(非 Ray)执行环境,返回 Hugging Face Dataset;
  • RayEmptyFormatter:面向 Ray 执行环境,返回 ray.data.Dataset。

两者的构造参数完全一致,仅底层数据载体不同,这也正是关联文档标题中 "ray_" 前缀的含义。

参数配置详解

依据关联文档的参数配置表,并结合源码签名(def __init__(self, length, feature_keys: List[str] = [], *args, **kwargs)),参数说明如下:

name 参数名 type 类型 default 默认值 desc 说明
length int 必填(文档默认值列为 '',实际源码中为必传位置参数) The empty dataset length,即要生成的空数据集行数
feature_keys typing.List[str] [] feature key name list,即空数据集的列名列表
args - '' 透传给 load_dataset 的额外位置参数
kwargs - '' 透传给 load_dataset 的额外关键字参数

需要特别说明两个参数的实现细节(见 empty_formatter.py):

  • length:决定生成数据集的记录条数,相当于"要造多少行空样本";
  • feature_keys:决定生成的列。源码中有一个友好的容错逻辑——当传入的是单个字符串而非列表时,会自动转换为单元素列表:
    if isinstance(self.feature_keys, str):
        self.feature_keys = [self.feature_keys]
    
    这意味着 feature_keys="text" 与 feature_keys=["text"] 等价。

底层实现原理:从参数到 Ray Dataset

空值语义:{} 而非 None

RayEmptyFormatter 通过 null_value 属性定义"空值"的形态(empty_formatter.py):

@property
def null_value(self):
    return {}

返回的是空字典 {}。这与非 Ray 版本 EmptyFormatter 的 null_value(返回 None)形成鲜明对比。原因在于 Ray 数据管道对样本结构的约束:每一行样本应是一个 dict 样式的记录,用空字典占位可以让后续算子以 sample<a href="https://link.gitcode.com/i/be354d5d459eee51220851992fd00b10" target="_blank">key] 的方式安全访问与覆写字段,而 None 值样本在部分算子中会被直接过滤(可参考 [formatter.py 中 non_empty_text 对 None 文本的过滤逻辑)。

load_dataset 的完整实现

核心加载逻辑位于 empty_formatter.py:

def load_dataset(self, *args, **kwargs):
    if len(self.feature_keys):
        df = pd.DataFrame({col: [self.null_value for _ in range(self.length)] for col in self.feature_keys})
    else:
        df = pd.DataFrame([self.null_value for _ in range(self.length)])

    empty_dataset = ray.data.from_pandas(df)

    return empty_dataset

执行流程可分为两步:

  1. 构造 pandas DataFrame:
    • 当指定了 feature_keys(如 ["text", "meta", "label"])时,为每个列名生成 length 行、每行值均为 {} 的列,形成一个"有结构无内容"的表格骨架;
    • 当 feature_keys 为空列表时,则生成 length 行、每行都是空字典 {} 的无列 DataFrame。
  2. 转换为 Ray Dataset:通过 ray.data.from_pandas(df) 把内存中的 DataFrame 包装为分布式 Ray 数据集,交给后续 Ray 执行器处理。

从实现可以看到,args/kwargs 在该类中并未参与逻辑运算,仅作为与 BaseFormatter.load_dataset 接口签名保持一致的透传参数(基类定义见 formatter.py)。

如何配置使用:--generated_dataset_config 入口

虽然 Data-Juicer 常规加载流程通过文件后缀自动匹配 formatter(见 load.py 中 load_formatter 的 SUFFIXES 匹配逻辑),但 RayEmptyFormatter 与 EmptyFormatter 的 SUFFIXES 均为空列表 [],无法靠后缀自动选中。它们的正确接入方式是使用全局配置中的 --generated_dataset_config 参数。

该参数在 data_juicer/config/config.py 中定义:

--generated_dataset_config: Configuration for generating a dataset.
The type field selects a registered formatter; other fields are passed to its constructor.

即:type 字段用于选择已注册的 formatter 名称,其余字段会作为构造参数传给该 formatter。由此可以写出如下配置(YAML 风格):

generated_dataset_config:
  type: ray_empty_formatter   # 选择注册名为 ray_empty_formatter 的算子
  length: 100                 # 生成 100 行空数据
  feature_keys:
    - text
    - meta

等价地,也可以在命令行直接传入 JSON 形式:

python tools/process_data.py \
  --generated_dataset_config '{"type": "ray_empty_formatter", "length": 100, "feature_keys": ["text", "meta"]}'

Data-Juicer 的数据集加载优先级为:generated_dataset_config > dataset_path > dataset(见 dataset_builder.py),因此只要配置了 generated_dataset_config,就不需要再提供任何真实数据文件路径。此外,type 字段使用的是注册名,两个 formatter 在 data_juicer/format/init.py 中通过 FORMATTERS.register_module() 注册(注册名即类名的小写形式:empty_formatter 与 ray_empty_formatter)。

与非 Ray 版 EmptyFormatter 的差异对照

对比维度 EmptyFormatter RayEmptyFormatter
返回类型 Hugging Face Dataset(包装为 NestedDataset) ray.data.Dataset
空值形态 None {}(空字典)
构造方式 Dataset.from_dict(data_dict, features=Features()),列类型固定为 Value("string") pd.DataFrame(...) + ray.data.from_pandas(df)
适用环境 单机/普通执行器 Ray 分布式执行器

一个值得注意的差异是类型系统:非 Ray 版为每个列显式声明了 Value("string") 特征,而 Ray 版依赖 pandas DataFrame 的列推断,不做显式 schema 声明。因此从数据结构层面看,Ray 版更像"宽松的占位骨架",适合流水线开发阶段用来验证算子链路是否能够跑通。

测试用例与行为验证

仓库对非 Ray 版 EmptyFormatter 提供了较为完整的单元测试(tests/format/test_empty_formatter.py),这些用例的行为约定同样适用于理解 Ray 版的语义(两版构造参数与空值策略同源):

  • 指定长度与单列:EmptyFormatter(length=10, feature_keys=["text"]) 生成 10 行、仅含 text 列、每行值为 None 的数据集,且可继续执行 map 与 filter 操作;
  • 多列:feature_keys=["text", "meta", "label"] 会为每个 key 各建一列,样本中所有 key 均为空值;
  • 字符串自动转列表:feature_keys="text" 会被包装为 ["text"];
  • 零长度:length=0 生成 0 行但保留列 schema;
  • 无列场景:feature_keys=[] 时生成 0 行数据集(因为没有任何列可供构造行)。

这些测试覆盖了参数容错、边界长度与空字段等典型场景,可作为把 RayEmptyFormatter 接入 Ray 执行器时的行为参考基线。目前仓库测试集中尚无 RayEmptyFormatter 的独立测试用例,其正确性验证主要依赖 data_juicer/format/init.py 中的注册导出以及 Ray 执行链路的集成运行。

使用场景与注意事项

典型场景

  • 算子链路冒烟测试:在 Ray 模式下,先用 ray_empty_formatter 生成若干行空数据,快速验证 filter/mapper/selector 各环节是否可执行,避免在调试阶段反复准备真实数据集;
  • 结构占位:当流水线中需要"先有 schema、后填充内容"(例如配合 python_lambda_mapper、replace_content_mapper 等就地覆写字段的算子)时,空字典占位可以确保字段访问不报错;
  • 配置化的合成数据入口:通过 generated_dataset_config 与 HPO、流水线实验工具组合,批量生成不同长度/列结构的空数据集用于基准测试。

注意事项

  • length 与 feature_keys 是必传参数(args/kwargs 仅透传),配置时不要省略;
  • 生成的每行值是 {} 而非 None,若下游算子按 None 语义判空,需要适配这一差异;
  • 该算子不产生任何磁盘 IO,因此它不会命中按文件后缀匹配 formatter 的自动加载流程,必须显式通过 generated_dataset_config 指定 type: ray_empty_formatter;
  • 该算子只负责"造数据",与数据校验、算子执行完全解耦,实际效果需结合 Ray 执行器(如 ray_executor)整体验证。

相关链接

登录后查看全文
data-juicer