Janus: 基于 eBPF 的 5G 实时 AI 控制器(下)

简介: Janus: 基于 eBPF 的 5G 实时 AI 控制器(下)

附录

附录.1 加载 Janus 小程序集及小程序调度

正如第 3.1 节所解释的,Janus 小程序通过 Janus 控制器提供的网络 API 进行 JIT 编译并加载到 Janus 设备上。开发者可以使用 Janus SDK 提供的工具将开发的小程序上传到 Janus 控制器并加载到 Janus 设备,如图 10 所示。将小程序上传到 Janus 控制器并加载到 Janus 设备所需的指令被编码在一个以 YAML 格式编写的描述符文件中,其结构和属性在示例 6 中列出。


1 codelet1: 2   codelet: codelet1.o 3   hook_name: hook_name1 4   priority: codelet1_running_priority 5   runtime_threshold: codelet1_max_runtime 6 codeletN: 7   codelet: codeletN.o 8   hook_name: hook_nameN 9   priority: codeletN_running_priority10   runtime_threshold: codeletN_max_runtime11   linked_maps:12     codeletN_map:13       codelet: codelet114       map_name: codelet1_map

复制代码

示例 6: 小程序 YAML 格式


YAML 文件指定了小程序集中所有小程序的名称(ELF 格式的 eBPF 字节码对象文件),被链接到 Janus 设备中的钩子名称以及调度优先级(多个小程序可以被链接到同一个钩子)。YAML 文件为进一步配置小程序提供了额外的可选配置参数,例如,在利用 5.1 节的运行时控制机制时,为每个打补丁的小程序设置运行时阈值。


图 10: 将小程序加载到 Janus 设备的过程。


同一小程序集的小程序可以共享状态并协调执行,这在执行监测或控制操作时特别有用,这些操作需要获取跨 vRAN 堆栈不同层的事件和数据(例如,在第 4 节干扰检测的情况下)。小程序之间的状态共享是通过共享 map 实现的,为了使这种机制发挥作用,想要共享状态的小程序必须在其typekey_sizevalue_sizemax_entries方面使用完全相同的共享 map 定义,而 map 名称可以不同。然后,开发者可以在用于加载小程序集的 YAML 描述符文件的linked_maps部分指定要链接的 map,如示例 6 所示。当小程序集被加载时,共享 map 的内存在 Janus 设备上只被分配一次,所有共享 map 的小程序都会得到一个指向相同内存的指针,然后可以通过辅助函数调用该内存来存储和加载状态。

附录.2 灵活的 Janus 输出格式

输出格式可以被多个小程序复用。通过 Janus SDK,输出格式定义和小程序被分别上传到 Janus 控制器,小程序在其环形输出缓冲区 map 的定义中指定使用哪种输出格式,包括三个字段,如示例 1 的第 11-13 行所示。proto_name字段包含名称,表示已经上传到 Janus 控制器的唯一的 protobuf 规范文件。proto_msg_name字段表示proto_name规范的根消息,用于通过这个环形缓冲区发送数据(一个 proto 规范可以包含多个消息定义)。最后,proto_hash字段包含哈希值,用来确保开发小程序时使用的 protobuf 规范文件的内容与上传到 Janus 控制器的文件内容相同。


图 11: 将带有输出格式定义的 Janus 小程序集加载到 Janus 设备的过程。


图 11 说明了带有输出格式定义的小程序集的加载过程。Janus 控制器解析小程序对象(ELF)文件,识别 map 部分中JANUS_MAP_TYPE_RINGBUF类型,然后基于 map 定义中的proto_name值,将小程序与先前上传到控制器的一些.proto规范文件联系起来。一旦找到规范定义,控制器将 map 的proto_hash字段与.proto文件的哈希值进行比较。如果匹配,控制器会给小程序的环形缓冲区 map 分配唯一的 stream-id(一个 16 字节的 UUID)。接下来,控制器自动生成编码器(encoder)解码器(decoder) 函数,负责对小程序发送至 Janus 输出数据收集器的proto_msg_name类型的消息进行序列化和反序列化。控制器通过网络 API 将自动生成的编码器函数与经过验证的小程序字节码和输出 map 的 stream-id 一起发送到 Janus 设备。控制器还将 stream-id 与自动生成的解码器函数一起发送给输出数据收集器。收集器维护一个键值结构,将 stream-id(键)映射到解码器函数(值)。


每次有JANUS_MAP_TYPE_RINGBUF类型输出 map 的小程序被加载到 Janus 设备上,一个单生产者/单消费者(SPSC)的环形缓冲区数据结构就会被创建并链接,其中小程序是生产者,输出线程是消费者。如图 12 所示,每次小程序调用janus_ringbuf_output()辅助函数(例如示例 1 的第 42 行),输出数据就被推送到环形缓冲区。输出线程消费这些数据,并调用相应的编码器函数对其进行序列化。在通过 UDP 将序列化数据发送到输出收集器之前,会附加一个头域,其中包括 16 字节的 stream-id 和 2 字节的序列号。一旦输出收集器收到输出信息,就会将 stream-id 与对应的解码器函数相匹配,用来反序列化该信息。最后,反序列化的消息被转换为 JSON 格式,然后可以被输送到流水线上的其他组件(如存储、ML 处理等)。


图 12: Janus 小程序输出数据。

附录.3 编写 Janus 钩子函数

为了简化编写 Janus 钩子的过程,Janus SDK 提供了一套用于声明和运行新钩子的宏。简而言之,调用DECLARE_JANUS_HOOK()宏,并将新钩子的名称、传递给钩子的小程序上下文类型、钩子函数签名(名称和参数类型)以及用于填充将被传递给小程序的上下文赋值列表作为输入参数。示例 7 是示例 1 中小程序的钩子示例。


1 DECLARE_JANUS_HOOK(fapi_dl_config_req , 2         struct janus_ran_fapi_ctx ctx , 3         ctx , 4         HOOK_PROTO ( 5             nfapi_dl_config_request_pdu_t * dl_config_req , 6             int ctx_id , 7             int frame , 8             int subframe , 9             int cell_id ,10             int fapi_list_size11             ) ,12         HOOK_ASSIGN (13             ctx.ctx_id = ctx_id ;14             ctx.cell_id = cell_id ;15             ctx.slot = subframe ;16             ctx.frame = frame ;17             ctx.data = ( void *) dl_config_req ;18             ctx.data_end = ( void *) ( dl_config_req + fapi_list_size ) ;19             )20         )

复制代码

示例 7: 示例 1 中小程序的钩子定义


基于这些输入,宏会自动生成构成钩子 API 的函数模板代码,包括钩子加载和卸载小程序的函数,以及运行与钩子链接的所有小程序的函数。表 5 显示了示例 7 自动生成的函数列表。


表 5: 示例 7 中通过DECLARE_JANUS_HOOK()宏自动生成的函数


一旦声明了钩子,开发者可以通过DEFINE_JANUS_HOOK()这个宏来实例化钩子,将钩子名称作为参数传入 vCU/vDU 代码中。最后,通过调用自动生成的函数hook_#hook_name(),可以在代码的任何地方调用该钩子,其中#hook_name是声明该钩子时使用的名称。例如,在示例 7 中,会生成钩子hook_fapi_dl_config_req()

附录.4 基于 Janus 进行推理

接下来我们详细解释 Janus 如何通过 map 使用更复杂的 ML 模型进行推理。示例 8 中显示了一个简单的小程序例子,用于第 4 节中讨论的随机森林模型。这个小程序对一个预先训练好的模型进行推理,然后返回。


1 struct janus_load_map_def SEC("maps") model_map = { 2   .type = JANUS_MAP_TYPE_ML_MODEL , 3   .max_entries = 16 , 4   .ml_model = "random_forest", 5 }; 6 7 struct features { 8   int f1 ; 9   int f2 ;10   int f3 ;11   int o1 ;12 };1314 SEC("janus_ran_fapi")15 uint64_t bpf_prog(void *state) {1617   struct features feats ;18   int res ;1920   feats.f1 = 1;21   feats.f2 = 1;22   feats.f3 = 1;2324   /* We store inference result in output */25   res = janus_model_predict(&model_map , &feats);2627   if (res) return 1;2829   return 0;30 }

复制代码

示例 8: ML 模型使用示例


加载 ML 模型 -- 从示例 8 第 2 行可以看出,我们定义了一个JANUS_MAP_TYPE_ML_MODEL类型的 map,该模型必须由控制器以序列化格式从一个名为random_forest的输入文件中加载(第 4 行)。序列化的模型以 char 数组的形式表示,并获取 janus 所需的所有重要信息,以便在内存中重新创建训练好的模型。例如,对于随机森林模型,序列化包含关于模型类型(随机森林)、估算器(树)数量、对每个估算器的每个节点进行检查的条件以及树的叶子节点的推理值等信息。Janus 只支持一组预先确定的模型类型(目前是随机森林和 SVM),每个模型都有自己的序列化参数集。在加载小程序的过程中,验证器将检查加载的序列化模型是否有效,包括检查模型类型以及模型参数的有效性(即模型是否可以在内存中重构)。如果任何一项检查失败,那么小程序就不会被加载到 Janus 设备中。


程序内推理 -- 加载的模型希望有一组输入特征以及确定数量和大小的输出,输入特征和输出所需的内存在 ML 模型 map 定义中指定。比如示例 8 有 3 个输入特征和 1 个输出(第 7-12 行),总内存大小为 16 字节(第 3 行)。输入特征和输出的确切内存布局取决于训练好的模型,因此是由小程序决定的。有了输入特征,可以通过调用辅助函数janus_model_predict()进行推理,如第 25 行所示。

附录.5 实时运算

还有更多为 Janus 所作的优化的细节,以确保实时性能:


输出 map -- 对于每个输出流,创建一个无锁的单生产者/单消费者环形缓冲区,将数据从小程序推送到输出线程,避免任何系统调用(见图 2)。这确保了小程序永远不会被抢占,并且在最坏情况下可能只是丢掉多余的数据包。


内存分配 -- Janus 使用预先分配的内存进行操作(加载小程序的 JIT 代码,map,数据输出等)。它依赖 DPDK mempools 和 Mbufs 在无锁模式下运行(使用rte_stack mempool),从而确保使用同一 mempool 的多个线程在被抢占的情况下不会影响其他线程的性能,并且最小化在 fastpath 中访问内存的抖动。


Janus map 的并发性 -- 根据设计,Janus map 不使用锁,以保证时间敏感的 vRAN 功能的实时性能,因此是非线程安全的,并可能会导致并发性问题(例如,在多实例工作线程代码中调用钩子)。然而,根据我们使用商业级 vRAN 功能的 Janus 的经验,大多数情况下,只要某种上下文 ID 作为钩子上下文的一部分被传递,以识别哪个实例正在调用钩子(例如,哪个 CPU 核心,哪个工作线程等),就可以用无锁的方式编写小程序。我们正计划在未来适当的时候通过采用线程安全的 map 解决这一限制。


加载/卸载小程序 -- 我们通过基于用户空间实现的 RCU(Read-Copy-Update)[65]将小程序加载(卸载)到 Janus 钩子上。RCU 可以实现无锁访问共享数据结构,代价是写入/更新时间[69, 70]更长。这对 Janus 很适合,执行小程序的快速 vRAN 线程会读取数据,而更新钩子小程序列表的(非实时)线程写入/更新该数据。




你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。

微信公众号:DeepNoMind

目录
相关文章
|
小程序 安全 5G
Janus: 基于 eBPF 的 5G 实时 AI 控制器(中)
Janus: 基于 eBPF 的 5G 实时 AI 控制器(中)
133 0
|
小程序 安全 算法
Janus: 基于 eBPF 的 5G 实时 AI 控制器(上)
Janus: 基于 eBPF 的 5G 实时 AI 控制器(上)
108 0
|
边缘计算 人工智能 物联网
物联网未来展望:5G、AI与边缘计算的融合之路
本篇详细探讨了物联网领域的未来展望,包括5G与物联网的融合、人工智能在物联网中的应用,以及边缘计算与物联网的发展趋势。通过代码示例,读者可以了解如何利用5G网络进行物联网设备间的高效数据传输,以及如何应用人工智能技术进行智能感知和数据分析。此外,我们还介绍了边缘计算在物联网中的重要作用,以及如何在边缘设备上进行数据处理和分析。通过本篇内容,读者将对物联网未来的技术发展有更全面的认识,为把握物联网领域的机遇提供有益的前瞻性指导。
448 0
|
机器学习/深度学习 人工智能 算法
【年终特辑】看见科技创新力量 洞见时代创业精神—文旅娱乐—智媒云图:5G时代的“AI交互+数字艺术”双效引擎
【年终特辑】看见科技创新力量 洞见时代创业精神—文旅娱乐—智媒云图:5G时代的“AI交互+数字艺术”双效引擎
143 0
|
机器学习/深度学习 数据采集 人工智能
5G网络怎样绿色减排?看联通巧用AI+CPU调频方案实现动态节能
5G网络怎样绿色减排?看联通巧用AI+CPU调频方案实现动态节能
|
人工智能 Cloud Native 关系型数据库
5G与AI“点燃”数字应用,云原生重塑数据库未来
5G与AI“点燃”数字应用,云原生重塑数据库未来
214 0
华为5G手机发布!5G和AI给世界带来什么?
5G+AI多元化的场景应用为企业提供了巨大的市场机遇,也提出了更高要求。
335 0
|
3天前
|
机器学习/深度学习 人工智能 供应链
AI技术在医疗领域的应用与未来展望###
本文深入探讨了人工智能(AI)技术在医疗领域的多种应用及其带来的革命性变化,从疾病诊断、治疗方案优化到患者管理等方面进行了详细阐述。通过具体案例和数据分析,展示了AI如何提高医疗服务效率、降低成本并改善患者体验。同时,文章也讨论了AI技术在医疗领域面临的挑战和未来发展趋势,为行业从业者和研究人员提供参考。 ###
|
4天前
|
机器学习/深度学习 人工智能 算法
AI技术在医疗领域的应用与挑战
【10月更文挑战第21天】 本文探讨了人工智能(AI)在医疗领域的多种应用,包括疾病诊断、治疗方案推荐、药物研发和患者管理等。通过分析这些应用案例,我们可以看到AI技术如何提高医疗服务的效率和准确性。然而,AI在医疗领域的广泛应用也面临诸多挑战,如数据隐私保护、算法透明度和伦理问题。本文旨在为读者提供一个全面的视角,了解AI技术在医疗领域的潜力和面临的困难。

热门文章

最新文章