RK1828 + RK3588 平台 LoRA 功能调研:用法、使用条件、限制与热加载能力 本文主要调研了以下资料:
LoRA 部署示例、RKNN Runtime C API 头文件、RKNN 板端 Python LoRA 测试脚本、RKNN3 SDK V1.1.0 官方文档
0. 结论速览
问题
结论
Qwen3_VL_LoRA 是否实现了 LoRA 微调的 Qwen3-VL 端侧部署?
是 。该示例在 PC 端将 LoRA 权重与基座模型一起转换为 RKNN(.rknn + .lora_weight),板端用 Base / LoRA 双 session 对同一输入做对比推理。
RK1828 是否支持多个 LoRA 同时驻留板端?
支持 。单个 context 最多 RKNN3_MAX_LORA_NUM = 32 个适配器;单个 .lora_weight 文件可打包多个适配器,context 级加载、多 session 共享。
是否支持热加载(运行时加载/卸载权重)?
支持 。rknn3_lora_load / rknn3_lora_unload 均为运行时接口;还支持 rknn3_lora_init_from_data 从内存数据初始化(可配合网络下发权重实现”热更新”)。
是否支持热切换(运行时启用/禁用/调 scale)?
支持 。session 级 rknn3_session_enable_lora / disable_lora 在每次 session_run 时重复生效;rkllm3-server 提供 /lora-adapters 接口和每请求 lora 字段动态调整 scale。
热切换的代价?
session 级 enable/disable 会自动清空该 session 的全部 KVCache ,长上下文场景切换适配器等于丢失对话记忆。
能否在板端直接加载 HuggingFace 的 adapter_model.safetensors?
不能 。LoRA 权重必须先在 PC 端经 RKNN3-Toolkit load_lora 与基座模型一起编译,生成配套的 .lora_weight 文件;板端只能加载该产物(文件或内存两种方式)。新增/更换适配器内容需回 PC 端重新转换。
rkllm3-server 形态下能否运行时挂载新权重文件?
不能 。--lora-weight 仅在启动时加载;运行时只能通过 /lora-adapters API 或每请求 lora 字段调整各 slot 的 scale(scale=0 即等效基座模型)。
1. Qwen3_VL_LoRA 示例解读 1.1 该目录做了什么 rknn3-model-zoo/examples/Qwen3_VL_LoRA 是官方提供的”基座 + LoRA”端侧部署示例,README 开篇即说明其定位:
1 2 3 4 5 6 7 8 9 10 # Qwen3-VL LoRA模型部署说明 (rknn3-model-zoo/examples/Qwen3_VL_ LoRA/README.md 第 1 行)### 3.2 LoRA 模型下载 本demo支持加载LoRA权重。以下为社区提供的 LoRA 模型示例: | 模型 | 下载链接 | |------|----------| | qwen3-vl-4b-ui-confidence-lora | [HuggingFace ](https://huggingface.co/bobbyzhong/qwen3-vl-4b-ui-confidence-lora ) |
它覆盖了 LoRA 使用的完整链路:
PC 端导出 (python/llm/):export_llm.py 只负责导出基座 ONNX(不含 LoRA,该文件中没有任何 LoRA 相关代码);export_rknn.py 在 ONNX 转 RKNN 阶段通过 --lora_path 注入 LoRA 权重。
板端 C++ 推理 (cpp/):初始化时同时创建 Base / LoRA 两个 LLM session,对同一张图 + 同一 prompt 先后跑两遍,输出两段结果与性能对比。
README 对双 session 架构的描述:
1 2 3 4 5 6 7 8 9 10 11 12 13 ### 5.1 Base 与 LoRA 双路推理说明 本示例在初始化时同时创建两个 LLM session: | Session | 说明 | |---------------|------| | **Base** | 仅加载基座模型权重,用于标准推理。 | | **LoRA** | 在基座基础上可加载 LoRA 权重(需传入 LoRA 路径且 SDK 支持),用于带 LoRA 的推理。 |**推理流程** :对同一张图片与同一 prompt,先跑一次 Vision 得到vision embedding, 再依次执行 **Base 模型推理** 和 **LoRA 模型推理** ,分别输出两段生成结果与性能 (Prefill/Generate、Vision 耗时等)。 (rknn3-model-zoo/examples/Qwen3_VL_ LoRA/README.md 第 106~115 行)
1.2 与 Qwen3_VL 的关系 Qwen3_VL_LoRA 是 Qwen3_VL 的 LoRA 扩展版:模型裁剪策略、Vision 导出、C++ 框架基本同构;两目录各自带一份 modeling_qwen3_vl.py(transformers 装饰器修复的适用范围同时覆盖两者):
1 2 3 4 5 将以下文件中所有的 `@check_model_inputs` 改为 `@check_model_inputs()` :- `python/modeling_qwen3_vl.py` (本示例)- `../Qwen3_VL_LoRA/python/modeling_qwen3_vl.py` - `../paddleocr_vl/python/modeling_paddleocr_vl.py` (rknn3-model-zoo/examples/Qwen3_VL/README.md 第 231~235 行)
2. 官方对 LoRA 功能的定位 《RKNN3 SDK 开发指南》4.2.7 节给出了 LoRA 的官方定义与两阶段使用模型,这是理解”热加载”边界的关键:
1 2 3 4 5 6 7 8 9 ## 4.2.7 LoRA ## 适用场景 LoRA(低秩适配)允许不修改基础模型权重动态加载/切换适配器,适用于多任务微调部署 --一份基础模型 + 多个 LoRA 适配器,按任务切换,节省存储与内存。LoRA 的使用分两个 阶段:转换阶段通过 RKNN3-Toolkit 将LoRA 权重与基础模型一起转换为 RKNN 模型;板端 部署阶段通过 Runtime 接口加载并按需启用适配器。 (docs_md_ v1.1.0/02_RKNN3_ SDK_开发指南_ V1.1/02_RKNN3_ SDK_开发指南_ V1.1.0.pdf 第 4077~4081 行)
注意两点:
官方明确的设计目标就是”一份基础模型 + 多个 LoRA 适配器,按任务切换 “——这正是”微调了几个功能不同的 LoRA 版本,按需加载不同低秩矩阵”的场景;
LoRA 被分为转换阶段 (Toolkit)与板端部署阶段 (Runtime)两段,板端接口只能”加载并按需启用”,不能凭空创建适配器。
《开发指南》修订记录显示 LoRA 支持自 V1.0.4 引入:
1 2 3 <tr > <td > V1.0.4</td > <td > HPC / NN</td > <td > 2026-05-12</td > <td > 1. 增加RK3572支持 2. 增加LLM推理暂停与恢复3. 增加KV Cache导入导出 4. 支持LoRA 5. 支持模型加密部署</td > <td > 熊伟</td > </tr > (docs_md_ v1.1.0/02_RKNN3_ SDK_开发指南_ V1.1/02_RKNN3_ SDK_开发指南_ V1.1.0.pdf 第 54 行)
3. 模型转换 3.1 LoRA 权重注入的时机与方式 官方 Toolkit Python API 文档 9.1 节规定 load_lora 的调用时机与参数:
1 2 3 4 5 6 7 8 9 10 11 12 ## 9.1.1 LoRA权重加载 <table > <tr > <td > API</td > <td > load_lora</td > </tr > <tr > <td > 描述</td > <td > 加载LoRA权重。在加载模型(load_ onnx/ load_llm)之后、在构建模型(build)之前加载LoRA权重</td > </tr > <tr > <td > 参数</td > <td > lora_ paths: LoRA权重文件(.safetensors后缀)路径列表。</td > </tr > <tr > <td > </td > <td > lora_config_ paths: LoRA配置文件(.json后缀)路径列表。</td > </tr > <tr > <td > </td > <td > lora_patterns: LoRA权重的模板名称列表,用于启动/关闭LoRA。</td > </tr > <tr > <td > </td > <td > lora_ quantized_dtype: LoRA权重的数据类型,目前支持的数据类型有int4、int6、int8、float16,默认为float16。</td > </tr > <tr > <td > </td > <td > lora_ quantized_method: LoRA权重的量化方法,目前支持layer、channel、group{SIZE},默认为None。</td > </tr > <tr > <td > </td > <td > lora_ distribute_strategy: LoRA权重的多核拆分方式,目前支持 'best_ perf', 'less_mem'. 默认为'best_ perf'</td > </tr > </table > (docs_md_ v1.1.0/03_RKNN3_ Toolkit_Python_ API_参考_ V1.1/03_RKNN3_ Toolkit_Python_ API_参考_ V1.1.0.pdf 第 600 行)
官方示例:
1 2 3 4 5 rknn.load_lora(lora_paths=[("./adapter_model.safetensors" ]], lora_config_paths= [("./adapter_config.json" ]], lora_patterns=[["lora0_pattern0" ]], lora_distribute_strategy='best_perf' ) (docs_md_v1.1 .0 /03_RKNN3_Toolkit_Python_API_参考_V1.1 /03_RKNN3_Toolkit_Python_API_参考_V1.1 .0 .pdf 第 604 ~608 行)
《开发指南》4.2.7 的转换阶段代码与之呼应,并补充了 enable/disable:
1 2 3 4 5 6 7 rknn.load_lora(lora_paths=[["/adapter_model.safetensors" ]], lora_config_paths=[["/adapter_config.json" ]], lora_patterns=[[ "lora0_pattern0" ], lora_distribute_strategy='best_perf' ) rknn.enable_lora(enable_patterns=["lora0_pattern0" ]) rknn.disable_lora(disable_patterns=["lora0_pattern0" ]) (docs_md_v1.1 .0 /02_RKNN3_SDK_开发指南_V1.1 /02_RKNN3_SDK_开发指南_V1.1 .0 .pdf 第 4087 ~4093 行)
3.2 Qwen3_VL_LoRA 示例的实际导出代码 python/llm/export_rknn.py 完整展示了”基座 ONNX + LoRA safetensors → RKNN”的流程,LoRA 权重不需要转 ONNX,直接由 toolkit 内部处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 print ('--> Loading model' ) ret = rknn.load_llm(model=args.onnx_path, config=args.config, seq=[1 ,128 ]) if (args.lora_path is not None and args.lora_config_path is not None ) and os.path.exists(args.lora_path): ret = rknn.load_lora( lora_paths=[[args.lora_path]], lora_config_paths=[[args.lora_config_path]], lora_patterns=[["lora0_pattern0" ]], lora_quantized_dtype="float16" , lora_quantized_method="" , lora_prefix="base_model.model.model." , lora_postfix_a=".lora_A.weight" , lora_postfix_b=".lora_B.weight" ) (rknn3-model-zoo/examples/Qwen3_VL_LoRA/python/llm/export_rknn.py 第 59 ~73 行)
要点:
lora_paths 是二维列表 ([[...]]),内层列表的多个元素即多个适配器——这是”多 LoRA 打包进一个产物”的入口;
示例还使用了文档参数表未列出的 lora_prefix / lora_postfix_a / lora_postfix_b,用于把 HuggingFace PEFT 命名(base_model.model.model.*.lora_A.weight / *.lora_B.weight)映射到基座模型层名,实际使用时以所用 toolkit 版本的接口定义为准;
README 特别强调无需中间格式转换:
1 2 3 4 5 6 7 8 > - LoRA 权重(如 `adapter_model.safetensors`)**无需** 单独转换为 ONNX 模型,只需在 > `export_ rknn.py` 中通过 `--lora_path` 传入权重路径、`--lora_ config_path` 传入配置 > 文件路径,脚本内部会调用 `rknn.load_ lora()` 直接加载 `.safetensors` 文件,无需中间> 格式转换。 > - 导出含 LoRA 的 RKNN 模型时,除 `.rknn` 文件外还会额外生成一个 `.lora_weight` 文件。 > C++ 推理时需同时提供这两个文件(`.rknn` 作为模型路径,`.lora_ weight` 作为 LoRA 权重> 路径)。 (rknn3-model-zoo/examples/Qwen3_VL_ LoRA/README.md 第 73~75 行)
更换不同 LoRA 重新导出时,若模型结构与量化参数未变,可用 --rebuild 复用 ./tmp 中间产物快速重建(README 第 77 行)。
3.3 转换产物形态与多适配器打包 一次转换的产物是成对 的:.rknn(基座计算图,内含 LoRA 分支结构)+ .lora_weight(LoRA 权重数据)。.lora_weight 单个文件可包含多个适配器,这一点在 rkllm3-server 文档中有明确表述:
1 2 3 4 5 6 7 8 9 10 <tr > <td > --lora-weight FNAME</td > <td > LLM lora 权重路径。单个文件可包含多个适配器,所有会话共享; 可通过 /lora-adapters API 为各会话独立设置缩放比例</td > </tr > (docs_md_ v1.1.0/06_RKLLM3_ Server_使用指南_ V1.1/06_RKLLM3_ Server_使用指南_ V1.1.0.pdf 第 330 行) RKLLM3 Server 支持通过 LoRA(Low-Rank Adaptation)对模型进行轻量级微调。单个 LoRA 权重文件 可以包含多个适配器,所有从该文件加载的适配器在所有会话(slot)之间共享,但每个会话可以在 运行时独立配置各适配器的缩放比例。 默认情况下,所有适配器的缩放比例(scale)将设置为 0。 (docs_md_ v1.1.0/06_RKLLM3_ Server_使用指南_ V1.1/06_RKLLM3_ Server_使用指南_ V1.1.0.pdf 第 1699~1701 行)
因此”微调了几个功能不同的 LoRA 版本”的标准做法是:在 PC 端把多个 adapter_model.safetensors 一次性(或分多次 rebuild)通过 load_lora(lora_paths=[[lora_a, lora_b, ...]], lora_patterns=[["lora0_pattern0", "lora1_pattern0", ...]]) 打包进同一个 .lora_weight,随基座模型一起部署。
在 NPU 算子层面,LoRA 的多分支合并由专用算子承担,说明 LoRA 分支是编译进基座计算图的:
1 2 3 4 ## exSum 多输入加权求和,常用于 LoRA 多分支结果合并。 (docs_md_ v1.1.0/07_RKNN3_ 算子支持与约束参考_V1.1/07_ RKNN3_算子支持与约束参考_ V1.1.0.pdf 第 5394~5396 行)
这也解释了后文的一个关键限制:.lora_weight 必须与 .rknn 中编译好的 LoRA 分支结构(patterns、目标层、秩)匹配,板端无法加载结构未知的原始 safetensors。
4. 板端部署的三种形态 4.1 C API(librknn3_api) Runtime C API 共提供 8 个 LoRA 接口(rknn3_api.h 实际声明):
1 2 3 4 5 6 7 8 9 int rknn3_lora_init (rknn3_context context, const char * lora_weight_path) ;int rknn3_lora_init_from_data (rknn3_context context, const void * weight_data, uint64_t weight_size) ;int rknn3_lora_load (rknn3_context context, rknn3_lora* lora) ;int rknn3_lora_unload (rknn3_context context, rknn3_lora* lora) ;int rknn3_lora_enable (rknn3_context context, rknn3_lora* lora) ;int rknn3_lora_disable (rknn3_context context, rknn3_lora* lora) ;int rknn3_session_enable_lora (rknn3_session* session, rknn3_lora* lora) ;int rknn3_session_disable_lora (rknn3_session* session, rknn3_lora* lora) ; (rknn3-runtime/rknn3-api/include/rknn3_api.h 第 1507 ~1575 行,注释略)
适配器描述符只有名字与缩放系数两个字段:
1 2 3 4 5 6 7 8 9 10 typedef struct { char lora_name[RKNN3_MAX_NAME_LEN]; float scale; } rknn3_lora; (rknn3-runtime/rknn3-api/include/rknn3_api.h 第 768 ~776 行)
单 context 适配器数量上限为 32:
1 2 #define RKNN3_MAX_LORA_NUM 32 (rknn3-runtime/rknn3-api/include/rknn3_api.h 第 60 行)
配套查询命令(rknn3_query):
1 2 3 4 5 <tr > <td > RKNN3_QUERY_ LORA_NUM</td > <td > 查询已初始化的LoRA数量,需在 rknn3_ lora_init / rknn3_ lora_init_ from_data 之后调用,结果为 uint32_ t</td > </tr > <tr > <td > RKNN3_QUERY_ LORA_INFO</td > <td > 查询LoRA信息列表,结果写入 rknn3_ lora 数组(大小为 RKNN3_MAX_ LORA_NUM)</td > </tr > (docs_ md_v1.1.0/05_ RKNN3_Runtime_ C_API_ 参考_V1.1/05_ RKNN3_Runtime_ C_API_ 参考_V1.1.0.pdf 第 2049 行)
《Runtime C API 参考》5.10 节对每个接口的语义与生效规则 有详细规定,其中与”热加载”直接相关的部分:
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 ## rknn3_lora_ init 从LoRA权重文件路径初始化上下文级别的LoRA支持并加载元数据。此接口需在 rknn3_model_ init 之后、会话使用LoRA之前调用。调用后可通过 RKNN3_QUERY_ LORA_NUM 和 RKNN3_ QUERY_LORA_ INFO 查询已加载的LoRA信息,再通过 rknn3_lora_ load 加载指定的适配器。## rknn3_lora_ init_from_ data 从内存中的LoRA权重数据初始化上下文级别的LoRA支持并加载元数据。适用于LoRA权重已在内存中 的场景,使用方式同 rknn3_lora_ init。## rknn3_lora_ load 将指定的LoRA加载到RKNN3上下文中。调用前需先通过 rknn3_lora_ init 或 rknn3_lora_ init_from_ data 完成LoRA初始化,并通过 RKNN3_QUERY_ LORA_INFO 查询获取合法的 rknn3_ lora 描述符。## rknn3_lora_ unload 从RKNN3上下文中卸载指定的LoRA,释放其占用的资源。卸载后该适配器不再可用于任何会话。## rknn3_lora_ enable 在上下文级别启用已加载的LoRA,使之对该上下文下的所有推理生效。若需要针对特定会话独立 控制LoRA,应使用 rknn3_session_ enable_lora。 生效规则:本接口仅在调用时更新一次LoRA配置,不会在后续每次推理时重复更新。若某个session 通过rknn3_ session_enable_ lora 启用了不同LoRA,则该session每次调用 rknn3_session_ run 时 都会覆盖本接口所做的配置。因此,不建议同时使用两种接口控制LoRA。## rknn3_session_ enable_lora 为指定会话启用LoRA。LoRA权重通过 rknn3_ lora_load 加载在上下文(context)级别,不同session 共享同一份权重;本接口仅控制该session是否应用已加载的LoRA,不会重复加载权重。调用前需确保 session和lora有效且lora已通过 rknn3_ lora_load 加载。 生效规则:通过本接口启用的LoRA配置会在每次调用 rknn3_ session_run 时重复更新,因此若同时 使用了 rknn3_ lora_enable 对不同LoRA进行上下文级启用, rknn3_ session_run 会以session级别 的配置覆盖上下文级别的配置。因此,建议不要混用两种接口对LoRA进行控制,以避免生效顺序不确定。 ## rknn3_ session_disable_ lora 为指定会话禁用LoRA。禁用后该session的推理不再应用LoRA,但权重仍保留在上下文中,其他session 不受影响,可随时通过 rknn3_session_ enable_lora 重新启用。 (docs_ md_v1.1.0/05_ RKNN3_Runtime_ C_API_ 参考_V1.1/05_ RKNN3_Runtime_ C_API_ 参考_V1.1.0.pdf 第 1743~1856 行,有删节)
Qwen3_VL_LoRA 的 C++ demo 把标准调用序列封装在 setup_context_lora() 中,是推荐顺序 lora_init → query → query → load → session_enable 的完整实现:
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 static int setup_context_lora (rknn3_context ctx, rknn3_session* session_lora, const char * lora_weight_path, rknn3_lora* lora_out) { int ret; uint32_t n_lora = 0 ; rknn3_lora lora_list[RKNN3_MAX_LORA_NUM] = {0 }; ret = rknn3_lora_init (ctx, lora_weight_path); if (ret < 0 ) { printf ("Failed to initialize lora on context\n" ); return ret; } ret = rknn3_query (ctx, RKNN3_QUERY_LORA_NUM, &n_lora, sizeof (n_lora)); ... ret = rknn3_query (ctx, RKNN3_QUERY_LORA_INFO, lora_list, sizeof (lora_list)); ... ret = rknn3_lora_load (ctx, &lora_list[0 ]); ... ret = rknn3_session_enable_lora (session_lora, &lora_list[0 ]); ... *lora_out = lora_list[0 ]; return 0 ; } (rknn3-model-zoo/examples/Qwen3_VL_LoRA/cpp/llm/rknn_qwen3_vl_llm.cc 第 39 ~87 行)
demo 的整体架构印证了”多 session 共享 context 级 LoRA 权重、各自独立启用”的设计:Base / LoRA 两个 session 建在同一个 context 上,仅 max_context_len 不同,LoRA 只对 session_lora 生效,Base session 完全不受影响:
1 2 3 4 5 6 7 8 9 10 11 12 13 session_base = rknn3_session_init (ctx, ¶ms_base, 1 ); ... session_lora = rknn3_session_init (ctx, ¶ms_lora, 1 ); ... rknn3_lora lora = {0 }; bool lora_enabled = false ; if (lora_weight_path != nullptr ) { ret = setup_context_lora (ctx, session_lora, lora_weight_path, &lora); ... } (rknn3-model-zoo/examples/Qwen3_VL_LoRA/cpp/llm/rknn_qwen3_vl_llm.cc 第 131 ~157 行)
释放阶段的逆序同样值得参考(先 session 级 disable,再 context 级 unload):
1 2 3 4 5 6 7 8 9 10 if (llm_ctx->rknn_sess_lora) { if (llm_ctx->lora_enabled && strlen (llm_ctx->lora.lora_name) > 0 ) { rknn3_session_disable_lora (llm_ctx->rknn_sess_lora, &llm_ctx->lora); rknn3_lora_unload (llm_ctx->rknn_ctx, &llm_ctx->lora); } rknn3_session_destroy (llm_ctx->rknn_sess_lora); ... } (rknn3-model-zoo/examples/Qwen3_VL_LoRA/cpp/llm/rknn_qwen3_vl_llm.cc 第 201 ~209 行)
此外,推理状态结构 RKLLMRunState 提供运行中已启用 LoRA 的查询入口:
1 2 3 int32_t n_loras_enabled; rknn3_lora* loras_enabled; (rknn3-runtime/rknn3-api/include/rknn3_api.h 第 1070 ~1071 行)
Toolkit Lite 的 LoRA 管理接口(3.11 节)与 C API 一一对应,生命周期为:
1 2 3 init_lora -> query_lora -> load_lora -> enable_lora(session) -> disable_lora(session) -> unload_lora (docs_md_v1.1.0 /04_ RKNN3_Toolkit_Lite_Python_API_参考_V1.1 /04_ RKNN3_Toolkit_Lite_Python_API_参考_V1.1.0 .pdf 第 1486 ~1488 行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 同一个 Base Runtime 可以创建多个 Session,并仅在指定 Session 上启用 LoRA,从而让不同 Session 分别运行 Base 模型或 LoRA 模型。 (同上,第 1491 行)## init_lora <table > <tr > <td > 功能</td > <td > 初始化 LoRA 支持,加载 LoRA 权重数据。</td > </tr > <tr > <td > 参数</td > <td > lora_ weight_path : LoRA 权重文件路径或目录</td > </tr > <tr > <td > </td > <td > weight_ data : 可选的内存 LoRA 权重数据,支持 bytes/bytearray/memoryview、 numpy.ndarray 或裸指针</td > </tr > <tr > <td > session_index : LLM Session 索引,默认值为 0。</td > </tr > </table > (同上,第 1495 行) ## enable_ lora 使用说明 LoRA 可按 Session 启用。不同 Session 可以共享同一 Runtime,并分别选择是否启用 LoRA。 RKNN3Lora.scale 可在启用前调整,用于控制适配器作用强度。 (同上,第 1540 行)## disable_lora 使用说明 只停止 LoRA 在指定 Session 上生效,不等同于从 Runtime 卸载权重。需要彻底释放适配器时, 再调用 unload_ lora() 。 (同上,第 1552 行)
注意 init_lora 的 lora_weight_path 支持”文件路径或目录 “——目录形式为多权重文件的组织提供了官方入口。
仓库内板端 Python 测试脚本 rknn3_session_test_lora.py 演示了包括”从内存加载”和”动态改 scale”在内的完整用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 use_lora_from_data = True if use_lora_from_data: with open (args.lora_path, "rb" ) as f: lora_data = f.read() ret = rknn.init_lora(weight_data=lora_data, weight_size=len (lora_data)) ... lora_list, n_lora = rknn.query_lora() print (f"Found {n_lora} LoRA adapter(s):" ) for i in range (n_lora): print (f" LoRA[{i} ]: name={lora_list[i].lora_name.decode('utf-8' )} , scale={lora_list[i].scale:.2 f} " ) cur_lora = lora_list[0 ] ret = rknn.load_lora(cur_lora) ... if s == 1 : cur_lora.scale = 0.0 rknn.enable_lora(cur_lora, session_index=s) cur_lora.scale = 1.0 rknn.enable_lora(cur_lora, session_index=s) (rknn3-toolkit/rknn3-toolkit-lite/examples/tools/rknn3_session_test_lora.py 第 282 ~328 行,有删节)
常见错误清单(Lite 文档 6.5.2 节)也指出了使用的前置条件:
1 2 3 4 5 6 7 8 常见错误包括: LLM Runtime 尚未初始化; LoRA 权重路径或内存数据无效; 使用了不存在的 lora_name ; 尚未 load_ lora() 就调用 enable_lora() ; 对已经释放或不存在的 Session 操作 LoRA; LoRA 与 Base 模型不匹配。 (docs_ md_v1.1.0/04_ RKNN3_Toolkit_ Lite_Python_ API_参考_ V1.1/04_RKNN3_ Toolkit_Lite_ Python_API_ 参考_V1.1.0.pdf 第 2724~2731 行)
其中”LoRA 与 Base 模型不匹配 “再次强调 .lora_weight 与 .rknn 的配套关系。
4.3 rkllm3-server(HTTP 服务) rkllm3-server 面向多会话服务化场景,LoRA 通过启动参数加载、运行时 API 调节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 GET /lora-adapters :获取所有 LoRA 适配器列表 此端点返回已加载的 LoRA 适配器列表。在启动服务器时通过 --lora-weight 参数添加适配器, 例如:lora-weight Qwen2.5-3B-Instruct.lora_weight 。 返回结果: [ {"name": "lora_ slot0", "scale": 0.0}, {"name": "lora_slot1", "scale": 0.0} ] POST /lora-adapters :设置 LoRA 适配器列表 此端点用于为指定会话(slot)设置 LoRA 适配器的 scale。请注意,该 scale 会被每个请求中 的 lora 字段值覆盖。 若要禁用某个适配器,可以将其从列表中移除,或者将其 scale 设置为 0。 (docs_md_ v1.1.0/06_RKLLM3_ Server_使用指南_ V1.1/06_RKLLM3_ Server_使用指南_ V1.1.0.pdf 第 1705~1741 行)
每次推理请求还可通过 extra_body 的 lora 字段按请求覆盖:
1 2 3 4 5 6 7 8 { "messages" : [ { "role" : "user" , "content" : "你好! " } ] , "lora" : [ { "name" : "lora_slot0" , "scale" : 0.5 } , { "name" : "lora_slot1" , "scale" : 1.0 } ] } (docs_md_v1.1 .0 /06 _RKLLM3_Server_使用指南_V1.1 /06 _RKLLM3_Server_使用指南_V1.1 .0 .pdf 第 1784 ~1792 行)
server 的适配器命名(lora_slot0、lora_slot1)展示了”一个 .lora_weight 文件含多个适配器”的实际效果:启动一次,所有 slot 共享,各 slot 独立设定 scale(默认 0,即等效基座)。
5. 热加载与热更新能力分析 问:rk1828 是否支持多个 LoRA 在板子上的热加载和热更新?
把”热”拆成三个层级来回答:
5.1 L1 已驻留适配器之间的运行时切换:完全支持 这是官方设计的主场景。开发指南 4.2.7 的表述”不修改基础模型权重动态加载/切换适配器 ……一份基础模型 + 多个 LoRA 适配器,按任务切换”即指此能力:
context 级:rknn3_lora_load / rknn3_lora_unload 可在进程运行中加载与卸载适配器权重(load 后用 rknn3_lora_enable 全局启用,unload 释放资源);
session 级:rknn3_session_enable_lora / rknn3_session_disable_lora 控制单个会话是否应用某个已加载适配器,”每次 session_run 重复更新”,且”不影响其他 session”;
scale 运行时可改:rknn3_lora.scale / RKNN3Lora.scale 是普通结构体字段,改完重新 enable 即生效(Lite 测试脚本第 322~327 行演示了 scale 0.0 → 1.0 的连续切换);server 形态下还可按 HTTP 请求粒度覆盖。
需要注意的语义细节:scale = 0 等效于关闭 (server 默认把所有适配器 scale 设为 0,且”若要禁用某个适配器……将其 scale 设置为 0”)。
5.2 L2 运行时载入新的权重数据:支持,但有格式前提 两条”热更新”通道:
rknn3_lora_init_from_data(ctx, weight_data, weight_size) / Lite 的 init_lora(weight_data=...):LoRA 权重以内存缓冲区形式进入 runtime,天然适合”新权重通过网络/文件下发后不重启进程即挂载”的更新流程;
rknn3_lora_unload 卸载旧适配器释放资源后,可再次 load 新适配器,实现权重的就地替换。
前提 :载入的数据必须是 PC 端 Toolkit 产出的 .lora_weight 格式,且其适配器集合(名称、patterns、目标层、秩)与当前 .rknn 编译的 LoRA 分支图匹配——因为合并分支(exSum 等)在转换期已固化进计算图。Lite 文档把”LoRA 与 Base 模型不匹配”列为典型错误,即此约束。运行中把新的 .lora_weight 数据推到板端再走 init → query → load → enable 流程,机制上是通的,但官方文档未提供跨基座模型复用 .lora_weight 的承诺,实践上应保持”同一套转换产物内部轮换”。
5.3 L3 运行时加载全新结构的适配器(未经转换的 safetensors):不支持 板端没有解析 HuggingFace PEFT 格式的能力:adapter_model.safetensors / adapter_config.json 只能在 PC 端经 rknn.load_lora(...) + rknn.build(...) 与基座一起编译。若要新增一个”功能不同的 LoRA 版本”(不同目标层、不同秩),必须:
PC 端重新执行 export_rknn.py --lora_path ... --lora_config_path ...(新增适配器涉及 .rknn 图结构变化时不能用 --rebuild,需完整 build);
将新的 .rknn + .lora_weight 一起部署。
换言之,“多 LoRA 版本”应在出厂/发版前打包齐备,板端负责按需切换 ,而不是板端在线学习新适配器。
5.4 热切换的代价与多 session 约束
KVCache 被清空 :Qwen3_VL_LoRA README 对 session 级接口的描述明确指出:
1 2 3 | `rknn3_session_enable_lora(session, lora)` | 为指定 session 启用 LoRA,调用后会自动清空所有kvcache | | `rknn3_session_disable_lora(session, lora)` | 为指定 session 关闭 LoRA,调用后会自动清空所有kvcache | (rknn3-model-zoo/examples/Qwen3_VL_ LoRA/README.md 第 180~181 行)
这意味着切换适配器后该 session 的多轮对话上下文丢失。若业务需要”切换后恢复上文”,可组合 SDK 的 KV Cache 导入/导出能力(开发指南 V1.0.4 新增特性之一,见本文第 2 节修订记录引文)在切换前保存、切换后恢复。
1 2 3 4 说明:一个 context 可创建多个 session,多个 session 共享模型权重和 internal memory,但 KVCache 及 LoRA 启用状态相互独立。多个 session 不能并发执行,同一时刻仅允许一个 session 运行。 (docs_md_ v1.1.0/02_RKNN3_ SDK_开发指南_ V1.1/02_RKNN3_ SDK_开发指南_ V1.1.0.pdf 第 2839 行)
即多 LoRA 多 session 是”分时复用”而非并行多任务。
server 形态的更新粒度 :--lora-weight 只在启动时读取;运行时能做的只有 scale 调节与按请求覆盖,不能挂新文件。
6. 使用条件与限制
维度
规格
出处
单 context 适配器上限
32(RKNN3_MAX_LORA_NUM)
rknn3_api.h 第 60 行;C API 参考 6.1 常量表
权重产物格式
.lora_weight(与 .rknn 成对生成、配套使用)
Qwen3_VL_LoRA/README.md 第 75 行
LoRA 权重量化类型
int4 / int6 / int8 / float16(默认 float16)
Toolkit API 参考 9.1.1;开发指南 4.2.7
多核拆分策略
best_perf / less_mem
Toolkit API 参考 9.1.1;开发指南 4.2.7
转换时机
load_llm 之后、build 之前
Toolkit API 参考 9.1.1
板端初始化时机
rknn3_model_init 之后、session 使用 LoRA 之前
C API 参考 5.10 rknn3_lora_init;开发指南 4.2.7
权重加载方式
文件路径(文件或目录)/ 内存数据
C API rknn3_lora_init(_from_data);Lite init_lora
作用域
仅 LLM(Vision 模型导出与推理不涉及 LoRA)
示例目录结构(export_vision.py 无 LoRA 参数);load_llm 时序
session 级切换副作用
自动清空该 session 全部 KVCache
Qwen3_VL_LoRA/README.md 第 180~181 行
context 级 vs session 级
建议二选一,混用生效顺序不确定
C API 参考 5.10 生效规则
多 session 并发
不允许并发,同一时刻仅一个 session 运行
开发指南 4.x session 说明(第 2839 行)
目标平台
协处理器 RK182X 系列(示例代码 target_platform='rk1820',即含 RK1828 的协处理器家族),主控为 RK3588 等(承担 Vision 裁剪算子、LM Head 等 CPU 任务)
export_rknn.py 第 46 行;rknn3_session_test_lora.py 第 223 行 init_runtime(target='rk1820')
错误排查
优先 query_lora() 返回的名称与 scale,勿硬编码;典型错误含”LoRA 与 Base 模型不匹配”
Lite API 参考 6.5.2
7. 多 LoRA 版本部署建议 结合以上调研,对”微调了几个功能不同的 LoRA 版本,按需加载”给出推荐做法:
打包 :PC 端转换时把全部业务 LoRA 适配器通过 load_lora(lora_paths=[[a, b, c, ...]], lora_patterns=[["lora0_pattern0", "lora1_pattern0", ...]]) 打进同一个 .lora_weight(受 32 个上限约束,注意不要超过);不同功能适配器若秩/目标层一致,一次转换即可。
板端初始化 :rknn3_lora_init(ctx, "<path-to>.lora_weight")(或目录),rknn3_query 拿到全部适配器名与默认 scale;按需对每个适配器执行 rknn3_lora_load(多适配器可同时 load 驻留,只按 session 粒度启用)。
运行时切换 :统一使用 session 级接口 rknn3_session_enable_lora / disable_lora(避免与 context 级混用);把切换动作安排在对话轮次边界,明确感知 KVCache 被清空的语义;需要保留上下文的场景,用 KV Cache 导出/导入在切换前后搬运状态。
灰度与调参 :scale 不必非 0 即 1,server 形态下可直接用 /lora-adapters 或每请求 lora 字段做 A/B 与强度渐变(等效于”软开关”)。
版本更新 :新增适配器走 PC 端重新转换 + --rebuild(结构未变时);下发方式可用”整文件替换 + init_from_data 内存挂载”实现免重启更新,但须保持与 .rknn 的结构匹配。
内存预算 :每个驻留适配器都会占用协处理器内存(低秩但全层覆盖时仍可观);内存紧张时选 lora_distribute_strategy='less_mem' 或降低驻留数量,用 unload 释放不再使用的适配器。
8. 参考文档与源码索引 官方文档
章节
内容
02_RKNN3_SDK_开发指南_V1.1/02_RKNN3_SDK_开发指南_V1.1.0.pdf § 4.2.7
LoRA 适用场景、两阶段流程、C API 调用示例、量化与拆分策略
03_RKNN3_Toolkit_Python_API_参考_V1.1/03_RKNN3_Toolkit_Python_API_参考_V1.1.0.pdf § 9.1
load_lora / enable_lora / disable_lora 转换期接口与参数
04_RKNN3_Toolkit_Lite_Python_API_参考_V1.1/04_RKNN3_Toolkit_Lite_Python_API_参考_V1.1.0.pdf § 3.11 / § 4.6 / § 6.5.2
板端 Python LoRA 生命周期、RKNN3Lora 结构、常见错误
05_RKNN3_Runtime_C_API_参考_V1.1/05_RKNN3_Runtime_C_API_参考_V1.1.0.pdf § 5.10 / § 6.1
8 个 LoRA C 接口语义与生效规则、RKNN3_MAX_LORA_NUM、查询命令
06_RKLLM3_Server_使用指南_V1.1/06_RKLLM3_Server_使用指南_V1.1.0.pdf § 2.2.2 / § 6.1
--lora-weight 启动参数、/lora-adapters API、每请求 lora 覆盖
07_RKNN3_算子支持与约束参考_V1.1/07_RKNN3_算子支持与约束参考_V1.1.0.pdf § exSum
LoRA 多分支合并算子
仓库源码
文件
内容
rknn3-model-zoo/examples/Qwen3_VL_LoRA/README.md
LoRA 示例完整使用说明(导出命令、双 session、接口表)
rknn3-model-zoo/examples/Qwen3_VL_LoRA/python/llm/export_rknn.py
rknn.load_lora() 实际调用(含 prefix/postfix 映射参数)
rknn3-model-zoo/examples/Qwen3_VL_LoRA/cpp/llm/rknn_qwen3_vl_llm.cc
setup_context_lora() 标准调用序列、双 session 初始化与释放
rknn3-model-zoo/examples/Qwen3_VL_LoRA/cpp/main.cc
15/16 参数命令行约定(第 16 参数为 llm_lora_weight_path)
rknn3-runtime/rknn3-api/include/rknn3_api.h
LoRA 接口声明、rknn3_lora 结构、RKNN3_MAX_LORA_NUM、RKLLMRunState
rknn3-toolkit/rknn3-toolkit-lite/examples/tools/rknn3_session_test_lora.py
板端 Python 全流程:内存 init、query、load、动态 scale、unload