CUDA编程(二)

概要

源于facebook的faiss库,其用过GPU 对于进行加速,另外xfr中也用到GPU,而需求源于之前xid对于大数据量的性能问题。之前也了解过一些机器学习的平台,如tersorflow,paddlepaddle,他们均将GPU加速作为平台的一大“卖点”。因此思考GPU编程对于智能基础服务还是有很大的帮助,于是“贸然”涉足了这一领域。

CUDA编程给我的感觉是 1. 入门很容易,我大概用了一周的时间完成了对于cuda基础知识的学习,又用了一周的时间尝试将xid的核心函数迁移过来进行试验,并且尝试了多种优化方案,效果也都还不错;2. 进一步深入去运用发现很难,比如cuda同c++的混合编译,host和device的异步执行,包括对于细节的调优。在某一些理论上觉得会产生优化效果的地方,性能反而变差;对于一些理论,反复推究也不得其理。中途一直有放弃的念头,1是目前没有明确的应用,2是中途遇到的各种困难导致我也不确信是否能做出来。不过最终还是坚持写出来了一版。

整体工作分为两个部分。第一是尽量和xid的接口兼容,完成后能集成进xid进行测试验证。这部分工作完成后,一个较大的问题是GPU与CPU的传输数据很大,导致这部分的性能开销不能忽略;另一个是相对简单,很多CUDA的并行特性没有使用到,因此有了后面第二版的开发工作。它不在依赖xid的框架限制,接口设计更为灵活,在很多方面也对第一版的功能进行了补充。

 

特性

第一版
  1. 支持多卡;
  2. 有内存池管理地址空间;
  3. 有对象池可以重复利用request相关的参数;
  4. 输出是所有item的distance,数据量较大;
第二版
  1. 支持多卡,并且可以控制使用的卡数;
  2. 有内存池管理地址空间,支持动态扩展;
  3. 有对象池可以重复利用request相关的参数,支持动态扩展;
  4. 支持数据的添加、更新和删除操作;
  5. 使用多stream支持并行请求,充分“压榨”GPU的性能;
  6. 输出是相似度最高的top k个数据,运算部分放到了gpu来做;
  7. 单元测试;

 

API

bool gpu_init(); // initialize

 

void gpu_set_max_devices(const int used); // maximun gpu used

 

void gpu_search(int block,  // in which block

                const float* item, // 256 dim search item

                const int k, // top k return

                DistanceIndex* output); // top k distances return

 

bool gpu_add_block(int block, // in which block

                   const int num, // numbers to insert, batch mode

                   const float* data); // insert data

 

bool gpu_update_block(int block, // in which block

                      const int seq, // the sequence number

                      const float* data); // update data

 

bool gpu_delete_block(int block, // in which block

                      const int seq); // delete data

 

bool gpu_clear_block(int block); // empty exact block data

 

bool gpu_close(); // all data cleared

 

bool gpu_dump_block(int block, // in which block

                    int start, // for check propose, both start and end are inclusive

                    int end,

                    vector<vector<float> >& result);

使用DEMO

struct DistanceIndex {

    float distance;  // 欧式距离

    int index;  // 序列号

};

 

 

TEST(testCase,test1){

    ASSERT_TRUE(xsearch::gpu_init());  // 初始化,准备资源

    int data_num = 500000; // 测试50000万底库的数据

    float* data = genBatchData(data_num);

    ASSERT_TRUE(xsearch::gpu_add_block(0, data_num, data));  // 添加数据

    float* search_item = genBatchData(1); 

    int top_k = 4;  // 只返回距离最小的四个数据

    DistanceIndex* ret = new DistanceIndex[4];

    struct timeval start, end;  // 记录时间开销

    gettimeofday( &start, NULL );

    gpu_search(0, search_item, top_k, ret);  // 开始在50000万底库中查找距离最近的档案

    gettimeofday( &end, NULL );

    int cost = end.tv_sec*1000000+end.tv_usec - start.tv_sec*1000000-start.tv_usec;

    cout << "cost: " << cost << endl;

    showSearchResult(ret, top_k);

    ASSERT_TRUE(xsearch::gpu_close());

    delete[] ret;

    delete[] search_item;

    delete[] data;

}

单卡跑50万底库的数据,时间开销8.3ms,符合之前的demo测试结果;

代码架构

 

核心类功能简介

类名

功能

API对外不暴露的接口
DeviceManager
  1. gpu卡的管理工作;
  2. 指定block数据存放在具体的卡;
DataManager对于档案数据的添加、更新和查找的管理工作;
GpuDataImp
  1. 对于档案数据的添加、更新执行工作;
  2. 作为查找接口的提供相应的“资源”;
KernelSearch计算当前档案和底库中所有档案的距离;
TopK计算距离最近的top k个档案,返回档案的编号和距离值;

 

GPU并行运算

当前的思路是,1. 先计算出当前特征值和底库中所有档案的距离;2. 选出top k个距离最小的值;关于1,前一篇wiki中已经介绍得很详细了,下面主要介绍2.

算法

  1. 在每个block中的thread先计算所管辖的数据,将top k个参数放入block的shared memory中;
  2. 使用reduction的方式,每个block整合shared memory中的数据,计算得出当前block top k的距离值和序列号;
  3. 重新启动一个核函数,只使用一个block number,类似于step 2的处理方式,计算得出最终的 top k的距离值和序列号;

优化方案

主要涉及到GPU的架构和CUDA算法

  1. GPU中每个SM是以warp的方式进行并行运算,同时只允许启动一个warp。每个warp包含了32个线程。shared memory是整个block中所有线程的共享空间,但是每个warp中的线程也可以获取到其它线程的变量值,并且理论上使用这种方法将提升性能;同时减少shared memory的使用量,可以提升SM管理block的数量(每个SM中shared memory的总量是一定的);
  2. 考虑到当前使用GPU是1080Ti,它允许每个block可以最大启动1024个thread,另外它包含了28个SM;
  3. 由于我们的算法涉及到两次核函数的调用,每次调用会涉及到一些参数值的选取。比如给定底库的总量total_num和选取topk的值,可以动态调整第一次核函数的thread_num和block_num,当total_num比较小或者topk较大时,可以让每个thread多处理一些数据;
  4. 临近数据的读取性能会更加,因此如何划分处理的数据也是有讲究的;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值