基于RK3576开发板的RKLLM大模型部署教程

科技堆里的狠货 2025-05-17 阅读:7691 评论:0

1. RKLLM简介

1.1 RKLLM工具链介绍

1.1.1 RKLLM-Toolkit功能介绍

RKLLM-Toolkit 是为用户提供在计算机上进行大语言模型的量化、转换的开发套件。通过该 工具提供的 Python 接口可以便捷地完成以下功能:

(1)模型转换:支持将 Hugging Face 和 GGUF 格式的大语言模型(Large Language Model, LLM) 转换为 RKLLM 模型,目前支持的模型包括 LLaMA, Qwen, Qwen2, Phi-2, Phi-3, ChatGLM3, Gemma, Gemma2, InternLM2, MiniCPM 和 MiniCPM3,转换后的 RKLLM 模型能够在 Rockchip NPU 平台上加载使用。

(2)量化功能:支持将浮点模型量化为定点模型,目前支持的量化类型包括 w4a16 、wa4a16分组量化(支持的分组数为32,64,128)、w8a8、w8a8分组量化(支持的分组数为128,256,512)。

1.1.2 RKLLM Runtime功能介绍

RKLLM Runtime 主 要 负 责 加 载 RKLLM-Toolkit 转换得到的 RKLLM 模型,并在RK3576/RK3588 板端通过调用 NPU 驱动在 Rockchip NPU 上实现 RKLLM 模型的推理。在推理 RKLLM 模型时,用户可以自行定义 RKLLM 模型的推理参数设置,定义不同的文本生成方式, 并通过预先定义的回调函数不断获得模型的推理结果。

1.2 RKLLM开发流程介绍

(1)模型转换:

在这一阶段,用户提供的 Hugging Face 格式的大语言模型将会被转换为 RKLLM 格式,以便在 Rockchip NPU 平台上进行高效的推理。这一步骤包括:

获取原始模型:1、开源的 Hugging Face 格式的大语言模型;2、自行训练得到的大语 言模型,要求模型保存的结构与 Hugging Face 平台上的模型结构一致;3、GGUF 模型,目前 仅支持 q4_0 和 fp16 类型模型;

模型加载:通过 rkllm.load_huggingface()函数加载 huggingface 格式模型,通过rkllm.load_gguf()函数加载 GGUF 模型;

模型量化配置:通过 rkllm.build() 函数构建 RKLLM 模型,在构建过程中可选择是否进行模型量化来提高模型部署在硬件上的性能,以及选择不同的优化等级和量化类型。

模型导出:通过 rkllm.export_rkllm() 函数将 RKLLM 模型导出为一个.rkllm 格式文件, 用于后续的部署。

(2)板端部署运行:

这个阶段涵盖了模型的实际部署和运行。它通常包括以下步骤:

模型初始化:加载 RKLLM 模型到 Rockchip NPU 平台,进行相应的模型参数设置来定义所需的文本生成方式,并提前定义用于接受实时推理结果的回调函数,进行推理前准备。

模型推理:执行推理操作,将输入数据传递给模型并运行模型推理,用户可以通过预先定义的回调函数不断获取推理结果。

模型释放:在完成推理流程后,释放模型资源,以便其他任务继续使用 NPU 的计算资源。以上这两个步骤构成了完整的 RKLLM 开发流程,确保大语言模型能够成功转换、调试,并 最终在 Rockchip NPU 上实现高效部署。

1.3 资料下载

模型文件、模型转换与部署代码的百度网盘下载链接(比较大,可以选择来下载):https://pan.baidu.com/s/13CHxaF-Cyp4tYxXpksD8LA?pwd=1234 (提取码:1234 )

基于RK3576开发板的RKLLM大模型部署教程

1.4 开发环境搭建

1.4.1 RKLLM-Toolkit安装

本节主要说明如何通过 pip 方式来安装 RKLLM-Toolkit,用户可以参考以下的具体流程说明完成 RKLLM-Toolkit 工具链的安装。

工具安装包链接: https://pan.baidu.com/s/1y5ZN5sl4e3HJI5d9Imt4pg?pwd=1234(提取码: 1234)。

1.4.1.1 安装miniforge3工具

为防止系统对多个不同版本的 Python 环境的需求,建议使用 miniforge3 管理 Python 环境。 检查是否安装 miniforge3 和 conda 版本信息,若已安装则可省略此小节步骤。

下载 miniforge3 安装包:

wget -c https://mirrors.bfsu.edu.cn/github-release/conda-forge/miniforge/LatestRelease/Miniforge3-Linux-x86_64.sh

安装miniforge3:

chmod 777 Miniforge3-Linux-x86_64.sh
bash Miniforge3-Linux-x86_64.sh

1.4.1.2 创建 RKLLM-Toolkit Conda 环境

进入 Conda base 环境:

source ~/miniforge3/bin/activate

创建一个 Python3.8 版本(建议版本)名为 RKLLM-Toolkit 的 Conda 环境:

conda create -n RKLLM-Toolkit python=3.8

进入 RKLLM-Toolkit Conda 环境:

conda activate RKLLM-Toolkit

1.4.1.3 安装RKLLM-Toolkit

在 RKLLM-Toolkit Conda 环境下使用 pip 工具直接安装所提供的工具链 whl 包,在安装过程 中,安装工具会自动下载 RKLLM-Toolkit 工具所需要的相关依赖包。

pip3 install nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl
pip3 install torch-2.1.0-cp38-cp38-manylinux1_x86_64.whl
pip3 install rkllm_toolkit-1.1.4-cp38-cp38-linux_x86_64.whl

若在安装的过程中,某些文件安装很慢,可以登录python官网单独下载:

https://pypi.org/

执行以下命令没有报错,则安装成功。

1.5 AI模型转换

本章主要说明如何实现Hugging Face格式的大语言模型(Large Language Model, LLM)

如何转换为RKLLM模型,目前支持的模型包括 LLaMA, Qwen, Qwen2, Phi-2, Phi-3, ChatGLM3, Gemma, InternLM2 和 MiniCPM。

1.5.1 模型下载

本节提供两种大模型文件,Hugging face的原始模型和转换完成的NPU模型。

下载链接: https://pan.baidu.com/s/14BDYQBOMTTK7jtsSa8evHw?pwd=1234 (提取码: 1234)。

1.5.2 模型转换

下载完成后模型和脚本放到同一个目录:

基于RK3576开发板的RKLLM大模型部署教程

在RKLLM-Toolkit环境,执行以下指令进行模型转换:

基于RK3576开发板的RKLLM大模型部署教程

至此模型转换成功,生成Qwen.rkllm NPU化的大模型文件:

基于RK3576开发板的RKLLM大模型部署教程

test.py转换脚本如下所示, 用于转换Qwen-1_8B-Chat模型:

from rkllm.api import RKLLM
from datasets import load_dataset
from transformers import AutoTokenizer
from tqdm import tqdm
import torch
from torch import nn
import os
# os.environ['CUDA_VISIBLE_DEVICES']='1'

'''
https://huggingface.co/Qwen/Qwen-1_8B-Chat
从上面网址中下载Qwen模型
'''

modelpath = '/home/developer/RKLLM-Toolkit/Qwen-1_8B-Chat'
# modelpath = "./path/to/Qwen-1.8B-F16.gguf"
llm = RKLLM()

# Load model
# Use 'export CUDA_VISIBLE_DEVICES=2' to specify GPU device
# options ['cpu', 'cuda']
ret = llm.load_huggingface(model=modelpath, model_lora = None, device='cpu')
# ret = llm.load_gguf(model = modelpath)
if ret != 0:
    print('Load model failed!')
    exit(ret)

# Build model
dataset = "./data_quant.json"
# Json file format, please note to add prompt in the input,like this:
# [{"input":"Human: 你好!nAssistant: ", "target": "你好!我是人工智能助手KK!"},...]

qparams = None
# qparams = 'gdq.qparams' # Use extra_qparams
ret = llm.build(do_quantization=True, optimization_level=1, quantized_dtype='w4a16',
                quantized_algorithm='normal', target_platform='rk3576', num_npu_core=2, extra_qparams=qparams, dataset=None)

if ret != 0:
    print('Build model failed!')
    exit(ret)

# Evaluate Accuracy
def eval_wikitext(llm):
    seqlen = 512
    tokenizer = AutoTokenizer.from_pretrained(
        modelpath, trust_remote_code=True)
    # Dataset download link:
    # https://huggingface.co/datasets/Salesforce/wikitext/tree/main/wikitext-2-raw-v1
    testenc = load_dataset(
        "parquet", data_files='./wikitext/wikitext-2-raw-1/test-00000-of-00001.parquet', split='train')
    testenc = tokenizer("nn".join(
        testenc['text']), return_tensors="pt").input_ids
    nsamples = testenc.numel() // seqlen
    nlls = []
    for i in tqdm(range(nsamples), desc="eval_wikitext: "):
        batch = testenc[:, (i * seqlen): ((i + 1) * seqlen)]
        inputs = {"input_ids": batch}
        lm_logits = llm.get_logits(inputs)
        if lm_logits is None:
            print("get logits failed!")
            return
        shift_logits = lm_logits[:, :-1, :]
        shift_labels = batch[:, 1:].to(lm_logits.device)
        loss_fct = nn.CrossEntropyLoss().to(lm_logits.device)
        loss = loss_fct(
            shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
        neg_log_likelihood = loss.float() * seqlen
        nlls.append(neg_log_likelihood)
    ppl = torch.exp(torch.stack(nlls).sum() / (nsamples * seqlen))
    print(f'wikitext-2-raw-1-test ppl: {round(ppl.item(), 2)}')

# eval_wikitext(llm)


# Chat with model
messages = "<|im_start| >system You are a helpful assistant.<|im_end| ><|im_start| >user你好!n<|im_end| ><|im_start| >assistant"
kwargs = {"max_length": 128, "top_k": 1, "top_p": 0.8,
          "temperature": 0.8, "do_sample": True, "repetition_penalty": 1.1}
# print(llm.chat_model(messages, kwargs))


# Export rkllm model
ret = llm.export_rkllm("./Qwen_w4a16.rkllm")
if ret != 0:
    print('Export model failed!')
    exit(ret)

1.6 模型转换API说明

1.6.1 RKLLM 初始化

在这一部分,用户需要先初始化 RKLLM 对象,这是整个工作流的第一步。在示例代码中使用 RKLLM()构造函数来初始化 RKLLM 对象:

rkllm = RKLLM()

1.6.2 模型加载

在 RKLLM 初始化完成后,用户需要调用 rkllm.load_huggingface()函数来传入模型的具体路径,RKLLM-Toolkit 即可根据对应路径顺利加载 Hugging Face 或 GGUF 格式的大语言模型,从而顺利完成后续的转换、量化操作,具体的函数定义如下:

表 1 load_huggingface 函数接口说明

函数名 load_huggingface
描述 用于加载开源的 Hugging Face 格式的大语言模型。
参数 model: LLM 模型的文件路径,用于加载模型进行后续的转换、量化;

model_lora: lora 权重的文件路径,转换时 model 必须指向相应的 base model 路径;

返回值 0 表示模型加载正常;-1 表示模型加载失败;

示例代码如下:

ret = rkllm.load_huggingface(
   model = './huggingface_model_dir', 
   model_lora = './huggingface_lora_model_dir'
   )
if ret != 0:
   print('Load model failed!')

表 2 load_gguf 函数接口说明

函数名 load_gguf
描述 用于加载开源的 GGUF 格式的大语言模型,所支持的数值类型为 q4_0 和 fp16 两种,gguf 格式的 lora 模型也可以通过此接口加载转换为 rkllm 模型。
参数 model: GGUF 模型文件路径;
返回值 0 表示模型加载正常;-1 表示模型加载失败;

示例代码如下:

ret = rkllm.load_gguf(model = './model-Q4_0.gguf')
if ret != 0:
   print('Load model failed!')

1.6.3 RKLLM 模型的量化构建

用户在通过 rkllm.load_huggingface()函数完成原始模型的加载后,下一步就是通过 rkllm.build()函数实现对 RKLLM 模型的构建。构建模型时,用户可以选择是否进行量化,量化有助于减小模型的大小和提高在 Rockchip NPU 上的推理性能。rkllm.build()函数的具体定义如下:

表 3 build 函数接口说明

函数名 build
描述 用于构建得到 RKLLM 模型,并在转换过程中定义具体的量化操作。
参数 do_quantization: 该参数控制是否对模型进行量化操作,建议设置为 True;

optimization_level: 该参数用于设置是否进行量化精度优化,可选择的设置为{0,1},0 表示不做任何优化,1 表示进行精度优化,精度优化可能造成模型推理性能下降;

quantized_dtype: 该参数用于设置量化的具体类型,目前支持的量化类型包括“ w4a16 ” , “ w4a16_g32 ” , “ w4a16_g64 ” , “ w4a16_g128 ” , “ w8a8 ” ,“w8a8_g128”,“w8a8_g256”,“w8a8_g512”,“w4a16”表示对权重进行 4bit 量化而对激活值不进行量化;“w4a16_g64”表示对权重进行 4bit 分组量化(groupsize=64)而对激活值不进行量化;“w8a8”表示对权重和激活值均进行 8bit 量化;“w8a8_g128”表示对权重和激活值均进行 8bit 分组量化(group size=128);目前rk3576 平台支持“w4a16”,“w4a16_g32”,“w4a16_g64”,“w4a16_g128”和“w8a8”五种量化类型,rk3588 支持“w8a8”,“w8a8_g128”,“w8a8_g256”,“ w8a8_g512 ” 四种量化类型; GGUF 模型的 q4_0 对应的量化类型为“w4a16_g32”;注意group size 应能被线性层的输出维度整除,否则会量化失败!

quantized_algorithm: 量化精度优化算法, 可选择的设置包括“normal”或“gdq”,所有量化类型均可选择 normal,而 gdq 算法只支持 w4a16 及 w4a16 分组量化,且 gdq

对算力要求高,必须使用 GPU 进行加速运算;

num_npu_core: 模型推理需要使用的 npu 核心数,“rk3576”可选项为[1,2],“rk3588”可选项为[1,2,3];

extra_qparams: 使用 gdq 算法会生成 gdq.qparams 量化权重缓存文件,将此参数设置为 gdq.qparams 路径,可以重复进行模型导出;

dataset: 用于量化校正数据集,格式为 json,内容示例如下,input 为问题,需要加上提示词,target 为回答,多条数据以{}字典形式保存在列表中:[{"input":"今天天气怎么样?","target":"今天天气晴。"},....]

hybrid_rate: 分组和不分组混合量化比率(∈[0,1)),当量化类型为 w4a16/w8a8 时,会按比率分别混合 w4a16 分组/w8a8 分组类型来提高精度,当量化类型为 w4a16分组/w8a8 分组类型时,会按比率分别混合 w4a16/w8a8 类型来提高推理性能,当hybrid_rate 值为 0 时,不进行混合量化;

target_platform: 模型运行的硬件平台, 可选择的设置包括“rk3576”或“rk3588”;

返回值 0 表示模型转换、量化正常;-1 表示模型转换失败;

示例代码如下:

ret = rkllm.build(
       do_quantization=True,
       optimization_level=1,
       quantized_dtype='w8a8',
       quantized_algorithm="normal",
       num_npu_core=3,
       extra_qparams=None,
       dataset="quant_data.json",
       hybrid_rate=0,
       target_platform='rk3588')
if ret != 0:
   print('Build model failed!')

1.6.4 导出 RKLLM 模型

用户在通过 rkllm.build()函数构建了 RKLLM 模型后,可以通过 rkllm.export_rkllm()函数将RKNN 模型保存为一个.rkllm 文件,以便后续模型的部署。rkllm.export_rkllm()函数的具体参数定义如下:

表 4 export_rkllm 函数接口说明

函数名 export_rkllm
描述 用于保存转换、量化后的 RKLLM 模型,用于后续的推理调用。
参数 export_path: 导出 RKLLM 模型文件的保存路径,lora 模型会自动保存为带_lora 后缀的 rkllm 模型;
返回值 0 表示模型成功导出保存;-1 表示模型导出失败;

示例代码如下:

ret = rkllm.export_rkllm(export_path = './model.rkllm')
if ret != 0:
   print('Export model failed!')

1.6.5 仿真精度评估

用户在通过 rkllm.build()函数构建了 RKLLM 模型后,可以通过 rkllm.get_logits()函数在 PC端进行仿真精度评估,rkllm.get_logits()函数的具体参数定义如下:

函数名 get_logits
函数名 用于 PC 端仿真精度评估。
参数 inputs: 仿真输入格式与 huggingface 模型推理一样,示例如下:{“input_ids":"","top_k":1,...}
返回值 返回模型推理出的 logits 值;

使用此函数进行 wikitext 数据集 ppl 测试示例代码如下:

ef eval_wikitext(llm):
  seqlen = 512
  tokenizer = AutoTokenizer.from_pretrained(
     modelpath,
     trust_remote_code=True
     )
  #Dataset download link: 
  #https://huggingface.co/datasets/Salesforce/wikitext/tree/main/wiki
text-2-raw-v1
   testenc = load_dataset("parquet", data_files='./wikitext/wikitext-
2-raw-1/test-00000-of-00001.parquet', split='train')
   testenc = tokenizer(
      "nn".join(testenc['text']), 
      return_tensors="pt").input_ids
 nsamples = testenc.numel() // seqlen
 nlls = []
 for i in tqdm(range(nsamples), desc="eval_wikitext: "):
    batch = testenc[:, (i * seqlen): ((i + 1) * seqlen)]
    inputs = {"input_ids": batch}
    lm_logits = llm.get_logits(inputs)
    if lm_logits is None:
       print("get logits failed!")
       return 
    shift_logits = lm_logits[:, :-1, :]
    shift_labels = batch[:, 1:].to(lm_logits.device)
    loss_fct = nn.CrossEntropyLoss().to(lm_logits.device)
    loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), 
shift_labels.view(-1))
    neg_log_likelihood = loss.float() * seqlen
    nlls.append(neg_log_likelihood)
   ppl = torch.exp(torch.stack(nlls).sum() / (nsamples * seqlen))
   print(f'wikitext-2-raw-1-test ppl: {round(ppl.item(), 2)}')

1.6.6 仿真模型推理

用户在通过 rkllm.build()函数构建了 RKLLM 模型后,可以通过 rkllm.chat_model()函数在 PC端进行仿真推理,rkllm.chat_model()函数的具体参数定义如下:

函数名 chat_model
描述 用于 PC 端仿真模型推理。
参数 messages: 文本输入,需要加上相应提示词

args: 推理配置参数,比如 topk 等采样策略参数

返回值 返回模型推理结果;

示例代码如下:

args ={
   "max_length":128,
   "top_k":1,
   "temperature":0.8,
   "do_sample":True,
   "repetition_penalty":1.1
   }
mesg = "Human: 今天天气怎么样?nAssistant:"
print(llm.chat_model(mesg, args))

以上的这些操作涵盖了 RKLLM-Toolkit 模型转换、量化的全部步骤,根据不同的需求和应用场景,用户可以选择不同的配置选项和量化方式进行自定义设置,方便后续进行部署。

1.7 AI模型部署

本章主要说明RKLLM格式的通义千问NPU大模型如何运行在EASY-EAI-Orin-Nano硬件上。

基于RK3576开发板的RKLLM大模型部署教程

1.7.1 快速上手

1.7.1.1 源码下载以及例程编译

本节提供转换成功的通义千问大模型文Qwen_w4a16.rkllm及对应的C/C++程序部署代码。

基于RK3576开发板的RKLLM大模型部署教程

下载链接:https://pan.baidu.com/s/19GZ_UjsA-IjA9zf10ZZ93w?pwd=1234(提取码: 1234)。

然后把例程【复制粘贴】到nfs挂载目录中。(不清楚目录如何构建的,可以参考《入门指南/开发环境准备/nfs服务搭建与挂载》)。特别注意:源码目录和模型最好cp到板子上,如/userdata,否则在nfs目录执行大模型会导致模型初始化过慢。

基于RK3576开发板的RKLLM大模型部署教程

进入到开发板对应的例程目录执行编译操作,具体命令如下所示:

cd /userdata/rkllm-demo
./build.sh
基于RK3576开发板的RKLLM大模型部署教程

1.7.1.2 例程运行及效果

进入例程的rkllm-demo/rkllm-demo_release目录,执行下方命令,运行示例程序:

cd rkllm-demo_release/
ulimit -HSn 102400 
./rkllm-demo Qwen_w4a16.rkllm 256 512
基于RK3576开发板的RKLLM大模型部署教程

至此可以进行对话测试了,试着输入“如何制作PCB板?”。回答如下所示:

基于RK3576开发板的RKLLM大模型部署教程

1.7.2 RKLLM算法例程

例程目录为rkllm-demo/src/main.cpp,操作流程如下。

基于RK3576开发板的RKLLM大模型部署教程

具体代码如下所示:

#include < string.h >
#include < unistd.h >
#include < string >
#include "rkllm.h"
#include < fstream >
#include < iostream >
#include < csignal >
#include < vector >

#define PROMPT_TEXT_PREFIX "<|im_start| >system You are a helpful assistant. <|im_end| > <|im_start| >user"
#define PROMPT_TEXT_POSTFIX "<|im_end| ><|im_start| >assistant"


using namespace std;
LLMHandle llmHandle = nullptr;

void exit_handler(int signal)
{
    if (llmHandle != nullptr)
    {
        {
            cout < < "程序即将退出" < < endl;
            LLMHandle _tmp = llmHandle;
            llmHandle = nullptr;
            rkllm_destroy(_tmp);
        }
    }
    exit(signal);
}

void callback(RKLLMResult *result, void *userdata, LLMCallState state)
{
    if (state == RKLLM_RUN_FINISH)
    {
        printf("n");
    } else if (state == RKLLM_RUN_ERROR) {
        printf("\run errorn");
    } else if (state == RKLLM_RUN_GET_LAST_HIDDEN_LAYER) {
        /* ================================================================================================================
        若使用GET_LAST_HIDDEN_LAYER功能,callback接口会回传内存指针:last_hidden_layer,token数量:num_tokens与隐藏层大小:embd_size
        通过这三个参数可以取得last_hidden_layer中的数据
        注:需要在当前callback中获取,若未及时获取,下一次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;
            }
        }
    } else if (state == RKLLM_RUN_NORMAL) {
        printf("%s", result- >text);
    }
}

int main(int argc, char **argv)
{
    if (argc < 4) {
        std::cerr < < "Usage: " < < argv[0] < < " model_path max_new_tokens max_context_lenn";
        return 1;
    }

    signal(SIGINT, exit_handler);
    printf("rkllm init startn");

    //设置参数及初始化
    RKLLMParam param = rkllm_createDefaultParam();
    param.model_path = argv[1];

    //设置采样参数
    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;

    int ret = rkllm_init(&llmHandle, ¶m, callback);
    if (ret == 0){
        printf("rkllm init successn");
    } else {
        printf("rkllm init failedn");
        exit_handler(-1);
    }

    vector< string > pre_input;
    pre_input.push_back("把下面的现代文翻译成文言文: 到了春风和煦,阳光明媚的时候,湖面平静,没有惊涛骇浪,天色湖光相连,一片碧绿,广阔无际;沙洲上的鸥鸟,时而飞翔,时而停歇,美丽的鱼游来游去,岸上与小洲上的花草,青翠欲滴。");
    pre_input.push_back("以咏梅为题目,帮我写一首古诗,要求包含梅花、白雪等元素。");
    pre_input.push_back("上联: 江边惯看千帆过");
    pre_input.push_back("把这句话翻译成中文: Knowledge can be acquired from many sources. These include books, teachers and practical experience, and each has its own advantages. The knowledge we gain from books and formal education enables us to learn about things that we have no opportunity to experience in daily life. We can also develop our analytical skills and learn how to view and interpret the world around us in different ways. Furthermore, we can learn from the past by reading books. In this way, we won't repeat the mistakes of others and can build on their achievements.");
    pre_input.push_back("把这句话翻译成英文: RK3588是新一代高端处理器,具有高算力、低功耗、超强多媒体、丰富数据接口等特点");
    cout < < "n**********************可输入以下问题对应序号获取回答/或自定义输入********************n"
         < < endl;
    for (int i = 0; i < (int)pre_input.size(); i++)
    {
        cout < < "[" < < i < < "] " < < pre_input[i] < < endl;
    }
    cout < < "n*************************************************************************n"
         < < endl;

    string text;
    RKLLMInput rkllm_input;

    // 初始化 infer 参数结构体
    RKLLMInferParam rkllm_infer_params;
    memset(&rkllm_infer_params, 0, sizeof(RKLLMInferParam));  // 将所有内容初始化为 0

    // 1. 初始化并设置 LoRA 参数(如果需要使用 LoRA)
    // 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 failedn");
    // }

    // 加载第二个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 failedn");
    // }

    // RKLLMLoraParam lora_params;
    // lora_params.lora_adapter_name = "test";  // 指定用于推理的 lora 名称
    // rkllm_infer_params.lora_params = &lora_params;

    // 2. 初始化并设置 Prompt Cache 参数(如果需要使用 prompt cache)
    // RKLLMPromptCacheParam prompt_cache_params;
    // prompt_cache_params.save_prompt_cache = true;                  // 是否保存 prompt cache
    // prompt_cache_params.prompt_cache_path = "./prompt_cache.bin";  // 若需要保存prompt cache, 指定 cache 文件路径
    // rkllm_infer_params.prompt_cache_params = &prompt_cache_params;
    
    // rkllm_load_prompt_cache(llmHandle, "./prompt_cache.bin"); // 加载缓存的cache

    rkllm_infer_params.mode = RKLLM_INFER_GENERATE;

    while (true)
    {
        std::string input_str;
        printf("n");
        printf("user: ");
        std::getline(std::cin, input_str);
        if (input_str == "exit")
        {
            break;
        }
        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;
            }
        }
        text = PROMPT_TEXT_PREFIX + input_str + PROMPT_TEXT_POSTFIX;
        //text = input_str;
        rkllm_input.input_type = RKLLM_INPUT_PROMPT;
        rkllm_input.prompt_input = (char *)text.c_str();
        printf("robot: ");

        // 若要使用普通推理功能,则配置rkllm_infer_mode为RKLLM_INFER_GENERATE或不配置参数
        rkllm_run(llmHandle, &rkllm_input, &rkllm_infer_params, NULL);
    }
    rkllm_destroy(llmHandle);

    return 0;
}

1.8 模型部署API说明

1.8.1 回调函数定义

回调函数是用于接收模型实时输出的结果。在初始化 RKLLM 时回调函数会被绑定,在模型推理过程中不断将结果输出至回调函数中,并且每次回调只返回一个 token。

示例代码如下,该回调函数将输出结果实时地打印输出到终端中:

void callback(RKLLMResult* result, void* userdata, LLMCallState state)
{
   if(state == LLM_RUN_NORMAL){
      printf("%s", result->text);
      for (int i=0; i< result- >num; i++) {
          printf("token_id: %d logprob: %f", result->tokens[i].id, 
          result->tokens[i].logprob);
      }
   }
   if (state == LLM_RUN_FINISH) {
      printf("finishn");
   } else if (state == LLM_RUN_ERROR){
      printf("run errorn");
   }
}

(1)LLMCallState 是一个状态标志,其具体定义如下:

表 1 LLMCallState 状态标志说明

枚举定义 LLMCallState
描述 用于表示当前 RKLLM 的运行状态。
枚举值 0, LLM_RUN_NORMAL, 表示 RKLLM 模型当前正在推理中;

1, LLM_RUN_FINISH, 表示 RKLLM 模型已完成当前输入的全部推理;

2, LLM_RUN_WAITING, 表示当前 RKLLM 解码出的字符不是完整 UTF8 编码,需等待与下一次解码拼接;

3, LLM_RUN_ERROR, 表示 RKLLM 模型推理出现错误;

用户在回调函数的设计过程中,可以根据 LLMCallState 的不同状态设置不同的后处理行为;

(2)RKLLMResult 是返回值结构体,其具体定义如下:

表 2 RKLLMResult 返回值结构体说明

结构体定义 RKLLMResult
描述 用于返回当前推理生成结果。
字段 0, text, 表示当前推理生成的文本内容;

1, token_id, 表示当前推理生成的 token id;

1.8.2 参数结构体 RKLLMParam 定义

结构体 RKLLMParam 用于描述、定义 RKLLM 的详细信息,具体的定义如下:

表 2 RKLLMParam 结构体参数说明

结构体定义 RKLLMParam
描述 用于定义 RKLLM 模型的各项细节参数。
字段 const char* model_path: 模型文件的存放路径;

int32_t max_context_len: 设置推理时的最大上下文长度;

int32_t max_new_tokens: 用于设置模型推理时生成 token 的数量上限;

int32_t top_k: top-k 采样是一种文本生成方法,它仅从模型预测概率最高的 k个 token 中选择下一个 token。该方法有助于降低生成低概率或无意义 token 的风险。更高的值(如 100)将考虑更多的 token 选择,导致文本更加多样化;而更低的值(如 10)将聚焦于最可能的 token,生成更加保守的文本。默认值为40;

float top_p: top-p 采样,也被称为核心采样,是另一种文本生成方法,从累计概率至少为 p 的一组 token 中选择下一个 token。这种方法通过考虑 token 的概率和采样的 token 数量在多样性和质量之间提供平衡。更高的值(如 0.95)使得生成的文本更加多样化;而更低的值(如 0.5)将生成更加保守的文本。默认值为 0.9;

float temperature: 控制生成文本随机性的超参数,它通过调整模型输出token的概率分布来发挥作用;更高的温度(如 1.5)会使输出更加随机和创造性,当温度较高时,模型在选择下一个 token 时会考虑更多可能性较低的选项,从而产生更多样和意想不到的输出;更低的温度(例 0.5)会使输出更加集中、保守,较低的温度意味着模型在生成文本时更倾向于选择概率高的 token,从而导致更一致、更可预测的输出;温度为 0 的极端情况下,模型总是选择最有可能的下一个 token,这会导致每次运行时输出完全相同;为了确保随机性和确定性之间的平衡,使输出既不过于单一和可预测,也不过于随机和杂乱,默认值为 0.8;

float repeat_penalty: 控制生成文本中 token 序列重复的情况,帮助防止模型生成重复或单调的文本。更高的值(例如 1.5)将更强烈地惩罚重复,而较低的值(例如 0.9)则更为宽容。默认值为 1.1;

float frequency_penalty: 单词/短语重复度惩罚因子,减少总体上使用频率较高的单词/短语的概率,增加使用频率较低的单词/短语的可能性,这可能会使生成的文本更加多样化,但也可能导致生成的文本难以理解或不符合预期。设置范围为[-2.0, 2.0],默认为 0;

int32_t mirostat: 在文本生成过程中主动维持生成文本的质量在期望的范围内的算法,它旨在在连贯性和多样性之间找到平衡,避免因过度重复(无聊陷阱)或不连贯(混乱陷阱)导致的低质量输出;取值空间为{0, 1, 2}, 0 表示不启动该算法,1 表示使用 mirostat 算法,2 则表示使用 mirostat2.0 算法;

float mirostat_tau: 选项设置 mirostat 的目标熵,代表生成文本的期望困惑度。调整目标熵可以控制生成文本中连贯性与多样性的平衡。较低的值将导致文本更加集中和连贯,而较高的值将导致文本更加多样化,可能连贯性较差。默认值是 5.0;

float mirostat_eta: 选项设置 mirostat 的学习率,学习率影响算法对生成文本反馈的响应速度。较低的学习率将导致调整速度较慢,而较高的学习率将使算法更加灵敏。默认值是 0.1;

bool skip_special_token: 是否跳过特殊 token 不输出,例如结束符号等;

bool is_async: 是否使用异步模式;

const char* img_start: 选项设置多模态输入图像编码的起始标志符,在多模态输入模式下需要配置;

const char* img_end: 选项设置多模态输入图像编码的终止标志符,在多模态输入模式下需要配置;

const char* img_content: 选项设置多模态输入图像编码的内容标志符,在多模态输入模式下需要配置;

在实际的代码构建中,RKLLMParam 需要调用 rkllm_createDefaultParam()函数来初始化定义,并根据需求设置相应的模型参数。示例代码如下:

RKLLMParam param = rkllm_createDefaultParam();
param.model_path = "model.rkllm";
param.top_k = 1;
param.max_new_tokens = 256;
param.max_context_len = 512;

1.8.3 输入结构体定义

为适应不同的输入数据,定义了 RKLLMInput 输入结构体,目前可接受文本、图片和文本、Token id 以及编码向量四种形式的输入,具体的定义如下:

表 3 RKLLMInput 结构体参数说明

结构体定义 RKLLMInput
描述 用于接收不同形式的输入数据。
字段 RKLLMInputType input_type: 输入模式;

union: 用于存储不同的输入数据类型,具体包含以下几种形式:

- const char* prompt_input: 文本提示输入,用于传递自然语言文本;

- RKLLMEmbedInput embed_input: 嵌入向量输入,表示已处理的特征向量;

- RKLLMTokenInput token_input: Token 输入,用于传递 Token 序列;

- RKLLMMultiModelInput multimodal_input: 多模态输入,可传递多模态数据,如图片和文本的联合输入。

表 4 RKLLMInputType 输入类型说明

枚举定义 RKLLMInputType
描述 用于表示输入数据类型。
枚举值 0, RKLLM_INPUT_PROMPT, 表示输入数据是纯文本;

1, RKLLM_INPUT_TOKEN, 表示输入数据是 Token id;

2, RKLLM_INPUT_EMBED, 表示输入数据是编码向量;

3, RKLLM_INPUT_MULTIMODAL, 表示输入数据是图片和文本;

当输入数据是纯文本时,使用 input_data 直接输入;当输入数据是 Token id、编码向量以及图片 和 文 本 时 , RKLLMInput 需 要 搭 配 RKLLMTokenInput, RKLLMEmbedInput 以 及RKLLMMultiModelInput 三个输入结构体使用,具体的介绍如下:

(1)RKLLMTokenInput 是接收 Token id 的输入结构体,具体的定义如下:

表 5 RKLLMTokenInput 结构体参数说明

结构体定义 RKLLMTokenInput
描述 用于接收 Token id 数据。
字段 int32_t* input_ids: 输入 token ids 的内存指针;

size_t n_tokens: 输入数据的 token 数量;

(2)RKLLMEmbedInput 是接收编码向量的输入结构体,具体的定义如下:

表 6 RKLLMEmbedInput 结构体参数说明

结构体定义 RKLLMEmbedInput
描述 用于接收 Embedding 数据。
字段 float* embed: 输入 token embedding 的内存指针;

size_t n_tokens: 输入数据的 token 数量;

(3)RKLLMMultiModelInput 是接收图片和文本的输入结构体,具体的定义如下:

表 7 RKLLMMultiModelInput 结构体参数说明

结构体定义 RKLLMMultiModelInput
描述 用于接收图片和文本多模态数据。
字段 char* prompt: 输入文本的内存指针;

float* image_embed: 输入图片 embedding 的内存指针;

size_t n_image_tokens: 输入图片 embedding 的 token 数量;

纯文本输入示例代码如下:

// 提前定义 prompt 前后的文本预设值
#define PROMPT_TEXT_PREFIX "<|im_start| >system You are a helpful 
assistant. <|im_end| > <|im_start| >user"
#define PROMPT_TEXT_POSTFIX "<|im_end| ><|im_start| >assistant"

// 定义输入的 prompt 并完成前后 prompt 的拼接
string input_str = "把这句话翻译成英文:RK3588 是新一代高端处理器,具有高算力、
低功耗、超强多媒体、丰富数据接口等特点";
input_str = PROMPT_TEXT_PREFIX + input_str + PROMPT_TEXT_POSTFIX;

RKLLMInput rkllm_input;
rkllm_input.input_data = (char*)input_str.c_str();
rkllm_input.input_type = RKLLM_INPUT_PROMPT;

// 初始化 infer 参数结构体
RKLLMInferParam rkllm_infer_params;
memset(&rkllm_infer_params, 0, sizeof(RKLLMInferParam)); // 将所有内容初
始化为 0
rkllm_infer_params.mode = RKLLM_INFER_GENERATE;

图片和文本多模态输入示例代码如下,注意多模态输入的 prompt 中需要加入占位符用于标示图像编码插入的位置:

// 提前定义 prompt 前后的文本预设值
#define PROMPT_TEXT_PREFIX "<|im_start| >system You are a helpful 
assistant. <|im_end| > <|im_start| >user"
#define PROMPT_TEXT_POSTFIX "<|im_end| ><|im_start| >assistant"

RKLLMInput rkllm_input;

// 定义输入的 prompt 并完成前后 prompt 的拼接
string input_str = "< image >Please describe the image shortly.";
input_str = PROMPT_TEXT_PREFIX + input_str + PROMPT_TEXT_POSTFIX;
rkllm_input.multimodal_input.prompt = (char*)input_str.c_str();

rkllm_input.multimodal_input.n_image_tokens = 256;
int rkllm_input_len = multimodal_input.n_image_tokens * 3072;
rkllm_input.multimodal_input.image_embed = (float 
*)malloc(rkllm_input_len * sizeof(float));
FILE *file;
file = fopen("models/image_embed.bin", "rb");
fread(rkllm_input.multimodal_input.image_embed, sizeof(float), 
rkllm_input_len, file);
fclose(file);

rkllm_input.input_type = RKLLM_INPUT_MULTIMODAL;

// 初始化 infer 参数结构体
RKLLMInferParam rkllm_infer_params;
memset(&rkllm_infer_params, 0, sizeof(RKLLMInferParam)); // 将所有内容初
始化为 0
rkllm_infer_params.mode = RKLLM_INFER_GENERATE;

RKLLM 支持不同的推理模式,定义了 RKLLMInferParam 结构体,目前可支持在推理过程与预加载的 LoRA 模型联合推理,或保存 Prompt Cache 用于后续推理加速,具体的定义如下:

表 8 RKLLMInferParam 结构体参数说明

结构体定义 RKLLMInferParam
描述 用于定义不同的推理模式。
字段 RKLLMInferMode mode: 推理模式,当前仅支持 RKLLM_INFER_GENERATE 模式;

RKLLMLoraParam* lora_params: 推理时使用的 LoRA 的参数配置,用于在加载多个 LoRA 时选择需要推理的 LoRA,若无需加载 LoRA 则设为 NULL 即可;

RKLLMPromptCacheParam* prompt_cache_params: 推理时使用Prompt Cache的参数配置,若无需生成 Prompt Cache 则设为 NULL 即可;

表 9 RKLLMLoraParam 结构体参数说明

结构体定义 RKLLMLoraParam
描述 用于定义推理时使用 LoRA 的参数;
字段 const char* lora_adapter_name: 推理时使用的 LoRA 名称

表 10 RKLLMInferParam 结构体参数说明

结构体定义 RKLLMPromptCacheParam
描述 用于定义推理时使用 Prompt Cache 的参数;
字段 int save_prompt_cache: 是否在推理时保存 Prompt Cache, 1 为需要, 0 为不需要;

const char* prompt_cache_path: Prompt Cache 保存路径, 若未设置则默认保存到"./prompt_cache.bin"中;

使用 InferRaram 的示例如下:

// 1. 初始化并设置 LoRA 参数(如果需要使用 LoRA)
RKLLMLoraParam lora_params;
lora_params.lora_adapter_name = "test"; // 指定用于推理的 lora 名称
// 2. 初始化并设置 Prompt Cache 参数(如果需要使用 prompt cache)
RKLLMPromptCacheParam prompt_cache_params;
prompt_cache_params.save_prompt_cache = true; // 是否保存 prompt cache
prompt_cache_params.prompt_cache_path = "./prompt_cache.bin"; // 若需要
保存 prompt cache, 指定 cache 文件路径
rkllm_infer_params.mode = RKLLM_INFER_GENERATE;
rkllm_infer_params.lora_params = &lora_params;
rkllm_infer_params.prompt_cache_params = &prompt_cache_params;

1.8.4 初始化模型

在进行模型的初始化之前,需要提前定义 LLMHandle 句柄,该句柄用于模型的初始化、推理和资源释放过程。注意,正确的模型推理流程需要统一这 3 个流程中的 LLMHandle 句柄对象。在模型推理前,用户需要通过 rkllm_init()函数完成模型的初始化,具体函数的定义如下:

表 11 rkllm_init 函数接口说明

函数名 rkllm_init
描述 用于初始化 RKLLM 模型的具体参数及相关推理设置。
参数 LLMHandle* handle: 将模型注册到相应句柄中,用于后续推理、释放调用;

RKLLMParam* param: 模型定义的参数结构体;

LLMResultCallback callback: 用于接受处理模型实时输出的回调函数;

返回值 0 表示初始化流程正常;-1 表示初始化失败;

示例代码如下:

LLMHandle llmHandle = nullptr;
rkllm_init(&llmHandle, ¶m, callback);

1.8.5 模型推理

用户在完成 RKLLM 模型的初始化流程后,即可通过 rkllm_run()函数进行模型推理,并可以通过初始化时预先定义的回调函数对实时推理结果进行处理;rkllm_run()的具体函数定义如下:

表 12 rkllm_run 函数接口说明

函数名 rkllm_run
描述 调用完成初始化的 RKLLM 模型进行结果推理;
参数 LLMHandle handle: 模型初始化注册的目标句柄;

RKLLMInput* rkllm_input: 模型推理的输入数据;

RKLLMInferParam* rkllm_infer_params: 模型推理过程中的参数传递;

void* userdata: 用户自定义的函数指针,默认设置为 NULL 即可;

返回值 0 表示模型推理正常运行;-1 表示调用模型推理失败;

模型推理的示例代码如下:

#define PROMPT_TEXT_PREFIX "<|im_start| >system You are a helpful 
assistant. <|im_end| > <|im_start| >user"
#define PROMPT_TEXT_POSTFIX "<|im_end| ><|im_start| >assistant"

string input_str = "把这句话翻译成英文:RK3588 是新一代高端处理器,具有高算力、
低功耗、超强多媒体、丰富数据接口等特点";
input_str = PROMPT_TEXT_PREFIX + input_str + PROMPT_TEXT_POSTFIX;

// 初始化 infer 参数结构体
RKLLMInferParam rkllm_infer_params;
memset(&rkllm_infer_params, 0, sizeof(RKLLMInferParam));
rkllm_infer_params.mode = RKLLM_INFER_GENERATE;
// 1. 初始化并设置 LoRA 参数(如果需要使用 LoRA)
RKLLMLoraParam lora_params;
lora_params.lora_adapter_name = "test"; // 指定用于推理的 lora 名称
// 2. 初始化并设置 Prompt Cache 参数(如果需要使用 prompt cache)
RKLLMPromptCacheParam prompt_cache_params;
prompt_cache_params.save_prompt_cache = true; // 是否保存 prompt cache
prompt_cache_params.prompt_cache_path = "./prompt_cache.bin";

rkllm_infer_params.mode = RKLLM_INFER_GENERATE;
// rkllm_infer_params.lora_params = &lora_params;
// rkllm_infer_params.prompt_cache_params = &prompt_cache_params;
rkllm_infer_params.lora_params = NULL;
rkllm_infer_params.prompt_cache_params = NULL;

RKLLMInput rkllm_input;
rkllm_input.input_type = RKLLM_INPUT_PROMPT;
rkllm_input.prompt_input = (char *)text.c_str();

rkllm_run(llmHandle, &rkllm_input, &rkllm_infer_params, NULL);

1.8.6 模型中断

在进行模型推理时,用户可以调用 rkllm_abort()函数中断推理进程,具体的函数定义如下:

表 13 rkllm_ abort 函数接口说明

函数名 rkllm_ abort
描述 用于中断 RKLLM 模型推理进程。
参数 LLMHandle handle: 模型初始化注册的目标句柄;
返回值 0 表示 RKLLM 模型中断成功;-1 表示模型中断失败;

示例代码如下:

// 其中 llmHandle 为模型初始化时传入的句柄
rkllm_abort(llmHandle);

1.8.7 释放模型资源

在完成全部的模型推理调用后,用户需要调用 rkllm_destroy()函数进行 RKLLM 模型的销毁,并释放所申请的 CPU、NPU 计算资源,以供其他进程、模型的调用。具体的函数定义如下:

表 14 rkllm_ destroy 函数接口说明

函数名 rkllm_ destroy
描述 用于销毁 RKLLM 模型并释放所有计算资源。
参数 LLMHandle handle: 模型初始化注册的目标句柄;
返回值 0 表示 RKLLM 模型正常销毁、释放;-1 表示模型释放失败;

示例代码如下:

// 其中 llmHandle 为模型初始化时传入的句柄
rkllm_destroy(llmHandle);

1.8.8 LoRA 模型加载

RKLLM 支持在推理基础模型的同时推理 LoRA 模型,可以在调用 rkllm_run 接口前通过rkllm_load_lora 接口加载 LoRA 模型。RKLLM 支持加载多个 LoRA 模型,每调用一次rkllm_load_lora 可加载一个 LoRA 模型。具体的函数定义如下:

表 15 rkllm_load_lora 函数接口说明

函数名 rkllm_load_lora
描述 用于加载 LoRA 模型。
参数 LLMHandle handle: 模型初始化注册的目标句柄;

RKLLMLoraAdapter* lora_adapter: 加载 LoRA 模型时的参数配置;

返回值 0 表示 LoRA 模型正常加载;-1 表示模型加载失败;

表 16 RKLLMLoraAdapter 结构体参数说明

结构体定义 RKLLMLoraAdapter
描述 用于配置加载 LoRA 时的参数。
字段 const char* lora_adapter_path: 待加载 LoRA 模型的路径;

const char* lora_adapter_name: 待加载 LoRA 模型的名称, 由用户自定义, 用于后续推理时选择指定 LoRA;

float scale: LoRA 模型在推理过程中对基础模型参数进行调整的幅度;

加载 LoRA 的示例代码如下:

RKLLMLoraAdapter lora_adapter;
memset(&lora_adapter, 0, sizeof(RKLLMLoraAdapter));
lora_adapter.lora_adapter_path = "lora.rkllm";
lora_adapter.lora_adapter_name = "lora_name";
lora_adapter.scale = 1.0;
ret = rkllm_load_lora(llmHandle, &lora_adapter);
if (ret != 0) {
   printf("nload lora failedn");
}

1.8.9 Prompt Cache 加载

RKLLM 支持加载预生成的 Prompt Cache 文件,以此加速模型 Prefill 阶段的推理。具体的函数定义如下:

表 17 rkllm_load_prompt_cache 函数接口说明

函数名 rkllm_load_prompt_cache
描述 用于加载 Prompt Cache。
参数 LLMHandle handle: 模型初始化注册的目标句柄,可见 4. 初始化模型;

const char* prompt_cache_path: 待加载 Prompt Cache 文件的路径;

返回值 0 表示 Prompt Cache 模型正常加载;-1 表示模型加载失败;

表 18 rkllm_release_prompt_cache 函数接口说明

函数名 rkllm_release_prompt_cache
描述 用于释放 Prompt Cache。
参数 LLMHandle handle: 模型初始化注册的目标句柄,可见 4. 初始化模型;
返回值 0 表示 Prompt Cache 模型正常释放;-1 表示模型释放失败;

加载 Prompt Cache 的示例代码如下:

rkllm_load_prompt_cache(llmHandle, "./prompt_cache.bin");
if (ret != 0) {
printf("nload Prompt Cache failedn");
}

审核编辑 黄宇

热门文章
  • 在中超联赛赛场北京成都球迷高呼:北京加油,成都雄起

    在中超联赛赛场北京成都球迷高呼:北京加油,成都雄起
      9月14日晚,中超联赛成都蓉城对北京国安的比赛在成都凤凰山体育场举行。首都文明办工作人员到现场力促两地球迷的友好互动,引导球迷文明观赛。   开赛前,两地球迷早早来到赛场,成都球迷在赛场通道为等待入场的北京球迷拉歌拍照。一边是北京球迷激昂的歌声,一边是身穿“雄起”“成都”等字样球衣的成都球迷,画面和谐温馨。首都文明办给两地球迷代表赠送了“向北京榜样学习”宣传品,呼吁两地球迷在场上是对手,在场下是朋友。合影留念时,成都球迷高喊“北京加油”,北京球迷高喊“成都雄起”。...
  • 防风防寒!北京今天晴朗伴大风寒意十足 周末将迎小幅升温

    防风防寒!北京今天晴朗伴大风寒意十足 周末将迎小幅升温
      中国天气网讯 今天(12月27日),北京天气晴间多云,最高气温2℃,白天北风劲吹,阵风可达六至七级,风寒效应明显。本周末,北京仍以晴为主,风力不大,气温将有小幅上升。   昨天,北京晴冷在线,气温继续下跌,南郊观象台最高气温仅有2.6℃,加上风力较大,体感十分寒冷。   北京市气象台预计,今天白天晴间多云,北风三四级(阵风六七级),最高气温2℃;夜间晴间多云,北风二三级间四级,最低气温零下7℃。   明后两天,北京仍以晴为主,风力不大,最高气温将略升至5℃,最低气温...
  • 新手如何开始跑步?

    新手如何开始跑步?
    大家好,我是小贝~ 有喜欢我的分享的可以给我点个关哟~多多互动吧~🫰 跑步是最简单的运动之一。人类进化30万年,跑步是基因自带的能力。可以说天生人人都会跑,人人都可以跑。 所以,很多平时不跑步的人,想入门跑步,建议从以下4个方面开始 一、跑步一定要穿跑鞋! 10年前我跑步穿平时的休闲鞋跑了2周,跟腱受伤!因为休闲鞋没有缓震效果;对膝盖和跟腱的损害较大。 专业跑鞋鞋底有缓震设计,能减少跑步时对膝盖和脚踝的冲击力。保护膝盖和脚踝不容易受伤。 二、注意跑步频率和强度。 1、频率...
  • 西南地区持续阴雨天气 华北黄淮等地大气扩散条件逐步转差

    西南地区持续阴雨天气 华北黄淮等地大气扩散条件逐步转差
      摘要:   国内方面,昨日,全国降水整体较弱;内蒙古、东北地区等地出现大风降温天气。未来三天,青藏高原及云南、四川、贵州等地多阴雨天气,关注局地强降雨或持续降雨可能引发的次生灾害。   全球方面,昨日,欧洲东部美国东南部等地出现强降雨。未来三天,飓风“米尔顿”继续影响美国东南部等地;强冷空气影响中亚等地;欧洲大部大范围降水降温。   一、国内天气情况   1.实况   全国降水整体较弱 内蒙古东北地区等地出现大风降温天气   昨日8时至今日6时,全国降水整体较...
  • 大雾黄色预警:京津冀等8省市部分地区有大雾 局地强浓雾

    大雾黄色预警:京津冀等8省市部分地区有大雾 局地强浓雾
      据报道10月14日电据中央气象台网站消息,预计10月14日早晨至上午,河北中南部、北京、天津西部、山东西部、山西中东部、陕西北部、河南东北部和南部部分地区、湖北中部等地有大雾天气,其中,河北中南部、北京西部、山西中部、陕西北部、湖北中部等地的部分地区有能见度低于500米的浓雾,局地有不足200米的强浓雾。中央气象台14日6时继续发布大雾黄色预警。   此外,14日,华北中南部、黄淮中西部、汾渭平原等地大气扩散条件较差,有轻至中度霾,其中,北京南部、河北西部沿山部分地区有...