コンテンツにスキップ

RKLLM AI モデルデプロイガイド

修正履歴

NOバージョン修正内容修正日
1Ver1.0新規作成2026/06/22

この文書の情報は、文書を改善するため、事前の通知なく変更されることがあります。最新版は弊社ホームページからご参照ください。

株式会社日昇テクノロジーの書面による許可のない複製は、いかなる形態においても厳重に禁じられています。

1. AIモデルデプロイ

  DeepSeek-R1は、MagicCube Quant社のAI企業であるDeepSeekが開発した推論モデルです。DeepSeek-R1は、強化学習を用いて事後学習を行うことで推論能力を向上させ、特に数学的推論、コード推論、自然言語推論といった複雑なタスクにおいて優れた性能を発揮します。その卓越した推論能力と効率的なテキスト生成技術により、世界の人工知能分野で広く注目を集めています。

本稿では、主にCSUN RV1126B基板上でDeepSeek-R1をオフラインで実行する方法について解説します。RV1126Bは、エッジAIにおける優れたエネルギー効率と極めて高いコストパフォーマンスを誇り、AI導入に最適な選択肢となります。

DeepSeek-R1 と主要モデルのベンチマーク比較

図:DeepSeek-R1 と主要モデルのベンチマーク比較

CSUN RV1126B基板を正常に動作させるには、最低でも2GBのRAMが必要であることに注意してください。

2. クイックスタート

2.1 事前準備

2.1.1. ハードウェア準備

RV1126B基板、Type-Cデータケーブル、LANケーブルを用意し、MobaXtermでsshよりRV1126B基板にログインしましょう。詳しくは入門ガイドを参照ください。

LANケーブルで接続:

RV1126B 基板の LAN 接続構成

図:RV1126B 基板の LAN 接続構成

シリアルケーブル(Tpye C)で接続:

RV1126B 基板の Type-C シリアル接続構成

図:RV1126B 基板の Type-C シリアル接続構成

2.1.2. 開発環境の準備

  本書を初めて読む場合は、『入門ガイド』を参照し、記載された手順に従ってコンパイル環境を構築してください。

PC側のUbuntuシステムで run スクリプトを実行し、RV1126Bコンパイル環境に入ります。手順は次のとおりです。

Terminal window
cd ~/develop_environment
./run.sh 2204

EASY-EAI 開発環境起動後の端末画面

図:EASY-EAI 開発環境起動後の端末画面

2.2 ソースコードコンパイル

1) Googleドライブからダウンロード:

https://drive.google.com/drive/folders/1nsL3pk75dyZHF_39KCKVoSevMq2F6Be9?usp=sharing

AI モデルデプロイ用プログラムパッケージの Google ドライブフォルダー

図:AI モデルデプロイ用プログラムパッケージの Google ドライブフォルダー

ダウンロードしたパッケージをDocker開発環境に移動した後、以下のコマンドを実行して展開します。

tar -xvf deepseek-demo.tar.bz2

deepseek-demo ソースコードディレクトリ構成

図:deepseek-demo ソースコードディレクトリ構成

  1. サンプルコンパイル:

    Docker開発環境で対象サンプルディレクトリへ移動し、ビルドを実行します。具体的なコマンドは以下の通りです。

cd
/opt/linuxshare/work/rv1126b/jp/AI/demo/Tutorial/RKLLM/04-AI_deploy/deepseek-demo
./build.sh

deepseek-demo サンプルプログラムのビルド端末出力

図:deepseek-demo サンプルプログラムのビルド端末出力

続いて、実行ファイルを含む deepseek-demo_release/ ディレクトリを開発ボードの /userdata ディレクトリへコピーします。

Terminal window
sudo mount -t nfs -o
vers=3,proto=tcp,mountproto=tcp,nolock,retrans=5,timeo=5 192.168.10.85:/
/mnt
cp deepseek-demo_release/ /mnt/userdata/ -rf

さらに、librkllmrt.so も開発ボード側の /usr/lib 環境へ同期します。

cp lib/librkllmrt.so /mnt/usr/lib

実行ファイルと librkllmrt.so を開発ボードへコピーする端末出力

図:実行ファイルと librkllmrt.so を開発ボードへコピーする端末出力

2.3 開発ボードで大規模言語モデルを実行

シリアルデバッグまたは SSH デバッグで開発ボードへログインし、サンプルのデプロイ先ディレクトリへ移動します。

cd /userdata/deepseek-demo_release/

開発ボード上の deepseek-demo_release ディレクトリ確認

図:開発ボード上の deepseek-demo_release ディレクトリ確認

サンプルの実行コマンドは以下の通りです。

Terminal window
ulimit -HSn 102400
sudo ./deepseek-demo deepseek_r1_rv1126b_w4a16.rkllm 1024
2048

開発ボード上で RKLLM デモを起動した端末出力

図:開発ボード上で RKLLM デモを起動した端末出力

開発ボード上で DeepSeek-R1 が回答を生成する端末出力

図:開発ボード上で DeepSeek-R1 が回答を生成する端末出力

注:RV1126Bスペックより軽量AIモデルを行えますが、LLMの場合、少ないパラメータしか動作していません。ただし、LLMは少ないパラメータの場合、効果は良くないです。本章では、LLM動作例として説明します。

3. RKLLMアルゴリズムサンプル

サンプルの実装ファイルは deepseek-demo/src/llm_demo.cpp です。処理フローは以下の通りです。

RKLLM デモプログラムの処理フロー

図:RKLLM デモプログラムの処理フロー

具体的なコードは以下の通りです。

// Copyright (c) 2025 by Rockchip Electronics Co., Ltd. All
Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the
"License");
// you may not use this file except in compliance with the
License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
software
// distributed under the License is distributed on an "AS IS"
BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
// See the License for the specific language governing permissions
and
// limitations under the License.
#include <string.h>
#include <unistd.h>
#include <string>
#include "rkllm.h"
#include <fstream>
#include <iostream>
#include <csignal>
#include <vector>
using namespace std;
LLMHandle llmHandle = nullptr;
void exit_handler(int signal)
{
if (llmHandle != nullptr)
{
{
cout << "Program is exiting" << endl;
LLMHandle _tmp = llmHandle;
llmHandle = nullptr;
rkllm_destroy(_tmp);
}
}
exit(signal);
}
int callback(RKLLMResult *result, void *userdata, LLMCallState
state)
{
if (state == RKLLM_RUN_FINISH)
{
printf("\n");
} else if (state == RKLLM_RUN_ERROR) {
printf("\\run error\n");
} else if (state == RKLLM_RUN_NORMAL) {
/*
================================================================================================================
When the GET_LAST_HIDDEN_LAYER function is used, the callback
interface returns the memory pointer last_hidden_layer, the token count
num_tokens, and the hidden-layer size embd_size
These three parameters can be used to obtain the data in
last_hidden_layer
Note: The data must be obtained in the current callback. If
it is not obtained in time, this pointer will be released in the next
callback
===============================================================================================================*/
if (result->last_hidden_layer.embd_size != 0 &&
result->last_hidden_layer.num_tokens != 0) {
int data_size = result->last_hidden_layer.embd_size *
result->last_hidden_layer.num_tokens * sizeof(float);
printf("\ndata_size:%d",data_size);
std::ofstream outFile("last_hidden_layer.bin",
std::ios::binary);
if (outFile.is_open()) {
outFile.write(reinterpret_cast<const
char*>(result->last_hidden_layer.hidden_states), data_size);
outFile.close();
std::cout << "Data saved to output.bin
successfully!" << std::endl;
} else {
std::cerr << "Failed to open the file for
writing!" << std::endl;
}
}
printf("%s", result->text);
}
return 0;
}
int main(int argc, char **argv)
{
if (argc < 4) {
std::cerr << "Usage: " << argv[0] << "
model_path max_new_tokens max_context_len\n";
return 1;
}
signal(SIGINT, exit_handler);
printf("rkllm init start\n");
// Set parameters and initialize
RKLLMParam param = rkllm_createDefaultParam();
param.model_path = argv[1];
// Set sampling parameters
param.top_k = 1;
param.top_p = 0.95;
param.temperature = 0.8;
param.repeat_penalty = 1.1;
param.frequency_penalty = 0.0;
param.presence_penalty = 0.0;
param.max_new_tokens = std::atoi(argv[2]);
param.max_context_len = std::atoi(argv[3]);
param.skip_special_token = true;
param.extend_param.base_domain_id = 0;
param.extend_param.embed_flash = 1;
int ret = rkllm_init(&llmHandle, &param, callback);
if (ret == 0){
printf("rkllm init success\n");
} else {
printf("rkllm init failed\n");
exit_handler(-1);
}
vector&lt;string&gt; pre_input;
pre_input.push_back("There are some chickens and rabbits in a
cage. There are 14 heads and 38 legs in total. How many chickens and
rabbits are there respectively?");
pre_input.push_back("There are 28 children standing in a row.
Xuedou is the 10th child from the left. What position is Xuedou from the
right?");
cout << "\n**********************Enter one of the question
numbers below to get an answer. You can also enter a custom
question********************\n"
<< endl;
for (int i = 0; i < (int)pre_input.size(); i++)
{
cout << "[" << i << "] " <<
pre_input[i] << endl;
}
cout <<
"\n*************************************************************************\n"
<< endl;
RKLLMInput rkllm_input;
memset(&rkllm_input, 0, sizeof(RKLLMInput)); // Initialize
all contents to 0
// Initialize the infer parameter structure
RKLLMInferParam rkllm_infer_params;
memset(&rkllm_infer_params, 0, sizeof(RKLLMInferParam)); //
Initialize all contents to 0
// 1. Initialize and set LoRA parameters if LoRA is used
// RKLLMLoraAdapter lora_adapter;
// memset(&lora_adapter, 0, sizeof(RKLLMLoraAdapter));
// lora_adapter.lora_adapter_path =
"qwen0.5b_fp16_lora.rkllm";
// lora_adapter.lora_adapter_name = "test";
// lora_adapter.scale = 1.0;
// ret = rkllm_load_lora(llmHandle, &lora_adapter);
// if (ret != 0) {
// printf("\nload lora failed\n");
// }
// Load the second LoRA
// lora_adapter.lora_adapter_path =
"Qwen2-0.5B-Instruct-all-rank8-F16-LoRA.gguf";
// lora_adapter.lora_adapter_name = "knowledge_old";
// lora_adapter.scale = 1.0;
// ret = rkllm_load_lora(llmHandle, &lora_adapter);
// if (ret != 0) {
// printf("\nload lora failed\n");
// }
// RKLLMLoraParam lora_params;
// lora_params.lora_adapter_name = "test"; // Specify the LoRA
name used for inference
// rkllm_infer_params.lora_params = &lora_params;
// 2. Initialize and set Prompt Cache parameters if Prompt Cache
is used
// RKLLMPromptCacheParam prompt_cache_params;
// prompt_cache_params.save_prompt_cache = true;
// Whether to save prompt cache
// prompt_cache_params.prompt_cache_path = "./prompt_cache.bin";
// Specify the cache file path if prompt cache needs to be saved
// rkllm_infer_params.prompt_cache_params =
&prompt_cache_params;
// rkllm_load_prompt_cache(llmHandle, "./prompt_cache.bin"); //
Load the cached cache
rkllm_infer_params.mode = RKLLM_INFER_GENERATE;
// By default, the chat operates in single-turn mode (no context
retention)
// 0 means no history is retained, each query is independent
rkllm_infer_params.keep_history = 0;
//The model has a built-in chat template by default, which
defines how prompts are formatted
//for conversation. Users can modify this template using this
function to customize the
//system prompt, prefix, and postfix according to their needs.
// rkllm_set_chat_template(llmHandle, "", "<|User|>",
"<|Assistant|>");
while (true)
{
std::string input_str;
printf("\n");
printf("user: ");
std::getline(std::cin, input_str);
if (input_str == "exit")
{
break;
}
if (input_str == "clear")
{
ret = rkllm_clear_kv_cache(llmHandle, 1, nullptr,
nullptr);
if (ret != 0)
{
printf("clear kv cache failed!\n");
}
continue;
}
for (int i = 0; i < (int)pre_input.size(); i++)
{
if (input_str == to_string(i))
{
input_str = pre_input[i];
cout << input_str << endl;
}
}
rkllm_input.input_type = RKLLM_INPUT_PROMPT;
rkllm_input.role = "user";
rkllm_input.prompt_input = (char *)input_str.c_str();
printf("robot: ");
// To use normal inference, set rkllm_infer_mode to
RKLLM_INFER_GENERATE or leave the parameter unset
rkllm_run(llmHandle, &rkllm_input,
&rkllm_infer_params, NULL);
}
rkllm_destroy(llmHandle);
return 0;
}