
💡 摘要: 公司要求所有AI应用数据不出内网,NVIDIA A100 一卡难求且价格翻倍,用CPU跑Qwen2.5-7B一个Token要2秒,用户等一个回答要40秒——这不是AI应用,是折磨用户。本文基于我在金融系统内网AI改造中的真实项目经验,介绍如何用 AMD ROCm + Ollama + Spring Boot + Spring AI 构建 AMD GPU 加速的本地大模型应用:ROCm 环境搭建与 GPU 架构兼容、Ollama ROCm 模式部署与量化模型选型、Spring AI 对接 Ollama 的对话/Embedding/Function Calling 全链路、UMA 统一内存架构跑 70B 大模型的实测数据、多 AMD GPU 负载均衡方案。
📅 技术栈版本: Spring Boot 3.4.x | Spring AI 1.0.x | ROCm 7.x | Ollama 0.20.x | JDK 17+ | 更新时间: 2026-06
去年负责一个金融系统的内网 AI 改造项目,要求所有数据不出内网。看似简单,实则处处碰壁:
困境 | 具体表现 | 影响 |
|---|---|---|
GPU 短缺 | NVIDIA A100/H100 价格翻倍,交期3个月+ | 项目启动延迟 |
云端不可用 | 金融监管要求数据不出内网 | 无法调用 OpenAI/通义等云端 API |
CPU 太慢 | Qwen2.5-7B 在 CPU 上推理 ~2s/Token | 40秒才出完一个回答,用户无法接受 |
2026年的 ROCm 7.x 已经不是2023年那个"勉强能用"的状态了:
优势 | 说明 | 对Java开发者意味着什么 |
|---|---|---|
生态成熟 | PyTorch/Ollama/LM Studio 一等公民支持 | 不用改代码,直接跑 |
UMA统一内存 | 128GB统一内存,GPU可直接访问 | 单机跑70B+模型,无需多卡 |
成本优势 | RX 7900 XTX 24GB 仅 ~6000元 | 同等显存成本是NVIDIA的1/3 |
NVIDIA RTX 4090 24GB 约 13000元,AMD RX 7900 XTX 24GB 约 6000元。对预算有限的企业,AMD 的性价比优势明显。

ROCm 不支持所有 AMD 显卡。选错卡,装不上驱动,一切白费。
GPU系列 | 架构代号 | ROCm支持 | 推荐型号 |
|---|---|---|---|
RX 7000 (RDNA3) | gfx1100/gfx1101 | 官方支持 | RX 7900 XTX 24GB |
RX 9000 (RDNA4) | gfx1200 | ROCm 6.2+ 扩展支持 | RX 9070 XT |
Radeon PRO W7800 | gfx1101 | 官方支持 | W7800 32GB |
MI300X (CDNA3) | gfx942 | 完整支持 | MI300X 192GB |
RX 6000 (RDNA2) | gfx1030 | 社区支持(不稳定) | 不推荐生产 |
ROCm 在 Linux 上性能最好、兼容性最稳定。Windows 通过 HIP SDK 也能跑,但性能约低 15-20%。
# Step1: 添加ROCm仓库
sudo apt update
wget https://repo.radeon.com/amdgpu-install/latest/ubuntu/noble/amdgpu-install_*.deb
sudo apt install ./amdgpu-install_*.deb
# Step2: 安装ROCm
sudo amdgpu-install --usecase=rocm
# Step3: 添加用户到渲染组(否则无权限访问GPU)
sudo usermod -aG render,video {spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>spring:
ai:
ollama:
# Ollama服务地址(本地部署)
base-url: http://localhost:11434
chat:
model: qwen2.5:7b
options:
temperature: 0.7
top-p: 0.9
num-predict: 2048 # 最大生成Token数
embedding:
model: bge-m3
options:
dimensions: 1024
# PGVector向量库配置
datasource:
url: jdbc:postgresql://localhost:5432/ai_knowledge
username: postgres
password: your_password@Service
@Slf4j
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder
.defaultSystem("你是一个专业的金融分析师助手,用中文回答问题。" +
"回答要准确、专业,引用具体数据。")
.build();
}
/**
* 单轮对话
*/
public String chat(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.call()
.content();
}
/**
* 流式对话(SSE推送,用户无需等待完整回答)
*/
public Flux<String> chatStream(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.stream()
.content();
}
}@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private ChatService chatService;
/**
* 同步对话
*/
@PostMapping
public ResponseEntity<Map<String, String>> chat(
@RequestBody Map<String, String> request) {
String answer = chatService.chat(request.get("message"));
return ResponseEntity.ok(Map.of("answer", answer));
}
/**
* 流式对话(SSE)
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String message) {
return chatService.chatStream(message);
}
}企业场景下,AI 不能只靠模型自身知识,必须结合企业内部文档。RAG(检索增强生成)是标配。
@Service
@Slf4j
public class RagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
public RagService(ChatClient.Builder chatClientBuilder,
VectorStore vectorStore,
EmbeddingModel embeddingModel) {
this.chatClient = chatClientBuilder.build();
this.vectorStore = vectorStore;
this.embeddingModel = embeddingModel;
}
/**
* 导入文档到向量库
*/
public void importDocument(String content, Map<String, Object> metadata) {
// 文本分片
List<Document> documents = List.of(
new Document(content, metadata)
);
// 自动向量化并存入PGVector
vectorStore.add(documents);
log.info("文档导入成功, metadata={}", metadata);
}
/**
* RAG问答:检索相关文档 + 生成回答
*/
public String ask(String question) {
// 1. 从向量库检索相关文档(Top 5)
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.builder()
.query(question)
.topK(5)
.similarityThreshold(0.7)
.build()
);
// 2. 拼接检索到的文档内容
String context = relevantDocs.stream()
.map(Document::getText)
.collect(Collectors.joining("\n\n"));
// 3. 基于检索结果生成回答
return chatClient.prompt()
.system("基于以下参考资料回答用户问题。如果资料中没有相关信息," +
"请说明,不要编造答案。\n\n参考资料:\n{context}")
.user(question)
.variables(Map.of("context", context))
.call()
.content();
}
}大模型本身无法查询数据库、调用API。Spring AI 的 Function Calling 让模型能自动调用你注册的 Java 方法。
@Configuration
public class FunctionConfig {
/**
* 注册一个查询股票行情的函数
* 模型会根据用户问题自动决定是否调用
*/
@Bean
@Description("查询指定股票代码的最新行情信息,包括当前价格、涨跌幅、成交量")
public Function<StockRequest, StockResponse> stockQuery() {
return request -> {
// 实际业务中调用行情API
StockResponse response = new StockResponse();
response.setCode(request.code());
response.setPrice(125.80);
response.setChangePercent(-1.23);
response.setVolume(15680000L);
return response;
};
}
public record StockRequest(
@Description("股票代码,如 600519") String code
) {}
@Data
public static class StockResponse {
private String code;
private double price;
private double changePercent;
private long volume;
}
}使用 Function Calling:
@Service
public class SmartChatService {
private final ChatClient chatClient;
public SmartChatService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultFunctions("stockQuery") // 注册函数
.build();
}
public String chat(String message) {
// 用户问:"贵州茅台今天涨了吗?"
// 模型会自动调用 stockQuery("600519"),然后基于结果回答
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
维度 | 离散GPU (RTX 4090 24GB) | UMA (Ryzen AI Max+ 128GB) |
|---|---|---|
显存容量 | 24GB(硬上限) | 64-120GB(可配置) |
70B模型运行 | ❌ 放不下(需2-3卡) | ✅ 单机可运行 |
CPU↔GPU数据传输 | PCIe ~12GB/s + 拷贝延迟 | 零拷贝,无传输延迟 |
硬件成本 | ~13000元 × 2-3 | 单机 ~15000元 |
# 拉取72B量化模型
ollama pull qwen2.5:72b
# 运行(Ollama自动识别UMA,混合加载)
ollama run qwen2.5:72b --verbose
# --verbose 输出示例:
# loaded 72B model
# GPU offload layers: 40/80 (UMA partial offload)
# GPU memory: 58GB / 64GB
# CPU memory: 22GB
# eval speed: 4.2 tok/s72B 模型在 24GB 独立显卡上完全放不下。UMA 的 64GB GPU 可访问内存 + CPU 补充加载,实现了单机运行 70B 级模型。虽然速度只有 4.2 tok/s,但比"跑不了"强太多。
测试环境:AMD Ryzen AI Max+ 395,128GB 统一内存,GPU 可访问 64GB
模型 | 量化 | GPU层 | CPU层 | 生成速度 | 质量 |
|---|---|---|---|---|---|
Qwen2.5-7B | Q4_K_M | 全部 | 0 | 35 tok/s | ★★★★ |
Qwen2.5-14B | Q4_K_M | 全部 | 0 | 22 tok/s | ★★★★☆ |
Qwen2.5-32B | Q4_K_M | 全部 | 0 | 10 tok/s | ★★★★★ |
Qwen2.5-72B | Q4_K_M | 40/80 | 40/80 | 4.2 tok/s | ★★★★★ |
Qwen3.5-122B (MoE) | Q3_K_M | 部分 | 部分 | 2.8 tok/s | ★★★★★ |
企业场景可能配置多张 AMD 显卡,需要合理分配模型到不同 GPU。Ollama 支持多 GPU 自动分配,也可手动指定。
# 查看所有GPU
rocm-smi
# 期望输出:
# GPU[0]: AMD Radeon RX 7900 XTX (gfx1100) 24GB
# GPU[1]: AMD Radeon RX 7900 XTX (gfx1100) 24GB
# 指定使用的GPU(逗号分隔)
export ROCR_VISIBLE_DEVICES=0,1
# Ollama自动将模型分配到多张GPU
ollama run qwen2.5:32b --verbose
# 输出: GPU offload layers: 60/60 (split across 2 GPUs)单实例 Ollama 同时处理多个请求时,GPU 显存会被多个上下文占用。部署多个 Ollama 实例,通过 Nginx 负载均衡,可提升并发能力。
# docker-compose.yml
services:
ollama-1:
image: ollama/ollama:rocm
devices:
- /dev/dri/renderD128 # GPU 0
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
ollama-2:
image: ollama/ollama:rocm
devices:
- /dev/dri/renderD129 # GPU 1
ports:
- "11435:11434"
volumes:
- ollama_data:/root/.ollama
volumes:
ollama_data:Spring AI 配置负载均衡:
spring:
ai:
ollama:
# 多实例时,通过Nginx代理
base-url: http://ollama-lb:11434Nginx 负载均衡:
upstream ollama_cluster {
least_conn;
server 127.0.0.1:11434;
server 127.0.0.1:11435;
}
server {
listen 11434;
location / {
proxy_pass http://ollama_cluster;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}问题:ROCm 已安装,但 ollama run --verbose 显示 CPU only。
原因:用户未加入 render 组,Ollama 进程无权访问 GPU 设备。
解决:
# 添加用户到render组
sudo usermod -aG render,video $LOGNAME
# 重新登录(关键!组变更需要重新登录生效)
logout
# 验证GPU权限
ls -la /dev/dri/renderD*
# 期望: crw-rw---- render组可读写
# 重启Ollama
systemctl restart ollama问题:设置 HSA_OVERRIDE_GFX_VERSION 后,GPU 初始化失败。
原因:版本号格式错误或与实际架构不匹配。
解决:对照架构代号表设置正确版本:
# ✅ 正确格式
export HSA_OVERRIDE_GFX_VERSION=10.3.0 # gfx1030 (RDNA2)
# ❌ 错误格式
export HSA_OVERRIDE_GFX_VERSION=1030 # 缺少点号
export HSA_OVERRIDE_GFX_VERSION=11.0.0 # RDNA3的gfx1100不能这样设问题:SSE 流式返回中文内容时,前端显示乱码。
解决:确保 Spring MVC 的 SSE 编码配置正确:
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> chatStream(@RequestParam String message) {
return chatService.chatStream(message)
.map(chunk -> ServerSentEvent.<String>builder()
.data(chunk)
.build());
}问题:BGE-M3 输出 1024 维向量,但 PGVector 建表时用了 768 维。
解决:确保 spring.ai.ollama.embedding.options.dimensions 与向量表维度一致:
-- 创建向量表时指定正确维度
CREATE TABLE vector_store (
id UUID PRIMARY KEY,
content TEXT,
metadata JSONB,
embedding VECTOR(1024) -- BGE-M3 输出1024维
);spring:
ai:
ollama:
embedding:
options:
dimensions: 1024 # 必须与向量表维度一致问题:Qwen2.5-72B Q4_K_M 需要约 40GB 显存,24GB 显卡放不下。
解决:Ollama 支持自动 CPU/GPU 混合加载,但需要足够系统内存:
# 设置Ollama使用CPU补充加载
export OLLAMA_NUM_GPU=40 # 只将40层放到GPU,其余CPU处理
# 需要足够的系统内存(至少64GB)
# 24GB GPU + 40GB CPU内存 ≈ 64GB 总需求
# 查看系统可用内存
free -h如果系统内存不足,只能降低模型参数量(使用 32B 或 14B)。
你的显存是多少?
├── 8GB(RX 7600)
│ └── Qwen2.5-7B Q4_K_M(4.5GB)
├── 16GB(RX 7800 XT)
│ └── Qwen2.5-14B Q4_K_M(8.5GB)
├── 24GB(RX 7900 XTX)
│ └── Qwen2.5-32B Q4_K_M(18GB)
├── 32GB(Radeon PRO W7800)
│ └── Qwen2.5-32B Q5_K_M(22GB)
├── 64GB+ UMA(Ryzen AI Max+)
│ └── Qwen2.5-72B Q4_K_M(混合加载)
└── 192GB(MI300X)
└── Qwen2.5-72B Q8_0(全精度)rocminfo 能正确识别 GPUrender 和 video 组中--verbose 输出确认 GPU offload 生效dimensions 配置与向量表一致维度 | AMD ROCm | NVIDIA CUDA |
|---|---|---|
生态成熟度 | 良好(2026年已大幅改善) | 极好 |
性价比 | 高(同显存成本低40-60%) | 低 |
UMA大模型 | ✅ Ryzen AI Max+ 128GB | ❌ 无UMA方案 |
Windows支持 | WSL2/HIP SDK,稍弱 | 原生支持 |
社区资源 | 较少但快速增长 | 丰富 |
适用场景 | 预算敏感/内网部署/大模型 | 全场景 |
📜 真实性声明: 本文所有内容均基于作者在2026年Q2期间使用 AMD ROCm + Spring AI 构建内网AI应用的真实开发实践。性能数据来自实际测试环境(RX 7900 XTX / Ryzen AI Max+ 395),踩坑经验来自部署过程中的真实问题。ROCm 7.x 的生态成熟度评价基于2026年6月的实际体验。
互动话题: