在AI浪潮席卷各行各业的今天,大多数Java开发者面临着一个尴尬的困境:明明身在Java技术栈,却不得不为了AI能力去学习Python、维护一套独立的Python服务、处理跨语言调用的各种麻烦。

如果你也有类似的困扰,那么这篇文章正是为你准备的。本文将带你认识Deep Java Library(DJL)—— Java生态中目前唯一真正意义上的AI框架,并阐述为什么“Python训练 + ONNX + DJL部署”是当下Java团队接入AI能力的最优路径。

一、背景:DJL是什么?它从何而来?

1.1 DJL介绍

Deep Java Library(DJL)是亚马逊在2019年12月的AWS re:Invent大会上正式宣布开源的一个深度学习开发包。它是一个引擎无关(Engine-Agnostic)的深度学习框架,使用原生Java概念构建,为Java开发者提供了训练和部署深度学习模型的能力。

简单来说,DJL不是一个“重新发明轮子”的深度学习框架——它不做底层的计算内核,而是做工程胶水。它在上层提供统一的 NDArrayModelPredictor 等Java风格API,底层可以无缝切换PyTorch、TensorFlow、ONNX Runtime等多种引擎。

1.2 发展历程:从AWS实验室到独立社区

DJL的发展历程颇具代表性。它于2019年在AWS re:Invent上开源,最初托管在awslabs组织下。随着社区的迅速壮大,2021年5月,DJL从awslabs迁移到了一个独立的GitHub组织deepjavalibrary。这一举措标志着DJL不再仅仅是AWS内部的一个实验性项目,而成长为一个真正独立的Java深度学习社区。

截至目前,DJL在GitHub上已获得超过4800颗Star、750+ fork,社区持续活跃。版本迭代从未停歇——从2021年的0.11.0到2025年底的0.36.0,DJL保持着稳定的发布节奏。每一次版本更新都在不断扩展引擎支持、优化性能和增强稳定性。

1.3 为什么说DJL是“Java界唯一的AI框架”?

在DJL出现之前,Java开发者想搞AI,选择非常有限:

  • Deeplearning4j(DL4J) :2014年诞生,是JVM生态的老牌深度学习库。但它与主流Python生态的模型格式兼容性较差,社区活跃度也远不如Python阵营。

  • TensorFlow Java API:长期停留在1.x时代,对现代模型(如Transformer、LSTM等)的支持残缺不全。

  • ONNX Runtime Java API:提供了推理能力,但缺乏完整的模型生命周期管理、预处理/后处理管道等高层抽象。

DJL的出现填补了这个空白。它既兼容主流Python生态训练出的模型(PyTorch、TensorFlow、ONNX等),又提供了Java开发者熟悉的API风格和完整的工程化能力。这正是它能在短短几年内成为Java AI部署事实标准的核心原因。

二、为什么说“Python训练 + DJL部署”是最佳实践?

2.1 认清现实:Python在训练端不可替代

我们必须承认一个事实:Python在AI研究和模型训练领域的主导地位短期内不会改变。PyTorch、TensorFlow、Hugging Face Transformers等工具链已经形成了极其完善的生态,算法工程师用Python训练模型是最高效的方式。

但这不意味着Java开发者必须全程使用Python。训练和部署是两个完全不同的场景,它们对技术栈的要求截然不同:

  • 训练:需要灵活的调试环境、丰富的实验工具、快速迭代的能力 → Python是王者

  • 部署:需要高并发、低延迟、稳定可靠、与现有系统无缝集成 → Java是王者

强行要求算法团队用Java写训练代码,或强行要求Java团队用Python写生产服务,都是不经济的。

2.2 Java部署的独特优势

为什么说Java是AI部署的更优选择?数据会说话:

  • Java/JVM承载了全球68%的企业级应用。绝大多数后端系统、大数据平台、微服务架构都运行在Java生态中。

  • Netflix使用DJL进行分布式深度学习推理,处理日志数据时单事件延迟仅7ms

  • 某头部互联网公司将CTR模型从TensorFlow转为ONNX + DJL后,推理延迟从22ms降至15ms

  • Amazon Search将TensorFlow模型转为ONNX + DJL后,延迟从12ms降至4ms

  • 国内某头部银行将OCR模型转为ONNX + DJL后,延迟从1s降至400ms

这些数据揭示了一个清晰的趋势:在推理部署场景中,Java + ONNX的组合往往能带来显著的性能提升。原因在于Java成熟的JIT编译、多线程优化和内存管理能力,以及ONNX Runtime底层C++算子的高效执行。

2.3 ONNX:连接Python训练与Java部署的桥梁

既然训练用Python、部署用Java,那么两者之间如何衔接?答案就是ONNX(Open Neural Network Exchange)。

ONNX是AI领域的开放标准格式,PyTorch、TensorFlow、PaddlePaddle等主流框架都已支持导出ONNX模型。这意味着:

  • 算法团队用熟悉的Python框架训练模型

  • 将模型导出为ONNX格式(一条命令的事)

  • Java团队用DJL加载ONNX模型直接推理

整个过程无需任何Python-to-Java的代码翻译无需部署额外的Python服务无需操心跨语言调用的网络开销和稳定性问题

2.4 DJL在部署端的核心优势

具体到DJL这个工具,它在部署端的优势可以归纳为以下几点:

① 原生Java体验,学习成本极低

DJL的API设计遵循Java惯用法,使用起来就像调用任何一个普通的Java库。对于熟悉Java的开发者来说,几乎没有学习曲线。

② 引擎无关,一次编写到处运行

你只需要写一套代码,底层可以在PyTorch、TensorFlow、ONNX Runtime之间自由切换。今天用ONNX部署,明天想换TensorRT获得更高性能?改一行配置就行。

③ 生产级性能

DJL原生支持多线程推理,在单个JVM中运行多线程推理时,吞吐量甚至超过了市面上大多数基于C++的模型服务器。它内置了NDManager自动管理张量内存生命周期,避免内存泄漏。

2.5 落地分工策略

环节技术选型负责团队
模型训练Python + PaddlePaddle/PyTorch/TensorFlow算法团队
模型导出ONNX算法团队
模型部署Java + DJL + ONNX RuntimeJava开发团队

三、代码实战

聊完背景和优势,干货环节正式开启。O(∩_∩)O 既然 DJL 被说得这么好,那么真正上手如何呢? 我可以告诉你:So Easy!接下来跟着我的步骤,保你不踩坑🕳,一次性上手。

本次实战的环境说明

  • Java:JDK 17 (请使用 Azul Zulu 发行版)
  • 包管理工具:maven
  • 推理设备:CPU
  • AI模型:使用 Paddle 端到端主体检测模型 picodet_lcnet_x2_5_640_mainbody

请大家按照如上环境配置,进行如下代码实战。尤其,在 Windows 平台下,不要使用 Oracle JDK(有Bug,会报错),一定使用 Azul Zulu 发行版(这里是坑一)

第 1 步:配置 maven pom

<properties>
	<maven.compiler.source>17</maven.compiler.source>
	<maven.compiler.target>17</maven.compiler.target>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<djl.version>0.36.0</djl.version>
	<onnxruntime.version>1.29.0</onnxruntime.version>
</properties>

<dependencies>	
	<!-- 1. DJL 核心 API -->
	<dependency>
		<groupId>ai.djl</groupId>
		<artifactId>api</artifactId>
		<version>${djl.version}</version>
	</dependency>

	<!-- 2. ONNX Runtime 引擎(主推理,高性能) -->
	<dependency>
		<groupId>ai.djl.onnxruntime</groupId>
		<artifactId>onnxruntime-engine</artifactId>
		<version>${djl.version}</version>
	</dependency>
	<dependency>
		<groupId>com.microsoft.onnxruntime</groupId>
		<artifactId>onnxruntime</artifactId>
		<version>${onnxruntime.version}</version>
	</dependency>

	<!-- 3. PyTorch 引擎(辅助,负责复杂 NDArray 预处理/后处理) -->
	<dependency>
		<groupId>ai.djl.pytorch</groupId>
		<artifactId>pytorch-engine</artifactId>
		<version>${djl.version}</version>
	</dependency>

	<!-- 日志 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>2.0.17</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>jul-to-slf4j</artifactId>
		<version>2.0.17</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-classic</artifactId>
		<version>1.5.32</version>
	</dependency>
</dependencies>

第 2 步:日志配置

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 日志格式 -->
    <property name="LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 根日志级别 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>

    <!-- 可选:调试DJL时放开注释 -->
    <logger name="ai.djl" level="DEBUG"/>
    <logger name="ai.onnxruntime" level="DEBUG"/>
</configuration>

第 3 步:Java 代码编写

  • 模型加载与推理:PicodetLcnetMainbodyTest.java
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.onnxruntime.engine.OrtEngine;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import com.ty.translator.PicodetLcnetMainbodyTranslator;
import com.ty.utils.ImageUtil;
import com.ty.utils.LogUtil;
import com.ty.utils.OnnxUtil;
import org.apache.commons.io.FilenameUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Paths;
import java.util.List;

/**
 * Paddle 主体检测模型 picodet_lcnet_x2_5_640_mainbody 测试
 *
 * @Author Tommy
 * @Date 2026/8/18
 */
public class PicodetLcnetMainbodyTest {

    static String ONNX_PATH = "/path/to/picodet_lcnet_x2_5_640_mainbody/inference.onnx";
    static String IMG_PATH = "/path/to/drink_dataset_v2.0/test_images/005.jpeg";
    static double THRESHOLD = 0.2;

    static {
        OnnxUtil.config();
        LogUtil.enabel_jul();
    }

    public static void main(String[] args) {
        // 1. 使用 Criteria 构建模型加载器
        Criteria<Image, DetectedObjects> criteria = Criteria.builder()
                .optEngine(OrtEngine.ENGINE_NAME)
                .optModelPath(Paths.get(ONNX_PATH)) // 指定ONNX模型文件路径
                .setTypes(Image.class, DetectedObjects.class)
                .optTranslator(new PicodetLcnetMainbodyTranslator()) // 前置处理与后置处理的Translator
                .optModelName("PicodetLcnetMainbodyModel")
                .optProgress(new ProgressBar())
                .build();

        // 2. 加载模型
        try (ZooModel<Image,DetectedObjects> model = criteria.loadModel()) {
            System.out.printf("成功加载 ONNX 模型:%s%n", ONNX_PATH);

            // 3. 创建 Predictor(推理对象)
            try (Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
                // 4. 载入图片
                Image inputImage = ImageFactory.getInstance().fromFile(Paths.get(IMG_PATH));

                // 5. 推理
                DetectedObjects results = predictor.predict(inputImage);

                // 6. 根据阈值, 过滤结果
                List<DetectedObjects.DetectedObject> resultList = results.topK(5).stream().map(r -> (DetectedObjects.DetectedObject) r).filter(r -> r.getProbability() >= THRESHOLD).toList();
                System.out.println("推理结果:\n " + resultList);

                // 7. 将结果绘制到图片
                ImageUtil.drawBoundingBoxes(inputImage, resultList);

                // 8. 保存绘制后的图片
                File outputFile = new File("result_" + FilenameUtils.getBaseName(IMG_PATH) + ".png");
                inputImage.save(new FileOutputStream(outputFile), "png");
                System.out.println("推理结果图片保存至:" + outputFile.getAbsolutePath());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • DJL Translator 前处理与后处理:PicodetLcnetMainbodyTranslator.java
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.output.Rectangle;
import ai.djl.modality.cv.util.NDImageUtils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.ndarray.types.DataType;
import ai.djl.ndarray.types.Shape;
import ai.djl.translate.NoBatchifyTranslator;
import ai.djl.translate.TranslatorContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

/**
 * Paddle 主体检测模型 前置处理与后置处理的Translator
 *
 * @Author Tommy
 * @Date 2026/8/18
 */
public class PicodetLcnetMainbodyTranslator implements NoBatchifyTranslator<Image, DetectedObjects> {

    private final float[] mean = {0.485f, 0.456f, 0.406f};
    private final float[] std = {0.229f, 0.224f, 0.225f};

    /**
     * 前置处理
     */
    @Override
    public NDList processInput(TranslatorContext ctx, Image input) throws Exception {
        long begin = System.currentTimeMillis();

        int origH = input.getHeight();
        int origW = input.getWidth();
        NDManager manager = ctx.getNDManager();
        NDArray img = input.toNDArray(manager);

        // 1. Resize 到 640x640,使用 INTER_CUBIC
        int targetSize = 640;
        img = NDImageUtils.resize(img, targetSize, targetSize, Image.Interpolation.BICUBIC);

        // 2. 归一化:缩放至 [0,1] 并 z-score标准化
        img = img.toType(DataType.FLOAT32, false);
        NDArray meanArray = manager.create(mean, new Shape(1, 1, 3));
        NDArray stdArray = manager.create(std, new Shape(1, 1, 3));

        img = img.div(255.0f);
        img = img.sub(meanArray).div(stdArray);

        // 3. 转置为 CHW 并增加 batch 维度
        img = img.transpose(2, 0, 1).expandDims(0);

        // 4. 计算缩放比
        float im_scale_y = (float) targetSize / origH;
        float im_scale_x = (float) targetSize / origW;
        NDArray scale_factor = manager.create(new float[] {im_scale_y, im_scale_x}, new Shape(1, 2));

        long end = System.currentTimeMillis();
        System.out.printf("前置处理耗时:%d ms %n", end - begin);

        // 5.按照 ONNX模型 输入顺序构造 NDList:image 在前,scale_factor 在后
        img.setName("image");
        scale_factor.setName("scale_factor");
        return new NDList(img, scale_factor);
    }

    /**
     * 后置处理
     */
    @Override
    public DetectedObjects processOutput(TranslatorContext ctx, NDList list) throws Exception {
        NDArray output = list.get(0);
        Shape shape = output.getShape();
        int rows = (int) shape.get(0); // 100
        int cols = (int) shape.get(1); // 6

        System.out.println(list);
        System.out.println(output.toDebugString((int) shape.size(), shape.dimension(), rows, cols, true));

        // 按行分组,每行6个元素
        float[] data = output.toFloatArray(); // 一次性拷贝 NDArray 全部数据
        float[][] rowsArray = IntStream.range(0, rows)
                .mapToObj(i -> Arrays.copyOfRange(data, (i * cols), ((i + 1) * cols)))
                .toArray(float[][]::new);

        // 封装推理结果
        List<String> classNames = new ArrayList<>(rows);
        List<Double> probabilities = new ArrayList<>(rows);
        List<BoundingBox> boundingBoxes = new ArrayList<>(rows);
        for (int i = 0; i < rows; i++) {
            String id = String.valueOf((int)rowsArray[i][0]); // 第一个值:分类ID
            double score = rowsArray[i][1]; // 第二个值:置信度

            // 3~6:矩形框4个坐标
            float x1 = rowsArray[i][2];
            float y1 = rowsArray[i][3];
            float x2 = rowsArray[i][4];
            float y2 = rowsArray[i][5];

            classNames.add(id + "_foreground," + Math.round(score * 100) / 100f);
            probabilities.add(score);
            boundingBoxes.add(new Rectangle(x1, y1, (x2 - x1), (y2 - y1)));
        }

        return new DetectedObjects(classNames, probabilities, boundingBoxes);
    }
}
  • Onnx Util(可选):OnnxUtil.java
import java.nio.file.Paths;

/**
 * Onnx Util
 *
 * @Author Tommy
 * @Date 2026/8/18
 */
public class OnnxUtil {

    public static final String ONNX_ENGINE_PATH = Paths.get(System.getProperty("user.home"), ".djl.ai", "onnx", "win-x64").toString();

    public static void config() {
        /*
         * 以下操作解决此问题:ONNX Runtime 每运行一次,都会将 onnxruntime.dll onnxruntime4j_jni.dll onnxruntime_providers_shared.dll 解压到一个临时目录
         * OnnxRuntime 源码中,finally 执行了 cleanUp (通过 File.deleteOnExit 实现),但它只能删除非空目录。因此,会造成大量垃圾数据。
         */

        // 设置ONNX引擎路径(可选,但建议设置)
        System.setProperty("onnxruntime.native.path", ONNX_ENGINE_PATH);

        System.out.printf("ONNX引擎路径:%s%n", ONNX_ENGINE_PATH);

        // 彻底杜绝任何网络请求(包括引擎 native 库的下载),可以设置离线模式
        //System.setProperty("ai.djl.offline", "true");
    }
}

说明:ONNX_ENGINE 相关 dll,全部在 onnxruntime.jar 中,可以从这里copy出来。对于初次使用的同学,可以在 PicodetLcnetMainbodyTest  中先注释掉 OnnxUtil.config() 这句代码。优先体验DJL,后续再关注DJL优化事宜。

  • Log Util 将 onnxruntime 的jul日志流转到slf4j,便于观察DJL运行,有利于深入了解
import org.slf4j.bridge.SLF4JBridgeHandler;

import java.util.logging.Level;
import java.util.logging.LogManager;

/**
 * Log Util
 *
 * @Author Tommy
 * @Date 2026/8/18
 */
public class LogUtil {

    public static void enabel_jul() {
        // 1. (可选) 重置 JUL 配置,移除所有已有的 Handler
        LogManager.getLogManager().reset();

        // 2. 移除 JUL 根 Logger 上可能存在的旧 Handler
        SLF4JBridgeHandler.removeHandlersForRootLogger();

        // 3. 安装 SLF4J 桥接器,将 JUL 日志转发给 SLF4J
        SLF4JBridgeHandler.install();

        // 4. 重要:设置 JUL 根 Logger 的级别,确保日志能传递到 BridgeHandler
        // JUL 默认级别是 INFO,如果需要 DEBUG 级别的日志,必须显式设置
        java.util.logging.Logger.getLogger("").setLevel(Level.FINEST); // FINEST 对应 TRACE
    }
}
  • 绘图工具类Image Util,用于可视化推理结果
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.output.DetectedObjects.DetectedObject;
import ai.djl.modality.cv.output.Rectangle;

import java.util.ArrayList;
import java.util.List;

/**
 * Image 工具类
 *
 * @Author Tommy
 * @Date 2026/8/18
 */
public class ImageUtil {

    /**
     * Draws the bounding boxes on the image.
     *
     * @param image DJL 图像
     * @param list  推理结果集合
     */
    public static void drawBoundingBoxes(Image image, List<DetectedObject> list) {
        int w = image.getWidth();
        int h = image.getHeight();
        int size = list.size();
        List<String> classNames = new ArrayList<>(size);
        List<Double> probabilities = new ArrayList<>(size);
        List<BoundingBox> boundingBoxes = new ArrayList<>(size);

        for (DetectedObject r : list) {
            classNames.add(r.getClassName());
            probabilities.add(r.getProbability());

            // DJL绘图 默认使用归一化坐标
            Rectangle box = r.getBoundingBox().getBounds();
            boundingBoxes.add(new Rectangle(box.getX() / w, box.getY() / h, box.getWidth() / w, box.getHeight()/ h));
        }
        image.drawBoundingBoxes(new DetectedObjects(classNames, probabilities, boundingBoxes));
    }
}

四、测试

4.1 分步日志解析

  • 运行 main 函数后,首先,看到如下日志,说明模型加载成功。
ONNX引擎路径:C:\Users\Tommy\.djl.ai\onnx\win-x64
2026-08-18 16:36:27.834 [main] DEBUG a.d.repository.RepositoryFactoryImpl - mount-s3 is not supported on Windows
2026-08-18 16:36:27.837 [main] DEBUG a.d.repository.RepositoryFactoryImpl - gcsfuse is not supported on Windows
2026-08-18 16:36:27.840 [main] DEBUG a.djl.repository.zoo.DefaultModelZoo - Scanning models in repo: class ai.djl.repository.SimpleRepository, file:/D:/models/picodet_lcnet_x2_5_640_mainbody.onnx
2026-08-18 16:36:27.843 [main] DEBUG ai.djl.repository.zoo.ModelZoo - Loading model with Criteria:
	Application: UNDEFINED
	Input: interface ai.djl.modality.cv.Image
	Output: class ai.djl.modality.cv.output.DetectedObjects
	Engine: OnnxRuntime
	ModelZoo: ai.djl.localmodelzoo

2026-08-18 16:36:27.844 [main] DEBUG ai.djl.repository.zoo.ModelZoo - Searching model in specified model zoo: ai.djl.localmodelzoo
2026-08-18 16:36:27.846 [main] DEBUG ai.djl.engine.Engine - Registering EngineProvider: RPC
2026-08-18 16:36:27.847 [main] DEBUG ai.djl.engine.Engine - Registering EngineProvider: OnnxRuntime
2026-08-18 16:36:27.848 [main] DEBUG ai.djl.engine.Engine - Registering EngineProvider: PyTorch
2026-08-18 16:36:27.849 [main] DEBUG ai.djl.engine.Engine - Found default engine: PyTorch
2026-08-18 16:36:31.902 [main] DEBUG ai.djl.repository.zoo.ModelZoo - Checking ModelLoader: djl://ai.djl.localmodelzoo/picodet_lcnet_x2_5_640_mainbody.onnx [
	ai.djl.localmodelzoo/inference.onnx/picodet_lcnet_x2_5_640_mainbody.onnx {}
]
2026-08-18 16:36:31.911 [main] DEBUG ai.djl.repository.MRL - Preparing artifact: file:/D:/models/picodet_lcnet_x2_5_640_mainbody.onnx, ai.djl.localmodelzoo/inference.onnx/inference.onnx {}
2026-08-18 16:36:31.911 [main] DEBUG ai.djl.repository.SimpleRepository - Skip prepare for local repository.
Loading:     100% |========================================|
2026-08-18 16:36:32.048 [main] DEBUG ai.onnxruntime.OnnxRuntime - Attempting to load native library 'onnxruntime' from specified path: C:\Users\Tommy\.djl.ai\onnx\win-x64
2026-08-18 16:36:32.079 [main] DEBUG ai.onnxruntime.OnnxRuntime - Loaded native library 'onnxruntime' from specified path
2026-08-18 16:36:32.079 [main] DEBUG ai.onnxruntime.OnnxRuntime - Attempting to load native library 'onnxruntime4j_jni' from specified path: C:\Users\Tommy\.djl.ai\onnx\win-x64
2026-08-18 16:36:32.082 [main] DEBUG ai.onnxruntime.OnnxRuntime - Loaded native library 'onnxruntime4j_jni' from specified path
2026-08-18 16:36:32.088 [main] DEBUG ai.onnxruntime.OnnxRuntime - Deleting C:\Users\Tommy\AppData\Local\Temp\onnxruntime-java16321184285320631155 on exit
2026-08-18 16:36:33.101 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\asmjit.dll
2026-08-18 16:36:33.106 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\c10.dll
2026-08-18 16:36:33.123 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\libiomp5md.dll
2026-08-18 16:36:33.144 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\libiompstubs5md.dll
2026-08-18 16:36:33.146 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\uv.dll
2026-08-18 16:36:33.153 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\fbgemm.dll
2026-08-18 16:36:33.168 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\torch_cpu.dll
2026-08-18 16:36:33.745 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\torch.dll
2026-08-18 16:36:33.748 [main] DEBUG ai.djl.pytorch.jni.LibUtils - Loading native library: C:\Users\Tommy\.djl.ai\pytorch\2.7.1-cpu-win-x86_64\0.36.0-djl_torch.dll
2026-08-18 16:36:33.780 [main] INFO  ai.djl.pytorch.engine.PtEngine - PyTorch graph executor optimizer is enabled, this may impact your inference latency and throughput. See: https://docs.djl.ai/master/docs/development/inference_performance_optimization.html#graph-executor-optimization
2026-08-18 16:36:33.800 [main] INFO  ai.djl.pytorch.engine.PtEngine - Number of inter-op threads is 4
2026-08-18 16:36:33.800 [main] INFO  ai.djl.pytorch.engine.PtEngine - Number of intra-op threads is 4
2026-08-18 16:36:33.804 [main] INFO  ai.djl.onnxruntime.engine.OrtModel - Onnx extension not found in classpath.
成功加载 ONNX 模型:D:/models/picodet_lcnet_x2_5_640_mainbody.onnx
  • 看到如下日志,说明推理成功
前置处理耗时:97 ms 

NDList size: 2
0 save_infer_model/scale_0.tmp_0: (100, 6) float32
1 save_infer_model/scale_1.tmp_0: (1) int32

save_infer_model/scale_0.tmp_0: (100, 6) cpu() float32
[[ 0.00000000e+00,  9.21961546e-01,  2.41165359e+02,  8.38219604e+01,  4.98690582e+02,  7.30696533e+02],
 [ 0.00000000e+00,  7.77122736e-01,  5.43242798e+02,  6.08345276e+02,  7.49417236e+02,  7.34613770e+02],
 [ 0.00000000e+00,  4.39203382e-01,  1.90857053e+00,  2.14971649e+02,  2.38050629e+02,  6.23279297e+02],
 [ 0.00000000e+00,  3.82271945e-01,  5.32699707e+02,  4.70653290e+02,  7.49765076e+02,  6.40688049e+02],
 [ 0.00000000e+00,  3.41555685e-01,  5.01894951e-01,  5.98395874e+02,  7.57524780e+02,  7.42105713e+02],
...
 [ 0.00000000e+00,  5.28227687e-02, -7.56917119e-01,  1.27942177e+02,  5.74728088e+01,  1.86737061e+02],
 [ 0.00000000e+00,  5.26817739e-02,  6.61091492e+02,  4.84884766e+02,  6.96175537e+02,  5.59596191e+02],
 [ 0.00000000e+00,  5.26706576e-02,  4.16414413e+01,  5.93630615e+02,  9.62088852e+01,  6.32613831e+02],
]

推理结果:
 [{"className": "0_foreground,0.92", "probability": 0.92196, "boundingBox": {"rect":[241.1653594970703,83.82196044921875,498.69056701660156,730.696533203125]}}, {"className": "0_foreground,0.78", "probability": 0.77712, "boundingBox": {"rect":[543.2427978515625,608.3452758789062,749.417236328125,734.61376953125]}}, {"className": "0_foreground,0.44", "probability": 0.43920, "boundingBox": {"rect":[1.9085705280303955,214.97164916992188,238.05062985420227,623.279296875]}}, {"className": "0_foreground,0.38", "probability": 0.38227, "boundingBox": {"rect":[532.69970703125,470.6532897949219,749.7650756835938,640.6880493164062]}}, {"className": "0_foreground,0.34", "probability": 0.34155, "boundingBox": {"rect":[0.5018949508666992,598.3958740234375,757.5247831344604,742.105712890625]}}]

推理结果图片保存至:D:\result_005.png

4.2 测试图片

4.3 测试结果对比

  • Python 推理结果

  • Java DJL 推理结果

结论:从推理结果进行对比,Java 与 Python 一致。

至此,Java 部署 AI 模型,全部结束,是不是 So so Easy 呀!O(∩_∩)O

最后

“Python训练 + ONNX导出 + DJL部署”是一条经过大规模生产验证的务实路径。它让算法团队做他们最擅长的事(训练模型),让Java团队做他们最擅长的事(构建高可用的生产系统)——各司其职,各取所长

希望这篇文章能让更多Java开发者了解DJL、使用DJL,让Java生态在AI时代继续发光发热。

附录

更多推荐