游乐游手机版
首页/AI热点日报/热点详情

MCU上DS-CNN关键词识别实现

类型:热点整理2026-07-06
借助前沿的高效算法,如今开发者能够在功耗受限、资源紧张的系统上,成功部署复杂的语音关键词识别功能。本系列文章分为上下两篇,上篇详细介绍了如何基于FPGA实现这一功能,而下篇将聚焦于MCU的具体实现方案。关键词唤醒(KWS)在可穿戴设备、物联网终端以及各类智能产品中扮演着日益关键的角色。机器学习方法能

借助前沿的高效算法,如今开发者能够在功耗受限、资源紧张的系统上,成功部署复杂的语音关键词识别功能。本系列文章分为上下两篇,上篇详细介绍了如何基于FPGA实现这一功能,而下篇将聚焦于MCU的具体实现方案。

关键词唤醒(KWS)在可穿戴设备、物联网终端以及各类智能产品中扮演着日益关键的角色。机器学习方法能够提供极高的KWS准确率,然而受限于产品本身的性能与功耗限制,直到最近,这类基于机器学习的KWS方案仍主要掌握在大型企业或经验丰富的机器学习专家手中。

问题的核心在于,可穿戴设备及其他物联网终端的资源极为有限,开发者迫切需要一种能够在这些约束条件下高效运行的KWS引擎。深度可分离卷积神经网络(DS-CNN)架构的出现,通过对传统CNN进行巧妙改造,恰好提供了所需的效率提升。

借助经过硬件优化的神经网络库,开发者可以在极低的资源占用下,实现基于MCU的DS-CNN推理引擎。本文将深入剖析DS-CNN的架构原理,并详细阐述如何在MCU上落地DS-CNN的KWS方案。

为何选择KWS?

如今,消费者已习惯于通过“Alexa”“Hey Siri”或“Ok Google”等唤醒词来操作手机和家电,这种语音激活的需求正迅速扩展至几乎所有具备交互界面的产品。精准的语音识别依赖于多种人工智能技术,将口语单词解析为应用能够理解的指令。

然而,要快速且准确地完成整套语音命令处理流程,所需的计算资源已经超出了低成本有线供电集线器的能力范围,更不用说那些依靠电池供电的个人电子设备了。

语音命令管道的要求

为了在这些产品上实现语音激活,开发者通常会拆分语音命令处理流水线,或者将命令词汇限制在“开”“关”等少数极简单的词语上。在资源有限的消费级产品中,开发者利用神经网络推理引擎实现KWS,该引擎必须能够对来自Alexa、Siri或Google的简单命令或唤醒短语做出高精度、低延迟的响应(图1)。

在此设计中,系统将输入的音频流数字化,提取语音特征,然后将这些特征传递给神经网络进行关键词识别。

图1:采用KWS进行语音激活时,处理管道从音频输入信号中提取频域特征,并对提取的特征进行分类,以此预测输入信号对应训练神经网络时所使用的某个标签的概率。(图片来源:Arm®)

将幅度调制的音频输入流转换为频谱图特征后,开发者就可以利用卷积神经网络(CNN)模型进行口语单词分类,并实现极高的准确率——分类依据正是训练时使用的标签。对于更复杂的语音接口,命令处理管道会超出设备本身的范围:KWS推理引擎检测到激活关键词后,产品将数字化音频流上传至云服务,由云端完成更复杂的语音处理与命令识别。

但话说回来,设备和推理引擎之间的资源冲突,一直让开发者在让小型产品变得更智能的道路上步履维艰。传统CNN虽然易于理解和训练,但资源消耗依然不小。随着CNN在图像识别领域准确率的大幅提升,CNN的规模与复杂度也随之水涨船高。

其结果是,一个高度精确的CNN模型需要数十亿次计算密集型的通用矩阵乘法(GEMM)操作来完成训练。训练完成后,对应的推理模型可能占用数百兆字节的内存,并且单次推理就需要进行海量的GEMM操作。

对于电池供电的可穿戴设备和物联网设备而言,有效的KWS推理模型必须在有限的内存和较低的处理要求下正常工作。更关键的是,KWS推理引擎需要始终处于开启状态(“始终在线”模式),因此功耗必须极低。

随着可穿戴和物联网设备日益受到欢迎,神经网络的潜力与有限资源之间的鸿沟也引起了机器学习专家的高度关注。这促使他们开发出能够优化CNN的技术以及替代性架构,从而弥合小型设备在性能要求与资源能力之间的差距。

小型化模型

在构建轻量级模型的技术中,专家们采用了网络剪枝和参数量化等优化方法,生成CNN的变体。这些变体几乎能够达到完整CNN的精度,但只消耗一小部分资源。这种低精度神经网络的成功,为二值化神经网络(BNN)铺平了道路——它将模型参数从32位浮点数(甚至早期CNN使用的16位、8位浮点数)压缩到1位值。正如上篇所述,莱迪思半导体的机器学习SensAI™平台就采用了这种高效的BNN架构,在iCE40 UltraPlus移动开发平台(MDP)上构建了仅1毫瓦(mW)的KWS解决方案。

除了网络剪枝和量化之外,还有其他方法通过修改CNN架构的拓扑结构来降低资源需求。在这些替代架构中,深度可分离卷积神经网络(DS-CNN)表现得尤为出色,它使得小型模型能够运行在通用的MCU上。

谷歌的机器学习专家找到了一种提升CNN效率的方法,即聚焦于卷积层本身。在传统CNN中,每个卷积层在一个步骤内同时完成输入特征的过滤与新特征的组合(图2,顶部)。

图2:与全卷积(上)不同,深度可分离卷积首先使用 DK x DK 滤波器(中)分别过滤 M 个输入通道中的每一个,然后使用逐点的 1 x 1 卷积来创建 N 个新特征。(图片来源:谷歌)

新方法将过滤和特征生成分成两个独立的阶段,统称为深度可分离卷积。第一阶段执行深度卷积,在每个输入通道上充当空间滤波器(图2,中间)。由于第一阶段不创建新特征(而这本是深度神经网络架构的核心目标),因此第二阶段执行逐点 1 x 1 卷积(图2,底部),将第一阶段的输出组合起来生成新特征。

这种DS-CNN架构被应用于谷歌的MobileNet模型中,该模型专为移动和嵌入式视觉应用而设计。它减少了参数数量以及相关的操作次数,从而得到更小的模型,仅需显著更少的计算量便可获得准确的结果[3]

与全卷积相比,在MobileNet模型中使用深度可分离卷积,在行业标准ImageNet数据集上仅降低了1%的准确度,但所使用的乘加运算量不到传统ImageNet CNN模型的12%,模型参数数量也仅为后者的14%。

尽管DS-CNN最初是为图像识别而设计的,但只要将音频输入流转换为频谱图,相同的模型便可应用于音频识别。音频前端将音频流转换为DS-CNN能够分类的特征集。在语音处理中,前端产生的特征通常是梅尔频率倒谱系数(MFCC),它更接近人类的听觉特性,同时显著降低了传递给DS-CNN分类器的特征维度。ARM的ML-KWS-for-MCU开源软件库正是采用了这一方法。

DS-CNN的实现

ARM的KWS代码仓库旨在演示如何在Cortex-M系列MCU上实现KWS,其中提供了大量预训练的TensorFlow模型,涵盖传统CNN、DS-CNN等多种架构。这些模型使用Google语音命令数据集[4]进行训练,将音频输入分为12个类别:“是”“否”“上”“下”“左”“右”“开”“关”“停止”“走”“静音”和“未知”。

开发者可以直接使用这些预训练模型来比较不同神经网络架构的推理性能,并深入了解它们的内部结构。例如,在ARM的预训练DS-CNN模型上运行TensorFlow的import_pb_to_tensorboard工具后,通过TensorBoard即可看到基于MobileNet的架构。

图3:在TensorBoard中显示的Arm预训练KWS模型,将熟悉的MobileNet DS-CNN模型(左侧红色轮廓)与使用梅尔频率倒谱系数(MFCC)的频域特征提取阶段(右侧展开)相结合。(图片来源:Digi-Key Electronics)

从TensorBoard可以看出,MobileNet将传统CNN中除第一个完整卷积层之外的所有层,都替换成了深度可分离卷积。

如前所述,这些阶段中的每一个都包含深度卷积和点卷积阶段,每个阶段都会输入一个batchnorm内核,对输出结果进行归一化处理(图3,左)。DS-CNN模型使用了一个特殊的TensorFlow融合batchnorm函数,它将多个选项合并到一个内核中。

通过放大音频特征提取阶段(图3,右),开发者可以检查音频处理序列:音频解码、频谱图生成和MFCC滤波。MFCC生成的特征通过一对重塑阶段,创建出MobileNet分类器所需的张量形状。

可以想象,开发者可以在基于MCU的系统(包括树莓派)上运行来自TensorFlow或其他框架的预训练模型[5]。这种方法能够对模型进行量化,从而生成小型版本。但如果没有GPU或其他硬件支持GEMM加速,推理延迟可能会让用户对语音激活性能感到失望。

ARM通过其针对Cortex微控制器软件接口标准(CMSIS)的神经网络(NN)扩展,提供了另一种实现途径。CMSIS-NN提供了一套完整的CNN函数,充分利用了Cortex-M7处理器(例如ST的STM32F7系列)内置的DSP扩展。除了传统的CNN函数外,CMSIS-NN API还支持深度可分离卷积,对应于DS-CNN架构下的深度和逐点卷积阶段:

ARM_status ARM_depthwise_separable_conv_HWC_q7_nonsquare
ARM_status ARM_convolve_1x1_HWC_q7_fast_nonsquare

该API还提供了专门为方形输入张量设计的版本,实现了这两个函数。

ARM在示例代码中使用了这些函数,展示了一个完整的基于DS-CNN的KWS应用,运行在STMicroelectronics STM32F746G-DISCO开发板上,板载STM32F746NGH6 MCU。示例代码的核心是原生CMSIS-NN C++模块实现了DS-CNN(清单1)。

void DS_CNN::run_nn(q7_t* in_data, q7_t* out_data)
{
    //CONV1 : regular convolution
    ARM_convolve_HWC_q7_basic_nonsquare(in_data, CONV1_IN_X, CONV1_IN_Y, 1, conv1_wt, CONV1_OUT_CH, CONV1_KX, CONV1_KY, CONV1_PX, CONV1_PY, CONV1_SX, CONV1_SY, conv1_bias, CONV1_BIAS_LSHIFT, CONV1_OUT_RSHIFT, buffer1, CONV1_OUT_X, CONV1_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer1, CONV1_OUT_X*CONV1_OUT_Y*CONV1_OUT_CH);
    // conv2: ds + pw conv
    // depthwise separable conv (batch norm params folded into conv wts/bias)
    ARM_depthwise_separable_conv_HWC_q7_nonsquare(buffer1, CONV2_IN_X, CONV2_IN_Y, CONV1_OUT_CH, conv2_ds_wt, CONV1_OUT_CH, CONV2_DS_KX, CONV2_DS_KY, CONV2_DS_PX, CONV2_DS_PY, CONV2_DS_SX, CONV2_DS_SY, conv2_ds_bias, CONV2_DS_BIAS_LSHIFT, CONV2_DS_OUT_RSHIFT, buffer2, CONV2_OUT_X, CONV2_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer2, CONV2_OUT_X*CONV2_OUT_Y*CONV2_OUT_CH);
    // pointwise conv
    ARM_convolve_1x1_HWC_q7_fast_nonsquare(buffer2, CONV2_OUT_X, CONV2_OUT_Y, CONV1_OUT_CH, conv2_pw_wt, CONV2_OUT_CH, 1, 1, 0, 0, 1, 1, conv2_pw_bias, CONV2_PW_BIAS_LSHIFT, CONV2_PW_OUT_RSHIFT, buffer1, CONV2_OUT_X, CONV2_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer1, CONV2_OUT_X*CONV2_OUT_Y*CONV2_OUT_CH);
    //CONV3 : DS + PW conv
    //Depthwise separable conv (batch norm params folded into conv wts/bias)
    ARM_depthwise_separable_conv_HWC_q7_nonsquare(buffer1, CONV3_IN_X, CONV3_IN_Y, CONV2_OUT_CH, conv3_ds_wt, CONV2_OUT_CH, CONV3_DS_KX, CONV3_DS_KY, CONV3_DS_PX, CONV3_DS_PY, CONV3_DS_SX, CONV3_DS_SY, conv3_ds_bias, CONV3_DS_BIAS_LSHIFT, CONV3_DS_OUT_RSHIFT, buffer2, CONV3_OUT_X, CONV3_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer2, CONV3_OUT_X*CONV3_OUT_Y*CONV3_OUT_CH);
    //Pointwise conv
    ARM_convolve_1x1_HWC_q7_fast_nonsquare(buffer2, CONV3_OUT_X, CONV3_OUT_Y, CONV2_OUT_CH, conv3_pw_wt, CONV3_OUT_CH, 1, 1, 0, 0, 1, 1, conv3_pw_bias, CONV3_PW_BIAS_LSHIFT, CONV3_PW_OUT_RSHIFT, buffer1, CONV3_OUT_X, CONV3_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer1, CONV3_OUT_X*CONV3_OUT_Y*CONV3_OUT_CH);
    //CONV4 : DS + PW conv
    //Depthwise separable conv (batch norm params folded into conv wts/bias)
    ARM_depthwise_separable_conv_HWC_q7_nonsquare(buffer1, CONV4_IN_X, CONV4_IN_Y, CONV3_OUT_CH, conv4_ds_wt, CONV3_OUT_CH, CONV4_DS_KX, CONV4_DS_KY, CONV4_DS_PX, CONV4_DS_PY, CONV4_DS_SX, CONV4_DS_SY, conv4_ds_bias, CONV4_DS_BIAS_LSHIFT, CONV4_DS_OUT_RSHIFT, buffer2, CONV4_OUT_X, CONV4_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer2, CONV4_OUT_X*CONV4_OUT_Y*CONV4_OUT_CH);
    //Pointwise conv
    ARM_convolve_1x1_HWC_q7_fast_nonsquare(buffer2, CONV4_OUT_X, CONV4_OUT_Y, CONV3_OUT_CH, conv4_pw_wt, CONV4_OUT_CH, 1, 1, 0, 0, 1, 1, conv4_pw_bias, CONV4_PW_BIAS_LSHIFT, CONV4_PW_OUT_RSHIFT, buffer1, CONV4_OUT_X, CONV4_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer1, CONV4_OUT_X*CONV4_OUT_Y*CONV4_OUT_CH);
    //CONV5 : DS + PW conv
    //Depthwise separable conv (batch norm params folded into conv wts/bias)
    ARM_depthwise_separable_conv_HWC_q7_nonsquare(buffer1, CONV5_IN_X, CONV5_IN_Y, CONV4_OUT_CH, conv5_ds_wt, CONV4_OUT_CH, CONV5_DS_KX, CONV5_DS_KY, CONV5_DS_PX, CONV5_DS_PY, CONV5_DS_SX, CONV5_DS_SY, conv5_ds_bias, CONV5_DS_BIAS_LSHIFT, CONV5_DS_OUT_RSHIFT, buffer2, CONV5_OUT_X, CONV5_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer2, CONV5_OUT_X*CONV5_OUT_Y*CONV5_OUT_CH);
    //Pointwise conv
    ARM_convolve_1x1_HWC_q7_fast_nonsquare(buffer2, CONV5_OUT_X, CONV5_OUT_Y, CONV4_OUT_CH, conv5_pw_wt, CONV5_OUT_CH, 1, 1, 0, 0, 1, 1, conv5_pw_bias, CONV5_PW_BIAS_LSHIFT, CONV5_PW_OUT_RSHIFT, buffer1, CONV5_OUT_X, CONV5_OUT_Y, (q15_t*)col_buffer, NULL);
    ARM_relu_q7(buffer1, CONV5_OUT_X*CONV5_OUT_Y*CONV5_OUT_CH);
    //A verage pool
    ARM_a vepool_q7_HWC_nonsquare (buffer1, CONV5_OUT_X, CONV5_OUT_Y, CONV5_OUT_CH, CONV5_OUT_X, CONV5_OUT_Y, 0, 0, 1, 1, 1, 1, NULL, buffer2, 2);
    ARM_fully_connected_q7(buffer2, final_fc_wt, CONV5_OUT_CH, OUT_DIM, FINAL_FC_BIAS_LSHIFT, FINAL_FC_OUT_RSHIFT, final_fc_bias, out_data, (q15_t*)col_buffer);
}

清单1:ARM ML-KWS-for-MCU软件存储库中的C++ DS-CNN模型,包含一个完整的卷积层后接几个深度可分离卷积(框),每个都使用深度卷积和1x1卷积函数(黄色高亮),这些函数由硬件优化的ARM CMSIS-NN软件库支持。(代码来源:ARM)

虽然C++ DS-CNN的实现与TensorBoard中显示的模型略有不同,但整体方法是一致的。在初始全卷积核之后,一系列深度可分离卷积核将输入送至最终池化层和全连接层,生成每个输出通道的预测值(对应训练模型所用的12个类标签)。

KWS应用程序将这个模型与代码相结合,对STM32F746G-DISCO开发板采集的实时音频流进行推理。主函数初始化推理引擎,启用音频采样,然后进入一个由单个等待中断(WFI)调用构成的无限循环(清单2)。

char output_class[12][8] = {"Silence", "Unknown","yes","no","up","down",
    "left","right","on","off","stop","go"};
int main()
{
    pc.baud(9600);
    kws = new KWS_F746NG(recording_win, a veraging_window_len);
    init_plot();
    kws->start_kws();
    T.start();
    while (1) {
        /* A dummy loop to wait for the interrupts. Feature extraction and
        neural network inference are done in the interrupt service routine. */
        __WFI();
    }
}
/*
* The audio recording works with two ping-pong buffers.
* The data for each window will be tranfered by the DMA, which sends
* sends an interrupt after the transfer is completed.
*/
// Manages the DMA Transfer complete interrupt.
void BSP_AUDIO_IN_TransferComplete_CallBack(void)
{
    ARM_copy_q7((q7_t *)kws->audio_buffer_in + kws->audio_block_size*4, (q7_t *)kws->audio_buffer_out + kws->audio_block_size*4, kws->audio_block_size*4);
    if(kws->frame_len != kws->frame_shift) {
        //copy the last (frame_len - frame_shift) audio data to the start
        ARM_copy_q7((q7_t *)(kws->audio_buffer)+2*(kws->audio_buffer_size-(kws->frame_len-kws->frame_shift)), (q7_t *)kws->audio_buffer, 2*(kws->frame_len-kws->frame_shift));
    }
    // copy the new recording data
    for (int i=0;iaudio_block_size;i++) {
        kws->audio_buffer[kws->frame_len-kws->frame_shift+i] = kws->audio_buffer_in[2*kws->audio_block_size+i*2];
    }
    run_kws();
    return;
}
// Manages the DMA Half Transfer complete interrupt.
void BSP_AUDIO_IN_HalfTransfer_CallBack(void)
{
    ARM_copy_q7((q7_t *)kws->audio_buffer_in, (q7_t *)kws->audio_buffer_out, kws->audio_block_size*4);
    if(kws->frame_len!=kws->frame_shift) {
        //copy the last (frame_len - frame_shift) audio data to the start
        ARM_copy_q7((q7_t *)(kws->audio_buffer)+2*(kws->audio_buffer_size-(kws->frame_len-kws->frame_shift)), (q7_t *)kws->audio_buffer, 2*(kws->frame_len-kws->frame_shift));
    }
    // copy the new recording data
    for (int i=0;iaudio_block_size;i++) {
        kws->audio_buffer[kws->frame_len-kws->frame_shift+i] = kws->audio_buffer_in[i*2];
    }
    run_kws();
    return;
}
void run_kws()
{
    kws->extract_features(); //extract mfcc features
    kws->classify(); //classify using dnn
    kws->a verage_predictions();
    plot_mfcc();
    plot_wa veform();
    int max_ind = kws->get_top_class(kws->a veraged_output);
    if(kws->a veraged_output[max_ind]>detection_threshold*128/100)
        sprintf(lcd_output_string,"%d%% %s",((int)kws->a veraged_output[max_ind]*100/128),output_class[max_ind]);
    lcd.ClearStringLine(8);
    lcd.DisplayStringAt(0, LINE(8), (uint8_t *) lcd_output_string, CENTER_MODE);
}

清单2:在ARM ML-KWS-for-MCU软件存储库中,DS-CNN KWS应用程序的主例程(通过KWS_F746NG)实例化推理引擎,激活STM32F746G-DISCO开发板的音频子系统,并进入无限循环,等待中断调用执行推理的完成例程(run_kws())。(代码来源:ARM)

主例程中包含的回调函数提供完成例程,这些例程缓冲记录的数据并通过调用run_kws()触发推理。run_kws函数调用推理引擎实例来提取特征、分类并给出预测,表明记录的音频样本属于前述12个类别之一的概率。

推理引擎本身通过一系列调用实例化,从主函数实例化KWS_F746NG类开始,这个类本身是KWS_DS_NN的子类。后者使用父类封装了前面展示的C++ DS-CNN模型,而父类KWS实现了具体的推理引擎方法:extract_features()classify()等(清单3)。

#include "kws.h"
KWS::KWS()
{
}
KWS::~KWS()
{
    delete mfcc;
    delete mfcc_buffer;
    delete output;
    delete predictions;
    delete a veraged_output;
}
void KWS::init_kws()
{
    num_mfcc_features = nn->get_num_mfcc_features();
    num_frames = nn->get_num_frames();
    frame_len = nn->get_frame_len();
    frame_shift = nn->get_frame_shift();
    int mfcc_dec_bits = nn->get_in_dec_bits();
    num_out_classes = nn->get_num_out_classes();
    mfcc = new MFCC(num_mfcc_features, frame_len, mfcc_dec_bits);
    mfcc_buffer = new q7_t[num_frames*num_mfcc_features];
    output = new q7_t[num_out_classes];
    a veraged_output = new q7_t[num_out_classes];
    predictions = new q7_t[sliding_window_len*num_out_classes];
    audio_block_size = recording_win*frame_shift;
    audio_buffer_size = audio_block_size + frame_len - frame_shift;
}
void KWS::extract_features()
{
    if(num_frames>recording_win) {
        //move old features left
        memmove(mfcc_buffer, mfcc_buffer+(recording_win*num_mfcc_features), (num_frames-recording_win)*num_mfcc_features);
    }
    //compute features only for the newly recorded audio
    int32_t mfcc_buffer_head = (num_frames-recording_win)*num_mfcc_features;
    for (uint16_t f = 0; f < recording_win; f++) {
        mfcc->mfcc_compute(audio_buffer+(f*frame_shift), &mfcc_buffer[mfcc_buffer_head]);
        mfcc_buffer_head += num_mfcc_features;
    }
}
void KWS::classify()
{
    nn->run_nn(mfcc_buffer, output);
    // Softmax
    ARM_softmax_q7(output, num_out_classes, output);
}
int KWS::get_top_class(q7_t* prediction)
{
    int max_ind=0;
    int max_val=-128;
    for(int i=0;i

清单3:在ARM DS-CNN KWS应用程序中,KWS模块在基本DS-CNN类上添加了执行推理操作所需的方法,包括特征提取、分类和生成由平均窗口平滑的结果。(代码来源:ARM)

所有这些软件复杂性都被封装到了直观的使用模型背后:主程序先实例化推理引擎,然后在音频输入可用时,利用完成例程来执行推理。据ARM称,在STM32F746G-DISCO开发板上运行这个示例CMSIS-NN实现,一次推理周期仅需大约12毫秒(ms),这包含了音频数据拷贝、特征提取和DS-CNN模型执行。更重要的是,整个KWS应用仅占用约70KB的内存。

结论

关键词唤醒(KWS)功能的重要性日益凸显,对于资源有限的可穿戴设备和物联网设计来说,开发者迫切需要体积小巧的推理引擎。ARM CMSIS-NN正是瞄准了Cortex-M7 MCU中的DSP功能,为DS-CNN这类优化神经网络架构的落地奠定了坚实基础,完全能够满足上述要求。

在基于Cortex-M7 MCU的开发系统上运行的KWS推理引擎,能够在物联网设备轻松承受的内存占用下,实现接近每秒10次推理的高性能表现。

来源:https://m.elecfans.com/article/1870766.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。