RK1828 + RK3588 平台 LoRA 功能调研:用法、限制、热更新

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 使用的完整链路:

  1. PC 端导出(python/llm/):export_llm.py 只负责导出基座 ONNX(不含 LoRA,该文件中没有任何 LoRA 相关代码);export_rknn.py 在 ONNX 转 RKNN 阶段通过 --lora_path 注入 LoRA 权重。
  2. 板端 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
# 从当前目录加载LoRA权重
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
        # Load model
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
/**
* @struct rknn3_lora
* @brief Defines parameters for a Lora used in model fine-tuning.
*/
typedef struct
{
char lora_name[RKNN3_MAX_NAME_LEN]; /**< Name of the Lora. */
float scale; /**< Scaling factor for applying the Lora. */
} rknn3_lora;
(rknn3-runtime/rknn3-api/include/rknn3_api.h 第 768~776 行)

单 context 适配器数量上限为 32:

1
2
#define RKNN3_MAX_LORA_NUM 32                      /* maximum number of lora. */
(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
// 在 context 级别初始化 LoRA 并为 lora session 启用 LoRA
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};

// 在 context 上初始化 LoRA
ret = rknn3_lora_init(ctx, lora_weight_path);
if (ret < 0) {
printf("Failed to initialize lora on context\n");
return ret;
}

// 使用 rknn3_query 查询 LoRA 数量
ret = rknn3_query(ctx, RKNN3_QUERY_LORA_NUM, &n_lora, sizeof(n_lora));
...
// 使用 rknn3_query 查询 LoRA 信息
ret = rknn3_query(ctx, RKNN3_QUERY_LORA_INFO, lora_list, sizeof(lora_list));
...
// 在 context 上加载 LoRA
ret = rknn3_lora_load(ctx, &lora_list[0]);
...
// 为 lora session 启用 LoRA
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
    // Base session: 初始化 -> 设置 chat template 和 callback
session_base = rknn3_session_init(ctx, &params_base, 1);
...
// LoRA session: 初始化
session_lora = rknn3_session_init(ctx, &params_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) {
// 禁用 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;  /**< Number of Lora enabled. */
rknn3_lora* loras_enabled; /**< Lora enabled. */
(rknn3-runtime/rknn3-api/include/rknn3_api.h 第 1070~1071 行)

4.2 Toolkit Lite(板端 Python)

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
    # 6. LoRA Initialization (必须在主 Session 循环前建立,支持从内存加载)
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:.2f}")

cur_lora = lora_list[0]
ret = rknn.load_lora(cur_lora)
...
if s == 1:
# 测试修改scale值
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 运行时载入新的权重数据:支持,但有格式前提

两条”热更新”通道:

  1. rknn3_lora_init_from_data(ctx, weight_data, weight_size) / Lite 的 init_lora(weight_data=...):LoRA 权重以内存缓冲区形式进入 runtime,天然适合”新权重通过网络/文件下发后不重启进程即挂载”的更新流程;
  2. 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 版本”(不同目标层、不同秩),必须:

  1. PC 端重新执行 export_rknn.py --lora_path ... --lora_config_path ...(新增适配器涉及 .rknn 图结构变化时不能用 --rebuild,需完整 build);
  2. 将新的 .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 节修订记录引文)在切换前保存、切换后恢复。

  • 两级接口不可混用:context 级 enable 与 session 级 enable 同时使用时”生效顺序不确定”(C API 文档 5.10 生效规则,见 4.1 节引文)。实践建议统一走 session 级。

  • session 不能并发:多 session 共享模型权重,但”多个 session 不能并发执行,同一时刻仅允许一个 session 运行”:

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 版本,按需加载”给出推荐做法:

  1. 打包:PC 端转换时把全部业务 LoRA 适配器通过 load_lora(lora_paths=[[a, b, c, ...]], lora_patterns=[["lora0_pattern0", "lora1_pattern0", ...]]) 打进同一个 .lora_weight(受 32 个上限约束,注意不要超过);不同功能适配器若秩/目标层一致,一次转换即可。
  2. 板端初始化:rknn3_lora_init(ctx, "<path-to>.lora_weight")(或目录),rknn3_query 拿到全部适配器名与默认 scale;按需对每个适配器执行 rknn3_lora_load(多适配器可同时 load 驻留,只按 session 粒度启用)。
  3. 运行时切换:统一使用 session 级接口 rknn3_session_enable_lora / disable_lora(避免与 context 级混用);把切换动作安排在对话轮次边界,明确感知 KVCache 被清空的语义;需要保留上下文的场景,用 KV Cache 导出/导入在切换前后搬运状态。
  4. 灰度与调参:scale 不必非 0 即 1,server 形态下可直接用 /lora-adapters 或每请求 lora 字段做 A/B 与强度渐变(等效于”软开关”)。
  5. 版本更新:新增适配器走 PC 端重新转换 + --rebuild(结构未变时);下发方式可用”整文件替换 + init_from_data 内存挂载”实现免重启更新,但须保持与 .rknn 的结构匹配。
  6. 内存预算:每个驻留适配器都会占用协处理器内存(低秩但全层覆盖时仍可观);内存紧张时选 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

RK1828 + RK3588 平台 LoRA 功能调研:用法、限制、热更新
http://www.horus-space.cloud/posts/d2de7c98.html
作者
Horus
发布于
2026年9月28日
许可协议