特征向量检索
Atlas 200/300/500 推理产品上,不支持该功能。
Atlas 训练系列产品上,不支持该功能。
Atlas A2训练系列产品/Atlas 800I A2推理产品上,不支持该功能。
Atlas 200I/500 A2推理产品上,不支持该功能。
基本原理
该部分主要实现了对特征检索的功能验证,生成随机底库,随机生成特征数据进行特征检索(当前支持1:N、M:N两种检索模式,下文的示例代码以1:N为例)。大致可分为初始化、添加特征到底库、底库搜索、精准修改或删除底库特征、去初始化几个主要步骤,具体接口调用方式如下:
- 初始化:调用aclInit接口进行AscendCL初始化、运行管理资源申请,调用aclfvCreateInitPara接口创建aclfvInitPara类型的数据来指定特征向量检索的初始化参数。
- 添加特征到底库:主要调用aclfvCreateFeatureInfo接口创建aclfvFeatureInfo类型数据来表示创建特征的描述信息,然后调用aclfvRepoAdd添加底库。
- 底库搜索:调用aclfvSearch接口来实现检索。
- 精准修改或删除底库特征:调用aclfvDel和aclfvModify接口来实现删除或修改底库中某个特征。下文的代码以删除底库特征为例。
- 去初始化:主要包括释放运行管理资源、调用aclfvDestroyInitPara接口销毁aclfvInitPara类型的数据、调用aclfvRelease接口特征检索模块去初始化,释放内存空间。
示例代码
本节中的示例重点介绍特征向量检索的代码逻辑,AscendCL初始化和去初始化请参见AscendCL初始化,运行管理资源申请与释放请参见运行管理资源申请与释放。
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
// 1.AscendCL初始化 // 2.申请运行管理资源 // 设置默认的运行模式为HOST aclrtRunMode runMode = ACL_HOST; // 3.初始化 // 3.1 初始化特征检索模块,此处以底库特征数100000为例 size_t fsNum = 100000; fvInitPara = aclfvCreateInitPara(fsNum); // 3.2 指定特征向量检索的初始化参数 ret = aclfvInit(fvInitPara); // 4.添加底库和特征向量 // 4.1 增加第一个特征,创建特征描述信息时,偏移量offset参数值为0 uint32_t offset = 0; uint32_t featureCount = 1000; uint32_t featureLen = 36; // 此处的自定义函数BaseShortFeaAlloc用于生成特征随机数据,由用户自行实现 void *featureData = BaseShortFeaAlloc(1000, static_cast<size_t>(featureCount), 0); std::shared_ptr<void> feaBufPtr(featureData, [](void *p){(void)aclrtFreeHost(p);}); void *inputData = featureData; std::shared_ptr<void> inputDataPtr = nullptr; // 如果运行模式为ACL_HOST,则需要申请内存,再通过aclrtMemcpy接口将Host的随机特征数据传输到Device;否则直接将随机特征数据读入Device内存 if (aclrtGetRunMode(&runMode) == ACL_HOST) { // 为inputData申请内存 ret = aclrtMalloc(&inputData, featureLen * featureCount, ACL_MEM_MALLOC_HUGE_FIRST); // 将随机特征数据读入到Device内存中 inputDataPtr.reset(inputData, [](void *p) {(void)aclrtFree(p);}); // 将featureData从Host侧拷贝到Device侧 ret = aclrtMemcpy(inputData, featureLen * featureCount, featureData, featureLen * featureCount, ACL_MEMCPY_HOST_TO_DEVICE); } // 创建特征描述信息,inputData表示前一步的特征随机数据 auto featureInfo = aclfvCreateFeatureInfo(id0, id1, offset, featureLen, featureCount, reinterpret_cast<uint8_t *>(inputData), featureLen * featureCount); // 添加底库并向底库中添加特征,featureInfo表示前一步的特征描述信息 aclError ret = aclfvRepoAdd(SEARCH_1_N, featureInfo); // 销毁aclfvFeatureInfo特征描述信息 aclfvDestroyFeatureInfo(featureInfo); // 4.2增加第二个特征,创建特征描述信息时,偏移值offset需要与库中已添加特征个数一致,并精确删除或修改底库中的某个特征 offset += featureCount; // 增加特征到底库的步骤,参考4.1中的代码 // .... uint8_t featureData[36]; for (size_t i = 0; i < 36; i++) { featureData[i] = static_cast<uint8_t>(i); } // 创建内存并传输特征数据 void *inputData = nullptr; aclrtMalloc(&inputData, 36, ACL_MEM_MALLOC_HUGE_FIRST); std::shared_ptr<void> inputDataPtr(inputData, [](void *p){(void)aclrtFree(p);}); aclrtMemcpyKind kind = ACL_MEMCPY_DEVICE_TO_DEVICE; // 如果运行模式是ACL_HOST,将特征数据拷贝到Device侧,否则无需拷贝,其中dataLen为featureData指针申请的内存长度 if (aclrtGetRunMode(&runMode) == ACL_HOST) { kind = ACL_MEMCPY_HOST_TO_DEVICE; } aclrtMemcpy(inputData, 36, featureData, dataLen, kind); // 创建特征描述信息 uint32_t id0 = 0; uint32_t id1 = 0; auto featureInfo1 = aclfvCreateFeatureInfo(id0, id1, offset, 36, 1, reinterpret_cast<uint8_t *>(inputData), 36); std::shared_ptr<aclfvFeatureInfo> featureInfoPtr(featureInfo1, [](aclfvFeatureInfo *p){(void)aclfvDestroyFeatureInfo(p);}); // 删除1个特征 aclfvDel(featureInfo1); // 4.3 增加特征到其它底库,其中一级底库为1,二级底库为1 id0 = 1; id1 = 1; offset = 0; // 增加特征到底库步骤,参考4.1中的代码 // .... // 5 底库检索(以1:N检索为例),主要包括特征检索预处理,特征1:N检索,特征检索结果处理三个部分 // 5.1 特征检索预处理,对于1:N来说, queryCnt必须为1 uint32_t queryCnt = 1; uint32_t topK = 5; uint32_t dataLen = queryCnt * topK * sizeof(uint32_t); uint32_t resultNumDataLen = queryCnt * sizeof(uint32_t); const uint32_t tableLen = 32 * 1024; uint32_t tableDataLen = queryCnt * tableLen; // 生成数据表,用户通过数据表进行检索比对,此处的自定义函数AdcTabInit用于初始化特征检索输入Adc表,由用户自行实现 uint8_t *tableDataTmp = (uint8_t *)AdcTabInit(1000, queryCnt * 1024); std::shared_ptr<void> tableDataTmpPtr(tableDataTmp,[](void *p){(void)aclrtFreeHost(p);}); // 为数据表分配内存,tableDataDev用于创建检索输入表信息 void *devPtr = nullptr; aclrtMalloc(&devPtr, tableDataLen, ACL_MEM_MALLOC_HUGE_FIRST); tableDataDev.reset(devPtr, [](void *p) {(void)aclrtFree(p);}); // 拷贝表数据到Device侧 uint8_t *devPtrTmp = reinterpret_cast<uint8_t *>(devPtr); for (uint32_t i = 0; i < queryCnt; ++i) { for (uint32_t j = 0; j < 32; ++j) { uint8_t *dst = devPtrTmp + i * 32 * 1024 + j * 1024; uint8_t *src = tableDataTmp + i * 1024; aclrtMemcpy(dst, 1024, src, 1024, ACL_MEMCPY_HOST_TO_DEVICE); } } // 为检索结果resultNumDev,id0Dev,id1Dev,resultOffsetDev,resultDistanceDev分配内存 aclrtMalloc(&devPtr, resultNumDataLen, ACL_MEM_MALLOC_HUGE_FIRST); resultNumDev.reset(devPtr, [](void *p) {(void)aclrtFree(p);}); aclrtMalloc(&devPtr, dataLen, ACL_MEM_MALLOC_HUGE_FIRST); id0Dev.reset(devPtr, [](void *p) {(void)aclrtFree(p);}); aclrtMalloc(&devPtr, dataLen, ACL_MEM_MALLOC_HUGE_FIRST); id1Dev.reset(devPtr, [](void *p) {(void)aclrtFree(p);}); aclrtMalloc(&devPtr, dataLen, ACL_MEM_MALLOC_HUGE_FIRST); resultOffsetDev.reset(devPtr, [](void *p) {(void)aclrtFree(p);}); aclrtMalloc(&devPtr, dataLen, ACL_MEM_MALLOC_HUGE_FIRST); resultDistanceDev.reset(devPtr, [](void *p) {(void)aclrtFree(p);}); // 创建检索输入表信息,结果用于创建检索任务输入信息 aclfvQueryTable *searchQueryTable = aclfvCreateQueryTable(queryCnt, tableLen, reinterpret_cast<uint8_t *> (tableDataDev.get()), tableDataLen); searchQueryTable.reset(searchQueryTable, [](aclfvQueryTable *p){(void)aclfvDestroyQueryTable(p);}); // 创建特征库范围参数,结果用于创建检索任务输入信息 aclfvRepoRange *searchRange = aclfvCreateRepoRange(0, 1023, 0, 1023); searchRange.reset(searchRange, [](aclfvRepoRange *p){(void)aclfvDestroyRepoRange(p);}); // 创建检索任务输入信息,结果用于特征1:N检索 aclfvSearchInput *searchInput = aclfvCreateSearchInput(searchQueryTable, searchRange, topK); searchInput.reset(searchInput, [](aclfvSearchInput *p){(void)aclfvDestroySearchInput(p);}); // 创建检索结果信息,结果用于特征1:N检索 aclfvSearchResult *searchResult = aclfvCreateSearchResult(queryCnt, reinterpret_cast<uint32_t *>(resultNumDev.get()), resultNumDataLen, reinterpret_cast<uint32_t *>(id0Dev.get()), reinterpret_cast<uint32_t *>(id1Dev.get()), reinterpret_cast<uint32_t *>(resultOffsetDev.get()), reinterpret_cast<float *>(resultDistanceDev.get()), dataLen); searchResult.reset(searchResult, [](aclfvSearchResult *p){(void)aclfvDestroySearchResult(p);}); // 5.2 特征1:N检索 aclfvSearch(SEARCH_1_N, searchInput.get(), searchResult.get()); // 5.3 特征检索结果处理 // 获取检索结果 uint32_t dataLen = queryCnt * topK * sizeof(uint32_t); uint32_t *id0 = (uint32_t *)id0Dev.get(); uint32_t *id1 = (uint32_t *)id1Dev.get(); uint32_t *resultOffset= (uint32_t *)resultOffsetDev.get(); float *resultDistance = (float *)resultDistanceDev.get(); // 如果运行模式为ACL_HOST,则需要通过aclrtMemcpy接口将Device的检索结果回传到Host侧;否则无需回传 if (aclrtGetRunMode(&runMode) == ACL_HOST) { // 从Device侧拷贝数据到Host侧 id0 = (uint32_t *)malloc(dataLen); id0Ptr.reset(id0); id1 = (uint32_t *)malloc(dataLen); id1Ptr.reset(id1); resultOffset = (uint32_t *)malloc(dataLen); resultOffsetPtr.reset(resultOffset); resultDistance = (float *)malloc(dataLen); resultDistancePtr.reset(resultDistance); aclrtMemcpy(id0, dataLen, id0Dev.get(), dataLen, ACL_MEMCPY_DEVICE_TO_HOST); aclrtMemcpy(id1, dataLen, id0Dev.get(), dataLen, ACL_MEMCPY_DEVICE_TO_HOST); aclrtMemcpy(resultOffset, dataLen, resultOffsetDev.get(), dataLen, ACL_MEMCPY_DEVICE_TO_HOST); aclrtMemcpy(resultDistance, dataLen, resultDistanceDev.get(), dataLen, ACL_MEMCPY_DEVICE_TO_HOST); } // 展示底库中的数据 for (uint32_t i = 0; i < queryCnt; i++) { for (uint32_t j = 0; j < topK; ++j) { uint32_t i0 = id0[i * topK + j]; uint32_t i1 = id1[i * topK + j]; uint32_t offset = resultOffset[i * topK + j]; float distance = resultDistance[i * topK + j]; } } // 6. 删除底库和数据 // 创建特征库范围并删除指定范围内的底库 uint32_t id0Min = 0; uint32_t id0Max = 1023; uint32_t id1Min = 0; uint32_t id1Max = 1023; aclfvRepoRange *repoRange = aclfvCreateRepoRange(id0Min, id0Max, id1Min, id1Max); aclfvRepoDel(SEARCH_1_N, repoRange) // 销毁aclfvInitPara类型的数据 aclfvDestroyInitPara(fvInitPara); // 7. 释放运行管理资源 // 8. AscendCL去初始化 // ...... |
父主题: 更多特性