diff --git a/.gitignore b/.gitignore
index d4c5e0c3f97f0941a0b78355cfceea61e8ee4c18..e5021b74cfbc32760c1fdcfdc421bcd99f052d26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ nshms/
 .factorypath
 .apt_generated*
 *version.json
+src/test/resources/e2e/actual
 
 # Node
 node_modules
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b2f90721e627dff8c318bde6007f9bdfbf6ca542..982acd057cc08007fbb3d447f0873a6b197527b4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -233,13 +233,16 @@ NSHM Tests:
   needs: []
   parallel:
     matrix:
-      - CMD: testAlaska2007
-      - CMD: testAlaska2023
-      - CMD: testConus2018
-      - CMD: testConus2023
-      - CMD: testHawaii2021
+      - NSHM: Alaska2007
+      - NSHM: Alaska2023
+      - NSHM: Conus2018
+      - NSHM: Conus2023
+      - NSHM: Hawaii2021
   script:
-    - ./gradlew ${CMD}
+    - CI_RUNNER_MEMORY="$(awk '/MemTotal/ {printf( "%d\n", $2 / 1024 / 1024 * .90 )}' /proc/meminfo)g"
+    - export CI_RUNNER_MEMORY
+    - ./gradlew generate${NSHM} --info
+    - ./gradlew test${NSHM} --info
     - cat ${JACOCO_HTML_DIR}/index.html
   stage: test
 
diff --git a/build.gradle b/build.gradle
index 5ab547bc09bba72f34d72c23a90064f7219ef804..78d36a5f00a7e492ed942c37129a398fdbd17f26 100644
--- a/build.gradle
+++ b/build.gradle
@@ -70,11 +70,11 @@ tasks.withType(JavaExec) {
   }
 
   jvmArgs(
-      '-noverify',
-      '-Xms2g',
-      '-Xmx8g',
-      '-XX:TieredStopAtLevel=1',
-      '-Dcom.sun.management.jmxremote',
+      "-noverify",
+      "-Xms2g",
+      "-Xmx${xmx}",
+      "-XX:TieredStopAtLevel=1",
+      "-Dcom.sun.management.jmxremote",
       )
 }
 
diff --git a/gradle/nshm.gradle b/gradle/nshm.gradle
index 0aad9e2f57e6de4ba66c985e4ac0d5ecaa1cf227..ea8f4b1076a19a9dbc091c17a86727d7ebdaef08 100644
--- a/gradle/nshm.gradle
+++ b/gradle/nshm.gradle
@@ -13,6 +13,8 @@ import org.yaml.snakeyaml.Yaml
 
 ext {
   nshmDir = "nshms";
+  envMemory = System.getenv("CI_RUNNER_MEMORY")
+  xmx = envMemory ? envMemory : "16g"
 
   // Download and unzip NSHM
   downloadNshm = {nshm ->
@@ -76,6 +78,81 @@ task nshms() {
   }
 }
 
+task cleanGenerated(type: Delete) {
+  delete "src/test/resources/e2e/actual"
+}
+
+// Generate Alaska 2007 for CI
+task generateAlaska2007(type: JavaExec) {
+  description = "Generate alaska-2007 acutal for CI/CD"
+  classpath = sourceSets.test.runtimeClasspath
+
+  doFirst {
+    downloadNshm(findNshm("nshm-alaska", 2007))
+  }
+
+  jvmArgs("-DNSHM=nshm-alaska-2007")
+
+  main = "gov.usgs.earthquake.nshmp.model.GenerateActual"
+}
+
+// Generate Alaska 2023 for CI
+task generateAlaska2023(type: JavaExec) {
+  description = "Generate alaska-2023 acutal for CI/CD"
+  classpath = sourceSets.test.runtimeClasspath
+
+  doFirst {
+    downloadNshm(findNshm("nshm-alaska", 2023))
+  }
+
+  jvmArgs("-DNSHM=nshm-alaska-2023")
+
+  main = "gov.usgs.earthquake.nshmp.model.GenerateActual"
+}
+
+
+// Generate CONUS 2018 for CI
+task generateConus2018(type: JavaExec) {
+  description = "Generate conus-2018 acutal for CI/CD"
+  classpath = sourceSets.test.runtimeClasspath
+
+  doFirst {
+    downloadNshm(findNshm("nshm-conus", 2018))
+  }
+
+  jvmArgs("-DNSHM=nshm-conus-2018")
+
+  main = "gov.usgs.earthquake.nshmp.model.GenerateActual"
+}
+
+// Generate CONUS 2023 for CI
+task generateConus2023(type: JavaExec) {
+  description = "Generate conus-2023 acutal for CI/CD"
+  classpath = sourceSets.test.runtimeClasspath
+
+  doFirst {
+    downloadNshm(findNshm("nshm-conus", 2023))
+  }
+
+  jvmArgs("-DNSHM=nshm-conus-2023")
+
+  main = "gov.usgs.earthquake.nshmp.model.GenerateActual"
+}
+
+// Generate Hawaii 2021 for CI
+task generateHawaii2021(type: JavaExec) {
+  description = "Generate hawaii-2021 acutal for CI/CD"
+  classpath = sourceSets.test.runtimeClasspath
+
+  doFirst {
+    downloadNshm(findNshm("nshm-hawaii", 2021))
+  }
+
+  jvmArgs("-DNSHM=nshm-hawaii-2021")
+
+  main = "gov.usgs.earthquake.nshmp.model.GenerateActual"
+}
+
 // Test Alaska 2007 NSHM
 task testAlaska2007(type: Test) {
   description = "Test Alaska 2007 NSHM"
@@ -89,10 +166,12 @@ task testAlaska2007(type: Test) {
     exceptionFormat "full"
   }
 
+  systemProperties(System.getProperties())
+
   useJUnitPlatform()
   jvmArgs(
       "-Xms2g",
-      "-Xmx8g",
+      "-Xmx${xmx}",
       )
 
   filter {
@@ -113,10 +192,12 @@ task testAlaska2023(type: Test) {
     exceptionFormat "full"
   }
 
+  systemProperties(System.getProperties())
+
   useJUnitPlatform()
   jvmArgs(
       "-Xms2g",
-      "-Xmx8g",
+      "-Xmx${xmx}",
       )
 
   filter {
@@ -137,10 +218,12 @@ task testConus2018(type: Test) {
     exceptionFormat "full"
   }
 
+  systemProperties(System.getProperties())
+
   useJUnitPlatform()
   jvmArgs(
       "-Xms2g",
-      "-Xmx8g",
+      "-Xmx${xmx}",
       )
 
   filter {
@@ -161,10 +244,12 @@ task testConus2023(type: Test) {
     exceptionFormat "full"
   }
 
+  systemProperties(System.getProperties())
+
   useJUnitPlatform()
   jvmArgs(
       "-Xms2g",
-      "-Xmx16g",
+      "-Xmx${xmx}",
       )
 
   filter {
@@ -185,10 +270,12 @@ task testHawaii2021(type: Test) {
     exceptionFormat "full"
   }
 
+  systemProperties(System.getProperties())
+
   useJUnitPlatform()
   jvmArgs(
       "-Xms2g",
-      "-Xmx8g",
+      "-Xmx${xmx}",
       )
 
   filter {
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/model/GenerateActual.java b/src/test/java/gov/usgs/earthquake/nshmp/model/GenerateActual.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a9c54d1114efbee5f249eafb6a582df206ca2f9
--- /dev/null
+++ b/src/test/java/gov/usgs/earthquake/nshmp/model/GenerateActual.java
@@ -0,0 +1,24 @@
+package gov.usgs.earthquake.nshmp.model;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import gov.usgs.earthquake.nshmp.model.NshmTestUtils.Nshm;
+import gov.usgs.earthquake.nshmp.model.NshmTestUtils.NshmModel;
+
+/**
+ * Generate actual results to compare to expected results.
+ *
+ * Used in CI environment for faster unit tests.
+ *
+ * See nshms.gradle for generate tasks.
+ */
+class GenerateActual {
+
+  public static void main(String[] args) throws IOException {
+    Nshm nshm = NshmTests.NSHMS.get(System.getProperty("NSHM"));
+    NshmModel nshmModel = NshmTestUtils.loadModel(nshm);
+    NshmTestUtils.writeExpecteds(nshmModel, Optional.of(NshmTests.DATA_PATH));
+    nshmModel.exec.shutdown();
+  }
+}
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTestUtils.java b/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTestUtils.java
index b876996693dd96cc8d7dc948c61d778587613824..9d0daaba41fdb008c10fa2de33581a33f4bf2f4e 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTestUtils.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTestUtils.java
@@ -13,9 +13,11 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -40,6 +42,8 @@ import io.swagger.v3.core.util.Yaml;
  * Utilities to run tests on a NSHM.
  */
 class NshmTestUtils {
+  private static Logger LOGGER = Logger.getLogger(NshmTestUtils.class.getName());
+
   private static final Path DATA_PATH = Paths.get("src/test/resources/e2e");
 
   private static final double TOLERANCE = 1e-12;
@@ -78,11 +82,12 @@ class NshmTestUtils {
    *
    * @param nshm The NSHM to test
    */
-  static void testNshm(Nshm nshm) {
+  static void testNshm(Nshm nshm, Optional<Path> dataPath) {
     NshmModel nshmModel = loadModel(nshm);
 
     for (NamedLocation location : nshm.locations()) {
-      compareCurves(nshmModel, location);
+      LOGGER.info("Location: " + location.toString());
+      compareCurves(nshmModel, location, dataPath);
     }
 
     nshmModel.exec.shutdown();
@@ -93,11 +98,11 @@ class NshmTestUtils {
    *
    * @param nshmModel The NSHM model
    */
-  static void writeExpecteds(NshmModel nshmModel) throws IOException {
+  static void writeExpecteds(NshmModel nshmModel, Optional<Path> dataPath) throws IOException {
     for (NamedLocation location : nshmModel.nshm.locations()) {
       Map<String, XySequence> xyMap = generateActual(nshmModel, location);
       String json = GSON.toJson(xyMap);
-      writeExpected(nshmModel.nshm, location, json);
+      writeExpected(nshmModel.nshm, location, json, dataPath);
     }
   }
 
@@ -128,9 +133,11 @@ class NshmTestUtils {
         Double.valueOf(expected).equals(Double.valueOf(actual));
   }
 
-  private static void compareCurves(NshmModel nshmModel, NamedLocation location) {
-    Map<String, XySequence> actual = generateActual(nshmModel, location);
-    Map<String, XySequence> expected = readExpected(nshmModel, location);
+  private static void compareCurves(NshmModel nshmModel, NamedLocation location,
+      Optional<Path> dataPath) {
+    Map<String, XySequence> actual = dataPath.isPresent()
+        ? readExpected(nshmModel, location, dataPath) : generateActual(nshmModel, location);
+    Map<String, XySequence> expected = readExpected(nshmModel, location, Optional.empty());
 
     for (String key : actual.keySet()) {
       assertCurveEquals(expected.get(key), actual.get(key), TOLERANCE);
@@ -163,8 +170,9 @@ class NshmTestUtils {
     return xyMap;
   }
 
-  private static Map<String, XySequence> readExpected(NshmModel nshmModel, NamedLocation loc) {
-    Path resultPath = DATA_PATH
+  private static Map<String, XySequence> readExpected(NshmModel nshmModel, NamedLocation loc,
+      Optional<Path> dataPath) {
+    Path resultPath = dataPath.orElse(DATA_PATH)
         .resolve(nshmModel.nshm.modelName())
         .resolve(nshmModel.nshm.resultFilename(loc));
 
@@ -187,8 +195,9 @@ class NshmTestUtils {
   private static void writeExpected(
       Nshm nshm,
       NamedLocation loc,
-      String json) throws IOException {
-    Path modelDir = DATA_PATH.resolve(nshm.modelName());
+      String json,
+      Optional<Path> dataPath) throws IOException {
+    Path modelDir = dataPath.orElse(DATA_PATH).resolve(nshm.modelName());
     if (!Files.exists(modelDir)) {
       Files.createDirectories(modelDir);
     }
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTests.java b/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTests.java
index 85927eac35ad14d37149ddb15ad7110e269241ec..d9c9eb6734faf5543934ca52c8fa2ce1a1e6cbcb 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/model/NshmTests.java
@@ -1,11 +1,15 @@
 package gov.usgs.earthquake.nshmp.model;
 
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import org.junit.jupiter.api.Test;
@@ -21,6 +25,8 @@ import gov.usgs.earthquake.nshmp.site.NshmpSite;
  * Test NSHMs.
  */
 class NshmTests {
+  static Path DATA_PATH = Paths.get("src/test/resources/e2e/actual");
+
   /* Alaska test sites */
   private static final List<NamedLocation> ALASKA_LOCATIONS = List.of(
       NshmpSite.ANCHORAGE_AK,
@@ -50,7 +56,7 @@ class NshmTests {
   private static final Set<Imt> IMTS = EnumSet.of(Imt.PGA, Imt.SA0P2, Imt.SA1P0, Imt.SA5P0);
   private static final Set<Imt> AK_2007_IMTS = EnumSet.of(Imt.PGA, Imt.SA0P2, Imt.SA1P0);
 
-  private static final Map<String, Nshm> NSHMS;
+  static final Map<String, Nshm> NSHMS;
 
   static {
     Map<String, Nshm> nshms = new HashMap<>();
@@ -97,7 +103,7 @@ class NshmTests {
     NshmModel nshmModel =
         NshmTestUtils.loadModel(NSHMS.get("nshm-conus-2023"));
 
-    NshmTestUtils.writeExpecteds(nshmModel);
+    NshmTestUtils.writeExpecteds(nshmModel, Optional.empty());
     nshmModel.exec.shutdown();
 
     // run all models
@@ -115,7 +121,8 @@ class NshmTests {
    */
   @Test
   final void testAlaska2007() throws IOException {
-    NshmTestUtils.testNshm(NSHMS.get("nshm-alaska-2007"));
+    Nshm nshm = NSHMS.get("nshm-alaska-2007");
+    NshmTestUtils.testNshm(nshm, getDataPath(nshm));
   }
 
   /**
@@ -125,7 +132,8 @@ class NshmTests {
    */
   @Test
   final void testAlaska2023() throws IOException {
-    NshmTestUtils.testNshm(NSHMS.get("nshm-alaska-2023"));
+    Nshm nshm = NSHMS.get("nshm-alaska-2023");
+    NshmTestUtils.testNshm(nshm, getDataPath(nshm));
   }
 
   /**
@@ -135,7 +143,8 @@ class NshmTests {
    */
   @Test
   final void testConus2018() throws IOException {
-    NshmTestUtils.testNshm(NSHMS.get("nshm-conus-2018"));
+    Nshm nshm = NSHMS.get("nshm-conus-2018");
+    NshmTestUtils.testNshm(nshm, getDataPath(nshm));
   }
 
   /**
@@ -145,7 +154,8 @@ class NshmTests {
    */
   @Test
   final void testConus2023() throws IOException {
-    NshmTestUtils.testNshm(NSHMS.get("nshm-conus-2023"));
+    Nshm nshm = NSHMS.get("nshm-conus-2023");
+    NshmTestUtils.testNshm(nshm, getDataPath(nshm));
   }
 
   /**
@@ -155,6 +165,12 @@ class NshmTests {
    */
   @Test
   final void testHawaii2021() throws IOException {
-    NshmTestUtils.testNshm(NSHMS.get("nshm-hawaii-2021"));
+    Nshm nshm = NSHMS.get("nshm-hawaii-2021");
+    NshmTestUtils.testNshm(nshm, getDataPath(nshm));
+  }
+
+  private static Optional<Path> getDataPath(Nshm nshm) {
+    return Files.exists(DATA_PATH.resolve(nshm.modelName())) ? Optional.of(DATA_PATH)
+        : Optional.empty();
   }
 }