MoreRSS

site iconbang修改

JSPatch 开发者。88年生于广东陆丰,毕业于华南师范大学,程序员,喜欢做东西。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

bang的 RSS 预览

DeepSeek R1 是怎么训练出来的?- R1 论文精读

2025-02-10 10:39:11

背景

DeepSeek 里程碑式的爆火,有必要学习下是怎么回事。

大语言模型的发展,之前一直是以预训练为主,虽然性能一直在提升,但主要是小修小补,跨越式的 GPT5 一直出不来。OpenAI 在 24 年 9 月发布的 o1 提出了新的路线:在预训练好的模型上,用强化学习做后训练,能显著提高模型推理能力,其效果在解数学、编码等问题上屠榜。

但 o1 只说了强化学习能让模型学会思维链的方式提升推理能力,其他的发现都没有公布,加上 o1 一直是期货,12月才正式推出,200美元一个月,普通人都用不上,充满神秘。

而 DeepSeek 自主研发了通过强化学习让模型学会思维链提升推理能力,性能逼近 o1,加上之前 DeepSeekV3 在预训练基础模型上的创新,推理成本也显著低于 o1,直接推出全民免费可用媲美 o1 的模型,甚至在一些中文语境下效果显著超过 o1,大众用户一用感受到 NB,业内人士震惊它的创新能力纷纷学习,美国人民恐慌中国 AI 的发展有超越美国引领技术潮流的可能性,结合大国叙事,爱国情怀,各种元素综合下各觉得都会乐此不疲地研究、使用、讨论,引爆全网。

接下来一步步精读下 DeepSeek-R1 的论文 《DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning》了解现象级 R1 模型是怎么做出来的。

R1 论文

DeepSeek R1 是基于预训练好的 DeepSeekV3 基础模型进行的后训练优化,V3 做了很多创新优化,很大降低了预训练模型和模型推理的成本,是 R1 的基础,这里先不讨论 V3,只看 R1 在 V3 基础上做了什么后训练。

在 DeepSeekR1 这篇论文里核心做的三个事:

  1. R1-Zero:首个证明只通过 RL 而不通过 SFT 就能让模型涌现推理能力。
  2. R1:新的强化学习后训练范式。
  3. 蒸馏:蒸馏显著提升小模型能力,小模型有潜力。

我们一个个具体看下这三个事。

R1-Zero

R1-Zero 证明了对已预训练好的模型,不需要经过 SFT,只需要纯粹的 RL,就能让模型涌现 CoT 推理能力。

SFT是监督式微调,也就是准备好一堆标注好的数据,让模型去学习拟合,可以粗略理解为刷题,大量的刷题学习能解决类似的题目;

RL是强化学习,只告诉模型什么是好的什么是不好的,过程中模型自己学习怎么达到目标,可以理解为不靠刷题,自己去理解探索数学和世界的规律,理论上灵活性强,上限更高,还有可能探索出人类未知的能力。

强化学习首次出圈是 AlphaGo,AlphaGo 先学习人类棋谱,再用强化学习自我对弈进化,而随后的 AlphaGo Zero 没有人类棋谱,只定义好围棋奖励规则,模型自己学习怎么下,达到更高的水平。R1-Zero 这命名也是致敬 Alpha-Zero,因为它们非常类似,脱离人类的指导自主发现规律提升智能。

为什么之前没人做到?

  1. 模型能力没达到一定阈值,仅通过强化学习没法涌现。
  2. 强化学习是种方法,过程中用什么算法做价值判定也很大程度影响效果
  3. o1 可能已经做了同样的探索和结果,也可能没有,它闭源不公开,而 DeepSeek 首次探索并公开了。

GRPO vs PPO

R1-Zero 最大的不同,是在强化学习中使用 GRPO 算法代替 PPO。GRPO 算法也是 DeepSeek 团队在 24 年 2 月《DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models》这篇论文提出的,其核心思想可以理解为两个字:内卷

简单来说,PPO 是用一个模型去评估当前输出的收益,模型觉得这个输出好,就更新参数往这方向靠近,类似四六级考试的判定,有个分数线,过了就好,不过就不好;GRPO 是让模型一次性输出一组数据,在这组数据中选得分最高的,让模型逐步靠近得分高的输出,类似高考的内卷选拔,你只要比同龄人好,就是好。

更具体的可以看这张图:

图上的一些概念:

  • Reference Model:预训练的大语言模型
  • Reward Model:奖励模型,给定输入和输出,给出得分。可以是基于神经网络训练的模型,使用人类标注数据做训练;也可以是规则模型,定死一些规则做打分。这里的输入是大语言模型一次完整的输出。
  • Value Model:对模型输出的每一步做一个预测,预测当前状态下后续能得到的收益。这里的输入是大语言模型每一次 token 的输出。
  • Policy Model:我们正在训练的大语言模型。base 是 Reference Model,训练过程中会不断更新参数,得到我们最终需要的模型。
  • GAE:Generalized Advantage Estimation 广义优势估计,评估每一个动作的好坏程度
  • q:输入 question
  • o:输出 output
  • r:模型完整的输出经过 Reward Model 计算后的分数
  • v:模型每一步的输出后的状态,经过 Value Model 计算后的价值估计值,用于评估当前 token 输出的好坏。
  • KL:Kullback-Leibler divergence KL散度,衡量新策略(当前训练中的模型输出的结果)和旧策略(base模型输出的结果)之间的差异,保障训练中策略更新幅度不要过大,保证稳定性。

PPO 用 Reward Model 计算大模型完整输出的奖励分数(r),过程中会跟原始模型的输出做对比(KL),避免偏离太远。大模型每步 token 输出,都会经过 Value Model 判定输出对后续奖励的期望值(v),经过 GAE 函数计算每步动作的好坏程度,更新我们在训练的大模型 Policy Model 参数。

GRPO 去掉了 Value Model,对每个输入(q)输出多个结果o1 o2 o3 …,奖励模型判断这些结果的好坏,从中选出最好的,更新模型参数。对算法和公式更详细的介绍,推荐这两个讲解:(1)(2)

GRPO 去掉 Value Model,带来了一些好处:

  1. 大大降低计算成本,少了价值模型,训练过程的内存占用、计算量、整体效率都更好。
  2. 更适合开放式的问题推理,不受限于价值模型判断的准确性。
  3. 整个流程也更简洁优美了。

Reward Model

从上图可以看到,主要需要设计的只剩 Reward Model,R1-Zero 设计了一个基于规则的 Reward Model,之所以用简单的规则而不是基于神经网络的模型做奖励判定,一个是不需要训练,简化了流程和降低成本,一个是如果用神经网络模型作为奖励判定,如果这个判定模型不够强,可能会让训练的大语言模型钻空子作弊,效果不一定好。

主要是两个简单的规则:

  1. 答案判断,如果是数学问题,会要求按格式要求输出最终答案,对比答案是否正确,对于 LeeCode 上的程序问题,会用编译器去判断能不能跑通对应的 test case。
  2. 格式判断,有没有按照指定的格式输出内容,也就是思维链的内容在<think></think>里,输出的内容在<answer></answer>里。

训练模板

训练时会输入预置 prompt,让模型按格式要求输出。这个模板有意设置得很简单,避免带偏模型,也不会告诉模型要进行反思性推理或者其他推理策略,确保可以观察到模型在强化学习过程中自发去发现怎样的推理方式才是更有效的。

现象&结果

训练过程中两个重要的现象:

1. 输出越来越长:随着训练量的推进,输出的长度稳步加长,推理能力也随之提升。这也验证了 OpenAI o1 里提到的 test-time scaling,也就是更长的推理输出能更好提升模型推理能力,模型在强化学习过程中自主发现了这个规律。

2. Aha moment:训练过程中模型学会了停下来重新评估前面的思考,而且使用拟人的口气进行了反思。强化学习过程中人类没有输入任何类似的反思引导,模型能自主进行这种反思,十分有趣。我理解为,在预训练的模型里,模型已经有这样的反思意识潜力,在训练的某次输出过程中偶然出现,而出现后的结果对复杂推理有帮助,强化学习的机制会持续鼓励这样的输出。

训练的结果,在推理相关的 benchmark上,基本达到 o1 的水平:

R1-Zero 有非常有趣的发现,即不做监督学习,仅对预训练模型进行强化学习这条路是 OK 的,最终的模型有媲美o1的推理能力,但当前这种方式训练出的 R1-Zero 实际对外使用会有些问题,主要是对人不友好,比如输出的内容中英文混杂,猜测是奖励模型并没有做这种人类阅读友好的奖励判定,模型没理由能做好这块。

R1

为了做出一个推理能力强,输出对人类友好,综合能力 OK 的模型,DeepSeek 另外训练了R1模型。

整个流程可以可以看这图,图片来自 这个视频,顺便推荐这个视频的讲解。

这里做了两个阶段的后训练,每个阶段都是先 SFT,再进行 RL,只是数据和目标不同。

一阶段

  1. 用少量的几千个包含了思维链推理的数据,对预训练模型做 SFT。虽然上面说 R1-Zero 不用 SFT 直接用 RL 也可以涌现推理思维链,但有少量精品数据做 SFT,RL 初期就能让模型生成可读性较好的结果,另外可能也能让训练冷启动效率更高,可以看这个视频感受下,在复现过程中,如果没有 SFT 直接 RL,前期要经历比较长的无效训练。
  2. 用跟训练 R1-Zero 同样的步骤,用 coding、数学、科学、逻辑 这些推理密集的数据,对 SFT 后的模型做强化学习。这次强化学习的奖励模型里增加了对语言一致性的奖励,减少模型输出中英夹杂的概率,虽然加了后发现对模型的推理能力会有些下降,但还能接受。

二阶段

  1. 用把上述强化学习后的模型,生成与推理相关的数据,并对这些数据择优,包括去掉语言一致性不行的case、去掉长篇大论的case、对同个问题生成多次并选择最好的一次,拿到质量相对好的60w个推理相关数据,加上另外收集标注的 20w 个跟推理无关,包含写作、事实性回答、翻译等数据,一起对模型再做一次SFT。R1 的中文输出很强,更大可能是跟这 20w 数据的质量高相关。
  2. 把上述 SFT 后的模型,再做一次强化学习。这次强化学习具体没有展开,主要是引导模型安全输出,过滤有害输出,以及再次提升推理能力的一次训练。这次的奖励模型额外再加上了对通用内容的奖励,在文科内容、日常问答上,应该也准备了一些质量比较高的 case 和奖励规则模型。

这几个步骤后,R1 就训练完成了,可以看到这个基于 V3 模型的后训练过程成本是很低的,数据量不大,但效果非常显著,特别是在编码和数学问题上,R1 相对 V3 提升了几个档次。

而这个过程,看起来也是可以 scale 的,可以用这个训好的模型继续多步生成一些case,择优组成新的数据,继续进行 SFT 和强化学习。

这条显著提升模型推理能力的后训练路跑通了,公开解了 o1 一直遮遮掩掩的强化学习路线,也展现了很好的低成本持续 scale up 的潜力。沿着这条路走能 scale 到什么程度还不太清楚,拭目以待。

蒸馏

R1 训完了,最终我们用的就是上述训练出来的模型,但这篇论文还没完,DeepSeek 的同学发现用上述 R1 训练过程中生成的 60w 推理相关的数据,以及 20w 的额外数据去对小模型做 SFT,小模型性能的提升非常明显。

看起来这一步纯粹是用质量更好的数据对小模型做SFT,只是这些数据大部分是 R1 生成的,相当于是蒸馏提取了 R1 的能力精华,拟合了 R1 的能力,这样也能给小模型带来较好的推理能力提升。

从分数看,这些小模型在数学和 coding 上的性能是不错的,如果1.5b在部分场景上能有媲美4o的效果,那端上大模型会迎来一波应用潮。但实际用起来没那么理想,试过 1.5B 模型基本不遵循指令,有些刷分的感觉,但这仅是做了初步的SFT 后的效果,在这基础上对小模型继续进行强化学习,可能整体能力能有提升,也是值得期待的点。

这里论文上还额外做了另一个实验,用类似 R1-Zero 的方法直接对小模型做强化学习,实验效果是相对用蒸馏数据做 SFT 的方式,效果差很多。一个猜测是小模型就是能力有限,直接用强化学习达到顿悟和性能提升,得基于模型本身能力足够才行。

到这里 R1 论文内容结束了,结尾部分提到后续会在多轮对话、json输出、语言混合、提示词问题、写工程代码弱这些问题上提升的展望,解决这些只是时间问题。

复现

这篇论文介绍了 R1 整个算法、训练流程和结果,但最核心的应该是数据,包括用于 R1-Zero 的数据是什么,数据量有多大,生成的 60w 数据具体是什么样的,标注的 20w 文科数据是什么,这是决定模型效果的另一个核心因素,DeepSeek 的中文效果出圈,应该很大程度还是标注的 20w 文科数据质量高,不确定 RL 带来的推理能力提升在文科这块上的帮助有多大。这些数据没有公开,友商要复刻出 DeepSeek 的效果没那么容易。

网上有不少开始复现 R1 和 R1-Zero 的开源项目研究,最大的是 huggingface 的 open-r1,也有学生在 3B 模型上小规模复现 R1-Zero 的开源项目 TinyZero

感受

  1. DeepSeek APP 上线 20天 DAU 超过 2000w,成为历史用户增长最快的 APP,更让人感受到通用 AI chatbot 没有护城河可言,无论是国内的豆包、kimi,还是 ChatGPT、Claude,只要有更好的模型出现,用户瞬间转移不带留恋的。搜索是有技术积累的壁垒的,社交有关系链,内容平台有内容壁垒,基于 chatbot 形态的产品,没看到有什么壁垒,用户数据没有作用,曾以为模型领先就是壁垒,OpenAI 可以凭借领先收高额费用和大部分用户的忠诚,DeepSeek 这波又打破了这种壁垒,领先者无法保证自己一直领先。
  2. DeepSeek 带来中国自信,曾经认为,类似 AICoding 这种全球无差异竞争的产品,国内同学们怎么搞得过海外那些清一色 MIT 天才搞的产品?DeepSeek 的成功叙事给了这些直面竞争产品很大的信心,这种信心下中概股都被疯狂拉动,还是很让人激动的。

500 美元一个月的 Devin 是怎么实现的

2025-01-19 17:58:23

使用

这两天有机会体验了下 Devin,感受到一些小小的震撼。

虽然之前已经用过 cursor 和 windsurf,它们用的模型都一样,理论上能完成的任务和智力是差不多的,但用 Devin 感受还是不太一样,有种 AGI 已经实现了的感觉。

Cursor 和 Devin 核心区别是交互范式,Cursor 是 Copilot,在你工作写代码过程中,实时辅助完成编程任务,而 Devin 是一个员工,交给他复杂任务后不用管它,它主动帮你搞定。可能现在这两者完成的很多任务是一致的,但体验有差异。

我试用的其中一个任务,是扔给它开源项目 JSPatch 的 github 地址,告诉它找个 issue 修一下。它会分解任务逐步执行,包括:

  1. 访问 github 网站,浏览issue列表,随机看几个 issue 详情
  2. 挑了个 block 相关 issue,浏览项目相关文件,寻找与 issue 相关联的代码文件,制定修复计划
  3. 写测试用例 → 修改项目代码尝试修复 issue → 跑用例验证(没 iOS 环境没跑起来) → commit 和提交 PR

这个过程是自动和异步的,它跑在虚拟机里,不需要你提供环境,不需要盯着它,它会自己去调研怎么完成这个任务,做完了会来告诉你(如果用 slack,这个体验过程更顺畅,@它下达任务,任务完成slack回复),这跟给一个员工布置任务,等他做完验收结果的体验很一致。

畅想

现在 Devin 解决问题的能力肯定还有限,真正用下来磕磕碰碰很多任务还是完成不好,现在的模型能力下 Cursor 这种 Copilot 的形态是更实用的,但未来理想的形态肯定是 Devin 这种“员工”形态,因为可以解放注意力,无限扩展同一时间能做的事。

可以想象,这种形态未来的优化速度会很乐观:

  1. 基础模型的思维规划能力还没收敛,从sonet o1 到 o3 可以看到还在快速提升
  2. 即使基础模型能力放缓了,模型在领域上的调优还有很大空间。
  3. 更多工具的接入,也能带来更强大的能力和体验。

以及,模型成本必然比摩尔定律更快速的下降,Devin 会持续用最好的模型最贵的方案,但今天 500美金的效果,一年后成本可能只要5美金就能做到。

Devin 所实现的概念早在 23年初 AutoGPT 就提出,只是当时模型能力还不具备,Claude Sonnet / GPT 4o / GPT o1 这种级别推理能力的模型出现后,才具备可用性,Devin 是实现了这个概念下初步可用的雏形,让人看到这个方向已经初步 ready,剩下的就是持续往这个方向优化和扩展了,Devin 确实称得上是数字员工的开端,设计师agent,交易员agent,数据分析师agent,电商agent,预计会陆续出现。

原理

Devin 是怎么实现的?

有个开源项目 OpenHands(前身 OpenDevin),尝试用开源社区的方式去构建类似 Devin 做的事,虽然能力和效果上不能完全对标 Devin,但可以看个基本雏形。相关论文:https://arxiv.org/abs/2407.16741

文中的这张架构图,可以比较好描述 OpenHands 是怎么做:

三个主要部分:

  1. Event Stream:记录每一步指令和执行结果,Action 是指令,Observation 是指令对应的工具执行的结果,Action 和 Observation 最终都是以纯文本记录结果。
  2. Runtime:程序运行在独立的 docker 容器里,提供一些工具给 Agent 调用执行,当前包括 Python 运行、终端命令行、浏览器
  3. Agent:把 Event Stream 里的所有内容作为上下文,输入到 LLM 推理下一步Action。

还有个关键点没有在图上画出来,为什么把 Event Stream 的所有内容输入到 LLM,LLM 就会按照要求推理出下一步 Action?因为输入到 LLM 的除了 Event Stream 的上下文,还有 Agent 本身的 Prompt,这个 Prompt 描述一些处理原则、当前环境、可用的工具、每个工具的参数、预期输出的格式等,以及还配套了一个示例,指引模型按要求输出。这个 Prompt 本身贴在了文末。

我们跟着图上 Event Stream 的示例,跑一下这个流程:

  1. 用户输入一个任务命令:“Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?” 到 Event Stream
  2. 当前 Event Stream 只有这一条命令内容,输入到 Agent,Agent 会拿它跟上述预置 prompt 一起输入给 LLM,LLM 会推理出下一步是调用 Python 去创建 app.py 文件,输出 Action 指令到 Event Stream。
  3. 工具 IPython 对应的 Observation 监听到这个指令,在 Runtime 环境执行了这个命令,输出执行结果app.py created” 记录在 Event Stream 上
  4. Event Stream 接收到 Observation 新的执行结果后,Agent 程序会自动继续把整个 Event Stream 记录连同预置 Prompt 输入给 LLM,推测下一步 Action。推测出来的下一步 Action 是把一大段代码用调用 python 命令的方式写入刚才创建的文件
  5. IPython Observation 接收到这个 Action,在 Runtime 环境执行命令,输出执行结果到 Event Stream。

接下来就是不断的循环:Action 驱动 Observation 用工具做处理 → 处理结果输出到 Event Stream → Event Stream 拿所有前文内容到 LLM 输出下一步 Action → 驱动Observation 用工具做处理…

后面的6-9步用了命令行工具和浏览器工具,流程是一样的。这个循环流程什么时候结束?有一个特殊的 Action 叫 finish,如果一个任务 LLM 认为完成了,就会输出调用 finish Action,程序接收到就退出循环,等用户下一步输入。

整体就是自动循环让 LLM 预测下一步动作 → Agent 程序调用工具执行动作 的过程。补充一些点:

  1. 整个项目不涉及模型训练,纯工程方案,使用通用 LLM 模型,可以配置 Claude/GPT/Deepseek 等,不过可预见的演进是根据用户使用数据去优化模型以达到领域内更好的效果。
  2. 这里没有实现像 Devin 在输出 Action 前先会规划好任务的步骤再一步步执行,但要在上述这个系统加上这个规划能力预计不难。
  3. 项目使用 BrowserGym 去和浏览器交互,Agent 对浏览器的操作和识别是另一个大课题,有单独的 benchmark 多种方案,待调研。
  4. 随着 Event Stream 里链路的不断增加,上下文会越来越长,到一定程度 openhands 会做两种处理,一种是压缩内容,把前面的上下文发给 llm 精练总结,用更简短的内容作为后续的上下文。另一种是让 LLM 对过去的内容进行重要度排序,只选择对预测下一步重要的几个记录作为上下文,具体逻辑在 condenser.py 里。更长的上下文会用向量数据库 ChromeDB 存储。

附:预置prompt

You are OpenHands agent, a helpful AI assistant that can interact with a computer to solve tasks.
<IMPORTANT>
//
* If user provides a path, you should NOT assume it's relative to the current working directory. Instead, you should explore the file system to find the file before working on it.
* When configuring git credentials, use "openhands" as the user.name and "openhands@all-hands.dev" as the user.email by default, unless explicitly instructed otherwise.
* The assistant MUST NOT include comments in the code unless they are necessary to describe non-obvious behavior.
RuntimeInfo(available_hosts={'http://localhost:54090': 54090, 'http://localhost:55602': 55602})
</IMPORTANT>

<RUNTIME_INFORMATION>
The user has access to the following hosts for accessing a web application,
each of which has a corresponding port:
* http://localhost:54090 (port 54090)
* http://localhost:55602 (port 55602)

When starting a web server, use the corresponding ports. You should also
set any options to allow iframes and CORS requests.
</RUNTIME_INFORMATION>
You have access to the following functions:

---- BEGIN FUNCTION #1: execute_bash ----
Description: Execute a bash command in the terminal.
* Long running commands: For commands that may run indefinitely, it should be run in the background and the output should be redirected to a file, e.g. command = `python3 app.py > server.log 2>&1 &`.
* Interactive: If a bash command returns exit code `-1`, this means the process is not yet finished. The assistant must then send a second call to terminal with an empty `command` (which will retrieve any additional logs), or it can send additional text (set `command` to the text) to STDIN of the running process, or it can send command like `C-c` (Ctrl+C) to interrupt the process.

Parameters:
  (1) command (string, required): The bash command to execute. Can be empty string to view additional logs when previous exit code is `-1`. Can be `C-c` (Ctrl+C) to interrupt the currently running process.
---- END FUNCTION #1 ----

---- BEGIN FUNCTION #2: finish ----
Description: Finish the interaction when the task is complete OR if the assistant cannot proceed further with the task.
No parameters are required for this function.
---- END FUNCTION #2 ----

---- BEGIN FUNCTION #3: web_read ----
Description: Read (convert to markdown) content from a webpage. You should prefer using the `web_read` tool over the `browser` tool, but do use the `browser` tool if you need to interact with a webpage (e.g., click a button, fill out a form, etc.).

You may use the `web_read` tool to read content from a webpage, and even search the webpage content using a Google search query (e.g., url=`https://www.google.com/search?q=YOUR_QUERY`).

Parameters:
  (1) url (string, required): The URL of the webpage to read. You can also use a Google search query here (e.g., `https://www.google.com/search?q=YOUR_QUERY`).
---- END FUNCTION #3 ----

---- BEGIN FUNCTION #4: browser ----
Description: Interact with the browser using Python code. Use it ONLY when you need to interact with a webpage.

See the description of "code" parameter for more details.

Multiple actions can be provided at once, but will be executed sequentially without any feedback from the page.
More than 2-3 actions usually leads to failure or unexpected behavior. Example:
fill('a12', 'example with "quotes"')
click('a51')
click('48', button='middle', modifiers=['Shift'])

Parameters:
  (1) code (string, required): The Python code that interacts with the browser.

The following 15 functions are available. Nothing else is supported.

goto(url: str)
    Description: Navigate to a url.
    Examples:
        goto('http://www.example.com')

go_back()
    Description: Navigate to the previous page in history.
    Examples:
        go_back()

go_forward()
    Description: Navigate to the next page in history.
    Examples:
        go_forward()

noop(wait_ms: float = 1000)
    Description: Do nothing, and optionally wait for the given time (in milliseconds).
    You can use this to get the current page content and/or wait for the page to load.
    Examples:
        noop()

        noop(500)

scroll(delta_x: float, delta_y: float)
    Description: Scroll horizontally and vertically. Amounts in pixels, positive for right or down scrolling, negative for left or up scrolling. Dispatches a wheel event.
    Examples:
        scroll(0, 200)

        scroll(-50.2, -100.5)

fill(bid: str, value: str)
    Description: Fill out a form field. It focuses the element and triggers an input event with the entered text. It works for <input>, <textarea> and [contenteditable] elements.
    Examples:
        fill('237', 'example value')

        fill('45', 'multi-line
example')

        fill('a12', 'example with "quotes"')

select_option(bid: str, options: str | list[str])
    Description: Select one or multiple options in a <select> element. You can specify option value or label to select. Multiple options can be selected.
    Examples:
        select_option('a48', 'blue')

        select_option('c48', ['red', 'green', 'blue'])

click(bid: str, button: Literal['left', 'middle', 'right'] = 'left', modifiers: list[typing.Literal['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']] = [])
    Description: Click an element.
    Examples:
        click('a51')

        click('b22', button='right')

        click('48', button='middle', modifiers=['Shift'])

dblclick(bid: str, button: Literal['left', 'middle', 'right'] = 'left', modifiers: list[typing.Literal['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']] = [])
    Description: Double click an element.
    Examples:
        dblclick('12')

        dblclick('ca42', button='right')

        dblclick('178', button='middle', modifiers=['Shift'])

hover(bid: str)
    Description: Hover over an element.
    Examples:
        hover('b8')

press(bid: str, key_comb: str)
    Description: Focus the matching element and press a combination of keys. It accepts the logical key names that are emitted in the keyboardEvent.key property of the keyboard events: Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ, etc. You can alternatively specify a single character you'd like to produce such as "a" or "#". Following modification shortcuts are also supported: Shift, Control, Alt, Meta, ShiftLeft, ControlOrMeta. ControlOrMeta resolves to Control on Windows and Linux and to Meta on macOS.
    Examples:
        press('88', 'Backspace')

        press('a26', 'ControlOrMeta+a')

        press('a61', 'Meta+Shift+t')

focus(bid: str)
    Description: Focus the matching element.
    Examples:
        focus('b455')

clear(bid: str)
    Description: Clear the input field.
    Examples:
        clear('996')

drag_and_drop(from_bid: str, to_bid: str)
    Description: Perform a drag & drop. Hover the element that will be dragged. Press left mouse button. Move mouse to the element that will receive the drop. Release left mouse button.
    Examples:
        drag_and_drop('56', '498')

upload_file(bid: str, file: str | list[str])
    Description: Click an element and wait for a "filechooser" event, then select one or multiple input files for upload. Relative file paths are resolved relative to the current working directory. An empty list clears the selected files.
    Examples:
        upload_file('572', '/home/user/my_receipt.pdf')

        upload_file('63', ['/home/bob/Documents/image.jpg', '/home/bob/Documents/file.zip'])

---- END FUNCTION #4 ----

---- BEGIN FUNCTION #5: execute_ipython_cell ----
Description: Run a cell of Python code in an IPython environment.
* The assistant should define variables and import packages before using them.
* The variable defined in the IPython environment will not be available outside the IPython environment (e.g., in terminal).

Parameters:
  (1) code (string, required): The Python code to execute. Supports magic commands like %pip.
---- END FUNCTION #5 ----

---- BEGIN FUNCTION #6: str_replace_editor ----
Description: Custom editing tool for viewing, creating and editing files
* State is persistent across command calls and discussions with the user
* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep
* The `create` command cannot be used if the specified `path` already exists as a file
* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`
* The `undo_edit` command will revert the last edit made to the file at `path`

Notes for using the `str_replace` command:
* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!
* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique
* The `new_str` parameter should contain the edited lines that should replace the `old_str`

Parameters:
  (1) command (string, required): The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`, `undo_edit`.
Allowed values: [`view`, `create`, `str_replace`, `insert`, `undo_edit`]
  (2) path (string, required): Absolute path to file or directory, e.g. `/workspace/file.py` or `/workspace`.
  (3) file_text (string, optional): Required parameter of `create` command, with the content of the file to be created.
  (4) old_str (string, optional): Required parameter of `str_replace` command containing the string in `path` to replace.
  (5) new_str (string, optional): Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert.
  (6) insert_line (integer, optional): Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`.
  (7) view_range (array, optional): Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.
---- END FUNCTION #6 ----


If you choose to call a function ONLY reply in the following format with NO suffix:

<function=example_function_name>
<parameter=example_parameter_1>value_1</parameter>
<parameter=example_parameter_2>
This is the value for the second parameter
that can span
multiple lines
</parameter>
</function>

<IMPORTANT>
Reminder:
- Function calls MUST follow the specified format, start with <function= and end with </function>
- Required parameters MUST be specified
- Only call one function at a time
- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after.
- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls


----------

Here's a running example of how to perform a task with the provided tools.

--------------------- START OF EXAMPLE ---------------------

USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.

ASSISTANT: Sure! Let me first check the current directory:
<function=execute_bash>
<parameter=command>
pwd && ls
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
/workspace
openhands@runtime:~/workspace$

ASSISTANT: There is no `app.py` file in the current directory. Let me create a Python file `app.py`:
<function=str_replace_editor>
<parameter=command>create</parameter>
<parameter=path>/workspace/app.py</parameter>
<parameter=file_text>
from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    numbers = list(range(1, 11))
    return str(numbers)

if __name__ == '__main__':
    app.run(port=5000)
</parameter>
</function>

USER: EXECUTION RESULT of [str_replace_editor]:
File created successfully at: /workspace/app.py

ASSISTANT: I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
<function=execute_bash>
<parameter=command>
python3 app.py > server.log 2>&1 &
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1] 121
[1]+  Exit 1                  python3 app.py > server.log 2>&1

ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
<function=execute_bash>
<parameter=command>
cat server.log
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/app.py", line 2, in <module>
    from flask import Flask
ModuleNotFoundError: No module named 'flask'

ASSISTANT: Looks like the server crashed because the `flask` module is not installed. Let me install the `flask` module for you:
<function=execute_bash>
<parameter=command>
pip3 install flask
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
Defaulting to user installation because normal site-packages is not writeable
Collecting flask
  Using cached flask-3.0.3-py3-none-any.whl (101 kB)
Collecting blinker>=1.6.2
  Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
Collecting Werkzeug>=3.0.0
  Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
Collecting click>=8.1.3
  Using cached click-8.1.7-py3-none-any.whl (97 kB)
Collecting itsdangerous>=2.1.2
  Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Requirement already satisfied: Jinja2>=3.1.2 in /home/openhands/.local/lib/python3.10/site-packages (from flask) (3.1.3)
Requirement already satisfied: MarkupSafe>=2.0 in /home/openhands/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0

ASSISTANT: The `flask` module is installed successfully. Let me run the Python file again:
<function=execute_bash>
<parameter=command>
python3 app.py > server.log 2>&1 &
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1] 124

ASSISTANT:
Let me check the server log again:
<function=execute_bash>
<parameter=command>
cat server.log
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
* Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

ASSISTANT:
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!

USER: Now kill the server, make it display the numbers in a table format.

ASSISTANT:
Sure! Let me stop the server first:
<function=execute_bash>
<parameter=command>
kill 124
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1]+  Terminated              python3 app.py > server.log 2>&1

ASSISTANT:
Now let me display the numbers in a table format:
<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/app.py</parameter>
<parameter=old_str>return str(numbers)</parameter>
<parameter=new_str>return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'</parameter>
</function>

USER: EXECUTION RESULT of [str_replace_editor]:
The file /workspace/app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/app.py:
     3
     4  @app.route('/')
     5  def index():
     6      numbers = list(range(1, 11))
     7      return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
     8
     9  if __name__ == '__main__':
    10      app.run(port=5000)
Review the changes and make sure they are as expected. Edit the file again if necessary.

ASSISTANT:
Running the updated file:
<function=execute_bash>
<parameter=command>
<parameter=command>
python3 app.py > server.log 2>&1 &
</parameter>
</function>

USER: EXECUTION RESULT of [execute_bash]:
[1] 126

ASSISTANT:
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Let me know if you have any further requests!
<function=finish>
</function>

--------------------- END OF EXAMPLE ---------------------

Do NOT assume the environment is the same as in the example above.

附:任务 Prompt示例

以下是真实执行一个任务时,循环添加输入 LLM 的 Prompt case:

--------------------- NEW TASK DESCRIPTION ---------------------
创建一个可以调用deepseek模型的程序,并运行测试确保可用
--------------------- END OF NEW TASK DESCRIPTION ---------------------

PLEASE follow the format strictly! PLEASE EMIT ONE AND ONLY ONE FUNCTION CALL PER MESSAGE.


----------

Let me start by checking the current directory and listing its contents:

<function=execute_bash>
<parameter=command>pwd && ls</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
/workspace
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

I see we are in the `/workspace` directory. Let's create a Python file that can call the DeepSeek model. First, I'll create a new file called `deepseek_app.py`:

<function=str_replace_editor>
<parameter=command>create</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=file_text>
from deepseek import DeepSeekModel

def call_deepseek_model(input_text):
    model = DeepSeekModel()
    response = model.generate(input_text)
    return response

if __name__ == "__main__":
    test_input = "Hello, DeepSeek!"
    result = call_deepseek_model(test_input)
    print("Model Response:", result)
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
File created successfully at: /workspace/deepseek_app.py

----------

Now, let's check if the `deepseek` module is installed. If not, we'll need to install it:

<function=execute_bash>
<parameter=command>pip3 show deepseek</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
WARNING: Package(s) not found: deepseek
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

The `deepseek` module is not installed. Let me install it using pip:

<function=execute_bash>
<parameter=command>pip3 install deepseek</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Collecting deepseek
  Downloading deepseek-1.0.0-py3-none-any.whl.metadata (3.9 kB)
Requirement already satisfied: requests in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from deepseek) (2.32.3)
Requirement already satisfied: charset-normalizer<4,>=2 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (3.4.1)
Requirement already satisfied: idna<4,>=2.5 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (2.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages (from requests->deepseek) (2024.12.14)
Downloading deepseek-1.0.0-py3-none-any.whl (4.5 kB)
Installing collected packages: deepseek
Successfully installed deepseek-1.0.0
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

The `deepseek` module is now installed. Let's run the `deepseek_app.py` program to test if it works:

<function=execute_bash>
<parameter=command>python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 1, in <module>
    from deepseek import DeepSeekModel
ImportError: cannot import name 'DeepSeekModel' from 'deepseek' (/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/__init__.py)
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

It seems the `DeepSeekModel` class is not available in the `deepseek` module. Let me check the documentation or available classes in the `deepseek` module:

<function=execute_bash>
<parameter=command>python3 -c "import deepseek; print(dir(deepseek))"</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
['DeepSeekAPI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'api', 'const']
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

The `deepseek` module provides a `DeepSeekAPI` class instead of `DeepSeekModel`. Let me update the `deepseek_app.py` file to use the correct class:

<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=old_str>
from deepseek import DeepSeekModel

def call_deepseek_model(input_text):
    model = DeepSeekModel()
    response = model.generate(input_text)
    return response
</parameter>
<parameter=new_str>
from deepseek import DeepSeekAPI

def call_deepseek_model(input_text):
    api = DeepSeekAPI()
    response = api.generate(input_text)
    return response
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
The file /workspace/deepseek_app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/deepseek_app.py:
     1        from deepseek import DeepSeekAPI
     2        
     3        def call_deepseek_model(input_text):
     4            api = DeepSeekAPI()
     5            response = api.generate(input_text)
     6            return response
     7        
     8        if __name__ == "__main__":
     9            test_input = "Hello, DeepSeek!"
    10            result = call_deepseek_model(test_input)
Review the changes and make sure they are as expected. Edit the file again if necessary.

----------

Now that the file is updated, let's run the program again to test if it works:

<function=execute_bash>
<parameter=command>python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 10, in <module>
    result = call_deepseek_model(test_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/deepseek_app.py", line 4, in call_deepseek_model
    api = DeepSeekAPI()
          ^^^^^^^^^^^^^
  File "/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/api.py", line 9, in __init__
    raise ValueError("DEEPSEEK_API_KEY is missing")
ValueError: DEEPSEEK_API_KEY is missing
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

The program requires an API key to use the `DeepSeekAPI`. Let me update the program to include the API key as an environment variable. First, I'll modify the `deepseek_app.py` file:

<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=old_str>
from deepseek import DeepSeekAPI

def call_deepseek_model(input_text):
    api = DeepSeekAPI()
    response = api.generate(input_text)
    return response
</parameter>
<parameter=new_str>
import os
from deepseek import DeepSeekAPI

def call_deepseek_model(input_text):
    api_key = os.getenv("DEEPSEEK_API_KEY")
    if not api_key:
        raise ValueError("Please set the DEEPSEEK_API_KEY environment variable.")
    api = DeepSeekAPI(api_key=api_key)
    response = api.generate(input_text)
    return response
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
The file /workspace/deepseek_app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/deepseek_app.py:
     1        import os
     2        from deepseek import DeepSeekAPI
     3        
     4        def call_deepseek_model(input_text):
     5            api_key = os.getenv("DEEPSEEK_API_KEY")
     6            if not api_key:
     7                raise ValueError("Please set the DEEPSEEK_API_KEY environment variable.")
     8            api = DeepSeekAPI(api_key=api_key)
     9            response = api.generate(input_text)
    10            return response
    11        
    12        if __name__ == "__main__":
    13            test_input = "Hello, DeepSeek!"
    14            result = call_deepseek_model(test_input)
Review the changes and make sure they are as expected. Edit the file again if necessary.

----------

Now, let's set the `DEEPSEEK_API_KEY` environment variable and run the program again. Please provide the API key for DeepSeek, and I will set it up for you. If you don't have one, you may need to sign up for an API key from DeepSeek's official website.

<function=execute_bash>
<parameter=command>export DEEPSEEK_API_KEY=your_api_key_here && python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 14, in <module>
    result = call_deepseek_model(test_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/deepseek_app.py", line 9, in call_deepseek_model
    response = api.generate(input_text)
               ^^^^^^^^^^^^
AttributeError: 'DeepSeekAPI' object has no attribute 'generate'
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

It seems the `DeepSeekAPI` class does not have a `generate` method. Let me check the available methods in the `DeepSeekAPI` class:

<function=execute_bash>
<parameter=command>python3 -c "from deepseek import DeepSeekAPI; print(dir(DeepSeekAPI))"</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_post_request', 'chat_completion', 'completion_impl', 'fim_completion', 'get_models', 'user_balance']
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

----------

The `DeepSeekAPI` class provides methods like `chat_completion`, `fim_completion`, and `get_models`. Let's update the `deepseek_app.py` file to use the `chat_completion` method instead of `generate`:

<function=str_replace_editor>
<parameter=command>str_replace</parameter>
<parameter=path>/workspace/deepseek_app.py</parameter>
<parameter=old_str>
api = DeepSeekAPI(api_key=api_key)
    response = api.generate(input_text)
    return response
</parameter>
<parameter=new_str>
api = DeepSeekAPI(api_key=api_key)
    response = api.chat_completion(input_text)
    return response
</parameter>
</function>

----------

EXECUTION RESULT of [str_replace_editor]:
The file /workspace/deepseek_app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/deepseek_app.py:
     4        def call_deepseek_model(input_text):
     5            api_key = os.getenv("DEEPSEEK_API_KEY")
     6            if not api_key:
     7                raise ValueError("Please set the DEEPSEEK_API_KEY environment variable.")
     8            api = DeepSeekAPI(api_key=api_key)
     9            response = api.chat_completion(input_text)
    10            return response
    11        
    12        if __name__ == "__main__":
    13            test_input = "Hello, DeepSeek!"
    14            result = call_deepseek_model(test_input)
Review the changes and make sure they are as expected. Edit the file again if necessary.

----------

Now, let's run the program again with the updated method. Please provide the `DEEPSEEK_API_KEY` environment variable:

<function=execute_bash>
<parameter=command>export DEEPSEEK_API_KEY=your_api_key_here && python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Traceback (most recent call last):
  File "/workspace/deepseek_app.py", line 14, in <module>
    result = call_deepseek_model(test_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/deepseek_app.py", line 9, in call_deepseek_model
    response = api.chat_completion(input_text)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/api.py", line 62, in chat_completion
    response = self._post_request(API_CHAT_COM, payload, stream)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openhands/poetry/openhands-ai-5O4_aCHf-py3.12/lib/python3.12/site-packages/deepseek/api.py", line 25, in _post_request
    raise Exception(f"HTTP Error {response.status_code}: {response.text}")
Exception: HTTP Error 401: {"error":{"message":"Authentication Fails (no such user)","type":"authentication_error","param":null,"code":"invalid_request_error"}}
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 1]

----------

The error indicates that the provided API key is invalid or does not correspond to a valid user. To proceed, you will need to:

1. Obtain a valid API key from DeepSeek's official platform.
2. Ensure the API key is correctly set in the environment.

Once you have a valid API key, you can set it and run the program again. Let me know if you need further assistance!

----------

key: *** ///在这里输入了key,隐藏

----------

Thank you for providing the API key. Let's set the environment variable and run the program again:

<function=execute_bash>
<parameter=command>export DEEPSEEK_API_KEY=sk-4a727097b1ef4e61a3444ec96e1e0db0 && python3 deepseek_app.py</parameter>
</function>

----------

EXECUTION RESULT of [execute_bash]:
Model Response: Hello! How can I assist you today? 😊
[Python Interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.12/bin/python]
[Command finished with exit code 0]

2024

2024-12-31 22:40:05

又到了 31 号这天,回想起来 24 年过得很快,回顾过去一些事,有的感觉就前几周的事,一看原来已经过去四五个月。照例在这天写篇生活记录。

学习

去年说今年要学下AI,总算有点进展,虽然进度不理想,但也算迈开了步伐。

学习如果没有一些事项引导,就很难进行,最好的学习方式是直接在做的过程中学,真正进去做这个事的过程中会不断遇到一些问题,解决这些问题过程就是很自然的逐渐学习和深入的过程。

没这个条件的话,就退而求其次,用分享输出的方式引导学习,所以我时隔四五年不写博客,今年又开始写了,主要就是让我的学习有个地方做完整的记录,有相应的引导。

在软件工程时代,一个功能能不能实现,原理链路大概是怎样,基本都能知道,上一轮以推荐为主的 AI 也大致能了解原理。而这次的生成式 AI 太魔法了,应用范围和影响力也远超过去,完全不了解它是怎么回事很让人难受,有种跟不上时代的感觉,学一些皮毛后感觉好一些。

不过最好还是机会边做边学,希望25年能多一些深入实践,让自己在创造的状态里学习。

今年 AI 继续快速发展,有几个时刻对 AI 的能力和带来的体验还是很震惊的:

  1. AI Coding:第一次用 cursor 的补全能力、compose 创建多个文件能力,第一次用 windsurf 直接创建项目跑起来,都让人震惊。虽然一些微调和 bug AI 还不能解决得很好,但做一个 demo 完全没问题,对程序员来说,也可以完全忽略不同编程语言/平台特性前期学习的障碍,无差别上手码代码。AI Coding 是今年完全跑出来的赛道,大家都看到了它的机会,以后没有一个程序员不需要 AI 辅助 Coding,而且 AI Coding 还能不断转换非程序员群体进来构建任意软件交互功能,想象空间很大,可以预料 25 年这个赛道会比百团大战更热闹。
  2. Prompt 的能力:看到 Glif 的 AI 梗图生成很惊讶,只需要输入工种比如“程序员”,LLM 就能生成信息量极大的针对这个工种的梗,当时感觉自己确实是 LLM 小白,LLM 本身存储压缩的信息量是巨大的。
  3. 视频生成:年初的 sora 震惊许多人,但第一次上手用视频生成才会真实感受到冲击,第一次用即梦的视频S2.0模型时,超高清和稳定的人脸运动属实惊艳,很容易做画面感很好的视频,一直想给小说《十日终焉》配个AI生成的小片,终于圆了这个念想。
  4. NotebookLM:生成的真实对话声音的真实性实在太强,一点AI味都没有。文字/图片/音频/视频,以后 AI 可以自由在多个模态之间自由切换,只要有源头内容,各种形态的展示都不会是问题。

播客

前两天小宇宙推送年度总结,看到今年我在上面听的总时长时很惊讶,166个小时,我已经成为播客的忠实用户了。想想也合理,一年200多个工作日,如果每个工作日上下班开车一小时听一小时,也有200多个小时。

网络上高质量内容一直在切换形态,从最开始的 blog,到后来的公众号知乎,在公众号和知乎泛化后,高质量的内容逐渐转移到播客上了,在海外更是这样,特别是AI前沿信息基本都在播客上,国内也逐渐是这个趋势,今年特别明显。

除了听几个AI相关的博客,听得最多的是半拿铁,内容做得太好了,有几个很喜欢的系列,包括任天堂往事、日漫史、改革开放系列。

改革开放系列让人看到整个路径的曲折,路线反复的历史背景,一大波有担当的政府官员推动历史,克服极大的困难才让改革开放走下去,听到最近一期成事后的片尾曲《春天的故事》时,竟有些动容。

日漫史和任天堂往事,也跟着它回忆了一下这些从小到大的精神粮食,任天堂不断把游戏欢乐带给更广泛的人,强大的创新能力,至暗时刻也能翻身,不愧是游戏界的神。日本漫画家没日没夜奋斗完成作品的干劲,这种投入强度和一根筋的热情,也只有日本能把漫画行业做出来。

游戏

今年玩过最好的游戏当然是《黑神话悟空》,远超预期,独有的中国文化之美,光看画面就是很大的享受,尤其是小西天,剧情音乐也都很顶,黄风岭陕西说唱、《壁上观》都很美,能玩到这样的游戏真好。不足的是难度没有循序渐进,开头的白衣居士卡得我差点放弃,后面反而障碍不多,但偶尔被 boss 卡还是挺难受。另外玩游戏过程中几乎感受不到剧情,这么顶的剧情全靠外部解说,也不知是不是个传播策略。

看了黑神话悟空相关的访谈,一个体制以外冒出来的好的作品,都是极具个人色彩的,精品是靠一小撮人投入极大的热情和毅力创造出来的。有黑神话悟空,国产 3A 大作有希望,但5年-10年内还会不会有其他精品出来,不太乐观,只能肯定的是大公司不会有。

物品

  1. 九号电动车,体验超预期,一线互联网水准的 APP 体验,停车自动锁,坐上自动解锁,二轮电动也跟上了智能电动汽车的步伐,生活中再也没有钥匙这个东西了。
  2. Vision Pro,年初在同事那里体验了一波 Vision Pro,效果很震撼,特别是演示的恐龙视频,3D 全景高分辨率带来真正身临其境的感觉,但它更像是一个概念机,长板很长,短板很短,希望苹果能坚持继续解决佩戴舒适度、成本价格问题,真正把这个领域做起来,还是很期待 Vision Pro 的体验能成为日常的时代,不知道10年后能不能达成,掐指一算 Google glass 都已经12年了,穿戴式设备的进度十分缓慢。
  3. meta 眼镜,Vision Pro 没那么快普及,但可以预见的是类 meta 眼镜接下来一年会爆发,可能是今年最吸引人的硬件,随时抓拍记录生活,叠加AI对话的概念,除了拍摄和AI,还有望部分代替掉 Airpods,绝对的爆款,期待国产卷出性价比体验都更高的版本,迫不及待想买了。
  4. Airpods 非入耳式的降噪,在飞机上效果极佳,至今每一代的Airpods 基本都买了,仍是非常值得入手的产品。
  5. 米家智能家居,今年终于出了独立的电动窗帘,让我这没留充电位置的窗帘也能支持电动了,外加捣鼓了玄关灯、灯带,可惜楼盘自带的“智能“系统阻止了我进一步捣鼓,如果有机会装修房子,一定让全家都配好米家。

旅游

去了银川中卫,香格里拉,西双版纳,哈尔滨雪乡,成都(团建),中山,长沙。

银川中卫沙漠星星酒店,感受非常好,就算在五一假期,也是一片片空旷的沙漠没什么人,小朋友骑骑骆驼玩玩沙,很惬意。

雪乡在我印象中是前几年坑人口碑很差的形象,实际上超预期,雪景小镇很美,游玩设施也不错,没遇到什么坑人的场景,体验不错,可惜的是时间太短了,有机会可以再去。

香格里拉没想象中美,可能因为天气一直是阴天和下雨,偶尔的蓝天惊鸿一瞥是很漂亮,另外带着小孩没法去徒步,也没去雪山,就小小感受下藏族的风格。西双版纳很有泰国风味,热带雨林的感觉很明显,云南这两个地方的差异太大了。

成都的毕棚沟和香格里拉海拔都3000米以上,感觉没什么高原反应,明年可以去西藏了。

对成都和长沙印象都挺好,长沙五一广场方圆一大片人山人海,走哪都是商店大商场,都是茶颜悦色,夜夜笙歌,真是娱乐之都。

25年希望继续解锁世界地图。

小孩

跟同事聊娃,经常会聊出焦虑,不鸡娃不上奥数和考KET就上不了好的初中,而我们什么都没准备,让我有点担心是不是对娃太佛系了,到时会后悔,但每次想来想去最终还是不想让小孩太卷。大娃三年级作业开始变多,偶尔拖拉和烦躁,但总体上学习还是比较自觉的,成绩也不错,只要保持这个感觉,应该不至于太落后?

小娃5岁了,3-4岁有段时间专门气人,回看以前大娃也是一样有这样的时期,然后今年某个时候突然间感觉就长大了,讲理、能沟通,情绪控制也好一点了,甚至惊奇发现他学习也是挺认真的,超出预期的成长,很欣慰。

再次推荐洪恩系列 APP,小娃玩着玩着就学会了很多汉字/拼音/英文单词,以前大娃也是,这过程就是在不断玩游戏,玩的过程中学,好的学习就应该是这样。

一些喜欢的瞬间:

  1. 跟小娃的玩马里奥惊奇,一不小心挂了,小屁孩嫌弃地说“你会不会的,我来给你示范一下”
  2. 某次玩黑猴时卡在 boss 上,我吐槽打不过好烦,小娃在旁边一本正经纠正我说:“玩游戏是享受”
  3. 我的朋友圈开始收到来自大娃的评论,微信开始用得溜了,长大了,不想她那么快长大。
  4. 大娃二娃在家日常的吵吵闹闹。每当这时候就想起《IQ博士》里小云和小吉在屋子里翻天覆地吵闹的画面,真是太像了。以后我一定会怀念这样的场景。

其他

身体好了很多,中招咳嗽也就两次,健身断断续续,没坚持上。

今年大环境不太好,但困难总会过去,希望能保持乐观和学习心态,希望25年大家都能过得好~

带文字的 AI 图片生成是怎么做的?

2024-12-15 19:37:15

近期即梦上线了 AI 图片生成文字的能力,在生成海报、封面以及各种场景下渲染文字效果是非常不错的。最开始AI生成的图片中,涉及到文字的基本都是不能看的乱码,需要针对性训练优化才能做到生成清晰的文字并融入图片。那这里是怎么做优化的?对这个原理比较好奇,尝试通过几篇公开论文学习下相关实现思路原理。

大致思路:Recraft

目前生成文字(英文)最好的模型是 Recraft,官方有篇文章 《How To Create SOTA Image Generation with Text: Recraft’s ML Team Insights》介绍了模型训练的大体过程,挺适合简单了解大致思路的,简单复述下。

首先说明下为什么图片生成文字容易乱码?

  1. 一是数据量不足:图片生成模型是通过大量图片+图片描述去做训练,而大部分图片的描述是不怎么包含图上的文字的,比如拍一个街道建筑图,图上会有很多店面的名字文字,图片描述可能就是类似 城市/街道/红色招牌等描述,并没有把图上的所有文字放进去,模型只能在少部分相对简单的场景(比如图上只有几个字,图片描述中也有这几个字)中学习生成正确的文本,幻觉会比较严重。
  2. 二是文字的错误更容易被发现,相对于人物动作不协调、衣服花纹的差错,文字只要有一笔一划错误就很容易被人察觉识别为乱码,需要更精确的生成。

接下来看优化文字生成能力的大致流程:

第一步,准备数据。准备大量的包含文字的图片,包括海报、封面、广告、Logo等,对这些图片进行处理。处理包含两部分,一是用 OCR 模型识别图像上的文字位置和文字内容,二是用多模态模型识别这张图的内容,输出描述文本。得到了海量的 图片 – 文本布局和内容 – 图片描述 组合的数据。

第二步,使用数据训练模型,跟第一步是反着的过程。先训练一个布局模型,可以通过输入 prompt → 输出文本布局+内容。再把 prompt 和文本布局输入生图模型,最终生成带文字的图片。

大流程就是这样,再稍微把其中布局模型展开一下:

输入 prompt 输出 文字内容+布局,用的是一个大语言模型(LLM),定义了一个输出的文本格式,包含文本内容和这些文本的坐标。同时还会根据文本和坐标数据,用文字渲染工具画张图片出来。

这张渲染出来的文字布局图会作为生图时的参考,用类似ControlNet 的方式作用在生图过程中,最终生成图上的文字。

这是个大致流程,文中没有展开里面模型架构的一些细节,原文上表示思路基于 TextDiffuser2,但看起来思路上跟 GlyphControl、TextDiffuser、TextDiffuser2 都有关系。

各方案大的思路都差不多,基本都是分两步,生成文字布局信息,再作用在生图过程中,主要是模型架构不同,以及数据集质量不同。下面看看这些相关的论文和一些模型细节。

GlyphControl

先看看相对简单的 GlyphControl,23年11月的论文,基本就是一种 ControlNet,跟边缘轮廓、姿态等 ControlNet 没太大差异。ControlNet 的相关介绍可以看回这篇

训练阶段:找一批带文字的图片,用OCR 识别文字内容和位置,再渲染出一张白底黑字的图片,将图片描述和这张白底黑字图片一起进入 Glyph ControlNet 网络训练。这个白底黑字的图片就是参考图,跟边缘轮廓/姿态等其他 ControlNet 的参考图作用和流程都一样。

推理阶段:分两部分输入,生图的 Prompt 和白底黑字参考图,这张参考图看起来是要用户自己另外准备的,可以直接画一张白底黑字的图,或者描述文字内容、行信息、大小位置布局,用工具生成白底黑字参考图,再和 prompt 一起去生成相应的带图的文字。

效果:文字能较准确生成,但没有控制字体样式和文本颜色的能力,泛化性会比较差。布局和位置需要额外输入,产品化实用性低一些。

疑问:controlNet 23年2月出现,为什么11月才有人用于改进图片文字渲染,ControlNet作者自己不试试呢?

还有一篇更直接的,直接用 ControlNet 的边缘轮廓做文字生成,也不用自己训练,做了个评测: 《Typographic Text Generation with Off-the-Shelf Diffusion Model》

TextDiffuser

TextDiffuser 是23年10月的论文,跟上面 ControlNet 的思路有差异:

  1. 不用准备参考图,用一个模型从 prompt 中推断文字布局。
  2. 直接在生图扩散模型中训练,非 ControlNet 插件的形式。

流程:

  1. 布局生成:先根据 prompt 生成逐个字母的文字形状 mask 图。用一个 transformer 模型(非LLM)理解输入的语义,识别出图上要画哪些文字,这些文字在画布上应该是在哪个位置,获得每一个字符在画布上的box位置,再用字体渲染库(如pillow)把这些文字渲染上去,生成这些字符的遮罩表示(Mask)。
  2. 图像生成:将上一步得到的字符遮罩输入扩散模型,参与引导扩散过程,使图片能在遮罩对应的位置生成对应的字符形状。

训练:

  1. 数据:作者从各处收集了1000万张带有文字的图像-文本对,称为MARIO-10M,主要来源是开源的LAION-400M,从中筛选带文字的高质量的图,也对数据进行了处理,包括文本检测识别、字符级的位置数据、原有的图片描述文字等。
  2. 布局阶段:会使用这个数据集去做训练上面提到的 transformer 模型,输入是图片描述文字,输出是每个字符的 mask 遮罩。在数据集中,每张图片的描述、以及每张图片经过 OCR 识别处理后字符的遮罩位置都有,模型就能学习到对不同的图片描述,对应的最终的文本位置和形状应该是怎样的。
  3. 图片生成阶段:这个数据集也会在扩散模型的基础上去做进一步训练,在这过程中 U-Net 的参数是冻结的,猜测是避免核心生图能力被破坏?训练过程中只会修改扩散模型 U-Net 以外的其他模块参数,整个网络还是能学习拟合到数据集里 图片描述(prompt) + 字符遮罩数据 → 带文字图片 这里的对应关系。

这整个过程,就是为生图增加信息量,布局阶段渲染的每个字符的 mask 是很大的信息量来源,引导图片扩散方向不飘。

效果:

相对未针对性训练的生图模型,能生成合理清晰的文字,在给定图像补充文字上效果也不错,也能做到控制文本颜色了,但字体多样性差一些。

TextDiffuser2

TextDiffuser 有个问题,它第一阶段产生的文字 mask 是用单一字体渲染的结果,用这个 mask 引导生图,结果是生成的结果字形的多样性比较差,生成的文字倾向于规整,手写或艺术字很难出现,GlyphControl也有同样的问题。另外 TextDiffuser 布局转换器对用户输入 prompt 的理解能力也有限。

TextDiffuser2 差异在于:

  1. 布局模型用大语言模型去替换。LLM 能表现出比较强的语义理解布局规划能力,用一个 LLM 去理解 prompt 转化为对应的布局格式,效果会更好。
  2. 生图阶段,对扩散模型中的语言模型(clip)和 U-Net 都做了训练。

训练

布局模型:

  1. 使用 LLM vicuna-7b-v1.5 模型进行微调,训练用的还是前面的 MARIO-10M 数据集,拿这个数据集每张图对应的描述文字作为输入,用 OCR 把每张图片的内容和位置信息提取出来作为预期输出做训练。
  2. 这里自定义了布局的格式,一个关键词以一组坐标和字母组成,比如 [x25][y89][x108][y96][W][I][L][D],两个坐标表示方块左上右下两个点。每个字符单独标记,会比去做BPE分词标记效果好。
  3. LLM在学习了大量文字对应图片的构图后,可以从语义推理这些文字的构图应该是怎样的,同时 LLM 自身也能很好理解哪些词是关键字,哪些词应该在同一行。比如上图的 旷野之息邮票 a stamp of Breath of the Wild,LLM 可以学到图上的文本应该是 Breath of the Wild,而对于邮票比较好的布局是上下两行,有个关键字 Wild 突出,得出相应的布局数据。
  4. 根据论文描述,5000个数据量的训练效果是最好的,可能数据多了反而过拟合效果不好。

生图模型:

  1. 直接在扩散模型中训练,图上的 M2 是扩散模型里的 clip 文本模型,布局内容和文本 prompt 会一起输入,U-Net 也参与了训练,继续在用 MARIO-10M 数据集做训练。为什么这种方式训练效果好,文中没怎么提到。

效果

TextDiffuser2 的多样性会好一些,字体形态多样。

总结

还有一些其他方案,例如 GlyphDrawAnyText等,大原理差不多,不展开多说了。最后,用 notion AI 总结下本篇文章:

AI 图片生成文字主要有以下几种方案:

  1. GlyphControl:通过白底黑字的参考图来控制生成文字的位置和内容,实现简单但泛化性较差。
  2. TextDiffuser:采用两阶段方案 – 先用 transformer 模型生成文字布局 mask,再用扩散模型生成最终图像。但生成的字体样式比较单一。
  3. TextDiffuser2:改进了 TextDiffuser,用大语言模型替代布局生成,并对扩散模型进行更全面的训练,使生成的文字样式更加丰富多样。

这些方案的核心思路都是:

  • 准备大量包含文字的图片数据集(如广告、海报等)
  • 设计两阶段架构:先生成文字布局,再生成最终图像
  • 通过不同的技术手段(如 ControlNet、LLM等)来提升生成效果

目前 TextDiffuser2 的效果最好,既保证了文字的准确性,又能生成多样化的字体样式。Recraft 借鉴了 TextDiffuser2 和 GlyphControl。

客户端大模型进展怎样了?

2024-12-08 17:45:36

近期苹果发布的新品,无论是 iPhone 还是 Mac,都一改之前挤牙膏的风格,在最低配机器上都加大了内存,目的很明确,就是支撑 iPhone 和 Mac 上的端 AI 大模型。过去一年,AI手机、AI电脑的概念也一度在炒,在之前写的文章也说过,在客户端上跑大模型,一定是未来趋势。那目前端上大模型情况怎样?

应用近况

总的来说,各家陆续出了不少小模型,相关工具链也能支持它们在客户端上跑起来,但可用的应用几乎没见到。

不少手机厂商都号称接入了端模型,但实际上没搜到相关具体应用,Apple Intelligence 还在路上,演示的能力似乎大多是云端模型,不确定本地小模型能做的事。Google Pixel 8 也没有接入Gemini nano,小米14上没有MiLM,小爱完全靠云端模型,OPPO find7 号称端侧模型用于生成通话摘要等一系列能力,但似乎得联网,不确定端模型在上面起到的作用有多大,真正能离线用的也只有图片消除功能。

为什么雷声大雨点小?

  1. 完全体 LLM 近一年的应用场景也有限,端上也就更少了,当前阶段业界精力还是主要投入在研发最好的模型上,很难顾得上端的优化。
  2. 现在的硬件和模型优化程度还不允许 LLM 在端上有作为。端设备基本都对体积和功耗敏感,这两者都限制了硬件能提供的最大性能,7B的模型硬件支持不好,3B的效果不好。

我在 Macbook pro M1 上试跑了下,感受是:3B级别的小模型基本不可用,7B/8B级别的模型速度太慢,资源占用也太大:

  1. llama3.2 3B模型,大小2G,推理速度 62 token/s,翻译/总结/简单的指令理解,都有很大偏差,基本不可用。3B 这个级别或更小的模型,目前看起来需要针对特定任务做微调才能有作用,通用能力不太行。
  2. llama 3.1 8B模型,大小15G,推理速度约 8 token/s,基本问答/翻译/总结可用,但速度太慢,资源要求太高。(这篇文章估算了推理速度,与实测差不多)

LLM 端推理引擎

客户端 LLM 应用还没到时候,但不妨碍大家对这个方向的投入热情,相关的工具链有比较大的进展。

这块工具链的核心是推理引擎,LLM 的训练和推理一般都用 PyTorch,它在GPU适配/加速/生态上都是最好的,但在客户端跑模型,有一些其他诉求:

  1. 在 CPU 上推理的能力,以及能适配多种 GPU 加速
  2. 量化技术,需要更小的模型、更低的资源消耗
  3. 可以轻量编译部署到多种客户端环境

所以需要另一种推理引擎,目前用得最多的是 llama.cpp。

llama.cpp 是 C++ 开发的 LLM 推理引擎,最开始只用于 meta 的 Llama 模型推理,后来扩展到更多模型,包括 Mistral / Gemma / Phi / QWen 等基本所有开源的 LLM,也包括基于 LLM 的多模态模型 llava。llama.cpp 是个人开源项目,基于同个作者的 ggml,在它基础上加了相关大模型推理的功能,token 化 / 缓存管理等。

llama.cpp 可以跑在基本所有主流操作系统上,Android、iOS、Linux、Windows、macOS,甚至 WebAssembly上也提供支持,支持各种 GPU / CPU / NPU 推理。

基于 llama.cpp,上层包装了很多应用,可以方便地在桌面端和移动端跑各种 LLM 模型,桌面端上使用最多的是 ollama,近期 LMStudio 也很不错,移动端上可以用 pocketPal

ollama 基于 llama.cpp,提供本地模型服务

上述这些都是包装了模型下载管理和聊天的壳,目前比较少见到基于 llama.cpp 包装更上层垂类场景的应用。有些些 Mac AI 应用会同时提供线上 GPT 接口以及本地 ollama 接口,LLM 处理可以在本地进行,例如做音频视频转文字和总结的 MemoAI,这也可能是后续 Mac/PC 本地 AI 应用的标配。

除了llama.cpp,还有类似的mlc-llm,也是全平台和多种 GPU 支持。还有专为苹果芯片优化的LM Studio MLX,不多介绍了。

LLM 以外

在实际应用中,端 LLM 还没能用起来,但一些厂商为了推 AI 手机 / AI 设备的概念,经常会包装进一些其他的 AI 能力,比如图片消除能力、语音唤醒识别能力。目前端 AI 真正能在实际场景中应用得好的,也还是这些多媒体图片/语音处理类的小模型,跟 LLM 无关。

常见的图片处理比如 杂物擦除、图片超清、背景去除等,都有很多小模型,转换为 ONNX 或其他推理引擎支持的格式就可以在端上跑。

ONNX 是一种标准开放的模型格式,PyTorch / TensorFlow 等各大深度学习框架训练的模型都可以转为 ONNX 格式,然后用统一的 ONNX Runtime 推理引擎部署在多种硬件和操作系统上,目前大多数端上推理引擎也都支持 ONNX 格式做推理,腾讯的 ncnn/TNN,阿里的MNN,小米的 mace 等都支持 ONNX 格式。

各种模型格式转为 ONNX,跑在各式各样的设备上

理论上只要模型不大,对硬件运算要求没有特别高,转化为 ONNX 格式后在端上都能很好地使用,很多特定的多媒体能力很符合这个条件,例如杂物擦除MI-GAN,只有590万个参数,直接跑在浏览器上 / APP 上都没问题,效果也不差。还有其他很多基于 GAN 的模型,图片超清Real-ESRGAN,老照片修复 GFPGAN 等,运算要求都不高,跑在端上没什么问题。IOPaint 这个项目可以看到比较多类似的模型。

IOPaint 本地运行各类图片编辑模型

如果不考虑多平台部署,把模型转为平台自带推理引擎支持的格式,是能更大程度优化性能的,例如可以将模型转为 CoreML 格式跑在 iOS/Mac 上,但相对比较少,大家更倾向于跨平台的方案。iOS 上比较有名的端生图 APP DrawThings 就是将 Stable Diffusion 转为 CoreML 格式并量化后跑在端上。也有把 SD 转为 ONNX 格式去端上跑的,但还没看到比较好的应用。

一些遐想

端模型的应用,从硬件上分两种:

AI 硬件

  1. 有些场景可以不受设备大小限制、甚至续航功率限制,可以做得比较大,车机系统是一种,这是最好最大的应用场景,端上大模型 AI 应用会最先产生在这个领域,FSD也可以认为是端 AI 的一种。
  2. 还有一些可能得 AI 教育硬件,陪伴的玩偶等,本身也足够塞个大运算量芯片和大电池。一些刚需的硬件,比如导盲眼镜,也可以是连着口袋里一个不小的计算设备,这些算是后续可能的端上大模型的应用场景。
  3. 但除了车载系统以外,其他 AI 硬件要采用这种方式,发展会比较难。技术体验是一回事,还有商业模式的问题。
  4. 这些设备是自带硬件端上跑,还是云端跑,其实就是买断制和订阅制的区别。在端上跑需要用户一次性付出较高的硬件成本,但后续没有其他额外的成本。云端跑初期用户付出的硬件成本低,甚至厂家也愿意赔钱卖机器,但后期是可以用订阅服务制长期收费。从这角度看,用户和商家基本都会选择订阅制,对双方都更友好。所以端大模型要在 AI 硬件上流行起来,还比较难,除非是有些场景对隐私和实时性要求就是很高。

手机电脑

另一种就是利用已有设备,不需要用户额外花钱买硬件,那就还是回到设备大小、续航功耗、发热、机型覆盖等限制,有些场景为了省成本可以先用起来,PC / Mac 陆续可以有一些应用场景,例如上面提到的连接 ollama 的 MemoAI,浏览器上的 AI 搜索也非常适合端上 LLM 去做,但可能这几年会一直处于小场景尝试的阶段,要到主流的程度还早得很,也可能一直不会是主流,手机更是了。

谁在用 AI 图片生成

2024-09-23 11:58:48

AIGC 图片生成的技术,基本是22年开始爆发,Midjourney 2022年7月推出,Stable Diffusion 2022年8月推出,至今两年发展迅速,已经广泛在很多场景应用,但这个市场上是谁在用图片生成,用来做什么,一直以来在我认知里都有些模糊,这篇文章做下相关调研。

线上线下所有用到图片的地方,都有 AI 图片生成的应用空间,而 AI 图片生成的能力,也会创造出新的领域和行业,就目前能看到的已经在应用的场景,归归类可以分为:生产力工具、大众娱乐、探索创作。

ToB:生产力工具

把 AI 图片生成能力作为实际工作中的生产力工具,用在各领域的内容生产,替换原来的工作流,效率有量级上的提升,同时也有因为 AI 图生成带来的新的领域,例如自媒体。

这里的用户大部分是设计师,全球设计师 9000w,包含建筑设计、室内设计、工业设计、服装设计、产品设计、平面设计等,Adobe 付费订阅人数2650w(2022年),是非常大的市场。

电商

电商有大量的市场,为了展示、介绍、美化不同种类的商品,对图片有巨大的诉求,是AI图片(以及视频)最好的应用场景。

  1. 模特图:模特换衣、模特生成、在线试衣,专门服务服饰品类的工具,全球电商服饰品类市场规模六千亿美元,这让它对应的工具需求也足够大,能搜到的有几十家公司专门在做,例如BotikaVModel.AI摹小仙千面AI模特ZMO.ailinkfox,美图秀秀/醒图等也有相关工具。入门门槛低,但效果的调优是wu’zhi’jing的,不同角度/动作/不同衣服穿上后的自然度等都需要不断调优。
    1 2
    换模特 换衣
  2. 商品图:上传商品图,AI 可以帮你生成商品在不同环境下的宣传图,免去摆拍。相对于直接抠图→套模板,AI生成质量高,可定制程度也高,可以创造符合商品的各种背景,商品能更好融入对应背景、环境的光线阴影、颜色、高保真,这里的效果调优也是无止尽。同样有非常多公司在做,photoecom灵动AIPicCopilot。综合性的图片工具大多也会加入这个功能,比如 photoroom
    3 4
    灵动AI photoroom
  3. 其他长尾:电商很庞大,除了上述两个类,整个上下游各个品类还有不少细小长尾的 AI 图片生成需求,例如 T恤定制、衣服花纹生成、款式生成、站外营销图等。
  4. 从发展趋势看,电商平台如果自身有余力,都会去做这样的工具,嵌入到自己平台内,整个工作流更顺,像淘宝千牛自己就做了。但竞争是无止境的,所有商家都用平台提供的工具,质量品质同质化后,就会有个性化或追求更好效果的诉求,外部工具一直会有机会。

素材

素材应该是需求第二大的领域,活动图、海报、封面插图(文章/播客/杂志)、PPT,日常工作很多场景会用到,以前是搜图片找素材拼接,但如果是商用场景,一不小心有侵权的风险,素材是需要付费的,AI 图生成目前没有这个问题,而中国的版权图片市场规模在2020年是34亿,在高速复合增长。素材生成的诉求很泛,不太依赖可控生成,应该大部分都用图生成质量最好的 Midjourney,海报生成因为涉及文字,ideogram.ai 有较大的优势。

5 6 7
ideogram海报 营销素材 壁纸

自媒体

AI 图片生成的能力会被一些自媒体创作者用于创作有趣的内容,带来流量,进而接商单。例如影视/动漫 IP 二创、自制IP形象(宠物打工、宠物时装秀等)、扩图玩梗、表情包等,会不断有各种有趣的玩法持续出现。

8 9 10 11 12
高质量图 扩图,玩梗 玩法 影视IP二创 自制IP

其他

  1. 游戏设计:首当其冲是游戏原画,AI 图片生成出来的质量,跟外包原画师已经没有太大差异,或者质量更好,去年就传出游戏公司大规模砍原画外包的新闻。同时游戏内容本身需要大量的角色、场景设计,对于质量要求不高的 2D 游戏,AI图生成已经可以很好满足需求。
    13 1415
    角色生成 游戏原画
  2. 建筑设计:借助 SD ControlNet 的能力,很容易做到建筑线稿设计图转绘为效果图,渲染不同风格,也不需要有多少微调的工作,各工作室自己可以部署。对于建筑灵感,直接用 Midjourney 看起来也是足够。
    1617 18
    概念设计 线稿转绘
  3. 漫画/绘本故事:核心是模型角色保持的能力。儿童绘本故事门槛很低,网上也有大量应用的教程,大众对质量的要求也没那么高,这是 AI 图生成目前擅长的。漫画门槛高一些,核心是故事、分镜的质量,生图所占的比例其实不高,所以如果用 AI 大规模生产,质量堪忧,但也有一些精品,比如这个。针对漫画有一些独立的产品和模型,例如dashtoonComic Factorycomicsmakerllamagen等。
     19 20
    武侠漫画 Comic Factory
  4. 动画/短剧:同样借助角色保持能力,生成图片后转成视频形式去消费,这也是后续内容制作的趋势。目前还没看到大规模成熟的应用,短剧类 midreal 相对小众,月活几万的级别。小说转动画视频有不少产品在尝试 剪映的故事成片、极虎漫剪漫剪猫等,规模比较小,但作为生产力工具,付费率是挺高的,做出来的内容有一定消费价值。

ToC:大众娱乐

图片特效

大众用户日常社交对图片是刚需,AI 图片生成在这个领域的应用是最广泛和成熟的,跑出很多爆款产品,Top 的是 Remini(23年MAU 8000w+,收入6643万美元),其他也有非常多产品冒出,AIMirror/FaceAPP/Lensa/Prisma等。

这个领域不断会有爆品出现,理论上不会一家独大,每个产品都有机会,逻辑是:出效果爆款→社交媒体传播全网引爆→大量用户使用&付费→热点几周后消退,用户少量留存,大量流失→找下一个爆款→找到进入下一个循环,找不到产品逐渐消亡。典型的持续活下来的产品是Remini,消亡的是妙鸭。

具体应用上,姑且分为 AI 写真和特效。

  1. AI写真:人像 P 图是刚需,AI写真算是这个刚需的分支,火过很多产品,国内的妙鸭,海外Remini,还有一大波专门做这块的垂类产品 PhotoAI星绘等。妙鸭虽然火一波以后销声匿迹,但这个需求是长期可持续的,photoAI 是独立开发者的产品,月流水已经到17万美元。主要用于各社交软件头像、linkedin商务照等。
  2. 特效:比如风格化的黏土风格、盲盒公仔、迪斯尼风等,还有其他例如换发型、换性别、变老变年轻、扩图等特效。
21 22 2324
Remini 众多特效 星绘 AI 写真 ailabtools 换性别、年龄

新场景

另一类 ToC 的应用,是把 AI 图片生成能力作为全新产品的一部分嵌入,跟产品形态有较强的绑定。

  1. 陪伴类产品:纯 LLM 文字陪伴发展下去肯定是结合图片生成/视频生成,让人更沉浸式,可以衍生抽卡、剧情图、虚拟女友形象等。产品非常多,MiniMax 的 星野/Talkiecandy.aidreamgf.ai 等,AI 陪伴还在爆发增长期,AI 生图在这个领域有很大应用空间。

  2. 教育类产品:DoDoboo 将儿童涂鸦实时转为绘画作品,激发儿童创造力。是一个尝试性的应用场景,没有很成功,但 AI 教育是万亿级别市场,儿童教育领域本身注重创造力想象力的培养,AI 图片生成就是想象力的呈现,是有机会创造或融入更多教育产品。

    image.png

  3. NSFW:成人产品,比较特殊,市场自然是巨大的,待分析。

25 26
Talkie DoDoboo

探索创作

除了上述 ToB 和 ToC 两类非常明确的应用场景外,AI图生成还衍生出另一波探索型用户。他们不是为工作,无商业目的,单纯喜欢玩 AI 创作,他们可能不会画画,AI 让他们可以不需要学习绘画技能,就能创作出好的作品,这对有创作欲的人有很强的吸引力。

Midjourney 付费用户中,只有 32% 的用户目的是工作或实际需求,68%的用户是为了娱乐。一方面因为 Midjourney 可控性不足,导致很难在真实生产环境使用,较少覆盖上述 ToB/ToC 的那部分用户,另一方面也能看出,纯粹探索 AI 玩图片生成的人群规模也不小,24 年 Q2 Midjourney 月活 600万+,24 年预计收入预计超过 3 亿美元。

27 28 29
Midjourney thehybridportraits 高端定制

图片生成技术,跟摄影技术有点像:

  1. 没有摄影时,只能通过超高的绘画技术记录现实画面,门槛很高,摄影技术让人人拥有记录现实的能力,只需要按个按钮。
  2. 而没有图片生成技术时,也只能通过绘画技术记录和创作现实没有的画面,把心中想象的创意具象化,图片生成技术让人人拥有创作的能力,只需要输入文字。
  3. 除此以外,还有一些相似点:
    1. 人人能用,但专业才能用得好:AIGC跟相机一样只是技术,日常拍照人人能拍,要拍出好的照片,不是人人能做到,即使摄影看起来只是按下快门,调下参数。图片生成随便输入 prompt 人人能创作图片,但要创作出好的作品,也不会是人人都能做到,即使看起来只需要输入文字。
    2. 大众需要,商业也需要:摄影可以记录生活,这是大众需要的,也可以杂质配图、做商业广告等,这是商业需要的。图片生成也一样。
    3. 新的艺术形式:摄影单独是一种艺术形式,相信 AI 图片生成也会带来独有的新的艺术形式,只是目前还未成型,摄影从诞生到成为一种艺术形式,也花了60年。跟画画与摄影不同的是,AI 图片生成创作,是有双向交互的,它不是定死的画笔或相机,创作过程中,AI 创作出来的内容会牵引下一步创作动作,不是一步到位,也不是忠实呈现自己脑里所想、呈现现实世界已有的东西,AI 不仅是工具,作品是人与 AI 的共创,有可能是新的艺术形式。

但跟摄影不同的是,图片生成技术,也许无法像拍照一样普及率那么高,摄像头记录美好生活是高频刚需,但创作不是,纯 AI 创作最终还是属于少部分创作者,就像能称为摄影师的只是少部分人。AI 技术进步是赋予了不会画画但有创意的一波人更强的能力,就像抖音最终赋予的也是少部分创作者展示他们才华的能力一样。

创作无法普及到大众,但创作出来的内容是能普及的,内容消费是大众刚需,至于这波创作者能否创作出跟摄像头相媲美的另一个维度的内容,支撑起一个 AI 内容消费社区,有待探索。

最后

生产工具、大众娱乐、探索创作,这三类图片生成的应用,差距还是比较大的。

  1. 生产工具,需要深入到场景做微调,不断优化效果、深入工作流。
  2. 大众娱乐,需要的是制造爆款的能力。
  3. 探索创作,需要有最好的基础模型能力,以及做好社区运营。

目前看起来没有一个产品能大面积覆盖这几个场景,未来会不会有?只要团队能满足这些条件,能造出一个超级应用满足所有图生成的诉求,大众认知上是没问题的,像上个时代的 Photoshop。