本文介绍的三个项目是由Snips创建的,Snips是一家专注于嵌入式语音理解的法国初创公司,在2019年被收购后,Snips现在组成了Sonos的语音体验团队,Sonos团队继续开发和维护这些开源项目。
在一个年轻的软件生态系统中,如果您在初始生态系统基础的主要关注点之外冒险,通常会缺少一些库或工具。我们遇到过几次这种现象,有几次,我们试图迎接挑战,为生态系统做出贡献。
作为Rust语言的早期采用者,我们对在手机和其他连接设备上运行测试和工作台的实际困难感到沮丧。这导致了小艇的发展。在一个大玩家更喜欢云的世界里,在设备上运行神经网络模型推动了我们开发Track。最后,瞄准像Sonos设备这样的嵌入式计算机需要与其他本地软件进行大量接口。我们开发FFI-CONVERT是因为我们想让它对我们和其他团队来说都更容易。
Sonos是一家软件公司,除了硬件之外,Sonos系统的特别之处在于软件。软件堆栈跨越云、移动设备,当然还有扬声器和组件本身。设备上的软件必须交叉编译。我们玩家核心的小型计算机可以运行软件,但他们不能构建它-或者他们可以,但不是以一种非常实用的方式。
Sonos语音体验软件是用Rust编写的。该语言解决了大部分交叉编译问题。实际的交叉编译由Rustc、Cargo和Rustup进行本机处理。编译器Rustc构建在LLVM之上,因此它可以为各种架构生成代码。构建和依赖项管理器Cargo知道交叉编译,并可以相应地驱动编译器。Rustup使设置和保持最新环境变得很容易,该环境能够对许多架构进行交叉编译。但我们想要更进一步-我们想在实际设备上像在PC上一样轻松地运行测试和工作台。
Dinghy是一个Cargo扩展,它重新连接了熟悉的“run”、“test”和“bench”命令。它处理交叉编译,将编译后的代码部署到设备上,然后在目标设备上远程运行测试或工作台。它会报告结果,就好像测试在本地运行一样。随着Rust生态系统的成熟,我们为第三方库打了补丁,以便它们可以在ARM处理器上交叉编译和工作。Dinghy的一个雄心是让任何Rust库开发人员在自己的手机上运行测试变得微不足道,即使对于没有明确针对手机的库也是如此。我们希望任何Rust开发人员都能够在手机上交叉编译和测试,而不必是Android或iOS专家。
在Android手机上从任意工具运行代码相对容易。IOS手机更是一个挑战,因为iOS设备将只接受签名的代码,即使是测试也是如此。Xcode为iOS应用程序开发人员处理签名代码和证书管理的大部分复杂性,但在XCode之外将该过程复制到Cargo等外部工具并不是一件容易的任务。
此外,dinghy可以使用ssh瞄准远程设备。这使得使用树莓pi或任何单板计算机作为测试和试验台设备变得非常容易,同时将大量的锈代码编译到功能强大的英特尔工作站、服务器或笔记本电脑上。
今天,dinghy是SVE构建系统的核心,因为SVE代码大多是铁锈的。内部开发人员还在互动测试和长凳上使用它,目标是解锁的Sonos设备或标准单板计算机作为代理。我们还将移动平台支持保持在良好的工作状态,尽管它不是SVE的主要目标。
TRACE是一个神经网络推理库。最明显的神经网络库,如TensorFlow或PyTorch,都是训练框架;机器学习团队使用它们来训练神经网络。培训通常在云中进行,可以访问大量的计算资源和培训数据。网络经过培训后,必须进行部署,才能运行和执行其设计和培训的任务。
培训框架也完全有能力完成这项任务,称为“推理”。然而,他们倾向于支持培训方面,而不是所有其他的要求,这对推理用例是不利的。它们也是巨大的软件,对于资源稀缺的嵌入式系统来说,它们是一个相当昂贵的解决方案。对于嵌入式团队来说,开发硬编码特定神经网络设计的ad-hoc神经网络运行器并不少见。
我们选择了另一种方式,将TRACE开发成一个通用的神经网络推理库。我们确保它在标准用例(如图像分类挑战)中与其他库竞争。由于推理是一个比训练容易得多的问题,因此相当多的库独立于大型训练框架而存在于该领域。
但是,当涉及到神经网络时,语音、音乐、声音和其他面向时间的信号不一定是一等公民。以流方式实时运行还增加了对模型设计和运行时工程的约束,这些约束可能会避开现成的解决方案。拥有自己的图书馆让我们有机会将精力投入到解决特定的硬件或应用限制上。我们坚信拥有对业务至关重要的工程部件的优点。
虽然TRAIL最初是作为开发人员的个人宠儿项目启动的,但它投入了大量的开发时间,以使其能够在嵌入式系统上运行实时语音应用程序。索诺斯很高兴让Track的故事继续下去,将开发者的时间投入到Track上。在我们交换了一些限制条件,并将库推向新方向的同时,我们尽最大努力确保TRACTRA仍然是一个良好的通用神经网络推理库。我们非常高兴为tractjs的开发提供帮助,这是一种第三方Javascript绑定,允许在node.js中甚至在浏览器中运行神经网络推理。
如前所述,我们主要使用Rust作为SVE代码库。然而,Sonos生态系统的其余大部分都是用C++编写的,我们需要一种让两个代码库进行通信的方法。让两种不同语言进行通信的标准方式是使用C ABI。这是一组由C语言定义并解释应该如何调用函数的约定,它的优点是定义正确且稳定。这意味着大多数语言都能够使用这些约定调入C代码,或者任何遵循这些约定的代码。此过程通常称为FFI,即外部函数接口。
对于Rust,为了拥有与C兼容的接口,需要对结构的布局和声明函数的方式进行一些调整。我们需要重写一些高级Rust结构,使其更接近于较低级别的C语义。例如,使用原始指针而不是Rust引用,或者强制rustc编译器像C编译器那样布局结构的内存。我们最终拥有两个表示通过C接口的数据的结构:一个类似C的结构,在Rust中使用起来很麻烦(您需要使用“不安全的”Rust块来访问原始指针后面的任何内容)和一个纯Rust结构,它可以很容易地在Rust代码中使用。有了两个有效地表示同一事物的结构,我们需要一种轻松地将数据从一种表示转换为另一种表示的方法。
这个转换代码有点容易编写,但是它相当重复,而且有相当多的陷阱(使用不安全的Rust代码是不安全的)。这就是为什么我们决定创建ffi-Convert:一组使转换过程标准化的Rust特征,并辅之以Rust proc宏,以自动派生这些特征的实现。这意味着我们不必再编写不安全且容易出错的转换代码,因为它是自动生成的。这还确保了所有转换代码都遵循良好的实践,提高了代码质量,同时如果我们在处理转换的方式中发现问题,可以轻松地快速系统地更改所有实现。
虽然编写FFI-Convert是为了支持我们的用例,但它不包含任何特定于我们项目的内容。它对任何必须处理非平凡的FFI接口的项目都很有帮助。它在GitHub和crates.io上都可用,因此您可以很容易地使用它为您自己的Ruust项目创建一个C ABI。
这三个项目正处于生命中的不同阶段,从非常活跃的发展到成熟稳定。它们在我们的日常活动中都扮演着重要的角色,所以我们自然会花一些时间来更多地培养它们,或者保持它们的良好状态。我们总是非常高兴地从社区获得一些反馈,无论是以错误报告还是功能请求的形式,所以请随时给他们一个机会,并联系他们!