Repository: deepjavalibrary/djl
Pull request #2077 (2022-10-11 19:25:30)

[android] Fixes gradle dependencies
## 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



Patch:
diff --git a/.github/workflows/native_jni_s3_pytorch.yml b/.github/workflows/native_jni_s3_pytorch.yml
index 683cbd4f0..d6eeb8839 100644
--- a/.github/workflows/native_jni_s3_pytorch.yml
+++ b/.github/workflows/native_jni_s3_pytorch.yml
@@ -163,26 +163,26 @@ jobs:
           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
           restore-keys: |
             ${{ runner.os }}-gradle-
-      - name: Install CUDA 11.6
+      - name: Release CPU JNI
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
-          curl.exe -L https://developer.download.nvidia.com/compute/cuda/11.6.1/local_installers/cuda_11.6.1_511.65_windows.exe -o cuda.exe
-          curl.exe -L https://developer.download.nvidia.com/compute/redist/cudnn/v8.4.1/local_installers/11.6/cudnn-windows-x86_64-8.4.1.50_cuda11.6-archive.zip -o cudnn.zip
-          cuda.exe -s
-          unzip.exe cudnn.zip
-          cp.exe -a cudnn*/include cudnn*/lib cudnn*/bin "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.6/"
-          rm.exe -Rf cuda.exe cuda.exe cudnn.zip cudnn*
-      - name: Release CPU JNI
+          gradlew :engines:pytorch:pytorch-native:compileJNI -Ppt_version=${{ github.event.inputs.pt_version }}
+      - name: Install CUDA 11.7
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
-          gradlew :engines:pytorch:pytorch-native:compileJNI -Ppt_version=${{ github.event.inputs.pt_version }}
+          curl.exe -L https://developer.download.nvidia.com/compute/cuda/11.7.1/local_installers/cuda_11.7.1_516.94_windows.exe -o cuda.exe
+          curl.exe -L https://developer.download.nvidia.com/compute/redist/cudnn/v8.5.0/local_installers/11.7/cudnn-windows-x86_64-8.5.0.96_cuda11-archive.zip -o cudnn.zip
+          cuda.exe -s
+          unzip.exe cudnn.zip
+          cp.exe -a cudnn*/include cudnn*/lib cudnn*/bin "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.7/"
+          rm.exe -Rf cuda.exe cuda.exe cudnn.zip cudnn*
       - name: Release cuda11 JNI
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
-          set "CUDA_PATH=%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v11.6"
+          set "CUDA_PATH=%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v11.7"
           set "PATH=%CUDA_PATH%\bin;%CUDA_PATH%\libnvvp;%PATH%"
           gradlew :engines:pytorch:pytorch-native:cleanJNI :engines:pytorch:pytorch-native:compileJNI -Pcu11 -Ppt_version=${{ github.event.inputs.pt_version }}
       - name: Configure AWS Credentials
diff --git a/android/README.md b/android/README.md
index ef167cb3f..f158c5825 100644
--- a/android/README.md
+++ b/android/README.md
@@ -16,9 +16,12 @@ In gradle, you can add the 5 modules in your dependencies:
 
 ```
 dependencies {
-    implementation "ai.djl:api:0.20.0"
+    implementation platform("ai.djl:bom:0.20.0")
+
+    implementation "ai.djl:api"
+    // TODO: remove djl android package version in 0.21.0
     implementation "ai.djl.android:core:0.20.0"
-    runtimeOnly "ai.djl.pytorch:pytorch-engine:0.20.0"
+    runtimeOnly "ai.djl.pytorch:pytorch-engine"
     runtimeOnly "ai.djl.android:pytorch-native:0.20.0"
     runtimeOnly "ai.djl.android:onnxruntime:0.20.0"
 }
diff --git a/android/core/build.gradle b/android/core/build.gradle
index 0a31876cf..8f964f6b3 100644
--- a/android/core/build.gradle
+++ b/android/core/build.gradle
@@ -22,8 +22,8 @@ android {
         }
     }
     compileOptions {
-        sourceCompatibility = 1.8
-        targetCompatibility = 1.8
+        sourceCompatibility = 11
+        targetCompatibility = 11
     }
 }
 
@@ -98,11 +98,15 @@ afterEvaluate {
 }
 
 dependencies {
-    implementation "ai.djl:api:${version}"
+    implementation platform("ai.djl:bom:${version}")
+
+    implementation "ai.djl:api"
     testImplementation 'junit:junit:4.13.2'
-    androidTestRuntimeOnly "ai.djl.pytorch:pytorch-engine:${version}"
+    androidTestRuntimeOnly "ai.djl.pytorch:pytorch-engine"
+
+    // TODO: remove version in 0.21.0
     androidTestRuntimeOnly "ai.djl.android:pytorch-native:${version}"
     androidTestImplementation "ai.djl.android:onnxruntime:${version}"
-    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java
new file mode 100644
index 000000000..d48176aa4
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/translator/BaseImageTranslatorFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.modality.cv.translator;
+
+import ai.djl.modality.Input;
+import ai.djl.modality.Output;
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.translator.wrapper.FileImagePreProcesor;
+import ai.djl.modality.cv.translator.wrapper.InputStreamImagePreProcessor;
+import ai.djl.modality.cv.translator.wrapper.UrlImagePreProcessor;
+import ai.djl.translate.ExpansionTranslatorFactory;
+import ai.djl.translate.PreProcessor;
+import ai.djl.util.Pair;
+
+import java.io.InputStream;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+/**
+ * A helper to create a {@link ai.djl.translate.TranslatorFactory} with the {@link
+ * BaseImageTranslator}.
+ *
+ * @param <O> the output type for the {@link ai.djl.translate.TranslatorFactory}.
+ */
+public abstract class BaseImageTranslatorFactory<O> extends ExpansionTranslatorFactory<Image, O> {
+
+    /** {@inheritDoc} */
+    @Override
+    protected Map<Type, Function<PreProcessor<Image>, PreProcessor<?>>>
+            getPreprocessorExpansions() {
+        Map<Type, Function<PreProcessor<Image>, PreProcessor<?>>> expansions =
+                new ConcurrentHashMap<>();
+        expansions.put(Path.class, FileImagePreProcesor::new);
+        expansions.put(URL.class, UrlImagePreProcessor::new);
+        expansions.put(InputStream.class, InputStreamImagePreProcessor::new);
+        return expansions;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected Map<Pair<Type, Type>, TranslatorExpansion<Image, O>> getExpansions() {
+        Map<Pair<Type, Type>, TranslatorExpansion<Image, O>> expansions = new ConcurrentHashMap<>();
+        expansions.put(new Pair<>(Input.class, Output.class), ImageServingTranslator::new);
+        return expansions;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Class<Image> getBaseInputType() {
+        return Image.class;
+    }
+}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactory.java
index 9ee08ceb5..24a63135b 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactory.java
@@ -14,42 +14,15 @@ package ai.djl.modality.cv.translator;
 
 import ai.djl.Model;
 import ai.djl.modality.Classifications;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
-import ai.djl.translate.ExpansionTranslatorFactory;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
-import ai.djl.util.Pair;
 
-import java.io.InputStream;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
 
 /** A {@link TranslatorFactory} that creates an {@link ImageClassificationTranslator}. */
 public class ImageClassificationTranslatorFactory
-        extends ExpansionTranslatorFactory<Image, Classifications> {
-
-    private static final Map<
-                    Pair<Type, Type>,
-                    Function<Translator<Image, Classifications>, Translator<?, ?>>>
-            EXPANSIONS = new ConcurrentHashMap<>();
-
-    static {
-        EXPANSIONS.put(new Pair<>(Image.class, Classifications.class), t -> t);
-        EXPANSIONS.put(new Pair<>(Path.class, Classifications.class), FileTranslator::new);
-        EXPANSIONS.put(new Pair<>(URL.class, Classifications.class), UrlTranslator::new);
-        EXPANSIONS.put(
-                new Pair<>(InputStream.class, Classifications.class), InputStreamTranslator::new);
-        EXPANSIONS.put(new Pair<>(Input.class, Output.class), ImageServingTranslator::new);
-    }
+        extends BaseImageTranslatorFactory<Classifications> {
 
     /** {@inheritDoc} */
     @Override
@@ -60,8 +33,7 @@ public class ImageClassificationTranslatorFactory
 
     /** {@inheritDoc} */
     @Override
-    protected Map<Pair<Type, Type>, Function<Translator<Image, Classifications>, Translator<?, ?>>>
-            getExpansions() {
-        return EXPANSIONS;
+    public Class<Classifications> getBaseOutputType() {
+        return Classifications.class;
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java
index b89c7a155..12e41b648 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ImageFeatureExtractorFactory.java
@@ -13,60 +13,24 @@
 package ai.djl.modality.cv.translator;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
-import ai.djl.util.Pair;
 
-import java.io.InputStream;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.nio.file.Path;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 /** A {@link TranslatorFactory} that creates an {@link ImageClassificationTranslator}. */
-public class ImageFeatureExtractorFactory implements TranslatorFactory {
-
-    private static final Set<Pair<Type, Type>> SUPPORTED_TYPES = new HashSet<>();
-
-    static {
-        SUPPORTED_TYPES.add(new Pair<>(Image.class, byte[].class));
-        SUPPORTED_TYPES.add(new Pair<>(Path.class, byte[].class));
-        SUPPORTED_TYPES.add(new Pair<>(URL.class, byte[].class));
-        SUPPORTED_TYPES.add(new Pair<>(InputStream.class, byte[].class));
-        SUPPORTED_TYPES.add(new Pair<>(Input.class, Output.class));
-    }
+public class ImageFeatureExtractorFactory extends BaseImageTranslatorFactory<byte[]> {
 
     /** {@inheritDoc} */
     @Override
-    public Set<Pair<Type, Type>> getSupportedTypes() {
-        return SUPPORTED_TYPES;
+    protected Translator<Image, byte[]> buildBaseTranslator(Model model, Map<String, ?> arguments) {
+        return ImageFeatureExtractor.builder(arguments).build();
     }
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        ImageFeatureExtractor translator = ImageFeatureExtractor.builder(arguments).build();
-        if (input == Image.class && output == byte[].class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == byte[].class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == byte[].class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == byte[].class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    public Class<byte[]> getBaseOutputType() {
+        return byte[].class;
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactory.java
index 7394356cb..d2216779c 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactory.java
@@ -13,19 +13,11 @@
 package ai.djl.modality.cv.translator;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** A {@link TranslatorFactory} that creates a {@link InstanceSegmentationTranslator} instance. */
@@ -33,22 +25,8 @@ public class InstanceSegmentationTranslatorFactory extends ObjectDetectionTransl
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        InstanceSegmentationTranslator translator =
-                InstanceSegmentationTranslator.builder(arguments).build();
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return InstanceSegmentationTranslator.builder(arguments).build();
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslatorFactory.java
index 3cd2dac1a..ee89e4002 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/ObjectDetectionTranslatorFactory.java
@@ -12,38 +12,18 @@
  */
 package ai.djl.modality.cv.translator;
 
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
-import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
 import ai.djl.translate.TranslatorFactory;
-import ai.djl.util.Pair;
-
-import java.io.InputStream;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.nio.file.Path;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * An abstract {@link TranslatorFactory} that creates a {@link ObjectDetectionTranslator} instance.
  */
-public abstract class ObjectDetectionTranslatorFactory implements TranslatorFactory {
-
-    private static final Set<Pair<Type, Type>> SUPPORTED_TYPES = new HashSet<>();
-
-    static {
-        SUPPORTED_TYPES.add(new Pair<>(Image.class, DetectedObjects.class));
-        SUPPORTED_TYPES.add(new Pair<>(Path.class, DetectedObjects.class));
-        SUPPORTED_TYPES.add(new Pair<>(URL.class, DetectedObjects.class));
-        SUPPORTED_TYPES.add(new Pair<>(InputStream.class, DetectedObjects.class));
-        SUPPORTED_TYPES.add(new Pair<>(Input.class, Output.class));
-    }
+public abstract class ObjectDetectionTranslatorFactory
+        extends BaseImageTranslatorFactory<DetectedObjects> {
 
     /** {@inheritDoc} */
     @Override
-    public Set<Pair<Type, Type>> getSupportedTypes() {
-        return SUPPORTED_TYPES;
+    public Class<DetectedObjects> getBaseOutputType() {
+        return DetectedObjects.class;
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactory.java
index d4f794ad7..e2c3f11a0 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactory.java
@@ -13,19 +13,11 @@
 package ai.djl.modality.cv.translator;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link SingleShotDetectionTranslator} instance. */
@@ -33,22 +25,8 @@ public class SingleShotDetectionTranslatorFactory extends ObjectDetectionTransla
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        SingleShotDetectionTranslator translator =
-                SingleShotDetectionTranslator.builder(arguments).build();
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return SingleShotDetectionTranslator.builder(arguments).build();
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslatorFactory.java
index b474e15fc..bd799ec15 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloTranslatorFactory.java
@@ -13,19 +13,11 @@
 package ai.djl.modality.cv.translator;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link YoloTranslator} instance. */
@@ -33,21 +25,8 @@ public class YoloTranslatorFactory extends ObjectDetectionTranslatorFactory {
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        YoloTranslator translator = YoloTranslator.builder(arguments).build();
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return YoloTranslator.builder(arguments).build();
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactory.java b/api/src/main/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactory.java
index c18051aa0..c5b0a971f 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactory.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactory.java
@@ -13,19 +13,11 @@
 package ai.djl.modality.cv.translator;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link YoloV5Translator} instance. */
@@ -33,21 +25,8 @@ public class YoloV5TranslatorFactory extends ObjectDetectionTranslatorFactory {
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        YoloV5Translator translator = YoloV5Translator.builder(arguments).build();
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return YoloV5Translator.builder(arguments).build();
     }
 }
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcesor.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcesor.java
new file mode 100644
index 000000000..ad91e8f52
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileImagePreProcesor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.modality.cv.translator.wrapper;
+
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.ImageFactory;
+import ai.djl.ndarray.NDList;
+import ai.djl.translate.PreProcessor;
+import ai.djl.translate.TranslatorContext;
+
+import java.nio.file.Path;
+
+/** Built-in {@code PreProcessor} that provides image pre-processing from file path. */
+public class FileImagePreProcesor implements PreProcessor<Path> {
+
+    private PreProcessor<Image> preProcessor;
+
+    /**
+     * Creates a {@code FileImagePreProcesor} instance.
+     *
+     * @param preProcessor a {@code {@link PreProcessor}} that can process image
+     */
+    public FileImagePreProcesor(PreProcessor<Image> preProcessor) {
+        this.preProcessor = preProcessor;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDList processInput(TranslatorContext ctx, Path input) throws Exception {
+        Image image = ImageFactory.getInstance().fromFile(input);
+        return preProcessor.processInput(ctx, image);
+    }
+}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileTranslator.java
deleted file mode 100644
index b3c9b9a42..000000000
--- a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/FileTranslator.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
- * with the License. A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0/
- *
- * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- */
-package ai.djl.modality.cv.translator.wrapper;
-
-import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.ImageFactory;
-import ai.djl.ndarray.NDList;
-import ai.djl.translate.Batchifier;
-import ai.djl.translate.Translator;
-import ai.djl.translate.TranslatorContext;
-
-import java.nio.file.Path;
-
-/**
- * Built-in {@code Translator} that provides image pre-processing from file path.
- *
- * @param <T> the output object type
- */
-public class FileTranslator<T> implements Translator<Path, T> {
-
-    private Translator<Image, T> translator;
-
-    /**
-     * Creates a {@code FileTranslator} instance.
-     *
-     * @param translator a {@code Translator} that can process image
-     */
-    public FileTranslator(Translator<Image, T> translator) {
-        this.translator = translator;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public NDList processInput(TranslatorContext ctx, Path input) throws Exception {
-        Image image = ImageFactory.getInstance().fromFile(input);
-        return translator.processInput(ctx, image);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public T processOutput(TranslatorContext ctx, NDList list) throws Exception {
-        return translator.processOutput(ctx, list);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Batchifier getBatchifier() {
-        return translator.getBatchifier();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void prepare(TranslatorContext ctx) throws Exception {
-        translator.prepare(ctx);
-    }
-}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/InputStreamImagePreProcessor.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/InputStreamImagePreProcessor.java
new file mode 100644
index 000000000..786623e3b
--- /dev/null
+++ b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/InputStreamImagePreProcessor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.modality.cv.translator.wrapper;
+
+import ai.djl.modality.cv.Image;
+import ai.djl.modality.cv.ImageFactory;
+import ai.djl.ndarray.NDList;
+import ai.djl.translate.PreProcessor;
+import ai.djl.translate.TranslatorContext;
+
+import java.io.InputStream;
+
+/** Built-in {@code PreProcessor} that provides image pre-processing from {@code InputStream}. */
+public class InputStreamImagePreProcessor<T> implements PreProcessor<InputStream> {
+
+    private PreProcessor<Image> preProcessor;
+
+    /**
+     * Creates a {@code InputStreamImagePreProcessor} instance.
+     *
+     * @param preProcessor a {@code PreProcessor} that can process image
+     */
+    public InputStreamImagePreProcessor(PreProcessor<Image> preProcessor) {
+        this.preProcessor = preProcessor;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDList processInput(TranslatorContext ctx, InputStream input) throws Exception {
+        Image image = ImageFactory.getInstance().fromInputStream(input);
+        return preProcessor.processInput(ctx, image);
+    }
+}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/InputStreamTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/InputStreamTranslator.java
deleted file mode 100644
index 1ed0698c0..000000000
--- a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/InputStreamTranslator.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
- * with the License. A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0/
- *
- * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- */
-package ai.djl.modality.cv.translator.wrapper;
-
-import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.ImageFactory;
-import ai.djl.ndarray.NDList;
-import ai.djl.translate.Batchifier;
-import ai.djl.translate.Translator;
-import ai.djl.translate.TranslatorContext;
-
-import java.io.InputStream;
-
-/**
- * Built-in {@code Translator} that provides image pre-processing from {@code InputStream}.
- *
- * @param <T> the output object type
- */
-public class InputStreamTranslator<T> implements Translator<InputStream, T> {
-
-    private Translator<Image, T> translator;
-
-    /**
-     * Creates a {@code UrlTranslator} instance.
-     *
-     * @param translator a {@code Translator} that can process image
-     */
-    public InputStreamTranslator(Translator<Image, T> translator) {
-        this.translator = translator;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public NDList processInput(TranslatorContext ctx, InputStream input) throws Exception {
-        Image image = ImageFactory.getInstance().fromInputStream(input);
-        return translator.processInput(ctx, image);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public T processOutput(TranslatorContext ctx, NDList list) throws Exception {
-        return translator.processOutput(ctx, list);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Batchifier getBatchifier() {
-        return translator.getBatchifier();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void prepare(TranslatorContext ctx) throws Exception {
-        translator.prepare(ctx);
-    }
-}
diff --git a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/UrlTranslator.java b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/UrlImagePreProcessor.java
similarity index 52%
rename from api/src/main/java/ai/djl/modality/cv/translator/wrapper/UrlTranslator.java
rename to api/src/main/java/ai/djl/modality/cv/translator/wrapper/UrlImagePreProcessor.java
index bf6b06a76..c12a32da8 100644
--- a/api/src/main/java/ai/djl/modality/cv/translator/wrapper/UrlTranslator.java
+++ b/api/src/main/java/ai/djl/modality/cv/translator/wrapper/UrlImagePreProcessor.java
@@ -15,52 +15,33 @@ package ai.djl.modality.cv.translator.wrapper;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.ImageFactory;
 import ai.djl.ndarray.NDList;
-import ai.djl.translate.Batchifier;
-import ai.djl.translate.Translator;
+import ai.djl.translate.PreProcessor;
 import ai.djl.translate.TranslatorContext;
 
 import java.net.URL;
 
 /**
- * Built-in {@code Translator} that provides image pre-processing from URL.
+ * Built-in {@code PreProcessor} that provides image pre-processing from URL.
  *
  * @param <T> the output object type
  */
-public class UrlTranslator<T> implements Translator<URL, T> {
+public class UrlImagePreProcessor<T> implements PreProcessor<URL> {
 
-    private Translator<Image, T> translator;
+    private PreProcessor<Image> preProcessor;
 
     /**
-     * Creates a {@code UrlTranslator} instance.
+     * Creates a {@code UrlImagePreProcessor} instance.
      *
-     * @param translator a {@code Translator} that can process image
+     * @param preProcessor a {@code PreProcessor} that can process image
      */
-    public UrlTranslator(Translator<Image, T> translator) {
-        this.translator = translator;
+    public UrlImagePreProcessor(PreProcessor<Image> preProcessor) {
+        this.preProcessor = preProcessor;
     }
 
     /** {@inheritDoc} */
     @Override
     public NDList processInput(TranslatorContext ctx, URL input) throws Exception {
         Image image = ImageFactory.getInstance().fromUrl(input);
-        return translator.processInput(ctx, image);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public T processOutput(TranslatorContext ctx, NDList list) throws Exception {
-        return translator.processOutput(ctx, list);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Batchifier getBatchifier() {
-        return translator.getBatchifier();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void prepare(TranslatorContext ctx) throws Exception {
-        translator.prepare(ctx);
+        return preProcessor.processInput(ctx, image);
     }
 }
diff --git a/api/src/main/java/ai/djl/ndarray/internal/NDArrayEx.java b/api/src/main/java/ai/djl/ndarray/internal/NDArrayEx.java
index 8368a4716..b12ac5dd0 100644
--- a/api/src/main/java/ai/djl/ndarray/internal/NDArrayEx.java
+++ b/api/src/main/java/ai/djl/ndarray/internal/NDArrayEx.java
@@ -227,13 +227,15 @@ public interface NDArrayEx {
             NDList inputs,
             NDList weights,
             float learningRate,
+            float learningRateBiasCorrection,
             float weightDecay,
             float rescaleGrad,
             float clipGrad,
             float beta1,
             float beta2,
             float epsilon,
-            boolean lazyUpdate);
+            boolean lazyUpdate,
+            boolean adamw);
 
     void nagUpdate(
             NDList inputs,
diff --git a/api/src/main/java/ai/djl/nn/AbstractBaseBlock.java b/api/src/main/java/ai/djl/nn/AbstractBaseBlock.java
index d20d6ba43..cb0d6ebe5 100644
--- a/api/src/main/java/ai/djl/nn/AbstractBaseBlock.java
+++ b/api/src/main/java/ai/djl/nn/AbstractBaseBlock.java
@@ -46,7 +46,7 @@ public abstract class AbstractBaseBlock implements Block {
     /** The shape of the input for this block, set by the initialization process. */
     protected Shape[] inputShapes;
 
-    protected DataType[] outputDatatypes;
+    protected DataType[] outputDataTypes;
 
     /** List of names for the input, named inputs should be manually set in sub class. */
     protected List<String> inputNames = Collections.emptyList();
@@ -375,6 +375,6 @@ public abstract class AbstractBaseBlock implements Block {
     /** {@inheritDoc} */
     @Override
     public DataType[] getOutputDataTypes() {
-        return outputDatatypes;
+        return outputDataTypes;
     }
 }
diff --git a/api/src/main/java/ai/djl/nn/LambdaBlock.java b/api/src/main/java/ai/djl/nn/LambdaBlock.java
index 26b3f38a3..c0c926499 100644
--- a/api/src/main/java/ai/djl/nn/LambdaBlock.java
+++ b/api/src/main/java/ai/djl/nn/LambdaBlock.java
@@ -122,7 +122,7 @@ public class LambdaBlock extends AbstractBlock {
                 outputShapes[i] = output.get(i).getShape();
                 dataTypes[i] = output.get(i).getDataType();
             }
-            outputDatatypes = dataTypes;
+            outputDataTypes = dataTypes;
             return outputShapes;
         }
     }
diff --git a/api/src/main/java/ai/djl/repository/zoo/BaseModelLoader.java b/api/src/main/java/ai/djl/repository/zoo/BaseModelLoader.java
index 6e15103ca..cb5e68921 100644
--- a/api/src/main/java/ai/djl/repository/zoo/BaseModelLoader.java
+++ b/api/src/main/java/ai/djl/repository/zoo/BaseModelLoader.java
@@ -28,6 +28,7 @@ import ai.djl.translate.TranslatorFactory;
 import ai.djl.util.ClassLoaderUtils;
 import ai.djl.util.Pair;
 import ai.djl.util.Progress;
+import ai.djl.util.Utils;
 
 import java.io.IOException;
 import java.io.Reader;
@@ -102,6 +103,7 @@ public class BaseModelLoader implements ModelLoader {
             if (modelDir == null) {
                 throw new AssertionError("Directory should not be null.");
             }
+            modelDir = Utils.getNestedModelDir(modelDir);
 
             loadServingProperties(modelDir, arguments, options);
             Application application = criteria.getApplication();
diff --git a/api/src/main/java/ai/djl/training/optimizer/Adam.java b/api/src/main/java/ai/djl/training/optimizer/Adam.java
index 5232dbb50..22ee0b390 100644
--- a/api/src/main/java/ai/djl/training/optimizer/Adam.java
+++ b/api/src/main/java/ai/djl/training/optimizer/Adam.java
@@ -24,16 +24,15 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * {@code Adam} is a generalization of the AdaGrad {@link Optimizer}.
- *
- * <p>Adam updates the weights using:<br>
- * <br>
+ * {@code Adam} is a generalization of the AdaGrad {@link Optimizer}. <br>
+ * \( grad += weight_decay * w\)<br>
  * \( m = beta1 * m + (1 - beta1) * grad\)<br>
  * \( v = beta2 * v + (1 - beta2) * grad^2 \)<br>
- * \( w -= learning_rate * m / (sqrt(v) + epsilon) \)<br>
+ * \( learning_rate_bias_correction = learning_rate / beta1**t * sqrt(beta2**t) \)<br>
+ * \( w -= learning_rate_bias_correction * m / (sqrt(v) + epsilon) \)<br>
  * <br>
  * where g represents the gradient, and m/v are 1st and 2nd order moment estimates (mean and
- * variance).
+ * variance), t is the step.
  *
  * @see <a href="https://d2l.djl.ai/chapter_optimization/adam.html">The D2L chapter on Adam</a>
  */
@@ -68,8 +67,8 @@ public class Adam extends Optimizer {
         int t = updateCount(parameterId);
         double coef1 = 1.0 - Math.pow(beta1, t);
         double coef2 = 1.0 - Math.pow(beta2, t);
-        float lr = learningRateTracker.getNewValue(parameterId, t);
-        float newLearningRate = (float) (lr * Math.sqrt(coef2) / coef1);
+        float newLearningRate = learningRateTracker.getNewValue(parameterId, t);
+        float learningRateBiasCorrection = (float) (newLearningRate * Math.sqrt(coef2) / coef1);
         float weightDecay = getWeightDecay();
 
         Preconditions.checkArgument(
@@ -97,13 +96,15 @@ public class Adam extends Optimizer {
                 inputs,
                 weights,
                 newLearningRate,
+                learningRateBiasCorrection,
                 weightDecay,
                 rescaleGrad,
                 clipGrad,
                 beta1,
                 beta2,
                 epsilon,
-                true);
+                true,
+                false);
     }
 
     /**
diff --git a/api/src/main/java/ai/djl/training/optimizer/AdamW.java b/api/src/main/java/ai/djl/training/optimizer/AdamW.java
new file mode 100644
index 000000000..f700315f0
--- /dev/null
+++ b/api/src/main/java/ai/djl/training/optimizer/AdamW.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.training.optimizer;
+
+import ai.djl.Device;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.internal.NDArrayEx;
+import ai.djl.training.tracker.ParameterTracker;
+import ai.djl.training.tracker.Tracker;
+import ai.djl.util.Preconditions;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * {@code Adam} is a generalization of the AdaGrad {@link Optimizer}.
+ *
+ * <p>Adam updates the weights using:<br>
+ * <br>
+ * \( w *= (1 - learning_rate * weight_decay\)<br>
+ * \( m = beta1 * m + (1 - beta1) * grad\)<br>
+ * \( v = beta2 * v + (1 - beta2) * grad^2 \)<br>
+ * \( learning_rate_bias_correction = learning_rate / beta1**t * sqrt(beta2**t) \)<br>
+ * \( w -= learning_rate_bias_correction * m / (sqrt(v) + epsilon) \)<br>
+ * <br>
+ * where g represents the gradient, and m/v are 1st and 2nd order moment estimates (mean and
+ * variance), t is the step.
+ *
+ * @see <a href="https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html">The algorithm of
+ *     AdamW</a>
+ */
+public class AdamW extends Optimizer {
+
+    private ParameterTracker learningRateTracker;
+    private float beta1;
+    private float beta2;
+    private float epsilon;
+
+    private Map<String, Map<Device, NDArray>> means;
+    private Map<String, Map<Device, NDArray>> variances;
+
+    /**
+     * Creates a new instance of {@code Adam} optimizer.
+     *
+     * @param builder the builder to create a new instance of {@code Adam} optimizer
+     */
+    protected AdamW(Builder builder) {
+        super(builder);
+        learningRateTracker = builder.learningRateTracker;
+        beta1 = builder.beta1;
+        beta2 = builder.beta2;
+        epsilon = builder.epsilon;
+        means = new ConcurrentHashMap<>();
+        variances = new ConcurrentHashMap<>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void update(String parameterId, NDArray weight, NDArray grad) {
+        int t = updateCount(parameterId);
+        double coef1 = 1.0 - Math.pow(beta1, t);
+        double coef2 = 1.0 - Math.pow(beta2, t);
+        float newLearningRate = learningRateTracker.getNewValue(parameterId, t);
+        float learningRateBiasCorrection = (float) (newLearningRate * Math.sqrt(coef2) / coef1);
+        float weightDecay = getWeightDecay();
+
+        Preconditions.checkArgument(
+                !Float.isNaN(newLearningRate)
+                        && !Float.isNaN(weightDecay)
+                        && !Float.isInfinite(newLearningRate)
+                        && !Float.isInfinite(weightDecay),
+                "learning rate or weight decay is nan or infinite");
+        NDList inputs =
+                new NDList(
+                        weight,
+                        grad,
+                        withDefaultState(
+                                means, parameterId, weight.getDevice(), k -> weight.zerosLike()),
+                        withDefaultState(
+                                variances,
+                                parameterId,
+                                weight.getDevice(),
+                                k -> weight.zerosLike()));
+        NDList weights = new NDList(weight);
+
+        NDArrayEx ex = weight.getNDArrayInternal();
+
+        ex.adamUpdate(
+                inputs,
+                weights,
+                newLearningRate,
+                learningRateBiasCorrection,
+                weightDecay,
+                rescaleGrad,
+                clipGrad,
+                beta1,
+                beta2,
+                epsilon,
+                true,
+                true);
+    }
+
+    /**
+     * Creates a builder to build a {@code Adam}.
+     *
+     * @return a new builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /** The Builder to construct an {@link AdamW} object. */
+    public static final class Builder extends OptimizerBuilder<Builder> {
+
+        private ParameterTracker learningRateTracker = Tracker.fixed(0.001f);
+        private float beta1 = 0.9f;
+        private float beta2 = 0.999f;
+        private float epsilon = 1e-8f;
+
+        Builder() {
+            optWeightDecays(0.01f);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected Builder self() {
+            return this;
+        }
+
+        /**
+         * Sets the {@link ParameterTracker} for this optimizer.
+         *
+         * @param learningRateTracker the {@link ParameterTracker} to be set
+         * @return this {@code Builder}
+         */
+        public Builder optLearningRateTracker(ParameterTracker learningRateTracker) {
+            this.learningRateTracker = learningRateTracker;
+            return this;
+        }
+
+        /**
+         * Sets the decay rate for the first moment estimates.
+         *
+         * @param beta1 the deacay rate for the the first moment estimates
+         * @return this {@code Builder}
+         */
+        public Builder optBeta1(float beta1) {
+            this.beta1 = beta1;
+            return this;
+        }
+
+        /**
+         * Sets the decay rate for the second moment estimates.
+         *
+         * @param beta2 the decay rate for the the second moment estimates
+         * @return this {@code Builder}
+         */
+        public Builder optBeta2(float beta2) {
+            this.beta2 = beta2;
+            return this;
+        }
+
+        /**
+         * Sets \(epsilon\) - a small quantity for numerical stability.
+         *
+         * @param epsilon a small quantity for numerical stability
+         * @return this {@code Builder}
+         */
+        public Builder optEpsilon(float epsilon) {
+            this.epsilon = epsilon;
+            return this;
+        }
+
+        /**
+         * Builds a {@link AdamW} block.
+         *
+         * @return the {@link AdamW} block
+         */
+        public AdamW build() {
+            return new AdamW(this);
+        }
+    }
+}
diff --git a/api/src/main/java/ai/djl/training/optimizer/Optimizer.java b/api/src/main/java/ai/djl/training/optimizer/Optimizer.java
index ce3853da0..36400fa0e 100644
--- a/api/src/main/java/ai/djl/training/optimizer/Optimizer.java
+++ b/api/src/main/java/ai/djl/training/optimizer/Optimizer.java
@@ -77,6 +77,16 @@ public abstract class Optimizer {
         return new Adam.Builder();
     }
 
+    /**
+     * Returns a new instance of {@link ai.djl.training.optimizer.AdamW.Builder} that can build an
+     * {@link AdamW} optimizer.
+     *
+     * @return the {@link AdamW} {@link ai.djl.training.optimizer.AdamW.Builder}
+     */
+    public static AdamW.Builder adamW() {
+        return new AdamW.Builder();
+    }
+
     /**
      * Returns a new instance of {@link RmsProp.Builder} that can build an {@link RmsProp}
      * optimizer.
diff --git a/api/src/main/java/ai/djl/translate/BasicTranslator.java b/api/src/main/java/ai/djl/translate/BasicTranslator.java
new file mode 100644
index 000000000..70ad2a749
--- /dev/null
+++ b/api/src/main/java/ai/djl/translate/BasicTranslator.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package ai.djl.translate;
+
+import ai.djl.ndarray.NDList;
+
+/**
+ * A {@link Translator} made by combining a {@link PreProcessor} and a {@link PostProcessor}.
+ *
+ * @param <I> the input class
+ * @param <O> the output class
+ */
+public class BasicTranslator<I, O> implements Translator<I, O> {
+
+    private PreProcessor<I> preProcessor;
+    private PostProcessor<O> postProcessor;
+    private Batchifier batchifier;
+
+    /**
+     * Constructs a {@link BasicTranslator} with the default {@link Batchifier}.
+     *
+     * @param preProcessor the preProcessor to use for pre-processing
+     * @param postProcessor the postProcessor to use for post-processing
+     */
+    public BasicTranslator(PreProcessor<I> preProcessor, PostProcessor<O> postProcessor) {
+        this.preProcessor = preProcessor;
+        this.postProcessor = postProcessor;
+    }
+
+    /**
+     * Constructs a {@link BasicTranslator}.
+     *
+     * @param preProcessor the preProcessor to use for pre-processing
+     * @param postProcessor the postProcessor to use for post-processing
+     * @param batchifier the batchifier to use
+     */
+    public BasicTranslator(
+            PreProcessor<I> preProcessor, PostProcessor<O> postProcessor, Batchifier batchifier) {
+        this.preProcessor = preProcessor;
+        this.postProcessor = postProcessor;
+        this.batchifier = batchifier;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public O processOutput(TranslatorContext ctx, NDList list) throws Exception {
+        return postProcessor.processOutput(ctx, list);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public NDList processInput(TranslatorContext ctx, I input) throws Exception {
+        return preProcessor.processInput(ctx, input);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Batchifier getBatchifier() {
+        if (batchifier != null) {
+            return batchifier;
+        }
+        return Translator.super.getBatchifier();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void prepare(TranslatorContext ctx) throws Exception {
+        if (preProcessor instanceof Translator) {
+            ((Translator<?, ?>) preProcessor).prepare(ctx);
+        }
+        if (postProcessor instanceof Translator && postProcessor != preProcessor) {
+            ((Translator<?, ?>) postProcessor).prepare(ctx);
+        }
+    }
+}
diff --git a/api/src/main/java/ai/djl/translate/ExpansionTranslatorFactory.java b/api/src/main/java/ai/djl/translate/ExpansionTranslatorFactory.java
index 952f2901a..6a58bd55d 100644
--- a/api/src/main/java/ai/djl/translate/ExpansionTranslatorFactory.java
+++ b/api/src/main/java/ai/djl/translate/ExpansionTranslatorFactory.java
@@ -16,6 +16,8 @@ import ai.djl.Model;
 import ai.djl.util.Pair;
 
 import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
@@ -26,13 +28,29 @@ import java.util.function.Function;
  * @param <IbaseT> the input type for the base translator
  * @param <ObaseT> the output type for the base translator
  */
-@SuppressWarnings("PMD.GenericsNaming")
+@SuppressWarnings({"PMD.GenericsNaming", "InterfaceTypeParameterName"})
 public abstract class ExpansionTranslatorFactory<IbaseT, ObaseT> implements TranslatorFactory {
 
     /** {@inheritDoc} */
     @Override
     public Set<Pair<Type, Type>> getSupportedTypes() {
-        return getExpansions().keySet();
+        Set<Pair<Type, Type>> results = new HashSet<>();
+        results.addAll(getExpansions().keySet());
+
+        Set<Type> preProcessorTypes = new HashSet<>();
+        preProcessorTypes.addAll(getPreprocessorExpansions().keySet());
+        preProcessorTypes.add(getBaseInputType());
+
+        Set<Type> postProcessorTypes = new HashSet<>();
+        postProcessorTypes.addAll(getPostprocessorExpansions().keySet());
+        postProcessorTypes.add(getBaseOutputType());
+
+        for (Type i : preProcessorTypes) {
+            for (Type o : postProcessorTypes) {
+                results.add(new Pair<>(i, o));
+            }
+        }
+        return results;
     }
 
     /** {@inheritDoc} */
@@ -43,15 +61,58 @@ public abstract class ExpansionTranslatorFactory<IbaseT, ObaseT> implements Tran
         return newInstance(input, output, baseTranslator);
     }
 
+    /**
+     * Returns a new instance of the {@link Translator} class.
+     *
+     * @param <I> the input data type
+     * @param <O> the output data type
+     * @param input the input class
+     * @param output the output class
+     * @param translator the base translator to expand from
+     * @return a new instance of the {@code Translator} class
+     */
     @SuppressWarnings("unchecked")
     <I, O> Translator<I, O> newInstance(
             Class<I> input, Class<O> output, Translator<IbaseT, ObaseT> translator) {
-        Function<Translator<IbaseT, ObaseT>, Translator<?, ?>> expansion =
+
+        if (input.equals(getBaseInputType()) && output.equals(getBaseOutputType())) {
+            return (Translator<I, O>) translator;
+        }
+
+        TranslatorExpansion<IbaseT, ObaseT> expansion =
                 getExpansions().get(new Pair<>(input, output));
-        if (expansion == null) {
-            throw new IllegalArgumentException("Unsupported expansion input/output types.");
+        if (expansion != null) {
+            return (Translator<I, O>) expansion.apply(translator);
+        }
+
+        // Note that regular expansions take precedence over pre-processor+post-processor expansions
+        PreProcessor<I> preProcessor = null;
+        if (input.equals(getBaseInputType())) {
+            preProcessor = (PreProcessor<I>) translator;
+        } else {
+            Function<PreProcessor<IbaseT>, PreProcessor<?>> expander =
+                    getPreprocessorExpansions().get(input);
+            if (expander != null) {
+                preProcessor = (PreProcessor<I>) expander.apply(translator);
+            }
+        }
+
+        PostProcessor<O> postProcessor = null;
+        if (output.equals(getBaseOutputType())) {
+            postProcessor = (PostProcessor<O>) translator;
+        } else {
+            Function<PostProcessor<ObaseT>, PostProcessor<?>> expander =
+                    getPostprocessorExpansions().get(output);
+            if (expander != null) {
+                postProcessor = (PostProcessor<O>) expander.apply(translator);
+            }
         }
-        return (Translator<I, O>) expansion.apply(translator);
+
+        if (preProcessor != null && postProcessor != null) {
+            return new BasicTranslator<>(preProcessor, postProcessor, translator.getBatchifier());
+        }
+
+        throw new IllegalArgumentException("Unsupported expansion input/output types.");
     }
 
     /**
@@ -74,14 +135,50 @@ public abstract class ExpansionTranslatorFactory<IbaseT, ObaseT> implements Tran
     protected abstract Translator<IbaseT, ObaseT> buildBaseTranslator(
             Model model, Map<String, ?> arguments);
 
+    /**
+     * Returns the input type for the base translator.
+     *
+     * @return the input type for the base translator
+     */
+    public abstract Class<IbaseT> getBaseInputType();
+
+    /**
+     * Returns the output type for the base translator.
+     *
+     * @return the output type for the base translator
+     */
+    public abstract Class<ObaseT> getBaseOutputType();
+
+    /**
+     * Returns the possible expansions of this factory.
+     *
+     * @return the possible expansions of this factory
+     */
+    protected Map<Pair<Type, Type>, TranslatorExpansion<IbaseT, ObaseT>> getExpansions() {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Returns the possible expansions of this factory.
+     *
+     * @return the possible expansions of this factory
+     */
+    protected Map<Type, Function<PreProcessor<IbaseT>, PreProcessor<?>>>
+            getPreprocessorExpansions() {
+        return Collections.singletonMap(getBaseInputType(), p -> p);
+    }
+
     /**
      * Returns the possible expansions of this factory.
      *
      * @return the possible expansions of this factory
      */
-    protected abstract Map<Pair<Type, Type>, Function<Translator<IbaseT, ObaseT>, Translator<?, ?>>>
-            getExpansions();
+    protected Map<Type, Function<PostProcessor<ObaseT>, PostProcessor<?>>>
+            getPostprocessorExpansions() {
+        return Collections.singletonMap(getBaseOutputType(), p -> p);
+    }
 
+    /** Represents {@link TranslatorOptions} by applying expansions to a base {@link Translator}. */
     final class ExpandedTranslatorOptions implements TranslatorOptions {
 
         private Translator<IbaseT, ObaseT> translator;
@@ -102,4 +199,15 @@ public abstract class ExpansionTranslatorFactory<IbaseT, ObaseT> implements Tran
             return newInstance(input, output, translator);
         }
     }
+
+    /**
+     * A function from a base translator to an expanded translator.
+     *
+     * @param <IbaseT> the base translator input type
+     * @param <ObaseT> the base translator output type
+     */
+    @FunctionalInterface
+    @SuppressWarnings({"PMD.GenericsNaming", "InterfaceTypeParameterName"})
+    public interface TranslatorExpansion<IbaseT, ObaseT>
+            extends Function<Translator<IbaseT, ObaseT>, Translator<?, ?>> {}
 }
diff --git a/bom/build.gradle b/bom/build.gradle
index 173aa612d..470aff1dd 100644
--- a/bom/build.gradle
+++ b/bom/build.gradle
@@ -19,6 +19,9 @@ dependencies {
         api "ai.djl:basicdataset:${version}"
         api "ai.djl:model-zoo:${version}"
         api "ai.djl:serving:${version}"
+        api "ai.djl.android:core:${version}"
+        api "ai.djl.android:onnxruntime:${version}"
+        api "ai.djl.android:pytorch-native:${version}"
         api "ai.djl.aws:aws-ai:${version}"
         api "ai.djl.audio:audio:${version}"
         api "ai.djl.dlr:dlr-engine:${version}"
diff --git a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArrayEx.java b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArrayEx.java
index 9dfd83110..b3d85dff5 100644
--- a/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArrayEx.java
+++ b/engines/mxnet/mxnet-engine/src/main/java/ai/djl/mxnet/engine/MxNDArrayEx.java
@@ -424,25 +424,36 @@ class MxNDArrayEx implements NDArrayEx {
             NDList inputs,
             NDList weights,
             float learningRate,
+            float learningRateBiasCorrection,
             float weightDecay,
             float rescaleGrad,
             float clipGrad,
             float beta1,
             float beta2,
             float epsilon,
-            boolean lazyUpdate) {
+            boolean lazyUpdate,
+            boolean adamw) {
         MxOpParams params = new MxOpParams();
-        params.addParam("lr", learningRate);
-        params.addParam("wd", weightDecay);
-        params.addParam("rescale_grad", rescaleGrad);
+        params.addParam("lr", learningRateBiasCorrection);
         params.addParam("clip_gradient", clipGrad);
 
         params.addParam("beta1", beta1);
         params.addParam("beta2", beta2);
         params.addParam("epsilon", epsilon);
-        params.addParam("lazy_update", lazyUpdate);
 
-        getManager().invoke("adam_update", inputs, weights, params);
+        if (!adamw) {
+            params.addParam("wd", weightDecay);
+            params.addParam("rescale_grad", rescaleGrad);
+            params.addParam("lazy_update", lazyUpdate);
+            getManager().invoke("adam_update", inputs, weights, params);
+        } else {
+            // https://github.com/apache/mxnet/blob/7d602e3b2382eb501fdeb94c4d97e652a723af11/src/operator/contrib/adamw.cc#L80-L121
+            // https://github.com/apache/mxnet/blob/7d602e3b2382eb501fdeb94c4d97e652a723af11/src/operator/contrib/adamw-inl.h#L172-L207
+            inputs.add(inputs.getManager().create(rescaleGrad));
+            params.addParam("eta", 1.0f);
+            params.addParam("wd", weightDecay * learningRate);
+            getManager().invoke("_adamw_update", inputs, weights, params);
+        }
     }
 
     /** {@inheritDoc} */
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/imageclassification/PpWordRotateTranslatorFactory.java b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/imageclassification/PpWordRotateTranslatorFactory.java
index ca4b83a3b..4a36afdce 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/imageclassification/PpWordRotateTranslatorFactory.java
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/imageclassification/PpWordRotateTranslatorFactory.java
@@ -14,20 +14,11 @@ package ai.djl.paddlepaddle.zoo.cv.imageclassification;
 
 import ai.djl.Model;
 import ai.djl.modality.Classifications;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.translator.ImageClassificationTranslatorFactory;
-import ai.djl.modality.cv.translator.ImageServingTranslator;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link PpWordRotateTranslatorFactory} instance. */
@@ -35,20 +26,8 @@ public class PpWordRotateTranslatorFactory extends ImageClassificationTranslator
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        if (input == Image.class && output == Classifications.class) {
-            return (Translator<I, O>) new PpWordRotateTranslator();
-        } else if (input == Path.class && output == Classifications.class) {
-            return (Translator<I, O>) new FileTranslator<>(new PpWordRotateTranslator());
-        } else if (input == URL.class && output == Classifications.class) {
-            return (Translator<I, O>) new UrlTranslator<>(new PpWordRotateTranslator());
-        } else if (input == InputStream.class && output == Classifications.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(new PpWordRotateTranslator());
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(new PpWordRotateTranslator());
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, Classifications> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return new PpWordRotateTranslator();
     }
 }
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpFaceDetectionTranslatorFactory.java b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpFaceDetectionTranslatorFactory.java
index c55987644..99203e9c1 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpFaceDetectionTranslatorFactory.java
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpFaceDetectionTranslatorFactory.java
@@ -13,21 +13,12 @@
 package ai.djl.paddlepaddle.zoo.cv.objectdetection;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.ImageServingTranslator;
 import ai.djl.modality.cv.translator.ObjectDetectionTranslatorFactory;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link PpFaceDetectionTranslator} instance. */
@@ -35,21 +26,8 @@ public class PpFaceDetectionTranslatorFactory extends ObjectDetectionTranslatorF
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        PpFaceDetectionTranslator translator = new PpFaceDetectionTranslator(arguments);
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return new PpFaceDetectionTranslator(arguments);
     }
 }
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslatorFactory.java b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslatorFactory.java
index 2abbbca0e..b16b9f36b 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslatorFactory.java
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/objectdetection/PpWordDetectionTranslatorFactory.java
@@ -13,21 +13,12 @@
 package ai.djl.paddlepaddle.zoo.cv.objectdetection;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.ImageServingTranslator;
 import ai.djl.modality.cv.translator.ObjectDetectionTranslatorFactory;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link PpWordDetectionTranslator} instance. */
@@ -35,21 +26,8 @@ public class PpWordDetectionTranslatorFactory extends ObjectDetectionTranslatorF
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        PpWordDetectionTranslator translator = new PpWordDetectionTranslator(arguments);
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return new PpWordDetectionTranslator(arguments);
     }
 }
diff --git a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/wordrecognition/PpWordRecognitionTranslatorFactory.java b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/wordrecognition/PpWordRecognitionTranslatorFactory.java
index 5223e1385..f8f341549 100644
--- a/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/wordrecognition/PpWordRecognitionTranslatorFactory.java
+++ b/engines/paddlepaddle/paddlepaddle-model-zoo/src/main/java/ai/djl/paddlepaddle/zoo/cv/wordrecognition/PpWordRecognitionTranslatorFactory.java
@@ -13,60 +13,25 @@
 package ai.djl.paddlepaddle.zoo.cv.wordrecognition;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.translator.ImageServingTranslator;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.modality.cv.translator.BaseImageTranslatorFactory;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
-import ai.djl.util.Pair;
 
-import java.io.InputStream;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.nio.file.Path;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 /** An {@link TranslatorFactory} that creates a {@link PpWordRecognitionTranslator} instance. */
-public class PpWordRecognitionTranslatorFactory implements TranslatorFactory {
-
-    private static final Set<Pair<Type, Type>> SUPPORTED_TYPES = new HashSet<>();
-
-    static {
-        SUPPORTED_TYPES.add(new Pair<>(Image.class, String.class));
-        SUPPORTED_TYPES.add(new Pair<>(Path.class, String.class));
-        SUPPORTED_TYPES.add(new Pair<>(URL.class, String.class));
-        SUPPORTED_TYPES.add(new Pair<>(InputStream.class, String.class));
-    }
+public class PpWordRecognitionTranslatorFactory extends BaseImageTranslatorFactory<String> {
 
     /** {@inheritDoc} */
     @Override
-    public Set<Pair<Type, Type>> getSupportedTypes() {
-        return SUPPORTED_TYPES;
+    protected Translator<Image, String> buildBaseTranslator(Model model, Map<String, ?> arguments) {
+        return new PpWordRecognitionTranslator();
     }
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        PpWordRecognitionTranslator translator = new PpWordRecognitionTranslator();
-        if (input == Image.class && output == String.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == String.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == String.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == String.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    public Class<String> getBaseOutputType() {
+        return String.class;
     }
 }
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArrayEx.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArrayEx.java
index 28375ef38..f5ae2cdbd 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArrayEx.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/engine/PtNDArrayEx.java
@@ -266,13 +266,15 @@ public class PtNDArrayEx implements NDArrayEx {
             NDList inputs,
             NDList weights,
             float learningRate,
+            float learningRateBiasCorrection,
             float weightDecay,
             float rescaleGrad,
             float clipGrad,
             float beta1,
             float beta2,
             float epsilon,
-            boolean lazyUpdate) {
+            boolean lazyUpdate,
+            boolean adamw) {
         // TODO: Lazy update not used
         PtNDManager manager = array.getManager();
         JniUtils.adamUpdate(
@@ -281,12 +283,14 @@ public class PtNDArrayEx implements NDArrayEx {
                 manager.from(inputs.get(2)),
                 manager.from(inputs.get(3)),
                 learningRate,
+                learningRateBiasCorrection,
                 weightDecay,
                 rescaleGrad,
                 clipGrad,
                 beta1,
                 beta2,
-                epsilon);
+                epsilon,
+                adamw);
         // call zero-grad
         JniUtils.zeroGrad(manager.from(weights.singletonOrThrow()));
     }
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
index 862833d44..47fb0b724 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/JniUtils.java
@@ -1647,24 +1647,28 @@ public final class JniUtils {
             PtNDArray mean,
             PtNDArray variance,
             float lr,
+            float learningRateBiasCorrection,
             float wd,
             float rescaleGrad,
             float clipGrad,
             float beta1,
             float beta2,
-            float eps) {
+            float eps,
+            boolean adamw) {
         PyTorchLibrary.LIB.adamUpdate(
                 weight.getHandle(),
                 grad.getHandle(),
                 mean.getHandle(),
                 variance.getHandle(),
                 lr,
+                learningRateBiasCorrection,
                 wd,
                 rescaleGrad,
                 clipGrad,
                 beta1,
                 beta2,
-                eps);
+                eps,
+                adamw);
     }
 
     public static void sgdUpdate(
diff --git a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
index 381712390..53b043b4e 100644
--- a/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
+++ b/engines/pytorch/pytorch-engine/src/main/java/ai/djl/pytorch/jni/PyTorchLibrary.java
@@ -599,12 +599,14 @@ final class PyTorchLibrary {
             long mean,
             long variance,
             float lr,
+            float learningRateBiasCorrection,
             float wd,
             float rescaleGrad,
             float clipGrad,
             float beta1,
             float beta2,
-            float eps);
+            float eps,
+            boolean adamw);
 
     native void sgdUpdate(
             long weight,
diff --git a/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/cv/objectdetection/PtSsdTranslatorFactory.java b/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/cv/objectdetection/PtSsdTranslatorFactory.java
index c0ac429fa..b552ea995 100644
--- a/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/cv/objectdetection/PtSsdTranslatorFactory.java
+++ b/engines/pytorch/pytorch-model-zoo/src/main/java/ai/djl/pytorch/zoo/cv/objectdetection/PtSsdTranslatorFactory.java
@@ -13,21 +13,12 @@
 package ai.djl.pytorch.zoo.cv.objectdetection;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.ImageServingTranslator;
 import ai.djl.modality.cv.translator.ObjectDetectionTranslatorFactory;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link PtSsdTranslator} instance. */
@@ -35,21 +26,8 @@ public class PtSsdTranslatorFactory extends ObjectDetectionTranslatorFactory {
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        PtSsdTranslator translator = PtSsdTranslator.builder(arguments).build();
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return PtSsdTranslator.builder(arguments).build();
     }
 }
diff --git a/engines/pytorch/pytorch-native/build.cmd b/engines/pytorch/pytorch-native/build.cmd
index d37d1be2b..a4e6ab939 100644
--- a/engines/pytorch/pytorch-native/build.cmd
+++ b/engines/pytorch/pytorch-native/build.cmd
@@ -2,7 +2,7 @@
 @rem https://chocolatey.org/docs/installation#install-with-cmdexe
 @rem to install cmake java etc..
 @rem choco install cmake.install --installargs '"ADD_CMAKE_TO_PATH=User"' -y
-@rem choco install jdk8 -y
+@rem choco install zulu11 -y
 
 set FILEPATH="libtorch"
 set VERSION=%1
@@ -21,6 +21,9 @@ if exist %FILEPATH% (
 
 copy /y src\main\patch\cuda.cmake libtorch\share\cmake\Caffe2\public\
 
+@rem workaround VS 17.4.0 issue: https://stackoverflow.com/questions/74366357/updating-to-visual-studio-17-4-0-yields-linker-errors-related-to-tls
+copy /y src\main\patch\%VERSION%\Parallel.h libtorch\include\ATen\
+
 if exist build rd /q /s build
 md build\classes
 cd build
diff --git a/engines/pytorch/pytorch-native/build.gradle b/engines/pytorch/pytorch-native/build.gradle
index 62bd3e025..a54ffd544 100644
--- a/engines/pytorch/pytorch-native/build.gradle
+++ b/engines/pytorch/pytorch-native/build.gradle
@@ -219,6 +219,10 @@ java {
     withSourcesJar()
 }
 
+formatCpp {
+    exclusions = ["main/patch/**"]
+}
+
 project.tasks.withType(GenerateModuleMetadata) {
     enabled = false
 }
diff --git a/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_optim.cc b/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_optim.cc
index 0b3a49358..70759b7e1 100644
--- a/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_optim.cc
+++ b/engines/pytorch/pytorch-native/src/main/native/ai_djl_pytorch_jni_PyTorchLibrary_optim.cc
@@ -18,8 +18,9 @@
 // The file is the implementation for PyTorch training operations
 
 JNIEXPORT void JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_adamUpdate(JNIEnv* env, jobject jthis, jlong jweight,
-    jlong jgrad, jlong jmean, jlong jvariance, jfloat learning_rate, jfloat weight_decay, jfloat rescale_grad,
-    jfloat clip_grad, jfloat beta1, jfloat beta2, jfloat eps) {
+    jlong jgrad, jlong jmean, jlong jvariance, jfloat learning_rate, jfloat learning_rate_bias_correction,
+    jfloat weight_decay, jfloat rescale_grad, jfloat clip_grad, jfloat beta1, jfloat beta2, jfloat eps,
+    jboolean adamw) {
   API_BEGIN()
   torch::autograd::AutoGradMode no_autograd_guard{false};
   const auto* weight_ptr = reinterpret_cast<torch::Tensor*>(jweight);
@@ -34,10 +35,15 @@ JNIEXPORT void JNICALL Java_ai_djl_pytorch_jni_PyTorchLibrary_adamUpdate(JNIEnv*
     // Add clip grad option
     grad.clamp_max_(clip_grad);
   }
-  grad.add_(*weight_ptr, weight_decay);
+  if (!adamw) {
+    // rescaled_grad is obtained here
+    grad.add_(*weight_ptr, weight_decay);
+  } else {
+    weight_ptr->sub_(weight_ptr->mul(learning_rate).mul(weight_decay));
+  }
   mean_ptr->mul_(beta1).add_(grad, 1 - beta1);
   variance_ptr->mul_(beta2).addcmul_(grad, grad, 1 - beta2);
-  weight_ptr->sub_(mean_ptr->mul(learning_rate).div(variance_ptr->sqrt().add(eps)));
+  weight_ptr->sub_(mean_ptr->mul(learning_rate_bias_correction).div(variance_ptr->sqrt().add(eps)));
   API_END()
 }
 
diff --git a/engines/pytorch/pytorch-native/src/main/patch/1.11.0/Parallel.h b/engines/pytorch/pytorch-native/src/main/patch/1.11.0/Parallel.h
new file mode 100644
index 000000000..06b5d883d
--- /dev/null
+++ b/engines/pytorch/pytorch-native/src/main/patch/1.11.0/Parallel.h
@@ -0,0 +1,154 @@
+#pragma once
+#include <ATen/Config.h>
+#include <c10/macros/Macros.h>
+#include <functional>
+
+namespace at {
+
+inline int64_t divup(int64_t x, int64_t y) {
+  return (x + y - 1) / y;
+}
+
+// Called during new thread initialization
+TORCH_API void init_num_threads();
+
+// Sets the number of threads to be used in parallel region
+TORCH_API void set_num_threads(int);
+
+// Returns the maximum number of threads that may be used in a parallel region
+TORCH_API int get_num_threads();
+
+// Returns the current thread number (starting from 0)
+// in the current parallel region, or 0 in the sequential region
+TORCH_API int get_thread_num();
+
+// Checks whether the code runs in parallel region
+TORCH_API bool in_parallel_region();
+
+namespace internal {
+
+// Initialise num_threads lazily at first parallel call
+TORCH_API void lazy_init_num_threads();
+
+TORCH_API void set_thread_num(int);
+
+class TORCH_API ThreadIdGuard {
+public:
+  ThreadIdGuard(int new_id):
+    old_id_(at::get_thread_num()) {
+    set_thread_num(new_id);
+  }
+
+  ~ThreadIdGuard() {
+    set_thread_num(old_id_);
+  }
+
+private:
+  int old_id_;
+};
+
+}  // namespace internal
+
+/*
+parallel_for
+
+begin: index at which to start applying user function
+
+end: index at which to stop applying user function
+
+grain_size: number of elements per chunk. impacts the degree of parallelization
+
+f: user function applied in parallel to the chunks, signature:
+  void f(int64_t begin, int64_t end)
+
+Warning: parallel_for does NOT copy thread local
+states from the current thread to the worker threads.
+This means for example that Tensor operations CANNOT be used in the
+body of your function, only data pointers.
+*/
+template <class F>
+inline void parallel_for(
+    const int64_t begin,
+    const int64_t end,
+    const int64_t grain_size,
+    const F& f);
+
+/*
+parallel_reduce
+
+begin: index at which to start applying reduction
+
+end: index at which to stop applying reduction
+
+grain_size: number of elements per chunk. impacts number of elements in
+intermediate results tensor and degree of parallelization.
+
+ident: identity for binary combination function sf. sf(ident, x) needs to return
+x.
+
+f: function for reduction over a chunk. f needs to be of signature scalar_t
+f(int64_t partial_begin, int64_t partial_end, scalar_t identifiy)
+
+sf: function to combine two partial results. sf needs to be of signature
+scalar_t sf(scalar_t x, scalar_t y)
+
+For example, you might have a tensor of 10000 entires and want to sum together
+all the elements. Parallel_reduce with a grain_size of 2500 will then allocate
+an intermediate result tensor with 4 elements. Then it will execute the function
+"f" you provide and pass the beginning and end index of these chunks, so
+0-2499, 2500-4999, etc. and the combination identity. It will then write out
+the result from each of these chunks into the intermediate result tensor. After
+that it'll reduce the partial results from each chunk into a single number using
+the combination function sf and the identity ident. For a total summation this
+would be "+" and 0 respectively. This is similar to tbb's approach [1], where
+you need to provide a function to accumulate a subrange, a function to combine
+two partial results and an identity.
+
+Warning: parallel_reduce does NOT copy thread local
+states from the current thread to the worker threads.
+This means for example that Tensor operations CANNOT be used in the
+body of your function, only data pointers.
+
+[1] https://software.intel.com/en-us/node/506154
+*/
+template <class scalar_t, class F, class SF>
+inline scalar_t parallel_reduce(
+    const int64_t begin,
+    const int64_t end,
+    const int64_t grain_size,
+    const scalar_t ident,
+    const F& f,
+    const SF& sf);
+
+// Returns a detailed string describing parallelization settings
+TORCH_API std::string get_parallel_info();
+
+// Sets number of threads used for inter-op parallelism
+TORCH_API void set_num_interop_threads(int);
+
+// Returns the number of threads used for inter-op parallelism
+TORCH_API int get_num_interop_threads();
+
+// Launches inter-op parallel task
+TORCH_API void launch(std::function<void()> func);
+namespace internal {
+void launch_no_thread_state(std::function<void()> fn);
+} // namespace internal
+
+// Launches intra-op parallel task
+TORCH_API void intraop_launch(std::function<void()> func);
+
+// Returns number of intra-op threads used by default
+TORCH_API int intraop_default_num_threads();
+
+} // namespace at
+
+#if AT_PARALLEL_OPENMP
+#include <ATen/ParallelOpenMP.h> // IWYU pragma: keep
+#elif AT_PARALLEL_NATIVE
+#include <ATen/ParallelNative.h> // IWYU pragma: keep
+#elif AT_PARALLEL_NATIVE_TBB
+#include <ATen/ParallelNativeTBB.h> // IWYU pragma: keep
+#endif
+
+#include <ATen/Parallel-inl.h> // IWYU pragma: keep
diff --git a/engines/pytorch/pytorch-native/src/main/patch/1.12.1/Parallel.h b/engines/pytorch/pytorch-native/src/main/patch/1.12.1/Parallel.h
new file mode 100644
index 000000000..06b5d883d
--- /dev/null
+++ b/engines/pytorch/pytorch-native/src/main/patch/1.12.1/Parallel.h
@@ -0,0 +1,154 @@
+#pragma once
+#include <ATen/Config.h>
+#include <c10/macros/Macros.h>
+#include <functional>
+
+namespace at {
+
+inline int64_t divup(int64_t x, int64_t y) {
+  return (x + y - 1) / y;
+}
+
+// Called during new thread initialization
+TORCH_API void init_num_threads();
+
+// Sets the number of threads to be used in parallel region
+TORCH_API void set_num_threads(int);
+
+// Returns the maximum number of threads that may be used in a parallel region
+TORCH_API int get_num_threads();
+
+// Returns the current thread number (starting from 0)
+// in the current parallel region, or 0 in the sequential region
+TORCH_API int get_thread_num();
+
+// Checks whether the code runs in parallel region
+TORCH_API bool in_parallel_region();
+
+namespace internal {
+
+// Initialise num_threads lazily at first parallel call
+TORCH_API void lazy_init_num_threads();
+
+TORCH_API void set_thread_num(int);
+
+class TORCH_API ThreadIdGuard {
+public:
+  ThreadIdGuard(int new_id):
+    old_id_(at::get_thread_num()) {
+    set_thread_num(new_id);
+  }
+
+  ~ThreadIdGuard() {
+    set_thread_num(old_id_);
+  }
+
+private:
+  int old_id_;
+};
+
+}  // namespace internal
+
+/*
+parallel_for
+
+begin: index at which to start applying user function
+
+end: index at which to stop applying user function
+
+grain_size: number of elements per chunk. impacts the degree of parallelization
+
+f: user function applied in parallel to the chunks, signature:
+  void f(int64_t begin, int64_t end)
+
+Warning: parallel_for does NOT copy thread local
+states from the current thread to the worker threads.
+This means for example that Tensor operations CANNOT be used in the
+body of your function, only data pointers.
+*/
+template <class F>
+inline void parallel_for(
+    const int64_t begin,
+    const int64_t end,
+    const int64_t grain_size,
+    const F& f);
+
+/*
+parallel_reduce
+
+begin: index at which to start applying reduction
+
+end: index at which to stop applying reduction
+
+grain_size: number of elements per chunk. impacts number of elements in
+intermediate results tensor and degree of parallelization.
+
+ident: identity for binary combination function sf. sf(ident, x) needs to return
+x.
+
+f: function for reduction over a chunk. f needs to be of signature scalar_t
+f(int64_t partial_begin, int64_t partial_end, scalar_t identifiy)
+
+sf: function to combine two partial results. sf needs to be of signature
+scalar_t sf(scalar_t x, scalar_t y)
+
+For example, you might have a tensor of 10000 entires and want to sum together
+all the elements. Parallel_reduce with a grain_size of 2500 will then allocate
+an intermediate result tensor with 4 elements. Then it will execute the function
+"f" you provide and pass the beginning and end index of these chunks, so
+0-2499, 2500-4999, etc. and the combination identity. It will then write out
+the result from each of these chunks into the intermediate result tensor. After
+that it'll reduce the partial results from each chunk into a single number using
+the combination function sf and the identity ident. For a total summation this
+would be "+" and 0 respectively. This is similar to tbb's approach [1], where
+you need to provide a function to accumulate a subrange, a function to combine
+two partial results and an identity.
+
+Warning: parallel_reduce does NOT copy thread local
+states from the current thread to the worker threads.
+This means for example that Tensor operations CANNOT be used in the
+body of your function, only data pointers.
+
+[1] https://software.intel.com/en-us/node/506154
+*/
+template <class scalar_t, class F, class SF>
+inline scalar_t parallel_reduce(
+    const int64_t begin,
+    const int64_t end,
+    const int64_t grain_size,
+    const scalar_t ident,
+    const F& f,
+    const SF& sf);
+
+// Returns a detailed string describing parallelization settings
+TORCH_API std::string get_parallel_info();
+
+// Sets number of threads used for inter-op parallelism
+TORCH_API void set_num_interop_threads(int);
+
+// Returns the number of threads used for inter-op parallelism
+TORCH_API int get_num_interop_threads();
+
+// Launches inter-op parallel task
+TORCH_API void launch(std::function<void()> func);
+namespace internal {
+void launch_no_thread_state(std::function<void()> fn);
+} // namespace internal
+
+// Launches intra-op parallel task
+TORCH_API void intraop_launch(std::function<void()> func);
+
+// Returns number of intra-op threads used by default
+TORCH_API int intraop_default_num_threads();
+
+} // namespace at
+
+#if AT_PARALLEL_OPENMP
+#include <ATen/ParallelOpenMP.h> // IWYU pragma: keep
+#elif AT_PARALLEL_NATIVE
+#include <ATen/ParallelNative.h> // IWYU pragma: keep
+#elif AT_PARALLEL_NATIVE_TBB
+#include <ATen/ParallelNativeTBB.h> // IWYU pragma: keep
+#endif
+
+#include <ATen/Parallel-inl.h> // IWYU pragma: keep
diff --git a/engines/pytorch/pytorch-native/src/main/patch/1.13.0/Parallel.h b/engines/pytorch/pytorch-native/src/main/patch/1.13.0/Parallel.h
new file mode 100644
index 000000000..90ba2f6ca
--- /dev/null
+++ b/engines/pytorch/pytorch-native/src/main/patch/1.13.0/Parallel.h
@@ -0,0 +1,154 @@
+#pragma once
+#include <ATen/Config.h>
+#include <c10/macros/Macros.h>
+#include <functional>
+#include <string>
+
+namespace at {
+
+inline int64_t divup(int64_t x, int64_t y) {
+  return (x + y - 1) / y;
+}
+
+// Called during new thread initialization
+TORCH_API void init_num_threads();
+
+// Sets the number of threads to be used in parallel region
+TORCH_API void set_num_threads(int);
+
+// Returns the maximum number of threads that may be used in a parallel region
+TORCH_API int get_num_threads();
+
+// Returns the current thread number (starting from 0)
+// in the current parallel region, or 0 in the sequential region
+TORCH_API int get_thread_num();
+
+// Checks whether the code runs in parallel region
+TORCH_API bool in_parallel_region();
+
+namespace internal {
+
+// Initialise num_threads lazily at first parallel call
+TORCH_API void lazy_init_num_threads();
+
+TORCH_API void set_thread_num(int);
+
+class TORCH_API ThreadIdGuard {
+ public:
+  ThreadIdGuard(int new_id) : old_id_(at::get_thread_num()) {
+    set_thread_num(new_id);
+  }
+
+  ~ThreadIdGuard() {
+    set_thread_num(old_id_);
+  }
+
+ private:
+  int old_id_;
+};
+
+} // namespace internal
+
+/*
+parallel_for
+
+begin: index at which to start applying user function
+
+end: index at which to stop applying user function
+
+grain_size: number of elements per chunk. impacts the degree of parallelization
+
+f: user function applied in parallel to the chunks, signature:
+  void f(int64_t begin, int64_t end)
+
+Warning: parallel_for does NOT copy thread local
+states from the current thread to the worker threads.
+This means for example that Tensor operations CANNOT be used in the
+body of your function, only data pointers.
+*/
+template <class F>
+inline void parallel_for(
+    const int64_t begin,
+    const int64_t end,
+    const int64_t grain_size,
+    const F& f);
+
+/*
+parallel_reduce
+
+begin: index at which to start applying reduction
+
+end: index at which to stop applying reduction
+
+grain_size: number of elements per chunk. impacts number of elements in
+intermediate results tensor and degree of parallelization.
+
+ident: identity for binary combination function sf. sf(ident, x) needs to return
+x.
+
+f: function for reduction over a chunk. f needs to be of signature scalar_t
+f(int64_t partial_begin, int64_t partial_end, scalar_t identifiy)
+
+sf: function to combine two partial results. sf needs to be of signature
+scalar_t sf(scalar_t x, scalar_t y)
+
+For example, you might have a tensor of 10000 entires and want to sum together
+all the elements. Parallel_reduce with a grain_size of 2500 will then allocate
+an intermediate result tensor with 4 elements. Then it will execute the function
+"f" you provide and pass the beginning and end index of these chunks, so
+0-2499, 2500-4999, etc. and the combination identity. It will then write out
+the result from each of these chunks into the intermediate result tensor. After
+that it'll reduce the partial results from each chunk into a single number using
+the combination function sf and the identity ident. For a total summation this
+would be "+" and 0 respectively. This is similar to tbb's approach [1], where
+you need to provide a function to accumulate a subrange, a function to combine
+two partial results and an identity.
+
+Warning: parallel_reduce does NOT copy thread local
+states from the current thread to the worker threads.
+This means for example that Tensor operations CANNOT be used in the
+body of your function, only data pointers.
+
+[1] https://software.intel.com/en-us/node/506154
+*/
+template <class scalar_t, class F, class SF>
+inline scalar_t parallel_reduce(
+    const int64_t begin,
+    const int64_t end,
+    const int64_t grain_size,
+    const scalar_t ident,
+    const F& f,
+    const SF& sf);
+
+// Returns a detailed string describing parallelization settings
+TORCH_API std::string get_parallel_info();
+
+// Sets number of threads used for inter-op parallelism
+TORCH_API void set_num_interop_threads(int);
+
+// Returns the number of threads used for inter-op parallelism
+TORCH_API int get_num_interop_threads();
+
+// Launches inter-op parallel task
+TORCH_API void launch(std::function<void()> func);
+namespace internal {
+void launch_no_thread_state(std::function<void()> fn);
+} // namespace internal
+
+// Launches intra-op parallel task
+TORCH_API void intraop_launch(std::function<void()> func);
+
+// Returns number of intra-op threads used by default
+TORCH_API int intraop_default_num_threads();
+
+} // namespace at
+
+#if AT_PARALLEL_OPENMP
+#include <ATen/ParallelOpenMP.h> // IWYU pragma: keep
+#elif AT_PARALLEL_NATIVE
+#include <ATen/ParallelNative.h> // IWYU pragma: keep
+#elif AT_PARALLEL_NATIVE_TBB
+#include <ATen/ParallelNativeTBB.h> // IWYU pragma: keep
+#endif
+
+#include <ATen/Parallel-inl.h> // IWYU pragma: keep
diff --git a/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArrayEx.java b/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArrayEx.java
index 8c2091137..b7c9ec3e0 100644
--- a/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArrayEx.java
+++ b/engines/tensorflow/tensorflow-engine/src/main/java/ai/djl/tensorflow/engine/TfNDArrayEx.java
@@ -286,13 +286,15 @@ public class TfNDArrayEx implements NDArrayEx {
             NDList inputs,
             NDList weights,
             float learningRate,
+            float learningRateBiasCorrection,
             float weightDecay,
             float rescaleGrad,
             float clipGrad,
             float beta1,
             float beta2,
             float epsilon,
-            boolean lazyUpdate) {
+            boolean lazyUpdate,
+            boolean adamw) {
         throw new UnsupportedOperationException("Not implemented");
     }
 
diff --git a/engines/tensorflow/tensorflow-model-zoo/src/main/java/ai/djl/tensorflow/zoo/cv/objectdetction/TfSsdTranslatorFactory.java b/engines/tensorflow/tensorflow-model-zoo/src/main/java/ai/djl/tensorflow/zoo/cv/objectdetction/TfSsdTranslatorFactory.java
index 2b0b7e52e..a97a0b25d 100644
--- a/engines/tensorflow/tensorflow-model-zoo/src/main/java/ai/djl/tensorflow/zoo/cv/objectdetction/TfSsdTranslatorFactory.java
+++ b/engines/tensorflow/tensorflow-model-zoo/src/main/java/ai/djl/tensorflow/zoo/cv/objectdetction/TfSsdTranslatorFactory.java
@@ -13,21 +13,12 @@
 package ai.djl.tensorflow.zoo.cv.objectdetction;
 
 import ai.djl.Model;
-import ai.djl.modality.Input;
-import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.ImageServingTranslator;
 import ai.djl.modality.cv.translator.ObjectDetectionTranslatorFactory;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
 import ai.djl.translate.Translator;
 import ai.djl.translate.TranslatorFactory;
 
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
 import java.util.Map;
 
 /** An {@link TranslatorFactory} that creates a {@link TfSsdTranslator} instance. */
@@ -35,21 +26,8 @@ public class TfSsdTranslatorFactory extends ObjectDetectionTranslatorFactory {
 
     /** {@inheritDoc} */
     @Override
-    @SuppressWarnings("unchecked")
-    public <I, O> Translator<I, O> newInstance(
-            Class<I> input, Class<O> output, Model model, Map<String, ?> arguments) {
-        TfSsdTranslator translator = TfSsdTranslator.builder(arguments).build();
-        if (input == Image.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) translator;
-        } else if (input == Path.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new FileTranslator<>(translator);
-        } else if (input == URL.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new UrlTranslator<>(translator);
-        } else if (input == InputStream.class && output == DetectedObjects.class) {
-            return (Translator<I, O>) new InputStreamTranslator<>(translator);
-        } else if (input == Input.class && output == Output.class) {
-            return (Translator<I, O>) new ImageServingTranslator(translator);
-        }
-        throw new IllegalArgumentException("Unsupported input/output types.");
+    protected Translator<Image, DetectedObjects> buildBaseTranslator(
+            Model model, Map<String, ?> arguments) {
+        return TfSsdTranslator.builder(arguments).build();
     }
 }
diff --git a/tools/gradle/cpp-formatter.gradle b/tools/gradle/cpp-formatter.gradle
index 6d5cc3fb1..d09e0ee60 100644
--- a/tools/gradle/cpp-formatter.gradle
+++ b/tools/gradle/cpp-formatter.gradle
@@ -26,6 +26,8 @@ class CppFormatterPlugin implements Plugin<Project> {
 
     void apply(Project project) {
 
+        project.extensions.create("formatCpp", FormatterConfig)
+
         project.task('formatCpp') {
             doLast {
                 if (!System.properties['os.name'].toLowerCase().contains('mac')) {
@@ -38,6 +40,12 @@ class CppFormatterPlugin implements Plugin<Project> {
                         .include("**/*.cc")
                         .include("**/*.cpp")
                         .include("**/*.h")
+                if (project.extensions.formatCpp.exclusions != null) {
+                    for (String exclusion in project.extensions.formatCpp.exclusions) {
+                        files = files.exclude(exclusion)
+                    }
+                }
+
                 for (File f : files) {
                     if (!f.isFile()) {
                         continue
@@ -73,3 +81,7 @@ class CppFormatterPlugin implements Plugin<Project> {
         }
     }
 }
+
+class FormatterConfig {
+    String[] exclusions
+}


Test patch:
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
index 941e2c3dc..5427cebe7 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/ImageClassificationTranslatorFactoryTest.java
@@ -17,9 +17,7 @@ import ai.djl.modality.Classifications;
 import ai.djl.modality.Input;
 import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.BasicTranslator;
 import ai.djl.translate.Translator;
 
 import org.testng.Assert;
@@ -56,15 +54,15 @@ public class ImageClassificationTranslatorFactoryTest {
 
             Translator<Path, Classifications> translator2 =
                     factory.newInstance(Path.class, Classifications.class, model, arguments);
-            Assert.assertTrue(translator2 instanceof FileTranslator);
+            Assert.assertTrue(translator2 instanceof BasicTranslator);
 
             Translator<URL, Classifications> translator3 =
                     factory.newInstance(URL.class, Classifications.class, model, arguments);
-            Assert.assertTrue(translator3 instanceof UrlTranslator);
+            Assert.assertTrue(translator3 instanceof BasicTranslator);
 
             Translator<InputStream, Classifications> translator4 =
                     factory.newInstance(InputStream.class, Classifications.class, model, arguments);
-            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+            Assert.assertTrue(translator4 instanceof BasicTranslator);
 
             Translator<Input, Output> translator5 =
                     factory.newInstance(Input.class, Output.class, model, arguments);
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
index 79df6bcc9..21eaedadc 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/InstanceSegmentationTranslatorFactoryTest.java
@@ -17,9 +17,7 @@ import ai.djl.modality.Input;
 import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.BasicTranslator;
 import ai.djl.translate.Translator;
 
 import org.testng.Assert;
@@ -56,15 +54,15 @@ public class InstanceSegmentationTranslatorFactoryTest {
 
             Translator<Path, DetectedObjects> translator2 =
                     factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator2 instanceof FileTranslator);
+            Assert.assertTrue(translator2 instanceof BasicTranslator);
 
             Translator<URL, DetectedObjects> translator3 =
                     factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator3 instanceof UrlTranslator);
+            Assert.assertTrue(translator3 instanceof BasicTranslator);
 
             Translator<InputStream, DetectedObjects> translator4 =
                     factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+            Assert.assertTrue(translator4 instanceof BasicTranslator);
 
             Translator<Input, Output> translator5 =
                     factory.newInstance(Input.class, Output.class, model, arguments);
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java
index 938a5cd0f..f26b7f2ab 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/SingleShotDetectionTranslatorFactoryTest.java
@@ -17,9 +17,7 @@ import ai.djl.modality.Input;
 import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.BasicTranslator;
 import ai.djl.translate.Translator;
 
 import org.testng.Assert;
@@ -51,15 +49,15 @@ public class SingleShotDetectionTranslatorFactoryTest {
 
             Translator<Path, DetectedObjects> translator2 =
                     factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator2 instanceof FileTranslator);
+            Assert.assertTrue(translator2 instanceof BasicTranslator);
 
             Translator<URL, DetectedObjects> translator3 =
                     factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator3 instanceof UrlTranslator);
+            Assert.assertTrue(translator3 instanceof BasicTranslator);
 
             Translator<InputStream, DetectedObjects> translator4 =
                     factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+            Assert.assertTrue(translator4 instanceof BasicTranslator);
 
             Translator<Input, Output> translator5 =
                     factory.newInstance(Input.class, Output.class, model, arguments);
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
index fae1a595f..cfbcfb829 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloTranslatorFactoryTest.java
@@ -17,9 +17,7 @@ import ai.djl.modality.Input;
 import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.BasicTranslator;
 import ai.djl.translate.Translator;
 
 import org.testng.Assert;
@@ -56,15 +54,15 @@ public class YoloTranslatorFactoryTest {
 
             Translator<Path, DetectedObjects> translator2 =
                     factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator2 instanceof FileTranslator);
+            Assert.assertTrue(translator2 instanceof BasicTranslator);
 
             Translator<URL, DetectedObjects> translator3 =
                     factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator3 instanceof UrlTranslator);
+            Assert.assertTrue(translator3 instanceof BasicTranslator);
 
             Translator<InputStream, DetectedObjects> translator4 =
                     factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+            Assert.assertTrue(translator4 instanceof BasicTranslator);
 
             Translator<Input, Output> translator5 =
                     factory.newInstance(Input.class, Output.class, model, arguments);
diff --git a/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java b/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
index a86c03fac..1ac8bf7b8 100644
--- a/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
+++ b/api/src/test/java/ai/djl/modality/cv/translator/YoloV5TranslatorFactoryTest.java
@@ -17,9 +17,7 @@ import ai.djl.modality.Input;
 import ai.djl.modality.Output;
 import ai.djl.modality.cv.Image;
 import ai.djl.modality.cv.output.DetectedObjects;
-import ai.djl.modality.cv.translator.wrapper.FileTranslator;
-import ai.djl.modality.cv.translator.wrapper.InputStreamTranslator;
-import ai.djl.modality.cv.translator.wrapper.UrlTranslator;
+import ai.djl.translate.BasicTranslator;
 import ai.djl.translate.Translator;
 
 import org.testng.Assert;
@@ -56,15 +54,15 @@ public class YoloV5TranslatorFactoryTest {
 
             Translator<Path, DetectedObjects> translator2 =
                     factory.newInstance(Path.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator2 instanceof FileTranslator);
+            Assert.assertTrue(translator2 instanceof BasicTranslator);
 
             Translator<URL, DetectedObjects> translator3 =
                     factory.newInstance(URL.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator3 instanceof UrlTranslator);
+            Assert.assertTrue(translator3 instanceof BasicTranslator);
 
             Translator<InputStream, DetectedObjects> translator4 =
                     factory.newInstance(InputStream.class, DetectedObjects.class, model, arguments);
-            Assert.assertTrue(translator4 instanceof InputStreamTranslator);
+            Assert.assertTrue(translator4 instanceof BasicTranslator);
 
             Translator<Input, Output> translator5 =
                     factory.newInstance(Input.class, Output.class, model, arguments);
diff --git a/integration/src/main/java/ai/djl/integration/tests/training/OptimizerTest.java b/integration/src/main/java/ai/djl/integration/tests/training/OptimizerTest.java
index bc1b07470..b54225dfb 100644
--- a/integration/src/main/java/ai/djl/integration/tests/training/OptimizerTest.java
+++ b/integration/src/main/java/ai/djl/integration/tests/training/OptimizerTest.java
@@ -172,6 +172,35 @@ public class OptimizerTest {
         }
     }
 
+    @Test
+    public void testAdamW() {
+        Optimizer optim = Optimizer.adamW().optLearningRateTracker(Tracker.fixed(0.1f)).build();
+
+        Device[] devices = Engine.getInstance().getDevices(1);
+        TrainingConfig config =
+                new DefaultTrainingConfig(Loss.l2Loss())
+                        .optInitializer(Initializer.ONES, Parameter.Type.WEIGHT)
+                        .optOptimizer(optim)
+                        .optDevices(devices);
+        Block block = Linear.builder().setUnits(CHANNELS).build();
+        try (Model model = Model.newInstance("model", devices[0])) {
+            model.setBlock(block);
+
+            try (Trainer trainer = model.newTrainer(config)) {
+                int batchSize = config.getDevices().length * BATCH_SIZE;
+                trainer.initialize(new Shape(batchSize, CHANNELS));
+
+                NDManager manager = trainer.getManager();
+                NDArray result = runOptimizer(manager, trainer, block, batchSize);
+                NDArray result2 = runOptimizer(manager, trainer, block, batchSize);
+                Assertions.assertAlmostEquals(
+                        result, manager.create(new float[] {0.8990001082420349f, -0.10000064f}));
+                Assertions.assertAlmostEquals(
+                        result2, manager.create(new float[] {0.7987207174301147f, -0.19939029f}));
+            }
+        }
+    }
+
     @Test
     public void testAdagrad() {
         Optimizer optim = Optimizer.adagrad().optLearningRateTracker(Tracker.fixed(0.1f)).build();