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

4-GGML:看一下加法算子!

GGML

    • CPU加法
    • GPU加法

矩阵加法作为矩阵运算中比较简单的计算 M d = M 0 + M 1 M_d = M_0 + M_1 Md=M0+M1只需要把对应位置的元素加起来就好了,对于GGML的矩阵加法,其实有一点扩展的意味,他在一定程度上允许了两个矩阵的维度不是很一致也就是进行repeat的操作,如下定义的,这里注意一下顺序,其实在用的过程中他又反了一下.也就是 M 0 M_0 M0的维度是 M 1 M_1 M1的整数倍

// 要求 t1的每个维度都必须是t0的对应维度的整数倍
bool ggml_can_repeat(const struct ggml_tensor * t0, const struct ggml_tensor * t1) {static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function");return ggml_is_empty(t0) ? ggml_is_empty(t1) :(t1->ne[0]%t0->ne[0] == 0) &&(t1->ne[1]%t0->ne[1] == 0) &&(t1->ne[2]%t0->ne[2] == 0) &&(t1->ne[3]%t0->ne[3] == 0);
}

CPU加法

多线程的矩阵加法代码如下所示,对于关键的信息一步一步来看.

  • 索引这个线程计算范围,也就是该线程计算多少个数从哪开始计算,矩阵计算中最小的单位是行,比如一共10个线程,矩阵一共100行,每个线程算10行就好了;如果矩阵只有1行,那么第一个线程算1行,其他线程休息.
const int ith = params->ith;// 线程编号
const int nth = params->nth;// 多线程总数
const int nr  = ggml_nrows(src0);// 行中变量的个数(tensor->ne[1]*tensor->ne[2]*tensor->ne[3])
// rows per thread 每个线程分配的行的个数
const int dr = (nr + nth - 1)/nth;
// row range for this thread 行的范围编号
const int ir0 = dr*ith;// 开始
const int ir1 = MIN(ir0 + dr, nr);// 结束,保证别超范围
  • 对于src是连续的情况,连续的意思是,在0维度比如2*2的矩阵{{1,2},{3,4}}存储的时候连续存的,暂时还没遇到非连续的,遇到后会补充一下例子.这里只分析连续的情况吧,因为不连续的情况计算索引的原理是一致的.
    计算在 M 0 M_0 M0矩阵下的这一维度的索引,将所有的行号索引还原回各个维度上的索引,也就是这个1,2,3维度的索引值,同时你可以看到他是int64的单位
// 每个i03块包含ne02*ne01行
const int64_t i03 = ir/(ne02*ne01);                     // 这一行在i3上的编号
// 剩下的子小块再锁定ne01
const int64_t i02 = (ir - i03*ne02*ne01)/ne01;          // 这一行在i2上的编号
const int64_t i01 = (ir - i03*ne02*ne01 - i02*ne01);    // 这一行在i1上的编号
  • 处理repeat的情况.
    可以看i03是在0矩阵上的索引,对于1矩阵的维度可以是0矩阵整数倍缩小的.以下是计算矩阵1的行数据的索引.同时nr0代表的是如果行也是倍数关系是需要把行也复制多遍的
const int64_t i13 = i03 % ne13;
const int64_t i12 = i02 % ne12;
const int64_t i11 = i01 % ne11;
const int64_t nr0 = ne00 / ne10;// 这个是0维度的整数倍率
  • 找到行地址.上面我们确定了计算的这一行位于矩阵各个维度的索引,用索引可以获得存储在内存空间的位置.注意这里用的是nb,关于nb不清楚的可以看解释tensor的那一个博文.
// 目标地址的行号 ptr
float * dst_ptr  = (float *) ((char *) dst->data  + i03*nb3  + i02*nb2  + i01*nb1 );
float * src0_ptr = (float *) ((char *) src0->data + i03*nb03 + i02*nb02 + i01*nb01);
float * src1_ptr = (float *) ((char *) src1->data + i13*nb13 + i12*nb12 + i11*nb11);
  • 对每一行进行批量拷贝
for (int64_t r = 0; r < nr0; ++r) {// 拷贝倍率次// 个数 输出地址 输入0地址 输入1地址ggml_vec_add_f32(ne10, dst_ptr + r*ne10, src0_ptr + r*ne10, src1_ptr);
}
  • 整体的代码可以过一遍了,梳理下流程:
  1. 分配任务:以行为单位,计算需要算哪行
  2. 将行号翻译成每一行在src0,src1上的矩阵维度
  3. 按唯独标号计算内存空间位置
  4. 批量加
// ggml前向推理中的add 32函数
static void ggml_compute_forward_add_f32(const struct ggml_compute_params * params,// 线程相关信息struct ggml_tensor * dst) {const struct ggml_tensor * src0 = dst->src[0];// add = 1+2 中的1const struct ggml_tensor * src1 = dst->src[1];// add = 1+2 中的2// src0与dst的维度要一样 && src0是src1的整数倍GGML_ASSERT(ggml_can_repeat(src1, src0) && ggml_are_same_shape(src0, dst));const int ith = params->ith;const int nth = params->nth;const int nr  = ggml_nrows(src0);// 行中变量的个数(tensor->ne[1]*tensor->ne[2]*tensor->ne[3])GGML_TENSOR_BINARY_OP_LOCALSGGML_ASSERT( nb0 == sizeof(float));GGML_ASSERT(nb00 == sizeof(float));// rows per thread 每个线程分配的行的个数const int dr = (nr + nth - 1)/nth;// row range for this thread 行的范围编号const int ir0 = dr*ith;const int ir1 = MIN(ir0 + dr, nr);if (nb10 == sizeof(float)) {// nb10是单个float大小 也就是src1是连续的 加一的最小单位是float 4个字节// 在所需要计算的行上编号上进行计算for (int ir = ir0; ir < ir1; ++ir) {// 对总行数进行区分// src1 is broadcastable across src0 and dst in i1, i2, i3// 每个i03块包含ne02*ne01行const int64_t i03 = ir/(ne02*ne01);                     // 这一行在i3上的编号// 剩下的子小块再锁定ne01const int64_t i02 = (ir - i03*ne02*ne01)/ne01;          // 这一行在i2上的编号const int64_t i01 = (ir - i03*ne02*ne01 - i02*ne01);    // 这一行在i1上的编号// 这一行在i3上的编号(考虑倍率) 如果ne13是等于ne03的,则肯定就按i03来计算// 其实这是为了处理src1和src0维度不一样的情况const int64_t i13 = i03 % ne13;const int64_t i12 = i02 % ne12;const int64_t i11 = i01 % ne11;const int64_t nr0 = ne00 / ne10;// 这个是0维度的整数倍率// 目标地址的行号 ptrfloat * dst_ptr  = (float *) ((char *) dst->data  + i03*nb3  + i02*nb2  + i01*nb1 );float * src0_ptr = (float *) ((char *) src0->data + i03*nb03 + i02*nb02 + i01*nb01);float * src1_ptr = (float *) ((char *) src1->data + i13*nb13 + i12*nb12 + i11*nb11);for (int64_t r = 0; r < nr0; ++r) {// 拷贝倍率次
#ifdef GGML_USE_ACCELERATEvDSP_vadd(src0_ptr + r*ne10, 1, src1_ptr, 1, dst_ptr + r*ne10, 1, ne10);
#else// 个数 输出地址 输入0地址 输入1地址ggml_vec_add_f32(ne10, dst_ptr + r*ne10, src0_ptr + r*ne10, src1_ptr);
#endif}}} else {// src1 is not contiguous 不连续的情况for (int ir = ir0; ir < ir1; ++ir) {// src1 is broadcastable across src0 and dst in i1, i2, i3const int64_t i03 = ir/(ne02*ne01);const int64_t i02 = (ir - i03*ne02*ne01)/ne01;const int64_t i01 = (ir - i03*ne02*ne01 - i02*ne01);const int64_t i13 = i03 % ne13;const int64_t i12 = i02 % ne12;const int64_t i11 = i01 % ne11;float * dst_ptr  = (float *) ((char *) dst->data  + i03*nb3  + i02*nb2  + i01*nb1 );float * src0_ptr = (float *) ((char *) src0->data + i03*nb03 + i02*nb02 + i01*nb01);for (int64_t i0 = 0; i0 < ne0; ++i0) {// 行的元素逐个进行计算const int64_t i10 = i0 % ne10;// 每次单独计算src1的地址float * src1_ptr = (float *) ((char *) src1->data + i13*nb13 + i12*nb12 + i11*nb11 + i10*nb10);dst_ptr[i0] = src0_ptr[i0] + *src1_ptr;}}}
}

GPU加法

http://www.dtcms.com/a/586492.html

相关文章:

  • TypeVariable 检测与转换 type instanceof TypeVariable (TypeVariable<?>) type
  • 做搜狗手机网站排joomla做的网站
  • Android Hilt教程_构造函数
  • 花都个性设计商城网站建设wordpress 优酷插件
  • NCP13992中文版手册 工作原理
  • 网站建设三亚做一个网站得多少钱
  • 11.8 脚本网页 星际逃生
  • 用数据库做学校网站做ic芯片的要去哪个网站
  • 石油大学网页设计与网站建设天津塘沽爆炸视频
  • 建设银行反钓鱼网站影视网站怎么建设
  • leetcode 1611
  • 可信网站多少钱网站建设 网络科技
  • 电容充电:电子在极板上“分家囤货”,靠电场把电能“锁”起来
  • 做网站前怎么写文档phpcms模板下载
  • 在哪里创建网站平台自己的电脑如何做网站
  • 做一个网站要多少钱wordpress 短信通知
  • 太原网站优化哪家专业网站字体选择
  • 知识图谱之——图数据库:以“关系”为核心的数据新大陆
  • doris通过外部表同步数据
  • 做网站视频存储在哪里网页设计与制作课件张松炎
  • 做网站dwshopex 如何看 网站后台
  • 网站建设要做哪些工作室想开个网站怎么做
  • Trie树相关算法题java实现
  • (根号分治)nfls #1982 倍数点更新 题解
  • 哪个网站注册域名便宜中小企业网站建设济南兴田德润电话
  • 深圳 网站建设 销售seo的中文含义是
  • ui网站建设wordpress视频设置方法
  • asp网站后台产品管理中增加一个功能怎样修改微信小程序怎么收费
  • 做网站应该画什么图合肥市城乡城乡建设局网站
  • 仓颉编程语言中的Option类型与模式匹配深入解析