To work solely on the Java control plane:
- Linux;
- JDK 17, including
javac; - Maven 3.9 or compatible.
For native adapters:
- CMake 3.16 or later;
- C/C++ compiler with C++17 support;
- JDK JNI headers;
- Git;
- SDK and drivers for the target backend.
Ryzen AI requires CMake 3.20 and C++20. Download and setup scripts also require Python 3, curl, tar, and network access.
mvn testThe repository may include Maven under .build-tools:
./.build-tools/apache-maven-3.9.9/bin/mvn testCurrently, there are no tests under src/test. A BUILD SUCCESS result proves that the 41 Java classes compile and resources are copied, but not that:
- Spring starts correctly with a specific
.so; - JNI and Java have a consistent ABI;
- an NPU device is reachable;
- streaming and payloads are compatible with clients;
- the model generates correct output.
mvn spring-boot:runOr:
mvn package
java -jar target/npu-hub-1.0.0-SNAPSHOT.jarTo change port or models directory:
SERVER_PORT=11434 \
NPU_MODELS_DIRECTORY=/srv/npu-hub/models \
java -jar target/npu-hub-1.0.0-SNAPSHOT.jarSpring Boot serves the frontend and APIs from the same process. No Node server needs to be started.
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release
cmake --build native/build --parallelThis build creates stub libraries useful for testing loading and simple signatures. It must not be used to conclude that inference is hardware-accelerated. In particular, the Rockchip stub does not implement all current native signatures and is replaced by the full build.
The supported path for an explicit Rocket build is:
NPU_HUB_BUILD_JOBS=2 NPU_HUB_BUILD_ALL_PLATFORMS=1 tools/build-all.shThis path is not selected by default on Radxa ARM64 boards, where the platform
uses Qualcomm QAIRT. Orange Pi ARM64 boards still select Rocket. Detection uses
the device-tree model; NPU_HUB_BOARD can override it. To force a multi-platform build, set
NPU_HUB_BUILD_ALL_PLATFORMS=1; this also builds Rocket.
The script uses or creates:
.rocket-runtime/llama.cpp
.rocket-runtime/ggml-rocket
.rocket-runtime/rocket-userspace
workers/rocket/build
native/build
src/main/resources/native
target
llama.cpp is updated to origin/master on every run. The patch workers/rocket/patches/llama-rocket-strict.patch must apply cleanly to the checkout. Before manually updating llama.cpp, verify:
git -C .rocket-runtime/llama.cpp apply --check \
"$(pwd)/workers/rocket/patches/llama-rocket-strict.patch"If the patch is already applied, the correct check is:
git -C .rocket-runtime/llama.cpp apply --reverse --check \
"$(pwd)/workers/rocket/patches/llama-rocket-strict.patch"The packaged llama, GGML, CPU, Rocket, and JNI libraries must come from the same build tree and compilation run. Mixing different ABIs causes symbol errors or runtime crashes.
The actual implementation resides in workers/openvino/src/openvino_jni.cpp. It requires a distribution providing the OpenVINOGenAI and OpenVINO CMake packages:
cmake -S workers/openvino -B workers/openvino/build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/path/to/openvino
cmake --build workers/openvino/build --parallelWhen building the worker from the control panel, set OpenVINOGenAI_DIR,
OPENVINO_GENAI_DIR, OpenVINO_DIR, or CMAKE_PREFIX_PATH in the environment
of the NPU Hub process. These values are forwarded to CMake automatically.
The post-build step copies libnpu_openvino_jni.so into native/build. To bundle it into the JAR, it must be intentionally copied to src/main/resources/native/libnpu_openvino_jni.so before running mvn package. Also ensure that OpenVINO dependencies are accessible to the dynamic linker at runtime.
tools/build-all.sh recompiles generic adapters and may overwrite this file with the stub. Do not run both build workflows blindly.
The actual implementation resides in workers/ryzenai/src/ryzenai_jni.cpp:
RYZEN_AI_INSTALLATION_PATH=/path/to/ryzen-ai \
cmake -S workers/ryzenai -B workers/ryzenai/build \
-DCMAKE_BUILD_TYPE=Release
cmake --build workers/ryzenai/build --parallelThe build configuration looks for ONNX Runtime GenAI headers and library. You can pass them directly:
ONNXRUNTIME_GENAI_INCLUDE_DIR
ONNXRUNTIME_GENAI_LIBRARY
Here too, the post-build step copies to native/build; packaging and runtime dependencies remain the responsibility of the distribution build.
There is no separate workers/qualcomm CMake tree. The Qualcomm adapter loads
libGenie.so directly through JNI and uses the native Genie dialog callback;
genie-t2t-run is only a reference tool. The QNN libraries are shipped in the
QAIRT model directory.
The model directory must contain at least:
- a Genie dialog configuration such as
htp-model-config-llama32-1b-gqa.json; libGenie.so;- the QNN libraries;
- the serialized model weights.
Every modification to the native contract must be atomic:
- update the Java bridge class;
- update all C++ implementations with the matching symbol;
- update the Java driver invoking the method;
- update CMake and staging if dependencies or names change;
- clean up old
.sofiles; - recompile native libraries and the JAR;
- verify exported symbols using
nm -D; - launch with the real backend and perform load, generate, stream, and unload.
Example:
nm -D workers/rocket/build/bin/libnpu_rockchip_jni.so \
| rg 'Java_com_npuhub_jni_rockchip_RockchipNativeBridge'If UnsatisfiedLinkError occurs, check in order:
- which copy of the library was loaded;
- JNI symbol and signature;
- dependencies using
ldd; - ABI consistency between
libllama,libggml-*, and Rocket plugin; - binary architecture using
file.
The catalog is defined in ModelManagementService.initCatalogModels(). Each entry defines:
id, name, path, architecture, quantization, parameterCount,
contextWindow, compatibleBackend
Checklist:
- use an ID matching the remote repository;
- choose a path under
models/; - set the architecture as it determines the chat template;
- for Rockchip, use a permitted quantization;
- check the real GGUF filename;
- verify download, >50 MiB detection, load, and
/api/tags; - verify
/api/showand GGUF context length.
To add a new Rockchip quantization, update ROCKCHIP_QUANTIZATIONS. The matcher is used by the catalog, downloader, Ollama resolution, and deletion: a change affects all these workflows.
Adding a new backend involves multiple layers:
- enum value in
BackendType; - Spring implementation of
NpuDriver; - Java bridge with native signatures;
- real C++ implementation;
- hardware probe;
- CMake targets and packaging;
- priority and display order in
NpuDriverRegistry; - model catalog;
- frontend groups and selectors;
- documentation and tests.
Aim for a fail-closed probe: a backend should not declare itself available merely because its library loaded. The probe must verify the device, vendor runtime, and perform a minimal meaningful operation. Currently, Rockchip does not follow this rule yet because its native probe always returns true.
Compatible API surfaces share OllamaInferenceFacade; prompt and sampling logic should be modified there whenever it needs to remain consistent across Ollama and OpenAI.
Pay attention to protocol differences:
- Ollama streaming:
application/x-ndjson, one JSON per line; - OpenAI streaming:
text/event-stream, SSE events and expected terminator; - non-streaming: a single JSON object;
- Ollama errors:
{"error":"..."}; - OpenAI errors: nested error object.
When adding an inference endpoint that must obey start/stop, also update InferenceApiGateFilter.isInferencePath().
Administrative APIs reside in ControlPanelApiController. Do not put external command execution inside controllers: wrap them in a service and expose status/progress.
Main files:
templates/index.html: server-rendered structure and content;static/js/app.js: state, fetch, chat streaming, and actions;static/css/style.css: layout and themes;static/vendor: vendorized libraries.
There is no frontend build step. After making a change:
- start Spring with DevTools;
- perform a hard refresh in the browser;
- check the browser console and network tab;
- test both desktop and mobile viewports;
- verify that Markdown is still sanitized by DOMPurify.
Recommended priority:
- unit tests for name/quantization resolution and paths;
- unit tests for
GgufMetadataReader; - MVC tests for gate, error envelope, and control APIs;
- NDJSON/SSE streaming tests;
- alias and persistence tests on temporary directories;
- fake
NpuDriverfor lifecycle and concurrency; - separate native smoke tests for backend/hardware.
Java tests must not automatically load real .so files. Injecting a fake driver allows the test suite to run reliably on CI.
For Java or frontend changes:
mvn test
git diff --checkAdditionally, when applicable:
- Spring context startup;
- dashboard opening;
- download/status/delete on a temporary directory;
- load/start/generate/stream/stop/unload;
/api/tags,/api/ps,/api/show;- one non-streaming OpenAI request and one SSE request;
- check that no model files or generated
.sofiles are included in the commit.
For native changes:
- clean build of the target;
lddcheck with nonot founddependencies;- JNI symbols present;
- testing on target hardware;
- verified fallback or failure logs;
- ensure no mock output is mistaken for real inference.
tools/cleanup.sh --builds
tools/cleanup.sh --downloads
tools/cleanup.sh --all--builds deletes CMake outputs, target, and generated native resources.
--downloads deletes .build-tools and the llama.cpp checkout.
--all combines both groups.
The script does not delete:
- models;
.npuhub/ollama-models.json;ggml-rocketsources;rocket-userspacesources;- logs or configuration files external to the repository.