新闻详情

新闻详情

首页 / 资讯中心 / 详情

在 Spring Boot 3.4.5 中通过 HuggingFace Transformer...

发布时间:2026/9/29 22:09:38来源:尧图网络
在 Spring Boot 3.4.5 中通过 HuggingFace Transformer...
在 Spring Boot 3.4.5 中通过 HuggingFace Transformers 4.49.0 构建离线语义向量缓存从 JVM 内存溢出到 C 堆优化的实战记录上周排查线上服务时发现某内部知识问答接口的 P99 延迟突然飙升至 2.3s远超 SLA 约定的 800ms。复现路径很直接用户提问 - 后端调用 LLM 生成答案 - 答案存入 Elasticsearch 8.16.0 并同步更新向量索引。问题不在 LLM 本身而在向量嵌入计算环节。原本使用在线 API 的方案因网络抖动频繁超时团队决定将嵌入模型本地化部署。选型阶段对比了多个方案最终锁定 HuggingFace Transformers 4.49.0 提供的 ONNX Runtime 后端将其集成到 Java 17.0.12 环境下。这个决策看似合理却在上线第一周引发了三次 OOMOut of Memory直到调整了内存堆外配置才彻底解决。背景为何选择本地化嵌入引擎核心业务是金融合规文档检索数据敏感度高不允许明文请求发送至第三方 API。此前使用text-embedding-ada-002API单次调用耗时 450ms±120ms且按 token 计费月度成本约 4 万元。内部评估后计划使用开源模型替换。技术栈基线Spring Boot 3.4.5Java 17.0.12Maven 3.9.9HuggingFace Transformers Java 端口 0.13.2基于 ONNX Runtime 1.19.0Redis 7.2.5 用于向量缓存最初设想很美好把模型文件bert-base-nli-mean-tokens.onnx约 90MB放入 classpath启动时加载到堆内存通过InferenceSession执行推理。过程从崩溃到稳定的三个阶段阶段一堆内存泄漏与 GC 频繁上线首日Tomcat 线程池耗尽。JVM 参数默认-Xmx1024m。使用jstat -gc观察YGYoung Gen回收频率高达每秒 15 次FGFull GC每 30 秒触发一次。根本原因每次请求都创建新的OnnxTensor对象且未释放 native memory。Transformers 底层调用的是 C 库Java 堆外的 Direct ByteBuffer 未手动清理导致 OS 级内存飙升。java// 错误示范每次请求都重新加载 Sessionpublic float[] embed(String text) {OnnxSession session OnnxSession.builder().loadModel(bert-base-nli-mean-tokens.onnx).build(); // 内存未复用GC 压力大long[] inputIds tokenizer.encode(text);OnnxTensor inputTensor OnnxTensor.createTensor(session.getInputs().get(0), new long[][]{inputIds});var results session.run(Map.of(input_ids, inputTensor));return results.get(0).asFloatBuffer().asArray();}阶段二静态初始化与预热改为单例模式将OnnxSession声明为static final在 Bean 初始化时加载。同时增加“预热”逻辑避免首个请求触发 JIT 编译延迟。javaComponentpublic class EmbeddingService {private static final OnnxSession SESSION;private static final Tokenizer TOKENIZER;static {try {SESSION OnnxSession.builder().loadModel(Paths.get(/models/bert-base-nli-mean-tokens.onnx)).addExecutionProvider(cpu, new XNNPACKExecutionProvider()).build();TOKENIZER BertTokenizer.fromPretrained();// 预热执行一次无效推理触发类加载与 JITSESSION.run(Map.of(input_ids, OnnxTensor.createTensor(SESSION.getInputs().get(0), new long[]{1, 2, 3})));} catch (Exception e) {throw new IllegalStateException(Model initialization failed, e);}}public float[] embed(String text) {long[] ids TOKENIZER.encode(text);var tensor OnnxTensor.createTensor(SESSION.getInputs().get(0), new long[][]{ids});var result SESSION.run(Map.of(input_ids, tensor));return result.get(0).asFloatBuffer().asArray();}}此阶段延迟降至 80ms但 72 小时后服务再次宕机。内存监控显示 RSSResident Set Size持续上涨但 Heap Usage 平稳。阶段三堆外内存治理查阅 ONNX Runtime 源码发现其内部使用 C 分配器Java 的System.gc()无法回收 Direct Buffer。必须显式释放 tensor 资源并限制堆外内存上限。在 JVM 启动参数中添加bash-Xmx2048m -Xms2048m -XX:MaxDirectMemorySize512m代码中改为使用try-with-resources或手动调用close()若框架支持并增加批处理逻辑将 10 条请求合并为一次推理摊薄上下文切换开销。javapublic List batchEmbed(List texts) {if (texts.isEmpty()) return Collections.emptyList();var input new long[texts.size()][];for (int i 0; i texts.size(); i) {input[i] TOKENIZER.encode(texts.get(i));}var tensor OnnxTensor.createTensor(SESSION.getInputs().get(0), input);var results SESSION.run(Map.of(input_ids, tensor));var buffer results.get(0).asFloatBuffer();List outputs new ArrayList();float[] vector new float[768];for (int i 0; i texts.size(); i) {buffer.get(vector);outputs.add(vector.clone());}return outputs;}质疑点官方文档推荐直接使用TransformerPipeline高级 API但在高并发场景下其内部同步锁导致吞吐量极低。直接操作InferenceSession虽繁琐但可控性更高。这个方案虽然官方不主推但在我们场景下反而更糟不实际上手动管理 session 比 Pipeline 稳定得多Pipeline 在处理长文本时的内存峰值不可预测。效果数据对比上线两周后监控数据如下表| 指标 | API 方案 (Optimizely Embedding) | 本地 ONNX 方案 | 变化 || :--- | :--- | :--- | :--- || P99 延迟 | 620ms | 95ms | ↓ 84.7% || 单请求成本 | ¥0.0028 | ¥0.0003 (电费折旧) | ↓ 89.3% || JVM 堆外内存 | N/A | 480MB (稳定) | 可控 || 吞吐量 (TPS) | 120 (受限于网络) | 450 | ↑ 275% || 可用性 | 99.5% (网络依赖) | 99.99% (本地计算) | ↑ |特别是向量检索命中率从 62% 提升至 81%因为嵌入维度更贴合中文金融术语本地微调版模型 vs 通用英文模型。总结核心经验有三点JVM 与 C 混合内存模型是陷阱ONNX Runtime 等 JNI 调用必须明确MaxDirectMemorySize否则 GC 形同虚设。批处理优于逐条即使业务是单条请求内部累积 10ms 内的请求进行批量推理可提升 GPU/CPU 利用率 30% 以上。版本锁定Transformers 4.49.0 与 ONNX Runtime 1.19.0 的兼容性存在已知 Issue #3021若使用 4.47.0 版本需回退 Runtime 至 1.17.0否则会导致向量偏移。#后端 #Java #SpringBoot #ONNX #向量检索你在实际项目中有遇到类似问题吗欢迎在评论区分享你的经验和解决方案。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

if条件判断全解析:原理、陷阱与重构思路 2026/9/29 23:12:04

if条件判断全解析:原理、陷阱与重构思路

接手第一个像样的业务需求时,我坐在工位上盯着需求文档看了整整一个下午,内容翻来覆去其实就几句话:满一百减十元、会员再打九五折、优惠券和促销不能叠加。那时我以为难点在算账,真正动手写代码才发现,所有业务规则最…

阅读更多 →
Pdf转Word,免费的网站列表 2026/9/29 23:12:03

Pdf转Word,免费的网站列表

这里,我只说免费的、鼠标点两下就搞定的方法。地址转换效果限制OCR在线编辑特点https://001pdf.com高;内容流式无免 费无国内https://smallpdf.com高;内容流式每天3个付 费有国外https://ilovepdf.com低;内容固定,后期…

阅读更多 →
使用LM Studio在WordPress基于大模型原创文章上稿进行SEO优化 2026/9/29 23:11:49

使用LM Studio在WordPress基于大模型原创文章上稿进行SEO优化

在进行自动化文章生成与发布的流程中,首先需要确保基础配置的完善性和数据的准确性。通过手动设置分类和标签,文章能够在发布时被准确归类,从而提升SEO的效果。通过Excel表格的方式管理这些分类与标签,结合Python脚本,可以高效地实现自动化文章的生成和发布。 该流程依赖…

阅读更多 →
【万字长文】深入解析8种LLM Agents开发框架:MCP Server全集成实战指南! 2026/9/29 23:11:43

【万字长文】深入解析8种LLM Agents开发框架:MCP Server全集成实战指南!

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
使用 Cursor + Claude Sonnet 4 的一些感受:从 settings.json 到 CC Switch 的配置记录 2026/9/29 23:11:42

使用 Cursor + Claude Sonnet 4 的一些感受:从 settings.json 到 CC Switch 的配置记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
多种方案对比实现 Kaggle 比赛介绍进行行业分类 2026/9/29 23:11:42

多种方案对比实现 Kaggle 比赛介绍进行行业分类

Kaggle 平台汇集了大量来自不同行业的数据科学竞赛,但这些比赛的标题或简介往往表述多样、不易直接归类。无论是做项目归档、行业研究,还是搭建竞赛推荐系统,都需要一个可靠的方法来将比赛自动归入对应行业标签。 本教程提供使用 HuggingFace 的 zero-shot pipeline 和 Sen…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉