11.RK3588本地大模型内存评估优化

tensor - 张量

1.板端C代码里评估内存

//示例代码
rknn_context ctx = 0;
// Load RKNN Model
int ret = rknn_init(&ctx, model_path, 0, NULL, NULL);
if (ret < 0) {
    printf("rknn_init fail! ret=%d\n", ret);
    return -1;
}

// Get weight and internal mem size
rknn_mem_size mem_size;
ret = rknn_query(ctx, RKNN_QUERY_MEM_SIZE, &mem_size,sizeof(mem_size));
if (ret != RKNN_SUCC) {
    printf("rknn_query fail! ret=%d\n", ret);
    return -1;
}
printf("total weight size: %d, total internal size: %d\n",mem_size.total_weight_size, mem_size.total_internal_size);

2.ubuntu 里python 连板推理方式内存评估

使用方法

# 在init_runtime时把eval_mem 设置为True
rknn.init_runtime(target=args.target, eval_mem=True)

# 然后调用
rknn.eval_memory()

完整例程:rknn_model_zoo-2.0.0/examples/mobilenet/python/mobilenet.py

import argparse
import os
import sys
import urllib
import urllib.request
import time
import traceback
import numpy as np
import cv2
from rknn.api import RKNN
from scipy.special import softmax

DATASET_PATH = '../../../datasets/imagenet/ILSVRC2012_img_val_samples/dataset_20.txt'
MODEL_DIR = '../model/'
MODEL_PATH = MODEL_DIR + 'mobilenetv2-12.onnx'
OUT_RKNN_PATH = MODEL_DIR + 'mobilenet_v2.rknn'
CLASS_LABEL_PATH = MODEL_DIR + 'synset.txt'

RKNPU1_TARGET = ['rk1808', 'rv1109', 'rv1126']


def readable_speed(speed):
    speed_bytes = float(speed)
    speed_kbytes = speed_bytes / 1024
    if speed_kbytes > 1024:
        speed_mbytes = speed_kbytes / 1024
        if speed_mbytes > 1024:
            speed_gbytes = speed_mbytes / 1024
            return "{:.2f} GB/s".format(speed_gbytes)
        else:
            return "{:.2f} MB/s".format(speed_mbytes)
    else:
        return "{:.2f} KB/s".format(speed_kbytes)


def show_progress(blocknum, blocksize, totalsize):
    speed = (blocknum * blocksize) / (time.time() - start_time)
    speed_str = " Speed: {}".format(readable_speed(speed))
    recv_size = blocknum * blocksize

    f = sys.stdout
    progress = (recv_size / totalsize)
    progress_str = "{:.2f}%".format(progress * 100)
    n = round(progress * 50)
    s = ('#' * n).ljust(50, '-')
    f.write(progress_str.ljust(8, ' ') + '[' + s + ']' + speed_str)
    f.flush()
    f.write('\r\n')


def check_and_download_origin_model():
    global start_time
    if not os.path.exists(MODEL_PATH):
        print('--> Download {}'.format(MODEL_PATH))
        url = 'https://ftrg.zbox.filez.com/v2/delivery/data/95f00b0fc900458ba134f8b180b3f7a1/examples/MobileNet/mobilenetv2-12.onnx'
        download_file = MODEL_PATH
        try:
            start_time = time.time()
            urllib.request.urlretrieve(url, download_file, show_progress)
        except:
            print('Download {} failed.'.format(download_file))
            print(traceback.format_exc())
            exit(-1)
        print('done')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='MobileNet Python Demo', add_help=True)
    parser.add_argument('--target', type=str,
                        default='rk3566', help='RKNPU target platform')
    parser.add_argument('--npu_device_test', action='store_true',
                        default=False, help='Connected npu device run')
    parser.add_argument('--accuracy_analysis', action='store_true',
                        default=False, help='Accuracy analysis')
    parser.add_argument('--eval_perf', action='store_true',
                        default=False, help='Time consuming evaluation')
    parser.add_argument('--eval_memory', action='store_true',
                        default=False, help='Memory evaluation')
    parser.add_argument('--model', type=str,
                        default=MODEL_PATH, help='onnx model path')
    parser.add_argument('--output_path', type=str,
                        default=OUT_RKNN_PATH, help='output rknn model path')
    parser.add_argument('--dtype', type=str, default='i8',
                        help='dtype of model, i8/fp32 for RKNPU2, u8/fp32 for RKNPU1')
    args = parser.parse_args()

    # Download model if not exist (from https://ftrg.zbox.filez.com/v2/delivery/data/95f00b0fc900458ba134f8b180b3f7a1/examples/MobileNet/mobilenetv2-12.onnx)
    check_and_download_origin_model()

    # Create RKNN object
    rknn = RKNN(verbose=False)

    # Pre-process config
    print('--> Config model')
    rknn.config(mean_values=[[255*0.485, 255*0.456, 255*0.406]], std_values=[[
                255*0.229, 255*0.224, 255*0.225]], target_platform=args.target)
    print('done')

    # Load model
    print('--> Loading model')
    if args.target in RKNPU1_TARGET:
        ret = rknn.load_onnx(model=args.model, inputs=['input'], input_size_list=[[3,224,224]])
    else:
        ret = rknn.load_onnx(model=args.model, inputs=['input'], input_size_list=[[1,3,224,224]])

    if ret != 0:
        print('Load model failed!')
        exit(ret)
    print('done')

    # Build model
    print('--> Building model')
    do_quant = True if (args.dtype == 'i8' or args.dtype == 'u8') else False
    ret = rknn.build(do_quantization=do_quant, dataset=DATASET_PATH)
    if ret != 0:
        print('Build model failed!')
        exit(ret)
    print('done')

    # Export rknn model
    print('--> Export rknn model')
    ret = rknn.export_rknn(args.output_path)
    if ret != 0:
        print('Export rknn model failed!')
        exit(ret)
    print('done')

    # Set inputs
    img = cv2.imread('../model/bell.jpg')
    img = cv2.resize(img, (224, 224))
    img = np.expand_dims(img, 0)

    # Init runtime environment
    print('--> Init runtime environment')
    if args.npu_device_test or args.target in RKNPU1_TARGET:
        # For RKNPU1, the simulator has beed disabled since version 1.7.5
        print('-------------------- init_runtime  here ---------------------')
        ret = rknn.init_runtime(target=args.target, perf_debug=True, eval_mem=True)
    elif args.eval_perf or args.eval_memory:
        ret = rknn.init_runtime(
            target=args.target, perf_debug=True, eval_mem=True)
    else:
        if args.target in RKNPU1_TARGET:
            print('The target {} does not support simulator.'.format(args.target))
            print('Please set `--npu_device_test` to init runtime with real target.')
            exit(-1)
        ret = rknn.init_runtime()
    if ret != 0:
        print('Init runtime environment failed!')
        exit(ret)
    print('done')

    # Eval Perf
    if args.eval_perf:
        print('--> Eval Perf')
        rknn.eval_perf()
        print('done')

    # Eval Memory
    if args.eval_memory:
        print('--> Eval Memory')
        rknn.eval_memory()
        print('done')

    # Inference
    print('--> Running model')
    outputs = rknn.inference(inputs=[img])

    # Post Process
    print('--> PostProcess')
    with open(CLASS_LABEL_PATH, 'r') as f:
        labels = [l.rstrip() for l in f]

    scores = softmax(outputs[0])
    # print the top-5 inferences class
    scores = np.squeeze(scores)
    a = np.argsort(scores)[::-1]
    print('-----TOP 5-----')
    for i in a[0:5]:
        print('[%d] score=%.2f class="%s"' % (i, scores[i], labels[i]))
    print('done')

    # Accuracy analysis
    if args.accuracy_analysis:
        print('--> Accuracy analysis')
        if args.npu_device_test:
            ret = rknn.accuracy_analysis(
                inputs=['../model/bell.jpg'], target=args.target)
        else:
            ret = rknn.accuracy_analysis(inputs=['../model/bell.jpg'])
        if ret != 0:
            print('Accuracy analysis failed!')
            exit(ret)
        print('done')

    # Release
    rknn.release()

测试

 python mobilenet.py --target rk3588 --eval_memory --npu_device_test

输出

3.内存优化方法

3.1 零拷贝

在推理 RKNN 模型时,原始数据要经过输入处理、NPU推理、输出处理三大流程。

根据不同模型输入格式和量化方式,接口内部会存在通用 API 和零拷贝 API 两种处理流程

两组 API 的主要区别在于,通用接口每次更新帧数据,需要将外部模块分配的数据拷贝到 NPU 的输入内存,而零拷贝流程的接口会直接使用预先分配的内存(包括 NPU 运行时创建的或外部其他框架创建的,比如 DRM 框架),减少了内存拷贝的花销,性能更优,带宽更少。

RK3588 是UMA 统一内存架构,CPU/NPU 共用一片 LPDDR 物理内存;

零拷贝(Zero-Copy)依靠 Linux dma-buf 共享缓冲区框架,硬件之间只传递内存 fd(文件描述符),不搬运图像 / 张量原始数据,彻底消除 CPU 内存拷贝,降低带宽占用、减少延迟、节省 DDR 内存。

3.2 内存复用

Internal 内存复用

RKNN API 提 供 了 外 部 管 理 NPU 内 存 的 机 制 , 通过RKNN_FLAG_MEM_ALLOC_OUTSIDE 参数,用户可以指定模型中间的 feature 内存由外部分配。该功能的典型应用场景如下:

  • 部署时,所有 NPU 内存均是用户自行分配,便于对整个系统内存进行统筹安排。
  • 用于多个模型串行运行场景,中间 feature 内存在不同上下文复用,特别是针对RV1103/RV1106 这种内存极为紧张的情况。

例如,下图中有两个模型,模型 1 的 Internal Tensor 占用大于模型 2,如果模型 1 和模型 2 顺序地运行,可以只开辟 0x00000000~0x000c4000 地址的一块内存给模型 1 和 2 共用,模型 1 推理结束后,这块内存可以被模型 2 用来读写 Internal Tensor 数据,从而节省内存。

图 9-1 两个模型 Internal Tensor 共享同一块内存地址空间的示例图

内存复用 = 同一块物理内存缓冲区,在不同阶段重复利用,用完立刻释放 / 覆盖,不重复新开内存

posted @ 2026-08-11 11:28  wssheng  阅读(2)  评论(0)    收藏  举报