TLDR:我编写了 BrainBox,这是一种本地优先的支持服务,用于开源 AI 系统,用于图像生成、对象识别、画外音、文本识别、音频转录和其他目的。这些系统在 Web 服务器的门面后面运行,Web 服务器安装它们,然后以单一格式接受和执行所有系统的任务。通过向服务器发送 Web 请求,可以从任何编程语言设置任务,并且已经为 Python 开发了一个方便的 API。该项目可在 pypi https://pypi.org/project/kaia-brainbox/ 上获得,并提供详细的英文文档和说明。我希望 BrainBox 能帮助那些想使用 AI 制作一个小项目,但又不想了解 python 和 torch,或者只是不知道从哪里开始的人。
在本文中,我将向您详细介绍 BrainBox 及其架构,并使用两个具体示例说明如何安装和使用此服务:文本转语音和图像描述。
赋予动机
2023 年,在 Stable Diffusion 和 Llama 出现后,人工智能不再是公司的专有财产,而是可供所有人使用。很明显,这需要得到解决,以免在历史的边缘某个地方留下你的熊猫和火炬。在工作中,这个话题并不是特别有前途:不可能完全沉浸在美丽中,也就是说,在技术上,业务仍然必须产生利润,这意味着创作冲动仅限于经济可行性和 KPI 等无聊的事情。
因此,我决定在这个领域做一个宠物项目,过了一会儿我遇到了一个很好的话题:Kitchen AI Assistant (Kaia) 是一款厨房助手,拥有我最喜欢的角色的面孔、声音和性格。它就像一个谷歌扬声器,只是有一个显示屏并且更加个性化:在 AI 的帮助下,我用我最喜欢的角色在不同情况下生成图片,克隆他们的声音,并根据角色的性格解释语音助手的确定性响应。助手的代码也是开放的。这一切看起来都是这样的(为了不违反版权和道德法,演示版使用了开源语音和美丽的 AI 景观):
凯雅厨房助理
该项目几乎准备就绪 — 剩下的就是在前端重写一些控制算法,以摆脱在设备上安装 Python 的要求,从而使 Kaya 与 Android 平板电脑和 iPad 兼容。
但我稍后会讨论这个项目本身,但现在我将重点介绍其中更复杂、更有用的部分 — BrainBox,一个支持 AI 系统的服务器。
Kaia 中有很多 AI 系统。很多。仅推理就至少需要两个。首先,您需要文本转语音,我正在使用 Piper,这是一种轻量级 TTS,可在非 GPU 机器甚至 Raspberry PI 上运行。它支持许多不同的语言,其中一些语言非常小。语音可以在单独的训练过程中进行训练。
其次,你需要语音识别。这里的事实标准是 Whisper,它将口语文本转换为具有所有必要标点符号的字符串。但是,我在 Rhasspy 中通过实现使用的 Kaldi 更适合助手:它允许您使用助手可以识别的短语来定义语法,并且使用这种封闭的语法,它比 Whisper 工作得更快,同时结合了文本到语音和自然语言理解。此外,还有 Vosk — 和 Whisper 一样,它适用于开放语法,速度更快,对资源的要求明显更低,其输出更好地与音频流相关联,但没有标点符号装饰。
但除了助手一直需要的这些系统之外,预先准备好的内容还有一个更耗时的部分:图片、语音和释义。
为了生成图像,我使用 ComfyUI,这是该领域的一种标准且有效的解决方案。要用我的角色画图,我首先需要为他们训练 LoRA,KohyaSS 可以帮助我做到这一点。反过来,要训练 LoRA,您需要准备一个数据集,这里您需要大量的工具库:将视频分割成单独的帧,使用 YOLO 在这些帧中搜索人脸,并使用 WD14Tagger 标记图像。
语音训练的情况类似。大约需要半小时的干净样本,没有音乐和噪音。在主要来源中找到如此数量的声音非常困难,因此我使用上采样:根据来自原始来源的 ~1 分钟的声音,我使用 Zonos 或 CoquiTTS 生成所需的 30 分钟。
释义是最少的:您只需要 LLM、Llama 或 Mistral(适用于欧洲语言),它们可以通过 Ollama 解决方案获得。
事实上,在所有提到的系统中,只有 Ollama 能够毫无问题地安装,下载工作所需的一切,然后通过 API 工作。委婉地说,其余的系统不在这个级别,通常只是一个带有代码的存储库和一台带有演示的笔记本电脑——你仍然需要努力启动它,因为在一半的情况下存在完全的依赖关系不确定性。因此,为了在项目中使用整个动物园,必须以某种方式对其进行组织。这就是 BrainBox 的出现方式
当我在 Kaia 项目中已经写好了 BrainBox 时,我突然意识到我所做的工作的规模,以及我一路上收集了多少坎坷。此外,我仍然在该项目活动中担任志愿者的乌尔联邦理工学院的学生帮助了我:“快速找出一个新的 AI 系统”的任务很容易持续数周;“准备系统以用于生产”的任务很容易需要几个月。我意识到一个想要做一个小型 AI 项目的人,即使是最简单的项目,实际上面临着多么严峻的挑战:为有声读物发声、总结笔记、在 YouTube 上绘制封面等等。以及如果 python 不是主要的编程语言,这些挑战会增加很多倍。进入这个领域的门槛太高了,它迫使你向企业低头。
因此,我决定将 BrainBox 作为独立于 Kaia 的产品,正是为了帮助此类项目。现在,任何想编写这样一个小项目的人都可以简单地安装 BrainBox 并立即访问 AI 系统,包括上面提到的和其他系统(完整列表可在文档中找到)。就是这样,问题解决了:要调用特定系统,您只需发送一个 HTTP 请求!
我真的希望 BrainBox 能帮助新人加入现代人工智能的美妙世界,创造出更多好用的产品。当前的时代让我想起了 Web2.0 的黎明:然后,各种工具也突然出现,人们借助这些工具创造了新产品,从 Facebook 到个人笔记应用程序。并非所有人都成为独角兽,但许多已经并且仍然对他们的用户有用。现在正是同一时间。
建筑
BrainBox 并不打算包括所有开源系统。相反,我专注于一种架构,它可以让您添加新系统而不会遇到很多麻烦。
BrainBox 架构
Docker 容器
在设计过程中,摆在我面前的第一个问题是:如何组织多个 AI 系统以在单个 Web 界面后面工作?
第一种解决方案是将所有这些系统塞进一个带有 Web 服务器的应用程序中。这个想法非常不成功,这在一开始就很清楚了:当我尝试只将三个系统集成在一起时(Oobabooga 而不是 Ollama,Automatic1111 而不是 ComfyUI 和 TortoiseTTS),它们都需要自己特定版本的库,要克服这个问题并不容易。这是一条死胡同。
第二种方法是编写几个小型服务器,每个服务器都服务于自己的系统:Python 有一个环境机制,允许您执行此作。这解决了 Oobabooga、Automatic1111 和 TortoiseTTS 的问题,但已经在 CoquiTTS 证明这种解决方案也不可行:CoquiTTS 需要安装 espeak,这在 Windows 下并不容易安装,而其他系统已经需要 wget 和 ffmpeg......很明显,安装这种解决方案的说明会很长,没有人会在 Windows 上安装它。同时,由于游戏的原因,Windows 是具有良好 GPU 的机器的主要系统,这意味着很大一部分潜在目标受众将被排除在这样的解决方案之外。
最终版本是在自己的 docker 容器中构建每个系统。ffmpeg、esspeak 和其他所有内容都通过 apt 自动安装,因此用户除了安装 Docker 和 Python 之外,无需处理任何其他事情。我决定直接在用户的机器上构建容器——否则,我将不得不管理一个不断增长的存储库池,这很懒惰。此外,集中容器会将系统绑定到 Internet 上的特定位置,并且在我看来,这将使它不那么自由。
因此,每个受支持的 AI 系统都会显示一个控制器实体:它构建一个 Docker 容器,运行它,并管理其作。BrainBox 服务器包含可通过 HTTP 请求触发这些作的端点。控制器将用户与容器完全隔离:用户仅使用 BrainBox,完全不使用 docker 命令。
任务队列
容器通常包含 HTTP 服务器,这些服务器主要是通过将 AI 系统附带的笔记本电脑复制粘贴到服务器的代码中来编写的。从理论上讲,这足以完成工作:一个极简的控制器 Web 服务器运行您想要的 AI,然后您通过它自己的 HTTP 服务器与它通信。
然而,这是不切实际的。首先,使用这么多不同的 API 会很不方便,这些 API 已经由不同的人以完全不同的风格编写,并且通常没有太多文档。其次,如果您运行这些系统进行研究,通常需要管理任务流程:例如,首先执行绘制图像的所有任务,然后执行画外音。此外,即使在渲染任务中,也可能需要对不同的基础模型进行排序,以防止重复加载同一模型。
因此,我在控制器层之上编写了第二个 BrainBox 层 — 内部人员层:其方法调用 HTTP 模型服务器的端点并在必要时转换其结果的实体。我将调用特定 desider 方法的请求称为 task。BrainBox 组织任务队列,确定其优先级,然后根据此优先级执行工作,升高和降低必要的 docker 容器。
优先级由另一个实体完成 — 调度程序,它取决于 BrainBox 用例。在 BrainBox 用作 Kaia 的支持服务器的情况下,所有必要的容器都会在 Kaia 开始时引发一次,然后 BrainBox 专门参与调度请求;在研究模式下,调度程序选择其中一个内部人员,提升容器,执行分配给它的所有任务,然后降低容器并移动到下一个内部人员。将任务添加到队列中,阻止进程直到获得结果,监控状态和获取结果都是通过 BrainBox 服务器的端点进行的。因此,desiders 层为所有支持的系统提供了一个单一的接口。
PythonAPI 进一步增强了此接口,这使得使用 python 功能创建任务变得更加容易。这将在 BrainBox 用例中进一步介绍。
BrainBox 还充当文件存储:许多 sides 都会生成图像或声音,将所有这些 GB 存储在数据库中似乎是错误的。因此,二进制结果存储在 BrainBox 缓存文件夹中,而 deserver 则返回文件名。如果 desider 需要输入文件(例如,语音识别),则必须将文件预加载到服务器上,并且必须将其名称传递给 deserver。
基本上,BrainBox 可以在单独的机器(例如云)上运行,也可以充当远程 AI 支持服务器,有点像 AWS Bedrock 的开源版本。我在本地网络上使用这个场景,但在云中部署可能需要某种安全解决方案,我没有这样做,因为我对它一无所知。因此,出于谨慎考虑,我不推荐这种方法,但如果您知道自己在做什么并愿意承担风险,那么我预计 BrainBox 本身不会出现问题。
自检
在自检期间,控制器执行一个或多个使用内部程序的方案,调用其端点 — 这可以是对端点的一次性调用,也可以是它们交互的演示(如果我们谈论的是内部人员训练模型,然后使用此模型)。
所有这些调用都是以使用 PythonAPI 时完成的确切方式进行的。自检本质上包含可以复制并粘贴到项目中以调用 desider 的代码。在自检中,我尝试涵盖所有用例,以便更轻松地进行后续复制粘贴。因此,自检除了检查运行状况外,还起到了记录功能的作用。
添加新的 Sider
要将新的 AI 系统添加到 BrainBox,您需要执行以下作序列:
-
让这个系统在 docker 容器之外的机器上工作,并了解这需要哪些依赖项
-
创建一个控制器来构建具有这些依赖项和系统源代码的容器
-
在容器内实现 Web 服务器
-
实现与此 Web 服务器通信的预览体验成员
-
编写自检
现在这些程序可以在几个小时内完成。可以在此处观看我从头开始为 Zonos 系统执行此作的详细视频。
使用 BrainBox
在本节中,我将讨论如何安装和使用 BrainBox。我将通过两个示例来演示这一点:使用标记在图像中进行文本转语音和对象识别。
安装 BrainBox
安装 BrainBox 需要执行以下步骤:
这些链接包含安装指南,它们通常运行良好。如果您是 Anaconda 和 Docker 的新手和/或遇到问题,我将在附录中提供有关如何安装它们的更详细说明。
安装 Docker 和 Anaconda 后,为 BrainBox 创建一个环境:
conda create --name brainbox python=3.11
BrainBox 针对 Python 3.9-3.13 进行了测试,但它是在 Python 3.11 上开发的,因此它是最可靠的 Python 版本。创建环境后,将其激活:
conda activate brainbox
使用 BrainBox 的所有后续作都必须在激活环境的情况下执行。现在您可以安装 BrainBox:
pip install kaia-brainbox
最后,运行它:
python -m brainbox.run --data-folder PATH_TO_SOME_LOCAL_FOLDER
成功启动的服务器将在控制台中打印:
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:8090 * Running on http://10.10.5.48:8090
Press CTRL+C to quit
由于 BrainBox 仍在开发中,因此我建议测试正在运行的服务器。为此,请在另一个控制台窗口中键入:
conda activate brainbox
python -m brainbox.run_test
如果成功,测试将在末尾打印 ALL GOOD。
失败的主要原因是未启动(在 Windows 或 MacOS 上)Docker 服务器。在这种情况下,测试控制台将打印一个以 Windows 结尾的长错误:
error during connect: this error may indicate that the docker daemon is not running: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/containers/json?all=1&filters=%7B%22ancestor%22%3A%7B%22hello_brain_box%22%3Atrue%7D%7D": open //./pipe/docker_engine: The system cannot find the file specified.
在 MacOS 上:
Cannot connect to the Docker daemon at unix:///Users/yura/.docker/run/docker.sock. Is the docker daemon running?
要解决此问题,请在这些作系统上运行 Docker Desktop,然后再次运行测试。BrainBox 服务器不需要重新启动。在 Linux 上,docker 守护程序在作系统启动时默认启动,并且不需要应用程序,因此不应出现此问题。
对于以下所有示例,您需要运行 BrainBox 并使其始终运行。在 Windows 和 macOS 上,您还需要保持 Docker Desktop 运行。
安装 Sider
在使用 desider 之前,您需要安装它。该单元将在您的机器上组装一个带有 AI 系统的 docker 容器,并从 Internet 下载其运行所需的模型。
最简单的方法是通过 http://127.0.0.1:8090/controllers/status 的 WebUI BrainBox。对于以下步骤,您将需要 Piper 和 WD14Tagger 收发器。这些 desiders 通常不需要 GPU 或密集的计算,并且或多或少可以在任何笔记本电脑上启动。
安装后,运行自检过程并熟悉其结果是有意义的:这将使您能够在早期阶段捕获错误,还可以让您了解如何使用已安装的 desiders。
Insiders 不仅可以通过 UI 安装,还可以使用 API 或 HTTP 请求安装,我将在下面给出这个例子。
配音 (API)
为 Python 开发了一个方便的 API,它允许您几乎无缝地使用 Brainbox,基本上是远程过程调用格式:
from brainbox import BrainBox
from brainbox.deciders import Piper
if __name__ == '__main__':
#Создаем api для уже запущенного сервера
api = BrainBox.Api("127.0.0.1:8090")
#Запускаем озвучивание текста
result = api.execute(BrainBox.Task.call(Piper).voiceover("Привет, мир!", "ru"))
#Результат является строкой - именем файла. Этот файл можно скачать:
api.download(result, "./piper_result.wav")
#Либо открыть без скачивания, например, для ipywidgets.Audio
print(api.open_file(result).content[:10])
Piper 是一个 desider,当从 BrainBox 下运行时,它的方法会向 Piper 容器发出请求。然而,在上面的代码中,没有调用,因为这个技巧是实现的:通过返回值注释,它“假装”返回一个对象,但实际上返回一个模拟,其调用为 BrainBox 构建了一个任务。但是,受骗的 IDE 在 Piper 类中显示方法,对于每个方法,它都会显示参数及其类型,当文档出现时,它也会显示它。此外,mock 会执行参数检查,此阶段的不一致将在任务到达 BrainBox(即客户端)之前被捕获。Piper::voiceover
voiceover
BrainBox.Task.call(Piper)
Piper
语音作 (HTTP 请求)
所有 BrainBox API 都可以在没有 python 的情况下使用。这不是访问 BrainBox 的主要方式,总的来说,我还不明白对它的需求有多大。因此,它是按照“只要它有效”的原则实现的,而且确实有效,但还有改进的余地,尤其是在文档和传输数据的格式方面。
以下说明将一个语音作任务的数组添加到队列中,适用于 Linux/MacOS:
curl -X POST "http://127.0.0.1:8090/jobs/add"
-H "Content-Type: application/json"
-d '{
"arguments": {
"jobs": [
{
"decider": "Piper",
"method": "voiceover",
"arguments": {
"text": "Привет, мир!",
"model": "ru"
}
}
]
}
}'
窗户:
curl -X POST "http://127.0.0.1:8090/jobs/add" ^
-H "Content-Type: application/json" ^
-d "{""arguments"":{""jobs"":[{""decider"":""Piper"",""method"":""voiceover"",""arguments"":{""text"":""Привет, мир!"",""model"":""ru""}}]}}"
这些命令以以下格式返回 JSON:
{
"error": null,
"result": ["4e1ddb3d-c50b-4201-a90c-f954c8024ca8"]
}
其中 result 是排队任务的 id 数组,其长度等于传输任务数组的长度。之后,您需要等待在 Linux/MacOS 中使用 join 终端节点执行此任务:
curl -X GET "http://127.0.0.1:8090/jobs/join"
-H "Content-Type: application/json"
-d '{
"arguments": {
"ids": ["4e1ddb3d-c50b-4201-a90c-f954c8024ca8"]
}
}'
在 Windows 上:
curl -X GET "http://127.0.0.1:8090/jobs/join" ^
-H "Content-Type: application/json" ^
-d "{""arguments"":{""ids"":[""4e1ddb3d-c50b-4201-a90c-f954c8024ca8""]}}"
此调用的结果是 VoiceOver 的直接结果:
{
"error": null,
"result": ["4e1ddb3d-c50b-4201-a90c-f954c8024ca8.output.wav"]
}
由于画外音的结果是二进制内容,因此此内容将保存到文件中,并且仅返回文件名。因此,必须从 BrainBox 缓存下载文件:
curl -o voiceover.wav http://127.0.0.1:8090/cache/download/4e1ddb3d-c50b-4201-a90c-f954c8024ca8.output.wav
图像识别
对于图像识别,顺序不同:您需要先上传一个文件,然后调用 desider。使用 BrainBoxAPI,它是这样完成的:
from brainbox import BrainBox
from brainbox.deciders import WD14Tagger
from pathlib import Path
if __name__ == '__main__':
api = BrainBox.Api("127.0.0.1:8090")
api.upload('image.png', Path(__file__).parent/'image.png')
result = api.execute(BrainBox.Task.call(WD14Tagger).interrogate('image.png'))
print(result)
事实上,可以通过多种方式 “绕过” 下载文件。在第一个示例中,传递文件的路径:
result = api.execute(BrainBox.Task.call(WD14Tagger).interrogate(Path(__file__).parent/'image.png'))
如果您确定 BrainBox 将始终与应用程序在同一台计算机上运行,则可以使用此方法;如果您改变主意并想在远程计算机上使用 BrainBox,则必须覆盖所有调用。
或者,您可以上传整个文件:
with open(Path(__file__).parent/'image.png', 'rb') as file:
image = file.read()
result = api.execute(BrainBox.Task.call(WD14Tagger).interrogate(image))
这也可以工作,但是随着这种方法的大规模使用,数据库将挤满 GB 的二进制数据,这可能会影响其性能。
如果一个内部人员的输入是另一个内部人员的输出,那最好。在这种情况下,您无需下载或上传任何内容:所有文件都在 BrainBox 缓存中,因此,只需要在内部人员之间传输它们的名称。
在 HTTP 请求级别,文件以标准方式下载:
curl -X POST -F "[email protected]" http://127.0.0.1:8090/cache/upload
之后,您需要使用以下 JSON 执行相同的添加/联接对:
{
"arguments": {
"jobs": [
{
"decider": "WD14Tagger",
"method": "interrogate",
"arguments": {
"image": "image.png"
}
}
]
}
}
结论
我希望给出的示例能够说服您在项目中尝试使用 BrainBox,并且此解决方案对您有用。我对它的稳定性非常有信心:大约一年来,我一直在使用 BrainBox 作为 Kaia 的支持服务器,以及研究服务器,用于执行诸如为语音克隆准备样本、制作 LoRA 模型、绘制图片或根据角色解释语音助手响应等任务。此外,我还在 BrainBox 上完成了几个小项目,例如为有声读物配音或描述照片中的衣服。
有关 BrainBox 的其他文档位于其 Pypi 页面上,其中包含以下主题:
-
管理在主机文件系统和容器之间共享的 Insider 资源
-
下载面向 Insiders 的其他模型
-
为 Deinsiders 构建工作流程
-
批量启动任务及其结果的集合
将来,我计划在我的项目中需要时添加新的 BrainBox 处理程序。此外,我想显著改进文档系统和 BrainBox Insiders 以及 API,使其在没有 Python 的情况下更易于使用。
应用
安装 Docker
在安装 Docker 之前,请确保尚未安装 Docker。为此,请在 Windows 和 MacOS 上查找 DockerDesktop 应用程序并运行它;在 Linux 上,此步骤不是必需的。接下来,打开命令提示符并键入
docker run hello-world
如果成功,将返回以 Hello from Docker 开头的文本!
如果缺少 Docker Desktop 且未检测到 docker 命令,请按照说明安装 Docker:
安装后,重新启动系统,打开 Docker Desktop,然后再次运行 docker run hello-world。
如果您的计算机上安装了 GPU,并且想要确保它已正确刷写到 Docker,请运行
docker run --gpus all hello-world
如果返回以 Hello from Docker!开头的文本,则一切正常。否则,您需要确保安装了正确的 CUDA 驱动程序才能与 Docker 交互。
安装 Anaconda
-
选择您的作系统
-
下载并安装发行版
在 Linux 和 MacOS 上,可以从常规终端访问 Anaconda 命令。在 Windows 上,您应该使用安装后出现的“Anaconda Prompt”程序。
通过 API 安装 Insider
以下代码检查是否安装了 Piper,如果缺少 Piper,则安装它:
from brainbox import BrainBox
from brainbox.deciders import Piper
if __name__ == '__main__':
api = BrainBox.Api("127.0.0.1:8090")
status = api.controller_api.controller_status(Piper)
if not status.installation_status.installed:
api.controller_api.install(Piper, True)
api.controller_api.self_test(Piper)
对于 HTTP 请求层,此检查稍微复杂一些,我也将在 Python 中引入它,但使用 requests 库。
import requests
if __name__ == '__main__':
status = requests.post(
'http://127.0.0.1:8090/controllers/status',
json=dict(arguments={})
)
containers = status.json()['result']['@content']['containers']
piper_container = next((c for c in containers if c['name']=='Piper'))
if not piper_container['installation_status']['installed']:
requests.post(
'http://127.0.0.1:8090/controllers/install',
json=dict(arguments=dict(decider='Piper', join=True))
)
requests.post(
'http://127.0.0.1:8090/controllers/self_test',
json=dict(arguments=dict(decider='Piper'))
)