动力转向:从小LLM中挤出巨大力量

社区文章 发布于2024年12月9日

因为没有足够的“铁”来运行那些热门的405B甚至70B LLM而感到FOMO(错失恐惧症)吗?如果任务涉及结构化输出,请首先确保一个小巧的12/13B模型在稍加引导下无法完成任务。如果你能提供一些辅助,你可能不需要你想象的那么庞大的LLM。

几天前,我与一位朋友在Discord上进行了一次非常有趣的对话,他正在追逐最大的LLM,将其视为一种暴力问题解决器。此后,我观察到这在探索LLM DIY的人群中是一种常见的方法,但我认为在许多情况下,这并非必需。

LLM秘密武器如何被弃之不用?

OP(我们姑且称之为他们)开场就说,他们64GB RAM的M1 Mac毫无用处,因为他们需要运行Llama 70B,而4位量化得到“糟糕的结果”,8位量化又无法安装。这是一个不明确的问题陈述,我不得不耐心地摸清OP的实际问题是什么。我将省去来回的问答,只总结如下:

  • 他们正在使用LLM分析一堆带有结构化输出的互联网帖子
    • 这在他们64GB的Mac上使用Ollama进行
  • 他们可以在Nvidia(双3090)上使用Ollama获得他们想要的结果
  • 他们尝试使用MLX进行基本生成(注意:Ollama不支持MLX),但他们说生成挂起了

实际上,OP经历了我们大多数人都经历过的事情:他们从一个问题开始,然后不得不使用越来越大的模型和量化,直到他们开始获得好的结果。因此,他们很容易将模型大小和基准视为所有LLM工作负载的主要因素。他们购买M1 Mac是因为它在运行此类工作负载方面享有盛誉,他们也带着追求最大模型的心态。

MLX尤其是我与他们对话的主要背景。我经常说MLX是AI DIY的秘密武器,对于那些喜欢自己动手实践和亲身参与GenAI革命的人来说。这是Apple Silicon在AI方面如此出色的重要原因:快速发展,代码设计精良——可惜在GenAI项目中这种情况有点罕见。最重要的是,硬件的能效非常高。

就我个人而言,我使用Toolio在MLX之上构建,Toolio是一个用于LLM生成且保证结构化输出的包,我将其更具体地称为**模式引导的结构化输出(3SO)**,灵感来源于llm-structured-output,另一个基于LLM采样器的开源结构化输出工具。我知道Ollama不支持3SO(现在已支持),并问OP是否尝试过任何形式的3SO。

我早就猜到他们之所以需要70B参数模型,并使用8位或更高位量化,并非因为底层问题如此复杂,而是因为更大的模型能更容易处理无引导的结构化输出请求。我告诉OP,如果他们转而使用引导式请求,他们可能会使用小得多的模型。

我做了很多LLMOps数据提取工作——从非结构化内容中提取结构化数据。我发现小型LLM在这方面表现出色,如果你能找到工具引导它们通过结构部分,这样它们就能“专注于”(是的,这里拟人化了)纯粹的语言和逻辑。Toolio最初就是为了解决我自己的这些痛点而诞生的。

image/png

从OP的回复中可以清楚地看出,他们不仅没有考虑或尝试过3SO,而且还假设足够大的LLM会保证结构。我一直在学习到这是一种常见的误解,甚至在几周前我在Toolio的README中添加了以下内容:

有时对于限制LLM输出的各种方法存在混淆

  • 你基本上可以通过提示工程(详细指令、少量样本等)“恳求”模型,然后尝试生成,检查结果,如果不符合要求就重试(可能在重新提示时进一步“恳求”LLM)。这会导致结果不一致,速度慢且浪费,并且最终需要更强大的LLM。
  • Toolio的方法,我们称之为模式引导的结构化输出(3SO),是将语法的输入格式(在本例中为JSON Schema)转换为一个状态机,该状态机将这些规则作为硬约束应用于输出采样器。我们不是恳求LLM,而是引导它。

在这两种情况下,如果你用大量期望输出语法和结构的例子训练或微调了模型,你都会得到更好的结果,但LLM的大小、能力和训练只是S3O图景的一部分。

模式引导的结构化输出(3SO)到底意味着什么?

我添加到Toolio README中的区别值得详细阐述,因为关于这个主题似乎存在如此根本性的误解;有太多工具提供了解决这个问题的不同方法,但并非总能清晰地阐述它们的方法。

想象一下LLM是一个聪明的程序员朋友。你给他们笔和纸,让他们写出从他们家到当地邮局的路线,但必须是严格的输出格式,比如XML或JSON。大多数程序员都能做到这一点,语法上零错误,并且在大多数时候都能提供功能上有用的旅行方向。这本质上是两个问题合二为一,利用了他们大脑中两个独立的知识来源。

  1. 他们对居住地和邮局路线的知识和记忆
  2. 他们对XML或JSON语法的知识(我们接下来只谈JSON)

但问题是:他们偶尔会出错。当然,他们可能会不小心把左转说成右转,但更常见的是语法上的错误。他们可能会忘记JSON对象中的逗号,或者忘记转义字符串中的双引号。我们的大脑确实不擅长这种严格的语法。然而,最有经验的程序员犯这种错误的可能性很小,以至于你会被误导认为他们保证输出正确,当然事实并非如此。

为此,请将大多数LLM视为极其聪明的程序员。如果你告诉他们生成JSON,他们可以做得很好,但他们可能会出错;而且通常以与人类不同的方式出错。最常见的错误是他们以一些引言文字开始输出,例如“Sure here is your JSON output”,可能还附带反引号——只是为了友好和乐于助人,正如他们的训练所鼓励的那样。不幸的是,这些额外的冗余最终会破坏JSON解析,阻碍任何期望语法完美响应的自动化。

有许多提示工程任务可以帮助解决这个问题。你可以添加额外的指令,让LLM不要添加任何前言。你可以包含少量示例。你甚至可以微调LLM以加强你想要的输出类型。这将改善你的结果到一定程度,但关键是,**所有这些都不能保证有效、结构化的输出**。

许多用于结构化输出的工具通过验证和重试来处理这个问题。这就像检查你的程序员朋友的工作,然后说“哎呀,你漏了一个逗号;再试一次。”通过足够的重试和提示工程技巧,你也许可以保证输出,但显然,最好能一次性保证,就像使用3SO一样。

方向盘上的辅助之手

在实践中,大多数程序员为了确保至少不必担心语法错误,并且能够专注于底层问题,他们会怎么做?他们会使用语法辅助工具,例如文本编辑器的自动完成功能。这样他们就能基本确保输出的有效性。这些“向导”基本上是在帮助用户正确地“转向”。

我们可以给LLM提供同样的帮助,甚至可以更进一步,严格引导其输出,确保其输出,例如,在语法上是正确的JSON,甚至符合特定的JSON模式,即JSON schema。在OP的例子中,我们希望确保它生成类似以下内容:

[
    {
      "summary": "ABC 123 Do re mi",
      "author": "Jackson 5",
      "link": "https://example.com/post/2312"
    },
    {
      "summary": "Stop! The love you save maybe your own",
      "author": "Jackson 5",
      "link": "https://example.com/post/9970"
    }
]

我们想确保它不会编造字段名,比如用`url`而不是`link`,或者添加一个未指定的`timestamp`字段,或者发明某种额外的结构,比如一个外部对象而不是数组。

这种模式引导生成的一个著名例子是OpenAI最近的公告,在API中引入结构化输出,其中他们也阐明了模式感知引导的新特点。

虽然 JSON 模式(OpenAI 于 2023 年 11 月发布)提高了模型生成有效 JSON 输出的可靠性,但它不能保证模型的响应会符合特定的模式。今天(2024 年 8 月),我们在 API 中引入了结构化输出,这是一项新功能,旨在确保模型生成的输出与开发人员提供的 JSON Schema 完全匹配。

我最早了解到这种功能是llama.cpp grammars,它出现在2023年末。llama.cpp是一个用于LLM推理的开源库。GBNF是一种用于定义形式语法以约束LLM输出的格式。

与此同时,就在上周五,Ollama也加入了这个行列,宣布了3SO

这涵盖了我所知道的最主流、最早和最新的例子。另一个是我自己的项目,Toolio。引导输出生成的其他例子相当稀少,但我相信将3SO添加到工具包中是有效使用GenAI的关键。

动力转向的差异

回到JSON导航输出的例子,请记住我们有两个问题空间合二为一:

  1. 一个区域的知识和记忆,以及旅行方向
  2. JSON语法知识

GenAI 的工作方式是,当响应生成时,有一个庞大的统计框架来预测每个下一个 token。在没有引导的天真方法中,LLM 同时利用这两个知识流,两者都对可能出现的下一个 token 的统计数据做出贡献。处理这些统计数据以进行选择的过程,即**采样**,以相对不复杂的方式进行,因此在核心知识 (1) 通过时存在一些摩擦。这不仅有时会增加错误的可能性,还会降低整个系统的效率。

有了3SO,你就可以让采样层处理(2)。实际上,采样器根据输出模式限制了下一个token的统计数据。例如,如果它由一个JSON模式控制,该模式规定外部结构是一个列表,那么采样器会调整,使得`[`作为第一个token的概率接近100%。

这不仅确保您获得有效输出,而且对标记统计数据的调整还具有减少主要知识流(1)摩擦的附加效果。这也意味着,如果LLM的功率稍低,并且可能难以满足输出结构要求,我们正在给予它足够的帮助,使其能够成功,只要它足够智能,能够处理主要知识流(1)。

根据我的经验,以及(剧透预警)对OP来说也是如此,人们常常最终寻找越来越强大的LLM(OP的情况是Llama 70B),不是因为底层问题(1)需要它,而是因为(1)和(2)的结合需要大量的LLM算力才能达到足够高的正确率。OP**以为**他得到了3SO,因为他从未亲眼目睹失败,但没有推理层的引导支持,他总是处于赌博状态,而且不得不追逐过高配置且资源消耗大的LLM。

image/png

是的,回到我们的朋友OP

OP 对我试图向他解释上述内容时表示怀疑,但值得称赞的是,他们确实同意尝试 Toolio。我查看了他们的代码,在帮助他们解决了一些奇怪的问题,例如畸形的标记化之后,我在 Claude 的帮助下为他们所需的输出格式快速编写了一个模式。然后我能够极大地简化他们的主提示,因为我可以删除所有恳求 LLM 表现良好、少量示例以及所有这些内容。所有这些对于 Toolio 的 3SO 都是不必要的。

OP 一直在尝试使用 `mlx-community/Llama-3-70B-Instruct-Gradient-262k-4bit` 模型,但我只想用我当前最喜欢的小(ish)模型 `mlx-community/Mistral-Nemo-Instruct-2407-4bit` 来尝试,也就是 12B 而不是 70B。使用 Toolio 的 3SO 的最终代码如下。

import sys
import asyncio
from toolio.llm_helper import model_manager
from toolio.common import response_text

SCHEMA = '''\
{
    "$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#",
    "type": "array",
    "items": {
        "type": "object",
        "required": ["summary", "author", "link"],
        "properties": {
            "summary": {
                "type": "string",
                "description": "Concise summary of the post"
            },
            "author": {
                "type": "string",
                "description": "Name of the person who shared or authored the response"
            },
            "link": {
                "type": "string",
                "description": "URL source of the original post or reference"
            }
        }
    }
}
'''

UPROMPT = '''\
You are a news analyst. Read the following material and extract information according to the provided schema.

''' + sys.stdin.read()

toolio_mm = model_manager('mlx-community/Mistral-Nemo-Instruct-2407-4bit')

async def main(tmm):
    msgs = [{"role": "user", "content": UPROMPT}]
    print(await response_text(tmm.complete(msgs, json_schema=SCHEMA, max_tokens=8192)))

asyncio.run(main(toolio_mm))

json_schema=SCHEMA 参数用于设置3SO。代码还尊重标准输入中通过管道传输的串联帖子以进行总结。

image/png OP 对如此小的 LLM 能够像冠军一样处理这项任务感到惊讶,所以讨论以一个愉快的结局告终。

总结

我应该承认我一直在进行一些过度简化。即使是3SO,在某些情况下也可能无法100%保证。OpenAI的实现可能会拒绝生成违反其内容标准和护栏的输出,这些会以结构化的拒绝响应形式出现。Toolio和大多数状态机方法在极少数情况下可能会遇到模式歧义,导致进入不可判定状态。然而,这种情况非常罕见,远不及未经引导的结构化输出所带来的风险。

3SO 无法帮助你创作创意散文或诗歌,也无法满足许多其他常见的 LLM 用例,但如果你正在更大的代码或 API 管道中进行某种数据处理,那么 3SO 是 LLMOps 工具包中的关键工具。

专题图片使用 Recraft.ai 生成

社区

注册登录 评论