【AI简报20210827期】AI芯片逐步落地智能教育硬件市场!用AI设计芯片会成为未来趋势吗?...



嵌入式AI硬件

1. 集成全球最大AI芯片,Cerebras发布全球首个人类大脑规模的AI解决方案

原文:

https://new.qq.com/omn/20210826/20210826A06VH900.html

8月25日凌晨,曾推出“全球最大”的AI芯片Wafer Scale Engine(以下简称“WSE”)的芯片初创公司CerebrasSystems,宣布推出了世界上第一个人类大脑规模的AI解决方案——CS-2 AI计算机,可支持超过120万亿参数规模的训练。相比之下,人类大脑大约有100万亿个突触。此外,Cerebras还实现了192台CS-2 AI计算机近乎线性的扩展,从而打造出包含高达1.63亿个核心的计算集群。

资料显示,Cerebras成立于2016年,迄今在14个国家拥有超过350位工程师,2019年Cerebras推出了世界最大AI芯片WSE,2020年Cerebras又推出了新一代的7nm的WSE-2,晶体管数量达到2.6万亿个,震惊业界。

根据官方公布的数据,WSE-2与上一代一样,依然是基于一整张12吋晶圆制造,面积依然是462.25平方厘米,但是制程工艺由台积电16nm工艺提升到了7nm工艺,这也使得WSE-2的晶体管数量提高到了2.6万亿个,同时他的人工智能内核数量也达到了85万个,打破首代WSE 处理器创造的世界纪录。无论是核心数还是片上内存容量均远高于迄今性能最强的GPU。

2. 嘉楠科技与闪现智能合作,AI芯片逐步落地智能教育硬件市场

原文:

https://baijiahao.baidu.com/s?id=1708971331548164815&wfr=spider&for=pc

集微网消息,据网易科技报道,日前,嘉楠科技与全年龄段智慧教育解决方案提供商闪现智能合作,并且已经进入产品开发阶段。

闪现智能基于勘智K210芯片开发了多款智能教育硬件产品,包括AI教育核心板、传感器拓展板和自动驾驶拓展小车等,将逐步落地西安高校和职教智能硬件市场。通过K210芯片和板载资源,教育核心板可支持离线实现机器学习、形状识别、动物识别、戴口罩识别、交通标志识别、智能家居、指南针、水平仪、环境监测和成语填充等多项功能。

据了解,勘智K210芯片采用RISC-V处理器架构,具备视听一体、自主IP核与可编程能力强三大特点,支持机器视觉与机器听觉多模态识别,可广泛应用于智能家居、智能园区、智能能耗和智能农业等场景。

3. AI芯片公司「苹芯科技」获近千万美元Pre-A轮融资,打造存内计算加速单元

原文:

https://baijiahao.baidu.com/s?id=1708938332878219718&wfr=spider&for=pc

AI芯片研究领域的苹芯科技已完成红点中国领投,真格基金、红杉资本跟投的近千万美元Pre-A轮融资,据悉,本轮融资将主要用于芯片研发相关工作。苹芯科技的天使轮股东包括普华资本,红杉资本等。

苹芯科技成立于2021年2月,公司总部位于北京,是一家基于存算一体技术的打造面向AI加速器芯片的创业公司,提供基于存算一体技术的用于提升深度学习计算性能的硬件单元和相关IC设计服务。公司在北京、台湾、新加坡等地设有研发团队及办公室,希望利用各地资源优势,推动全球化发展战略。

其产品主要用于可穿戴设备、无人机摄像头、安防领域、机器人领域、智能家居等低能耗、长待机的场景。

传统的芯片架构基于冯诺依曼架构,计算单元和存储单元物理上分离,存储单元的带宽速度低于计算单元,限制了系统的整体性能;此外,数据传输过程会带来延迟和性能消耗,且这种消耗能达到70%~90%以上。随着AI算法的持续升级与AI应用的持续普及,AI领域迫切需要性能更强、功耗更低、成本更低的芯片。

4. 特斯拉发布D1 AI芯片:500亿晶体管、400W热设计功耗

原文:

https://baijiahao.baidu.com/s?id=1708794510144411423&wfr=spider&for=pc

近日的特斯拉AI日活动上,特斯拉公布了最新的AI训练芯片“D1”,规模庞大,令人称奇。

该芯片采用台积电7nm工艺制造,核心面积达645平方毫米,仅次于NVIDIA Ampere架构的超级计算核心A100(826平方毫米)、AMD CDNA2架构的下代计算核心Arcturus(750平方毫米左右),集成了多达500亿个晶体管,相当于Intel Ponte Vecchio计算芯片的一半。

其内部走线,长度超过11英里,也就是大约18公里。

5. IBM发布首款7纳米AI芯片,加码人工智能基础设施竞争

原文:

https://www.jiemian.com/article/6530725.html

当人工智能(AI)加速芯片已成为大型科技公司标配后,老牌科技公司IBM亦给出回应。在本周举行的芯片行业会议HotChips上,IBM正式公布新款处理器“Telum”,Telum是IBM首款具有芯片上AI加速功能的处理器,用于IBM下一代Z系列大型机和LinuxONE服务器。

IBM Telum包含8个处理器核心,频率超过5GHz,每个核都由重新设计的32MB专用2级缓存支持。该处理器采用三星7纳米制程工艺,并且单芯片内采用17层金属连接,来完成高密度电路互连,总线长可达约30公里。

为了支持AI加速处理性能,新处理器面积为530平方毫米,集成多达225亿个晶体管,并拥有全新的分支预测、缓存,支持多芯片一致性互连,性能提升超过40%。

此外,IBM Z Telum处理器还采用双向环形互连拓扑结构,带宽接近320GB/s。三级缓存所有核心共享,通过二级缓存与核心相连,平均延迟达到12纳秒(1纳秒等于10的负9次方秒)。

IBM称,新处理器通过芯片内深度学习推理(Inference) ,帮助即时解决金融客户解决欺诈等问题,从而不需要将数据转移至芯片外。AI计算大致分为两个层面,首先是对模型进行训练(training),整个过程可能耗时数天或数周;之后是训练出的模型做出推理。

实际上,在芯片内集成和加强AI能力,IBM与英特尔思路一致,两者均强调增强芯片的AI计算能力作为卖点。例如英特尔推出的第三代至强服务器芯片,在深度学习方面增加了最新深度学习加速指令集,提高AI处理速度和模型计算精度。

IBM称,其人工智能硬件研究中心花了3年的时间研发打造出Telum处理器。打造出第一款结合AI加速推论功能的处理器芯片,用于大型机上时,可以协助银行解决诈骗、高频交易等金融难,预计2022年上半年采用该芯片的大型机系统将会推出。

AI热点新闻

6. 千倍设计效率提升,EDA终极形式,用AI设计芯片会成为未来趋势吗?

链接:

https://www.163.com/dy/article/GID7KKQF05312NX9.html

数字经济正从数字化迈向智能化新阶段,未来十年,智能经济将成为产业发展的核心。在百度创始人、董事长兼首席执行官李彦宏看来,“产业智能化在每一个领域都会发生。”物联网、云计算等技术的发展方向全部指向了智能化,“人工智能+”逐渐成为产业结构升级的驱动力。在智能化产业趋势下,利用人工智能进行芯片设计也逐渐成为行业研究的热门方向。

早在2020年4月,谷歌AI团队便描述了一种基于机器学习的芯片设计方法。Google Brain总监Jeff Dean指出,某些情况下,机器学习会做出比人类更加精准的决策,例如规划芯片中的电路布局。该技术能够极大地缩减研发设计周期,自动生成芯片布局方案,并且在功耗、性能和芯片面积(PPA)等关键参数指标上都有不俗的表现。

而前段时间,据Wired报道显示,三星(Samsung)下一代Exynos处理器将采用AI进行芯片设计,人工智能设计软件(DSO.ai)由新思科技(Synopsys)提供。这些芯片在设计完成并量产后,将会运用到三星智能手机、平板电脑中,并且有部分还将供货给国产手机厂商。这就意味着,未来我们将有机会用上搭载由人工智能进行芯片设计的智能终端产品。

事实上,在利用AI设计芯片方面,三星只能算后来者。谷歌TPU(张量处理单元)采用人工智能优化;英伟达利用人工智能技术生产GPU和云计算TPU平台。这无疑都在传递同一个信号,即芯片设计即将走向人工智能时代。

7. 自动驾驶落地 上汽成立智能驾驶公司

链接:

https://www.163.com/dy/article/GICTO8T50527AQ4N.html

8月26日,上汽集团宣布正式成立商用车智能驾驶科创公司上海友道智途科技公司,将以港口、厂区等特定场景作为L4级自动驾驶技术和L3自动驾驶技术的应用切入点,也就是说,L4和L3级自动驾驶技术,将会在港口与厂区落地。

此次,上汽集团商用车智能驾驶科创公司计划落地在中国(上海)自由贸易试验区临港新片区,打造“技术+产品+运营”的全新业务模式。不久的未来,是不是遍地都是威震天、擎天柱了呢?我们拭目以待。

8. 人工智能破解蛋白质结构可能引发医学革命

原文:

https://www.bbc.com/zhongwen/simp/57969896

人工智能被用来预测人体产生的几乎每一种蛋白质的结构。

这一重大科研发展能够加速推进研发治疗疾病的新药物,还能用在其他许多地方。

蛋白质是所有生物有机体的组成部分,我们体内的每一个细胞都充满蛋白质。

了解蛋白质的结构和形状对医药研究的进步至关重要,但是到现在为止,我们只研究出少数蛋白质的构造。

研究人员用一个名为“AlphaFold”的人工智能软件来预测人体和其他有机体的35万种蛋白质的结构。

人类蛋白质如何组成是由我们体内的基因组决定,也就是人类细胞核里面的遗传物质DNA,能够决定大约两万种蛋白质。

“AlphaFold”的人工智能软件是谷歌旗下的DeepMind人工智能公司开发的,该公司共同创办人兼首席执行官哈萨比斯(Demis Hassabis)表示,这是迄今为止人类蛋白质最完整、最准确的图像。

  • 人工智能破解生物学最大谜团之一的意义所在

  • 新冠疫情:人工智能算法能“听咳嗽声音辨识新冠病毒

“我们认为这代表了人工智能对科学知识进展所做的最大贡献。” 他表示,这是人工智能有助于社会的最佳范例,未来还会有更多激励人心的发展。

有趣的开源项目

9. 首个移动端AI虚拟人像加速方案!这研究将让科幻般VR离你更进一步

原文:

https://baijiahao.baidu.com/s?id=1694900288211624038&wfr=spider&for=pc

斯皮尔伯格指导的电影《头号玩家》相信大家都不陌生,电影中高度成熟发达的VR(虚拟现实)技术是每一位科技爱好者都会憧憬的场景,谁又不想在这个光怪陆离的世界里来一场惊心动魄的冒险之旅呢~

在众多VR技术的难题当中,如何让高质量VR应用在资源及功耗受限的移动设备上部署成为可能是一大关键。

而近日,来自UIUC(伊利诺伊大学厄班纳香槟分校)和FRL(Facebook Reality Labs)Research的研究人员发表了题为《F-CAD: A Framework to Explore Hardware Accelerators for Codec Avatar Decoding》的论文,为移动终端设备部署新一代高拟真VR人像应用提供硬件加速器设计方案。

论文链接:https://arxiv.org/abs/2103.04958

10.给图片打「马赛克」可骗过AI视觉系统,阿里安全新研究入选ICCV 2021

原文:

https://mp.weixin.qq.com/s/hw65luLEaQ1pI0e0SuZovQ

来自阿里安全人工智能治理与可持续发展实验室(AAIG)等机构的研究者提出了一个新的机制来生成对抗样本,即与增加对抗扰动相反,他们通过扔掉一些不可察觉的图像细节来生成对抗样本。这项研究成果已被 AI 顶会 ICCV 2021 收录。

人类拥有很强的抽象能力和联想力,例如一个有几块积木拼成的乐高玩具,小朋友也能轻易认出其中描述的场景 (人开着小车)。甚至几个像素,玩家也可以轻易认出这是一个戴着帽子的小人 (超级玛丽奥)。

尽管我们期望模型能具有和人相当的能力,但是「抽象能力」对于模型来说,在当前显然还是一个相当具有挑战性的任务。但相反的,如果我们从对抗样本的角度来考虑:存不存在一种可能,如果我们去掉图片中一些对模型来说关键而微小的特征,模型就无法再正确识别这些图片?感兴趣的大家可以查看原文,文章的创新一定会给大家提供非常好的思路。

11. ICCV 2021 | Transformer再助力!用CWT进行小样本语义分割

原文:

https://mp.weixin.qq.com/s/SSvtl1cy4eS8OSdvigkABg

本文是对发表于计算机视觉领域的顶级会议 ICCV 2021的论文“Simpler is Better: Few-shot Semantic Segmentation with Classifier Weight Transformer(简而优:用分类器变换器进行小样本语义分割)”的解读。

该论文由英国萨里大学Centre for Vision, Speech and Signal Processing (CVSSP)发表,针对小样本语义分割问题,提出一种更加简洁的元学习范式,即只对分类器进行元学习,对特征编码解码器采用常规分割模型训练方式。元学习训练后的Classifier Weight Transformer使分类器可以动态地适应测试样本,从而提高分割准确率。

Simpler is Better: Few-shot Semantic Segmentation with Classifier Weight Transformer

论文:https://arxiv.org/abs/2108.03032

代码:https://github.com/zhiheLu/CWT-for-FSS

12. 李飞飞团队给机器人造了一个“模拟厨房”:洗切炒菜一条龙训练!人类还能VR监管

原文:

https://mp.weixin.qq.com/s/HgcyVs8eQfrQ4tWsFUo02g

李飞飞团队的机器人模拟训练场2.0版本来了!这个拥有超过8000个交互式场景的模拟环境iGibson,再次发生了进化!

进化之后的iGibson 2.0,核心就一句话:机器人们别抓小球儿了,来做家务吧!像是模拟环境中增加的温度、湿度、切片等多种物理状态,环境信息已经完事具备了,亟待各位机器人来个洗、切、炒菜一条龙服务。

人类还能通过VR进入模拟环境,给机器人示范下如何做一个标准的家务,那么现在,如果你已经迫不及待的想看看具体的内容,请点击原文连接。

聊点技术

13. 手撕非极大值抑制算法NMS

原文:

https://oldpan.me/archives/write-hard-nms-c

非极大值抑制算法(Non-maximum suppression, NMS)是有anchor系列目标检测的标配,如今大部分的One-StageTwo-Stage算法在推断(Inference)阶段都使用了NMS作为网络的最后一层,例如YOLOv3、SSD、Faster-RCNN等。

当然NMS在目前最新的anchor-free目标检测算法中(CornerNet、CenterNet等)并不是必须的,对这种检测算法提升的精度也有限,但是这并不影响我们学习NMS。

NMS的本质是搜索局部极大值,抑制非极大值元素,在目标检测中,我们经常将其用于消除多余的检测框(从左到右消除了重复的检测框,只保留当前最大confidence的检测框):

NMS有很多种变体,这里介绍最为常见的Hard-NMS,我们通常所说的NMS就是指Hard-NMS,还有另外一种NMS叫做Soft-NMS,是Hard-NMS的变体,两者的代码几乎相同,只需要改动一个地方。

Hard-NMS

Hard-NMS就是我们传统意义上的NMS,也是最常用的NMS算法。

因为NMS主要用于消除多余的检测框,那么消除的标准是什么,我们使用IOU作为标准来进行演示,IOU的原称为Interp over Union,也就是两个box区域的交集比上并集,下图可以方便理解:

具体介绍可以看这里:深度学习中IU、IoU(Interp over Union)的概念理解以及python程序实现。[https://oldpan.me/archives/iu-iou-interp-over-union-python]

因为我们要手撸么,所以废话不多说,直接开始看代码,首先使用Pytorch来看一篇:

 1def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200):
 2    """
 3    Args:
 4        box_scores (N, 5): box的集合,N为框的数量,5即4(位置信息)+1(可能为物体的概率)
 5        iou_threshold: 我们用IOU标准去除多余检测框的阈值
 6        top_k: 保留多少个计算后留下来的候选框,如果为-1则全保留
 7        candidate_size: 参与计算的boxes数量
 8    Returns:
 9         picked: 经过nms计算后保留下来的box
10    """
11    scores = box_scores[:, -1]                # 首先我们取出box中的最后一个元素也就是当前box检测到物体的概率
12    boxes = box_scores[:, :-1]                # 取出box中的四个坐标(左上、右下)
13    picked = []  
14    _, indexes = scores.sort(descending=True) # 按照降序排列所有的物体的概率,得到排序后在原数组中的索引信息 indexes
15    indexes = indexes[:candidate_size]        # 只保留前 candidate_size 个 boxes 其余的不考虑了
16    while len(indexes) > 0:
17        current = indexes[0]                  # 每次取出当前在 indexes 中 检测到物体概率最大的一个 
18        picked.append(current.item())         # 将这个最大的存在结果中
19        if 0 < top_k == len(picked) or len(indexes) == 1:
20            break
21        current_box = boxes[current, :]       # 当前第一个也就是最高概率的box
22        indexes = indexes[1:]                
23        rest_boxes = boxes[indexes, :]        # 剩下其余的box
24        iou = iou_of(                         # 将当前的box与剩下其余的boxes用IOU标准进行筛选
25            rest_boxes,
26            current_box.unsqueeze(0),
27        )
28        indexes = indexes[iou <= iou_threshold]# 保留与当前box的IOU小于一定阈值的boxes,
29
30    return box_scores[picked, :]

看了上面的代码,我们可以知道大概的流程:

  • 选取这类box中scores最大的那一个,记为current_box,并保留它(为什么保留它,因为它预测出当前位置有物体的概率最大啊,对于我们来说当前confidence越大说明当前box中包含物体的可能行就越大)

  • 计算current_box与其余的box的IOU

  • 如果其IOU大于我们设定的阈值,那么就舍弃这些boxes(由于可能这两个box表示同一目标,因此这两个box的IOU就比较大,会超过我们设定的阈值,所以就保留分数高的那一个)

  • 从最后剩余的boxes中,再找出最大scores的那一个(之前那个大的已经保存到输出的数组中,这个是从剩下的里面再挑一个最大的),如此循环往复

至于上述代码中iou_of部分:

 1def area_of(left_top, right_bottom) -> torch.Tensor:
 2    """Compute the areas of rectangles given two corners.
 3
 4    Args:
 5        left_top (N, 2): left top corner.
 6        right_bottom (N, 2): right bottom corner.
 7
 8    Returns:
 9        area (N): return the area.
10    """
11    hw = torch.clamp(right_bottom - left_top, min=0.0)
12    return hw[..., 0] * hw[..., 1]
13
14
15def iou_of(boxes0, boxes1, eps=1e-5):
16    """Return interp-over-union (Jaccard index) of boxes.
17
18    Args:
19        boxes0 (N, 4): ground truth boxes.
20        boxes1 (N or 1, 4): predicted boxes.
21        eps: a small number to avoid 0 as denominator.
22    Returns:
23        iou (N): IoU values.
24    """
25    overlap_left_top = torch.max(boxes0[..., :2], boxes1[..., :2])
26    overlap_right_bottom = torch.min(boxes0[..., 2:], boxes1[..., 2:])
27
28    overlap_area = area_of(overlap_left_top, overlap_right_bottom)
29    area0 = area_of(boxes0[..., :2], boxes0[..., 2:])
30    area1 = area_of(boxes1[..., :2], boxes1[..., 2:])
31    return overlap_area / (area0 + area1 - overlap_area + eps)

手撕NMS

手撕代码用什么撕,当然是用C++撕,这才爽么!

直接看代码,其中使用了OpenCV库中的Point2f结构体:

  1// 这是一个模板函数,接受一个已经排好序的vector,然后降序返回其索引
  2template <typename T>
  3vector<int> sort_indexes(const vector<T> &v) {
  4
  5
  6    vector<int> idx(v.size());
  7    iota(idx.begin(), idx.end(), 0);
  8    sort(idx.begin(), idx.end(),
  9         [&v](int i1, int i2) {return v[i1] > v[i2];});
 10    return idx;
 11}
 12// 这就是我们的NMS函数 输入的坐标已经标准化,所有数值的范围为 0-1
 13/*
 14* numBoxes:窗口数目
 15* points:窗口左上角坐标点
 16* oppositePoints:窗口右下角坐标点
 17* score:窗口得分
 18* overlapThreshold:重叠阈值控制
 19* numBoxesOut:输出窗口数目
 20* pointsOut:输出窗口左上角坐标点
 21* oppositePoints:输出窗口右下角坐标点
 22* scoreOut:输出窗口得分
 23*/
 24
 25int nonMaximumSuppression(int numBoxes, const vector<Point2f>& points,
 26                          const vector<Point2f>& oppositePoints, const vector<float>& score,
 27                          float overlapThreshold,
 28                          int *numBoxesOut, vector<Point2f>& pointsOut,
 29                          vector<Point2f>& oppositePointsOut, vector<float>& scoreOut)
 30{
 31
 32    const float eps = 1e-5;
 33    int i, j, index;
 34    float* box_area = (float*)malloc(numBoxes * sizeof(float));  // 定义窗口面积变量并分配空间
 35    vector<int> indices;
 36    int* is_suppressed = (int*)malloc(numBoxes * sizeof(int));   // 定义是否抑制表标志并分配空间
 37
 38    // 初始化indices、is_supperssed、box_area信息
 39    for (i = 0; i < numBoxes; i++)
 40    {
 41        indices.push_back(i);
 42        is_suppressed[i] = 0;
 43        box_area[i] = ((oppositePoints[i].x - points[i].x + eps) *
 44                       (oppositePoints[i].y - points[i].y + eps));
 45    }
 46
 47    // 对输入窗口按照分数比值进行排序,排序后的编号放在indices中
 48    indices = sort_indexes(score);
 49
 50    for (i = 0; i < numBoxes; i++)                // 循环所有窗口
 51    {
 52        if (!is_suppressed[indices[i]])           // 判断窗口是否被抑制
 53        {
 54            for (j = i + 1; j < numBoxes; j++)    // 循环当前窗口之后的窗口
 55            {
 56                if (!is_suppressed[indices[j]])   // 判断窗口是否被抑制
 57                {
 58                    float x1max = max(points[indices[i]].x, points[indices[j]].x);                     // 求两个窗口左上角x坐标最大值
 59                    float x2min = min(oppositePoints[indices[i]].x, oppositePoints[indices[j]].x);     // 求两个窗口右下角x坐标最小值
 60                    float y1max = max(points[indices[i]].y, points[indices[j]].y);                     // 求两个窗口左上角y坐标最大值
 61                    float y2min = min(oppositePoints[indices[i]].y, oppositePoints[indices[j]].y);     // 求两个窗口右下角y坐标最小值
 62                    float overlapWidth = x2min - x1max + eps;            // 计算两矩形重叠的宽度
 63                    float overlapHeight = y2min - y1max + eps;           // 计算两矩形重叠的高度
 64                    if (overlapWidth > 0 && overlapHeight > 0)
 65                    {
 66                        float overlapPart = (overlapWidth * overlapHeight) / box_area[indices[j]];    // 计算重叠的比率
 67                        if (overlapPart > overlapThreshold)          // 判断重叠比率是否超过重叠阈值
 68                        {
 69                            is_suppressed[indices[j]] = 1;           // 将窗口j标记为抑制
 70                        }
 71                    }
 72                }
 73            }
 74        }
 75    }
 76
 77    *numBoxesOut = 0;    // 初始化输出窗口数目0
 78    for (i = 0; i < numBoxes; i++)
 79    {
 80        if (!is_suppressed[i]) (*numBoxesOut)++;    // 统计输出窗口数目
 81    }
 82
 83    for (i = 0; i < numBoxes; i++)                  // 遍历所有输入窗口
 84    {
 85        if (!is_suppressed[indices[i]])             // 将未发生抑制的窗口信息保存到输出信息中
 86        {
 87            Point2f temp_xy(points[indices[i]].x, points[indices[i]].y);
 88            Point2f temp_Opxy(oppositePoints[indices[i]].x, oppositePoints[indices[i]].y);
 89            pointsOut.push_back(temp_xy);
 90            oppositePointsOut.push_back(temp_Opxy);
 91            scoreOut.push_back(score[indices[i]]);
 92        }
 93    }
 94
 95    indices.clear();
 96    free(box_area);         // 释放box_area空间
 97    free(is_suppressed);    // 释放is_suppressed空间
 98
 99    return 0;
100}

好了,代码撕完了。

原文还提供了soft-NMS的手撕菜单,感兴趣的同学可以点击原文连接对比查看两者之间的区别。

你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群!

???? 点击阅读原文进入官网

Guess you like

Origin blog.csdn.net/rtthreadiotos/article/details/119975934