Repository: deepjavalibrary/djl
Pull request #1385 (2021-11-22 06:09:08)

[pytorch] move pytorch JNI into separate package
Change-Id: I78c950aa2ea3866d0a702617a2fcbb290d3e6a5e

## Description ##

Brief description of what this PR is about

- If this change is a backward incompatible change, why must this change be made?
- Interesting edge cases to note here


codecov-commenter: # [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) Report
> Merging [#1385](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) (bb246d8) into [master](https://codecov.io/gh/deepjavalibrary/djl/commit/bb5073f7ffc33f1883621fc1783dbb01e70b418f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) (bb5073f) will **decrease** coverage by `1.58%`.
> The diff coverage is `50.19%`.

[![Impacted file tree graph](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/graphs/tree.svg?width=650&height=150&src=pr&token=mDKVNwFXRV&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary)](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary)

```diff
@@             Coverage Diff              @@
##             master    #1385      +/-   ##
============================================
- Coverage     72.08%   70.50%   -1.59%     
- Complexity     5126     5234     +108     
============================================
  Files           473      487      +14     
  Lines         21970    23030    +1060     
  Branches       2351     2516     +165     
============================================
+ Hits          15838    16237     +399     
- Misses         4925     5527     +602     
- Partials       1207     1266      +59     
```


| [Impacted Files](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) | Coverage Δ | |
|---|---|---|
| [api/src/main/java/ai/djl/modality/cv/Image.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L0ltYWdlLmphdmE=) | `69.23% <ø> (-4.11%)` | :arrow_down: |
| [...i/djl/modality/cv/translator/BigGANTranslator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvQmlnR0FOVHJhbnNsYXRvci5qYXZh) | `21.42% <ø> (-5.24%)` | :arrow_down: |
| [...odality/cv/translator/BigGANTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvQmlnR0FOVHJhbnNsYXRvckZhY3RvcnkuamF2YQ==) | `33.33% <0.00%> (+8.33%)` | :arrow_up: |
| [...nslator/InstanceSegmentationTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvSW5zdGFuY2VTZWdtZW50YXRpb25UcmFuc2xhdG9yRmFjdG9yeS5qYXZh) | `14.28% <0.00%> (-3.90%)` | :arrow_down: |
| [.../modality/cv/translator/YoloTranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1RyYW5zbGF0b3JGYWN0b3J5LmphdmE=) | `8.33% <0.00%> (-1.67%)` | :arrow_down: |
| [...i/djl/modality/cv/translator/YoloV5Translator.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1Y1VHJhbnNsYXRvci5qYXZh) | `5.69% <0.00%> (ø)` | |
| [...odality/cv/translator/YoloV5TranslatorFactory.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L2N2L3RyYW5zbGF0b3IvWW9sb1Y1VHJhbnNsYXRvckZhY3RvcnkuamF2YQ==) | `8.33% <0.00%> (-1.67%)` | :arrow_down: |
| [...modality/nlp/embedding/TrainableWordEmbedding.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL21vZGFsaXR5L25scC9lbWJlZGRpbmcvVHJhaW5hYmxlV29yZEVtYmVkZGluZy5qYXZh) | `45.00% <0.00%> (-3.34%)` | :arrow_down: |
| [...pi/src/main/java/ai/djl/ndarray/BytesSupplier.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL25kYXJyYXkvQnl0ZXNTdXBwbGllci5qYXZh) | `54.54% <0.00%> (-12.13%)` | :arrow_down: |
| [...i/src/main/java/ai/djl/ndarray/NDArrayAdapter.java](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary#diff-YXBpL3NyYy9tYWluL2phdmEvYWkvZGpsL25kYXJyYXkvTkRBcnJheUFkYXB0ZXIuamF2YQ==) | `15.23% <0.00%> (-0.83%)` | :arrow_down: |
| ... and [139 more](https://codecov.io/gh/deepjavalibrary/djl/pull/1385/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary). Last update [29c18f5...bb246d8](https://codecov.io/gh/deepjavalibrary/djl/pull/1385?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deepjavalibrary).


Patch:
diff --git a/.github/workflows/codeql-analysis-java.yml b/.github/workflows/codeql-analysis-java.yml
index e860f350a..55cdae73e 100644
--- a/.github/workflows/codeql-analysis-java.yml
+++ b/.github/workflows/codeql-analysis-java.yml
@@ -36,6 +36,9 @@ jobs:
     - name: Checkout repository
       uses: actions/checkout@v2
 
+    - name: Init gradle
+      run: ./gradlew --no-daemon clean
+
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
       uses: github/codeql-action/init@v1
diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml
index b237bfbfe..1f4b381b3 100644
--- a/.github/workflows/continuous.yml
+++ b/.github/workflows/continuous.yml
@@ -43,72 +43,12 @@ jobs:
         run: ./gradlew :engines:paddlepaddle:paddlepaddle-native:compileJNI
       - name: Build with Gradle
         run: ./gradlew build jRR jRV
-      - name: API test results
-        uses: actions/upload-artifact@v1
+      - name: Upload test results
+        uses: actions/upload-artifact@v2
         if: always()
         with:
-          name: api
-          path: api/build/reports
-      - name: basicdataset test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: basicdataset
-          path: basicdataset/build/reports
-      - name: examples test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: examples
-          path: examples/build/reports
-      - name: integration test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: integration
-          path: integration/build/reports
-      - name: model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: model-zoo
-          path: model-zoo/build/reports
-      - name: mxnet-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: mxnet-engine
-          path: engines/mxnet/mxnet-engine/build/reports
-      - name: mxnet-model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: mxnet-model-zoo
-          path: engines/mxnet/mxnet-model-zoo/build/reports
-      - name: pytorch-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: pytorch-engine
-          path: engines/pytorch/pytorch-engine/build/reports
-      - name: pytorch-model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: pytorch-model-zoo
-          path: engines/pytorch/pytorch-model-zoo/build/reports
-      - name: tensorflow-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: tensorflow-engine
-          path: engines/tensorflow/tensorflow-engine/build/reports
-      - name: tensorflow-model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: tensorflow-model-zoo
-          path: engines/tensorflow/tensorflow-model-zoo/build/reports
+          name: reports
+          path: ${{ github.workspace }}/**/build/reports/**/*
       - name: upload to codecov
         uses: codecov/codecov-action@v1.5.0
         with:
diff --git a/.github/workflows/native_build_mxnet_osx.yml b/.github/workflows/native_build_mxnet_osx.yml
index 2aec961a8..b55dacad3 100644
--- a/.github/workflows/native_build_mxnet_osx.yml
+++ b/.github/workflows/native_build_mxnet_osx.yml
@@ -23,7 +23,7 @@ jobs:
           clang --version
           CMAKE_STATICBUILD=1 ./tools/staticbuild/build.sh cpu
       - name: Upload libmxnet.dylib
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v2
         with:
           name: libmxnet.dylib
           path: build/libmxnet.dylib
diff --git a/.github/workflows/native_jni_s3_paddle.yml b/.github/workflows/native_jni_s3_paddle.yml
index 9e41ed81b..bd5be8c7e 100644
--- a/.github/workflows/native_jni_s3_paddle.yml
+++ b/.github/workflows/native_jni_s3_paddle.yml
@@ -28,7 +28,7 @@ jobs:
       - name: Release JNI prep
         run: ./gradlew :engines:paddlepaddle:paddlepaddle-native:compileJNI
       - name: Upload compiled jni library
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v2
         if: always()
         with:
           name: jnilib-${{ runner.os }}
@@ -68,7 +68,7 @@ jobs:
         run: |
           ./gradlew :engines:paddlepaddle:paddlepaddle-native:compileJNI
       - name: Upload compiled jni library
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v2
         if: always()
         with:
           name: jnilib-${{ runner.os }}
diff --git a/.github/workflows/native_jni_s3_pytorch_android.yml b/.github/workflows/native_jni_s3_pytorch_android.yml
index d62238532..b7459f4fb 100644
--- a/.github/workflows/native_jni_s3_pytorch_android.yml
+++ b/.github/workflows/native_jni_s3_pytorch_android.yml
@@ -28,7 +28,7 @@ jobs:
           export ANDROID_NDK=${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}
           ./gradlew :engines:pytorch:pytorch-native:compileAndroidJNI
       - name: Upload compiled jni library
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v2
         if: always()
         with:
           name: jnilib-Android
diff --git a/.github/workflows/native_publish_pytorch.yml b/.github/workflows/native_publish_pytorch.yml
index 86dc4a86d..2edac6d8f 100644
--- a/.github/workflows/native_publish_pytorch.yml
+++ b/.github/workflows/native_publish_pytorch.yml
@@ -35,13 +35,3 @@ jobs:
           ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword }}
           ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.ORG_GRADLE_PROJECT_ossrhUsername }}
           ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.ORG_GRADLE_PROJECT_ossrhPassword }}
-      - name: Download native binaries precxx11
-        run: ./gradlew clean :engines:pytorch:pytorch-native:dPTNL -Pprecxx11
-      - name: Publish to precxx11 snapshot/staging repository
-        working-directory: engines/pytorch/pytorch-native
-        run: ./gradlew publish -P${{ github.event.inputs.mode }} -Pprecxx11
-        env:
-          ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_signingKey }}
-          ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword }}
-          ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.ORG_GRADLE_PROJECT_ossrhUsername }}
-          ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.ORG_GRADLE_PROJECT_ossrhPassword }}
diff --git a/.github/workflows/nightly_publish.yml b/.github/workflows/nightly_publish.yml
index a627a13ac..4562d41d9 100644
--- a/.github/workflows/nightly_publish.yml
+++ b/.github/workflows/nightly_publish.yml
@@ -35,72 +35,12 @@ jobs:
         run: df -h
       - name: Build with Gradle
         run: ./gradlew -Dnightly=true build jRR jRV
-      - name: API test results
-        uses: actions/upload-artifact@v1
+      - name: Upload test results
+        uses: actions/upload-artifact@v2
         if: always()
         with:
-          name: api
-          path: api/build/reports
-      - name: basicdataset test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: basicdataset
-          path: basicdataset/build/reports
-      - name: examples test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: examples
-          path: examples/build/reports
-      - name: integration test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: integration
-          path: integration/build/reports
-      - name: model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: model-zoo
-          path: model-zoo/build/reports
-      - name: mxnet-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: mxnet-engine
-          path: engines/mxnet/mxnet-engine/build/reports
-      - name: mxnet-model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: mxnet-model-zoo
-          path: engines/mxnet/mxnet-model-zoo/build/reports
-      - name: onnxruntime/onnxruntime-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: onnxruntime
-          path: engines/onnxruntime/onnxruntime-engine/build/reports
-      - name: pytorch-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: pytorch-engine
-          path: engines/pytorch/pytorch-engine/build/reports
-      - name: tensorflow-engine test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: tensorflow-engine
-          path: engines/tensorflow/tensorflow-engine/build/reports
-      - name: tensorflow-model-zoo test results
-        uses: actions/upload-artifact@v1
-        if: always()
-        with:
-          name: tensorflow-model-zoo
-          path: engines/tensorflow/tensorflow-model-zoo/build/reports
+          name: reports
+          path: ${{ github.workspace }}/**/build/reports/**/*
 
   test-pytorch:
     if: github.repository == 'deepjavalibrary/djl'
diff --git a/api/src/main/java/ai/djl/engine/Engine.java b/api/src/main/java/ai/djl/engine/Engine.java
index 977ccb66a..e99d6367b 100644
--- a/api/src/main/java/ai/djl/engine/Engine.java
+++ b/api/src/main/java/ai/djl/engine/Engine.java
@@ -61,8 +61,7 @@ public abstract class Engine {
     private static synchronized String initEngine() {
         ServiceLoader<EngineProvider> loaders = ServiceLoader.load(EngineProvider.class);
         for (EngineProvider provider : loaders) {
-            logger.debug("Found EngineProvider: {}", provider.getEngineName());
-            ALL_ENGINES.put(provider.getEngineName(), provider);
+            registerEngine(provider);
         }
 
         if (ALL_ENGINES.isEmpty()) {
@@ -144,6 +143,16 @@ public abstract class Engine {
         return ALL_ENGINES.containsKey(engineName);
     }
 
+    /**
+     * Registers a {@link EngineProvider} if not registered.
+     *
+     * @param provider the {@code EngineProvider} to be registered
+     */
+    public static void registerEngine(EngineProvider provider) {
+        logger.debug("Registering EngineProvider: {}", provider.getEngineName());
+        ALL_ENGINES.putIfAbsent(provider.getEngineName(), provider);
+    }
+
     /**
      * Returns a set of engine names that are loaded.
      *
diff --git a/api/src/main/java/ai/djl/util/Platform.java b/api/src/main/java/ai/djl/util/Platform.java
index 02cf6beee..363a0ab42 100644
--- a/api/src/main/java/ai/djl/util/Platform.java
+++ b/api/src/main/java/ai/djl/util/Platform.java
@@ -25,6 +25,7 @@ import java.util.Properties;
 public final class Platform {
 
     private String version;
+    private String apiVersion;
     private String osPrefix;
     private String osArch;
     private String flavor;
@@ -39,26 +40,36 @@ public final class Platform {
      * Returns the platform that matches current operating system.
      *
      * @param engine the name of the engine
-     * @param path the path to the placeholder properties file
-     * @param key the version key in the "engine".properties file
+     * @param overrideVersion the version of the engine
      * @return the platform that matches current operating system
      */
-    public static Platform detectPlatform(String engine, String path, String key) {
+    public static Platform detectPlatform(String engine, String overrideVersion) {
+        Platform platform = Platform.fromSystem(engine);
+        platform.version = overrideVersion;
+        return platform;
+    }
+
+    /**
+     * Returns the platform that matches current operating system.
+     *
+     * @param engine the name of the engine
+     * @return the platform that matches current operating system
+     */
+    public static Platform detectPlatform(String engine) {
+        String nativeProp = "native/lib/" + engine + ".properties";
         Enumeration<URL> urls;
         try {
-            urls =
-                    Thread.currentThread()
-                            .getContextClassLoader()
-                            .getResources("native/lib/" + engine + ".properties");
+            urls = Thread.currentThread().getContextClassLoader().getResources(nativeProp);
         } catch (IOException e) {
-            throw new IllegalArgumentException("Failed to list property files.", e);
+            throw new AssertionError("Failed to list property files.", e);
         }
 
-        Platform systemPlatform = Platform.fromSystem();
+        Platform systemPlatform = Platform.fromSystem(engine);
         Platform placeholder = null;
         while (urls.hasMoreElements()) {
             URL url = urls.nextElement();
             Platform platform = Platform.fromUrl(url);
+            platform.apiVersion = systemPlatform.apiVersion;
             if (platform.isPlaceholder()) {
                 placeholder = platform;
             } else if (platform.matches(systemPlatform)) {
@@ -69,25 +80,13 @@ public final class Platform {
             return placeholder;
         }
 
-        if (path == null) {
-            throw new IllegalArgumentException("No property file found for engine: " + engine);
+        if (systemPlatform.version == null) {
+            throw new AssertionError("No " + engine + " version found in property file.");
         }
-
-        URL url = Platform.class.getResource(path);
-        if (url == null) {
-            throw new IllegalArgumentException("Property file not found: " + path);
-        }
-        try (InputStream is = url.openStream()) {
-            Properties prop = new Properties();
-            prop.load(is);
-            String preferredVersion = prop.getProperty(key);
-            if (preferredVersion == null) {
-                throw new IllegalArgumentException("No version found in property file: " + key);
-            }
-            return fromSystem(preferredVersion);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to read " + path, e);
+        if (systemPlatform.apiVersion == null) {
+            throw new AssertionError("No " + engine + " djl_version found in property file.");
         }
+        return systemPlatform;
     }
 
     /**
@@ -96,18 +95,21 @@ public final class Platform {
      * @param url the url to the "engine".properties file
      * @return the platform that parsed from "engine".properties file
      */
-    public static Platform fromUrl(URL url) {
+    static Platform fromUrl(URL url) {
         Platform platform = Platform.fromSystem();
         try (InputStream conf = url.openStream()) {
             Properties prop = new Properties();
             prop.load(conf);
-            // 1.6.0 later should always has version property
             platform.version = prop.getProperty("version");
             if (platform.version == null) {
                 throw new IllegalArgumentException(
                         "version key is required in <engine>.properties file.");
             }
             platform.placeholder = prop.getProperty("placeholder") != null;
+            String flavor = prop.getProperty("flavor");
+            if (flavor != null) {
+                platform.flavor = flavor;
+            }
             String flavorPrefixedClassifier = prop.getProperty("classifier", "");
             String libraryList = prop.getProperty("libraries", "");
             if (libraryList.isEmpty()) {
@@ -118,9 +120,14 @@ public final class Platform {
 
             if (!flavorPrefixedClassifier.isEmpty()) {
                 String[] tokens = flavorPrefixedClassifier.split("-");
-                platform.flavor = tokens[0];
-                platform.osPrefix = tokens[1];
-                platform.osArch = tokens[2];
+                if (flavor != null) {
+                    platform.osPrefix = tokens[0];
+                    platform.osArch = tokens[1];
+                } else {
+                    platform.flavor = tokens[0];
+                    platform.osPrefix = tokens[1];
+                    platform.osArch = tokens[2];
+                }
             }
         } catch (IOException e) {
             throw new IllegalStateException("Failed to read property file: " + url, e);
@@ -128,16 +135,23 @@ public final class Platform {
         return platform;
     }
 
-    /**
-     * Returns the platform for the current system with the specified version.
-     *
-     * @param version the engine version
-     * @return the platform for the current system
-     */
-    public static Platform fromSystem(String version) {
+    private static Platform fromSystem(String engine) {
+        String engineProp = '/' + engine + "-engine.properties";
+        String versionKey = engine + "_version";
         Platform platform = fromSystem();
-        platform.version = version;
         platform.placeholder = true;
+
+        URL url = Platform.class.getResource(engineProp);
+        if (url != null) {
+            try (InputStream is = url.openStream()) {
+                Properties prop = new Properties();
+                prop.load(is);
+                platform.version = prop.getProperty(versionKey);
+                platform.apiVersion = prop.getProperty("djl_version");
+            } catch (IOException e) {
+                throw new AssertionError("Failed to read property file: " + engineProp, e);
+            }
+        }
         return platform;
     }
 
@@ -146,7 +160,7 @@ public final class Platform {
      *
      * @return the platform for the current system
      */
-    public static Platform fromSystem() {
+    static Platform fromSystem() {
         Platform platform = new Platform();
         String osName = System.getProperty("os.name");
         if (osName.startsWith("Win")) {
@@ -180,6 +194,15 @@ public final class Platform {
         return version;
     }
 
+    /**
+     * Returns the Engine API version.
+     *
+     * @return the Engine API version
+     */
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
     /**
      * Returns the os (win, osx, or linux).
      *
@@ -250,29 +273,17 @@ public final class Platform {
      * @return true if the platforms match
      */
     public boolean matches(Platform system) {
-        return matches(system, true);
-    }
-
-    /**
-     * Returns true the platforms match (os and flavor).
-     *
-     * @param system the platform to compare it to
-     * @param strictModel cuda minor version must match
-     * @return true if the platforms match
-     */
-    public boolean matches(Platform system, boolean strictModel) {
         if (!osPrefix.equals(system.osPrefix) || !osArch.equals(system.osArch)) {
             return false;
         }
         // if system Machine is GPU
         if (system.flavor.startsWith("cu")) {
             // system flavor doesn't contain mkl, but MXNet has: cu110mkl
-            return "cpu".equals(flavor)
+            return flavor.startsWith("cpu")
                     || "mkl".equals(flavor)
-                    || flavor.startsWith(system.flavor)
-                    || (!strictModel && flavor.compareTo(system.flavor) <= 0);
+                    || flavor.startsWith(system.flavor.substring(0, 4));
         }
-        return "cpu".equals(flavor) || "mkl".equals(flavor);
+        return flavor.startsWith("cpu") || "mkl".equals(flavor);
     }
 
     /** {@inheritDoc} */
diff --git a/bom/build.gradle b/bom/build.gradle
index e6eb9a719..382058cf4 100644
--- a/bom/build.gradle
+++ b/bom/build.gradle
@@ -31,6 +31,7 @@ dependencies {
         api "ai.djl.paddlepaddle:paddlepaddle-model-zoo:${version}"
         api "ai.djl.python:python:${version}"
         api "ai.djl.pytorch:pytorch-engine:${version}"
+        api "ai.djl.pytorch:pytorch-jni:${pytorch_version}-${version}"
         api "ai.djl.pytorch:pytorch-model-zoo:${version}"
         api "ai.djl.sentencepiece:sentencepiece:${version}"
         api "ai.djl.tensorflow:tensorflow-api:${version}"
@@ -97,14 +98,15 @@ publishing {
                     addDependency(dependencies, "ai.djl.mxnet", "mxnet-native-mkl", "win-x86_64", "${mxnet_version}")
                     addDependency(dependencies, "ai.djl.mxnet", "mxnet-native-cu102mkl", "linux-x86_64", "${mxnet_version}")
                     addDependency(dependencies, "ai.djl.mxnet", "mxnet-native-cu110mkl", "linux-x86_64", "${mxnet_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu", "osx-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu", "linux-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu", "win-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu-precxx11", "linux-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu102", "linux-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu102", "win-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu111", "linux-x86_64", "${pytorch_version}")
-                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu111", "win-x86_64", "${pytorch_version}")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu", "osx-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu", "linux-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu", "win-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cpu-precxx11", "linux-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu102", "linux-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu102", "win-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu113", "linux-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu113", "win-x86_64", "${pytorch_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.pytorch", "pytorch-native-cu113-precxx11", "linux-x86_64", "${pytorch_version}-SNAPSHOT")
                     addDependency(dependencies, "ai.djl.tensorflow", "tensorflow-native-cpu", "osx-x86_64", "${tensorflow_version}")
                     addDependency(dependencies, "ai.djl.tensorflow", "tensorflow-native-cpu", "linux-x86_64", "${tensorflow_version}")
                     addDependency(dependencies, "ai.djl.tensorflow", "tensorflow-native-cpu", "win-x86_64", "${tensorflow_version}")
@@ -112,10 +114,11 @@ publishing {
                     addDependency(dependencies, "ai.djl.paddlepaddle", "paddlepaddle-native-cpu", "osx-x86_64", "${paddlepaddle_version}")
                     addDependency(dependencies, "ai.djl.paddlepaddle", "paddlepaddle-native-cpu", "linux-x86_64", "${paddlepaddle_version}")
                     addDependency(dependencies, "ai.djl.paddlepaddle", "paddlepaddle-native-cpu", "win-x86_64", "${paddlepaddle_version}")
+                    addDependency(dependencies, "ai.djl.paddlepaddle", "paddlepaddle-native-cu102", "linux-x86_64", "${paddlepaddle_version}")
                     addDependency(dependencies, "ai.djl.dlr", "dlr-native-cpu", "osx-x86_64", "${dlr_version}")
                     addDependency(dependencies, "ai.djl.dlr", "dlr-native-cpu", "linux-x86_64", "${dlr_version}")
-                    addDependency(dependencies, "ai.djl.tflite", "tflite-native-cpu", "osx-x86_64", "${tflite_version}")
-                    addDependency(dependencies, "ai.djl.tflite", "tflite-native-cpu", "linux-x86_64", "${tflite_version}")
+                    addDependency(dependencies, "ai.djl.tflite", "tflite-native-cpu", "osx-x86_64", "${tflite_version}-SNAPSHOT")
+                    addDependency(dependencies, "ai.djl.tflite", "tflite-native-cpu", "linux-x86_64", "${tflite_version}-SNAPSHOT")
                 }
             }
         }
diff --git a/engines/dlr/dlr-engine/build.gradle b/engines/dlr/dlr-engine/build.gradle
index 7c3021620..42401f9a3 100644
--- a/engines/dlr/dlr-engine/build.gradle
+++ b/engines/dlr/dlr-engine/build.gradle
@@ -48,8 +48,8 @@ processResources {
         }
 
         // write properties
-        def propFile = file("${project.buildDir}/classes/java/main/jnilib/dlr.properties")
-        propFile.text = "version=${version}\ndlr_version=${dlr_version}"
+        def propFile = file("${project.buildDir}/classes/java/main/dlr-engine.properties")
+        propFile.text = "djl_version=${version}\ndlr_version=${dlr_version}"
     }
 }
 
diff --git a/engines/dlr/dlr-engine/src/main/java/ai/djl/dlr/jni/LibUtils.java b/engines/dlr/dlr-engine/src/main/java/ai/djl/dlr/jni/LibUtils.java
index d041aab39..4e44ba531 100644
--- a/engines/dlr/dlr-engine/src/main/java/ai/djl/dlr/jni/LibUtils.java
+++ b/engines/dlr/dlr-engine/src/main/java/ai/djl/dlr/jni/LibUtils.java
@@ -24,7 +24,6 @@ import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.Collections;
 import java.util.List;
-import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.slf4j.Logger;
@@ -73,7 +72,7 @@ public final class LibUtils {
     }
 
     private static synchronized String findNativeLibrary() {
-        Platform platform = Platform.detectPlatform("dlr", "/jnilib/dlr.properties", "dlr_version");
+        Platform platform = Platform.detectPlatform("dlr");
         if (platform.isPlaceholder()) {
             return downloadDlr(platform);
         }
@@ -157,29 +156,22 @@ public final class LibUtils {
 
     private static String copyJniLibraryFromClasspath(Path nativeDir) {
         String name = System.mapLibraryName(LIB_NAME);
-        Platform platform = Platform.fromSystem();
+        Platform platform = Platform.detectPlatform("dlr");
         String classifier = platform.getClassifier();
-        String flavor = platform.getFlavor();
-        Properties prop = new Properties();
-        try (InputStream stream = LibUtils.class.getResourceAsStream("/jnilib/dlr.properties")) {
-            prop.load(stream);
-        } catch (IOException e) {
-            throw new IllegalStateException("Cannot find DLR property file", e);
-        }
-        String version = prop.getProperty("version");
-        Path path = nativeDir.resolve(version + '-' + flavor + '-' + name);
+        String djlVersion = platform.getApiVersion();
+        Path path = nativeDir.resolve(djlVersion + '-' + name);
         if (Files.exists(path)) {
             return path.toAbsolutePath().toString();
         }
         Path tmp = null;
-        try (InputStream stream =
-                // both cpu & gpu share the same jnilib
-                LibUtils.class.getResourceAsStream("/jnilib/" + classifier + '/' + name)) {
-            if (stream == null) {
+        // both cpu & gpu share the same jnilib
+        String lib = "/jnilib/" + classifier + '/' + name;
+        try (InputStream is = LibUtils.class.getResourceAsStream(lib)) {
+            if (is == null) {
                 throw new UnsupportedOperationException("DLR is not supported by this platform");
             }
             tmp = Files.createTempFile(nativeDir, "jni", "tmp");
-            Files.copy(stream, tmp, StandardCopyOption.REPLACE_EXISTING);
+            Files.copy(is, tmp, StandardCopyOption.REPLACE_EXISTING);
             Utils.moveQuietly(tmp, path);
             return path.toAbsolutePath().toString();
         } catch (IOException e) {
diff --git a/engines/mxnet/mxnet-engine/build.gradle b/engines/mxnet/mxnet-engine/build.gradle
index 1a5b1e912..a17c18618 100644
--- a/engines/mxnet/mxnet-engine/build.gradle
+++ b/engines/mxnet/mxnet-engine/build.gradle
@@ -26,7 +26,7 @@ processResources {
         def classesDir = file("${project.buildDir}/classes/java/main/")
         classesDir.mkdirs()
         def file = new File(classesDir, "mxnet-engine.properties")
-        file.text = "mxnet_version=${mxnet_version}"
+        file.text = "djl_version=${djl_version}\nmxnet_version=${mxnet_version}"
     }
 }
 
diff --git a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/jna/LibUtils.java b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/jna/LibUtils.java
index 01978923e..54b7b09f5 100644
--- a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/jna/LibUtils.java
+++ b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/jna/LibUtils.java
@@ -103,12 +103,11 @@ public final class LibUtils {
             overrideVersion = System.getProperty("MXNET_VERSION");
         }
         if (overrideVersion != null) {
-            Platform platform = Platform.fromSystem(overrideVersion);
+            Platform platform = Platform.detectPlatform("mxnet", overrideVersion);
             return downloadMxnet(platform);
         }
 
-        Platform platform =
-                Platform.detectPlatform("mxnet", "/mxnet-engine.properties", "mxnet_version");
+        Platform platform = Platform.detectPlatform("mxnet");
         if (platform.isPlaceholder()) {
             return downloadMxnet(platform);
         }
@@ -201,10 +200,10 @@ public final class LibUtils {
 
         String libName = System.mapLibraryName(LIB_NAME);
         Path cacheFolder = Utils.getEngineCacheDir("mxnet");
-        logger.debug("Using cache dir: {}", cacheFolder);
         Path dir = cacheFolder.resolve(version + '-' + flavor + '-' + classifier);
         Path path = dir.resolve(libName);
         if (Files.exists(path)) {
+            logger.debug("Using cache dir: {}", dir);
             return path.toAbsolutePath().toString();
         }
 
@@ -233,8 +232,31 @@ public final class LibUtils {
                         flavor = "mkl";
                     }
                 } else if ("linux".equals(os)) {
-                    if (!lines.contains(os + '/' + flavor + "/libmxnet.so.gz")
-                            || notSupported(platform)) {
+                    boolean match =
+                            lines.contains(os + '/' + flavor + "/libmxnet.so.gz")
+                                    && supported(platform);
+                    if (!match) {
+                        String cudaMajor = flavor.substring(0, 4);
+                        Pattern pattern =
+                                Pattern.compile(
+                                        '('
+                                                + cudaMajor
+                                                + ".+)/"
+                                                + classifier
+                                                + "/native/lib/"
+                                                + libName
+                                                + ".gz");
+                        for (String line : lines) {
+                            Matcher m = pattern.matcher(line);
+                            if (m.matches()) {
+                                flavor = m.group(1);
+                                match = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (!match) {
                         logger.warn(
                                 "No matching cuda flavor for {} found: {}/sm_{}.",
                                 os,
@@ -247,16 +269,16 @@ public final class LibUtils {
                     throw new AssertionError("Unsupported GPU operating system: " + os);
                 }
 
-                // check again in case fallback to cpu
-                if ("mkl".equals(flavor)) {
-                    dir = cacheFolder.resolve(version + '-' + flavor + '-' + classifier);
-                    path = dir.resolve(libName);
-                    if (Files.exists(path)) {
-                        return path.toAbsolutePath().toString();
-                    }
+                // check again in case fallback to cpu or different cuda minor version
+                dir = cacheFolder.resolve(version + '-' + flavor + '-' + classifier);
+                path = dir.resolve(libName);
+                if (Files.exists(path)) {
+                    return path.toAbsolutePath().toString();
                 }
             }
 
+            logger.debug("Using cache dir: {}", dir);
+
             boolean found = false;
             for (String line : lines) {
                 if (line.startsWith(os + "/common/") || line.startsWith(os + '/' + flavor + '/')) {
@@ -295,18 +317,18 @@ public final class LibUtils {
         }
     }
 
-    private static boolean notSupported(Platform platform) {
+    private static boolean supported(Platform platform) {
         // mxnet-native-cu102mkl:1.8.0: 3.0, 5.0, 6.0, 7.0, 7.5
         // mxnet-native-cu110mkl:1.8.0: 5.0, 6.0, 7.0, 8.0
         if (platform.getVersion().startsWith("1.8.")) {
             String flavor = platform.getFlavor();
             String cudaArch = platform.getCudaArch();
-            if ("cu110".equals(flavor)) {
-                return !Arrays.asList("50", "60", "70", "80").contains(cudaArch);
-            } else if ("cu102".equals(flavor)) {
-                return !Arrays.asList("30", "50", "60", "70", "75").contains(cudaArch);
+            if (flavor.startsWith("cu11")) {
+                return Arrays.asList("50", "60", "70", "80").contains(cudaArch);
+            } else if (flavor.startsWith("cu10")) {
+                return Arrays.asList("30", "50", "60", "70", "75").contains(cudaArch);
             }
         }
-        return false;
+        return true;
     }
 }
diff --git a/engines/paddlepaddle/paddlepaddle-engine/build.gradle b/engines/paddlepaddle/paddlepaddle-engine/build.gradle
index e7d6e1fed..ed93a1871 100644
--- a/engines/paddlepaddle/paddlepaddle-engine/build.gradle
+++ b/engines/paddlepaddle/paddlepaddle-engine/build.gradle
@@ -47,8 +47,8 @@ processResources {
         }
 
         // write properties
-        def propFile = file("${project.buildDir}/classes/java/main/jnilib/paddlepaddle.properties")
-        propFile.text = "version=${version}\npaddlepaddle_version=${paddlepaddle_version}"
+        def propFile = file("${project.buildDir}/classes/java/main/paddlepaddle-engine.properties")
+        propFile.text = "djl_version=${djl_version}\npaddlepaddle_version=${paddlepaddle_version}"
     }
 }
 
diff --git a/engines/paddlepaddle/paddlepaddle-engine/src/main/java/ai/djl/paddlepaddle/jni/LibUtils.java b/engines/paddlepaddle/paddlepaddle-engine/src/main/java/ai/djl/paddlepaddle/jni/LibUtils.java
index f817daa08..0891cc616 100644
--- a/engines/paddlepaddle/paddlepaddle-engine/src/main/java/ai/djl/paddlepaddle/jni/LibUtils.java
+++ b/engines/paddlepaddle/paddlepaddle-engine/src/main/java/ai/djl/paddlepaddle/jni/LibUtils.java
@@ -25,7 +25,6 @@ import java.nio.file.StandardCopyOption;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Properties;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -77,7 +76,7 @@ public final class LibUtils {
         if (nativeLibDir == null || !nativeLibDir.toFile().isDirectory()) {
             throw new IllegalStateException("Native folder cannot be found");
         }
-        libName = copyJniLibraryFromClasspath(nativeLibDir, fallback.get());
+        libName = copyJniLibraryFromClasspath(nativeLibDir);
         logger.debug("Loading paddle library from: {}", libName);
         System.load(libName); // NOPMD
     }
@@ -132,23 +131,12 @@ public final class LibUtils {
         return null;
     }
 
-    private static String copyJniLibraryFromClasspath(Path nativeDir, boolean fallback) {
+    private static String copyJniLibraryFromClasspath(Path nativeDir) {
         String name = System.mapLibraryName(LIB_NAME);
-        Platform platform = Platform.fromSystem();
+        Platform platform = Platform.detectPlatform("paddlepaddle");
         String classifier = platform.getClassifier();
-        String flavor = platform.getFlavor();
-        if (fallback) {
-            flavor = "cpu";
-        }
-        Properties prop = new Properties();
-        try (InputStream stream =
-                LibUtils.class.getResourceAsStream("/jnilib/paddlepaddle.properties")) {
-            prop.load(stream);
-        } catch (IOException e) {
-            throw new IllegalStateException("Cannot find paddle property file", e);
-        }
-        String version = prop.getProperty("version");
-        Path path = nativeDir.resolve(version + '-' + flavor + '-' + name);
+        String djlVersion = platform.getApiVersion();
+        Path path = nativeDir.resolve(djlVersion + '-' + name);
         if (Files.exists(path)) {
             return path.toAbsolutePath().toString();
         }
@@ -175,9 +163,7 @@ public final class LibUtils {
     }
 
     private static synchronized String findLibraryInClasspath(AtomicBoolean fallback) {
-        Platform platform =
-                Platform.detectPlatform(
-                        "paddlepaddle", "/jnilib/paddlepaddle.properties", "paddlepaddle_version");
+        Platform platform = Platform.detectPlatform("paddlepaddle");
         if (platform.isPlaceholder()) {
             return downloadLibrary(platform, fallback);
         }
diff --git a/engines/pytorch/pytorch-engine/build.gradle b/engines/pytorch/pytorch-engine/build.gradle
index f99e8841a..7eeb7c710 100644
--- a/engines/pytorch/pytorch-engine/build.gradle
+++ b/engines/pytorch/pytorch-engine/build.gradle
@@ -8,58 +8,18 @@ dependencies {
     }
     testImplementation "org.slf4j:slf4j-simple:${slf4j_version}"
     testRuntimeOnly project(":engines:pytorch:pytorch-model-zoo")
+    testRuntimeOnly project(":engines:pytorch:pytorch-jni")
 }
 
 compileJava.dependsOn(processResources)
 
 processResources {
-    outputs.dir file("${project.buildDir}/classes/java/main/jnilib")
-    doLast {
-        String url = "https://publish.djl.ai/pytorch/${pytorch_version}/jnilib/${djl_version}"
-        def files = [
-                "linux-x86_64/cpu/libdjl_torch.so",
-                "linux-x86_64/cu102/libdjl_torch.so",
-                "linux-x86_64/cu113/libdjl_torch.so",
-                "linux-x86_64/cpu-precxx11/libdjl_torch.so",
-                "linux-x86_64/cu113-precxx11/libdjl_torch.so",
-                "osx-x86_64/cpu/libdjl_torch.dylib",
-                "win-x86_64/cpu/djl_torch.dll",
-                "win-x86_64/cu102/djl_torch.dll",
-                "win-x86_64/cu113/djl_torch.dll"
-        ]
-        String jnilibDir = "${project.projectDir}/jnilib/${djl_version}"
-        files.each { entry ->
-            def file = new File("${jnilibDir}/${entry}")
-            if (file.exists()) {
-                project.logger.lifecycle("prebuilt or cached file found for ${entry}")
-            } else {
-                String nativeDir = "${project.parent.projectDir}/pytorch-native/jnilib/${djl_version}/"
-                def jnilibFile = new File("${nativeDir}/${entry}")
-                if (jnilibFile.exists()) {
-                    project.logger.lifecycle("Copying " + jnilibFile)
-                    copy {
-                        from jnilibFile
-                        into file.getParent()
-                    }
-                } else if (!project.hasProperty("jni")) {
-                    project.logger.lifecycle("Downloading ${url}/${entry}")
-                    file.getParentFile().mkdirs()
-                    new URL("${url}/${entry}").withInputStream { i -> file.withOutputStream { it << i } }
-                }
-            }
-        }
-        copy {
-            from jnilibDir
-            into "${project.buildDir}/classes/java/main/jnilib"
-        }
-
-        // write properties
-        String ptVersion = "${pytorch_version}"
-        if (project.hasProperty("pt_version") && project.property("pt_version") != "") {
-            ptVersion = project.property("pt_version")
-        }
-        def propFile = file("${project.buildDir}/classes/java/main/jnilib/pytorch.properties")
-        propFile.text = "version=${version}\njni_version=${ptVersion}"
+    outputs.file file("${project.buildDir}/classes/java/main/pytorch-engine.properties")
+    doFirst {
+        def classesDir = file("${project.buildDir}/classes/java/main/")
+        classesDir.mkdirs()
+        def propFile = new File(classesDir, "pytorch-engine.properties");
+        propFile.text = "djl_version=${djl_version}\npytorch_version=${pytorch_version}"
     }
 }
 
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/LibUtils.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/LibUtils.java
index 8e9f01ed0..013afbdd9 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/LibUtils.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/LibUtils.java
@@ -22,16 +22,12 @@ import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
@@ -55,140 +51,102 @@ public final class LibUtils {
 
     private static final Logger logger = LoggerFactory.getLogger(LibUtils.class);
 
-    private static final String LIB_NAME = "djl_torch";
-    private static final String NATIVE_LIB_NAME = "torch";
+    private static final String NATIVE_LIB_NAME = System.mapLibraryName("torch");
+    private static final String JNI_LIB_NAME = System.mapLibraryName("djl_torch");
 
     private static final Pattern VERSION_PATTERN =
             Pattern.compile("(\\d+\\.\\d+\\.\\d+(-[a-z]+)?)(-SNAPSHOT)?(-\\d+)?");
 
-    private static String version;
-    private static String libtorchPath;
+    private static LibTorch libTorch;
 
     private LibUtils() {}
 
-    public static void loadLibrary() {
-        Properties prop = new Properties();
-        try (InputStream stream =
-                LibUtils.class.getResourceAsStream("/jnilib/pytorch.properties")) {
-            prop.load(stream);
-        } catch (IOException e) {
-            throw new IllegalStateException("Cannot find pytorch property file", e);
-        }
-        String djlVersion = prop.getProperty("version");
-        String jniVersion = prop.getProperty("jni_version");
-
-        version = jniVersion; // default to bundled jni version
-
+    public static synchronized void loadLibrary() {
         // TODO workaround to make it work on Android Studio
         // It should search for several places to find the native library
         if ("http://www.android.com/".equals(System.getProperty("java.vendor.url"))) {
-            System.loadLibrary(LIB_NAME); // NOPMD
+            System.loadLibrary(JNI_LIB_NAME); // NOPMD
             return;
         }
-        libtorchPath = getLibName(djlVersion, jniVersion);
-        logger.debug("Loading pytorch library from: {}", libtorchPath);
-        if (System.getProperty("os.name").startsWith("Win")) {
-            loadWinDependencies(libtorchPath);
-        }
-        loadNativeLibrary(libtorchPath);
+        libTorch = getLibTorch();
+        loadLibTorch(libTorch);
+
+        Path path = findJniLibrary(libTorch).toAbsolutePath();
+        loadNativeLibrary(path.toString());
     }
 
-    public static String getLibName(String djlVersion, String jniVersion) {
-        String libName = findOverrideLibrary();
-        if (libName == null) {
-            AtomicBoolean fallback = new AtomicBoolean(false);
-            String nativeLibDir = findNativeLibrary(fallback);
-            if (nativeLibDir != null) {
-                Path nativeDir = Paths.get(nativeLibDir);
-                libName =
-                        copyJniLibraryFromClasspath(
-                                nativeDir, djlVersion, jniVersion, fallback.get());
-            } else {
-                throw new IllegalStateException("Native library not found");
-            }
+    private static LibTorch getLibTorch() {
+        LibTorch lib = findOverrideLibrary();
+        if (lib != null) {
+            return lib;
         }
-        return libName;
+        return findNativeLibrary();
     }
 
     public static String getVersion() {
-        return version;
+        return libTorch.version;
     }
 
     public static String getLibtorchPath() {
-        return libtorchPath;
+        return libTorch.dir.toString();
     }
 
-    private static void loadWinDependencies(String libName) {
-        Path libDir = Paths.get(libName).getParent();
-        if (libDir == null) {
-            throw new IllegalArgumentException("Invalid library path!");
-        }
-
-        Set<String> loadLater =
-                new HashSet<>(
-                        Arrays.asList(
-                                "c10_cuda.dll",
-                                "torch.dll",
-                                "torch_cpu.dll",
-                                "torch_cuda.dll",
-                                "torch_cuda_cpp.dll",
-                                "torch_cuda_cu.dll",
-                                "fbgemm.dll"));
-
+    private static void loadLibTorch(LibTorch libTorch) {
+        Path libDir = libTorch.dir.toAbsolutePath();
+        List<String> deferred =
+                Arrays.asList(
+                        System.mapLibraryName("fbgemm"),
+                        System.mapLibraryName("caffe2_nvrtc"),
+                        System.mapLibraryName("torch_cpu"),
+                        System.mapLibraryName("c10_cuda"),
+                        System.mapLibraryName("torch_cuda_cpp"),
+                        System.mapLibraryName("torch_cuda_cu"),
+                        System.mapLibraryName("torch_cuda"),
+                        System.mapLibraryName("torch"));
+
+        Set<String> loadLater = new HashSet<>(deferred);
         try (Stream<Path> paths = Files.walk(libDir)) {
             paths.filter(
                             path -> {
                                 String name = path.getFileName().toString();
                                 return !loadLater.contains(name)
                                         && Files.isRegularFile(path)
-                                        && !name.endsWith("djl_torch.dll")
+                                        && !name.endsWith(JNI_LIB_NAME)
+                                        && !name.contains("torch_")
+                                        && !name.contains("caffe2_")
                                         && !name.startsWith("cudnn");
                             })
-                    .map(path -> path.toAbsolutePath().toString())
-                    .forEach(System::load);
-            loadNativeLibrary(libDir.resolve("fbgemm.dll").toAbsolutePath().toString());
-            loadNativeLibrary(libDir.resolve("torch_cpu.dll").toAbsolutePath().toString());
-            if (Files.exists(libDir.resolve("c10_cuda.dll"))) {
-                if (Files.exists((libDir.resolve("cudnn64_8.dll")))) {
-                    loadNativeLibrary(libDir.resolve("cudnn64_8.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("cudnn_ops_infer64_8.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("cudnn_ops_train64_8.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("cudnn_cnn_infer64_8.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("cudnn_cnn_train64_8.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("cudnn_adv_infer64_8.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("cudnn_adv_train64_8.dll").toAbsolutePath().toString());
-                } else if (Files.exists((libDir.resolve("cudnn64_7.dll")))) {
-                    loadNativeLibrary(libDir.resolve("cudnn64_7.dll").toAbsolutePath().toString());
-                }
-                // Windows System.load is global load
-                loadNativeLibrary(libDir.resolve("c10_cuda.dll").toAbsolutePath().toString());
-                // workaround for CU111, these files not exist in CU102
-                if (Files.exists(libDir.resolve("torch_cuda_cpp.dll"))) {
-                    loadNativeLibrary(
-                            libDir.resolve("torch_cuda_cpp.dll").toAbsolutePath().toString());
-                    loadNativeLibrary(
-                            libDir.resolve("torch_cuda_cu.dll").toAbsolutePath().toString());
+                    .map(Path::toString)
+                    .forEach(LibUtils::loadNativeLibrary);
+            if (Files.exists((libDir.resolve("cudnn64_8.dll")))) {
+                loadNativeLibrary(libDir.resolve("cudnn64_8.dll").toString());
+                loadNativeLibrary(libDir.resolve("cudnn_ops_infer64_8.dll").toString());
+                loadNativeLibrary(libDir.resolve("cudnn_ops_train64_8.dll").toString());
+                loadNativeLibrary(libDir.resolve("cudnn_cnn_infer64_8.dll").toString());
+                loadNativeLibrary(libDir.resolve("cudnn_cnn_train64_8.dll").toString());
+                loadNativeLibrary(libDir.resolve("cudnn_adv_infer64_8.dll").toString());
+                loadNativeLibrary(libDir.resolve("cudnn_adv_train64_8.dll").toString());
+            } else if (Files.exists((libDir.resolve("cudnn64_7.dll")))) {
+                loadNativeLibrary(libDir.resolve("cudnn64_7.dll").toString());
+            }
+
+            for (String dep : deferred) {
+                Path path = libDir.resolve(dep);
+                if (Files.exists(path)) {
+                    loadNativeLibrary(path.toString());
                 }
-                loadNativeLibrary(libDir.resolve("torch_cuda.dll").toAbsolutePath().toString());
             }
-            loadNativeLibrary(libDir.resolve("torch.dll").toAbsolutePath().toString());
         } catch (IOException e) {
             throw new IllegalArgumentException("Folder not exist! " + libDir, e);
         }
     }
 
-    private static String findOverrideLibrary() {
+    private static LibTorch findOverrideLibrary() {
         String libPath = System.getenv("PYTORCH_LIBRARY_PATH");
         if (libPath != null) {
-            String libName = findLibraryInPath(libPath);
-            if (libName != null) {
-                return libName;
+            LibTorch lib = findLibraryInPath(libPath);
+            if (lib != null) {
+                return lib;
             }
         }
 
@@ -199,66 +157,79 @@ public final class LibUtils {
         return null;
     }
 
-    private static String findLibraryInPath(String libPath) {
+    private static LibTorch findLibraryInPath(String libPath) {
         String[] paths = libPath.split(File.pathSeparator);
-        List<String> mappedLibNames;
-        mappedLibNames = Collections.singletonList(System.mapLibraryName(LIB_NAME));
-
         for (String path : paths) {
             File p = new File(path);
             if (!p.exists()) {
                 continue;
             }
-            for (String name : mappedLibNames) {
-                if (p.isFile() && p.getName().endsWith(name)) {
-                    return p.getAbsolutePath();
-                }
 
-                File file = new File(path, name);
-                if (file.exists() && file.isFile()) {
-                    return file.getAbsolutePath();
-                }
+            if (p.isFile() && NATIVE_LIB_NAME.equals(p.getName())) {
+                return new LibTorch(p.getParentFile().toPath().toAbsolutePath());
+            }
+
+            File file = new File(path, NATIVE_LIB_NAME);
+            if (file.exists() && file.isFile()) {
+                return new LibTorch(p.toPath().toAbsolutePath());
             }
         }
         return null;
     }
 
-    private static String copyJniLibraryFromClasspath(
-            Path nativeDir, String djlVersion, String jniVersion, boolean fallback) {
-        String name = System.mapLibraryName(LIB_NAME);
-        Platform platform = Platform.fromSystem();
-        String classifier = platform.getClassifier();
-        String flavor = platform.getFlavor();
-        if (fallback) {
-            flavor = "cpu";
-        }
-        Path precxx11Lib = nativeDir.resolve("libstdc++.so.6");
-        if (Files.exists(precxx11Lib)) {
-            flavor += "-precxx11"; // NOPMD
-            logger.info("Using precxx11 jnilib.");
-        }
+    private static Path findJniLibrary(LibTorch libTorch) {
+        String classifier = libTorch.classifier;
+        String version = libTorch.version;
+        String djlVersion = libTorch.apiVersion;
+        String flavor = libTorch.flavor;
 
-        Path path = nativeDir.resolve(djlVersion + '-' + flavor + '-' + name);
+        // always use cache dir, cache dir might be different from libTorch.dir
+        Path cacheDir = Utils.getEngineCacheDir("pytorch");
+        Path dir = cacheDir.resolve(version + '-' + flavor + '-' + classifier);
+        Path path = dir.resolve(djlVersion + '-' + JNI_LIB_NAME);
         if (Files.exists(path)) {
-            return path.toAbsolutePath().toString();
+            return path;
         }
 
-        if (!version.startsWith(jniVersion)) {
-            downloadJniLib(nativeDir, path, djlVersion, classifier, flavor, name);
-            return path.toAbsolutePath().toString();
+        Matcher matcher = VERSION_PATTERN.matcher(version);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Unexpected version: " + version);
+        }
+        version = matcher.group(1);
+
+        try (InputStream is = LibUtils.class.getResourceAsStream("/jnilib/pytorch.properties")) {
+            String jniVersion = null;
+            if (is != null) {
+                Properties prop = new Properties();
+                prop.load(is);
+                jniVersion = prop.getProperty("jni_version");
+                if (jniVersion == null) {
+                    throw new AssertionError("No PyTorch jni version found.");
+                }
+            }
+            if (jniVersion == null) {
+                downloadJniLib(dir, path, djlVersion, version, classifier, flavor);
+                return path;
+            } else if (!jniVersion.startsWith(version + '-' + djlVersion)) {
+                logger.warn("Found mismatch PyTorch jni: {}", jniVersion);
+                downloadJniLib(dir, path, djlVersion, version, classifier, flavor);
+                return path;
+            }
+        } catch (IOException e) {
+            throw new AssertionError("Failed to read PyTorch jni properties file.", e);
         }
 
         Path tmp = null;
-        String libPath = "/jnilib/" + classifier + '/' + flavor + '/' + name;
+        String libPath = "/jnilib/" + classifier + '/' + flavor + '/' + JNI_LIB_NAME;
         logger.info("Extracting {} to cache ...", libPath);
-        try (InputStream stream = LibUtils.class.getResourceAsStream(libPath)) {
-            if (stream == null) {
-                throw new IllegalStateException("PyTorch jni not found: " + libPath);
+        try (InputStream is = LibUtils.class.getResourceAsStream(libPath)) {
+            if (is == null) {
+                throw new AssertionError("PyTorch jni not found: " + libPath);
             }
-            tmp = Files.createTempFile(nativeDir, "jni", "tmp");
-            Files.copy(stream, tmp, StandardCopyOption.REPLACE_EXISTING);
+            tmp = Files.createTempFile(dir, "jni", "tmp");
+            Files.copy(is, tmp, StandardCopyOption.REPLACE_EXISTING);
             Utils.moveQuietly(tmp, path);
-            return path.toAbsolutePath().toString();
+            return path;
         } catch (IOException e) {
             throw new IllegalStateException("Cannot copy jni files", e);
         } finally {
@@ -268,81 +239,45 @@ public final class LibUtils {
         }
     }
 
-    private static synchronized String findNativeLibrary(AtomicBoolean fallback) {
+    private static LibTorch findNativeLibrary() {
+        Platform platform = Platform.detectPlatform("pytorch");
         String overrideVersion = System.getenv("PYTORCH_VERSION");
         if (overrideVersion == null) {
             overrideVersion = System.getProperty("PYTORCH_VERSION");
         }
         if (overrideVersion != null
                 && !overrideVersion.isEmpty()
-                && !overrideVersion.equals(version)) {
+                && !overrideVersion.equals(platform.getVersion())) {
             logger.warn("Override PyTorch version: {}.", overrideVersion);
-            version = overrideVersion;
-            Platform auto = Platform.fromSystem(overrideVersion);
-            return downloadPyTorch(auto, fallback);
-        }
-        Enumeration<URL> urls;
-        try {
-            urls =
-                    Thread.currentThread()
-                            .getContextClassLoader()
-                            .getResources("native/lib/pytorch.properties");
-        } catch (IOException e) {
-            logger.warn("", e);
-            return null;
-        }
-        // No native jars
-        if (!urls.hasMoreElements()) {
-            Platform auto = Platform.fromSystem(version);
-            return downloadPyTorch(auto, fallback);
-        }
-
-        Platform systemPlatform = Platform.fromSystem();
-        Platform matching = null;
-        Platform placeholder = null;
-        while (urls.hasMoreElements()) {
-            URL url = urls.nextElement();
-            Platform platform = Platform.fromUrl(url);
-            if (platform.isPlaceholder()) {
-                placeholder = platform;
-            } else if (platform.matches(systemPlatform, false)) {
-                matching = platform;
-                break;
-            }
-        }
-
-        if (matching != null) {
-            if ("cpu".equals(matching.getFlavor())) {
-                fallback.set(true);
-            }
-            return copyNativeLibraryFromClasspath(matching);
+            platform = Platform.detectPlatform("pytorch", overrideVersion);
+            return downloadPyTorch(platform);
         }
 
-        if (placeholder != null) {
-            return downloadPyTorch(placeholder, fallback);
+        if (platform.isPlaceholder()) {
+            return downloadPyTorch(platform);
         }
 
-        throw new IllegalStateException(
-                "Your PyTorch native library jar does not match your operating system. Make sure the Maven Dependency Classifier matches your system type.");
+        return copyNativeLibraryFromClasspath(platform);
     }
 
-    private static String copyNativeLibraryFromClasspath(Platform platform) {
-        Path tmp = null;
-        version = platform.getVersion();
+    private static LibTorch copyNativeLibraryFromClasspath(Platform platform) {
+        String version = platform.getVersion();
         String flavor = platform.getFlavor();
-        // TODO: include precxx11 into native jar's flavor property
-        if (Arrays.asList(platform.getLibraries()).contains("libstdc++.so.6")) {
+        if (!flavor.endsWith("-precxx11")
+                && Arrays.asList(platform.getLibraries()).contains("libstdc++.so.6")) {
+            // for PyTorch 1.9.1 and older
             flavor += "-precxx11"; // NOPMD
         }
         String classifier = platform.getClassifier();
+
+        Path tmp = null;
         try {
-            String libName = System.mapLibraryName(NATIVE_LIB_NAME);
             Path cacheDir = Utils.getEngineCacheDir("pytorch");
             logger.debug("Using cache dir: {}", cacheDir);
             Path dir = cacheDir.resolve(version + '-' + flavor + '-' + classifier);
-            Path path = dir.resolve(libName);
+            Path path = dir.resolve(NATIVE_LIB_NAME);
             if (Files.exists(path)) {
-                return dir.toAbsolutePath().toString();
+                return new LibTorch(dir.toAbsolutePath(), platform, flavor);
             }
 
             Files.createDirectories(cacheDir);
@@ -359,7 +294,7 @@ public final class LibUtils {
             }
 
             Utils.moveQuietly(tmp, dir);
-            return dir.toAbsolutePath().toString();
+            return new LibTorch(dir.toAbsolutePath(), platform, flavor);
         } catch (IOException e) {
             throw new IllegalStateException("Failed to extract PyTorch native library", e);
         } finally {
@@ -370,6 +305,7 @@ public final class LibUtils {
     }
 
     private static void loadNativeLibrary(String path) {
+        logger.debug("Loading native library: {}", path);
         String nativeHelper = System.getProperty("ai.djl.pytorch.native_helper");
         if (nativeHelper != null && !nativeHelper.isEmpty()) {
             try {
@@ -383,22 +319,26 @@ public final class LibUtils {
         System.load(path); // NOPMD
     }
 
-    private static String downloadPyTorch(Platform platform, AtomicBoolean fallback) {
-        version = platform.getVersion();
+    private static LibTorch downloadPyTorch(Platform platform) {
+        String version = platform.getVersion();
         String flavor = platform.getFlavor();
         String classifier = platform.getClassifier();
+        String precxx11;
         if (Boolean.getBoolean("PYTORCH_PRECXX11")
                 || Boolean.parseBoolean(System.getenv("PYTORCH_PRECXX11"))) {
-            flavor += "-precxx11";
+            precxx11 = "-precxx11";
+        } else {
+            precxx11 = "";
         }
 
-        String libName = System.mapLibraryName(NATIVE_LIB_NAME);
+        flavor += precxx11;
+
         Path cacheDir = Utils.getEngineCacheDir("pytorch");
         Path dir = cacheDir.resolve(version + '-' + flavor + '-' + classifier);
-        logger.debug("Using cache dir: {}", dir);
-        Path path = dir.resolve(libName);
+        Path path = dir.resolve(NATIVE_LIB_NAME);
         if (Files.exists(path)) {
-            return dir.toAbsolutePath().toString();
+            logger.debug("Using cache dir: {}", dir);
+            return new LibTorch(dir.toAbsolutePath(), platform, flavor);
         }
 
         Matcher matcher = VERSION_PATTERN.matcher(version);
@@ -410,24 +350,45 @@ public final class LibUtils {
         try (InputStream is = new URL(link + "/files.txt").openStream()) {
             // if files not found
             Files.createDirectories(cacheDir);
-
             List<String> lines = Utils.readLines(is);
-            if (!lines.contains(flavor + '/' + classifier + "/native/lib/" + libName + ".gz")) {
-                if (flavor.startsWith("cu")) {
+            if (flavor.startsWith("cu")) {
+                String cudaMajor = flavor.substring(0, 4);
+                Pattern pattern =
+                        Pattern.compile(
+                                '('
+                                        + cudaMajor
+                                        + "\\d"
+                                        + precxx11
+                                        + ")/"
+                                        + classifier
+                                        + "/native/lib/"
+                                        + NATIVE_LIB_NAME
+                                        + ".gz");
+                boolean match = false;
+                for (String line : lines) {
+                    Matcher m = pattern.matcher(line);
+                    if (m.matches()) {
+                        flavor = m.group(1);
+                        match = true;
+                        break;
+                    }
+                }
+                if (!match) {
                     logger.warn("No matching cuda flavor for {} found: {}.", classifier, flavor);
                     // fallback to CPU
-                    flavor = "cpu";
-                    fallback.set(true);
-
-                    // check again
-                    dir = cacheDir.resolve(version + '-' + flavor + '-' + classifier);
-                    path = dir.resolve(libName);
-                    if (Files.exists(path)) {
-                        return dir.toAbsolutePath().toString();
-                    }
+                    flavor = "cpu" + precxx11;
+                }
+
+                // check again
+                dir = cacheDir.resolve(version + '-' + flavor + '-' + classifier);
+                path = dir.resolve(NATIVE_LIB_NAME);
+                if (Files.exists(path)) {
+                    return new LibTorch(dir.toAbsolutePath(), platform, flavor);
                 }
             }
 
+            logger.debug("Using cache dir: {}", dir);
+
             tmp = Files.createTempDirectory(cacheDir, "tmp");
             boolean found = false;
             for (String line : lines) {
@@ -448,7 +409,7 @@ public final class LibUtils {
             }
 
             Utils.moveQuietly(tmp, dir);
-            return dir.toAbsolutePath().toString();
+            return new LibTorch(dir.toAbsolutePath(), platform, flavor);
         } catch (IOException e) {
             throw new IllegalStateException("Failed to download PyTorch native library", e);
         } finally {
@@ -459,45 +420,74 @@ public final class LibUtils {
     }
 
     private static void downloadJniLib(
-            Path nativeDir,
+            Path cacheDir,
             Path path,
             String djlVersion,
+            String version,
             String classifier,
-            String flavor,
-            String name) {
-        Matcher matcher = VERSION_PATTERN.matcher(djlVersion);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException("Unexpected djl version: " + djlVersion);
-        }
-        djlVersion = matcher.group(1);
-        matcher = VERSION_PATTERN.matcher(version);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException("Unexpected pytorch version: " + version);
-        }
-
-        StringBuilder sb = new StringBuilder(100);
-        sb.append("https://publish.djl.ai/pytorch/")
-                .append(matcher.group(1))
-                .append("/jnilib/")
-                .append(djlVersion)
-                .append('/')
-                .append(classifier)
-                .append('/')
-                .append(flavor)
-                .append('/')
-                .append(name);
-        logger.info("Downloading jni {} to cache ...", sb);
+            String flavor) {
+        String url =
+                "https://publish.djl.ai/pytorch/"
+                        + version
+                        + "/jnilib/"
+                        + djlVersion
+                        + '/'
+                        + classifier
+                        + '/'
+                        + flavor
+                        + '/'
+                        + JNI_LIB_NAME;
+        logger.info("Downloading jni {} to cache ...", url);
         Path tmp = null;
-        try (InputStream is = new URL(sb.toString()).openStream()) {
-            tmp = Files.createTempFile(nativeDir, "jni", "tmp");
+        try (InputStream is = new URL(url).openStream()) {
+            Files.createDirectories(cacheDir);
+            tmp = Files.createTempFile(cacheDir, "jni", "tmp");
             Files.copy(is, tmp, StandardCopyOption.REPLACE_EXISTING);
             Utils.moveQuietly(tmp, path);
         } catch (IOException e) {
-            throw new IllegalStateException("Cannot download jni files", e);
+            throw new IllegalStateException("Cannot download jni files: " + url, e);
         } finally {
             if (tmp != null) {
                 Utils.deleteQuietly(tmp);
             }
         }
     }
+
+    private static final class LibTorch {
+
+        Path dir;
+        String version;
+        String apiVersion;
+        String flavor;
+        String classifier;
+
+        LibTorch(Path dir) {
+            Platform platform = Platform.detectPlatform("pytorch");
+            this.dir = dir;
+            this.apiVersion = platform.getApiVersion();
+            this.classifier = platform.getClassifier();
+            version = System.getenv("PYTORCH_VERSION");
+            if (version == null) {
+                version = System.getProperty("PYTORCH_VERSION");
+                if (version == null) {
+                    version = platform.getVersion();
+                }
+            }
+            flavor = System.getenv("PYTORCH_FLAVOR");
+            if (flavor == null) {
+                flavor = System.getProperty("PYTORCH_FLAVOR");
+                if (flavor == null) {
+                    flavor = "cpu-precxx11";
+                }
+            }
+        }
+
+        LibTorch(Path dir, Platform platform, String flavor) {
+            this.dir = dir;
+            this.version = platform.getVersion();
+            this.apiVersion = platform.getApiVersion();
+            this.classifier = platform.getClassifier();
+            this.flavor = flavor;
+        }
+    }
 }
diff --git a/engines/pytorch/pytorch-jni/build.gradle b/engines/pytorch/pytorch-jni/build.gradle
new file mode 100644
index 000000000..e6c0a648e
--- /dev/null
+++ b/engines/pytorch/pytorch-jni/build.gradle
@@ -0,0 +1,78 @@
+group "ai.djl.pytorch"
+if (project.hasProperty("pt_version") && project.property("pt_version") != "") {
+    version = project.property("pt_version") + "-${djl_version}"
+} else {
+    version = "${pytorch_version}-${djl_version}"
+}
+boolean isRelease = project.hasProperty("release") || project.hasProperty("staging")
+if (!isRelease) {
+    version = version + "-SNAPSHOT"
+}
+
+compileJava.dependsOn(processResources)
+
+processResources {
+    outputs.dir file("${project.buildDir}/classes/java/main/jnilib")
+    doFirst {
+        String url = "https://publish.djl.ai/pytorch/${pytorch_version}/jnilib/${djl_version}"
+        def files = [
+                "linux-x86_64/cpu/libdjl_torch.so",
+                "linux-x86_64/cu102/libdjl_torch.so",
+                "linux-x86_64/cu113/libdjl_torch.so",
+                "linux-x86_64/cpu-precxx11/libdjl_torch.so",
+                "linux-x86_64/cu113-precxx11/libdjl_torch.so",
+                "osx-x86_64/cpu/libdjl_torch.dylib",
+                "win-x86_64/cpu/djl_torch.dll",
+                "win-x86_64/cu102/djl_torch.dll",
+                "win-x86_64/cu113/djl_torch.dll"
+        ]
+        String jnilibDir = "${project.projectDir}/jnilib/${djl_version}"
+        files.each { entry ->
+            def file = new File("${jnilibDir}/${entry}")
+            if (file.exists()) {
+                project.logger.lifecycle("prebuilt or cached file found for ${entry}")
+            } else {
+                String nativeDir = "${project.parent.projectDir}/pytorch-native/jnilib/${djl_version}/"
+                def jnilibFile = new File("${nativeDir}/${entry}")
+                if (jnilibFile.exists()) {
+                    project.logger.lifecycle("Copying " + jnilibFile)
+                    copy {
+                        from jnilibFile
+                        into file.getParent()
+                    }
+                } else if (!project.hasProperty("jni")) {
+                    project.logger.lifecycle("Downloading ${url}/${entry}")
+                    file.getParentFile().mkdirs()
+                    new URL("${url}/${entry}").withInputStream { i -> file.withOutputStream { it << i } }
+                }
+            }
+        }
+        copy {
+            from jnilibDir
+            into "${project.buildDir}/classes/java/main/jnilib"
+        }
+
+        // write properties
+        def propFile = file("${project.buildDir}/classes/java/main/jnilib/pytorch.properties")
+        propFile.text = "jni_version=${project.version}"
+    }
+}
+
+publishing {
+    publications {
+        maven(MavenPublication) {
+            pom {
+                name = "DJL Engine Adapter for PyTorch"
+                description = "Deep Java Library (DJL) Engine Adapter for PyTorch"
+                url = "http://www.djl.ai/engines/pytorch/${project.name}"
+            }
+        }
+    }
+}
+
+clean.doFirst {
+    delete "jnilib"
+    delete fileTree(System.getProperty("user.home") + "/.djl.ai/pytorch/") {
+        include '**/*djl_torch.*'
+    }
+}
diff --git a/engines/pytorch/pytorch-jni/gradlew b/engines/pytorch/pytorch-jni/gradlew
new file mode 120000
index 000000000..ab9334b00
--- /dev/null
+++ b/engines/pytorch/pytorch-jni/gradlew
@@ -0,0 +1,1 @@
+../../../gradlew
\ No newline at end of file
diff --git a/engines/pytorch/pytorch-jni/src/main/resources/META-INF/native-image/ai/djl/pytorch/pytorch-jni/resource-config.json b/engines/pytorch/pytorch-jni/src/main/resources/META-INF/native-image/ai/djl/pytorch/pytorch-jni/resource-config.json
new file mode 100644
index 000000000..e84383f67
--- /dev/null
+++ b/engines/pytorch/pytorch-jni/src/main/resources/META-INF/native-image/ai/djl/pytorch/pytorch-jni/resource-config.json
@@ -0,0 +1,7 @@
+{
+  "resources": [
+    {
+      "pattern": "jnilib/.*"
+    }
+  ]
+}
diff --git a/engines/pytorch/pytorch-native/build.gradle b/engines/pytorch/pytorch-native/build.gradle
index 899332400..f92bf21e8 100644
--- a/engines/pytorch/pytorch-native/build.gradle
+++ b/engines/pytorch/pytorch-native/build.gradle
@@ -68,11 +68,11 @@ def downloadBuildAndroid(String ver) {
                 from "${project.buildDir}/libdjl_torch.so"
                 into ciDir
             }
-            cleanBuild()
+            delete "${project.buildDir}/"
     }
 }
 
-def prepareNativeLib(String binaryRoot, String ver, boolean precxx11, boolean publish) {
+def prepareNativeLib(String binaryRoot, String ver) {
     def url = "https://download.pytorch.org/libtorch"
     String cu11
     if (ver.startsWith("1.8.") || ver.startsWith("1.9.")) {
@@ -82,27 +82,16 @@ def prepareNativeLib(String binaryRoot, String ver, boolean precxx11, boolean pu
     }
 
     def files = [
-            "cpu/libtorch-cxx11-abi-shared-with-deps-${ver}%2Bcpu.zip"        : "cpu/linux",
-            "cpu/libtorch-macos-${ver}.zip"                                   : "cpu/osx",
-            "cpu/libtorch-win-shared-with-deps-${ver}%2Bcpu.zip"              : "cpu/win",
-            "cu102/libtorch-cxx11-abi-shared-with-deps-${ver}%2Bcu102.zip"    : "cu102/linux",
-            "${cu11}/libtorch-cxx11-abi-shared-with-deps-${ver}%2B${cu11}.zip": "${cu11}/linux",
-            "cu102/libtorch-win-shared-with-deps-${ver}%2Bcu102.zip"          : "cu102/win",
-            "${cu11}/libtorch-win-shared-with-deps-${ver}%2B${cu11}.zip"      : "${cu11}/win"
+            "cpu/libtorch-cxx11-abi-shared-with-deps-${ver}%2Bcpu.zip"        : "cpu/linux-x86_64",
+            "cpu/libtorch-macos-${ver}.zip"                                   : "cpu/osx-x86_64",
+            "cpu/libtorch-win-shared-with-deps-${ver}%2Bcpu.zip"              : "cpu/win-x86_64",
+            "cu102/libtorch-cxx11-abi-shared-with-deps-${ver}%2Bcu102.zip"    : "cu102/linux-x86_64",
+            "${cu11}/libtorch-cxx11-abi-shared-with-deps-${ver}%2B${cu11}.zip": "${cu11}/linux-x86_64",
+            "cu102/libtorch-win-shared-with-deps-${ver}%2Bcu102.zip"          : "cu102/win-x86_64",
+            "${cu11}/libtorch-win-shared-with-deps-${ver}%2B${cu11}.zip"      : "${cu11}/win-x86_64",
+            "cpu/libtorch-shared-with-deps-${ver}%2Bcpu.zip"                  : "cpu-precxx11/linux-x86_64",
+            "${cu11}/libtorch-shared-with-deps-${ver}%2B${cu11}.zip"          : "${cu11}-precxx11/linux-x86_64"
     ]
-    if (precxx11) {
-        if (publish) {
-            files = [
-                    "cpu/libtorch-shared-with-deps-${ver}%2Bcpu.zip"        : "cpu/linux",
-                    "${cu11}/libtorch-shared-with-deps-${ver}%2B${cu11}.zip": "${cu11}/linux"
-            ]
-        } else {
-            files = [
-                    "cpu/libtorch-shared-with-deps-${ver}%2Bcpu.zip"        : "cpu-precxx11/linux",
-                    "${cu11}/libtorch-shared-with-deps-${ver}%2B${cu11}.zip": "${cu11}-precxx11/linux"
-            ]
-        }
-    }
     files.each { entry ->
         project.logger.lifecycle("Downloading ${url}/${entry.key}")
         def outputDir = new File("${binaryRoot}/${entry.value}")
@@ -127,7 +116,7 @@ def prepareNativeLib(String binaryRoot, String ver, boolean precxx11, boolean pu
             }
             into("${outputDir}/native/lib")
         }
-        if (precxx11) {
+        if (entry.value.contains("-precxx11")) {
             def libstd = new File("${outputDir}/native/lib/libstdc++.so.6")
             new URL("https://publish.djl.ai/extra/libstdc%2B%2B.so.6").withInputStream {
                 i -> libstd.withOutputStream { it << i }
@@ -180,14 +169,10 @@ jar {
     // when there is no java code inside src/main
     outputs.dir file("build/libs")
     doFirst {
-        def versionName = project.version
-        if (!isRelease) {
-            versionName += String.format("-%s", new Date().format('yyyyMMdd'))
-        }
         def dir = file("${placeholder}/native/lib")
         dir.mkdirs()
         def propFile = file("${placeholder}/native/lib/pytorch.properties")
-        propFile.text = "placeholder=true\nversion=${versionName}\n"
+        propFile.text = "placeholder=true\nversion=${project.version}\n"
     }
 
     from placeholder
@@ -235,30 +220,29 @@ publishing.repositories {
 
 task downloadPyTorchNativeLib() {
     doLast {
-        prepareNativeLib("${BINARY_ROOT}", "${VERSION}", isPrecxx11, true)
+        prepareNativeLib("${BINARY_ROOT}", "${VERSION}")
     }
 }
 
 task uploadS3 {
     doLast {
         delete "${BINARY_ROOT}"
-        prepareNativeLib("${BINARY_ROOT}", "${VERSION}", false, false)
-        prepareNativeLib("${BINARY_ROOT}", "${VERSION}", true, false)
+        prepareNativeLib("${BINARY_ROOT}", "${version}")
 
         exec {
             commandLine "sh", "-c", "find ${BINARY_ROOT} -type f | xargs gzip"
         }
         def f = new File("${BINARY_ROOT}/files.txt")
         def uploadDirs = [
-                "${BINARY_ROOT}/cpu/linux/native/lib/",
-                "${BINARY_ROOT}/cpu/osx/native/lib/",
-                "${BINARY_ROOT}/cpu/win/native/lib/",
-                "${BINARY_ROOT}/cpu-precxx11/linux/native/lib/",
+                "${BINARY_ROOT}/cpu/linux-x86_64/native/lib/",
+                "${BINARY_ROOT}/cpu/osx-x86_64/native/lib/",
+                "${BINARY_ROOT}/cpu/win-x86_64/native/lib/",
+                "${BINARY_ROOT}/cpu-precxx11/linux-x86_64/native/lib/",
                 "${BINARY_ROOT}/cu102/linux/native/lib/",
-                "${BINARY_ROOT}/cu102/win/native/lib/",
-                "${BINARY_ROOT}/cu113/linux/native/lib/",
-                "${BINARY_ROOT}/cu113/win/native/lib/",
-                "${BINARY_ROOT}/cu113-precxx11/linux/native/lib/"
+                "${BINARY_ROOT}/cu102/win-x86_64/native/lib/",
+                "${BINARY_ROOT}/cu113/linux-x86_64/native/lib/",
+                "${BINARY_ROOT}/cu113/win-x86_64/native/lib/",
+                "${BINARY_ROOT}/cu113-precxx11/linux-x86_64/native/lib/"
         ]
         uploadDirs.each { item ->
             fileTree(item).files.name.each {
@@ -289,10 +273,10 @@ flavorNames.each { flavor ->
                 def dsStore = file("${BINARY_ROOT}/${flavor}/${osName}/native/lib/.DS_Store")
                 dsStore.delete()
 
-                def versionName = String.format("${version}-%s", new Date().format('yyyyMMdd'))
+                def versionName = String.format("${project.version}-%s", new Date().format('yyyyMMdd'))
                 def dir = file("${BINARY_ROOT}/${flavor}/${osName}/native/lib")
                 def sb = new StringBuilder()
-                sb.append("version=${versionName}\nclassifier=${flavor}-${osName}-x86_64\nlibraries=")
+                sb.append("version=${versionName}\nflavor=${flavor}\nclassifier=${osName}\nlibraries=")
                 def first = true
                 for (String name : dir.list().sort()) {
                     if (first) {
@@ -311,14 +295,14 @@ flavorNames.each { flavor ->
                 def binaryLicenseFile = new File(metaInf, "NOTICE")
                 binaryLicenseFile.text = new URL("https://raw.githubusercontent.com/pytorch/pytorch/master/NOTICE").text
 
-                if (isPrecxx11) {
+                if (flavor.contains("-precxx11")) {
                     def libstd = new File(metaInf, "ATTRIBUTION")
                     libstd.text = new URL("https://publish.djl.ai/extra/THIRD-PARTY-LICENSES_qHnMKgbdWa.txt").text
                 }
             }
             from file("${BINARY_ROOT}/${flavor}/${osName}")
             from file("src/main/resources")
-            archiveClassifier = "${osName}-x86_64"
+            archiveClassifier = "${osName}"
 
             manifest {
                 attributes("Automatic-Module-Name": "ai.djl.pytorch_native_${flavor}_${osName}")
@@ -332,7 +316,7 @@ flavorNames.each { flavor ->
     // and the native jars have to be published separately
     if (project.getProjectDir().toString() == System.getProperty("user.dir")) {
         publishing.publications.create("${flavor}", MavenPublication) {
-            artifactId "pytorch-native-${flavor}" + (isPrecxx11 ? "-precxx11" : "")
+            artifactId "pytorch-native-${flavor}"
             from components.java
             artifacts = artifactsNames
             artifact jar
diff --git a/engines/tensorflow/tensorflow-engine/build.gradle b/engines/tensorflow/tensorflow-engine/build.gradle
index d1d8e4b24..4166ef21d 100644
--- a/engines/tensorflow/tensorflow-engine/build.gradle
+++ b/engines/tensorflow/tensorflow-engine/build.gradle
@@ -16,11 +16,10 @@ processResources {
         def classesDir = file("${project.buildDir}/classes/java/main/")
         classesDir.mkdirs()
         def file = new File(classesDir, "tensorflow-engine.properties")
-        file.text = "tensorflow_version=${tensorflow_version}"
+        file.text = "djl_version=${djl_version}\ntensorflow_version=${tensorflow_version}"
     }
 }
 
-
 publishing {
     publications {
         maven(MavenPublication) {
diff --git a/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/javacpp/LibUtils.java b/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/javacpp/LibUtils.java
index 7fcf1d79c..4761473aa 100644
--- a/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/javacpp/LibUtils.java
+++ b/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/javacpp/LibUtils.java
@@ -79,9 +79,7 @@ public final class LibUtils {
 
     private static synchronized String findLibraryInClasspath() {
         // TensorFlow doesn't support native library override
-        Platform platform =
-                Platform.detectPlatform(
-                        "tensorflow", "/tensorflow-engine.properties", "tensorflow_version");
+        Platform platform = Platform.detectPlatform("tensorflow");
         if (platform.isPlaceholder()) {
             return downloadTensorFlow(platform);
         }
diff --git a/engines/tensorrt/build.gradle b/engines/tensorrt/build.gradle
index 6b176d0f6..8fdff7b2c 100644
--- a/engines/tensorrt/build.gradle
+++ b/engines/tensorrt/build.gradle
@@ -14,7 +14,7 @@ dependencies {
 compileJava.dependsOn(processResources)
 
 processResources {
-    outputs.dir file("${project.buildDir}/classes/java/main/jnilib")
+    outputs.dir file("${project.buildDir}/classes/java/main/native/lib")
     doLast {
         def url = "https://publish.djl.ai/tensorrt/${trt_version}/jnilib/${djl_version}"
         def files = [
@@ -31,14 +31,15 @@ processResources {
                 new URL("${url}/${entry.key}").withInputStream { i -> file.withOutputStream { it << i } }
             }
         }
+        file("${project.buildDir}/classes/java/main/native/lib").mkdirs()
         copy {
             from jnilibDir
-            into "${project.buildDir}/classes/java/main/jnilib"
+            into "${project.buildDir}/classes/java/main/native/lib"
         }
 
         // write properties
-        def propFile = file("${project.buildDir}/classes/java/main/jnilib/tensorrt.properties")
-        propFile.text = "version=${trt_version}-${version}\n"
+        def propFile = file("${project.buildDir}/classes/java/main/native/lib/tensorrt.properties")
+        propFile.text = "version=${trt_version}-${version}"
     }
 }
 
diff --git a/engines/tensorrt/src/main/java/ai/djl/tensorrt/jni/LibUtils.java b/engines/tensorrt/src/main/java/ai/djl/tensorrt/jni/LibUtils.java
index e2e080cac..61d7c0823 100644
--- a/engines/tensorrt/src/main/java/ai/djl/tensorrt/jni/LibUtils.java
+++ b/engines/tensorrt/src/main/java/ai/djl/tensorrt/jni/LibUtils.java
@@ -51,8 +51,7 @@ public final class LibUtils {
 
     private static String copyJniLibraryFromClasspath() {
         String name = System.mapLibraryName(LIB_NAME);
-        Platform platform =
-                Platform.detectPlatform("tensorrt", "/jnilib/tensorrt.properties", "version");
+        Platform platform = Platform.detectPlatform("tensorrt");
         String classifier = platform.getClassifier();
         String version = platform.getVersion();
         Path cacheDir = Utils.getEngineCacheDir("tensorrt");
diff --git a/engines/tflite/tflite-engine/build.gradle b/engines/tflite/tflite-engine/build.gradle
index f2771f3df..bc548b1ae 100644
--- a/engines/tflite/tflite-engine/build.gradle
+++ b/engines/tflite/tflite-engine/build.gradle
@@ -16,7 +16,7 @@ processResources {
         def classesDir = file("${project.buildDir}/classes/java/main/")
         classesDir.mkdirs()
         def file = new File(classesDir, "tflite-engine.properties")
-        file.text = "tflite_version=${tflite_version}"
+        file.text = "djl_version=${djl_version}\ntflite_version=${tflite_version}"
     }
 }
 
diff --git a/engines/tflite/tflite-engine/src/main/java/ai/djl/tflite/engine/LibUtils.java b/engines/tflite/tflite-engine/src/main/java/ai/djl/tflite/engine/LibUtils.java
index be95a791e..96332c76d 100644
--- a/engines/tflite/tflite-engine/src/main/java/ai/djl/tflite/engine/LibUtils.java
+++ b/engines/tflite/tflite-engine/src/main/java/ai/djl/tflite/engine/LibUtils.java
@@ -55,8 +55,7 @@ public final class LibUtils {
     }
 
     private static synchronized String findLibraryInClasspath() {
-        Platform platform =
-                Platform.detectPlatform("tflite", "/tflite-engine.properties", "tflite_version");
+        Platform platform = Platform.detectPlatform("tflite");
         if (platform.isPlaceholder()) {
             return downloadTfLite(platform);
         }
diff --git a/examples/pom.xml b/examples/pom.xml
index ea618974e..626820dff 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -43,11 +43,6 @@
             <artifactId>log4j-slf4j-impl</artifactId>
             <version>2.12.1</version>
         </dependency>
-        <dependency>
-            <groupId>com.google.code.gson</groupId>
-            <artifactId>gson</artifactId>
-            <version>2.8.5</version>
-        </dependency>
         <dependency>
             <groupId>ai.djl</groupId>
             <artifactId>api</artifactId>
@@ -69,14 +64,6 @@
             <groupId>ai.djl.mxnet</groupId>
             <artifactId>mxnet-engine</artifactId>
         </dependency>
-        <dependency>
-            <!--
-                See https://github.com/deepjavalibrary/djl/blob/master/mxnet/mxnet-engine/README.md for more MXNet library selection options
-            -->
-            <groupId>ai.djl.mxnet</groupId>
-            <artifactId>mxnet-native-auto</artifactId>
-            <scope>runtime</scope>
-        </dependency>
         <!-- Pytorch -->
         <dependency>
             <groupId>ai.djl.pytorch</groupId>
diff --git a/extensions/fasttext/src/main/java/ai/djl/fasttext/jni/LibUtils.java b/extensions/fasttext/src/main/java/ai/djl/fasttext/jni/LibUtils.java
index 369f61d8f..68dd98d9e 100644
--- a/extensions/fasttext/src/main/java/ai/djl/fasttext/jni/LibUtils.java
+++ b/extensions/fasttext/src/main/java/ai/djl/fasttext/jni/LibUtils.java
@@ -55,7 +55,7 @@ public final class LibUtils {
     private static String copyJniLibraryFromClasspath() {
         String name = System.mapLibraryName(LIB_NAME);
         Path nativeDir = Utils.getEngineCacheDir("fasttext");
-        Platform platform = Platform.detectPlatform("fasttext", null, null);
+        Platform platform = Platform.detectPlatform("fasttext");
         String classifier = platform.getClassifier();
         String version = platform.getVersion();
         Path path = nativeDir.resolve(version).resolve(name);
diff --git a/extensions/sentencepiece/src/main/java/ai/djl/sentencepiece/jni/LibUtils.java b/extensions/sentencepiece/src/main/java/ai/djl/sentencepiece/jni/LibUtils.java
index 80fee4395..7a51d7471 100644
--- a/extensions/sentencepiece/src/main/java/ai/djl/sentencepiece/jni/LibUtils.java
+++ b/extensions/sentencepiece/src/main/java/ai/djl/sentencepiece/jni/LibUtils.java
@@ -55,7 +55,7 @@ public final class LibUtils {
     private static String copyJniLibraryFromClasspath() {
         String name = System.mapLibraryName(LIB_NAME);
         Path nativeDir = Utils.getEngineCacheDir("sentencepiece");
-        Platform platform = Platform.detectPlatform("sentencepiece", null, null);
+        Platform platform = Platform.detectPlatform("sentencepiece");
         String classifier = platform.getClassifier();
         String version = platform.getVersion();
         Path path = nativeDir.resolve(version).resolve(name);
diff --git a/settings.gradle b/settings.gradle
index f9aa78cf7..da6aad371 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -14,6 +14,7 @@ include ':engines:paddlepaddle:paddlepaddle-engine'
 include ':engines:paddlepaddle:paddlepaddle-model-zoo'
 include ':engines:paddlepaddle:paddlepaddle-native'
 include ':engines:pytorch:pytorch-engine'
+include ':engines:pytorch:pytorch-jni'
 include ':engines:pytorch:pytorch-model-zoo'
 include ':engines:pytorch:pytorch-native'
 include ':engines:tensorflow:tensorflow-api'
diff --git a/tools/gradle/publish.gradle b/tools/gradle/publish.gradle
index 28021d259..25a9afb18 100644
--- a/tools/gradle/publish.gradle
+++ b/tools/gradle/publish.gradle
@@ -9,6 +9,7 @@ configure([
         project(':engines:paddlepaddle:paddlepaddle-engine'),
         project(':engines:paddlepaddle:paddlepaddle-model-zoo'),
         project(':engines:pytorch:pytorch-engine'),
+        project(':engines:pytorch:pytorch-jni'),
         project(':engines:pytorch:pytorch-model-zoo'),
         project(':engines:tensorflow:tensorflow-api'),
         project(':engines:tensorflow:tensorflow-engine'),


Test patch:
diff --git a/api/src/test/java/ai/djl/util/PlatformTest.java b/api/src/test/java/ai/djl/util/PlatformTest.java
index 3026280b9..b3d0b27cc 100644
--- a/api/src/test/java/ai/djl/util/PlatformTest.java
+++ b/api/src/test/java/ai/djl/util/PlatformTest.java
@@ -41,14 +41,26 @@ public class PlatformTest {
         Assert.assertEquals(system.getOsPrefix(), "linux");
         Assert.assertEquals(system.getOsArch(), "x86_64");
 
-        url = createPropertyFile("version=1.8.0\nplaceholder=true");
+        url = createPropertyFile("version=1.8.0\nflavor=cpu-precxx11\nclassifier=linux-x86_64");
         Platform platform = Platform.fromUrl(url);
+        Assert.assertEquals(platform.getFlavor(), "cpu-precxx11");
+        Assert.assertEquals(platform.getClassifier(), "linux-x86_64");
+        Assert.assertEquals(platform.getOsPrefix(), "linux");
+        Assert.assertEquals(platform.getOsArch(), "x86_64");
+        Assert.assertTrue(platform.matches(system));
+        Assert.assertFalse(system.matches(platform));
+
+        url = createPropertyFile("version=1.8.0\nplaceholder=true");
+        platform = Platform.fromUrl(url);
         Assert.assertTrue(platform.isPlaceholder());
 
         url = createPropertyFile("version=1.8.0\nclassifier=cu111-linux-x86_64");
         platform = Platform.fromUrl(url);
+        Assert.assertTrue(platform.matches(system));
+
+        url = createPropertyFile("version=1.8.0\nclassifier=cu102-linux-x86_64");
+        platform = Platform.fromUrl(url);
         Assert.assertFalse(platform.matches(system));
-        Assert.assertTrue(platform.matches(system, false));
 
         // MXNet
         url = createPropertyFile("version=1.8.0\nclassifier=cu113mkl-linux-x86_64");