
引言
尽管 Python 在机器学习生态系统中占据主导地位,但大多数企业应用仍然运行在 Java 上。这种不匹配性造成了部署瓶颈。在PyTorch或Hugging Face中训练的模型通常需要 REST 包装器、微服务或多语言等变通方法才能在生产环境中运行。这些方式增加了延迟,增加了复杂性,并使可控性受到了影响。
对于企业架构师来说,这个挑战是非常熟悉的:我们如何在不破坏基于 Java 系统的简单性、可观测性和可靠性的情况下集成现代 AI 呢?这个挑战建立在将GPU级别的性能带入企业级Java的早期探索之上,其中保持 JVM 的原生效率和可控性至关重要。
开放神经网络交换(Open Neural Network Exchange,ONNX)标准提供了一个值得关注的答案。ONNX 得到了微软的支持,并支持多个主要的框架,使基于 transformer 的推理能够在 JVM 中原生运行,包括命名实体识别(Named Entity Recognition,NER)、分类和情感模型。它不需要 Python 进程,不会出现容器扩散。
本文为寻求将机器学习推理引入 Java 生产系统的架构师提供了一个设计层面的指南。它探讨了分词器集成、GPU 加速、部署模式和生命周期策略,以便在受监管的 Java 环境中安全、可扩展地运维 AI。
这对架构师为何如此重要
企业系统越来越需要 AI 来改进客户体验、自动化工作流程和从非结构化数据中提取洞察信息。但在金融和医疗保健等受监管的领域,生产环境优先要考虑可审计性、资源控制和 JVM 原生工具。
虽然 Python 在实验和训练方面表现出色,但在部署到 Java 系统时会带来架构冲突。将模型包装为 Python 微服务会破坏可观测性,增加暴露面积,并引入运行时不一致性。
ONNX 改变了这一现状。它提供了一个标准化的格式,用于导出在 Python 中训练的模型,并在 Java 中运行它们,它原生支持 GPU 加速,并且不依赖外部运行时。关于直接在 JVM 内利用 GPU 加速的额外模式,请参见“将GPU级别的性能带入企业级Java”一文。
对于架构师来说,ONNX 解锁了四项关键收益:
在 JVM 中使用一致的语言运行推理,而不是作为 sidecar。
部署简单,无需管理 Python 运行时或 REST 代理。
通过利用现有的基于 Java 的监控、跟踪和安全控制,重用基础设施。
可扩展性,在需要时提供 GPU 执行,无需重构核心逻辑。
通过消除训练和部署之间的运行时不匹配,ONNX 使得我们可以将 AI 推理视为其他可重用的 Java 模块,符合模块化、可观测性和生产环境强化。
设计目标
在使用 Java 进行 AI 推理的设计中,不仅仅关系到模型准确性,还要考虑到将机器学习嵌入到企业系统的架构、运维和安全网中。对于架构师来说,好的设计设定了系统级目标,确保对 AI 的应用是可持续的、可测试的,并在各个环境中符合规定。
以下设计目标反映了在 Java 中构建 ML 驱动服务的高性能企业级团队所观察到的成功模式:
从生产中消除 Python
ONNX 使团队能够导出在 Python 中训练的模型,并在 Java 中原生运行它们,消除了对嵌入式 Python 运行时、gRPC 桥或容器化 Python 推理服务器的需求,这些都会添加运维冲突并使安全部署复杂化。
支持可插拔的分词和推理
分词器(tokenizer)和模型应该是模块化和可配置的。分词器文件(如tokenizer.json
)和模型文件(如model.onnx
)应该能够根据用例的差异实现互相替换。分词器和模型促进了对 NER、分类和摘要等任务的适应性,无需重写代码或违反清晰架构原则。
确保 CPU-GPU 灵活性
相同的推理逻辑应该能够在开发人员的笔记本电脑(CPU)上运行,并扩展到生产环境的 GPU 集群中,而不需要任何代码更改。ONNX Runtime 通过 CPU 和 CUDA 执行提供程序(execution provider)原生支持这种推理逻辑,使跨环境一致性既可行又具有成本效益。
优化可预测的延迟和线程安全性
推理的表现必须像任何其他企业级服务一样,实现确定性、线程安全性和资源高效性。清晰的多线程、模型预加载和显式内存控制对于满足 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 生态系统之外训练的,通常使用 Python 和 Hugging Face Transformers 或 PyTorch 等框架。一旦最终确定,模型会被导出为 ONNX 格式,连同它们的分词器配置,产生一个 model.onnx 文件和一个兼容的 tokenizer.json 文件。
对于基于 Java 的推理系统来说,这些制品会作为版本化的输入,类似于外部 JAR 或模式文件。架构师应该将它们视为受控的部署资产:经过了验证、测试,并在环境之间,要按照与代码或数据库迁移相同的纪律进行推广。
可重复的模型生命周期包括导出模型和分词器,对它们进行代表性案例的测试,并将它们存储在内部注册中心或制品存储中。在运行时,推理引擎和分词器模块通过配置加载这些文件,实现安全更新而不需要完整的应用程序重新部署。
通过将模型和分词器提升为一级部署组件,团队获得了可追溯性和版本控制。在需要可复制性、可解释性和回滚能力的受监管环境中,这种提升至关重要。
分词器架构
分词器是基于 transformer 的推理系统中最容易被忽视但至关重要的组件之一。虽然注意力通常集中在模型上,但是分词器会将人类可读的文本转换为模型所需的输入 ID 和注意力掩码(attention mask)。在这个转换过程中的任何不匹配都会导致静默失败,即预测看起来在语法上有效,但在语义上是错误的。
在 Hugging Face 生态系统中,分词逻辑被序列化在一个tokenizer.json
文件中。这个制品编码了词汇表、分词策略(如字节对编码(Byte-Pair Encoding)或WordPiece)、特殊 token 处理和配置设置。它必须严格使用训练期间所使用的分词器类和参数来生成。即使是微小的差异,如缺少[CLS] token 或移动的词汇表索引,都可能会降低性能或破坏推理输出。
从架构上讲,分词器应该作为一个独立的、线程安全的 Java 模块存在,它消费 tokenizer.json 文件并产生推理就绪的结构。它必须接受原始字符串并返回包含 token ID、注意力掩码和(可选)下游解释的偏移映射的结构化输出。将这种逻辑直接嵌入到 Java 服务中,而不是依赖于基于 Python 的微服务,可以减少延迟并避免脆弱的基础设施依赖。
在 Java 中构建分词器层可以实现监控、单元测试,并完全集成到企业 CI/CD 流程中,有助于在禁止 Python 运行时的安全或受监管环境中部署。在我们自己的架构中,分词器是一个模块化的运行时组件,动态加载 tokenizer.json 文件,并支持跨模型和团队的重用。
推理引擎
输入文本被转换为标记 ID 和注意力掩码后,推理引擎的核心任务就是将这些张量(tensor)传递到 ONNX 模型并返回有意义的输出。在 Java 中,这个过程是使用ONNX Runtime的Java API来处理的,它为加载模型、构建张量、执行推理和检索结果提供了成熟的绑定。
这个引擎的核心是OrtSession
类,这是一个已编译和初始化的 ONNX 模型表述,可以在请求之间进行重用。这个会话应该在应用程序启动时初始化一次,并在线程之间共享。如果每个请求都重新创建会话,将引入不必要的延迟和内存压力。
准备输入涉及到创建NDArray
张量,如input_ids
、attention_mask
,以及可选的token_type_ids
,这些是 transformer 模型预期的标准输入字段。这些张量是从 Java 原生数据结构构建的,然后传递到 ONNX 会话中。会话运行推理并产生输出,通常包括 logits、类别概率或结构化标签,具体取决于模型。
在 Java 中,推理调用通常看起来如下所示:
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 等提供程序提供客户端抽象,简化了调用外部大型语言模型的过程。这些框架对于原型化会话接口和检索增强生成(Retrieval-Augmented Generation,RAG)管道非常有价值,但它们在根本上与基于 ONNX 的推理操作在不同的层级。Spring AI 将推理委托给远程服务,而 ONNX 在 JVM 内部直接执行模型,使推理保持确定性、可审计性,并完全在企业控制之下。
这种区别会带来实际的后果。外部框架可能产生不可重复的输出,并依赖于第三方提供程序的可用性和不断演变的 API。相比之下,ONNX 推理使用版本化的制品,即 model.onnx 和 tokenizer.json,在不同环境中表现一致,从开发人员的笔记本电脑到生产 GPU 集群均是如此。这种可重复性对于合规性和回归测试至关重要,模型行为的微小变化可能会对下游产生重大影响。它还确保敏感数据永远不会离开企业边界,这在金融和医疗保健等领域是一个基本要求。
也许最重要的是,ONNX 保持了供应商中立性。因为它是一个跨训练框架支持的开放标准,组织可以自由地使用他们偏好的生态系统来训练模型,并在 Java 中部署它们,而不必担心供应商锁定或 API 漂移。通过这种方式,ONNX 补充了像 Spring AI 这样的框架,而不是与它们竞争。前者为看重合规性的工作负载提供了一个稳定的、进程内的基础,而后者使开发人员能够在应用边缘快速探索生成性用例。对于架构师来说,能够清晰地划分这条线是确保 AI 采用保持创新和运营可持续的关键。
接下来的工作
现在我们已经知道了如何通过原生标记化和无状态推理层将 ONNX 模型集成到 Java 系统中,下一个逻辑挑战是在生产环境中安全、可靠地扩展这种架构。
在下一篇文章中,我们将探讨:
Java 中 AI 的安全和可审计性,用于实现符合企业治理政策和监管框架的可追溯、可解释的 AI 管道。
可扩展的推理模式,用于在 CPU/GPU 线程、异步作业队列和使用原生 Java 结构的高吞吐量管道中对 ONNX 推理实现负载均衡。
内存管理和可观测性,用于分析推理的内存占用、追踪慢路径和使用 JVM 原生工具调整延迟。
超越 JNI,在实际操作中查看如何使用外部函数和内存 API(Foreign Function & Memory API,JEP 454)作为未来推理管道中 JNI 的替代方案。
作者注:此实现基于独立的技术研究,并不反映任何特定组织的架构。
原文链接:
Bringing AI Inference to Java with ONNX: A Practical Guide for Enterprise Architects
评论