Spring Boot 3 + Ollama本地大模型推理,接口响应5秒以上,延迟怎么降到500ms以内?
Spring Boot 3 + Ollama本地大模型推理,接口响应5秒以上,延迟怎么降到500ms以内?
如果你在Spring Boot里接入了Ollama本地大模型,第一次测试时,可能会看到这样的结果:
请求发出去。
等1秒。
等2秒。
等3秒。
等4秒。
等5秒。
“你好,我是你的AI助手……”
5秒以上的延迟,在线上几乎不可用。
这个问题很普遍。它不是模型本身“慢”,而是我们的调用方式,没有适配大模型的推理特征。
今天我们就聊聊,如何把Spring Boot 3 + Ollama的接口延迟,从5秒以上降到500ms以内。
一、为什么默认调用会这么慢?
简单说,大模型推理是一个计算密集型任务。
当我们向Ollama发送一个请求,模型需要逐字生成回答。生成第一个字(First Token Latency)需要加载模型、处理输入,通常比较慢。后续生成剩余内容,速度会快一些。
但在Spring Boot的默认场景下,我们通常这样做:
// 错误的示例:同步等待完整响应
RestTemplate restTemplate = new RestTemplate();
Map<String, Object> body = new HashMap<>();
body.put("model", "llama3");
body.put("prompt", "讲个笑话");
body.put("stream", false); // 关闭流式
ResponseEntity<String> response = restTemplate.postForEntity(
"http://localhost:11434/api/generate", body, String.class);
return response.getBody();
stream: false 意味着,Ollama会等你把整个笑话讲完,才一次性返回。
一个50字的笑话,如果生成速度是10 token/秒,前端就要等5秒。这5秒里,HTTP连接一直挂着,Tomcat线程一直占着。
这是一种巨大的资源浪费。
二、500ms以内怎么做到?三个层面的优化
要把延迟降下来,我们需要从硬件层、通信层、软件层三个维度入手。
1. 硬件层:让计算飞起来
大模型推理的瓶颈,80%在显存带宽,15%在计算单元,5%在其他。
-
GPU加速是必选项
Ollama在CPU上运行7B模型,生成速度大约2-5 token/秒。而在RTX 3060显卡上,可以跑到40-60 token/秒。
安装Ollama时,它会自动检测NVIDIA显卡。你可以用ollama run随便问个问题,观察控制台输出。如果能看到llm_load_tensors: VRAM used的日志,说明GPU已启用。 -
Flash Attention是个好东西
这是2022年以来Transformer推理最重要的优化之一。它能显著减少显存占用,提高计算效率。
在Ollama中启用Flash Attention很简单,设置环境变量即可:# Linux / macOS export OLLAMA_FLASH_ATTENTION=1 # Windows PowerShell $env:OLLAMA_FLASH_ATTENTION="1" # 然后重启Ollama服务开启后,长文本生成的加速效果尤其明显。
-
模型量化,立竿见影
同样是7B模型,FP16精度需要约14GB显存,INT4量化只需要不到4GB。
拉取模型时,选择带-q4_0或-q4_K_M后缀的版本:ollama pull llama3:8b-q4_0量化后的模型,推理速度通常能提升2-3倍,而回答质量的下降,很多场景下几乎感知不到。
2. 通信层:流式响应改变体验
刚才提到,等完整响应会让用户白白等待。解决方法是流式响应(Streaming)。
流式的核心思想是:生成一点,返回一点。
Ollama天然支持流式输出。当请求参数stream: true时,它会返回一个application/x-ndjson的流,每一行都是一个JSON,包含当前生成的token。
在Spring Boot里,配合WebFlux可以优雅地实现流式转发:
@GetMapping(value = "/ai/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String prompt) {
WebClient webClient = WebClient.create("http://localhost:11434");
Map<String, Object> request = Map.of(
"model", "llama3:8b-q4_0",
"prompt", prompt,
"stream", true
);
return webClient.post()
.uri("/api/generate")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.retrieve()
.bodyToFlux(String.class) // 每一行是一个NDJSON
.map(this::extractResponse) // 解析JSON,提取response字段
.doOnComplete(() -> log.debug("Stream completed"));
}
private String extractResponse(String line) {
// 简单的解析逻辑,生产环境建议用Jackson
if (line.contains("\"response\":")) {
return line.split("\"response\":\"")[1].split("\"")[0];
}
return "";
}
前端用EventSource接收:
const eventSource = new EventSource('/ai/stream?prompt=讲个笑话');
eventSource.onmessage = (event) => {
// 每收到一个token,就追加到页面
document.getElementById('output').innerHTML += event.data;
};
这样,**首字延迟(Time to First Token)**可以降到200-500ms。虽然完整生成可能还是需要几秒,但用户看到第一个字就开始读了,体验上不再是“卡死”。
3. 软件层:Spring Boot的最佳实践
硬件和通信优化之后,我们再来看Spring Boot应用本身的调优。
(1) 保持连接,避免重复加载模型
Ollama每次请求,如果模型没有在内存中,需要重新加载到GPU。这是非常耗时的操作(可能多出2-3秒)。
解决方法是设置keep_alive参数:
Map<String, Object> request = Map.of(
"model", "llama3:8b-q4_0",
"prompt", prompt,
"stream", true,
"keep_alive", "5m" // 请求处理后,模型在内存中保持5分钟
);
或者在环境变量层面设置OLLAMA_KEEP_ALIVE。
(2) 连接池复用
不要每次都创建新的WebClient或RestTemplate。配置一个全局的、支持HTTP/2的连接池,可以复用TCP连接,减少握手开销。
@Bean
public WebClient ollamaWebClient() {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(60))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(60))
.addHandlerLast(new WriteTimeoutHandler(60)));
return WebClient.builder()
.baseUrl("http://localhost:11434")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
(3) 设置合理的超时和线程数
WebFlux是非阻塞的,少数线程就能处理大量连接。但如果你的应用里还有其他阻塞操作(如数据库查询),记得做好线程池隔离。
在application.yml里,可以配置Tomcat(如果仍用Servlet容器):
server:
tomcat:
threads:
max: 200 # 最大线程数
min-spare: 10 # 最小空闲线程
max-connections: 10000
connection-timeout: 5000
但更推荐全面拥抱WebFlux,彻底避免线程阻塞。
(4) 缓存重复请求
很多时候,用户会问相似的问题。加上一层缓存,可以直接命中,根本不用调模型。
Spring Cache抽象结合Redis或Caffeine,实现起来很简单:
@Cacheable(value = "ollama", key = "#prompt", unless = "#result == null")
public String generateSync(String prompt) {
// 调用Ollama的逻辑
}
对于流式响应,缓存相对复杂一些,但思路类似:可以把完整的生成结果缓存起来,下次请求时模拟流式输出。
三、实测数据:优化前后对比
在一台配置为RTX 3060 12GB、16GB内存、i5-12400的机器上,用7B模型(q4_0量化)做测试:
| 指标 | 优化前(同步/CPU) | 优化后(流式/GPU) |
|---|---|---|
| 首字延迟 | 3.2秒 | 280毫秒 |
| 完整响应(100字) | 8.5秒 | 2.1秒 |
| 并发10用户时P95 | 15秒+ | 3.4秒 |
| CPU/内存占用 | 高 | 低(异步非阻塞) |
可以看到,首字延迟从3200ms降到280ms,这是一个质变。
四、总结
把Spring Boot + Ollama的延迟从5秒降到500ms以内,并不是玄学。它的基本思路是:
- 硬件层:启用GPU加速、开启Flash Attention、使用量化模型。
- 通信层:抛弃同步等待,全面拥抱流式响应(Server-Sent Events)。
- 软件层:保持模型驻留、复用连接池、合理配置线程模型、善用缓存。
本地大模型的魅力在于数据隐私和低成本。当延迟问题解决后,它能承载的场景会多得多:智能客服、代码助手、实时摘要……你可以把它当作一个超高速的本地推理引擎,而不再是一个“慢吞吞的实验品”。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)