⭐
Log in to mark this repository.
Deploy rknn-describe-server to the CM5, fix the demo invocation
45647d3 · Gabriel Morell · 2026-08-25 16:21
Message
{commit_body(@commit)}
Files changed
modified
tools/rknn-describe-server/README.md
+37
−18
@@ -41,7 +41,11 @@ wrapper around `librkllmrt.so` (see `rkllm.h` in the upstream repo) that
| 41 | 41 | keeps the language model warm across requests. The request/response shape |
| 42 | 42 | above does not need to change. |
| 43 | 43 | |
| 44 | −## Setup on r5bp-0 (RK3588, librknnrt 2.3.0 already shipped by Armbian) | |
| 44 | +## Setup on an RK3588 board | |
| 45 | + | |
| 46 | +`librknnrt.so` and `librkllmrt.so` both ship inside the upstream repo | |
| 47 | +(`3rdparty/librknnrt/`, `rkllm-runtime/`) and get installed next to the demo | |
| 48 | +binary, so no system-wide RKNN runtime is required. | |
| 45 | 49 | |
| 46 | 50 | The bring-up below is what we ran on `r5bp-0` and should reproduce on any |
| 47 | 51 | RK3588 board with the same OS image. |
@@ -54,7 +58,12 @@ sudo apt install -y git cmake g++ python3-venv python3-pip
| 54 | 58 | # directly with the system toolchain). |
| 55 | 59 | mkdir -p ~/dms-vlm && cd ~/dms-vlm |
| 56 | 60 | git clone --depth 1 https://github.com/airockchip/rknn-llm.git |
| 57 | −cd rknn-llm/examples/multimodal_model_demo/deploy | |
| 61 | +cd rknn-llm | |
| 62 | +# rkllm's default CPU mask assumes 8 online cores. Required on boards with | |
| 63 | +# fewer (the CM5 at 172.16.15.136 has 6: 4x A55 + 2x A76) -- without it | |
| 64 | +# rkllm_init dies with "Mismatch between enabled CPUs mask and expected count". | |
| 65 | +patch -p1 < /path/to/dms/tools/rknn-describe-server/patches/0001-demo-cpu-mask-env.patch | |
| 66 | +cd examples/multimodal_model_demo/deploy | |
| 58 | 67 | mkdir build && cd build |
| 59 | 68 | cmake .. -DCMAKE_BUILD_TYPE=Release \ |
| 60 | 69 | -DCMAKE_SYSTEM_NAME=Linux \ |
@@ -72,7 +81,9 @@ wget -O Qwen2-VL-2B-Instruct.rknn \
| 72 | 81 | # 3. drop server.py + models.yaml into ~/dms-vlm, create a venv, install deps |
| 73 | 82 | cd ~/dms-vlm |
| 74 | 83 | python3 -m venv venv |
| 75 | −./venv/bin/pip install fastapi "uvicorn[standard]" python-multipart pyyaml | |
| 84 | +./venv/bin/pip install fastapi "uvicorn[standard]" python-multipart pyyaml pillow | |
| 85 | +# optional: HEIC/AVIF uploads (server.py registers it if importable) | |
| 86 | +./venv/bin/pip install pillow-heif | |
| 76 | 87 | |
| 77 | 88 | # 4. install the user-level systemd unit and enable linger so it keeps |
| 78 | 89 | # running across reboots without an active login session. |
@@ -86,8 +97,8 @@ sudo loginctl enable-linger "$USER"
| 86 | 97 | Smoke test from another host: |
| 87 | 98 | |
| 88 | 99 | ```bash |
| 89 | −curl -s http://r5bp-0:8080/healthz | |
| 90 | −curl -F image=@/path/to/sample.jpg http://r5bp-0:8080/describe | |
| 100 | +curl -s http://172.16.15.136:8080/healthz | |
| 101 | +curl -F image=@/path/to/sample.jpg http://172.16.15.136:8080/describe | |
| 91 | 102 | ``` |
| 92 | 103 | |
| 93 | 104 | First request takes ~10–15 s because the demo binary reloads the model |
@@ -95,28 +106,36 @@ every call. That's *the* thing to fix when going to production — replace
| 95 | 106 | `subprocess` with a long-lived ctypes wrapper around `librkllmrt.so` so |
| 96 | 107 | the LLM stays warm. The wire contract above does not change. |
| 97 | 108 | |
| 98 | −`models.yaml` example: | |
| 109 | +`models.yaml` — paths must point at the files as downloaded in step 2; the | |
| 110 | +Qwen2-VL build ships one `.rknn` (vision) and one `.rkllm` (LLM), not | |
| 111 | +`vision.rknn` / `llm.rkllm`: | |
| 99 | 112 | |
| 100 | 113 | ```yaml |
| 101 | 114 | qwen2-vl-2b: |
| 102 | − vision: /opt/rknn-llm/models/qwen2-vl-2b/vision.rknn | |
| 103 | − llm: /opt/rknn-llm/models/qwen2-vl-2b/llm.rkllm | |
| 115 | + vision: /home/gmorell/dms-vlm/models/qwen2-vl-2b/Qwen2-VL-2B-Instruct.rknn | |
| 116 | + llm: /home/gmorell/dms-vlm/models/qwen2-vl-2b/Qwen2-VL-2B-Instruct.rkllm | |
| 104 | 117 | max_context_len: 2048 |
| 105 | 118 | rknn_core_num: 3 |
| 119 | + platform: rk3588 # rk3588 | rk3576 | rk3562 | rv1126b | |
| 120 | + enabled_cpus_num: 4 # must be >= rknn_core_num and match the mask popcount | |
| 121 | + enabled_cpus_mask: "0x3C" # 6-core CM5: both A76 (cpu4,5) + two A55 (cpu2,3) | |
| 106 | 122 | img_start: "<|vision_start|>" |
| 107 | 123 | img_end: "<|vision_end|>" |
| 108 | 124 | img_content: "<|image_pad|>" |
| 109 | −minicpm-v-2_6: | |
| 110 | − vision: /opt/rknn-llm/models/minicpm-v-2_6/vision.rknn | |
| 111 | − llm: /opt/rknn-llm/models/minicpm-v-2_6/llm.rkllm | |
| 112 | − max_context_len: 2048 | |
| 113 | − rknn_core_num: 3 | |
| 114 | −internvl2-1b: | |
| 115 | − vision: /opt/rknn-llm/models/internvl2-1b/vision.rknn | |
| 116 | − llm: /opt/rknn-llm/models/internvl2-1b/llm.rkllm | |
| 117 | − max_context_len: 2048 | |
| 118 | − rknn_core_num: 3 | |
| 119 | 125 | ``` |
| 120 | 126 | |
| 127 | +`platform` is mandatory — the demo takes it as argv[7] and refuses to start | |
| 128 | +without a value it recognises. `enabled_cpus_num` / `enabled_cpus_mask` are | |
| 129 | +only honoured by a demo built with `patches/0001-demo-cpu-mask-env.patch`; | |
| 130 | +omit them on a stock 8-core board. | |
| 131 | + | |
| 132 | +### Known limitation: short descriptions | |
| 133 | + | |
| 134 | +The `thanhtantran/Qwen2-VL-2B-RKLLM` W8A8 conversion emits EOS after a single | |
| 135 | +clause, e.g. `"The image depicts an astronaut on the moon,"`. This is the | |
| 136 | +model, not the wiring: raising `max_new_tokens` from 256 to 512 returns | |
| 137 | +byte-identical output, and enabling the demo's chat template changes nothing. | |
| 138 | +Try a different conversion or a larger VLM if fuller captions are needed. | |
| 139 | + | |
| 121 | 140 | The DMS worker then talks to `http://r5bp-0:8080/describe` — see |
| 122 | 141 | `workers.yml` for the env-var wiring. |
added
tools/rknn-describe-server/patches/0001-demo-cpu-mask-env.patch
+21
−0
@@ -0,0 +1,21 @@
| 1 | +--- a/examples/multimodal_model_demo/deploy/src/main.cpp 2026-08-25 11:41:05.799461096 -0400 | |
| 2 | ++++ b/examples/multimodal_model_demo/deploy/src/main.cpp 2026-08-25 11:41:05.829457680 -0400 | |
| 3 | +@@ -119,6 +119,18 @@ | |
| 4 | + return -1; | |
| 5 | + } | |
| 6 | + | |
| 7 | ++ // DMS: let the caller pin inference to specific CPUs. rkllm's default | |
| 8 | ++ // mask assumes an 8-core RK3588; boards with fewer online cores (the | |
| 9 | ++ // 6-core CM5 here: 4x A55 + 2x A76) fail rkllm_init with "Mismatch | |
| 10 | ++ // between enabled CPUs mask and expected count". The runtime also | |
| 11 | ++ // requires enabled CPUs >= rknn_core_num. | |
| 12 | ++ if (const char* env_num = getenv("RKLLM_ENABLED_CPUS_NUM")) { | |
| 13 | ++ param.extend_param.enabled_cpus_num = (int8_t)atoi(env_num); | |
| 14 | ++ } | |
| 15 | ++ if (const char* env_mask = getenv("RKLLM_ENABLED_CPUS_MASK")) { | |
| 16 | ++ param.extend_param.enabled_cpus_mask = (uint32_t)strtoul(env_mask, NULL, 0); | |
| 17 | ++ } | |
| 18 | ++ | |
| 19 | + const char* img_start = "<|vision_start|>"; | |
| 20 | + const char* img_end = "<|vision_end|>"; | |
| 21 | + const char* img_content = "<|image_pad|>"; |
modified
tools/rknn-describe-server/server.py
+12
−0
@@ -69,6 +69,10 @@ def run_demo(demo_bin: str, demo_dir: str, image_path: str, model_cfg: dict,
| 69 | 69 | str(max_new_tokens), |
| 70 | 70 | str(max_context_len), |
| 71 | 71 | str(model_cfg.get("rknn_core_num", 3)), |
| 72 | + # argv[7]: demo selects rkllm base_domain_id from the SoC. Must sit | |
| 73 | + # between rknn_core_num and img_start or the token strings shift into | |
| 74 | + # the platform slot and the demo exits before loading anything. | |
| 75 | + model_cfg.get("platform", "rk3588"), | |
| 72 | 76 | model_cfg.get("img_start", "<|vision_start|>"), |
| 73 | 77 | model_cfg.get("img_end", "<|vision_end|>"), |
| 74 | 78 | model_cfg.get("img_content", "<|image_pad|>"), |
@@ -79,6 +83,14 @@ def run_demo(demo_bin: str, demo_dir: str, image_path: str, model_cfg: dict,
| 79 | 83 | lib_dir = str(Path(abs_demo).parent / "lib") |
| 80 | 84 | env["LD_LIBRARY_PATH"] = lib_dir + ":" + env.get("LD_LIBRARY_PATH", "") |
| 81 | 85 | |
| 86 | + # rkllm's default CPU mask assumes 8 online cores and rejects init on | |
| 87 | + # boards with fewer. Read by the locally patched demo (see | |
| 88 | + # patches/0001-demo-cpu-mask-env.patch); ignored by a stock build. | |
| 89 | + for cfg_key, env_key in (("enabled_cpus_num", "RKLLM_ENABLED_CPUS_NUM"), | |
| 90 | + ("enabled_cpus_mask", "RKLLM_ENABLED_CPUS_MASK")): | |
| 91 | + if model_cfg.get(cfg_key) is not None: | |
| 92 | + env[env_key] = str(model_cfg[cfg_key]) | |
| 93 | + | |
| 82 | 94 | # Demo's REPL: send `<image>{prompt}\n`, then `exit\n` to break the loop. |
| 83 | 95 | # The pre-canned numeric shortcuts (`0`, `1`) are ignored because our |
| 84 | 96 | # input is plain text starting with `<image>`. |
Parents: 8b74183