[译] 消耗数亿 GPT Token 后的经验教训

    原文:https://kenkantzer.com/lessons-after-a-half-billion-gpt-tokens/

    我的初创产品 Truss 在过去六个月中新增了几个重度依赖 LLM 的功能。在实践过程中,我发现在 HackNews 上看到的 LLM 叙述和我实际情况不太一样,所以,在我大约使用了超过5亿个 Token 后,分享一些“令人惊讶”的教训。

    首先是一些细节:

    • 我们使用的是 OpenAI 的模型(对于其他模型的看法可以看底部的 Q&A)
    • 使用情况:85% 的 GPT-4 以及 15% 的 GPT-3.5
    • 只处理文本,所以没有使用 gpt-4-vision、sora、whisper 等
    • 有一个 ToB 的案例,业务专注于总结、分析、提取
    • 5亿 Token 实际没有那么多,更直观的看大概是75万页的文本

    第一课:对于 Prompt,少即是多

    我们发现,在给 GPT 下指令的时候,有些常识如果不特意写出来,反而能得到更好的结果。GPT 并不傻,过度指定会让其困惑。

    与编程不同,在编程中必须明确一切。

    有个业务的流程是读取一段文本,并要求 GPT 将其分类为与美国50个州之一或联邦政府有关。这不算一个困难的任务,本可以使用字符串或正则表达式来匹配,但会有一些奇怪的边缘情况,而继续使用编程会花费更多的时间。所以我们第一次尝试大概是这样的:

    Here's a block of text. One field should be "locality_id", and it should be the ID of one of the 50 states, or federal, using this list:
    [{"locality: "Alabama", "locality_id": 1}, {"locality: "Alaska", "locality_id": 2} ... ]
    

    估计在98%的情况下是有效的,但剩余的2%我们并没放过,进行了深入研究。

    调查过程中,我们注意到在没有要求的情况下,会出现一个字段name,对应的值是州的全名。

    更好的方法应该是:GPT,显然你知道美国的50个州,所以请直接告诉我这条信息对应的州的全名,如果是涉及美国联邦政府的,则回答“联邦”。(You obviously know the 50 states, GPT, so just give me the full name of the state this pertains to, or Federal if this pertains to the US government.)。

    当你给出的指令较为模糊时,GPT 的质量和泛化能力竟然还能得到提升,这是一种高级别委托/思考能力的典型标志。这就意味着 GPT 能够在一定程度上理解并填补指令中的空白,从而展现出更强的理解力和适应性,而这在传统意义上是反直觉的,也是令人惊奇的地方。

    第二课: 没必要上 LangChain OpenAI 官方的 Chat API 就行

    LangChain 就是一个过早抽象化的绝佳例子。互联网上都说必须使用 LangChain,一开始我们也笃定用上 LangChain。但事实恰恰相反,在消耗数百万个 Token 以及在生产环境中实现了3-4种非常多样化的 LLM 功能后,我们的 openai_service 文件中仍然只有一个40行的函数:

    def extract_json(prompt, variable_length_input, number_retries)
    

    唯一被使用的 API 就是 chat,并从其中提取 json 数据。我们没有用 JSON mode、函数调用(Function Calling)、助手,甚至是 System Prompt。在 gpt-4-turbo 发布时,我们的代码库只更新了一个字符串。

    这就是强大的通用大模型魅力所在:少即是多。

    这个函数中40行代码大多数是在围绕 OpenAI API 出现500错误和 Socket 关闭做异常处理。

    我们还内置了一些自动截断逻辑,这样就无需担心上下文的长度限制。我们还设计了自己专有的 Token 长度估算器,大致如下:

    if s.length > model_context_size * 3:
        # 对其进行截断!
    

    在遇到大量句号或数字的时候(这种情况下字符和 Token 的比例小于 3),上面的代码估算就不算太准,所以我们还添加了一段定制化的 try/catch 重试逻辑:

    if response_error_code == "context_length_exceeded":
        s.truncate(model_context_size * 3 / 1.3)
    

    采用这种策略后,算是迈了一大步,这种方法已经满足了目前的需求,而且还有不错的灵活性。

    第三课:通过 Stream API 改善延迟,并以打字机效果向用户展示,实际上是 ChatGPT 在用户体验方面的一项重大创新

    起初我们认为这只是一个小噱头,但用户对可变速度“输入”的字符反应非常积极——这就像鼠标/光标在人工智能领域的用户体验突破时刻一样重要。这种模拟打字效果能够给用户带来更自然、更沉浸式的互动体验,使得人机交互更加顺畅且引人注目。

    第四课: GPT 在生成“零假设”的时候表现不佳

    “如果没有找到任何信息,请返回空输出”(Return an empty output if you don’t find anything) ———— 这个可能算是最容易出错的 Prompt。GPT 不仅在应该返回空白内容的时候喜欢编造内容,而且还在应该返回真实答案的时候还返回空白内容。

    我们大部分的 Prompt 都是这个形式:

    这里有一段关于某公司的陈述文本,我希望你能输出提取到的公司信息的 JSON 格式。如果找不到相关的内容,请返回空内容。以下是文本:[文本块]
    
    Here’s a block of text that’s making a statement about a company, I want you to output JSON that extracts these companies. If there’s nothing relevant, return a blank. Here’s the text: [block of text]
    

    有一段时间,我们遇到了一个 Bug,即 [block of text] 可能会是空的。这种情况下,GPT 很容易产生幻觉。一个有趣的点是,GPT 特别喜欢编造烘培店的名字,例如

    • Sunshine Bakery
    • Golden Grain Bakery
    • Bliss Bakery

    后面就修复了这个 Bug,解决方案是在没有文本的情况下就不发送请求。然而,判断是否为空难以通过编程的方式明确定义,这一点还需要 GPT 参与判断。这个问题就变的棘手了。

    第五课:“上下文窗口”的说法容易误导人,变大的只有输入窗口

    鲜为人知的一点是:尽管 GPT-4 可能具有高达 128K Token 的输入窗口,但其输出窗口仍然只有区区 4K Token。称其为“上下文窗口”确实容易引起混淆。

    我们经常要求 GPT 返回一个 JSON 对象列表,而不是复杂结构。比如一个包含多个任务的JSON数组列表,每个任务都有一个名字和标签。

    而实际上,GPT 往往无法一次性给出超过10个项目的列表。即使尝试让它返回15个项目,也可能只有大约15%的成功率。

    起初,我们认为这是由于 4K Token 输出窗口限制导致的,但实际上我们在达到10个条目时,总体输出可能仅在700-800个 Token 左右,GPT 就会停止生成。

    当然,可以通过调整 Prompt 方式来换取更多的输出,比如分步请求,先让GPT生成一个任务,然后将其与原始提示合并再请求下一个任务,如此循环。但这样一来,我们就像是在和 GPT 玩传话游戏,同时还需要处理诸如 LangChain 等额外的集成问题。这意味着为了获得更长的输出列表,我们必须采用一种间接策略,并接受由此带来的复杂性和潜在误差积累的风险。

    第六课:向量数据库、RAG 以及 Embedding 模型对普通开发者大多用途有限

    每次我以为找到了向量数据库/RAG/Embedding 的杀手级应用场景时,最后都是一脸问号。我认为向量数据库和 RAG 这类技术主要是为搜索引擎而设计的,而且仅适用于真正的搜索场景,而不适用于那些看似与检索相关的其他应用领域。

    论据如下:

    1. 相关性没有明确的阈值划分。虽然市面上存在一些解决方案,并且可以自定义相关性阈值的启发式方法,但这些方法往往是不可靠的。这在我看来极大地削弱了 RAG 的优势,系统要么引入无关结果(降低检索质量),要么过于保守而错过重要结果。

    2. 将向量存储在一个专用且通常是封闭源代码的数据库中,远离所有其他业务数据,除非您的数据规模达到了如 Google 或 Bing 的程度,否则这种做法所带来的语境缺失并不值得。

    3. 对于非开放性的搜索(即不是针对整个互联网的大范围搜索),用户不太喜欢那些和自己没直接提到有关的搜索结果(users typically don’t like semantic searches that return things they didn’t directly type)。对于大多数企业应用程序内部的搜索功能来说,用户通常是领域的专家,他们不需要系统去猜测他们的意图,他们会直接表达自己的需求。

    综上所述,在我看来(尽管尚未经过严格测试),对于大多数搜索场景而言,LLM 的一个更有效的使用方式是:通过常规的补全提示将用户的搜索转化为多维度筛选搜索,甚至是构建更复杂的查询语句(甚至是可以转换为SQL查询)。但这并不是 RAG 原本的设计目的或优势所在。与其依赖于复杂的向量数据库和检索增强模型,直接利用 LLM 的自然语言理解和生成能力,更好地理解用户意图并转化成精准的查询形式,可能更为实用有效。

    第七课:hallucination(幻觉生成)现象基本不会发生

    我们所遇到的所有应用场景基本上都是“这里有一段文本,请从中提取信息”。通常情况下,如果你要求 GPT 从一段文本中给出提及的公司名称,它并不会随机生成一个不存在于文本中的公司名(除非文本中确实没有提及任何公司,此时会出现空假设问题)。

    如果你是一名工程师,你可能已经注意到:GPT 实际上并不会凭空编造代码,或者在重写一段代码中出现随机的拼写错误。当你让 GPT 生成某个功能时,它可能会臆想存在某个标准库函数,但这更多是因为它无法表达“我不知道”的状态,而非真正意义上的幻觉生成。

    然而,如果你的应用场景完全是“提供详尽的上下文细节,分析/总结/提取”,GPT 的表现则极为可靠。近期有很多产品发布正是围绕着这个具体应用场景来强调其性能。

    所以还是那句:Garbage in, garbage out(废料进,废品出)

    结论:这一切将何去何从?

    我没有准备一篇长篇大论作为回答(编辑备注:后来我还是写了一篇后续文章,详细阐述了我的更多思考,因为 HN 评论中有许多发人深思且颇具启发的观点),这里简单给出一些即兴问答:

    我们是否会实现AGI(通用人工智能)?

    不会,至少不会通过当前基于 Transformers 模型、互联网数据和巨额基础设施投入的方式实现。

    GPT-4真的有用还是纯属营销手段?

    它绝对是有用的。我们现在仍处于互联网发展的早期阶段。GPT-4 是否会让所有人都失业?并不会,它主要降低了之前只有谷歌等巨头才能触及的 ML/AI 技术门槛。

    你们试过Claude、Gemini等模型吗?

    哎呀,感觉一般般。说正经的,虽然我们没有做过严格的 A/B 测试,但我日常编码时有试用过这些模型,它们离 GPT-4 的感觉还有很大差距,尤其是在揣摩用户意图等微妙之处。

    如何紧跟最近LLMs/AI的发展动态?

    其实没有必要紧追每一个细节。我一直都在思考“苦涩的教训”,即普遍模型性能提升的重要性远超特定领域的优化。如果这个观点正确,你只需关注 GPT-5 何时发布即可,其余的一切都不那么重要。OpenAI 在这期间发布的其他项目(除了 Sora 等完全不同的东西),在某种程度上可以说是噪音。

    那么 GPT-5 何时发布?它又将有多好?

    我和其他人一样一直在观察 OpenAI 的动向。我认为我们将看到渐进式的改进,遗憾的是,我对 GPT-5 能“改变一切”并不抱太大希望。这其中有一些经济原理层面的原因:从 GPT-3 到 GPT-3.5,我曾认为模型性能随着训练强度翻倍就能提高约2.2倍,呈现超级线性增长。

    然而,实际情况并非如此,我们看到的是对数级别的改进。实际上,为了获得微小的性能提升,模型每个 Token 的训练速度和成本都在以指数级增长。

    如果真是这样,我们可能正处于一条帕累托最优曲线之上,而 GPT-4 可能已经接近最优状态:虽然我愿意为 GPT-4 支付比 GPT-3.5 高出20倍的价格,但对于 GPT-4 的任务集来说,我不太可能为了从 GPT-4 升级到 GPT-5 ,Token 费用增加20倍的成本。

    GPT-5也许会打破这一现状,但也有可能只是iPhone 5相对于iPhone 4那样的小幅升级。即便如此,那也不是损失!