作者: 步子哥

  • 🔍 追踪的艺术:揭开 MLflow Tracing 的神秘面纱

    在机器学习和人工智能的世界中,模型的训练、部署和优化早已成为日常。然而,当面对复杂的生成式 AI(GenAI)应用时,如何深入了解模型的执行过程、优化性能、调试问题,甚至是解锁隐藏的潜力?这正是 MLflow 最新发布的功能——MLflow Tracing,为我们带来的革命性工具。

    今天,我们将深入探讨 MLflow Tracing 的核心功能、应用场景以及它如何帮助开发者在复杂的 AI 应用中如虎添翼。准备好了吗?让我们开始这场追踪之旅吧!


    🚀 MLflow Tracing 的诞生:从愿景到现实

    在 2024 年 6 月 10 日,MLflow 团队正式发布了 MLflow Tracing,这是一个专为生成式 AI 应用设计的全新功能。无论是简单的聊天机器人,还是复杂的多阶段检索增强生成(Retrieval Augmented Generation, RAG)应用,MLflow Tracing 都能为开发者提供全面的执行追踪能力。

    但为什么追踪如此重要?在复杂的 AI 系统中,模型的行为常常如同黑箱,难以预测和理解。MLflow Tracing 的目标是打开这个黑箱,为开发者提供可视化的执行轨迹、详细的性能数据以及优化的方向。

    值得注意的是,这项功能仅在 MLflow 2.14.0 及更高版本中可用。如果你还在使用旧版本,是时候升级了!


    🧩 什么是 MLflow Tracing?

    MLflow Tracing 是一种全新的追踪工具,它通过多种方式为开发者提供灵活的追踪能力。无论你是希望快速上手,还是需要对追踪过程进行精细控制,MLflow Tracing 都能满足你的需求。

    🌟 三种追踪方式

    1. 自动化追踪:与 LangChain 的无缝集成
      通过调用 mlflow.langchain.autolog(),你可以轻松启用自动追踪功能。此功能特别适合那些使用 LangChain 构建生成式 AI 应用的开发者。
    2. 手动追踪:高层次的 Fluent API
      通过装饰器、函数包装器和上下文管理器,开发者可以在不大幅修改代码的情况下,手动添加追踪功能。这种方法提供了更大的灵活性。
    3. 低层次追踪:客户端 API 的精细控制
      对于高级用户,MLflow 提供了线程安全的客户端 API,允许开发者以编程方式管理追踪过程。这种方法适合需要高度定制的场景。

    🛠️ 如何开始使用 MLflow Tracing?

    🤖 LangChain 自动化追踪

    如果你是 LangChain 的用户,那么 MLflow Tracing 的自动化功能将是你的最佳选择。只需几行代码,你就可以将追踪功能集成到你的应用中:

    import os
    from langchain.prompts import PromptTemplate
    from langchain_openai import OpenAI
    import mlflow
    
    # 确保环境变量中包含 OpenAI API 密钥
    assert "OPENAI_API_KEY" in os.environ, "请设置 OPENAI_API_KEY 环境变量。"
    
    # 设置实验名称
    mlflow.set_experiment("LangChain Tracing")
    
    # 启用自动追踪
    mlflow.langchain.autolog(log_models=True, log_input_examples=True)
    
    # 定义生成式 AI 模型
    llm = OpenAI(temperature=0.7, max_tokens=1000)
    prompt_template = "Imagine you are {person}, and you are answering a question: {question}"
    chain = prompt_template | llm
    
    # 调用链条
    chain.invoke({"person": "Richard Feynman", "question": "Why should we colonize Mars?"})
    chain.invoke({"person": "Linus Torvalds", "question": "Can I set everyone's access to sudo?"})
    

    运行上述代码后,你可以在 MLflow UI 中查看详细的追踪数据,包括输入、输出和模型性能。


    🛠️ 手动追踪:Fluent API 的灵活性

    如果你需要更大的控制权,MLflow 的 Fluent API 提供了多种手动追踪方法。例如,通过装饰器捕获函数的输入和输出:

    import mlflow
    
    mlflow.set_experiment("Tracing Demo")
    
    @mlflow.trace
    def some_function(x, y, z=2):
        return x + (y - z)
    
    some_function(2, 4)
    

    或者使用上下文管理器,为追踪数据添加额外的信息:

    import mlflow
    
    @mlflow.trace
    def first_func(x, y=2):
        return x + y
    
    @mlflow.trace
    def second_func(a, b=3):
        return a * b
    
    def do_math(a, x, operation="add"):
        with mlflow.start_span(name="Math") as span:
            span.set_inputs({"a": a, "x": x})
            span.set_attributes({"mode": operation})
            first = first_func(x)
            second = second_func(a)
            result = first + second if operation == "add" else first - second
            span.set_outputs({"result": result})
            return result
    
    do_math(8, 3, "add")
    

    这种方法不仅灵活,还能帮助你捕获更详细的追踪数据。


    🧬 低层次追踪:客户端 API 的深度控制

    对于那些需要完全掌控追踪过程的开发者,MLflow 提供了低层次的客户端 API。通过这些 API,你可以手动创建、管理和结束追踪。例如:

    from mlflow import MlflowClient
    
    client = MlflowClient()
    
    # 开始一个新的追踪
    root_span = client.start_trace("my_trace")
    request_id = root_span.request_id
    
    # 创建子追踪
    child_span = client.start_span(
        name="child_span",
        request_id=request_id,
        parent_id=root_span.span_id,
        inputs={"input_key": "input_value"},
        attributes={"attribute_key": "attribute_value"},
    )
    
    # 结束子追踪
    client.end_span(
        request_id=child_span.request_id,
        span_id=child_span.span_id,
        outputs={"output_key": "output_value"},
        attributes={"custom_attribute": "value"},
    )
    
    # 结束根追踪
    client.end_trace(
        request_id=request_id,
        outputs={"final_output_key": "final_output_value"},
        attributes={"token_usage": "1174"},
    )
    

    这种方法虽然复杂,但为开发者提供了最大程度的灵活性。


    🌌 MLflow Tracing 的未来:无限可能

    MLflow Tracing 的推出标志着 MLflow 在机器学习工作流管理领域的又一次飞跃。从简单的函数追踪到复杂的异步工作流,这项功能为开发者提供了前所未有的工具和灵活性。

    未来,随着生成式 AI 的进一步发展,MLflow Tracing 有望成为开发者优化和调试 AI 应用的标配工具。无论你是初学者还是资深开发者,MLflow Tracing 都能为你的项目注入新的活力。


    📚 参考文献

    1. MLflow 官方博客:Introducing MLflow Tracing
    2. MLflow 文档:API Documentation
    3. LangChain 项目:LangChain 官方文档

    无论你是希望优化模型性能,还是深入了解生成式 AI 的执行细节,MLflow Tracing 都是你的得力助手。让我们一起拥抱这项新技术,开启 AI 应用的新篇章吧!

  • 🌟 突破极限的算法:深度解读 GRPO 的奥秘

    在人工智能的世界里,强化学习(Reinforcement Learning, RL)一直是推动模型性能提升的重要手段。尤其是在复杂任务如数学推理中,RL的作用尤为显著。然而,传统的强化学习方法如PPO(Proximal Policy Optimization)虽然强大,却在资源消耗和训练效率上存在一定的瓶颈。今天,我们将聚焦于一种全新的RL算法——Group Relative Policy Optimization(GRPO),它不仅优化了资源使用,还在奖励信号转化为模型参数调整的过程中展现了独特的创新。


    🚀 GRPO 是什么?

    GRPO 是 PPO 的一种变体,旨在通过更高效的方式优化语言模型的策略(policy)。与 PPO 的“演员-评论家”(actor-critic)架构不同,GRPO 摒弃了评论家模型,而是通过组内相对奖励来估计基线(baseline)。这种设计极大地减少了对内存和计算资源的需求,同时保持了高效的训练效果。

    GRPO 的核心思想是:通过对同一问题的多组输出进行比较,利用组内的相对奖励来指导模型参数的更新。 这种方法不仅降低了对复杂价值函数的依赖,还更贴合奖励模型的比较性本质。


    🧩 奖励信息如何转化为模型参数调整?

    GRPO 的关键步骤在于如何将奖励信号转化为模型参数的调整。我们从以下几个核心环节逐步解析这一过程:

    1️⃣ 采样阶段:生成多组输出

    对于每个输入问题 q,GRPO 从当前的策略模型 \pi_\theta​ 中采样 G 个不同的输出 \{o_1, o_2, \dots, o_G\}。这些输出代表了模型在当前策略下对同一问题的多种可能回答。

    这一步的目标是为后续的奖励比较提供多样化的样本,确保奖励信号的计算基于组内的相对表现


    2️⃣ 奖励计算:组内相对评分

    对于每个输出 o_i​,奖励模型 r_\phi​ 会为其生成一个奖励分数 r_i​。这些奖励分数随后被标准化为组内的相对奖励

        \[\hat{r}_i = \frac{r_i - \text{mean}(r)}{\text{std}(r)}\]

    其中,\text{mean}(r)\text{std}(r) 分别是组内奖励的均值和标准差。

    通过这种方式,GRPO 将奖励信号从绝对值转化为相对值,使得模型能够更关注组内表现的相对优劣,而非绝对的奖励高低。


    3️⃣ 优势估计:基于组内奖励的梯度系数

    在传统的 PPO 中,优势函数(Advantage Function)通常由评论家模型估计,用于衡量当前策略与基线策略的相对优劣。而在 GRPO 中,优势函数直接基于组内的相对奖励计算:

        \[\hat{A}_{i,t} = \hat{r}_i\]

    这里,\hat{A}_{i,t} 是输出 o_i 在第 t 个时间步的优势值。由于 GRPO 的奖励是基于组内比较的,因此无需额外的评论家模型来估计基线。


    4️⃣ 策略更新:基于梯度的优化

    GRPO 的目标是最大化以下优化目标

        \[J_\text{GRPO}(\theta) = \mathbb{E}_{q \sim P(Q), \{o_i\} \sim \pi_\theta} \left[ \frac{1}{G} \sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \min \left( \frac{\pi_\theta(o_{i,t} | q, o_{i,<t})}{\pi_\text{old}(o_{i,t} | q, o_{i,<t})} \hat{A}_{i,t}, \text{clip}(\cdot) \right) \right]\]

    其中,\text{clip}(\cdot) 是用于稳定训练的裁剪操作,防止策略更新过快导致模型崩溃。

    通过对上述目标函数求梯度,GRPO 更新策略模型的参数 \theta

        \[\nabla_\theta J_\text{GRPO}(\theta) = \mathbb{E}_{q, \{o_i\}} \left[ \frac{1}{G} \sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \hat{A}_{i,t} \nabla_\theta \log \pi_\theta(o_{i,t} | q, o_{i,<t}) \right]\]

    在这里,梯度的大小由优势值 \hat{A}_{i,t}​ 决定,而优势值又直接来源于组内的相对奖励。这种设计使得模型能够更有效地利用奖励信号,专注于提升相对表现较差的输出。


    5️⃣ 正则化:控制策略分布的变化

    为了防止策略模型偏离初始策略 \pi_\text{ref}​ 过远,GRPO 在目标函数中加入了 KL 散度正则化项:

        \[\text{KL}(\pi_\theta || \pi_\text{ref}) = \sum_{t=1}^{|o_i|} \left( \frac{\pi_\text{ref}(o_{i,t} | q, o_{i,<t})}{\pi_\theta(o_{i,t} | q, o_{i,<t})} - 1 \right)\]

    这一步确保了模型在优化过程中不会过度拟合奖励信号,保持策略的稳定性。


    🔍 GRPO 的优势何在?

    相比于传统的 PPO,GRPO 在以下几个方面展现了显著优势:

    1. 资源效率:通过摒弃评论家模型,GRPO 减少了对内存和计算资源的需求,适合大规模模型的训练。
    2. 奖励信号的充分利用:组内相对奖励的设计使得模型能够更敏感地捕捉组内表现的差异,从而更高效地优化策略。
    3. 训练稳定性:通过 KL 散度正则化和组内标准化,GRPO 在提升性能的同时保持了训练的稳定性。

    🌈 未来展望:更强大的 RL 方法

    GRPO 的成功为强化学习在语言模型中的应用提供了新的思路。然而,仍有许多潜在的改进方向,例如:

    • 奖励模型的泛化能力:如何让奖励模型更好地适应分布外的问题?
    • 更高效的采样策略:如基于树搜索的采样方法,可能进一步提升 GRPO 的性能。
    • 弱监督到强监督的学习:如何在奖励信号存在噪声的情况下,设计更鲁棒的强化学习算法?

    这些问题的解决将进一步推动 GRPO 和其他 RL 方法的发展,为人工智能的未来开辟更广阔的道路。


    GRPO 的出现无疑是强化学习领域的一次重要突破。它不仅为数学推理等复杂任务提供了更高效的解决方案,也为未来的算法设计树立了新的标杆。让我们拭目以待,见证 GRPO 和它的继任者们如何继续突破人工智能的极限!

  • 🌀 DeepSeek-R1-Zero:让机器学会自己打分的艺术

    在人工智能的世界里,如何让机器学会生成“好答案”,一直是一个令人头疼的问题。DeepSeek-R1-Zero 的出现,仿佛为这个难题打开了一扇窗。它采用了一种看似神奇的方式——强化学习(Reinforcement Learning, RL),让模型在没有明确“正确答案”的情况下,自己摸索出一套生成优质答案的策略。那么,这背后的秘密是什么?奖励模型又是如何扮演关键角色的?让我们一探究竟。


    🌟 从标签到奖励:AI 学习的两条路

    在机器学习的领域,有两种主要的学习方式:监督学习和强化学习。它们就像两种截然不同的教学方法。

    🎓 监督学习:老师手把手教

    想象你在学画画,老师会给你一幅参考图,然后不断指出你哪里画错了,直到你的作品与参考图几乎一模一样。这就是监督学习的本质:模型需要明确的“标签”或“正确答案”,通过对比自己的输出和标签来调整自己。

    比如,给定一个问题“1+1等于几?”,监督学习会告诉模型答案是“2”,并根据模型的回答与“2”的差距来计算损失,调整模型的参数。

    🧗‍♂️ 强化学习:自己摸索前进

    强化学习则完全不同。它更像是让你在没有地图的情况下爬山,唯一的指引是山顶的方向。你不知道每一步是否完全正确,但只要你离山顶更近了,就会得到奖励;反之,则没有奖励甚至会被“惩罚”。

    在强化学习中,模型通过与环境交互,尝试各种可能的策略,并根据环境反馈的“奖励信号”来调整自己的行为。这里没有明确的“正确答案”,只有一个模糊的目标:最大化长期的累积奖励。


    🏆 奖励模型:AI 的“裁判”

    在 DeepSeek-R1-Zero 中,奖励模型扮演了一个重要的角色——它是生成模型的“裁判”,负责评估答案的好坏。但问题来了,这个裁判是如何被训练出来的呢?毕竟,生成答案的优劣并不像数学题那样有明确的标准答案。

    🤝 对比学习:让裁判学会“更好”而非“正确”

    一种常见的方法是通过对比学习训练奖励模型。具体来说:

    1. 生成多组答案:模型会为同一个问题生成多个版本的答案,这些答案可能是从不同模型中获得的,或者是同一模型在不同参数下生成的。
    2. 人类标注者进行对比:标注者不需要指出哪一个答案是“正确”的,而只需根据自己的判断,指出哪一个答案“更好”。
    3. 训练奖励模型:奖励模型通过这些对比数据学会了一种偏好排序的能力,它可以为每个答案打分,分数越高表示答案越优。

    这种方法的好处是,它不需要为每个问题提供明确的标签,只需提供相对的优劣判断,大大降低了数据标注的难度。

    📊 无监督信号:让裁判自学成才

    在某些情况下,奖励模型甚至可以通过无监督的方法训练。例如:

    • 流畅性和语法正确性:利用语言模型的内置能力,评估答案是否符合语言的基本规则。
    • 逻辑一致性:检查答案是否前后矛盾,或者是否符合已知的逻辑推理规则。
    • 知识对齐:将答案与领域知识库进行比对,判断其是否符合已知的事实。

    这些方法不需要人工干预,而是利用现有的工具和规则,为奖励模型提供一种“自学”的能力。

    🔄 自洽性检测:答案的一致性是金标准

    另一种有趣的方法是利用模型自身的行为作为反馈信号。具体来说:

    1. 生成多个变体:模型会回答同一个问题的多个变体,比如“地球的年龄是多少?”和“地球形成于几年前?”。
    2. 检查一致性:如果模型的回答在不同问法下保持一致,就可以认为这些答案更可信。

    这种方法不需要人工标注,而是通过模型的自洽性来间接评估答案的质量。


    🚀 强化学习的魔力:探索与利用的平衡

    强化学习的核心在于“探索”和“利用”的平衡。模型需要不断尝试新的策略(探索),以发现更优的答案;同时,它也需要利用已有的经验(利用),以避免无谓的尝试。

    在 DeepSeek-R1-Zero 中,生成模型通过与奖励模型的交互,逐步优化自己的策略。即使奖励模型本身并不完美,只要它能提供相对一致的反馈,生成模型就能通过反复试验,逼近最优策略。


    🌌 DeepSeek-R1-Zero 的意义:从数据到智慧

    DeepSeek-R1-Zero 的方法有一个重要的优势:它避免了对大规模人工标注数据的依赖。在传统的监督学习中,训练一个高质量的生成模型往往需要大量的人工标注数据,这既昂贵又耗时。而通过强化学习和奖励模型,DeepSeek-R1-Zero 能够利用间接的信号优化生成策略,大大降低了数据需求。

    这种方法的意义不仅在于技术上的突破,更在于它为生成式 AI 的发展提供了一种新的思路:让机器学会自己评估自己的答案,从而实现真正的自主学习。


    📚 总结:AI 的自我进化之路

    DeepSeek-R1-Zero 的核心在于将奖励模型与强化学习相结合,为生成式 AI 的优化开辟了一条新的道路。通过奖励模型的反馈,生成模型能够在没有明确标签的情况下,逐步提升答案的质量。这种方法不仅高效,而且灵活,为未来的 AI 发展提供了无限可能。

    所以,下次当你看到一个 AI 给出令人惊叹的答案时,不妨想想背后的故事:也许,它正是通过像 DeepSeek-R1-Zero 这样的机制,一步步学会了如何成为一个更好的回答者。


    🔖 参考文献

    1. OpenAI, “Fine-Tuning Language Models with Reinforcement Learning from Human Feedback,” 2022.
    2. Sutton, R. S., & Barto, A. G., “Reinforcement Learning: An Introduction,” MIT Press, 2018.
    3. Zoph, B. , et al., “Learning Transferable Architectures for Scalable Image Recognition,” 2018.
    4. Silver, D. , et al., “Mastering the game of Go with deep neural networks and tree search,” Nature, 2016.
    5. Christiano, P. , et al., “Deep reinforcement learning from human preferences,” 2017.
  • 🌌 泰坦的崛起:一种全新记忆学习架构的探索

    “真正的记忆艺术是专注的艺术!”
    ——塞缪尔·约翰逊,1787

    🧠 记忆与学习:从人类大脑到机器模型的启发

    在漫长的科学探索中,记忆一直是人类学习和认知的核心。没有记忆,人类和动物将只能依靠简单的反射行为生存。而在机器学习领域,记忆的概念同样占据了重要地位。从早期的霍普菲尔德网络(Hopfield Networks)到长短期记忆网络(LSTMs),再到近年来的变压器(Transformers),研究者们不断试图模拟人脑的记忆系统。然而,这些架构在面对复杂任务时,仍然存在诸多限制:短期记忆的局限性、长序列数据的处理难题,以及对推理和泛化能力的不足。

    本文介绍了一种全新的神经记忆模块及其架构——Titans,它试图突破现有模型的限制,结合短期记忆与长期记忆的优势,打造更高效、更强大的深度学习系统。


    🔍 现有模型的挑战:短期记忆与长期记忆的权衡

    🌟 变压器的辉煌与瓶颈

    自 2017 年变压器(Transformers)问世以来,其注意力机制(Attention)凭借对序列数据的强大建模能力,迅速成为自然语言处理、视频理解和时间序列预测等领域的主流。然而,变压器的注意力计算复杂度为二次方(O(n^2)),这使得它在处理超长序列时面临内存和计算成本的瓶颈。

    变压器的注意力机制本质上是一种短期记忆:它通过查询(Query)、键(Key)和值(Value)矩阵,捕捉当前上下文窗口内的直接依赖关系。然而,这种短期记忆的特性使得它无法有效利用更长时间跨度的信息。

    🌀 线性变压器与递归模型的尝试

    为了应对变压器的扩展性问题,研究者提出了线性变压器(Linear Transformers)和现代递归神经网络(RNNs)的变体。这些模型通过内核函数或矩阵压缩技术,降低了计算复杂度。然而,这种压缩会导致信息丢失,尤其是在处理超长序列时,模型性能往往不如传统变压器。

    例如,线性变压器将历史数据压缩为固定大小的矩阵,而递归模型则将信息存储在向量中。这种压缩机制虽然提高了效率,却牺牲了长期记忆的表达能力。


    🛠️ Titans:一种全新的记忆学习架构

    🌌 记忆的双重角色

    Titans 的核心思想是将记忆分为两种互补的模块:

    1. 短期记忆(Short-term Memory):通过注意力机制捕捉当前上下文窗口内的依赖关系。
    2. 长期记忆(Long-term Memory):通过神经网络模块存储历史信息,并在测试时动态更新。

    这种设计灵感来源于人类大脑的记忆系统。心理学研究表明,人类记忆系统是一个由短期记忆、工作记忆和长期记忆组成的复杂网络,各模块既独立又相互关联。Titans 的设计正是试图模拟这种复杂的记忆交互。

    🧩 神经长期记忆模块

    Titans 的核心创新在于其神经长期记忆模块(Neural Long-term Memory)。这一模块不仅能够存储历史数据,还能在测试时动态学习和更新记忆。与传统递归模型不同,Titans 的长期记忆模块具备以下特点:

    1. 深度记忆(Deep Memory):采用多层神经网络(MLP)作为记忆存储结构,显著提高了记忆的表达能力。
    2. 惊奇度驱动的记忆更新:受心理学启发,Titans 使用输入数据的“惊奇度”来决定记忆更新的优先级。惊奇度通过模型对输入数据的梯度大小来衡量,梯度越大,表示输入与历史数据的差异越大,因而更值得记忆。
    3. 遗忘机制(Forgetting Mechanism):通过引入权重衰减(Weight Decay),实现对不重要信息的自适应遗忘,从而更好地管理有限的记忆容量。

    公式上,记忆的更新规则为:

        \[M_t = (1 - \alpha_t)M_{t-1} + S_t\]

    其中,S_t​ 包括过去的惊奇度(Past Surprise)和当前的惊奇度(Momentary Surprise),通过以下公式计算:

        \[S_t = \eta_t S_{t-1} - \theta_t \nabla \ell(M_{t-1}; x_t)\]

    这里,\eta_t​ 和 \theta_t​ 是数据驱动的参数,用于控制记忆的遗忘和更新强度。


    🏗️ Titans 的三种架构变体

    Titans 提供了三种不同的架构设计,以适应不同的任务需求:

    🧩 1. 记忆作为上下文(Memory as Context, MAC)

    在这种设计中,长期记忆模块的输出被直接作为当前上下文的一部分,供注意力机制使用。这种方法能够充分利用历史信息,同时允许注意力机制动态决定哪些信息是相关的。

    🔗 2. 记忆作为门控模块(Memory as Gating, MAG)

    在这种设计中,短期记忆和长期记忆通过门控机制(Gating)进行融合。短期记忆负责捕捉精确的上下文信息,而长期记忆则提供更广泛的背景支持。

    🏗️ 3. 记忆作为独立层(Memory as Layer, MAL)

    在这种设计中,长期记忆模块被作为一个独立的网络层,与注意力机制串联。这种方法更适合需要显式分离短期和长期记忆的任务。


    📊 实验验证:Titans 的性能如何?

    为了评估 Titans 的性能,研究者进行了广泛的实验,涵盖语言建模、常识推理、基因组学、时间序列预测等任务。

    📝 1. 语言建模与常识推理

    在 WikiText 和 LAMBADA 数据集上的实验表明,Titans 在困惑度(Perplexity)和准确率上均优于现有的变压器和线性递归模型。特别是,Titans 的 MAC 和 MAG 变体在处理长序列依赖时表现尤为出色。

    🧬 2. 基因组学建模

    在 DNA 序列建模任务中,Titans 的长期记忆模块展现了强大的泛化能力,在多个基准数据集上超过了 DNABERT 和 HyenaDNA 等先进模型。

    📈 3. 时间序列预测

    在 ETT 和 Traffic 数据集上的实验表明,Titans 的长期记忆模块不仅能够有效捕捉长时间跨度的信息,还能显著降低预测误差。

    🧵 4. 超长上下文任务

    在“针在大海捞”任务中,Titans 能够在超过 2M 的上下文窗口中准确定位目标信息,远超 GPT-4 和 Llama 等大模型。


    🔮 未来的可能性:Titans 的启示与展望

    Titans 的设计为深度学习模型的记忆管理提供了全新的思路。通过结合短期记忆和长期记忆的优势,Titans 不仅提升了模型的效率,还拓展了其在超长序列任务中的适用性。

    未来的研究可以进一步探索以下方向:

    1. 更深层次的记忆模块设计:结合图神经网络或其他非线性结构,提升记忆的表达能力。
    2. 跨领域应用:将 Titans 应用于视频分析、医疗诊断等需要处理长时间依赖的领域。
    3. 与外部记忆的结合:探索 Titans 与外部存储系统(如数据库)的集成,进一步扩展其记忆能力。

    📚 参考文献

    1. Vaswani, A. , et al. (2017). Attention is All You Need.
    2. Schmidhuber, J. , & Hochreiter, S. (1997). Long Short-Term Memory.
    3. Mandler, G. (2014). Human Memory: An Introduction to Research and Theory.
    4. Liu, S. , et al. (2024). DeltaNet: A Gradient-based Memory Model.
    5. Sun, Y. , et al. (2024). Test-Time Training with Memory Modules.

    Titans 的出现为深度学习领域注入了新的活力。它不仅是对现有模型的改进,更是对记忆与学习关系的深刻再思考。在这个信息爆炸的时代,如何高效地记忆和利用历史信息,将是机器智能发展的关键,而 Titans 无疑为此提供了一个令人兴奋的解决方案。

  • 💾 Saving and Loading DSPy Programs: 永久保存你的智能程序

    在使用 DSPy 优化和训练复杂的程序后,保存和加载这些程序是非常重要的一步。无论是为了后续的部署,还是为了在未来复用优化成果,DSPy 提供了灵活的保存和加载机制。本教程将带你了解如何保存和加载 DSPy 程序。


    🗂️ 两种保存方式

    DSPy 提供了两种主要的保存方式:

    1. 仅保存状态(State-only Saving)
      类似于 PyTorch 的权重保存,仅保存程序的内部状态(如签名、few-shot 示例、配置等),不包含程序的架构。
    2. 保存整个程序(Whole Program Saving)
      从 DSPy 2.6.0 开始支持,保存程序的架构和状态,便于直接加载完整程序。

    ⚙️ 状态保存(State-only Saving)

    状态保存包括程序的内部状态,例如:

    • 签名(Signature)
    • few-shot 示例(Demos)
    • 配置(如 dspy.Predict 模块中使用的语言模型)

    保存状态到 JSON 文件

    以下是一个示例,展示如何保存程序的状态到 JSON 文件:

    import dspy
    from dspy.datasets.gsm8k import GSM8K, gsm8k_metric
    
    # 配置语言模型
    dspy.settings.configure(lm=dspy.LM("openai/gpt-4o-mini"))
    
    # 加载数据集
    gsm8k = GSM8K()
    gsm8k_trainset = gsm8k.train[:10]
    
    # 定义程序
    dspy_program = dspy.ChainOfThought("question -> answer")
    
    # 使用优化器编译程序
    optimizer = dspy.BootstrapFewShot(
        metric=gsm8k_metric,
        max_bootstrapped_demos=4,
        max_labeled_demos=4,
        max_rounds=5
    )
    compiled_dspy_program = optimizer.compile(dspy_program, trainset=gsm8k_trainset)
    
    # 保存状态到 JSON 文件
    compiled_dspy_program.save("./dspy_program/program.json", save_program=False)
    

    保存状态到 Pickle 文件

    当程序中包含不可序列化的对象(如 dspy.Imagedatetime.datetime)时,可以选择保存为 Pickle 文件:

    compiled_dspy_program.save("./dspy_program/program.pkl", save_program=False)
    

    加载保存的状态

    要加载保存的状态,需要重新创建相同的程序,然后使用 load 方法加载状态:

    # 重新创建程序
    loaded_dspy_program = dspy.ChainOfThought("question -> answer")
    
    # 加载保存的状态
    loaded_dspy_program.load("./dspy_program/program.json")
    
    # 验证加载的状态
    assert len(compiled_dspy_program.demos) == len(loaded_dspy_program.demos)
    for original_demo, loaded_demo in zip(compiled_dspy_program.demos, loaded_dspy_program.demos):
        assert original_demo.toDict() == loaded_demo
    assert str(compiled_dspy_program.signature) == str(loaded_dspy_program.signature)
    

    如果是从 Pickle 文件加载:

    loaded_dspy_program.load("./dspy_program/program.pkl")
    

    🏗️ 保存整个程序(Whole Program Saving)

    从 DSPy 2.6.0 开始,你可以保存整个程序,包括程序的架构和状态。这种方法使用了 cloudpickle 库,可以序列化和反序列化 Python 对象。

    保存整个程序

    将程序保存到一个目录中:

    compiled_dspy_program.save("./dspy_program/", save_program=True)
    

    加载整个程序

    直接加载保存的程序,无需重新创建架构:

    loaded_dspy_program = dspy.load("./dspy_program/")
    
    # 验证加载的程序
    assert len(compiled_dspy_program.demos) == len(loaded_dspy_program.demos)
    for original_demo, loaded_demo in zip(compiled_dspy_program.demos, loaded_dspy_program.demos):
        assert original_demo.toDict() == loaded_demo
    assert str(compiled_dspy_program.signature) == str(loaded_dspy_program.signature)
    

    🔄 向后兼容性(Backward Compatibility)

    • DSPy < 2.7:保存的程序不保证向后兼容性。如果你使用 DSPy 2.5.35 保存程序,请确保加载时使用相同版本的 DSPy。
    • DSPy >= 2.7:在主要版本中保证向后兼容性,例如,使用 DSPy 2.7.0 保存的程序可以在 DSPy 2.7.10 中加载。

    🎯 选择合适的保存方式

    • 如果你只需要保存程序的状态(如 few-shot 示例和配置),选择 状态保存
    • 如果你需要保存整个程序的架构和状态,便于直接加载和部署,选择 保存整个程序

    通过保存和加载功能,DSPy 程序可以轻松地复用和部署。无论是简单的状态保存,还是完整的程序保存,DSPy 都为你的项目提供了灵活的解决方案。快试试吧!🎉

  • 🎮 Fine-Tuning Agents with DSPy: 从零开始优化游戏智能体

    在这个教程中,我们将学习如何使用 DSPy 优化一个语言模型驱动的智能体(Agent),让它在复杂的游戏任务中表现更出色。通过优化提示和微调模型权重,我们将从一个基础表现的智能体出发,最终构建一个高效的游戏玩家。


    🛠️ 安装依赖并下载数据

    首先,安装最新版本的 DSPy 和所需的依赖项:

    pip install -U --pre dspy
    pip install -U alfworld==0.3.5 multiprocess
    alfworld-download
    

    推荐设置 MLflow 来追踪优化进程并可视化结果:

    pip install mlflow>=2.20
    mlflow ui --port 5000
    

    在代码中连接 MLflow:

    import mlflow
    mlflow.set_tracking_uri("http://localhost:5000")
    mlflow.set_experiment("DSPy")
    mlflow.dspy.autolog()
    

    🤖 设置语言模型

    我们将使用两个模型:

    • GPT-4o:用于提示优化和生成高质量示例。
    • GPT-4o-mini:一个更小的模型,经过微调后将成为最终的智能体。
    import dspy
    
    gpt4o_mini = dspy.LM('gpt-4o-mini-2024-07-18')
    gpt4o = dspy.LM('openai/gpt-4o')
    dspy.configure(experimental=True)
    

    加载 AlfWorld 数据集(一个家庭模拟任务数据集):

    from dspy.datasets.alfworld import AlfWorld
    
    alfworld = AlfWorld()
    trainset, devset = alfworld.trainset[:200], alfworld.devset[-200:]
    len(trainset), len(devset)
    

    🧑‍💻 定义智能体程序

    智能体的核心是一个简单的 DSPy 模块,名为 Agent。它包含一个子模块 self.react,用于根据任务、轨迹和可能的动作生成下一步行动。

    class Agent(dspy.Module):
        def __init__(self, max_iters=50, verbose=False):
            self.max_iters = max_iters
            self.verbose = verbose
            self.react = dspy.Predict("task, trajectory, possible_actions: list[str] -> action")
    
        def forward(self, idx):
            with alfworld.POOL.session() as env:
                trajectory = []
                task, info = env.init(idx)
                if self.verbose:
                    print(f"Task: {task}")
    
                for _ in range(self.max_iters):
                    trajectory_ = "\n".join(trajectory)
                    possible_actions = info["admissible_commands"][0] + ["think: ${...thoughts...}"]
                    prediction = self.react(task=task, trajectory=trajectory_, possible_actions=possible_actions)
                    trajectory.append(f"> {prediction.action}")
    
                    if prediction.action.startswith("think:"):
                        trajectory.append("OK.")
                        continue
    
                    obs, reward, done, info = env.step(prediction.action)
                    obs, reward, done = obs[0], reward[0], done[0]
                    trajectory.append(obs)
    
                    if self.verbose:
                        print("\n".join(trajectory[-2:]))
    
                    if done:
                        break
    
            assert reward == int(info["won"][0]), (reward, info["won"][0])
            return dspy.Prediction(trajecotry=trajectory, success=reward)
    

    🧪 零样本评估(Zero-Shot Evaluation)

    在未优化的情况下,直接测试智能体的表现:

    agent_4o = Agent()
    agent_4o.set_lm(gpt4o)
    agent_4o.verbose = True
    
    example = trainset[0]
    agent_4o(**example.inputs())
    

    通过评估整个开发集的表现,查看平均成功率:

    metric = lambda x, y, trace=None: y.success
    evaluate = dspy.Evaluate(devset=devset, metric=metric, display_progress=True, num_threads=16)
    
    agent_4o.verbose = False
    evaluate(agent_4o)
    

    结果显示:

    • GPT-4o:约 57.5% 的成功率。
    • GPT-4o-mini:仅 15% 的成功率。

    提示优化(Prompt Optimization)

    我们将使用 MIPROv2 优化器对 GPT-4o 的提示进行轻量级优化:

    optimizer = dspy.MIPROv2(metric=metric, auto="light", num_threads=16, prompt_model=gpt4o)
    
    config = dict(max_bootstrapped_demos=1, max_labeled_demos=0, minibatch_size=40)
    optimized_4o = optimizer.compile(agent_4o, trainset=trainset, **config, requires_permission_to_run=False)
    

    🔧 微调 GPT-4o-mini

    接下来,我们将使用优化后的 GPT-4o 作为教师模型,微调 GPT-4o-mini:

    student_4o_mini = optimized_4o.deepcopy()
    student_4o_mini.set_lm(gpt4o_mini)
    
    optimizer = dspy.BootstrapFinetune(metric=metric, num_threads=16)
    finetuned_4o_mini = optimizer.compile(student_4o_mini, teacher=optimized_4o, trainset=trainset)
    

    📊 评估微调后的智能体

    微调后,GPT-4o-mini 的成功率显著提升:

    evaluate(finetuned_4o_mini)
    

    结果显示成功率达到 71.5%,如果使用更多训练数据(如 500 个任务),成功率甚至可以提升到 82%


    💾 保存和加载优化后的智能体

    将优化后的智能体保存到文件中:

    finetuned_4o_mini.save('finetuned_4o_mini_001.pkl')
    

    稍后可以通过以下方式加载并使用:

    loaded = Agent()
    loaded.load('finetuned_4o_mini_001.pkl')
    

    🚀 总结

    通过 DSPy 的提示优化和微调技术,我们成功将一个基础的 GPT-4o-mini 智能体从 15% 的成功率提升到超过 70%。这种方法不仅高效,还能充分利用小数据集的潜力,为复杂任务提供强大的解决方案。

    准备好让你的智能体更智能了吗?快试试 DSPy 吧!🎉

  • 🧠 DSPy Optimizers:让语言模型程序更聪明的秘密武器

    在现代自然语言处理(NLP)的世界中,语言模型(Language Models, LMs)已成为解决复杂任务的核心工具。然而,如何让这些模型更高效、更智能地工作,尤其是在多模块的复杂程序中,始终是一个挑战。今天,我们将深入探讨 DSPy 优化器,一种专为优化 DSPy 程序设计的算法,它不仅能提升模型性能,还能让你的小数据集发挥大作用!


    🌟 什么是 DSPy 优化器?

    简单来说,DSPy 优化器是一种算法,用于调整 DSPy 程序的参数(例如提示模板和语言模型权重),以最大化你指定的指标(如准确率)。它的核心目标是通过优化提示和示例,或者直接微调模型权重,让你的程序表现更出色。

    一个典型的 DSPy 优化器需要以下三样东西:

    1. DSPy 程序:这可以是一个简单的模块(如 dspy.Predict),也可以是一个复杂的多模块程序。
    2. 评估指标:一个评估程序输出的函数,分数越高越好。
    3. 少量训练输入:可能只有 5 到 10 个示例,甚至不需要完整的标签。

    即使你只有极少的数据,DSPy 也能从中挖掘价值。当然,如果你有更多数据,优化器也会充分利用它。


    🔧 DSPy 优化器优化什么?如何优化?

    DSPy 优化器通过以下方式提升程序质量:

    • 生成高质量的 few-shot 示例:例如 dspy.BootstrapRS
    • 优化自然语言指令:例如 dspy.MIPROv2
    • 构建数据集并微调模型权重:例如 dspy.BootstrapFinetune

    🎯 一个例子:MIPROv2 的优化过程

    dspy.MIPROv2 为例,其优化过程分为三个阶段:

    1. 引导阶段:运行未优化的程序,收集输入/输出行为的轨迹,并过滤出得分较高的轨迹。
    2. 指令生成阶段:根据程序代码、数据和轨迹,生成多个潜在指令。
    3. 离散搜索阶段:在训练集上评估候选程序,并通过贝叶斯优化不断改进指令和示例。

    一个特别之处在于,DSPy 优化器可以组合使用。例如,你可以先用 dspy.MIPROv2 优化程序,再将其结果输入到 dspy.BootstrapFinetune,进一步提升性能。这种组合优化策略被称为 BetterTogether


    📋 当前可用的 DSPy 优化器

    以下是 DSPy 提供的优化器分类及其功能:

    1. 自动 Few-Shot 学习

    这些优化器会自动生成并优化 few-shot 示例,用于提示模型:

    • LabeledFewShot:从提供的标注数据中随机选择示例。
    • BootstrapFewShot:通过程序自身生成示例,并结合标注数据进行优化。
    • BootstrapFewShotWithRandomSearch:在 BootstrapFewShot 的基础上,随机搜索生成的示例组合,选择最佳程序。
    • KNNFewShot:使用 k 近邻算法为每个输入找到最相似的示例。

    2. 自动指令优化

    这些优化器专注于优化提示中的自然语言指令:

    • COPRO:通过坐标上升法(hill-climbing)优化指令。
    • MIPROv2:同时优化指令和 few-shot 示例,采用贝叶斯优化搜索生成空间。

    3. 自动微调

    • BootstrapFinetune:将基于提示的程序转化为微调模型的权重更新。

    4. 程序变换

    • Ensemble:将多个 DSPy 程序组合成一个集成程序。

    🤔 如何选择合适的优化器?

    选择优化器需要根据任务特点和数据量进行权衡。以下是一些建议:

    • 如果你只有 极少的示例(约 10 个),从 BootstrapFewShot 开始。
    • 如果你有 更多数据(50 个以上),尝试 BootstrapFewShotWithRandomSearch
    • 如果你只想进行 指令优化(保持 0-shot 提示),使用 MIPROv2
    • 如果你有足够的数据(200 个以上)并愿意进行 长时间优化,选择 MIPROv2
    • 如果你需要一个高效的程序,可以用 BootstrapFinetune 微调一个小型模型。

    🚀 如何使用 DSPy 优化器?

    所有优化器共享一个通用接口,只需根据具体任务调整参数即可。以下是一个使用 BootstrapFewShotWithRandomSearch 的示例:

    from dspy.teleprompt import BootstrapFewShotWithRandomSearch
    
    config = dict(max_bootstrapped_demos=4, max_labeled_demos=4, num_candidate_programs=10, num_threads=4)
    
    teleprompter = BootstrapFewShotWithRandomSearch(metric=YOUR_METRIC_HERE, **config)
    optimized_program = teleprompter.compile(YOUR_PROGRAM_HERE, trainset=YOUR_TRAINSET_HERE)
    

    💾 保存和加载优化结果

    优化后的程序可以保存为 JSON 格式,方便后续使用:

    optimized_program.save(YOUR_SAVE_PATH)
    

    加载时,只需调用 load 方法:

    loaded_program = YOUR_PROGRAM_CLASS()
    loaded_program.load(path=YOUR_SAVE_PATH)
    

    💡 总结:DSPy 优化器的强大之处

    DSPy 优化器不仅能帮助你在少量数据上获得强大的性能,还能通过组合优化策略实现更高效的程序设计。无论是自动生成示例、优化指令,还是微调模型权重,DSPy 都能为你的 NLP 项目提供强有力的支持。

    所以,准备好让你的语言模型程序更聪明了吗?🎉

  • 🧠 掌控 DSPy:从零到英雄的 8 步指南

    在机器学习的世界里,DSPy 是一个新颖而强大的工具,专注于帮助你构建和优化基于语言模型(LM)的复杂程序。无论你是想打造一个智能聊天机器人,还是设计一个高效的信息提取系统,DSPy 都能助你一臂之力。但正如任何强大的工具一样,掌握它需要一些策略和耐心。在这篇文章中,我们将以通俗易懂的方式,带你走过使用 DSPy 的完整流程——从定义任务到优化模型,逐步揭开它的神秘面纱。


    🌟 第一步:明确你的任务

    在 DSPy 的世界里,模糊的目标只会让你迷失方向。你需要清晰地定义你想解决的问题。

    你到底想做什么?

    是想构建一个能回答问题的聊天机器人?还是一个能从论文中提取关键信息的工具?或者是一个能翻译多语言文本的系统?无论目标是什么,最好的起点就是列出 3-4 个具体的输入和输出示例。例如:

    • 输入:用户提问“什么是量子力学?”
    • 输出:简明扼要的答案,比如“量子力学是研究微观粒子行为的物理学分支。”

    质量与成本的平衡

    预算有限?别担心!选择合适的语言模型是关键。比如,GPT-4-turbo 功能强大但昂贵,而 T5-base 则适合资源受限的项目。记住,找到适合你需求的模型是第一步。


    🛠️ 第二步:设计你的流水线

    流水线是 DSPy 项目的核心,它定义了你的程序如何处理任务。

    从简单开始

    你的任务是否可以用一个简单的“思维链”(Chain-of-Thought)模块解决?还是需要更复杂的工具,比如检索系统或 API 调用?无论如何,建议从简单的模块开始,然后逐步增加复杂性。

    模块化设计

    DSPy 提供了丰富的模块(modules),你可以像搭积木一样组合它们。每个模块都有输入和输出的“签名”(signature),这让你的程序更加灵活和可扩展。


    🔍 第三步:探索几个示例

    有了初步的流水线后,是时候用一些示例来测试它了。

    试验与观察

    运行几个输入示例,看看输出如何。此时,你的流水线可能还很粗糙,但这没关系。记录下有趣的结果,尤其是那些表现不佳的地方。这些数据将为后续优化提供宝贵的参考。


    📊 第四步:定义你的数据

    数据是机器学习的燃料。为了让 DSPy 更好地优化你的程序,你需要准备一些训练和验证数据。

    数据从哪里来?

    • 如果你的任务非常独特,手动创建 10 个示例是个好起点。
    • 如果你的任务有现成的数据集(比如 HuggingFace 数据集),那就太棒了!只要数据许可允许,尽可能利用它们。

    数据量的建议

    DSPy 的优化器可以在少量数据(如 10 个示例)上工作,但如果你能提供 50-100 个,甚至 300-500 个示例,效果会更好。


    📏 第五步:定义你的评估指标

    没有清晰的指标,就很难知道你的系统是否在进步。

    什么是好输出?

    简单的任务可以用“准确率”或“F1 分数”来评估。但对于生成长文本的任务,你可能需要更复杂的指标,比如检查输出的逻辑性、相关性和准确性。

    动态优化指标

    DSPy 的一大亮点是,你可以用小型 DSPy 程序来定义复杂的评估指标。这种灵活性让你能更精确地衡量系统性能。


    🧪 第六步:零样本评估

    在优化之前,先用现有的数据和指标对流水线进行初步评估。这不仅能帮助你发现明显的问题,还能为后续优化提供基准。


    🚀 第七步:用 DSPy 优化器编译

    有了数据和指标后,就可以使用 DSPy 的优化器来提升你的程序性能。

    选择合适的优化器

    • 小数据集(~10 个示例):使用 BootstrapFewShot 优化器。
    • 中等数据集(~50 个示例):尝试 BootstrapFewShotWithRandomSearch
    • 大数据集(300 个以上示例):使用 MIPRO 优化器。
    • 高效模型需求:用 BootstrapFinetune 将复杂模型编译为小型高效模型。

    🔄 第八步:迭代改进

    即使你的系统已经表现不错,也别停下脚步。回顾每个步骤,看看是否有改进的空间:

    • 任务定义是否清晰?
    • 数据是否足够多样化?
    • 评估指标是否准确?
    • 是否需要更复杂的流水线?

    DSPy 的设计理念就是支持迭代开发。通过不断调整数据、程序结构和优化步骤,你的系统会变得越来越强大。


    🎉 总结:DSPy 的魔力

    DSPy 是一个为语言模型优化而生的工具,它将复杂的任务分解为简单的模块化步骤,并通过优化器提升性能。虽然它的学习曲线可能稍陡,但一旦掌握,你将拥有前所未有的能力来构建智能系统。

    如果在使用过程中遇到问题,别忘了加入 DSPy 社区 Discord,与其他开发者交流经验。


    📚 参考文献

    1. DSPy 官方文档:https://discord.com/invite/XCGy2WDCQB
    2. HuggingFace 数据集:https://huggingface.co/datasets
    3. GPT 模型指南:https://openai.com
  • 🧠 TokenSkip:压缩大模型思维链的艺术

    在人工智能的浩瀚星海中,Chain-of-Thought(CoT,思维链) 是一颗璀璨的明星。它赋予了大型语言模型(LLMs)逐步推理的能力,使其能够将复杂问题拆解为一系列可管理的子任务。然而,随着思维链长度的增加,计算成本和推理延迟也随之飙升,尤其当思维链长度超过 10,000 个 token 时,这种问题变得尤为严重。那么,是否每一个 token 都对推理结果同等重要?如果不是,我们是否可以跳过那些“无足轻重”的 token,而不损害模型的推理能力?这正是 TokenSkip 试图回答的问题。

    本文将带你深入探索这项创新技术,它如何通过“跳过”不重要的 token 来压缩思维链,同时保持推理性能的稳定。让我们从头开始,一步步揭开 TokenSkip 的秘密。


    🌟 思维链的魅力与困境

    🧩 什么是思维链?

    思维链是一种逐步推理的方法,它让大型语言模型能够像人类一样,通过分解复杂问题来找到答案。例如,解决一个数学问题时,思维链会将问题分解为多个步骤,从已知条件开始,逐步推导出最终答案。这种方法已被证明可以显著提高模型在复杂任务上的表现。

    近年来,研究者发现,延长思维链的长度可以进一步提升模型的推理能力。例如,OpenAI 的 o1 和 DeepSeek-R1 等研究表明,将推理步骤从数百扩展到数千,甚至数万个 token,可以让模型更好地解决复杂问题。

    效率的代价

    然而,思维链的效率问题不容忽视。由于 LLM 的自回归解码特性,思维链的长度与推理延迟呈线性增长。此外,注意力机制的二次计算复杂度进一步加剧了这一问题。当思维链变得过长时,用户体验受到严重影响。

    那么,问题的核心是:思维链中的每一个 token 是否都对推理结果至关重要? 如果不是,我们是否可以通过某种方式减少 token 的使用,而不显著降低推理性能?


    🔍 TokenSkip 的灵感与诞生

    🧠 每个 token 的重要性是一样的吗?

    研究者首先对思维链中的 token 进行了深入分析,试图回答一个关键问题:“思维链中的每个 token 是否对推理结果同等重要?” 他们发现,答案是否定的。

    通过实验,研究者揭示了一个有趣的现象:某些 token 对推理结果的贡献远大于其他 token。 例如,在数学推理中,表达式和数字往往比连接词(如“所以”或“因为”)更重要。这种语义重要性的差异为 TokenSkip 的设计提供了灵感。

    ✂️ TokenSkip 的核心思路

    基于上述发现,研究者提出了 TokenSkip,这是一种简单而有效的方法,能够让 LLM 在推理过程中跳过那些语义重要性较低的 token。具体来说,TokenSkip 包括以下三个核心步骤:

    1. Token 剪枝:根据 token 的语义重要性,对思维链进行剪枝,去除那些对推理结果贡献较小的 token。
    2. 模型微调:使用剪枝后的思维链对目标模型进行监督微调,使其能够在推理过程中自动跳过冗余 token。
    3. 可控压缩:通过调整压缩比例,用户可以灵活控制思维链的压缩程度,从而在推理效率和准确性之间找到最佳平衡。

    🛠️ TokenSkip 的工作原理

    ✂️ Token 剪枝:找到关键 token

    TokenSkip 的第一步是对思维链进行剪枝。研究者使用了一种名为 LLMLingua-2 的语义重要性度量方法,该方法基于双向语言模型(如 BERT)来评估每个 token 的重要性。具体来说,LLMLingua-2 会为每个 token 分配一个重要性分数,分数越高,表示该 token 对推理结果的贡献越大。

    剪枝过程如下:

    1. 对思维链中的每个 token 计算重要性分数。
    2. 按重要性分数降序排列 token。
    3. 根据用户指定的压缩比例(例如 0.7),保留前 70% 的重要 token,去除剩余的 token。

    🎯 模型微调:教会模型跳过冗余

    剪枝后的思维链会被用作训练数据,对目标模型进行微调。训练过程中,模型会学习如何在推理过程中自动跳过那些不重要的 token,同时保持推理的连贯性和准确性。

    🚀 推理阶段:高效的思维链生成

    在推理阶段,TokenSkip 会根据用户指定的压缩比例,生成压缩后的思维链。这种方法不仅减少了推理所需的 token 数量,还显著降低了推理延迟。


    📊 实验结果:TokenSkip 的魔力

    研究者在多个模型和任务上验证了 TokenSkip 的效果,包括 LLaMA-3.1-8B-Instruct 和 Qwen2.5-Instruct 系列模型,并使用了两个数学推理基准数据集:GSM8K 和 MATH-500。

    🌟 显著的压缩效果

    在 GSM8K 数据集上,TokenSkip 将 Qwen2.5-14B-Instruct 的思维链 token 数量从 313 减少到 181,压缩比例达到 40%,而推理准确率仅下降了不到 0.4%。在更具挑战性的 MATH-500 数据集上,TokenSkip 将 token 数量减少了 30%,推理准确率仅下降了不到 4%。

    推理速度大幅提升

    TokenSkip 的压缩效果直接转化为推理速度的提升。例如,在 GSM8K 数据集上,TokenSkip 将推理延迟减少了 1.8 倍,使得用户体验得到了显著改善。

    🔍 案例分析:TokenSkip 的压缩策略

    以下是一个 GSM8K 数据集上的示例:

    问题:Marcus 是 Leo 年龄的一半,比 Deanna 小五岁。Deanna 今年 26 岁。Leo 多大?

    • 原始思维链:逐步推导 Marcus 和 Leo 的年龄,包含 313 个 token。
    • 压缩后的思维链:去除了连接词和冗余描述,仅保留关键推理步骤,最终仅用 181 个 token 得出答案。

    💡 TokenSkip 的优势与未来方向

    🎯 低成本、高效率

    TokenSkip 的一个显著优势是其低训练成本。在 Qwen2.5-14B-Instruct 模型上,TokenSkip 仅微调了 0.2% 的参数,训练时间不到 2.5 小时。这使得 TokenSkip 成为一种高效且可复现的解决方案,适合在资源受限的场景下部署。

    🌌 未来的可能性

    尽管 TokenSkip 已经展示了其强大的压缩能力,但仍有许多值得探索的方向。例如:

    • 更强的压缩技术:结合更先进的 token 重要性度量方法,进一步提升压缩效果。
    • 更大规模的模型:在更大规模的 LLM(如 Qwen2.5-72B-Instruct)上验证 TokenSkip 的性能。
    • 领域优化:针对特定领域(如数学或医学)优化 token 重要性度量方法,以提高压缩的针对性。

    📚 结语

    TokenSkip 的出现为思维链的压缩问题提供了一种优雅的解决方案。通过跳过不重要的 token,它在推理效率和准确性之间找到了一个理想的平衡。这不仅为大型语言模型的高效部署铺平了道路,也为未来的研究提供了新的思路。

    在这个信息爆炸的时代,TokenSkip 就像是一位“高效的编辑”,帮助我们从冗长的思维链中提炼出最有价值的部分。未来,它或许会成为我们与人工智能对话时不可或缺的工具。让我们拭目以待!


    🔗 参考文献

    1. Heming Xia et al. (2025). TokenSkip: Controllable Chain-of-Thought Compression in LLMs.
    2. Nye et al. (2021). Chain-of-Thought Prompting for Complex Reasoning Tasks.
    3. Pan et al. (2024). LLMLingua-2: A Token Importance Metric for Prompt Compression.
    4. OpenAI (2024). Advancements in Chain-of-Thought Scaling.
    5. Hendrycks et al. (2021). MATH: A Dataset for Mathematical Reasoning.
  • 🧠 从零开始构建智能代理:DSPy 与多跳搜索的奇妙旅程

    在当今的人工智能世界中,构建智能代理已经不再是遥不可及的梦想。无论是自动化的客户服务机器人,还是能够解决复杂问题的搜索助手,这些技术都在逐步改变我们的生活方式。而在这个过程中,DSPy(Declarative Signature Programming for LLMs)作为一个强大的框架,正在重新定义如何高效地构建和优化语言模型程序。

    本文将带您深入探索如何使用 DSPy 构建一个智能代理,并优化其在复杂任务中的表现。我们将从基础开始,逐步搭建一个能够进行多跳搜索(multi-hop search)的 ReAct 代理,并通过优化提升其性能。无论您是初学者还是经验丰富的开发者,这篇文章都将为您提供实用的指导和深刻的启发。


    🌟 什么是 DSPy?

    在进入实践之前,让我们先了解一下 DSPy 的核心理念。DSPy 是一个专为语言模型(LLM)设计的编程框架,它的目标是通过模块化的方式,让开发者能够高效地构建、优化和部署复杂的语言模型程序。与传统的提示工程(prompt engineering)不同,DSPy 提供了一个更系统化的方式来定义任务、优化模型和管理整个工作流。

    DSPy 的核心概念包括:

    1. Signatures(签名):定义输入输出的结构化接口。
    2. Modules(模块):将任务分解为多个可组合的子任务。
    3. Optimizers(优化器):通过数据驱动的方法优化提示和模型权重。
    4. Assertions(断言):为模型输出提供约束和验证机制。

    通过这些工具,DSPy 不仅让开发者能够快速构建复杂的程序,还能通过优化器自动提升模型的性能。


    🚀 第一步:安装 DSPy 和设置环境

    在开始之前,请确保您已经安装了 DSPy。您可以通过以下命令安装最新版本:

    pip install -U dspy
    

    此外,我们还推荐使用 MLflow 进行实验跟踪和可视化。MLflow 是一个强大的 LLMOps 工具,可以帮助您记录模型的优化过程并进行深入分析。以下是设置 MLflow 的步骤:

    1. 安装 MLflow: pip install mlflow>=2.20
    2. 启动 MLflow UI: mlflow ui --port 5000
    3. 在代码中连接 MLflow: import mlflow mlflow.set_tracking_uri("http://localhost:5000") mlflow.set_experiment("DSPy")
    4. 启用自动跟踪: mlflow.dspy.autolog()

    通过这些设置,您可以在 MLflow 的界面中直观地查看每次程序执行的详细信息。


    🛠️ 第二步:构建一个简单的 ReAct 代理

    在本教程中,我们将使用 DSPy 构建一个 ReAct(Reasoning + Acting)代理。这个代理的任务是处理复杂的多跳搜索问题,即从一个复杂的声明中推导出需要检索的多个维基百科页面。

    1. 配置语言模型

    我们选择了一个小型的语言模型 Llama-3.2-3B-Instruct 作为主要模型。尽管它的性能不如大型模型,但它的速度快且成本低,非常适合快速迭代。我们还将使用一个更大的模型 GPT-4o 作为教师模型,帮助优化小型模型的表现。

    以下是配置代码:

    import dspy
    
    llama3b = dspy.LM('<provider>/Llama-3.2-3B-Instruct', temperature=0.7)
    gpt4o = dspy.LM('openai/gpt-4o', temperature=0.7)
    
    dspy.configure(lm=llama3b)
    

    2. 加载数据集

    我们将使用 HoVer 数据集,它是一个多跳任务数据集,要求模型从复杂的声明中推导出需要检索的维基百科页面。以下是加载数据的代码:

    import random
    from dspy.datasets import DataLoader
    
    kwargs = dict(fields=("claim", "supporting_facts", "hpqa_id", "num_hops"), input_keys=("claim",))
    hover = DataLoader().from_huggingface(dataset_name="hover-nlp/hover", split="train", trust_remote_code=True, **kwargs)
    
    hpqa_ids = set()
    hover = [
        dspy.Example(claim=x.claim, titles=list(set([y["key"] for y in x.supporting_facts]))).with_inputs("claim")
        for x in hover
        if x["num_hops"] == 3 and x["hpqa_id"] not in hpqa_ids and not hpqa_ids.add(x["hpqa_id"])
    ]
    
    random.Random(0).shuffle(hover)
    trainset, devset, testset = hover[:100], hover[100:200], hover[650:]
    

    3. 定义搜索工具

    为了让代理能够进行搜索,我们需要定义两个工具:search_wikipedialookup_wikipedia。前者用于检索维基百科页面,后者用于查看特定页面的内容。

    def search_wikipedia(query: str) -> list[str]:
        """返回前 5 个结果以及第 5 到第 30 个结果的标题。"""
        topK = search(query, 30)
        titles, topK = [f"`{x.split(' | ')[0]}`" for x in topK[5:30]], topK[:5]
        return topK + [f"Other retrieved pages have titles: {', '.join(titles)}."]
    
    def lookup_wikipedia(title: str) -> str:
        """返回维基百科页面的文本内容。"""
        if title in DOCS:
            return DOCS[title]
    
        results = [x for x in search(title, 10) if x.startswith(title + " | ")]
        if not results:
            return f"No Wikipedia page found for title: {title}"
        return results[0]
    

    4. 定义 ReAct 代理

    接下来,我们定义一个简单的 ReAct 代理,它将接收一个声明(claim),并输出需要检索的维基百科页面列表。

    instructions = "Find all Wikipedia titles relevant to verifying (or refuting) the claim."
    signature = dspy.Signature("claim -> titles: list[str]", instructions)
    react = dspy.ReAct(signature, tools=[search_wikipedia, lookup_wikipedia], max_iters=20)
    

    📊 第三步:评估代理的性能

    在优化之前,我们需要评估代理的初始性能。我们定义了一个 top5_recall 指标,用于衡量代理在前 5 个返回结果中找到正确页面的比例。

    def top5_recall(example, pred, trace=None):
        gold_titles = example.titles
        recall = sum(x in pred.titles[:5] for x in gold_titles) / len(gold_titles)
    
        if trace is not None:
            return recall >= 1.0
        return recall
    
    evaluate = dspy.Evaluate(devset=devset, metric=top5_recall, num_threads=16, display_progress=True, display_table=5)
    

    运行评估:

    evaluate(react)
    

    初始结果显示,代理的 top5_recall 仅为 8%。这表明模型在当前配置下的性能较差,需要进一步优化。


    🔧 第四步:优化代理

    DSPy 提供了强大的优化器,可以帮助我们提升代理的性能。在这里,我们使用 MIPROv2 优化器,它能够通过教师模型(如 GPT-4o)生成更好的提示并优化代理的表现。

    kwargs = dict(teacher_settings=dict(lm=gpt4o), prompt_model=gpt4o, max_errors=999)
    
    tp = dspy.MIPROv2(metric=top5_recall, auto="medium", num_threads=16, **kwargs)
    optimized_react = tp.compile(react, trainset=trainset, max_bootstrapped_demos=3, max_labeled_demos=0)
    

    优化过程可能需要一些时间,并会调用 GPT-4o 生成提示。优化完成后,我们再次评估代理的性能:

    evaluate(optimized_react)
    

    优化后的结果显示,代理的 top5_recall 提升到了 41.7%,性能显著提高。


    💾 第五步:保存和加载优化后的代理

    为了方便后续使用,我们可以将优化后的代理保存到文件中,并在需要时加载:

    optimized_react.save("optimized_react.json")
    
    loaded_react = dspy.ReAct("claim -> titles: list[str]", tools=[search_wikipedia, lookup_wikipedia], max_iters=20)
    loaded_react.load("optimized_react.json")
    

    通过这种方式,您可以轻松地在不同项目中复用优化后的代理。


    🔍 第六步:深入分析优化结果

    优化后的代理不仅性能更强,还能够生成更合理的提示和推理过程。您可以使用以下代码查看优化后的提示历史:

    dspy.inspect_history(n=2)
    

    此外,如果您启用了 MLflow 跟踪,还可以在 MLflow 的界面中查看每一步的详细信息,包括模型调用、提示内容和工具执行结果。


    🎉 总结

    通过本文的教程,我们成功构建了一个基于 DSPy 的 ReAct 代理,并通过优化显著提升了其在多跳搜索任务中的表现。从最初的 8% 提升到 41.7%,这一过程展示了 DSPy 在构建和优化语言模型程序方面的强大能力。

    无论您是希望构建智能搜索助手,还是探索更复杂的语言模型应用,DSPy 都是一个值得尝试的工具。希望本文能为您的开发之旅提供灵感和指导!

人生梦想 - 关注前沿的计算机技术 acejoy.com