基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

6月7日,地平线「你好,开发者」技术公开课在智东西公开课顺利完结直播。公开课由地平线TROS.A系统架构师傅亚涛主讲,主题为《基于TogetheROS.Auto的智能驾驶软件开发范式》。

本文是此次技术公开课主讲环节的实录整理。如果对直播回放以及Q&A有需求,可以点击阅读原文前去观看。

大家好,我是来自地平线的架构师傅亚涛,主要负责TROS.A的技术规划和系统设计,很荣幸有机会和大家来分享《基于TogetheROS.Auto的智能驾驶软件开发范式》。

首先,我会简单介绍智驾开发中遇到的各种问题,然后会基于这些问题来看如何从设计角度将问题做简化;然后,我会把TROS.A基于这些设计方式和设计思路所做的相关设计及实践进行分享;最后,我会分享智驾软件开发的发展趋势,以及智驾中间件的发展方向。

01
智能驾驶软件研发的易与难
智驾已经发展很多年,近几年也越来越热。最开始我们做的时候,做一个智驾的demo都会非常困难。当时做一些AI应用,最开始是在一些通用的CPU上,之后是在一些类似于FPGA板上做智驾开发。当时参加各种展会的时候,如果有一套基于FPGA板的算法demo,且整体效果OK,就已经非常亮眼了。而近两年参加智驾相关的展会,如果拿不出一个实车的demo,然后做一些试驾评测的话,在整个展会上看起来会薄弱很多。
为什么现在整个智驾开发较之前成熟且看起来简单了许多,我简单总结了一下。
基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录
一是智驾开发生态百花齐放。大家可以看到,目前基于智驾的各种开发生态环境以及开源软件发展地非常好。一个典型的例子,比如说ROS2。可以看到基于ROS2往上,做的感知、规控这种智驾的服务,以及往下的厂家硬件接入,现在已经发展得比较成熟了。此外,目前各种开源软件在智驾里面使用很成熟,并且经过了多轮迭代,比如DDS、FAST DDS、Cyclone DDS。同时,有多家DDS商用的开发商在开发这种功能安全的DDS版本。另外一个就是AUTOSAR。最近几年,AUTOSAR AP的标准发展非常快,可以看它最近的一些更新。实际上,它对于智能驾驶往上的AI开发,会变得越来越友好。现在很多特性,都已经很贴近上层的应用开发了。算法的话,基本上每过一两年,算法都会有一个大的迭代。从原来的最早的检测模型,到BEV,再到Transformer,算法的迭代非常快。而且,基本上是业界有什么新的算法,大家很快就能跟上。硬件平台算力方面,每年都可以看到哪个厂家的算力破新高,发了新的计算平台。二是智驾开发能力快速提升。可以看到,整个智驾,从研发的投入上来说,增长非常明显。这周我用智驾这一关键字查了一下相关的企业,大概有15000多家,但里面有一些可能和智驾关系也不大,但相较几年前来说,这个数量增长的非常快。而且,涉及到智驾的开发人员也大幅增加,大家从技术栈上,成长也非常快。然后,从平时接触到的各种相关厂家,比方说OEM、Tier1、Tier2,现在基本上都把自己的能力栈建立起来了。原来很难搞定的一些事情,现在来说可能都是一些基础的事情。算法原型以及工程的原型,现在也有各种开源的实现、各种样板间。算法的迭代方法,包括方法论,也不是一个新鲜的话题。有很多的公司实际上都建立了类似的能力,让整个算法迭代越来越快。最后,可以看到,目前做智驾已经初步形成了一些分工。往往在做一个量产产品的时候,会遇到好几家方案解决商,然后多家协作,这家做感知,那家做规控,另一个做硬件加集成。整个量产好像变成了一个拼图游戏,相对来说,难度大幅降低。相对于几年前,智驾开发难度低了很多。但从目前来看,是不是真的低到了大家都可以去做,并能做完整的量产,也是不一定的。我和很多OEM的小伙伴聊过,得到的一个反馈是:现在和任意一家方案解决商或者其他从业者,去聊有个东西能不能做?能做;但是能不能量产?现在还没有验证过。所以,基本上是一问就会,但真正到量产的话,还有很多困难。这就是为什么从做一个智驾的原型到量产会这么难。基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录首先,从开发模式上,我们常见的智驾开发模式,这里写了主要的两种:

一种是典型的V模型。做过智驾开发的应该都会比较熟悉。这个V模型,一般是从研发侧,基本上是从系统需求、系统架构,再到子系统的设计研发。相对于常见的V模型,从需求,到设计,到研发来说,智驾会复杂很多。

基于智驾的软件规模,基本上经常做的是一个大的系统需求,从顶层的架构,再分解到多个不同的模块或公司。这个公司本身会再去做顶层的系统需求的分解,然后再去做设计。这种层级可能有两到三层,导致的一个问题是:在需求设计上经过层层分解,而这个分解的过程和软件研发其实是分离的。需求设计可能更多停留在文档上,研发基本上就到了整个系统的设计,然后再到编码。这个过程其实是分离的,流程越长,分离的越开的话,会造成在真正开发过程中,越容易造成变形。有可能做着做着就做歪了,有一些东西可能会漏掉。

反过来做这种集成的时候,又会发现有这么多层这么多组在并行开发,反向去层层做集成的时候,集成的成本非常高。遇到一个小问题,可能都需要拉一大拨人去长时间的定位。一个问题当涉及到跨团队跨公司的时候,来回踢皮球的情况,其实非常常见。

另一种开发模式其实也很常见。我们一般会去做一个原型系统,其实是用来做算法验证快速迭代的。基于这个原型系统,并对效果做了一定迭代之后,再往量产的环境上迁移这时候会面临很多问题:

一是原型系统和量产环境的架构差异很大。在原型上,可能是怎么快怎么来,比如说基于ROS2,或者基于Python来写;但在量产上,比方说,可能需要一个功能安全的软件底座,为了性能的话,可能会基于C++,甚至写一些性能更高的定制化实现。这就导致迁移过程很重,软件的效果一致性以及算法的一致性很难保障。此外,在原型开发的时候,大家更多关注的是算法的效果,但很少关注软件的性能,这会导致在原型系统向量产系统迁移的时候,性能优化成本非常高,整个软件的质量收敛速度会较低。

另外一个问题刚才也提到过,就是我们现在的智驾开发,通常是多团队开发,很多时候还是多公司联合开发。那它带来了一个问题,就是大家都有自己的一套技术栈,然后底下的软件栈,包括中间件和基础库可能也不太一样,最终集成到一个系统会非常困难。若出了一个问题,其实很难定位,比如某个线程执行发现总是出现delay,这种时候去定位线程被哪些信息影响,是非常难定位的。因为大家对于任务划分的粒度,以及开发控制的界面都不同。

此外整个智驾涉及到的系统非常复杂,不同模块间的影响会非常大。比如感知输出的周期可能不是那么固定,或者我的Latency的方差会比较大,就会影响到后面,比如预测、规控、定位相关的计算效果。

另外,前面也说到,多团队的开发,由于底层的框架不统一,再来做性能优化的时候会非常难。我们和一些团队接触过,在某些情况下,整个软件的线程数可能会达到三到四千。这种情况下,想去优化某个具体任务的性能会非常难,不一定是这个任务算得慢,而是整个系统就调不过来。同时,在这种规模下,去做问题定位,会很困难。比如说运行的过程,若发现延迟不符合预期,或者整个过程block住了,要定位是哪个模块的问题,都非常困难。

最后,整个软件栈的话,从最上层的算法、工程、策略,再到中间的中间件,再到底层的BSP、Driver是非常复杂的,有时候面对一个简单的问题,可能会需要联合多个团队来进行联合定位。所有的这些都会造成一个问题,就是在自己模块开发的时候,可能已经开发好了,但一旦真正做集成量产的时候,会发现整个周期依旧拉得非常长。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

第三点,前面也有讲到,现在做智驾这一块,涉及到的知识面,不管是横向还是纵向,都非常广。比方说在基础能力方面,你可能需要C++,然后也需要有些python或者shell能力,至少能看懂算法工程师写的算法策略;然后可能需要去了解一些基础的系统知识、基础的工具;做性能优化的时候,常规的优化手段,比方说线程池、协程池、对象池、内存池等会用上去;对于热点,可能会做一些定点化的优化,然后做一些OMP、neon、汇编优化都有可能;然后问题定位的时候,用GDB还有ftrace、地址消除器各种各样的工具来去定位,比方说热点的性能问题,或者一些内存问题。

虽然做工程开发的时候,可能大部分用到的是软件工程的能力。但做智驾的话,其实需要有一定的算法知识。最基础的,例如数据结构算法,是通用知识;另外基础的CV算法肯定要会,比如至少要去了解一个图怎么转成一个BEV的图,大概的原理和计算方式是什么;关于机器学习,可能相关的算法工程师会研究的更深入,但在做软件开发的时候,至少要了解对应模型是什么、大致的逻辑以及它的输入输出、相关的处理调用;做性能优化的时候,对于常用的调度算法,肯定要有了解;此外,还涉及到数据压缩、数据加密、标定算法等。

另外就是业务相关能力,各种传感器基础知识、硬件基础知识;然后AutoSAR、ROS、DDS这种常规的软件,各种数据、渲染工具,以及功能安全一类的等。

所以,做智驾方面的软件开发,对工程师的技术栈要求会非常高,也带来几个问题:一是很难招到合适的人;二是从整个团队来说,新人进来要真正地能开始动手干一些比较实际的活,可能需要几个月的培养;而他开始能做一些跨模块复杂的工作,可能需要一年左右。所有这些都会导致整个软件开发难度加大,整个开发的带宽会非常地受限。

我这里面只列了一些常见的问题,当然会有更多的问题。比如整个智驾,要去做数据的管理,从采集到传输分析,然后再到Software2.0这种数据驱动整个软件的迭代。

02
从设计角度如何化难为易
今天我主要是从软件的角度来看怎样去减少这些软件开发中各种各样的问题带来的复杂度。接下来,从设计的角度简单介绍一下如何化难为易,分两个方面:
基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录
首先,从横向来说,因为会面临多团队多公司的并行开发,所以在整个设计上肯定会选择,例如SOA架构,来进行横向设计的解耦。各个团队开发的功能模块需要能做到自洽可验证,但仅仅只做到SOA,可能不太够。除此之外,在顶层设计上协议标准需要是统一可控的,且模块间的交付界面需要非常清晰。这种交付界面,除了SOA的基础的服务协议以外,实际上对于模块的约束,也需要有完整的定义。前面讲到在整个V模型开发里面,做设计开发分解其实是会有多个层级。在顶层的约束一层一层往下分解,需要在架构上能支持一层一层的传递下去。除了这些以外,还需要整个架构能提供非常丰富的问题定位能力,至少可以快速做分责。在做软件开发经常会遇到一个情况,就是一个问题涉及到多团队,然后这个问题的定位就会变得非常困难。这个时候其实就需要整个框架有能力,能很快速地确定问题具体在哪一端,然后involve相关的人员进行分析,有效地加快整个软件开发的过程。在做顶层架构的时候,实际上会做两部分东西:一部分去做功能界面的分离。比如,有哪些大的模块?它的能力是什么?它的接口是什么?然后,另外一个,我们会做算力以及部署上的划分。但这一部分,一般来说可能会做非常顶层的划分。比方说,感知和规划放在哪一块,定位地图放在哪一块。但再往下的话,一般不会在设计阶段做太多约束。这个就要求框架能支持设计实现和部署的分离解耦。也就是说在设计实现的时候,我来做开发,然后多个模块有可能是完全分离地去做开发;但实际部署的时候,我可能会把它部署在同一个进程或者同一个服务内。当有问题,或者算力有一些不足,或者有一些调整的时候,需要灵活地把这些模块再分离成不同的服务。部署与实现分离,从架构层面支持,不需要软件开发去做过多的适配和考虑。要实现上面这个能力,就需要底层在架构上做到统一。这其实也是现在合作分工开发的一个痛点,就是大家现在或多或少都有自己的一部分内容。现在看到的一个情况是,如果底层架构不统一,遇到一些问题可能根本就没办法定位。前面举过一个例子,我们有遇到整个应用非常复杂,几千个线程。而线程这么多的原因是架构底层完全不统一,都有自己的线程池,有自己的调度策略。在这种情况下,出现了一个高优任务延迟上百毫秒才执行的情况,根本就没有办法进行问题定位。基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录说完横向的设计,再从纵向设计进行说明。前面有提到,现在智驾的软件开发,需要了解的知识点会非常多,对人员的要求会很高。这就需要从架构上去做逻辑概念的分层和屏蔽。一般来说,大部分算法开发人员和应用开发人员,关注的点,其实是算法实现或者是应用的逻辑策略实现。其实不必再往下层关注,比如应用应该怎么配,资源是怎么样,应该和谁通信。再往下层,会有同学负责应用的集成。他可能对上层的算法策略、应用策略开发并不关心。他关心的是整个应用资源的分配,从功能安全怎么去做配置,整个通讯拓扑怎么配,整个应用怎么部署执行。然后再往下,其实会有一些基础的能力。比方说传感器接入,异构加速器的使用,各种比较常见的算法、基础服务、基础库。从框架上来说,属于非常固定的东西。这些在整个框架内应该是已实现的,用户可直接调用,完全不关注它的细节。这样,从纵向上来说,做到多层的概念和任务分离。当一个应用开发人员只关注他需要关注的部分的时候,对开发人员的要求会低非常多。这么做的好处,是整个软件开发对于开发人员的要求降低,整个团队更容易搭建,也更容易进行扩充。

03
TROS.Auto开发最佳实践
从设计角度围绕横向和纵向简单做了分析,接下来和大家分享一下TROS.Auto怎么样从横向和纵向两个维度简化整个智驾开发的过程。基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录首先,什么是TROS.Auto?这个图是我们现在的系统架构图。看起来很复杂,因为里面包含的东西会很多。在基础应用框架里面,有Communication做通信框架、DataFlow去做应用界面以及调度、会有各种的基础服务,如EM、SM、 PHM诊断;另外,我们也会做很多SoC上的基础能力抽象,比如用easyDNN来做模型推理的抽象、用HobotCV来做各种CV硬核的抽象,也提供了各种基础库,做了各种工具;在PC端,我们做了整套从设计,到开发到部署,再到调试的开发工具,也做了类似于录制回灌、2D /3D渲染的数据相关的工具。然后基于这一框架,我们也做了很多标准模块。比方说,sensor center用来做传感器接入的基础服务,vehicleIO用来做车辆底盘信号的接入。然后,再往上也有我们各个项目做的标准的服务,比方说一些trigger或者标定。基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录前面看到这种架构图确实会比较复杂,它是属于我们内部的一个设计架构。但从大的概念上来说,简单来看,会包含五个部分:一是应用框架,就刚才说的Dataflow用的开发和调度的界面;Communication是属于底层的通信框架;二是开发工具;三是功能模块,是基于我们的框架之上做的一些标准的应用模块;四是各种基础库;五是基础服务,我们参考AutoSAR AP做了自己的一套实现,里面的一些概念会有些类似。从用户,或者从开发者视角来看,其实并不需要了解这么多。从最顶层的开发视角,比方说我是做算法策略开发,或者做应用逻辑的话,了解的概念主要是这三个:基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录第一个是package,实际上是最顶层的一个概念,用来做功能模块的打包封装,然后可以去package去做模块的共享以及执行。相当于我在做应用开发时候,可以把package作为一个功能模块引进来,依赖它去做开发。我也可以把多个package放在一起 然后直接去执行。这一块,我们也提供了相关的工具,把基于TROS.Auto开发的工程打成package,提供部署安装编译执行的一些基础的能力。然后,package往下,graphlet是我们软件模块的一个顶层概念,很多时候我们也叫它为DAG,实际上是一个有向无环图的概念,由多个软件模块组成,构成了一个基础的执行单元。整个graphlet是支持嵌套的。相当于一个graphlat里面可以嵌套多个不同的子图。我们可以基于graphlet这一层级,去做整个应用的调度部署相关的一些配置。这一块更多会在整个应用的部署、调度优化的时候,会从这一层的概念上出发,做相关的配置和优化。最底层的概念module,是我们的功能模块开发的最小单元,一般是用来封装一个最小的任务的概念。从图上可以看到,一个module里面包含三个概念:

一个是port,是我们功能模块的一个输入输出的端点。一个module可以有多个输入,也可以有多个输出。

第二个概念proc,是最小的执行单元。在很多情况下,可以认为一个proc 其实就是一个task,它会由触发器来触发进行运行。这个触发器有可能是基于前面的消息触发,也有可能是基于一些时间触发。然后,实际触发之后的执行会由底层的调度器来执行。用户从开发界面其实不太会关注具体怎么调。怎么调其实是调度器部分的策略,它的配置更多是在graphlet这一层,我们来看这个子图应该是怎么去执行。

第三个概念filter或者是condition,其实是proc的触发条件。一般来说,一个proc可能会由多条消息或者多个条件,来进行触发,我们其实是内置了一些常用的。比方说A消息和B消息同时到达,再执行proc A;或者A消息或者B消息随便来一个,我都去执行。同时,这部分能力,也支持用户做扩展,比方说我们自己常用的一个扩展,会对消息进行筛选,来做消息的时间对齐。

单纯从用户的开发界面,主要了解这三层概念,就可以进行相关的功能模块的开发。

怎么样从TROS.Auto上做设计,前面其实有提到。TROS.Auto从概念上可以分三层:package,graphlet和module。这其实和设计是相呼应的。前面讲到V模型有多层的设计分解,体现在TROS.Auto里面,我们从设计上也做了一个层层分解的设计。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

在顶层架构上,可能会有一个非常大的graphlet,里面的模块相对来说可能都是非常粗粒度的。比方说,感知就是一个大的module,或者是一个package;定位、预测、规控都是相关的这种粒度。基于这个确定这些大模块之间的交互是什么样的,约束是什么样的。

然后再往下,我们会做模块的分解。比方说感知,是由一个复杂的graphlet组成的感知。由于前面感知在顶层设计上,已经定义了它的输入输出的标准,以及对于感知的限制,比如执行资源或者其他的限制。这时展开做设计时,这些限制和输出,天然会在下一层设计上会被引入进来。这一层的设计和顶层的一样,对这一层做架构的分解。

再往后可能会有更进一层的架构分解,也是一样来做各种输入输出、限制的传递,来保证整个架构从顶层到底层,是基于同一套约束层层传递,不会造成架构多级分解的信息丢失或者变形。

整个设计,到最底下一层的时候,支持基于这个设计去做对应的代码框架生成。在生成的代码上,天然的里面定义的各种输入输出、各种限制,就会嵌在生成的代码框架里面。用户在这一套框架里面,去做自己的功能开发就可以。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

这是TROS.Auto真正从设计到实际研发的一个大致过程。可以看到,最左侧,架构已经分解到最底层,可能在我这一层是一个比较小的功能模块。在这一层上,我们定义了一个graphlet,里面的模块与模块间的数据流会被定义出来。基于它,我们会去用工具生成整个应用的代码的框架,这个框架在这个时候实际上是属于已经可编译、可运行的框架,只是没有具体的业务逻辑。用户在开发的时候,基于生成的这个框架去做具体的功能模块的应用开发。因为输入输出已经定义好,基本上是把自己的业务逻辑加进去。

可以看到,部署、调度和开发是分离的。因为从我们的设计上来说,用户真正做这种应用的业务开发的时候,不应太关注应用如何配置以及如何调用。它实际上是配置运行的一个过程。会有少部分的同学做应用集成的时候,会更关注应用怎么配置,以及应用怎么做调度优化。他和实际开发同学有可能是同一个人,也有可能不是。所以,这两部分概念在设计里面应该是分离的。最终在部署配置、调度优化之后去上板运行。

整个开发过程是这样一个过程的循环迭代。部署运行之后,来看功能以及性能,效果是否符合预期;如果不符合预期,可能会返回到模块开发、部署配置、调度优化,来循环做这种持续的迭代,最终达到一个比较稳定,比较好的效果。

刚才有提到,我们将整个软件的应用逻辑的开发、部署以及调度做了隔离。这里可以看到,我们是如何做纵向封装来减轻用户在调度方面的投入,和降低调度的理解成本。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

左侧是基于刚才说到graphlet在工具上通过拖拉拽的方式,生成的一个graphlet的实例。它会生成具体的一个框架代码。

可以看到,实际上它定义的是刚才的内容:input的一个port,output port以及里面不同的proc;input和output之间会去定义一些具体的执行触发条件。

整个代码框架生成之后,用户基于这个界面去做具体模块的开发。而实际在输入的时候,用户其实不会太关注具体每一个proc怎么配。因为整个支架其实里面任务数会非常多,可能几千个任务,或者上万个任务。如果对每个任务去配它的调度策略,比方说绑核、优先级以及任务之间的这种关联关系,其实会非常困难。都做这种配置,在某种程度上可能性其实很低。

所以,目前我们的做法,更多是在用户界面上基于graph来配。就是整个graphlet实际上会有一个子图的概念。前面有说过graphlet也支持嵌套。那我的配置可能更关注执行链路应该有一个什么样的执行效果。整个graphlet,我会划分成不同的子图,不同的子图有不同的调度预期。而实际的调度是由底下的scheduleGroup来进行调度和分配。scheduleGroup里面,我们现在有做大概4种不同的调度器:基于定时的(Timer Scheduler)、基于线程优先级的(Priority Scheduler)、基于公平调度的(Deteministic Scheduler),也提供了基于协程的调度的能力(Coroutine Scheduler)。当然,每一种调度器里面有很多种策略。

对于调度的确定性要求比较高的,我们也提供了基于任务链时间做编排的一些调度的逻辑。所以,整个来说,在上层用户的开发界面,可能更多看到的是,我希望graphlet达到一个什么样的调度效果;映射到底层的scheduleGroup会映射到不同的调度器里面去;这些调度器再基于它的调度策略来进行实际的调度。

右下角这个图,是我们在进行了调度配置之后,实际生成的一个调度配置文件。可以看到,不同的调度器可能会有不同的配置字段,但这一部分更多的是基于我们的工具来帮你生成。当然一些工程能力比较强的同学,可以直接基于这个配置文件去手动做修改。我们尽量简化整个应用在调度上的复杂度,希望通过框架来屏蔽整个调度的一个过程,做到应用开发和调度的分离。

前面也有提到,我们也做了应用开发和部署的分离。分离这部分是基于我们的通信框架来实现的,主要由两部分能力构成:

一部分是我们实现了一套IDL工具,目的主要是将消息定义和实际的序列化方式做解绑。做软件开发的同学可能会体会到,实际在做消息定义的时候,一般会绑定某一种序列化方式,比方说绑定protobuf,或者DDS的话,有可能是Fast Buffer或者其他的序列化方式。

这会导致一个问题,就是当我们一个消息发送给不同的序列化接收对象时,在代码里面可能需要定制开发。比方说我发给对端,对端是走DDS的,我可能要在发送的时候就给他绑Fast Buffer;如果对方是进程内,我可能什么都不绑。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

我们IDL目前做的方式,实际上就是将消息和序列方式做解绑,从用户界面看到的消息实际上就是一个一个的struct,我们当成常用的结构体来进行使用。真正和对端通信走什么样的序列化方式的时候,实际上是IDL生成的library来做决定。我们会在里面做IDL 的扩展,比方说,现在默认其实已经做了protobuf、 Fast DDS 的序列化方式的扩展,也可以扩展第三方自定义的序列化方式。在这种情况下,整个消息通信走哪一种,更多是运行时看通道的配置。

另外一部分就是具体的通信和通信协议、通信链路的解绑。我们开发了一套通信框架,它对上提供了这种常用的Pub/Sub、CS和Action的通信接口。然后往下可以看到,会接入非常多的不同的通信协议,比方说DDS,ZMQ,PCIE或者一些其他的。我们当前的做法是一种插件化的接入,也就是说我实际使用了哪一种,我把对应的SO扔进来就可以支持。但是对上来说,用户使用界面是完全不感知。

具体使用哪一种通道或者协议去跑,更多决定于我运行的配置。可以在部署配置里面配,也可以基于类似于Hybrid的方式,让它自动去选择,这个更多取决于我们使用的时候的一个场景。通常来说,我们可能在调试的时候用一些Hybrid的方式,但真正量产的时候可能会把通信的配置给固化下来,直接走平台定制。

通过这两种方式,我们就做到了整个通信方式和实际的通信链路以及通信协议的解耦。这时候就可以做到多模块开发的时候,开发的接口完全一致,但把它部署在同一个进程,还是部署在不同的进程,甚至部署在不同的SoC上,这时候应用的代码是完全一致的,它只是一个运行期的配置,就达到了前面说到的软件研发和通信配置的解耦。这种能力在后期做功能调试的时候,会非常有用。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

第三个方面,我们也做了基础服务简化。前面有聊到过,我们有参考AP来定义一套我们自己的基础服务,比方说EM、SM、PHM诊断时间同步。在这一部分,前面有讲到在Dataflow这一层,我们有DAG或者是graphlet这层概念。很多这种基础能力,我们可以在这一层界面上和它打通。

比方说EM去做应用生命周期管理,但应用生命周期某种程度来说,是应用的一个graphlet执行的管理。是把graphlet的运行起来,还是停掉或者暂停,都可以通过这个界面。

SM其实也类似,比方做functionGroup。functionGroup粗粒度的可能是进程级的进程组控制。它和前面的EM的逻辑有点像,就是我去做进程级的graphlet的启停。

基于graphlet,其实也可以做更细粒度的。前面有讲到过,graphlet支持嵌套,我们可以在这个概念上,去做更细粒度的进程内的一些子功能的控制。

PHM和诊断这一块,我们在框架内会有集成。因为框架本身也会有一些PHM的检查,比方说执行逻辑或者执行超时,也会有诊断信息的上报。用户也可以调这一块的接口去做执行检查和上报信息。这一块其实对用户来说是可见的一部分内容。

其他的,也有一些基础的库,比方说时间相关的timer,我们也做了封装。基于它可以屏蔽掉底层的时间变化,比方说基于系统时间、基于虚拟时间,还是基于外部时钟源。在应用开发界面,就不需要特别关注切换时间导致的代码适配变更。

另外,我们其他的一些基础模块也是类似的一些设计思路。比方说也做了一套log的前端,统一由这个前端按照固定的格式进行输入。那它的后端可以接各种各样的log后端的服务,比方说默认做这种文件传输,但它也可以接比方说安卓的Alog,也可以接AutoSAR的log service,或者一些其他的自定义的log服务,来做log后端log盘的管理。

整个基础服务,和AP很大层面上概念会比较接近。所以,在实际的项目里面,我们也遇到厂家已经买了某种AP。但对于我们来说,这种基础服务和AP的替换,从应用的开发界面来说,其实是无感的。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

前面简单聊了一下,TROS.A从横向或者纵向是怎么样设计,来简化整个软件开发的界面,并降低智驾开发的难度。举一个简单的例子,刚才也讲过我们会做一些能力的屏蔽。实际开发的时候,我们可能的一个做法,在顶层上会有一些大的模块:感知、定位、预测、规控。这些模块内部本身是一个graphlet的组成,里面不同模块有可能是用户已有的一些模块,可以从某个其他项目直接给迁移过来。因为每个module都是可以单独进行引用的,也有可能是你自己去开发。

当然,从TROS.A来说,我们也做了很多标准的功能模块,比方说Sensor center、VechicleIO、Odom、tf,我们其实做了一些标准实现,可以直接用。这一整套架构会在DataFlow的开发界面上,进行框架的组成以及调度的配置划分。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

做完开发之后,接下来就是去做调试。在很多时候,调试比开发更麻烦。前面有讲过,现在做软件开发,其实并不是我一组人聚在一起把整个软件开发就做完,很多时候涉及到跨团队甚至跨公司去并行开发。在这种时候,需要很完善的工具来进行问题的定位。某种程度上来说,很多时候我们需要快速分锅。

我们会提供一些基础的能力:

首先是在线调试。在整个软件的graph上,我们在不同层级提供了不同的能力。

在通信这一层,我们做了一些工具,用于查看整个通信拓扑以及通信节点的状态;同时,我们也做了通信相关的统计,比方说通信节点发送了多少数据,接收了多少数据,接收的延迟是多少;也会有工具去做模拟的数据发送,来进行问题的定位跟踪。

从调度的层面,前面其实有讲过,我们是基于上层Graph界面做调度。但在执行的时候,我们基于proc这个力度做了执行统计,包括执行时长、CPU占用情况。从右侧可以看到,这个图是studio上的一个工具,显示的是整个应用在不同的计算核上的计算分布情况。整个应用在不同计算核上,在某个时间段是哪个task在上面执行。基于这个,可以比较清晰分辨出具体的软件执行情况,来做各种性能问题、功能问题的定位;同时,我们也提供了一些msg trace能力来跟踪整个消息在整个graphlet里面是怎么运行的,可以有效地去做功能定位,比方说我跑着跑着block住了,或者在某些节点有大量的消息积压。

从控制的角度,基于graphlet这一层,我们提供了一些控制的能力,包括基于module/ proc的启停控制、基于graphlet的切换,还有一些配置相关的,这个在功能开发阶段会非常有用。一般不会在量产阶段去做控制,但在功能开发阶段,可能会为了调整功能,需要把整个图里面的某一部分给停掉,或者需要直接把图的执行逻辑改一下。

最后会有一些状态的数据。像前面说到通信的状态、module的节点的状态、整个系统的状态,包括各种资源的使用率、系统执行的基础情况。这是我们在线调试阶段提供的基础能力。

其次,我们有做各种数据分析的一些工具。首先,我们会提供基于pack的各种数据相关的工具。pack是地平线自定义的一套深度数据存储格式。基于pack,我们做了一系列的数据存储查看、查询分析的工具。比方说右侧第二个图,就是用来查看pack里面具体的数据内容。基于pack,我们也做了各种命令行的小工具,比方说基于topic做数据的录制、做数据的回放。在这个过程中做各种基础的控制以及做pack的数据转换。这个转换更多是和第三方做一些数据兼容.比方说,我们现在做了RosBag-to-pack的转换,可以和RosBag做一些简单的互转。

最后,我们也做了一些数据展示相关工具。比如文本的展示,像pack数据展示分析、log数据的展示分析和过滤;以及2D数据的展示,比方说统计数据、2D的image渲染;还有3D的数据展示。

基于这些工具,不管是在开发过程中进行功能调试,还是在出现问题之后,去确定问题是发生在哪个模块,进而进行问题的分解,会提供非常大的帮助,能有效提高开发效率。

04
智驾发展趋势对软件开发平台的影响和要求

前面简单介绍了我们基于TROS.A做软件设计、开发、调试的一些基础内容。接下来,想和大家分享和讨论一下,智驾的发展趋势对软件开发工作的影响。这也是地平线目前正在讨论和预研的,希望分享出来后可以引起更大范围的讨论。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

首先,一个很明显的趋势,提出来也有很多年了。智驾相关的电子电气架构,从传统架构,往中央计算平台架构进行迭代,这一趋势已经非常明显了。可以看到,有很多家都提出了舱驾一体。

各种算力平台,特别是一些大算力平台,基本上也可以开始做计算任务的融合。这对于软件开发平台或者说中间件,影响会是什么?

虽然大家在一个平台上,但实际执行的时候需要做多域的隔离。最简单来说,虽然做舱驾一体,但座舱和智驾在很多地方要求会不一样,比方说功能安全等级。这种时候我们就需要做多域隔离。在底层,比如BSP这一层,要做资源隔离引擎;在更上一层,需要去做基于资源隔离引擎的应用框架,然后基于它来做各种调度相关的任务。

基于中央计算平台,整个数据的通信模式会发生比较大的变化。现在常见的多SOC,会有很大的数据量去做SOC之间的交互,比如通过网络、通过PCIE或者通过其他的一些通讯链路。中央计算平台下,数据通讯其实更多会往IPC的方式去发展。这就会要求整个软件平台对于内存管理、对于IPC零拷贝,会提出更高的要求。我们现在也支持零拷贝,但更多是在SoC内。但基于做多域隔离之后,怎么做内存管理,怎么做零拷贝,可能是后面需要去优化的一个方向。

由于多个不同应用都在一个计算平台上运行,但各种计算核实际上是同一套。CPU相对会比较好处理,比方说,有8核或者16核,做多域隔离的时候,可以分别给2个核、4个核。但其他的一些计算资源,比方说DSP,GPU,BPU,可能不会按照核来划分。这时候就需要有设备虚拟化的能力,对不同的应用来说都是一个完整的设备。这些设备可能会使用不同数量的计算资源。我们可能会基于设备虚拟化的技术,去做计算资源的管理。对于上层来说让他能无感地进行使用,而不需要考虑太多应用的计算核的抢占。

最后,基于同一个计算平台,调度其实要求会比较高。前面有讲到,我们现在看到已经有几千个线程,再往后这个数量只会更高。那在这种情况下就需要全局的调度能力。比方说你可能总共就8个核或16核,如果有4000或5000个线程,那花在线程切换上的代价会非常高。我们会真正的把上层的计算任务和底下的执行调度,去做分离和解耦。在全局上去做调度资源的分配,有效地把计算资源给利用起来。

以上是刚才提到的中央架构的演化对智能驾驶软件开发范式的影响。

基于TogetheROS.Auto的智能驾驶软件开发范式 | 地平线「你好,开发者」技术公开课全程实录

另一个比较显著的趋势,就是整个智驾智能化的程度会越来越高。最开始我们做智驾的时候,相对来说AI占的比重会比较少。更多的是在感知部分,做各种物体的感知。但现在可以看到,AI模型的能力会越来越强,比方说BEV大模型或者Transformer。另外一部分,就是我们也会把越来越多的任务往AI核上放。比方说,规控有可能就直接在AI核上跑。所以,整个智驾的AI化程度显著的提高。然后和一些同学聊的时候,也得到一个观点,大家也在想后面整个智驾会不会就是一个AI大模型?前面数据结构,中间一个AI大模型,后面直接接对应的控制器。现在确实有这个趋势。

我们前面有提DAG或者graphlet,可以看到它更多是一个在CPU上的纯软的graphlet的概念。但再往后,我们在SoC上不同的计算核越来越多,能力越来越强,整个应用的graphlet可能越来越多,直接在硬件上跑,那实际上整个graphlet可能会硬件化。有可能一个大graphlet,中间有很大一部分都是直接在不同的专用计算核上跑,比方说在BPU或者在DSP上。这种时候,它的调度逻辑、执行逻辑,和在纯CPU上跑会非常不一样,比方说不需要来回去做CPU的中断,通信链路上也不需要在CPU上把数据来回倒腾。整个稳定性和性能其实会有非常显著地提高。

对于开发平台来说,对于这种趋势,我们可能需要设计合适的异构调度框架。然后和现有的调度框架融合起来,做无感地、平稳地过渡。

第三个点,刚才其实也有聊到,大家在说后面整个智驾会不会往大模型的方向去走,有可能智驾工程开发的内容会越来越少。现在,算法工程师和应用工程师其实工作分得还比较开:算法工程师去做算法的迭代、模型的效果,应用工程师来做整个应用的落地。后面这部分工作的区分,就不会那么明显。

对于整个开发平台来说,可能需要提供的一个能力,其实就是“原型即应用”。我们真正在做算法原型的时候,比方说我在x86上做算法原型,直接就可以在SoC上直接去跑,做到无感地切换。把里面大量的通信调度相关东西,屏蔽在底层,上层的工程师可能就不必太关注。

整个应用的开发界面,可能就不会像现在这样,基于C++,然后一个一个的coding,可能是基于一些DSL,甚至基于python的方式去做应用的开发。但底下执行可能是基于编译器,或者基于一些转换的工具,将它转化为可高速执行的二进制文件直接执行。

所以,整个智驾AI化程度越来越高,有可能算法和应用的分界会越来越不明显。对于应用开发平台来说,怎么样去做算法开发、往应用开发过渡以及直接运行,也是我们在探索和讨论的一个点,希望和更多业界的同学有更广泛地讨论。

关于发展趋势,我先和大家分享这两个点。当然,会有更多额外的内容,比方说GPT相关的内容,及Copilot相关的内容等等。这些对后面整个智驾开发的影响都会非常大,期待之后有机会和大家做更多的探讨。谢谢大家。

END