章
目
录
最近我发现自己被AI“拿捏”了,以前遇到问题第一反应是上搜索引擎,现在直接找AI询问。不得不说,大型语言模型(LLMs)彻底颠覆了人工智能领域,那些曾经只有人类能完成的自然语言处理任务,现在AI也能轻松应对。前段时间公司组织去微软参观,感受了人家的前沿技术,我深刻意识到,咱们得跟上这波AI潮流。就算不搞AI模型开发,也得对其中的概念和原理有所了解。今天就跟大家分享一下我在探索QWen和DeepSeek过程中的收获。
一、AMD在AI领域的布局
之前我买了块AMD显卡,本想着闲暇时玩AAA游戏,欣赏游戏里的精美风景。一次逛显卡驱动官网时,意外发现AMD也进军AI领域了,还推出了和NVIDIA类似的开发工具,像AMD HIP SDK和AMD ROCm。
AMD HIP SDK(Heterogeneous-computing Interface for Portability)是一套跨平台的GPU编程工具,能帮开发者编写可在AMD和NVIDIA GPU上运行的高性能计算代码。它和CUDA编程模型类似,能让开发者更轻松地开发GPU加速代码。
AMD ROCm(Radeon Open Compute)则是一个开源的GPU计算平台,专门用于高性能计算和机器学习。它包含运行时环境、工具链以及各种库,可以在AMD GPU上高效完成高性能计算任务。这里要注意,HIP是GPU编程工具,而ROCm是GPU计算平台,ROCm包含了HIP SDK。
刚接触这些概念时,我满脑子疑问。比如用PyTorch + ROCm开发时,需不需要把HIP引入工程?而且官方提供的HIP只有Windows版本,ROCm却只能在Linux环境下使用,这可把我搞懵了,用PyTorch开发不就受限了吗?看看官方的架构图,里面的各种框架、库、工具让人眼花缭乱。
二、解决开发与部署难题
官网没找到答案,我就求助AI。原来,PyTorch并不直接依赖HIP,它通过ROCm提供的底层运行时和加速库(像MIOpen、rocBLAS等)来实现GPU加速。Windows上的HIP SDK主要用于开发其他GPU应用,比如科学计算或图形处理,不是用来运行PyTorch的。
解决了开发问题,新的疑问又冒出来了:开发完成后,模型怎么运行?在Linux上用ROCm开发的模型,Windows没有ROCm环境,该怎么部署?用ROCm开发的程序能不能用NVIDIA显卡加速呢?
后来了解到,AI模型分为训练和运行两个阶段。训练阶段,可以在Linux上借助ROCm和PyTorch开发并训练深度学习模型,训练完成后,把PyTorch模型保存为通用格式,比如.pt或.onnx文件。运行阶段,在Windows系统上重新配置PyTorch环境,加载保存的模型进行推理。如果不需要GPU加速,直接用PyTorch CPU后端就行;要是需要GPU加速,这里以onnx文件为例详细说说。
2.1 认识ONNX
ONNX是一种开放的神经网络交换格式,由微软和Facebook(现在叫Meta)联合推出,主要是为了解决深度学习模型跨平台和跨框架的兼容性问题。ONNX模型很强大,能在Windows、Linux、macOS等多个操作系统上运行,而且对硬件的支持也非常广泛,无论是CPU、NVIDIA GPU(基于CUDA)、AMD GPU(基于ROCm或DirectML),还是像谷歌TPU这样的特定AI加速器,它都能适配。
DirectML是微软基于DirectX 12构建的通用GPU加速推理API,游戏玩家对DirectX肯定不陌生,很多AAA游戏的GPU加速选项里都有它的身影。借助DirectML,AMD、NVIDIA和Intel的GPU都能实现加速。
2.2 在Linux上安装ONNX Runtime
在Linux系统上安装ONNX Runtime,有不同的选择:
# 安装支持CPU的ONNX Runtime:
pip install onnxruntime
# 安装支持ROCm的ONNX Runtime:
pip install onnxruntime-rocm
# 安装支持CUDA的ONNX Runtime:
pip install onnxruntime-gpu
具体安装哪种,要根据自己手头的硬件来决定。
2.3 在Windows上安装ONNX Runtime
Windows上安装ONNX Runtime也有多种方式:
# 安装通用的ONNX Runtime:
pip install onnxruntime
# DirectML是微软推出的跨厂商GPU加速API,支持AMD和NVIDIA GPU
pip install onnxruntime-directml
# Windows可直接安装支持CUDA的ONNX Runtime:
pip install onnxruntime-gpu
要是只有AMD的GPU,在Windows上只能通过DirectML对PyTorch训练的模型进行加速。另外,在macOS上,ONNX Runtime目前仅支持CPU推理,还不支持GPU加速。
这里要注意,ONNX模型的opset版本(操作集版本)必须和目标推理引擎兼容,常见的选择是opset 11及更新版本(ONNX Runtime通常支持opset版本11 – 17)。如果目标ONNX Runtime不支持模型的opset版本,模型可能就无法加载。
2.4 ONNX Runtime的执行提供者
ONNX Runtime是一个跨平台的推理引擎,为了适配不同的硬件平台,它提供了多种执行提供者,每个执行提供者对应特定的硬件和加速方案:
- CPUExecutionProvider:适用于所有平台,是默认的执行提供者,不使用硬件加速,没有GPU时可以用它。
- CUDAExecutionProvider:用于NVIDIA GPU,借助NVIDIA的CUDA和cuDNN加速推理,NVIDIA GPU用户可以选择它。
- ROCMExecutionProvider:在Linux环境下,为AMD GPU加速推理提供支持,Linux系统中使用AMD GPU的用户可以用这个。
- DmlExecutionProvider:基于DirectML,在Windows平台上提供跨硬件加速,AMD、NVIDIA和Intel的GPU都能支持,Windows用户可以考虑。
- OpenVINOExecutionProvider:利用Intel的OpenVINO工具包加速推理,适用于Intel CPU和VPU(比如Movidius)。
- TensorrtExecutionProvider:专门针对NVIDIA GPU优化,在Linux系统下,使用NVIDIA TensorRT提供更高效的推理性能。
- QNNExecutionProvider:借助Qualcomm的Hexagon DSP加速推理,适用于移动设备上的Qualcomm芯片。
- CoreMLExecutionProvider:在macOS和iOS系统上,使用Apple的CoreML框架加速推理。
三、不同硬件平台下的模型运行
假设有开发者小明,他有一块NVIDIA GPU;还有开发者小红,她有一块AMD GPU。小明用NVIDIA GPU并通过CUDA加速训练了一个模型,然后把模型导出为ONNX格式的文件。
ONNX格式的优势在于它跨框架、跨硬件平台,所以不管是小明的NVIDIA GPU,还是小红的AMD GPU,都能加载和运行这个ONNX文件。而且ONNX与硬件无关,小明用CUDA训练模型的细节不会影响最终模型的运行。
小明可以直接用NVIDIA GPU和ONNX Runtime + CUDAExecutionProvider来运行他的ONNX模型,通过下面的代码执行推理:
pip install onnxruntime-gpu
import onnxruntime as ort
# 使用NVIDIA CUDA加速
ort_session = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"])
小红的电脑是Windows系统,配备了AMD显卡。她想运行小明导出的ONNX模型,虽然没有NVIDIA GPU和CUDA环境,但借助DirectML提供者,也能利用AMD GPU加速运行模型:
pip install onnxruntime-directml
import onnxruntime as ort
# 使用DirectML提供者(适用于Windows平台)
ort_session = ort.InferenceSession("model.onnx", providers=["DmlExecutionProvider"])
# 验证当前正在使用的执行提供者
print("当前使用的执行提供者:", ort_session.get_providers())
四、其它概念
4.1 GGUF
GGUF是GGML(Georgi Gerganov Machine Learning)系列框架的一种新模型格式,主要用于优化大语言模型(LLM)的存储和推理。它专为CPU和轻量化GPU推理场景设计,非常适合资源受限的设备。
像PyTorch或TensorFlow等深度学习框架,都支持将模型导出或转换为GGUF和ONNX格式。不过,目前GGUF格式主要由llama.cpp及相关工具链支持,不能直接在深度学习框架中生成,需要借助特定工具。llama.cpp提供了工具,可以把大语言模型(比如LLaMA、Qwen等)从主流框架(如PyTorch)转换为GGUF格式。
下面对比一下GGUF和ONNX的运行环境:
- 格式设计目标:GGUF注重轻量化推理,适合低资源设备;ONNX追求通用性,支持多任务、多框架、多硬件。
- 操作系统支持:GGUF支持Linux、macOS、Windows(WSL)及安卓;ONNX支持全平台,像Linux、Windows、macOS等都没问题。
- 硬件支持:GGUF主要依赖CPU,初步支持GPU;ONNX支持CPU、GPU、TPU、FPGA等,能实现复杂的硬件加速。
- 工具链依赖:GGUF依赖llama.cpp这个轻量化工具链;ONNX依赖ONNX Runtime、TensorRT、OpenVINO等。
- 运行优化需求:GGUF追求低资源消耗,适合边缘设备;ONNX侧重于高性能优化,依赖硬件加速。
- 是否硬件无关:两者理论上都与硬件无关,但GGUF取决于llama.cpp的实现,ONNX取决于ONNX Runtime的实现。
llama.cpp支持DirectML,能调用DirectML的GPU加速能力,实现GGUF格式的推理加速。目前,DirectML支持所有支持DirectX 12的AMD GPU(比如RX 6000系列、RX 7000系列显卡)。不过,llama.cpp目前还不直接支持ROCm,它支持OpenCL后端(可用于NVIDIA GPU和AMD GPU)和Vulkan后端(支持AMD和NVIDIA GPU),但尚未直接支持NVIDIA的CUDA后端。
4.2 Q(Quantization,量化)
在模型领域,“q”通常代表量化(Quantized),用于描述模型的量化权重或激活值。量化就是把浮点数(比如FP32或FP16)转换为整数(比如INT8)或低比特精度的表示形式,这样做的目的是减少模型的存储和计算成本,同时尽量保证模型的精度。比如,q6和q5可能表示量化为6位和5位精度的权重或数据。
以使用Qwen2.5 – Coder – 32B – Instruct – GGUF模型为例,假设计算机内存为32GB,GPU显存为24GB,该怎么选择量化方案呢?
- q6_k(6 – bit量化):6 – bit量化后,每个参数占用约0.75字节。模型占用显存 ≈ 32 × 10⁹ × 0.75 / (1024³) = 22.39GB。考虑到总显存需求(包括临时激活值和上下文长度),可能接近或略超过24GB,所以可能需要优化上下文长度设置。
- q5_k_m(5 – bit量化):5 – bit量化后,每个参数占用约0.625字节。模型占用显存 ≈ 32 × 10⁹ × 0.625 / (1024³) = 18.66GB。总显存需求通常在20GB左右,在24GB的GPU显存内运行会比较轻松。
综合来看,更推荐q5_k_m这种量化方案。在一些高负载场景(比如较长上下文或多批量推理)中,q6_k可能会超出显存限制。
4.3 开源AI库
4.3.1 DeepSeek
以DeepSeek的deepseek – r1 – distill – llama – 8b模型为例,来看看在Android上用Rust写一个FFI的Demo,并调用C++方法,再通过rust – jni把结果返回给Java层的具体步骤:
- 安装依赖项:安装一些必要的依赖项,对于rust – jni,在Android上可能需要特定配置。如果还没安装cargo.toml,可以先安装:
cargo install --dev
安装rustfmt确保代码格式正确:
cargo add rustfmt --all-features
安装rust – jni:
cargo add https://github.com/Bytedance/rust-jni
- 创建项目结构:创建新的Rust项目,添加必要的配置文件。在Cargo.toml中添加以下内容:
[package]
name = "rust_jni_demo"
version = "0.1.0"
edition = "2021"
[dependencies]
rust-jni = "latest"
jni_utils = "0.3"
android_support = "0.4"
[[path]]
name = "src/main.rs"
- 编写Rust代码:在src/main.rs中编写简单的FFI Demo:
use jni::JNIEnv;
use jni::sys::System;
#[derive(Debug)]
struct RustStruct {}
impl RustStruct {
fn new() -> Result<Self, Box<dyn std::error::Error>> {
println!("创建了一个Rust结构体");
Ok(RustStruct {})
}
}
#[jni::main]
fn main(
mut env: JNIEnv,
_class: *const u8,
) -> i32 {
// 初始化Android环境
System::initialize(env);
let result = RustStruct::new();
println!("Rust调用成功,结果={}", result);
// 返回结果给Java层
return result.map(|_| 0).try_into().unwrap_or(0);
}
- 编写Java层的代码:创建MainActivity类,在其中使用rust – jni调用Rust方法:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.id.main);
try {
// 调用Rust层的方法
String result = callRustMethod();
Toast.makeText(this, "从Rust返回的结果是:" + result, Toast.LENGTH_LONG).show();
} catch (JNIError e) {
Toast.makeText(this, "JNI错误:" + e.toString(), Toast.LENGTH_LONG).show();
}
}
// 使用rust-jni注册一个方法
@Override
public boolean onTouchEvent(MotionEvent event) {
callRustMethod();
return true;
}
// 使用rust-jni创建一个函数,调用Rust层的功能
private static final String METHOD_NAME = "callRustFunction";
private static final int METHOD_ID = JNIEnv.getStaticMethodID(
getClass().getDeclaredClass(),
METHOD_NAME,
new Class<?>[] {}
);
private String callRustMethod() {
try (JNIEnv env = new Env()) {
return env.call-static<java.lang.String>(METHOD_ID);
} catch (JNIError e) {
throw new RuntimeException(e);
}
}
// 为了让rust-jni识别这个类,添加注解
@RustJavaClass(name = "com.example.MainActivity", is_jni=False)
public class Activity extends AppCompatActivity {
// ...
}
- 安装和构建C++库(可选):如果需要调用C++函数,可以编写一个简单的C++库,并编译成.so文件。比如:
// src/cpp/hello.cpp
#include <string>
using namespace std;
extern "C" {
string sayHello() {
return "Hello from C++";
}
}
然后在Cargo.toml中添加构建步骤:
[project]
name = "rust_jni_demo"
[dependencies]
...
// 添加一个构建阶段,编译C++库,并生成.so文件
[target.mk]
all : $(objdir)/$(target)/libmyjnilib.so
$(objdir)/$(target)/libmyjnilib.so: src/cpp/hello.o
g++ -o $@ -shared src/cpp/hello.o
- 编译和运行:在命令行编译:
cargo build
- 注意事项:
- 线程安全:确保JNI操作在主线程执行,对于长时间运行的任务,可以考虑在子线程处理,并通过JNI发送回调。
- 错误处理:rust – jni提供了丰富的错误处理机制,比如JNIEnv::try来管理资源。
- 性能优化:如果频繁调用JNI方法,可能需要优化Rust代码,提高效率。
- 验证和调试:在Rust中用println!输出日志信息,验证调用是否成功;在Android中使用Log utility输出日志,比如Log.d(“TAG”, “消息”),方便在Android Studio中查看。
4.3.2 QWen
同样在Android平台上,用Rust通过FFI调用C++方法,并通过rust – jni将结果返回给Java层,以QWen – Coder – 2.5 – 32B – q5_k_m模型为例,具体步骤如下:
- 编写C++代码:编写一个简单的C++库,包含要被Rust调用的方法。例如:
// src/native-lib.cpp
extern "C" {
int add(int a, int b) {
return a + b;
}
}
- 构建C++库:
- 在
Android.mk
文件中配置编译规则,假设你的项目结构是在src
目录下有native - lib.cpp
文件。
LOCAL_PATH := $(call my - dir) include $(CLEAR_VARS) LOCAL_MODULE := native - lib LOCAL_SRC_FILES := native - lib.cpp include $(BUILD_SHARED_LIBRARY)
- 运行
ndk - build
命令(确保你已正确配置Android NDK环境变量),生成libnative - lib.so
库文件。
- 在
- 配置Rust项目:
- 创建一个新的Rust项目,在
Cargo.toml
文件中添加依赖:
[package] name = "qwen_rust_jni_demo" version = "0.1.0" edition = "2021" [dependencies] rust - jni = "latest"
- 创建一个新的Rust项目,在
- 编写Rust代码调用C++库:
- 在
src/main.rs
文件中编写如下代码:
extern "C" { fn add(a: i32, b: i32) -> i32; } use jni::JNIEnv; use jni::sys::jint; #[no_mangle] pub extern "system" fn Java_com_example_qwen_1rust_1jni_1demo_MainActivity_callCppFunction( env: JNIEnv, _class: jni::sys::jclass, a: jint, b: jint, ) -> jint { unsafe { add(a, b) } }
- 这里定义了一个Rust函数
Java_com_example_qwen_1rust_1jni_1demo_MainActivity_callCppFunction
,它会被Java层调用,在函数内部通过extern "C"
块声明的add
函数调用C++库中的add
方法。
- 在
- 编写Java代码调用Rust方法:
- 在
MainActivity.java
文件中编写如下代码:
package com.example.qwen_rust_jni_demo; import android.os.Bundle; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native - lib"); } public native int callCppFunction(int a, int b); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); int result = callCppFunction(3, 5); Toast.makeText(this, "调用C++函数结果: " + result, Toast.LENGTH_SHORT).show(); } }
- 在Java代码中,通过
System.loadLibrary("native - lib")
加载之前编译生成的C++库,然后定义了一个native
方法callCppFunction
,该方法会调用Rust中实现的与C++交互的函数。
- 在
- 编译和运行:
- 在Rust项目目录下运行
cargo build --target aarch64 - linux - android
(根据你的Android设备架构选择正确的目标平台,这里以aarch64
为例),生成对应的动态库文件。 - 将生成的动态库文件(如
target/aarch64 - linux - android/debug/libqwen_rust_jni_demo.so
)复制到Android项目的jniLibs/armeabi - v8a
目录下(同样根据架构选择正确的目录)。 - 运行Android项目,验证是否能正确调用C++函数并获取结果。
- 在Rust项目目录下运行
通过以上步骤,在Android平台上实现了利用Rust通过FFI调用C++方法,并通过rust – jni将结果返回给Java层,以QWen相关模型在Android平台上的使用场景为例,展示了跨语言调用的实践过程。在实际应用中,对于QWen模型,可能需要根据其具体的API和使用方式,在C++或Rust层进行更复杂的模型加载、推理等操作,然后将结果返回给Java层供应用使用。同时,在处理QWen模型相关的量化等特性时,也需要在不同语言层协同工作,确保模型在Android设备上能够高效且准确地运行。例如,在加载量化后的QWen模型时,可能需要在C++层编写特定的加载函数,通过Rust的FFI调用该函数,再将加载结果传递给Java层,以便在Android应用中进行推理等操作。
以上就是关于QWen和DeepSeek在不同方面的详细介绍与实践,希望能帮助大家更好地理解和应用这些技术。在实际开发中,根据具体的需求和硬件环境,灵活选择合适的工具和方法,以实现高效的AI应用开发。如果在实践过程中遇到问题,欢迎在评论区留言讨论。