🧑‍⚖️ 使用 distilabel “用陪审团取代法官”

社区文章 发布于 2024 年 5 月 3 日

TL;DR

distilabel 是一个用于构建合成数据生成和 AI 反馈 (AIF) 流水线的框架,它使用 LLM 作为有向无环图 (DAG),并附带不断增长的预定义任务集合。“用陪审团取代法官:用多样化模型小组评估 LLM 生成” 是 Cohere 最近发表的一篇论文,探讨了使用单个大型 LLM 作为生成判断器的问题,并提出了使用由更多更小的 LLM 组成的 LLM 评估器小组 (PoLL)(即所谓的陪审团)的建议,从而实现更具多样性、更少内部偏差且成本更低的生成判断。

引言

“用陪审团取代法官:用多样化模型小组评估 LLM 生成” 是 Cohere (Path Verga et al.) 发表的一篇论文,探讨了使用像 OpenAI 的 GPT-4 这样的大型模型来判断/评分单个 LLM 生成或多个 LLM 生成之间的比较的问题,因为他们声称这会引入模型内部偏差,并且大多数情况下使用如此大的模型通常是不必要的。因此,他们提出了所谓的 LLM 评估器小组 (PoLL),即所谓的“陪审团”,它是由更多更小的 LLM 组成的池,用于判断/评分 LLM 输出,然后使用这些评分的聚合或平均池,而不是由大型 LLM(即所谓的“法官”)提供的单个评分。

使用提议的 PoLL 不仅成本更低,而且由于其由不相交的模型家族组成,还可以避免某种程度上的模型内部偏差;论文中提到的模型是 Anthropic 的 Claude Haiku、OpenAI 的 GPT-3.5 和 Cohere 的 Command R Plus;后者是开源的,其余的是商业/专有模型。

这篇文章的目的是复现一个类似的流水线,其中一些 LLM(Gemma 1.1 7B Instruct、Llama 3 8B Instruct、Phi 3 Mini (4K) Instruct 和 Mistral 7B v0.2 Instruct;所有这些都是开源的,并在 Hugging Face Hub 中可用)用于为给定的一组指令/提示生成补全,然后让其他 LLM(Claude Haiku、GTP-3.5 和 Command R Plus)使用 UltraFeedback 提示对其进行判断,最后聚合评分,计算每个生成的平均评分,并使用该评分将数据集二元化为基于 PoLL 评分而不是像 UltraFeedback 中那样仅仅基于 GPT-4 评分的偏好数据集。

什么是 distilabel?

distilabel 是一个用于构建合成数据生成和 AI 反馈 (AIF) 流水线的框架,它将一系列步骤定义并连接为有向无环图 (DAG),以便轻松地将数据处理步骤与运行 LLM 的步骤结合起来,以完成各种任务,例如文本生成、偏好评分等。

本文档假设使用 distilabel v1.0.0 实现,因为以前的版本仍处于实验阶段。

distilabel 的基本概念如下:

  • 步骤:一个步骤是接收批处理数据作为输入并产生或更改接收到的数据作为输出的过程,它是最基本的步骤。
  • 生成器步骤:一个只生成数据(即不接收任何输入)的 Step
  • 全局步骤:一个接收输入并像默认 Step 一样产生输出的 Step,但它是全局的,这意味着它是阻塞的,并且在处理完前一个步骤的所有批次之前不会执行。
  • 任务:任务是一种特殊类型的 Step,其中包含一个强制参数 LLM,它将处理数据,以便在调用时,输入数据将被准备并流式传输到 LLM 作为输入,然后由 LLM 生成的输出将根据任务进行处理和格式化。
  • 流水线:流水线是主类,它协调作为 Pipeline 一部分的定义的所有步骤的执行,并将处理数据批处理以及验证、日志记录和任何其他相关逻辑。

有关 distilabel 的更多详细信息,建议您查阅 distilabel - Documentation,特别是“学习”部分。

安装

您可以使用 pip 进行安装,如下所示,它还将安装 anthropichf-inference-endpointsopenai 附加组件,这些组件分别是 Anthropic、推理端点和 OpenAI 集成所必需的。

distilabel 将从 develop 分支安装,因为它包含本文中用到的一些功能,但一旦 v1.1.0 发布,您可以随意将其固定到该版本。请参阅 GitHub Milestone:https://github.com/argilla-io/distilabel/milestone/8

pip install "distilabel[anthropic,hf-inference-endpoints,openai] @ git+https://github.com/argilla-io/distilabel.git@develop"

此外,您需要设置以下环境变量才能运行下面的 Pipeline

  • ANTHROPIC_API_KEY:是 Anthropic API 密钥,通过其 API 向 Anthropic 模型发送请求所需。
  • HF_TOKEN:是 Hugging Face 认证令牌,使用推理端点并将生成的 distilabel.Distiset 推送到 Hugging Face Hub 所必需。
  • OPENAI_API_KEY:是 OpenAI API 密钥,通过其 API 向 OpenAI 模型发送请求所需。

代码

构建块

  • LoadHubDataset:是一个 GeneratorStep,它将从 Hugging Face Hub 加载数据集,并将该数据集以批处理方式流式传输到后续步骤。在这种情况下,由于我们使用的数据集是 HuggingFaceH4/instruction-dataset,我们需要将列 prompt 重命名为 instruction,因为这是 TextGeneration 任务所需的输入。

  • TextGeneration:这是一个 Task,它将为给定作为输入的 instruction 生成一个助手响应,并在输出中生成 generation 列。TextGeneration 任务需要一个 LLM 作为参数,在这种情况下,我们将使用

  • CombineColumns:由于连接到此步骤的 TextGeneration 任务并行运行,输出未合并,因此此步骤将接收此作为输入(即所有先前步骤的所有输出),并将它们合并到一个列表中。这样,对于每个 generation,我们将有一个名为 generations 的列表,其中包含接收到的输入的每个 generation 值。这对于为下一步 UltraFeedback 准备数据也很有用,因为它需要一个 instruction 和一个 generations 列表作为输入。

  • UltraFeedback:这是一个 Task,它实现了 UltraFeedback 提示和后处理,以便使用 GPT-4 判断给定指令的生成列表,但在这种情况下,我们将根据引言中提到的内容,将其与较小的 LLM 一起使用,因为这正是论文所讨论的。在这种情况下,我们将使用以下 LLM 实现

    • InferenceEndpointsLLM:如上所述,在这种情况下,它将运行 CohereForAI/c4ai-command-r-plus,而不是论文中提到的 CohereForAI/c4ai-command-r,这仅仅是因为 Command R+ 在 Hugging Face Hub 中有可用的无服务器端点。
    • AnthropicLLM:是 Anthropic 模型的 LLM 实现,在这种情况下,我们将使用 Claude 3 Haiku,这是他们最快、最紧凑的模型,旨在实现近乎即时的响应能力和模仿人类交互的无缝 AI 体验。尽管对于当前的 UltraFeedback 提示,它尚未被证明如此强大,但将在后面详细说明。
    • OpenAILLM:是 OpenAI 模型通过其客户端的 LLM 实现,可以扩展到更多符合 OpenAI 客户端的 API;尽管在这种情况下,我们将把它用于 GPT-3.5 (gpt-3.5-turbo-0125)。
  • CombineColumns:如上所述,此步骤需要来自多个步骤的输入,并将提供的列合并到另一个列名下的列表中,在这种情况下,我们将对 ratingsrationalesmodel_name 进行分组以计算评分的平均值,而将其余的留作参考。

  • AvgPooling:这是一个通过 step 装饰器自定义的 Step,它将期望 poll_ratings 作为输入,并将计算这些评分的平均值,并将每个生成的平均值放入一个与生成长度(本例中为四个)匹配的列表中。它还展示了通过 step 装饰器使用 distilabel 创建自定义 Step 实现是多么容易。

实现

from distilabel.llms import InferenceEndpointsLLM, OpenAILLM
from distilabel.pipeline import Pipeline
from distilabel.steps import (
    CombineColumns,
    KeepColumns,
    LoadHubDataset,
    StepInput,
    step,
)
from distilabel.steps.formatting import FormatTextGenerationDPO
from distilabel.steps.tasks import TextGeneration, UltraFeedback
from distilabel.steps.typing import StepOutput


@step(inputs=["poll_ratings"], outputs=["avg_poll_ratings"])
def AveragePooling(*inputs: StepInput) -> StepOutput:
    """Custom `Step` that calculates the average of the ratings for each generation."""
    for input in inputs:
        for item in input:
            item["avg_poll_ratings"] = [
                sum(col) / len(col) for col in zip(*item["poll_ratings"])
            ]
        yield input


if __name__ == "__main__":
    # We use `Pipeline` context manager to ensure all the steps defined inside
    # are included as part of the `pipeline`
    with Pipeline(name="replacing-judges-with-juries") as pipeline:
        # First we load the dataset from the Hugging Face Hub, but for local testing
        # one could just define a dataset a list of dicts and provide that to `LoadDataFromDicts`
        load_dataset = LoadHubDataset(
            name="load_dataset",
            repo_id="HuggingFaceH4/instruction-dataset",
            split="test",
            num_examples=10,
            output_mappings={"prompt": "instruction"},
        )

        # We create a `TextGeneration` task running Llama 3 on serverless endpoints
        text_generation_llama3 = TextGeneration(
            name="text_generation_llama3",
            llm=InferenceEndpointsLLM(
                model_id="meta-llama/Meta-Llama-3-8B-Instruct",
                tokenizer_id="meta-llama/Meta-Llama-3-8B-Instruct",
            ),
            input_batch_size=10,
            output_mappings={"model_name": "generation_model"},
        )
        # We create a `TextGeneration` task running Gemma 1.1 on serverless endpoints
        text_generation_gemma = TextGeneration(
            name="text_generation_gemma",
            llm=InferenceEndpointsLLM(
                model_id="google/gemma-1.1-7b-it",
            ),
            input_batch_size=10,
            output_mappings={"model_name": "generation_model"},
        )
        # We create a `TextGeneration` task running Phi 3 on serverless endpoints
        text_generation_phi3 = TextGeneration(
            name="text_generation_phi3",
            llm=InferenceEndpointsLLM(
                model_id="microsoft/Phi-3-mini-4k-instruct",
            ),
            input_batch_size=10,
            output_mappings={"model_name": "generation_model"},
        )
        # We create a `TextGeneration` task running Mistral v0.2 on serverless endpoints
        text_generation_mistral = TextGeneration(
            name="text_generation_mistral",
            llm=InferenceEndpointsLLM(
                model_id="mistralai/Mistral-7B-Instruct-v0.2",
            ),
            input_batch_size=10,
            output_mappings={"model_name": "generation_model"},
        )

        # Combine the `generation` and `generation_model` columns from the previous step
        # under a single column name as a list
        combine_generation_columns = CombineColumns(
            name="combine_generation_columns",
            columns=["generation", "generation_model"],
            output_columns=["generations", "generation_models"],
        )

        # We create the UltraFeedback task with the `instruction-following` aspect to evaluate
        # the LLM capabilities on following instructions, running Command R+ on serverless
        # endpoints and GPT-3.5 from OpenAI
        ultrafeedback_cmdr_plus = UltraFeedback(
            name="ultrafeedback_cmdr_plus",
            llm=InferenceEndpointsLLM(
                model_id="CohereForAI/c4ai-command-r-plus",
            ),
            input_batch_size=5,
            aspect="instruction-following",
        )
        ultrafeedback_gpt35 = UltraFeedback(
            name="ultrafeedback_gpt35",
            llm=OpenAILLM(
                model="gpt-3.5-turbo-0125",
            ),
            input_batch_size=5,
            aspect="instruction-following",
        )

        # Then we combine again the generated `ratings` and `rationales` into a single column
        combine_ultrafeedback_columns = CombineColumns(
            name="combine_ultrafeedback_columns",
            columns=["ratings", "rationales", "model_name"],
            output_columns=["poll_ratings", "poll_rationales", "poll_models"],
        )

        # Finally, we call our custom task to calculate the average of the ratings for each generation
        avg_pooling = AveragePooling(name="avg_pooling", input_batch_size=1)

        # Here we define the orchestration of the steps using the `rshift` operator showing how the
        # different steps are connected to each other in the `Pipeline`
        (
          load_dataset
          >> [text_generation_llama3, text_generation_gemma, text_generation_phi3, text_generation_mistral]
          >> combine_generation_columns
          >> [ultrafeedback_cmdr_plus, ultrafeedback_gpt35]
          >> combine_ultrafeedback_columns
          >> avg_pooling
        )

最后,一旦定义了 Pipeline,您可以如下运行它,定义一些运行时参数,主要控制 LLM 的生成。

distiset = pipeline.run(
    parameters={
        "text_generation_llama3": {
            "llm": {
                "generation_kwargs": {
                    "temperature": 0.7,
                    "max_new_tokens": 1024,
                    "stop_sequences": ["<|eot_id|>", "<|end_of_text|>"],
                },
            },
        },
        "text_generation_gemma": {
            "llm": {
                "generation_kwargs": {
                    "temperature": 0.7,
                    "max_new_tokens": 1024,
                    "stop_sequences": ["<eos>", "<end_of_turn>"],
                },
            },
        },
        "text_generation_phi3": {
            "llm": {
                "generation_kwargs": {
                    "temperature": 0.7,
                    "max_new_tokens": 1024,
                    "stop_sequences": ["</s>", "<|endoftext|>"],
                },
            },
        },
        "text_generation_mistral": {
            "llm": {
                "generation_kwargs": {
                    "temperature": 0.7,
                    "max_new_tokens": 1024,
                    "stop_sequences": ["</s>"],
                },
            },
        },
        # "ultrafeedback_haiku": {
        #     "llm": {"generation_kwargs": {"temperature": 0.7, "max_tokens": 4096}},
        # },
        "ultrafeedback_cmdr_plus": {
            "llm": {
                "generation_kwargs": {
                    "temperature": 0.7,
                    "max_new_tokens": 4096,
                    "stop_sequences": ["<EOS_TOKEN>", "<|END_OF_TURN_TOKEN|>"],
                },
            },
        },
        "ultrafeedback_gpt35": {
            "llm": {
                "generation_kwargs": {"temperature": 0.7, "max_new_tokens": 4096}
            },
        },
    }
)

最后,我们可以选择性地通过 push_to_hub 方法将生成的数据集,即 distilabel.Distiset,推送到 Hugging Face Hub,这样叶子步骤中生成的每个子集都将被推送到 Hub,在本例中,由于只有一个叶子步骤,因此只推送该步骤;但如果有多个,则每个叶子步骤都将在 Hub 中以不同的配置进行推送。

distiset.push_to_hub("replacing-judges-with-juries-distilabel")

🤗 数据集可在 alvarobartt/replacing-judges-with-juries-distilabel 获取

image/png

注意事项(截至 2024 年 5 月 3 日)

请注意,您可以将下面使用的 LLM 替换为您选择的 LLM,使用这些 LLM 的原因是用于 TextGeneration 任务的 LLM 在 Hugging Face Hub 中作为无服务器端点提供,而用于 UltraFeedback 的 LLM 与官方论文中使用的 LLM 相同。

为了广泛使用部署在 Hugging Face Hub 中的无服务器推理端点,建议订阅专业版(参见定价),因为这将启用专业版推理,您将获得更高的免费推理 API 使用速率限制。

我在将 Claude Haiku 与 UltraFeedback 提示一起使用时遇到了问题,因为它似乎无法生成符合预期格式的内容,但我将对此进行调查;与此同时,运行 Claude Haiku 与 UltraFeedback 的代码已被注释掉。这可以通过简单地忽略 rating=None 的值来解决,但在进行进一步调查之前,我觉得最好暂时将其搁置一边。

接下来呢?

目前,我们正在积极开发 distilabel v1.1.0,力求使其尽可能方便开发者使用,鼓励社区中的每个人都使用 distilabel 进行构建,并旨在弥合开放模型和消费级硬件上合成数据生成之间的差距。

参考文献

社区

注册登录 以评论