引言
虽然 Python 主导了机器学习生态,但大多数企业应用仍运行在 Java 上。这种脱节造成了部署瓶颈。用 PyTorch 或 Hugging Face 训练的模型在生产中往往需要 REST 封装、微服务或多语言变通方式才能运行。这些做法会增加延迟、提高复杂度,并削弱对系统的控制力。
对企业架构师而言,这个挑战并不陌生:如何在不破坏 Java 系统的简单性、可观测性和可靠性的前提下引入现代 AI?这个挑战延续了我们此前关于将 GPU 级性能带入企业 Java的探索,其中保持 JVM 原生的效率与可控性被证明至关重要。
开放神经网络交换(ONNX) 标准给出了有力答案。在微软的支持下并被各大框架广泛采用,ONNX 能让包含命名实体识别(NER)、分类、情感分析等在内的 Transformer 推理原生地在 JVM 中运行。无需 Python 进程,无需容器蔓延。
本文面向希望将机器学习推理引入 Java 生产系统的架构师,提供设计层面的指南。我们将探讨分词器集成、GPU 加速、部署模式以及在受监管的 Java 环境中安全、可扩展地运行 AI 的生命周期策略。
为什么对架构师重要
企业系统日益需要 AI 来驱动客户体验、自动化工作流、并从非结构化数据中提取洞见。但在金融、医疗等受监管领域,生产环境更看重可审计性、资源控制与 JVM 原生工具链。
虽然 Python 在试验与训练阶段表现出色,但部署到 Java 系统时会带来架构摩擦。将模型包装为 Python 微服务会割裂可观测性、扩大攻击面,并引入运行时不一致。
ONNX 改变了这种局面。它为在 Python 中训练并在 Java 内运行的模型提供标准化的导出格式,原生支持 GPU 加速且不依赖外部运行时。关于在 JVM 内直接利用 GPU 加速的更多模式,请参见:将 GPU 级性能带入企业 Java。
对架构师而言,ONNX 解锁四项关键收益:
- 语言一致性:推理运行在 JVM 内,而不是旁路的进程。
- 部署简化:无需管理 Python 运行时或 REST 代理。
- 基础设施复用:沿用现有的基于 Java 的监控、追踪与安全控制。
- 可扩展性:在需要时启用 GPU 执行,无需重构核心逻辑。
通过消除训练与部署之间的运行时不匹配,ONNX 让我们可以像对待任何可复用的 Java 模块一样对待 AI 推理:模块化、可观测、并可经受生产环境的考验。
设计目标
在 Java 中设计 AI 推理不只是模型准确度问题,更是将机器学习嵌入企业系统的架构、运营与安全肌理中。良好的设计会设定系统级目标,确保 AI 的采用在各环境中可持续、可测试、且符合法规约束。
以下设计目标来自在 Java 中构建机器学习服务的高绩效企业团队的成功实践:
消除生产中的 Python
ONNX 让团队可以将 Python 中训练的模型导出并在 Java 中原生运行,避免嵌入 Python 运行时、gRPC 桥接或容器化的 Python 推理服务,这些都会增加运维摩擦并复杂化安全部署。
支持可插拔的分词与推理
分词器与模型应当模块化且可配置。分词器文件(如 tokenizer.json
)和模型文件(如 model.onnx
)应可按用例互换。通过分词器与模型的组合,可以适配 NER、分类、摘要等任务,而无需重写代码或违反整洁架构原则。
确保 CPU -- GPU 的灵活性
相同的推理逻辑应当既能在开发者笔记本(CPU)上运行,也能无改动地扩展到生产 GPU 集群。ONNX Runtime 原生支持通过 CPU 与 CUDA 执行提供者运行推理,使跨环境一致性既可行也具成本效益。
优化为可预测延迟与线程安全
推理必须像任何企业级服务一样:确定性、线程安全、资源高效。干净的多线程、模型预加载与显式内存控制对于满足 SLA、增强可观测性并避免并发系统中的竞态条件至关重要。
设计为跨栈复用
基于 ONNX 的推理模块应能够整洁地集成到 REST API、批处理管道、事件驱动处理器与嵌入式分析层。将预处理、模型执行与后处理解耦,是实现复用、可测试与长期维护合规性的关键。
这些目标帮助企业团队在不牺牲架构完整性、开发敏捷性或合规要求的情况下采用机器学习。
系统架构概览
将机器学习推理引入企业级 Java 系统不仅仅是模型集成的问题,更需要清晰的架构分层与模块化。一个健壮的基于 ONNX 的推理系统应被设计为一组松耦合组件,每个组件负责推理生命周期中的特定环节。
核心流程从接收来自 REST 端点、Kafka 流或基于文件的集成等多种来源的输入开始。原始输入会交由分词器组件处理,将其转换为 Transformer 模型所需的数值格式。分词器通过一个兼容 Hugging Face 的 tokenizer.json
文件进行配置,确保与训练时使用的词表与编码方式一致。
完成分词后,输入流入 ONNX 推理引擎。该组件调用 ONNX Runtime,在 CPU 或 GPU 后端执行模型推理。如果可用 GPU 资源,ONNX Runtime 可无缝委派给基于 CUDA 的执行提供者,而无需更改应用逻辑。推理引擎返回一组预测,通常是logits(模型的原始、未经过 softmax 的输出分数)或类别 ID,随后由后处理模块进行解释。
后处理器将原始输出转化为有意义的领域实体,如标签、类别或抽取字段。最终结果会路由到下游消费者,无论是业务流程引擎、关系型数据库还是 HTTP 响应管道。
系统遵循整洁的架构流:适配器到分词器、分词器到推理引擎、推理引擎到后处理器、后处理器到消费者。每个模块都可以独立开发、测试与部署,使整个管道高度可复用与可维护。
图 1:Java 中可插拔的 ONNX 推理架构
通过将推理视为一条定义清晰的转换流水线,而非嵌入到单体服务内部的逻辑,架构师可以对性能、可观测性与部署进行精细化控制。这种模块化方法也支持模型的演进,能够在不破坏系统稳定性的情况下更新分词器或 ONNX 模型。
模型生命周期
在多数企业场景中,机器学习模型在 Java 生态之外进行训练,通常使用 Hugging Face Transformers 或 PyTorch 等 Python 框架。模型定稿后,会连同其分词器配置一起导出为 ONNX 格式,生成 model.onnx
文件与兼容的 tokenizer.json
文件。
对于基于 Java 的推理系统,这些工件相当于版本化输入,类似外部 JAR 或模式文件。架构师应将其视为受控的部署资产:经过验证、测试,并在各环境间有序发布,遵循与代码或数据库迁移同样的纪律。
可重复的模型生命周期包括导出模型与分词器、用代表性用例进行测试,并存储到内部的注册表或制品库。在运行时,推理引擎与分词器模块通过配置加载这些文件,实现安全更新而无需重启或完整重新部署应用。
通过将模型与分词器提升为一等部署组件,团队可以获得可追溯性与版本控制。在受监管环境中,这种提升对于可复现性、可解释性与回滚能力至关重要。
分词器架构
分词器是 Transformer 推理系统中最容易被忽视却至关重要的组件之一。人们的注意力往往集中在模型上,但分词器负责将人类可读文本转换为模型需要的输入 ID 与注意力掩码。只要这一转换过程存在任何不匹配,就会出现静默失败------预测在语法上看似合理,语义上却不正确。
在 Hugging Face 生态中,分词逻辑被序列化到 tokenizer.json
文件中。该工件编码了词表、分词策略(例如字节对编码或 WordPiece)、特殊 token 处理与配置设置。它必须使用训练时完全相同的分词器类与参数生成。即便是细微差异,如缺少 [CLS] token 或词表索引偏移,都会降低性能或破坏推理输出。
从架构上看,分词器应以独立、线程安全的 Java 模块存在,消费 tokenizer.json
文件并生成可用于推理的结构。它必须接受原始字符串并返回结构化输出,包含 token ID、注意力掩码以及(可选)偏移映射,以便下游解释。在 Java 服务中直接嵌入这层逻辑,而不是依赖 Python 微服务,可以降低延迟并避免脆弱的基础设施依赖。
在 Java 中构建分词层还能实现监控、单元测试,并完整集成到企业 CI/CD 流水线,同时便于在禁止 Python 运行时的安全或受监管环境中部署。在我们的架构中,分词器是一个模块化的运行时组件,能够动态加载 tokenizer.json
文件并在不同模型与团队之间复用。
推理引擎
一旦输入文本被转换为 token ID 与注意力掩码,推理引擎的核心任务就是将这些张量传入 ONNX 模型并返回有意义的输出。在 Java 中,这一过程由 ONNX Runtime 的 Java API 负责,提供成熟的绑定以加载模型、构造张量、执行推理与获取结果。
推理引擎的核心是 OrtSession
类,它是 ONNX 模型的编译、初始化后的表示,可在请求之间复用。该会话应在应用启动时初始化,并在各线程之间共享。每次请求都重建会话会引入不必要的延迟与内存压力。
准备输入涉及创建 NDArray
张量,如 input_ids
、attention_mask
,以及可选的 token_type_ids
,这些都是 Transformer 模型通常期望的标准输入。张量由 Java 原生数据结构构造,再传入 ONNX 会话。会话运行推理并生成输出,通常包括 logits、类别概率或结构化标签,具体取决于模型。
在 Java 中,推理调用通常如下:
java
OrtSession.Result result = session.run(inputs);
ONNX Runtime 还支持执行提供者,用以决定推理在 CPU 还是 GPU 上运行。在启用了 CUDA 的系统上,推理可以以最小配置卸载到 GPU。如果 GPU 资源不可用,则优雅地回退到 CPU,使得跨环境行为保持一致。这种灵活性让单一 Java 代码库可以从开发者笔记本扩展到生产 GPU 集群,无需分支逻辑,与我们在"将 GPU 级性能带入企业 Java"一文中讨论的理念一脉相承。
从架构角度看,推理引擎必须保持无状态、线程安全与资源高效。它应提供干净的可观测性接口:日志、追踪与结构化错误处理。对于高吞吐场景,池化与微批处理有助于优化性能;在低延迟语境下,内存复用与会话调优对于保持可预测的推理成本至关重要。
将推理视为具有清晰契约与可边界化性能特征的模块化服务,架构师即可将 AI 逻辑完全与业务工作流解耦,实现独立演进与可靠扩展。
部署模式
设计推理引擎只是挑战的一半;如何在企业环境中部署同样重要。Java 系统涵盖从 REST API 到 ETL 管道与实时引擎的广泛场景,因此基于 ONNX 的推理必须在不重复逻辑或割裂配置的前提下进行适配。
在多数情况下,分词器与推理引擎作为 Java 库直接嵌入,从而避免运行时依赖,并与日志、监控与安全框架整洁集成。在 Spring Boot 与 Quarkus 等框架中,推理就是另一个可注入的服务。
更大的团队常将这层逻辑外置为共享模块,负责分词器与模型加载、张量准备与 ONNX 会话执行。外置化促进复用、简化治理、并在各服务之间提供一致的 AI 行为。
在具备 GPU 的环境中,可以通过配置启用 ONNX Runtime 的 CUDA 提供者,无需更改代码。相同的 Java 应用既能在 CPU 集群上运行,也能在 GPU 集群上运行,使部署既可移植又资源感知。
模型制品既可随应用打包,也可从模型注册表或挂载卷动态加载。后者支持热替换、回滚与 A/B 测试,但需要谨慎的验证与版本管理。关键在于灵活性。一个可插拔、对环境敏感的部署模型,无论是嵌入式、共享式还是容器化,都能确保推理无缝融入现有的 CI/CD 与运行策略。
与框架层抽象的比较
Spring AI 等框架通过为 OpenAI、Azure 或 AWS Bedrock 等提供者提供客户端抽象,简化了调用外部大型语言模型的过程。这些框架在原型化对话界面与检索增强生成(RAG)管道方面很有价值,但其工作层次与基于 ONNX 的推理根本不同。前者将推理委托给远程服务,后者则在 JVM 内直接执行模型,使推理保持确定性、可审计且完全受企业控制。
这种区别具有实际影响。外部框架的输出不可重复,并依赖第三方提供者的可用性与不断演进的 API。相比之下,ONNX 推理使用版本化的工件,model.onnx
与 tokenizer.json
,在各环境中表现一致,从开发者笔记本到生产 GPU 集群都不例外。这种可重复性对合规与回归测试至关重要,因为模型行为的细微差异可能带来显著的下游影响。它也确保敏感数据不离开企业边界,这在金融与医疗等领域是基本要求。
也许更为重要的是,ONNX 保持供应商中立。作为被各训练框架广泛支持的开放标准,组织可以自由使用偏好的生态进行训练,并在 Java 中部署,而不必担心供应商锁定或 API 漂移。由此,ONNX 与 Spring AI 等框架是互补关系而非竞争关系。前者为合规关键的工作负载提供稳定的、进程内的基础;后者帮助开发者在应用边缘快速探索生成式用例。对架构师而言,能够清晰划定这条边界,才能确保 AI 采用既具创新性又在运营上可持续。
下一步
既然我们已经阐明如何通过原生分词与无状态推理层将 ONNX 模型集成到 Java 系统中,下一步的挑战是在生产环境中安全、可靠地扩展这套架构。
在下一篇文章中,我们将探讨:
- Java 中的安全与审计:实现可追踪、可解释且符合法治的 AI 管道。
- 可扩展的推理模式:在 CPU/GPU 线程、异步作业队列与高吞吐管道间负载均衡,使用 Java 原生构造。
- 内存管理与可观测性:剖析推理内存占用、追踪慢路径,并使用 JVM 原生工具调优延迟。
- 超越 JNI 的演进:动手解读外部函数与内存 API(JEP 454)作为未来可靠推理管道对 JNI 的替代方案。
作者注:本文实现基于独立的技术研究,不代表任何特定组织的架构。
本文翻译自:https://www.infoq.com/articles/onnx-ai-inference-with-java/,作者:Syed Danish Ali