Repository: deepjavalibrary/djl
Pull request #2076 (2022-10-11 16:30:50)

Improve memory management and batchify docs


Patch:
diff --git a/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java b/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java
index 7017370dd..c208b6ae4 100644
--- a/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java
+++ b/android/core/src/main/java/ai/djl/android/core/BitmapImageFactory.java
@@ -327,7 +327,7 @@ public class BitmapImageFactory extends ImageFactory {
 
         private int randomColor() {
             return Color.rgb(
-                    RandomUtils.nextInt(255), RandomUtils.nextInt(255), RandomUtils.nextInt(255));
+                    RandomUtils.nextInt(256), RandomUtils.nextInt(256), RandomUtils.nextInt(256));
         }
 
         private int darker(int color) {
diff --git a/api/src/main/java/ai/djl/ndarray/NDArray.java b/api/src/main/java/ai/djl/ndarray/NDArray.java
index 74f8b64fb..f8d1427c8 100644
--- a/api/src/main/java/ai/djl/ndarray/NDArray.java
+++ b/api/src/main/java/ai/djl/ndarray/NDArray.java
@@ -4582,7 +4582,17 @@ public interface NDArray extends NDResource, BytesSupplier {
      * @return the debug string representation of this {@code NDArray}
      */
     default String toDebugString() {
-        return toDebugString(100, 10, 10, 20);
+        return NDFormat.format(this, 100, 10, 10, 20);
+    }
+
+    /**
+     * Runs the debug string representation of this {@code NDArray}.
+     *
+     * @param withContent true to show the content of NDArray
+     * @return the debug string representation of this {@code NDArray}
+     */
+    default String toDebugString(boolean withContent) {
+        return toDebugString(1000, 10, 10, 20, withContent);
     }
 
     /**
@@ -4592,10 +4602,12 @@ public interface NDArray extends NDResource, BytesSupplier {
      * @param maxDepth the maximum depth to print out
      * @param maxRows the maximum rows to print out
      * @param maxColumns the maximum columns to print out
+     * @param withContent true to show the content of NDArray
      * @return the debug string representation of this {@code NDArray}
      */
-    default String toDebugString(int maxSize, int maxDepth, int maxRows, int maxColumns) {
-        return NDFormat.format(this, maxSize, maxDepth, maxRows, maxColumns);
+    default String toDebugString(
+            int maxSize, int maxDepth, int maxRows, int maxColumns, boolean withContent) {
+        return NDFormat.format(this, maxSize, maxDepth, maxRows, maxColumns, withContent);
     }
 
     /** {@inheritDoc} */
diff --git a/api/src/main/java/ai/djl/ndarray/internal/NDFormat.java b/api/src/main/java/ai/djl/ndarray/internal/NDFormat.java
index 40a2ad621..c60998c13 100644
--- a/api/src/main/java/ai/djl/ndarray/internal/NDFormat.java
+++ b/api/src/main/java/ai/djl/ndarray/internal/NDFormat.java
@@ -29,9 +29,10 @@ public abstract class NDFormat {
     private static final int PRECISION = 8;
     private static final String LF = System.getProperty("line.separator");
     private static final Pattern PATTERN = Pattern.compile("\\s*\\d\\.(\\d*?)0*e[+-](\\d+)");
-    private static final boolean DEBUG =
-            ManagementFactory.getRuntimeMXBean().getInputArguments().stream()
-                    .anyMatch(arg -> arg.startsWith("-agentlib:jdwp"));
+    private static final boolean DEBUGGER =
+            !Boolean.getBoolean("jshell")
+                    && ManagementFactory.getRuntimeMXBean().getInputArguments().stream()
+                            .anyMatch(arg -> arg.startsWith("-agentlib:jdwp"));
 
     /**
      * Formats the contents of an array as a pretty printable string.
@@ -45,6 +46,27 @@ public abstract class NDFormat {
      */
     public static String format(
             NDArray array, int maxSize, int maxDepth, int maxRows, int maxColumns) {
+        return format(array, maxSize, maxDepth, maxRows, maxColumns, !DEBUGGER);
+    }
+
+    /**
+     * Formats the contents of an array as a pretty printable string.
+     *
+     * @param array the array to print
+     * @param maxSize the maximum elements to print out
+     * @param maxDepth the maximum depth to print out
+     * @param maxRows the maximum rows to print out
+     * @param maxColumns the maximum columns to print out
+     * @param withContent true to show the content of NDArray
+     * @return the string representation of the array
+     */
+    public static String format(
+            NDArray array,
+            int maxSize,
+            int maxDepth,
+            int maxRows,
+            int maxColumns,
+            boolean withContent) {
         StringBuilder sb = new StringBuilder(1000);
         String name = array.getName();
         if (name != null) {
@@ -60,7 +82,7 @@ public abstract class NDFormat {
         if (array.hasGradient()) {
             sb.append(" hasGradient");
         }
-        if (DEBUG) {
+        if (!withContent) {
             return sb.toString();
         }
 
diff --git a/api/src/main/java/ai/djl/training/listener/TrainingListener.java b/api/src/main/java/ai/djl/training/listener/TrainingListener.java
index 993273bd2..3d81601f2 100644
--- a/api/src/main/java/ai/djl/training/listener/TrainingListener.java
+++ b/api/src/main/java/ai/djl/training/listener/TrainingListener.java
@@ -76,7 +76,7 @@ public interface TrainingListener {
     interface Defaults {
 
         /**
-         * A basic {@link TrainingListener} set with minimal recommended functionality.
+         * A basic {@link TrainingListener} set with the minimal recommended functionality.
          *
          * <p>This contains:
          *
diff --git a/api/src/main/java/ai/djl/translate/Batchifier.java b/api/src/main/java/ai/djl/translate/Batchifier.java
index 7f21a696f..fd8446f9c 100644
--- a/api/src/main/java/ai/djl/translate/Batchifier.java
+++ b/api/src/main/java/ai/djl/translate/Batchifier.java
@@ -18,8 +18,8 @@ import java.util.Arrays;
 import java.util.stream.IntStream;
 
 /**
- * An interface that provides methods to convert an un-batched {@link NDList} into a batched {@link
- * NDList} and vice versa.
+ * An interface for different strategies to convert between {@link ai.djl.training.dataset.Record}
+ * {@link NDList}s and {@link ai.djl.training.dataset.Batch} {@link NDList}.
  *
  * <p>Different implementations of {@code Batchifier} represent different ways of creating batches.
  * The most common would be the {@link StackBatchifier} that batchifies by creating a new batch axis
@@ -49,7 +49,8 @@ public interface Batchifier {
     }
 
     /**
-     * Converts an array of {@link NDList} into an NDList.
+     * Converts an array of {@link ai.djl.training.dataset.Record} {@link NDList}s into a combined
+     * {@link ai.djl.training.dataset.Batch} NDList.
      *
      * <p>The size of the input array is the batch size. The data in each of the {@link NDList} are
      * assumed to be the same, and are batched together to form one batched {@link NDList}.
@@ -60,7 +61,10 @@ public interface Batchifier {
     NDList batchify(NDList[] inputs);
 
     /**
-     * Reverses the {@link #batchify(NDList[]) batchify} operation.
+     * Splits a combined {@link ai.djl.training.dataset.Batch} {@link NDList} into it's constituent
+     * {@link ai.djl.training.dataset.Record} {@link NDList}s.
+     *
+     * <p>This reverses the {@link #batchify(NDList[]) batchify} operation.
      *
      * @param inputs the {@link NDList} that needs to be 'unbatchified'
      * @return an array of NDLists, of size equal to batch size, where each NDList is one element
@@ -69,7 +73,11 @@ public interface Batchifier {
     NDList[] unbatchify(NDList inputs);
 
     /**
-     * Splits the given {@link NDList} into the given number of slices.
+     * Partitions the given {@link ai.djl.training.dataset.Batch} {@link NDList} into multiple
+     * {@link ai.djl.training.dataset.Batch} lists with smaller batch size.
+     *
+     * <p>As an example, this function might be used for multi-GPU training where it takes the main
+     * batch and splits it into sub-batches that can be run on each GPU.
      *
      * <p>This function unbatchifies the input {@link NDList}, redistributes them into the given
      * number of slices, and then batchify each of the slices to form an array of {@link NDList}.
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AmesRandomAccess.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AmesRandomAccess.java
index b9b4e8edb..e0e8ee7cf 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AmesRandomAccess.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/AmesRandomAccess.java
@@ -29,7 +29,6 @@ import java.io.Reader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -44,6 +43,9 @@ import java.util.Set;
  * <p>Test Set: 1459 Records
  *
  * <p>Can enable/disable features Set one hot vector for categorical variables
+ *
+ * <p>Call {@link Builder#addAllFeatures()} to include all features from the dataset. The label is a
+ * numeric column named "saleprice".
  */
 public class AmesRandomAccess extends CsvDataset {
 
@@ -200,15 +202,29 @@ public class AmesRandomAccess extends CsvDataset {
         public Builder addFeature(String name, boolean onehotEncode) {
             parseFeatures();
             if (af.categorical.contains(name)) {
-                Map<String, Integer> map = af.featureToMap.get(name);
-                if (map == null) {
-                    return addCategoricalFeature(name, onehotEncode);
-                }
-                return addCategoricalFeature(name, map, onehotEncode);
+                return addCategoricalFeature(name, onehotEncode);
             }
             return addNumericFeature(name);
         }
 
+        /**
+         * Adds all features to the features set.
+         *
+         * @return this builder
+         */
+        public Builder addAllFeatures() {
+            if (features.isEmpty()) {
+                parseFeatures();
+                for (String name : af.featureArray) {
+                    addFeature(name);
+                }
+            }
+            if (labels.isEmpty()) {
+                addNumericLabel("saleprice");
+            }
+            return this;
+        }
+
         /**
          * Returns the available features of this dataset.
          *
@@ -226,15 +242,6 @@ public class AmesRandomAccess extends CsvDataset {
          */
         @Override
         public AmesRandomAccess build() {
-            if (features.isEmpty()) {
-                parseFeatures();
-                for (String name : af.featureArray) {
-                    addFeature(name);
-                }
-            }
-            if (labels.isEmpty()) {
-                addNumericLabel("saleprice");
-            }
             return new AmesRandomAccess(this);
         }
 
@@ -260,6 +267,5 @@ public class AmesRandomAccess extends CsvDataset {
 
         List<String> featureArray;
         Set<String> categorical;
-        Map<String, Map<String, Integer>> featureToMap;
     }
 }
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java
index 5de3eccc9..702ceaa5f 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/CsvDataset.java
@@ -155,7 +155,6 @@ public class CsvDataset extends TabularDataset {
          * @return the new {@link CsvDataset}
          */
         public CsvDataset build() {
-            validate();
             return new CsvDataset(this);
         }
     }
diff --git a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
index 9a8380c1c..9a0febfe0 100644
--- a/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
+++ b/basicdataset/src/main/java/ai/djl/basicdataset/tabular/TabularDataset.java
@@ -44,6 +44,13 @@ public abstract class TabularDataset extends RandomAccessDataset {
         super(builder);
         features = builder.features;
         labels = builder.labels;
+
+        if (features.isEmpty()) {
+            throw new IllegalArgumentException("Missing features.");
+        }
+        if (labels.isEmpty() && !builder.allowNoLabels) {
+            throw new IllegalArgumentException("Missing labels.");
+        }
     }
 
     /**
@@ -135,6 +142,7 @@ public abstract class TabularDataset extends RandomAccessDataset {
 
         protected List<Feature> features;
         protected List<Feature> labels;
+        protected boolean allowNoLabels;
 
         protected BaseBuilder() {
             features = new ArrayList<>();
@@ -283,17 +291,13 @@ public abstract class TabularDataset extends RandomAccessDataset {
         }
 
         /**
-         * Validates the builder to ensure it is correct.
+         * Indicates the dataset should not have any labels.
          *
-         * @throws IllegalArgumentException if there is an error with the builder arguments
+         * @return this builder
          */
-        protected void validate() {
-            if (features.isEmpty()) {
-                throw new IllegalArgumentException("Missing features.");
-            }
-            if (labels.isEmpty()) {
-                throw new IllegalArgumentException("Missing labels.");
-            }
+        public T noLabels() {
+            allowNoLabels = true;
+            return self();
         }
     }
 }
diff --git a/basicdataset/src/main/resources/ai/djl/basicdataset/tabular/ames.json b/basicdataset/src/main/resources/ai/djl/basicdataset/tabular/ames.json
index 07ac15318..336365f10 100644
--- a/basicdataset/src/main/resources/ai/djl/basicdataset/tabular/ames.json
+++ b/basicdataset/src/main/resources/ai/djl/basicdataset/tabular/ames.json
@@ -30,7 +30,6 @@
     "exterqual",
     "extercond",
     "foundation",
-    "bsmtqual",
     "bsmtcond",
     "bsmtexposure",
     "bsmtfintype1",
@@ -103,6 +102,7 @@
     "exterqual",
     "extercond",
     "foundation",
+    "bsmtqual",
     "bsmtcond",
     "bsmtexposure",
     "bsmtfintype1",
@@ -124,354 +124,5 @@
     "miscfeature",
     "saletype",
     "salecondition"
-  ],
-  "featureToMap": {
-    "lotconfig": {
-      "CulDSac": 3,
-      "Corner": 2,
-      "FR3": 4,
-      "FR2": 1,
-      "Inside": 0
-    },
-    "garagefinish": {
-      "Fin": 2,
-      "NA": 3,
-      "RFn": 0,
-      "Unf": 1
-    },
-    "poolqc": {
-      "NA": 0,
-      "Ex": 1,
-      "Fa": 2,
-      "Gd": 3
-    },
-    "kitchenqual": {
-      "Ex": 2,
-      "Fa": 3,
-      "Gd": 0,
-      "TA": 1
-    },
-    "functional": {
-      "Mod": 4,
-      "Min2": 3,
-      "Sev": 6,
-      "Maj2": 5,
-      "Typ": 0,
-      "Min1": 1,
-      "Maj1": 2
-    },
-    "mszoning": {
-      "FV": 3,
-      "RH": 4,
-      "RL": 0,
-      "RM": 1,
-      "C (all)": 2
-    },
-    "bsmtexposure": {
-      "No": 0,
-      "Mn": 2,
-      "NA": 4,
-      "Av": 3,
-      "Gd": 1
-    },
-    "heatingqc": {
-      "Ex": 0,
-      "Fa": 3,
-      "Gd": 1,
-      "TA": 2,
-      "Po": 4
-    },
-    "foundation": {
-      "CBlock": 1,
-      "BrkTil": 2,
-      "Wood": 3,
-      "PConc": 0,
-      "Slab": 4,
-      "Stone": 5
-    },
-    "utilities": {
-      "NoSeWa": 1,
-      "AllPub": 0
-    },
-    "paveddrive": {
-      "P": 2,
-      "Y": 0,
-      "N": 1
-    },
-    "exterior1st": {
-      "AsbShng": 8,
-      "Stucco": 9,
-      "BrkComm": 10,
-      "AsphShn": 11,
-      "CBlock": 14,
-      "WdShing": 5,
-      "Stone": 12,
-      "BrkFace": 4,
-      "MetalSd": 1,
-      "Wd Sdng": 2,
-      "HdBoard": 3,
-      "VinylSd": 0,
-      "Plywood": 7,
-      "CemntBd": 6,
-      "ImStucc": 13
-    },
-    "roofstyle": {
-      "Mansard": 3,
-      "Shed": 5,
-      "Gambrel": 2,
-      "Gable": 0,
-      "Flat": 4,
-      "Hip": 1
-    },
-    "street": {
-      "Pave": 0,
-      "Grvl": 1
-    },
-    "alley": {
-      "NA": 0,
-      "Grvl": 1,
-      "Pave": 2
-    },
-    "heating": {
-      "Grav": 2,
-      "OthW": 4,
-      "Floor": 5,
-      "Wall": 3,
-      "GasA": 0,
-      "GasW": 1
-    },
-    "bldgtype": {
-      "Twnhs": 4,
-      "1Fam": 0,
-      "TwnhsE": 3,
-      "2fmCon": 1,
-      "Duplex": 2
-    },
-    "masvnrtype": {
-      "BrkFace": 0,
-      "NA": 4,
-      "None": 1,
-      "Stone": 2,
-      "BrkCmn": 3
-    },
-    "extercond": {
-      "Ex": 4,
-      "Fa": 2,
-      "TA": 0,
-      "Gd": 1,
-      "Po": 3
-    },
-    "garagetype": {
-      "CarPort": 3,
-      "Detchd": 1,
-      "NA": 4,
-      "Basment": 5,
-      "Attchd": 0,
-      "2Types": 6,
-      "BuiltIn": 2
-    },
-    "electrical": {
-      "FuseA": 2,
-      "NA": 5,
-      "FuseF": 1,
-      "SBrkr": 0,
-      "Mix": 4,
-      "FuseP": 3
-    },
-    "exterqual": {
-      "Ex": 2,
-      "Fa": 3,
-      "Gd": 0,
-      "TA": 1
-    },
-    "landslope": {
-      "Mod": 1,
-      "Sev": 2,
-      "Gtl": 0
-    },
-    "bsmtcond": {
-      "NA": 2,
-      "Fa": 3,
-      "TA": 0,
-      "Gd": 1,
-      "Po": 4
-    },
-    "landcontour": {
-      "Lvl": 0,
-      "Low": 2,
-      "Bnk": 1,
-      "HLS": 3
-    },
-    "fireplacequ": {
-      "NA": 0,
-      "Ex": 4,
-      "Fa": 3,
-      "TA": 1,
-      "Gd": 2,
-      "Po": 5
-    },
-    "housestyle": {
-      "1Story": 1,
-      "2.5Fin": 7,
-      "2.5Unf": 6,
-      "2Story": 0,
-      "SLvl": 5,
-      "1.5Unf": 3,
-      "SFoyer": 4,
-      "1.5Fin": 2
-    },
-    "exterior2nd": {
-      "Stucco": 8,
-      "AsbShng": 9,
-      "AsphShn": 12,
-      "CBlock": 15,
-      "CmentBd": 6,
-      "Brk Cmn": 10,
-      "Wd Shng": 2,
-      "Stone": 13,
-      "BrkFace": 7,
-      "MetalSd": 1,
-      "HdBoard": 3,
-      "Wd Sdng": 5,
-      "VinylSd": 0,
-      "Plywood": 4,
-      "ImStucc": 11,
-      "Other": 14
-    },
-    "condition1": {
-      "RRAe": 4,
-      "RRNn": 5,
-      "RRAn": 6,
-      "PosA": 7,
-      "Artery": 3,
-      "RRNe": 8,
-      "Norm": 0,
-      "PosN": 2,
-      "Feedr": 1
-    },
-    "garagequal": {
-      "NA": 3,
-      "Ex": 4,
-      "Fa": 1,
-      "TA": 0,
-      "Gd": 2,
-      "Po": 5
-    },
-    "roofmatl": {
-      "Tar\u0026Grv": 5,
-      "ClyTile": 7,
-      "WdShngl": 1,
-      "Membran": 4,
-      "Metal": 2,
-      "Roll": 6,
-      "CompShg": 0,
-      "WdShake": 3
-    },
-    "condition2": {
-      "RRAe": 7,
-      "RRNn": 2,
-      "Norm": 0,
-      "PosA": 5,
-      "RRAn": 6,
-      "PosN": 4,
-      "Feedr": 3,
-      "Artery": 1
-    },
-    "saletype": {
-      "New": 1,
-      "Oth": 8,
-      "Con": 7,
-      "ConLw": 6,
-      "WD": 0,
-      "CWD": 5,
-      "COD": 2,
-      "ConLI": 4,
-      "ConLD": 3
-    },
-    "garagecond": {
-      "NA": 2,
-      "Ex": 5,
-      "Fa": 1,
-      "TA": 0,
-      "Gd": 3,
-      "Po": 4
-    },
-    "centralair": {
-      "Y": 0,
-      "N": 1
-    },
-    "lotshape": {
-      "IR2": 2,
-      "IR1": 1,
-      "IR3": 3,
-      "Reg": 0
-    },
-    "neighborhood": {
-      "NWAmes": 6,
-      "BrDale": 22,
-      "Somerst": 5,
-      "Timber": 16,
-      "Gilbert": 17,
-      "SawyerW": 12,
-      "NPkVill": 20,
-      "Sawyer": 9,
-      "NoRidge": 3,
-      "NridgHt": 10,
-      "StoneBr": 18,
-      "Veenker": 1,
-      "BrkSide": 8,
-      "NAmes": 11,
-      "CollgCr": 0,
-      "MeadowV": 14,
-      "Blmngtn": 21,
-      "Blueste": 24,
-      "IDOTRR": 13,
-      "Edwards": 15,
-      "ClearCr": 19,
-      "Mitchel": 4,
-      "OldTown": 7,
-      "Crawfor": 2,
-      "SWISU": 23
-    },
-    "fence": {
-      "GdPrv": 3,
-      "NA": 0,
-      "MnPrv": 1,
-      "GdWo": 2,
-      "MnWw": 4
-    },
-    "bsmtfintype2": {
-      "Rec": 4,
-      "NA": 2,
-      "BLQ": 1,
-      "ALQ": 3,
-      "LwQ": 5,
-      "Unf": 0,
-      "GLQ": 6
-    },
-    "miscfeature": {
-      "TenC": 4,
-      "NA": 0,
-      "Shed": 1,
-      "Othr": 3,
-      "Gar2": 2
-    },
-    "salecondition": {
-      "AdjLand": 3,
-      "Alloca": 4,
-      "Partial": 2,
-      "Family": 5,
-      "Normal": 0,
-      "Abnorml": 1
-    },
-    "bsmtfintype1": {
-      "Rec": 3,
-      "NA": 5,
-      "ALQ": 1,
-      "BLQ": 4,
-      "LwQ": 6,
-      "Unf": 2,
-      "GLQ": 0
-    }
-  }
+  ]
 }
diff --git a/docs/development/development_guideline.md b/docs/development/development_guideline.md
index 4c7afebaa..1b043de3b 100644
--- a/docs/development/development_guideline.md
+++ b/docs/development/development_guideline.md
@@ -159,7 +159,8 @@ You can create your own NDArray renderer as follows:
 Please make sure to:
 
 - Check the "On-demand" option, which causes IntelliJ to only render the NDArray when you click on the variable.
-- Change the "Use following expression" field to something like [toDebugString(100, 10, 10, 20)](https://javadoc.io/static/ai.djl/api/0.19.0/ai/djl/ndarray/NDArray.html#toDebugString-int-int-int-int-)
+- Change the "Use following expression" field to `toDebugString(true)` to show NDArray content
+- Change the "Use following expression" field to something like `toDebugString(1000, 10, 10, 20, true)`
 if you want to adjust the range of NDArray's debug output.
 
 ## Common Problems
diff --git a/docs/development/memory_management.md b/docs/development/memory_management.md
index 867348c82..470bb5f25 100644
--- a/docs/development/memory_management.md
+++ b/docs/development/memory_management.md
@@ -1,44 +1,96 @@
 # Memory Management
 
-Memory is one of the biggest challenge in area of deep learning. There are several challenges in Java to manage the memory.
-Firstly, GC(Garbage Collector) doesn't have the control over the native memory.
-Secondly, to close every AutoClosable manually makes the code boilerplate and not practical.
-Thirdly, the system lacks in support of releasing a group of native resources.
-As a result, we create the [NDManager](https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDManager.html)
-to help us release the native memory.
-
-We design the NDManager in tree structure. It provides fine-grained control of native resource and manage the resource scope in more effectively way.
-NDManager can make any kind of tree. However, using the Predictor/Trainer classes will automatically create a certain kind of tree.
-The structure of the NDManager for the classic inference case is like ![structure of the NDManager](https://raw.githubusercontent.com/deepjavalibrary/djl/master/docs/development/img/ndmanager_structure_for_inference.png).
-The structure of the NDManager for the classic training case is like ![structure of the NDManager](https://github.com/deepjavalibrary/djl/blob/master/docs/development/img/ndmanager_structure_for_training.png?raw=true).
-The topmost is System NDManager. The model, which is one layer below, contains the weight and bias of the Neural Network.
-The bottommost NDManager takes care of the intermediate NDArrays we would like to close as soon as the program exit the scope of the functions that use them.
-
-If the native resources is no longer used, we allow GC collect it's native memory before NDManager close them.
-However, there are still some limitations. For example, it may cause OOM(Out of memory) if preprocessing and postprocessing create a large amount of NDArrays in the training loop before GC kicks in.
- 
-Here are the rule of thumb:
-
-* The output of the NDArray operation should be attached to the same manager as the input one. The order matters as we use first NDArray's manager on the NDArray operation by default.
-* If intermediate NDArray get into upper level NDManager, e.g from Trainer -> Model, the memory leak will happen.
+Memory is one of the biggest challenge in the area of deep learning, especially in Java.
+The greatest issue is that the garbage collector doesn't have control over the native memory.
+It doesn't know how much is used and how to free it.
+Beyond that, it can be too slow for high-memory usages such as training on a GPU.
+
+Without the automatic memory management, it is still possible to manually free the memory.
+However, there are too many NDArrays that are created for such a thing to be practical.
+
+For this reason, DJL uses the [NDManager](https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDManager.html).
+The NDManager behaves as both a factory and a scope.
+All arrays created by a manager are part of its scope.
+By freeing the manager, it can then free all the arrays attached to it.
+This also includes arrays produced by operations such as `NDArray.add(other)` or `Activation.relu(array)`.
+They are attached to the manager of the input argument.
+If it takes multiple `NDArray` inputs, it is attached to the manager for the first one.
+
+It is also common to have a hierarchy of NDManagers.
+Each of them represent a different level of abstraction and point at which the data are freed.
+As an example, let's look at some common usages:
+
+### Inference Use Case
+
+The structure of the NDManager for the classic inference case is like:
+
+![structure of the NDManager](https://raw.githubusercontent.com/deepjavalibrary/djl/master/docs/development/img/ndmanager_structure_for_inference.png).
+
+At the top of the hierarchy is the system manager.
+It contains memory which is global, and the system manager can't be closed.
+When you create a new manager by calling `NDManager.newBaseManager()`, it is created as a child of the system manager.
+
+Under the system manager, there is the manager for the model and then the predictor.
+Those are both likely to be long-lasting and contain the parameters to run prediction.
+The model would contain the standard copy and the predictor may contain additional copies of the parameters depending on the engine and wheter multiple devices are used.
+
+Each time data is passed into a call to `predict()`, it is added to a `PredictorContext` manager.
+This data is temporary and only lasts as long as the call to predict.
+It ensures that all the temporary arrays created during the `predict()` call are promptly freed.
+As long as the input and output to the `Translator` for the predictor are standard Java classes and not resources like `NDArray` or `NDList`, this will handle the memory automatically.
+If instead an `NDResource` is used for either an input or output, the user must make sure that the memory is attached to a specific manager and freed when no longer needed.
+
+Make sure that within the preprocessing, post-processing, and model, data is created only within the `PredictorContext`.
+Memory created in other managers (especially the model or predictor) would not be freed and would constitute a memory leak.
+
+### Training Use Case
+
+The structure of the NDManager for the classic training case is like:
+
+![structure of the NDManager](https://github.com/deepjavalibrary/djl/blob/master/docs/development/img/ndmanager_structure_for_training.png?raw=true).
+
+Like the inference use case, it contains the same system manager and model.
+The `Trainer` behaves similarly to the `Predictor`, although only one `Trainer` should be created rather than one per device.
+
+Then, there is also the `Batch`.
+It contains memory which should be used for a training step, and freed directly afterwards.
+Here, the use of the `NDManager` makes it possible to take up the whole GPU for each batch without straining the GC.
+Note that a **`Batch` must be manually closed at the end of the training step**.
+
+Make sure that within the training steps, loss, and model, data is created only within the `Batch` manager.
+Memory created in other managers (especially the model or trainer) would not be freed and would constitute a memory leak.
+If you remember to close the `Batch` and memory is still growing during the training step, some memory is likely being attached to the wrong manager.
+
+## Rules of Thumb:
+
+* The output of an NDArray operation should be attached to the same manager as the input NDArray. The order matters as we use first NDArray's manager on the NDArray operation by default.
+* If an intermediate NDArray gets into an upper level NDManager (e.g. from Trainer -> Model), this is a memory leak
 * You can use `NDManager.debugDump()` to see if any of NDManager's resource count is keep increasing.
 * If a large amount of intermediate NDArrays are needed, it is recommended to create your own subNDManager or close them manually.
 
-## Inference case
-For the majority of the inference cases, you would be working on the ProcessInput and ProcessOutput.
-Make sure all temporary NDArrays are attached to the NDManager in TranslatorContext.
-Note that if you don't specify NDManager in a NDArray operation, it uses the NDManger from the input NDArray.
-
-## Training
-The intermediate NDArrays involving in training case are usually
- 
-1. a batch of the dataset
-2. custom operation you write
-
-In general, all the parameters in the model should be associated with Model level NDManager.
-All of the input and output NDArrays should be associated with one NDManager which is one level down to the model NDManager.
-Please check if you call [batch.close()](https://javadoc.io/static/ai.djl/api/0.19.0/ai/djl/training/dataset/Batch.html#close--)
-to release one batch of the dataset at the end of each batch.
-If you still see the memory grows as the training process goes, it is most likely that intermediate NDArrays are attached to the Model(Block) parameter level.
-As a result, those NDArrays would not closed until the training is finished.
-On the other hand, if you implement your own block, be aware of not attaching the parameter to batch level NDManager.
+## Advanced Memory Management
+
+There are also a few tools for more sophisticated memory management.
+The first is by using `ndManager.tempAttachAll()`.
+This will allow a manager to "borrow" some resources (NDManager or NDList) from another manager.
+When the borrower is closed, the resources are returned to the original manager.
+
+The most common usage of temp attach is to create a scope for performing some computations.
+A dedicated manager is created for the computation and the inputs are temporarily attached to it.
+After the result is computed, the result is attached to a higher manager either manually or by using `computationManager.ret(resource)`.
+Then, the computation manager is closed which frees all intermediate computation memory and returns the input arrays back to their original manager.
+
+## Troubleshooting
+
+If you find your memory increasing, here are some things to look for.
+If you are doing training, make sure that you close the `Batch`.
+If you are doing inference, make sure that the translator either uses standard Java classes (not `NDManager` or `NDList`) or that the prediction input/output are freed manually.
+
+If none of these are the problem, one test is to try calling `ndManager.cap()`.
+Calling `cap` will block a manager from having additional data attached to it.
+Try to `cap` the manager for the Model and/or the Trainer/Predictor before a training batch or prediction call.
+This will throw an exception if any memory is attached to these higher managers accidentally.
+
+You can also use `BaseNDManager.debugDump(...)` to help see what managers and resources are attached to a given manager.
+Calling it on a manager repeatedly will let you know if the resource count is increasing.
+It may also help to set names for the arrays or managers to help identify them within the dump.
\ No newline at end of file
diff --git a/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java b/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java
index aaa277255..183775154 100644
--- a/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java
+++ b/extensions/tablesaw/src/main/java/ai/djl/tablesaw/TablesawDataset.java
@@ -104,7 +104,6 @@ public class TablesawDataset extends TabularDataset {
          * @return the new {@link TablesawDataset}
          */
         public TablesawDataset build() {
-            validate();
             return new TablesawDataset(this);
         }
     }


Test patch:
diff --git a/basicdataset/src/test/java/ai/djl/basicdataset/AmesRandomAccessTest.java b/basicdataset/src/test/java/ai/djl/basicdataset/AmesRandomAccessTest.java
index d0a008aeb..ab58b8cb8 100644
--- a/basicdataset/src/test/java/ai/djl/basicdataset/AmesRandomAccessTest.java
+++ b/basicdataset/src/test/java/ai/djl/basicdataset/AmesRandomAccessTest.java
@@ -64,6 +64,7 @@ public class AmesRandomAccessTest {
                             .addFeature("MiscVal")
                             .addFeature("id")
                             .addFeature("alley", true)
+                            .addNumericLabel("saleprice")
                             .setSampling(32, true)
                             .build();
 
@@ -75,7 +76,7 @@ public class AmesRandomAccessTest {
             Record record = amesRandomAccess.get(manager, 0);
             NDList data = record.getData();
             NDList labels = record.getLabels();
-            Assert.assertEquals(data.head().toFloatArray(), new float[] {0, 1, 1, 0, 0});
+            Assert.assertEquals(data.head().toFloatArray(), new float[] {0, 1, 0, 1, 0});
             Assert.assertEquals(labels.singletonOrThrow().toFloatArray()[0], 208500.0);
 
             try (Trainer trainer = model.newTrainer(config)) {
@@ -86,4 +87,39 @@ public class AmesRandomAccessTest {
             }
         }
     }
+
+    @Test
+    public void testAmesRandomAccessAllFeatures() throws IOException, TranslateException {
+        try (NDManager manager = NDManager.newBaseManager()) {
+            AmesRandomAccess amesRandomAccess =
+                    AmesRandomAccess.builder()
+                            .optUsage(Dataset.Usage.TRAIN)
+                            .addAllFeatures()
+                            .setSampling(32, true)
+                            .build();
+
+            amesRandomAccess.prepare();
+
+            long size = amesRandomAccess.size();
+            Assert.assertEquals(size, 1460);
+
+            Record record = amesRandomAccess.get(manager, 0);
+            NDList data = record.getData();
+            Assert.assertEquals(data.head().size(), 79);
+        }
+    }
+
+    @Test
+    public void testAmesNoFeatures() {
+        Assert.assertThrows(
+                () -> {
+                    AmesRandomAccess amesRandomAccess =
+                            AmesRandomAccess.builder()
+                                    .optUsage(Dataset.Usage.TRAIN)
+                                    .setSampling(32, true)
+                                    .build();
+
+                    amesRandomAccess.prepare();
+                });
+    }
 }
diff --git a/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java b/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java
index a7efa6496..6a68c6431 100644
--- a/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java
+++ b/basicdataset/src/test/java/ai/djl/basicdataset/DailyDelhiClimateTest.java
@@ -51,6 +51,7 @@ public class DailyDelhiClimateTest {
                             .optUsage(Dataset.Usage.TEST)
                             .addFeature("humidity")
                             .addFeature("wind_speed")
+                            .noLabels()
                             .setSampling(32, true)
                             .build();