当前位置: 首页 > article >正文

基于vpk180边缘场景下分布式神经网络训练模型部署

本项目目标在于针对边缘场景实现P2P的分布式训练过程,设计方案为将神经网络训练过程对应的裸机程序部署在了PS端的ARM Cortex-A72核上,传输方案采用开发板板载的GTM收发器硬件资源通过外部QSFP-DD光模块光传输至对端,最终完成了设计目标。

整个项目的实现细节可分为如下几个重要部分,同时整个系统的实现框图及实际部署图如下图所示:

欲部署神经网络模型的介绍及相关公式的推导;
PS端ARM Cortex-A72裸机训练程序部署;
PL端基于aurora64b66b自定义协议的GTM板间光传输工程搭建;
PL与PS间通过AXI-FULL总线进行数据交互。

内部模块系统框图如下:

接下来,将按照整个项目几个重要部分详细论述:

一、神经网络模型的介绍及相关公式的推导

1.1 神经网络模型相关介绍

本次推导的网络模型结构如下,其中模型层数共3层,各层神经元个数可重定义。以输入层神经元2个,隐藏层神将元3个,输出层神将元1个为例,对应模型图如下:

1.2 反向传播过程公式推导

二、PS端ARM Cortex-A72裸机训练程序部署

对应代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "xparameters.h"
#include "xstatus.h"
#include "math.h"
#include "string.h"
#include "xtimer_config.h"
#include "xiltimer.h"
#include "xil_types.h"
#include "time.h"


// 模型参数结构体
typedef struct {
    int input_size; // 输入层的大小
    int hidden_size; // 隐藏层的大小
    int output_size; // 输出层的大小
    double *W1; // 第一层的权重
    double *b1; // 第一层的偏置
    double *W2; // 第二层的权重
    double *b2; // 第二层的偏置
} NeuralNet;


//函数声明
double sigmoid(double x);
void init_net(NeuralNet *net);
void forward(NeuralNet *net, double *input, double *output);
void backward(NeuralNet *net, double *input, double *target, double learning_rate);
void train(NeuralNet *net, double *inputs, double *targets, int num_epochs, int num_inputs, double learning_rate);
void predict(NeuralNet *net, double *input, double *output);


//gpio0 baseaddr 
//gpio0用了两个通道,通道一作为输入用于传输channel_up的拉高情况
//gpio0的通道二作为输出,用于传输将参数写到Bram完成的标志
#define XPAR_AXI_GPIO_0_BASEADDR 0x80000000
//gpio1 baseaddr
//gpio1只用了一个通道,作为输入,用于传输将参数写到另一块板子BRAM中完成的标志
#define XPAR_XGPIO_1_BASEADDR 0x80010000
//AXI_Bram baseaddr
#define XPAR_AXI_BRAM_0_BASEADDRESS 0x80020000 //结束地址为0x80022000 

//gpio初始化用到的结构体变量
XGpio Axi_gpio0;
XGpio_Config *Xgpio_cfgptr0;
XGpio Axi_gpio1;
XGpio_Config *Xgpio_cfgptr1;

//gpio初始化函数声明
void gpio_init();

int main()
{
    init_platform();
    gpio_init();
    //u32 *LitePtr = (u32*) XPAR_AXI_BRAM_0_BASEADDRESS;
    //u32 wrdata = 0;
    while(XGpio_DiscreteRead( &Axi_gpio0, 1) == 0x00){}//channel_up link flag
    printf("Aurora is linked!\n\r");

    u64 begin_time;
    u64 end_time;
    long int time_dif;

    float time_cost;

   XTime_GetTime(&begin_time);//计时开始时间

    NeuralNet net;
    net.input_size = 2;
    net.hidden_size = 3;
    net.output_size = 1;
    init_net(&net);
    double inputs[] = {0,0,0,1,1,0,1,1};//训练数据集
    double targets[] = {0,1,1,0};
    //将数据集分成两份
    //double inputs[]={0,0,0,1};
    //double targets[]={0,1};
    train(&net, inputs, targets, 1, 4, 0.001);//训练一次      
    //打印各层参数,共有13个参数
    for(int i=0;i<net.input_size*net.hidden_size;i++){
        printf("The first layer of training weight parameters are as follows:W1[%d]=%f\n\r",i,net.W1[i]);//6个参数
    }
    for(int i=0;i<net.hidden_size;i++){
        printf("The first layer training bias parameters are as follows:b1[%d]=%f\n\r",i,net.b1[i]);//3个参数
    }
    for(int i=0;i<net.hidden_size*net.output_size;i++){
        printf("The second layer training weight parameters are as follows:W2[%d]=%f\n\r",i,net.W2[i]);//3个参数
    }
    for(int i=0;i<net.output_size;i++){
        printf("The second layer training bias parameters are as follows:b2[%d]=%f\n\r",i,net.b2[i]);//1个参数
    }
    // //对训练完的参数进行量化,量化成32位有符号定点数*8192
    // int w1_storage[6]={0,0,0,0,0,0};
    // int b1_storage[3]={0,0,0};
    // int w2_storage[3]={0,0,0};
    // int b2_storage[1]={0};
    int32_t *LitePtr = (u32*) XPAR_AXI_BRAM_0_BASEADDRESS;//将存储空间指向BRAM的基地址
    // //w1量化
    // for(int i=0;i<net.input_size*net.hidden_size;i++){
    //     w1_storage[i] = net.W1[i]*8192;
    // }
    // //w1写入Bram 0-5地址
    // for(int i=0;i<net.input_size*net.hidden_size;i++){
    //      *(LitePtr+i) =  w1_storage[i];//0-5
    // }
    // //b1量化
    // for(int i=0;i<net.hidden_size;i++){
    //     b1_storage[i] = net.b1[i]*8192;
    // }
    // //b1写入BRAM 6-8地址
    // *(LitePtr+6) = b1_storage[0];
    // *(LitePtr+7) = b1_storage[1];
    // *(LitePtr+8) = b1_storage[2];
    // //w2量化
    // for(int i=0;i<net.hidden_size*net.output_size;i++){
    //     w2_storage[i] = net.W2[i]*8192;
    // }
    // //w2写入Bram 9-11地址
    // *(LitePtr+9) =  w2_storage[0];
    // *(LitePtr+10) = w2_storage[1];
    // *(LitePtr+11) = w2_storage[2];
    // //b2量化
    // for(int i=0;i<net.output_size;i++){
    //     b2_storage[i] = net.b2[i]*8192;
    // }
    // //将量化完的b2写入12地址
    // *(LitePtr+12) = b2_storage[0];
    *LitePtr = net.W1[0]*8192;
    *(LitePtr+1) = net.W1[1]*8192;




    //利用AXI_GPIO通知PL端已写完
    XGpio_DiscreteWrite(&Axi_gpio0, 2, 0x01);
    //另外一块板已收到数据,并将数据写入PS中
    while(XGpio_DiscreteRead( &Axi_gpio1, 1) == 0x00){}//write to ps end
    

    //这部分会将收到的数据存到指定的地址,接着来作reduce操作,做完reduce操作计数结束
    // //read data from Bram
    // LitePtr = (u32*) XPAR_AXI_BRAM_0_BASEADDRESS;
    // for(int i=9;i<18;i++){
    //     printf("write data is %d",*(LitePtr+i));
    // }


    XTime_GetTime(&end_time);
    time_dif = end_time - begin_time;
    time_cost =(float)(time_dif)/COUNTS_PER_SECOND;
    printf("time_cost = %.4fs",time_cost);

    // for(int i=0;i<9;i++){
    //     *LitePtr++ = wrdata++;//0-9
    // }
    // XGpio_DiscreteWrite(&Axi_gpio0, 2, 0x01);
    // while(XGpio_DiscreteRead( &Axi_gpio1, 1) == 0x00){}//write to ps end
    // //read data from Bram
    // LitePtr = (u32*) XPAR_AXI_BRAM_0_BASEADDRESS;
    // for(int i=9;i<18;i++){
    //     printf("write data is %d",*(LitePtr+i));
    // }
    cleanup_platform();
    return 0;
}

//gpio init
void gpio_init(){
    Xgpio_cfgptr0 = XGpio_LookupConfig(XPAR_AXI_GPIO_0_BASEADDR);
    XGpio_CfgInitialize(&Axi_gpio0,Xgpio_cfgptr0,Xgpio_cfgptr0->BaseAddress);
    XGpio_SetDataDirection(&Axi_gpio0, 1,1);//channel 1:input
    XGpio_SetDataDirection(&Axi_gpio0, 2,0);//channel 2:output

    Xgpio_cfgptr1 = XGpio_LookupConfig(XPAR_AXI_GPIO_1_BASEADDR);
    XGpio_CfgInitialize(&Axi_gpio1,Xgpio_cfgptr1,Xgpio_cfgptr1->BaseAddress);
    XGpio_SetDataDirection(&Axi_gpio1, 1,1);//channel 1:input
    //XGpio_SetDataDirection(&Axi_gpio0, 2,0);//channel 2:output
}

double sigmoid(double x) {
    return 1 / (1 + exp(-x));
}

// 初始化权重和偏置参数
void init_net(NeuralNet *net) {
    net->W1 = (double*) malloc(net->input_size * net->hidden_size * sizeof(double));
    net->b1 = (double*) malloc(net->hidden_size * sizeof(double));
    net->W2 = (double*) malloc(net->hidden_size * net->output_size * sizeof(double));
    net->b2 = (double*) malloc(net->output_size * sizeof(double));
    //srand(time(NULL));
    for (int i = 0; i < net->input_size * net->hidden_size; i++) {
        net->W1[i] = (double)rand()/RAND_MAX;
    }
    for (int i = 0; i < net->hidden_size * net->output_size; i++) {
        net->W2[i] = (double)rand()/RAND_MAX;
    }
    for (int i = 0; i < net->hidden_size; i++) {
        net->b1[i] = (double)rand()/RAND_MAX;
    }
    for (int i = 0; i < net->output_size; i++) {
        net->b2[i] = (double)rand()/RAND_MAX;
    }
}

//前向传播
void forward(NeuralNet *net, double *input, double *output) {
    double *h1 = (double*) malloc(net->hidden_size * sizeof(double));
    double *h2 = (double*) malloc(net->output_size * sizeof(double));
    // 第一层的计算
    for (int i = 0; i < net->hidden_size; i++) {
        h1[i] = 0;
        for(int j = 0; j < net->input_size; j++){
            h1[i] += input[j] * net->W1[j*net->hidden_size+i];
        }
        h1[i] += net->b1[i];
        h1[i] = tanh(h1[i]);
    }
    // 第二层的计算
    for (int i = 0; i < net->output_size; i++) {
        h2[i] = 0;
        for (int j = 0; j < net->hidden_size; j++) {
            h2[i] += h1[j] * net->W2[j*net->output_size+i];
        }
        h2[i] += net->b2[i];
    }
    // 输出的激活函数    
    for (int i = 0; i < net->output_size; i++) {
        output[i] = sigmoid(h2[i]);
    }
    free(h1);
    free(h2);
}

// 反向传播
void backward(NeuralNet *net, double *input, double *target, double learning_rate) {
    double *h1 = (double*) malloc(net->hidden_size * sizeof(double));
    double *h2 = (double*) malloc(net->output_size * sizeof(double));
    double *delta1 = (double*) malloc(net->hidden_size * sizeof(double));
    double *delta2 = (double*) malloc(net->output_size * sizeof(double));
    // 第一层的计算
    for (int i = 0; i < net->hidden_size; i++) {
        h1[i] = 0;
        for (int j = 0; j < net->input_size; j++) {
            h1[i] += input[j] * net->W1[j*net->hidden_size+i];
        }
        h1[i] += net->b1[i];
        h1[i] = tanh(h1[i]);
    }
    // 第二层的计算
    for (int i = 0; i < net->output_size; i++) {
        h2[i] = 0;
        for (int j = 0; j < net->hidden_size; j++) {
            h2[i] += h1[j] * net->W2[j*net->output_size+i];
        }
        h2[i] += net->b2[i];
    }
    // 输出激活函数    
    for (int i = 0; i < net->output_size; i++) {
        h2[i] = sigmoid(h2[i]);
        delta2[i] = h2[i] * (1-h2[i]) * (target[i]-h2[i]);
    }
    // 第一层的误差传递  
    for (int i = 0; i < net->hidden_size; i++) {
        delta1[i] = 0;
        for (int j = 0; j < net->output_size; j++) {
            delta1[i] += delta2[j] * net->W2[i*net->output_size+j];
        }
        delta1[i] *= (1-h1[i]) * (1+h1[i]);
    }
    // 权重和偏置的更新
    for (int i = 0; i < net->hidden_size; i++) {
        for (int j = 0; j < net->input_size; j++) {
            net->W1[j*net->hidden_size+i] += learning_rate * delta1[i] * input[j];
        }
        net->b1[i] += learning_rate * delta1[i];
    }
    for (int i = 0; i < net->output_size; i++) {
        for (int j = 0; j < net->hidden_size; j++) {
            net->W2[j*net->output_size+i] += learning_rate * delta2[i] * h1[j];
        }
        net->b2[i] += learning_rate * delta2[i];
    }
    free(h1);
    free(h2);
    free(delta1);
    free(delta2);
}

// 训练神经网络
void train(NeuralNet *net, double *inputs, double *targets, int num_epochs, int num_inputs, double learning_rate) {
    for (int i = 0; i < num_epochs; i++) {
        for (int j = 0; j < num_inputs; j++) {
            double *input = &inputs[j*net->input_size];
            double *target = &targets[j*net->output_size];
            backward(net, input, target, learning_rate);
        }
    }
}

// 使用神经网络进行预测
void predict(NeuralNet *net, double *input, double *output) {
    forward(net, input, output);
}

三、PL端基于aurora64b66b自定义协议的GTM板间光传输工程搭建

这部分采用xilinx官方提供的aurora64B66B IP,IP配置如下:

配置完成后点击run block automation后的界面如下:

通过阅读官方手册可知,CIPS与ZYNQ系列不同点在于即使不同PS端也要将其加入工程,因为由它启动硬件系统的初始化工作。

这里主要注意在example基础上,将参考时钟、初始化时钟及复位信号处理好,对QSFP-DD模块自回环测试或者仿真观察lane_up及channel_up信号拉高即完成对aurora64b66b自定义协议的配置工作。拉高界面如下:

四、PL与PS间通过AXI-FULL总线进行数据交互

同ZYNQ系列芯片类似,PL与PS端交互有对应的AXI接口,本次选用主机LPD接口:

整个数据交互通过AXI-GPIO IP和AXI BRAM Controller配合真双端口RAM实现(一侧连接到PS,另一端连接到PL),整个交互流程为PL端channel up信号拉高后,通过AXI-GPIO发送给PS端;PS端收到channel up信号并判断本机训练完后,将梯度数据存入BRAM中,存完告知PL端;PL端收到反馈信号后,将读出的数据传给aurora顶层模块,同时通过fifo进行跨时钟域,并在握手成功的情况下与上~empty信号启动读数据;对板收到数据后同样写入BRAM,并由PS读出,读取数据完成后,将数据做reduce操作,完成后继续训练,这样整个过程便完成了。

对应的模块框图及中间结果记录如下:

建链成功标志:

ps向BRAM写完的标志:

存入参数:

五、训练时间对比

训练时间统计如下:

2个数据集单板训练时间为0.000006s,4个数据集单板训练时间为0.0000095S。训练完写入bram过程时间为:0.0000006s。通过回环将数据传入另一块板子ram中整个通讯过程消耗的时间为:0.0000020s。则说明即使在数据量较小的情况下,两板间通信带来的开销仍对分布式训练时间加速有益处。


http://www.kler.cn/a/429771.html

相关文章:

  • PyCharm文档管理
  • 亚远景-ASPICE评估:汽车软件项目的过程能力评价
  • 医院远程诊断管理系统|Java|SSM|JSP| 前后端分离
  • 发布订阅者=>fiber=>虚拟dom
  • 【AI】Jetson Nano中安装DeepStream
  • MySQL生产环境备份脚本
  • 【JavaWeb后端学习笔记】登录校验(JWT令牌技术、Interceptor拦截器、Filter过滤器)
  • 学生信息管理系统(简化版)数据库部分
  • 基于公网的无线全双工内部通话系统在演出行业可以用吗?
  • 纯虚函数和抽象类在面向对象编程中的意义
  • 【机器学习】基础知识:SSR-残差平方和(Sum of Squared Residuals)
  • 能源变革,分布式光伏与储能协调控制
  • socket UDP 环路回显的服务端
  • OPC UA 客户端开发工具,模拟器,可视化GUI
  • HarmonyOS-高级(一)
  • 使用 `typing_extensions.TypeAlias` 简化类型定义:初学者指南
  • 入门网络安全工程师要学习哪些内容【2025年寒假最新学习计划】
  • 在2023idea中如何创建SpringBoot
  • 嵌入式蓝桥杯学习6 定时中断按键(短按 长按 双击)
  • Spring Boot前沿技术集成:驱动招聘信息管理系统高效运转
  • Rust之抽空学习系列(一)—— Hello World
  • Java的Mvc整合Swagger的knife4框架