ncnn icon indicating copy to clipboard operation
ncnn copied to clipboard

Android端Mali GPU推理结果异常

Open Cat-myq opened this issue 1 year ago • 5 comments

简述

  • Android端在使用gpu进行推理的过程中,发现Mail的gpu推理结果出错,而晓龙的Adreno gpu推理结果正常

detail | 详细描述 | 詳細な説明

  1. ncnn,使用官方提供的ncnn-20241226-android-vulkan,或者自行根据源码进行编译
  2. 模型 yolov11-obb模型,没经过魔改
  3. 模型参数(部分)
7767517
311 373
Input                    in0                      0 1 in0
Convolution              conv_0                   1 1 in0 1 0=16 1=3 11=3 12=1 13=2 14=1 2=1 3=2 4=1 5=1 6=432
Swish                    silu_93                  1 1 1 2
Convolution              conv_1                   1 1 2 3 0=32 1=3 11=3 12=1 13=2 14=1 2=1 3=2 4=1 5=1 6=4608
Swish                    silu_94                  1 1 3 4
Convolution              conv_2                   1 1 4 5 0=32 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=1024
Swish                    silu_95                  1 1 5 6
Slice                    split_0                  1 2 6 7 8 -23300=2,16,16 1=0
Split                    splitncnn_0              1 3 8 9 10 11
Convolution              conv_3                   1 1 11 12 0=8 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=1152
Swish                    silu_96                  1 1 12 13
Convolution              conv_4                   1 1 13 14 0=16 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=1152
Swish                    silu_97                  1 1 14 15
BinaryOp                 add_0                    2 1 10 15 16 0=0
Concat                   cat_0                    3 1 7 9 16 17 0=0
Convolution              conv_5                   1 1 17 18 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=3072
Swish                    silu_98                  1 1 18 19
Convolution              conv_6                   1 1 19 20 0=64 1=3 11=3 12=1 13=2 14=1 2=1 3=2 4=1 5=1 6=36864
Swish                    silu_99                  1 1 20 21
Convolution              conv_7                   1 1 21 22 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
Swish                    silu_100                 1 1 22 23
Slice                    split_1                  1 2 23 24 25 -23300=2,32,32 1=0
Split                    splitncnn_1              1 3 25 26 27 28
Convolution              conv_8                   1 1 28 29 0=16 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=4608
Swish                    silu_101                 1 1 29 30
Convolution              conv_9                   1 1 30 31 0=32 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=4608
Swish                    silu_102                 1 1 31 32
BinaryOp                 add_1                    2 1 27 32 33 0=0
Concat                   cat_1                    3 1 24 26 33 34 0=0
Convolution              conv_10                  1 1 34 35 0=128 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=12288
Swish                    silu_103                 1 1 35 36
Split                    splitncnn_2              1 2 36 37 38
Convolution              conv_11                  1 1 38 39 0=128 1=3 11=3 12=1 13=2 14=1 2=1 3=2 4=1 5=1 6=147456
...
Reshape                  reshape_182              1 1 132 140 0=20 1=20 2=128
ConvolutionDepthWise     convdw_199               1 1 140 141 0=128 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=1152 7=128
BinaryOp                 add_7                    2 1 139 141 142 0=0
Convolution              conv_35                  1 1 142 143 0=128 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=16384
BinaryOp                 add_8                    2 1 125 143 144 0=0
Split                    splitncnn_15             1 2 144 145 146
Reshape                  view_188                 1 1 272 273 0=400 1=1
Concat                   cat_17                   3 1 261 267 273 274 0=1
Sigmoid                  sigmoid_178              1 1 274 275
BinaryOp                 sub_15                   1 1 275 276 0=1 1=1 2=2.500000e-01
BinaryOp                 mul_16                   1 1 276 277 0=2 1=1 2=3.141593e+00
Split                    splitncnn_27             1 3 277 278 279 280
Convolution              conv_71                  1 1 192 281 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=36864
Swish                    silu_158                 1 1 281 282
Convolution              conv_72                  1 1 282 283 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=36864
Swish                    silu_159                 1 1 283 284
Convolution              conv_73                  1 1 284 285 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
ConvolutionDepthWise     convdw_200               1 1 195 286 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=576 7=64
Swish                    silu_160                 1 1 286 287
Convolution              conv_74                  1 1 287 288 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
Swish                    silu_161                 1 1 288 289
ConvolutionDepthWise     convdw_201               1 1 289 290 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=576 7=64
Swish                    silu_162                 1 1 290 291
Convolution              conv_75                  1 1 291 292 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
Swish                    silu_163                 1 1 292 293
Convolution              conv_76                  1 1 293 294 0=7 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=448
Concat                   cat_18                   2 1 285 294 295 0=0
Convolution              conv_77                  1 1 214 296 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=73728
Swish                    silu_164                 1 1 296 297
Convolution              conv_78                  1 1 297 298 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=36864
Swish                    silu_165                 1 1 298 299
Convolution              conv_79                  1 1 299 300 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
ConvolutionDepthWise     convdw_202               1 1 217 301 0=128 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=1152 7=128
Swish                    silu_166                 1 1 301 302
Convolution              conv_80                  1 1 302 303 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=8192
Swish                    silu_167                 1 1 303 304
ConvolutionDepthWise     convdw_203               1 1 304 305 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=576 7=64
Swish                    silu_168                 1 1 305 306
Convolution              conv_81                  1 1 306 307 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
Swish                    silu_169                 1 1 307 308
Convolution              conv_82                  1 1 308 309 0=7 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=448
Concat                   cat_19                   2 1 300 309 310 0=0
Convolution              conv_83                  1 1 252 311 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=147456
Swish                    silu_170                 1 1 311 312
Convolution              conv_84                  1 1 312 313 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=36864
Swish                    silu_171                 1 1 313 314
Convolution              conv_85                  1 1 314 315 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
ConvolutionDepthWise     convdw_204               1 1 254 316 0=256 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=2304 7=256
Swish                    silu_172                 1 1 316 317
Convolution              conv_86                  1 1 317 318 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=16384
Swish                    silu_173                 1 1 318 319
ConvolutionDepthWise     convdw_205               1 1 319 320 0=64 1=3 11=3 12=1 13=1 14=1 2=1 3=1 4=1 5=1 6=576 7=64
Swish                    silu_174                 1 1 320 321
Convolution              conv_87                  1 1 321 322 0=64 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=4096
Swish                    silu_175                 1 1 322 323
Convolution              conv_88                  1 1 323 324 0=7 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=1 6=448
Concat                   cat_20                   2 1 315 324 325 0=0
Reshape                  view_189                 1 1 295 326 0=6400 1=71
Reshape                  view_190                 1 1 310 327 0=1600 1=71
Reshape                  view_191                 1 1 325 328 0=400 1=71
Concat                   cat_21                   3 1 326 327 328 329 0=1
Slice                    split_10                 1 2 329 330 331 -23300=2,64,7 1=0
Reshape                  view_192                 1 1 330 332 0=8400 1=16 2=4
Permute                  transpose_198            1 1 332 333 0=2
Softmax                  softmax_181              1 1 333 334 0=0 1=1
Convolution              conv_89                  1 1 334 335 0=1 1=1 11=1 12=1 13=1 14=0 2=1 3=1 4=0 5=0 6=16
Reshape                  view_193                 1 1 335 336 0=8400 1=4
MemoryData               pnnx_fold_anchor_points.1 0 1 337 0=8400 1=2
Slice                    split_11                 1 2 336 338 339 -23300=2,2,-233 1=0
Split                    splitncnn_29             1 2 339 340 341
Split                    splitncnn_28             1 2 338 342 343
UnaryOp                  cos_17                   1 1 279 344 0=10
Split                    splitncnn_30             1 2 344 345 346
UnaryOp                  sin_18                   1 1 280 347 0=9
Split                    splitncnn_31             1 2 347 348 349
BinaryOp                 sub_19                   2 1 340 342 350 0=1
BinaryOp                 div_20                   1 1 350 351 0=3 1=1 2=2.000000e+00
Slice                    split_12                 1 2 351 352 353 -23300=2,1,-233 1=0
Split                    splitncnn_33             1 2 353 354 355
Split                    splitncnn_32             1 2 352 356 357
BinaryOp                 mul_21                   2 1 354 348 358 0=2
BinaryOp                 mul_22                   2 1 356 345 359 0=2
BinaryOp                 sub_23                   2 1 359 358 360 0=1
BinaryOp                 mul_24                   2 1 355 346 361 0=2
BinaryOp                 mul_25                   2 1 357 349 362 0=2
BinaryOp                 add_26                   2 1 362 361 363 0=0
Concat                   cat_22                   2 1 360 363 364 0=0
BinaryOp                 add_27                   2 1 364 337 365 0=0
BinaryOp                 add_28                   2 1 343 341 366 0=0
Concat                   cat_23                   2 1 365 366 367 0=0
Reshape                  reshape_183              1 1 255 368 0=8400 1=1
BinaryOp                 mul_29                   2 1 367 368 369 0=2
Sigmoid                  sigmoid_179              1 1 331 370
Concat                   cat_24                   2 1 369 370 371 0=0
Concat                   cat_25                   2 1 371 278 out0 0=0
  1. 代码
_hasGPU = ncnn::get_gpu_count() > 0;
_Net->opt.use_fp16_arithmetic = false;
_Net->opt.use_fp16_storage = false;
_Net->opt.use_fp16_packed = false;

_Net->opt.use_vulkan_compute = _hasGPU;   // 将其设置为false,推理结果均正常
_Net->opt.use_packing_layout=true;
// in_pad 的w, h 均为640
ncnn::Extractor ex = _Net->create_extractor();
ex.input("in0", in_pad);
ncnn::Mat out;
int extract_result = ex.extract("out0", out);
  1. 出错描述
  • 抽取out0进行后处理时发现box的x,y,w,h出现异常,x,y远大于640,wh也远大于实际的box尺寸,但是confidence和angle是正常的
  • 模型配置倒数第四行,单独抽取369发现x,y,w,h的计算结果依然异常,于是抽取367,368自行进行乘法运算得到结果是正常的(369box的位置,370置信度,278角度)
// 提取box
ncnn::Mat out_box;
int extract_result = ex.extract(367, out_box);

// 提取步长
ncnn::Mat out_stride;
ex.extract(368, out_stride);

// 提取置信度
ncnn::Mat out_conf;
ex.extract(370, out_conf);
  1. 出错设备
  • huawei mate 40,gpu: Mali-G78
  • vivo x100, x90系列 gpu: Mali-G715 Immortalis MC11
  1. 搭载骁龙Adreno的设备推理结果正常,禁用gpu推理, _Net->opt.use_vulkan_compute = false; 所有设备推理结果均正常

Cat-myq avatar Jan 21 '25 07:01 Cat-myq

hi, ncnn yolo11 examples are on board https://github.com/Tencent/ncnn/tree/master/examples https://github.com/nihui/ncnn-android-yolo11

model conversion guide (zh) https://zhuanlan.zhihu.com/p/1903414797195781701

nihui avatar May 07 '25 03:05 nihui

我也遇到了同样的问题,有的输出是对的,有的输出是错的。

liushuan avatar May 20 '25 05:05 liushuan

hi, ncnn yolo11 examples are on board https://github.com/Tencent/ncnn/tree/master/examples https://github.com/nihui/ncnn-android-yolo11

model conversion guide (zh) https://zhuanlan.zhihu.com/p/1903414797195781701

首先感谢大佬,用例子中的方式裁剪模型,修改推理代码后,在原先出问题的GPU上输出结果正常了。

分析例子发现,外部实现后处理相当于将box位置的计算拿出来放在CPU上进行。令我不解的是,为什么后处理放在GPU上会运行错误。

后面我又进行了以下测试:

  • 测试环境:同样放在出错的GPU上运行
  1. 代码1:
ncnn::Extractor ex = net->create_extractor();
ex.input("in0", in_pad);
ncnn::Mat out;
int extract_result = ex.extract("out0", out);

结果:第一次结果正常,后续结果全是异常的

  1. 代码2:
ncnn::Extractor ex = net->create_extractor();
ex.input("in0", in_pad);
// 这一块什么也不做,只单纯的extract
ncnn::Mat out_367;
ex.extract("367", out_367);
//////////////////////////
ncnn::Mat out;
int extract_result = ex.extract("out0", out);

结果:所有推理结果恢复正常(仅当我extract 367层时正常,extract其他层:369,370结果都异常)

感觉像是显存复用出了问题。

Cat-myq avatar Sep 05 '25 08:09 Cat-myq

抱歉,刚才误触了关闭,这里直接说明最新进展:

错误原因

经排查,出现错误的GPU是因为其不支持Vulkan的推送描述符VK_KHR_push_descriptor

vkdev->info.support_VK_KHR_push_descriptor();

错误情况

  • box位置错误
  • 概率、置信度、角度正常

错误定位

  • 最终定位到错误出现在layer_index = 204层,后续位置计算依赖该层结果,因此该层出错导致最终位置错误:
    layer_204->MemoryData               pnnx_188                 0 1 255 0=8400
    
  • 影响204层出错的层大概率是305层:
    layer_305->Concat                   cat_23                   2 1 365 366 367 0=0
    

实验过程

实验一

在305层之前计算204层,并将204层结果加入blob_mats_gpu,结果恢复正常:

int NetPrivate::do_forward_layer(int dst_index, const Layer* layer, std::vector<VkMat>& blob_mats_gpu, VkCompute& cmd, const Option& opt) const
{
 ...// 前面保持不变
            if (layer->name == "cat_23") {
                NCNN_LOGE("cat_23 之前");
                const Layer* layer_temp = layers[204];
                std::vector<VkMat> top_blobs_temp(layer->tops.size());
                std::vector<VkMat> bottom_blobs_temp(layer->bottoms.size());
                layer_temp->forward(bottom_blobs_temp, top_blobs_temp, cmd, opt);
                for (size_t i = 0; i < layer_temp->tops.size(); i++)
                {
                    int top_blob_index = layer_temp->tops[i];

                    blob_mats_gpu[top_blob_index] = top_blobs_temp[i];
                }
            }
            std::vector<VkMat> top_blobs(layer->tops.size());
            int ret = layer->forward(bottom_blobs, top_blobs, cmd, opt);
            
            if (ret != 0)
                return ret;
....// 后面保持不变
}

实验二

在305层计算之后执行submit_and_wait,结果恢复正常:

int NetPrivate::do_forward_layer(int dst_index, const Layer* layer, std::vector<VkMat>& blob_mats_gpu, VkCompute& cmd, const Option& opt) const
{
...// 前面保持不变
            std::vector<VkMat> top_blobs(layer->tops.size());
            int ret = layer->forward(bottom_blobs, top_blobs, cmd, opt);
            if (layer->name == "cat_23") {
                cmd.submit_and_wait();
                cmd.reset();
            }
            if (ret != 0)
                return ret;
....// 后面保持不变
}

实验三

MemoryData_vulkan forward clone操作之后加入cmd.record_download,结果恢复正常:

int MemoryData_vulkan::forward(const std::vector<VkMat>& /*bottom_blobs*/, std::vector<VkMat>& top_blobs, VkCompute& cmd, const Option& opt) const
{
    VkMat& top_blob = top_blobs[0];
    cmd.record_clone(data_gpu, top_blob, opt);
    if (name == "pnnx_188") {  
        Mat temp1;
        cmd.record_download("", data_gpu, temp1, opt);
    }
    if (top_blob.empty())
        return -100;

    return 0;
}

同时重载函数record_download

void VkCompute::record_download(std:string flag, const VkMat& src, Mat& dst, const Option& opt)
{
    // resolve dst_elempack
    int dims = src.dims;
    int elemcount = 0;
    if (dims == 1) elemcount = src.elempack * src.w;
    if (dims == 2) elemcount = src.elempack * src.h;
    if (dims == 3 || dims == 4) elemcount = src.elempack * src.c;

    int dst_elempack = 1;
    if (opt.use_packing_layout)
        dst_elempack = elemcount % 4 == 0 ? 4 : 1;
    else
        dst_elempack = 1;

    // gpu cast to fp32 on the fly (integrated gpu)
    Option opt_staging = opt;
    if (!opt_staging.blob_vkallocator->mappable)
    {
        opt_staging.blob_vkallocator = opt.staging_vkallocator;
    }
    int cast_type_to = 0;
    if (vkdev->info.type() != 0)
    {
        cast_type_to = 1;
    }

    if (src.elemsize == src.elempack * 1u)
    {
        cast_type_to = 4;
    }

    VkMat dst_staging;
    vkdev->convert_packing(src, dst_staging, dst_elempack, cast_type_to, *this, opt_staging);
    // 后续代码删除
}

实验四

修改record_clone代码(固定增加内存屏障),结果恢复正常:

原代码:

void VkCompute::record_clone(const VkMat& src, VkMat& dst, const Option& opt)
{
    //     NCNN_LOGE("record_clone buffer to buffer");

    // create dst
    dst.create_like(src, opt.blob_vkallocator);
    if (dst.empty())
        return;

    if (src.data->access_flags & VK_ACCESS_TRANSFER_WRITE_BIT || src.data->stage_flags != VK_PIPELINE_STAGE_TRANSFER_BIT)
    {
        // barrier device any @ compute to transfer-read @ compute
        VkBufferMemoryBarrier* barriers = new VkBufferMemoryBarrier[1];
        barriers[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
        barriers[0].pNext = 0;
        barriers[0].srcAccessMask = src.data->access_flags;
        barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        barriers[0].buffer = src.buffer();
        barriers[0].offset = src.buffer_offset();
        barriers[0].size = src.buffer_capacity();

        VkPipelineStageFlags src_stage = src.data->stage_flags;
        VkPipelineStageFlags dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;

        if (vkdev->info.support_VK_KHR_push_descriptor())
        {
            vkCmdPipelineBarrier(d->compute_command_buffer, src_stage, dst_stage, 0, 0, 0, 1, barriers, 0, 0);
            delete[] barriers;
        }
        else
        {
            VkComputePrivate::record r;
            r.type = VkComputePrivate::record::TYPE_buffer_barrers;
            r.command_buffer = d->compute_command_buffer;
            r.buffer_barrers.src_stage = src_stage;
            r.buffer_barrers.dst_stage = dst_stage;
            r.buffer_barrers.barrier_count = 1;
            r.buffer_barrers.barriers = barriers;
            d->delayed_records.push_back(r);
        }

        // mark device transfer-read @ transfer
        src.data->access_flags = VK_ACCESS_TRANSFER_READ_BIT;
        src.data->stage_flags = VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
... // 后面保持不变
}

修改后代码:

void VkCompute::record_clone(const VkMat& src, VkMat& dst, const Option& opt)
{
    // create dst
    dst.create_like(src, opt.blob_vkallocator);
    if (dst.empty())
        return;

    if (src.data->access_flags & VK_ACCESS_TRANSFER_WRITE_BIT || src.data->stage_flags != VK_PIPELINE_STAGE_TRANSFER_BIT)
    {
        // 若当前是COMPUTE阶段后的clone,自动补充COMPUTE同步
        VkAccessFlags real_src_access = src.data->access_flags;
        VkPipelineStageFlags real_src_stage = src.data->stage_flags;
        if (!vkdev->info.support_VK_KHR_push_descriptor())
        {
            // 强制加入“计算写入”的同步(覆盖干扰)
            real_src_access |= VK_ACCESS_SHADER_WRITE_BIT;
            real_src_stage |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
        }

        VkBufferMemoryBarrier* barriers = new VkBufferMemoryBarrier[1];
        barriers[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
        barriers[0].srcAccessMask = real_src_access; // 用扩展后的access
        barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        barriers[0].buffer = src.buffer();
        barriers[0].offset = src.buffer_offset();
        barriers[0].size = src.buffer_capacity();

        VkPipelineStageFlags src_stage = real_src_stage; // 用扩展后的stage
        VkPipelineStageFlags dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;

        if (vkdev->info.support_VK_KHR_push_descriptor())
        {
            NCNN_LOGE("gpu support_VK_KHR_push_descriptor!");
            vkCmdPipelineBarrier(d->compute_command_buffer, src_stage, dst_stage, 0, 0, 0, 1, barriers, 0, 0);
            delete[] barriers;
        }
        else
        {
            NCNN_LOGE("gpu does not support_VK_KHR_push_descriptor!");
            VkComputePrivate::record r;
            r.type = VkComputePrivate::record::TYPE_buffer_barrers;
            r.command_buffer = d->compute_command_buffer;
            r.buffer_barrers.src_stage = src_stage;
            r.buffer_barrers.dst_stage = dst_stage;
            r.buffer_barrers.barrier_count = 1;
            r.buffer_barrers.barriers = barriers;
            d->delayed_records.push_back(r);
        }
        // 注释掉这两行,
        // src.data->access_flags = VK_ACCESS_TRANSFER_READ_BIT;
        // src.data->stage_flags = VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
... // 后面保持不变
}

实验结论

  1. 首次推理正常,后续推理出错。
  2. 将204层forward放在305层forward之前,结果恢复正常。
  3. 305层forward后执行cmd.submit_and_wait(),结果恢复正常。
  4. 204层forward后执行cmd.record_download(),结果恢复正常。
  5. record_clone中固定增加内存屏障,结果恢复正常。

总结(AI)

核心根因总结

不支持VK_KHR_push_descriptor的GPU上,ncnn需将所有GPU指令(如record_pipelinerecord_clone)存入delayed_records延迟队列,等待submit_and_wait()统一提交执行。该机制存在**“跨推理的资源状态累积”** 与**“管线阶段同步缺失”** 两大缺陷:

  1. 状态累积:首次推理后,data_gpu等资源的“访问标志(access_flags)”“管线阶段(stage_flags)”等状态未被完全重置,残留至后续推理;
  2. 同步缺失:延迟队列仅保证“CPU提交顺序”,不保证“GPU执行顺序与资源可见性”,且record_clone等操作的内置屏障因状态残留而失效,无法约束305层(计算阶段)与204层(传输阶段)的执行关系。

最终导致:204层clone操作因“资源状态误判”“管线阶段未同步”读取到异常数据,表现为后续推理结果错误。

关键现象的技术解析

1. 首次推理正常,后续异常

  • 首次正常的本质
    初始化时,data_gpu通过upload_model上传至GPU,ncnn会自动插入显式内存屏障(确保CPU写入的数据被GPU可见),此时data_gpu的状态为VK_ACCESS_HOST_WRITE_BIT(主机写入)+VK_PIPELINE_STAGE_HOST_BIT(主机阶段),状态干净;
    延迟队列是“全新创建+一次性执行”,submit_and_wait()按顺序跑完所有指令后,清空队列并重置命令缓冲,无状态残留,clone操作可正常读取data_gpu

  • 后续异常的根源
    首次推理后,record_clone会永久修改data_gpu的状态(改为VK_ACCESS_TRANSFER_READ_BIT+VK_PIPELINE_STAGE_TRANSFER_BIT),导致后续推理中:

    • clone的内置屏障因“状态条件不满足”而失效(无法触发跨阶段同步);
    • 305层的计算指令(COMPUTE阶段)与204层的clone指令(TRANSFER阶段)无同步约束,GPU可能重排序或状态误判,导致clone读取data_gpu时的“管线阶段权限”“内存可见性”异常。

2. 204层放305层前执行,结果正常

  • 逻辑链:延迟队列顺序变为[204层clone → 305层concat],利用了GPU的**“短任务优先调度策略”**:
    • 204层clone是“传输操作”(计算量极小、执行时间短),会被GPU优先调度执行,在305层concat(计算操作,耗时久)开始前就完成数据拷贝,获取到data_gpu的“干净状态”;
    • 后续305层的concat虽会干扰GPU的全局状态,但clone的结果(top_blobs[0])已生成且不再被修改,因此不受影响。
  • 关键澄清:305层并未“污染204层的结果”,而是clone提前执行规避了后续的状态干扰。

3. 305层后执行cmd.submit_and_wait(),结果正常

  • submit_and_wait()的完整作用(不止“等待执行”):
    1. 强制执行延迟队列:立即将305层的所有计算指令提交至GPU并等待执行完成,消除“未提交指令的状态残留”;
    2. 显式同步内存状态:通过vkWaitForFences确保GPU的计算结果已刷入内存,管线阶段重置为“空闲”;
    3. 清空队列与重置缓冲:执行后delayed_records.clear(),并重置命令缓冲的隐性状态,相当于“人为初始化”,后续204层clone可触发正常的同步逻辑。
  • 本质:通过主动提交消除“状态累积”,修复同步缺失。

上述总结来源于AI

实验虽然恢复正常,但是不够通用,性能方面也有折扣

这属于vulkan的bug吗?这种现象有方式解决吗?

Cat-myq avatar Sep 19 '25 08:09 Cat-myq

@nihui 大佬,我后续又深入vulkan研究了一下,麻烦你看看这种现象

只要存在为临时变量top_blob_unpacked申请内存,并且不支持VK_KHR_push_descriptor的GPU感觉都会必然出现这种现象。

一、RenderDoc捕获vulkan指令

通过RenderDoc捕获Vulkan指令,发现204层(clone运算)与305层(concat运算)存在显存地址重叠,最终导致依赖204层结果的306层读取异常。

1. 显存地址重叠验证

核心冲突对象为buffer5271,两层对该buffer的写入地址范围完全重叠,具体如下:

  • 204层(clone传输单元):写入地址 3069888 ~ 3069888 + 33600
Image
  • 305层(CS计算单元):写入地址 3069888 ~ 3069888 + 134400
Image
  • 整体地址重叠:两层写入起始地址均为3069888,204层地址范围完全包含于305层地址范围内。

2. 指令调度异常与数据覆盖

Image
  • 关键指令关联:204层对应指令1719,305层对应指令1718,306层(依赖层)对应指令1725
  • 异常根源:17181719之间无显式同步屏障,GPU为优化性能触发「指令重排」或「并行运算」。
  • 数据覆盖过程:
    1. 1719(204层,传输操作)执行更快,先向buffer5271写入数据;
    2. 1718(305层,CS计算操作)后执行,覆盖buffer5271中已写入的1719数据;
    3. 1725(306层)依赖1719的原始数据,最终读取到被覆盖的305层数据,导致结果异常。

二、源码定位:ncnn Concat_vulkan层内存分配分析

通过分析ncnn源码中Concat_vulkan::forward函数,定位305层(concat运算)的内存分配与释放逻辑,明确其与204层共享内存的根源。

1. 核心源码片段(305层concat运算)

int Concat_vulkan::forward(const std::vector<VkMat>& bottom_blobs, std::vector<VkMat>& top_blobs, VkCompute& cmd, const Option& opt) const
{
    // ...(省略无关逻辑)
    if (dims == 2 && positive_axis == 0)
    {
        // ...(省略参数计算逻辑)
        int out_elempack = top_h % 4 == 0 ? 4 : 1;
        size_t out_elemsize = elemsize / elempack * out_elempack;

        VkMat& top_blob = top_blobs[0];
        // 1. 初始化top_blob,占用地址:0xb40000744ea042a0[+2935488] ========
        top_blob.create(w, top_h / out_elempack, out_elemsize, out_elempack, opt.blob_vkallocator);
        if (top_blob.empty())
            return -100;
        
        VkMat top_blob_unpacked = top_blob;
        // 初始时top_blob_unpacked与top_blob共享地址:0xb40000744ea042a0[+2935488] ========
        if (elempack < out_elempack)
        {
            // 2. 重新分配top_blob_unpacked,占用地址:0xb40000744ea042a0[+3069888] ========
            top_blob_unpacked.create(w, top_h / elempack, elemsize, elempack, opt.workspace_vkallocator);
            if (top_blob_unpacked.empty())
                return -100;
        }
        // ...(后续计算逻辑)
    }
    // ...(函数返回)
}

2. 305层与204层内存占用时序

通过跟踪两层forward函数前后的buffer状态,明确内存复用过程:

(1)305层(concat)内存时序

  1. forward前:空闲buffer为 0xb40000744ea042a0,地址范围 2935488 ~ 15603712(大小12668224);
  2. forward中
    • top_blob 占用 0xb40000744ea042a0[+2935488]
    • top_blob_unpacked 重新分配后,占用 0xb40000744ea042a0[+3069888]
  3. forward后top_blob_unpacked 占用的 0xb40000744ea042a0[+3069888] 被释放,回归空闲状态。

(2)204层(clone)内存时序

  1. forward前:空闲buffer包含 0xb40000744ea042a0[+3069888](即305层释放的地址);
  2. forward后:204层复用该空闲地址,占用 0xb40000744ea042a0[+3069888],最终与305层形成地址重叠。

三、问题总结

1. 核心结论

306层读取异常的根本原因是305层与204层共享同一片显存地址+无显式同步屏障:GPU指令重排导致204层数据被305层覆盖,而306层依赖204层原始数据,最终引发错误。

2. 冲突根因拆解

1. 内存共享的原因(CPU视角)

305层计算时会临时占用内存地址A,运算结束后地址A被释放;204层计算时检测到地址A空闲,便复用该地址存储自身结果,最终导致305层与204层共享同一片内存。

2. 并行冲突的原因(GPU视角)

GPU调度器以性能优化为目标,当两层无显式同步屏障时,会允许二者并行执行或调整指令顺序,导致两层同时对地址A进行“写操作”,最终数据覆盖引发错误。

3. 设备差异补充

设备类型 关键特性 表现差异
正常设备 支持VK_KHR_push_descriptor GPU与CPU内存管理同步,即使存在地址重叠,也可避免异步时序错配;
异常设备 不支持上述特性 CPU内存释放时序与GPU异步执行时序不匹配,叠加内存复用,直接触发数据覆盖;

Cat-myq avatar Oct 15 '25 09:10 Cat-myq