rzo1 commented on code in PR #1462:
URL: https://github.com/apache/opennlp/pull/1462#discussion_r4242487322


##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/vectors/TinyVectorModels.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl.vectors;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.Objects;
+
+import ai.onnxruntime.OrtEnvironment;
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtProvider;
+
+import opennlp.dl.InferenceOptions;
+
+import static org.junit.jupiter.api.Assumptions.abort;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * Test fixtures for the bundled tiny ONNX models. {@code tiny-vectors.onnx} 
(see
+ * {@code gen_tiny_vectors_model.py}) computes {@code output[b][t] = 
float(input_ids[b][t]) * W}
+ * with {@code W = [0.5, -1, 2]}; {@code tiny-pooled.onnx} (see {@code 
gen_tiny_pooled_model.py})
+ * adds a {@code sentence_embedding} output holding the sum of the ids times 
{@code W}.
+ */
+public final class TinyVectorModels {
+
+  /** The model with a token output only. */
+  public static final String TINY_VECTORS = "tiny-vectors.onnx";
+
+  /** The model with an additional {@code sentence_embedding} output. */
+  public static final String TINY_POOLED = "tiny-pooled.onnx";
+
+  private static final String VECTORS_RESOURCES = "/opennlp/dl/vectors/";
+
+  private TinyVectorModels() {
+  }
+
+  /**
+   * Copies a classpath resource into a directory. The resource is copied 
rather than resolved in
+   * place, because inside the {@code opennlp-dl} test-jar, as used by {@code 
opennlp-dl-gpu}, it
+   * is not a file.
+   *
+   * @param dir The directory to copy into.
+   * @param resource The absolute classpath name of the resource.
+   * @param name The file name of the copy.
+   * @return The copied file.
+   * @throws IOException Thrown if the resource cannot be copied.
+   */
+  public static File copy(final Path dir, final String resource, final String 
name)
+      throws IOException {
+    final Path file = dir.resolve(name);
+    try (InputStream is = Objects.requireNonNull(
+        TinyVectorModels.class.getResourceAsStream(resource), resource)) {
+      Files.copy(is, file, StandardCopyOption.REPLACE_EXISTING);
+    }
+    return file.toFile();
+  }
+
+  /**
+   * Copies a tiny model into a directory.
+   *
+   * @param dir The directory to copy into.
+   * @param name {@link #TINY_VECTORS} or {@link #TINY_POOLED}.
+   * @return The model file.
+   * @throws IOException Thrown if the model cannot be copied.
+   */
+  public static File model(final Path dir, final String name) throws 
IOException {
+    return copy(dir, VECTORS_RESOURCES + name, name);
+  }
+
+  /**
+   * Writes the vocabulary of the tiny models, where the line number is the id:
+   * {@code [UNK]=2, [SEP]=3, hello=4, world=5, [CLS]=7}.
+   *
+   * @param dir The directory to write into.
+   * @return The vocabulary file.
+   * @throws IOException Thrown if the vocabulary cannot be written.
+   */
+  public static File vocab(final Path dir) throws IOException {
+    final Path file = dir.resolve("vocab.txt");
+    Files.write(file, List.of("[PAD]", "unused1", "[UNK]", "[SEP]", "hello", 
"world",
+        "unused2", "[CLS]"));
+    return file.toFile();
+  }
+
+  /**
+   * {@return {@code W} times {@code factor}, the expected vector for a mean 
or summed id}
+   *
+   * @param factor The mean or summed token id.
+   */
+  public static float[] scale(final float factor) {
+    return new float[] {0.5f * factor, -1f * factor, 2f * factor};
+  }
+
+  /**
+   * {@return inference options that request the GPU with the given device id}
+   *
+   * @param deviceId The CUDA device id.
+   */
+  public static InferenceOptions gpu(final int deviceId) {
+    final InferenceOptions options = new InferenceOptions();
+    options.setGpu(true);
+    options.setGpuDeviceId(deviceId);
+    return options;
+  }
+
+  /**
+   * Opens a session on CUDA device {@code 0}, or aborts the calling test 
where the ONNX Runtime
+   * on the classpath has no CUDA execution provider or the device is not 
usable.
+   *
+   * @param model The model file.
+   * @param vocab The vocabulary file.
+   * @param pooling The pooling.
+   * @param normalize {@code true} to scale every vector to unit length.
+   * @return The session on the GPU.
+   * @throws IOException Thrown if the model or vocabulary cannot be read.
+   */
+  public static SentenceVectorsDL gpuOrAbort(final File model, final File 
vocab,
+      final Pooling pooling, final boolean normalize) throws IOException {
+    
assumeTrue(OrtEnvironment.getAvailableProviders().contains(OrtProvider.CUDA),

Review Comment:
   The check for the CUDA provider 
(`OrtEnvironment.getAvailableProviders().contains(OrtProvider.CUDA)`) also 
appears in `AbstractDLColdStartCudaTest.java:71`. Please extract it into one 
helper.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/GpuRequestTest.java:
##########
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtSession;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.dl.doccat.DocumentCategorizerDL;
+import opennlp.dl.doccat.scoring.AverageClassificationScoringStrategy;
+import opennlp.dl.namefinder.NameFinderDL;
+import opennlp.tools.sentdetect.NewlineSentenceDetector;
+
+import static opennlp.dl.vectors.TinyVectorModels.TINY_VECTORS;
+import static opennlp.dl.vectors.TinyVectorModels.copy;
+import static opennlp.dl.vectors.TinyVectorModels.gpu;
+import static opennlp.dl.vectors.TinyVectorModels.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests how a GPU request in {@link InferenceOptions} reaches ONNX Runtime: a 
request for a
+ * device that is not usable is rejected with an {@link OrtException}, never 
served by the CPU,
+ * and a CPU request does not ask for any device.
+ */
+class GpuRequestTest {
+
+  private static final int UNUSABLE_DEVICE_ID = 99;

Review Comment:
   `UNUSABLE_DEVICE_ID = 99` is also declared in 
`SentenceVectorsDLInferenceOptionsTest.java:52`. Please declare it once, next 
to `gpu(int)`.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/AbstractDLColdStartCudaTest.java:
##########
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import ai.onnxruntime.OrtEnvironment;
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtProvider;
+import ai.onnxruntime.OrtSession;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * Tests that {@link AbstractDL#sessionOptions(InferenceOptions)} can request 
the CUDA execution
+ * provider as the first ONNX Runtime call of a JVM. The request either adds 
the provider or is
+ * rejected because the CUDA provider library cannot be loaded, never because 
ONNX Runtime's
+ * default logger is missing.
+ *
+ * <p>Whether ONNX Runtime has been used is process-wide state that the other 
tests of the module
+ * change, so the request runs in a child JVM through {@link 
FirstCudaRequest}. The test runs
+ * where ONNX Runtime has the CUDA provider, which is {@code opennlp-dl-gpu}; 
a CUDA device is not
+ * needed.</p>
+ */
+class AbstractDLColdStartCudaTest {
+
+  private static final String ADDED = "ADDED";
+
+  private static final String REJECTED = "REJECTED ";
+
+  private static final String DEFAULT_LOGGER_FAILURE = "DefaultLogger";
+
+  private static final String CUDA_PROVIDER_FAILURE =
+      "OrtSessionOptionsAppendExecutionProvider_Cuda: Failed to load shared 
library";
+
+  private static final long TIMEOUT_MINUTES = 2;
+
+  /**
+   * Runs {@link FirstCudaRequest} in a child JVM and checks that its outcome 
line reports the
+   * provider as added, or rejected because the CUDA provider library could 
not be loaded.
+   */
+  @Test
+  void testFirstCudaRequestOfAJvm(@TempDir final Path dir)
+      throws IOException, InterruptedException {
+    
assumeTrue(OrtEnvironment.getAvailableProviders().contains(OrtProvider.CUDA),
+        "the ONNX Runtime on the classpath has no CUDA execution provider");
+    final Path out = dir.resolve("stdout.log");
+    final Path err = dir.resolve("stderr.log");
+    final Path java = Path.of(System.getProperty("java.home"), "bin", "java");
+    final Process process = new ProcessBuilder(List.of(java.toString(), "-cp",
+        System.getProperty("java.class.path"), 
FirstCudaRequest.class.getName()))
+        .redirectOutput(out.toFile())
+        .redirectError(err.toFile())
+        .start();
+    if (!process.waitFor(TIMEOUT_MINUTES, TimeUnit.MINUTES)) {
+      process.destroyForcibly();
+      fail("the child JVM did not finish within " + TIMEOUT_MINUTES + " 
minutes");
+    }
+    // The outcome line is read from stdout only: ONNX Runtime logs to stderr, 
on Windows in UTF-16.
+    final String stdout = read(out);
+    final String report = "stdout:\n" + stdout + "\nstderr:\n" + read(err);
+    assertEquals(0, process.exitValue(), report);
+    final String outcome = outcomeLine(stdout, report);
+    assertFalse(outcome.contains(DEFAULT_LOGGER_FAILURE), report);
+    assertTrue(outcome.equals(ADDED) || 
outcome.contains(CUDA_PROVIDER_FAILURE), report);
+  }
+
+  /**
+   * Reads a file of child JVM output, replacing bytes that are not UTF-8.
+   *
+   * @param file The file to read.
+   * @return The content of the file.
+   * @throws IOException Thrown if the file cannot be read.
+   */
+  private static String read(final Path file) throws IOException {
+    return new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
+  }
+
+  /**
+   * Finds the outcome line that {@link FirstCudaRequest} printed.
+   *
+   * @param stdout The standard output of the child JVM.
+   * @param report The complete child JVM output, for the failure message.
+   * @return The line that is {@link #ADDED} or starts with {@link #REJECTED}.
+   */
+  private static String outcomeLine(final String stdout, final String report) {
+    return stdout.lines()
+        .filter(line -> line.equals(ADDED) || line.startsWith(REJECTED))
+        .findFirst()
+        .orElseGet(() -> fail("no outcome line in the child JVM output:\n" + 
report));
+  }
+
+  /**
+   * The child JVM entry point: requests the CUDA execution provider for 
device {@code 0} as the
+   * first ONNX Runtime call, then prints {@link #ADDED} or {@link #REJECTED} 
followed by the
+   * ONNX Runtime error on one line.
+   */
+  public static final class FirstCudaRequest {
+
+    private FirstCudaRequest() {
+    }
+
+    /**
+     * Requests the CUDA execution provider and prints the outcome.
+     *
+     * @param args Not used.
+     */
+    public static void main(final String[] args) {
+      final InferenceOptions options = new InferenceOptions();
+      options.setGpu(true);

Review Comment:
   The GPU options are built by hand, although `TinyVectorModels.gpu(int)` 
already does this. Please reuse it.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/vectors/SentenceVectorsDLInferenceOptionsTest.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl.vectors;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Stream;
+
+import ai.onnxruntime.OrtException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.dl.InferenceOptions;
+
+import static opennlp.dl.vectors.TinyVectorModels.TINY_POOLED;
+import static opennlp.dl.vectors.TinyVectorModels.TINY_VECTORS;
+import static opennlp.dl.vectors.TinyVectorModels.gpu;
+import static opennlp.dl.vectors.TinyVectorModels.gpuOrAbort;
+import static opennlp.dl.vectors.TinyVectorModels.model;
+import static opennlp.dl.vectors.TinyVectorModels.scale;
+import static opennlp.dl.vectors.TinyVectorModels.vocab;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests the {@link SentenceVectorsDL} constructor that takes {@link 
InferenceOptions}.
+ */
+class SentenceVectorsDLInferenceOptionsTest {
+
+  private static final int UNUSABLE_DEVICE_ID = 99;
+
+  private static final float DELTA = 1e-6f;

Review Comment:
   `DELTA = 1e-6f` here, `1e-5f` in `SentenceVectorsDLEmbedderTest.java:65`. 
Please use one tolerance for the tiny model tests.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/GpuRequestTest.java:
##########
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtSession;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.dl.doccat.DocumentCategorizerDL;
+import opennlp.dl.doccat.scoring.AverageClassificationScoringStrategy;
+import opennlp.dl.namefinder.NameFinderDL;
+import opennlp.tools.sentdetect.NewlineSentenceDetector;
+
+import static opennlp.dl.vectors.TinyVectorModels.TINY_VECTORS;
+import static opennlp.dl.vectors.TinyVectorModels.copy;
+import static opennlp.dl.vectors.TinyVectorModels.gpu;
+import static opennlp.dl.vectors.TinyVectorModels.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests how a GPU request in {@link InferenceOptions} reaches ONNX Runtime: a 
request for a
+ * device that is not usable is rejected with an {@link OrtException}, never 
served by the CPU,
+ * and a CPU request does not ask for any device.
+ */
+class GpuRequestTest {
+
+  private static final int UNUSABLE_DEVICE_ID = 99;
+
+  private static final String PLAIN_VOCAB = "/opennlp/dl/vocab-plain.txt";
+
+  /** The device id is not read while {@code gpu} is off, so no provider is 
requested. */
+  @ParameterizedTest
+  @ValueSource(ints = {-1, 0, UNUSABLE_DEVICE_ID})
+  void testSessionOptionsWithoutGpuIgnoreTheDeviceId(final int deviceId) {
+    final InferenceOptions options = new InferenceOptions();
+    options.setGpuDeviceId(deviceId);
+    assertDoesNotThrow(() -> {
+      try (OrtSession.SessionOptions sessionOptions = 
AbstractDL.sessionOptions(options)) {
+        assertNotNull(sessionOptions);
+      }
+    });
+  }
+
+  /** A name finder on a device id that no machine has fails instead of 
running on the CPU. */
+  @Test
+  void testNameFinderRejectsAnUnusableDevice(@TempDir final Path dir) throws 
IOException {
+    final File model = model(dir, TINY_VECTORS);
+    final File vocab = copy(dir, PLAIN_VOCAB, "vocab.txt");
+    final InferenceOptions options = gpu(UNUSABLE_DEVICE_ID);
+    final Map<Integer, String> labels = Map.of(0, "O", 1, "B-PER");
+    assertThrows(OrtException.class, () -> new NameFinderDL(model, vocab, 
labels, options,
+        new NewlineSentenceDetector()).close());
+  }
+
+  /** A categorizer on a device id that no machine has fails instead of 
running on the CPU. */
+  @Test
+  void testDocumentCategorizerRejectsAnUnusableDevice(@TempDir final Path dir)
+      throws IOException {
+    final File model = model(dir, TINY_VECTORS);
+    final File vocab = copy(dir, PLAIN_VOCAB, "vocab.txt");
+    final InferenceOptions options = gpu(UNUSABLE_DEVICE_ID);
+    final Map<Integer, String> categories = Map.of(0, "a", 1, "b");
+    assertThrows(OrtException.class, () -> new DocumentCategorizerDL(model, 
vocab, categories,
+        new AverageClassificationScoringStrategy(), options).close());
+  }
+
+  /** Null options and invalid split sizes are rejected before a device is 
requested. */
+  @Test
+  void testSessionOptionsRejectNullAndInvalidSplitBeforeAnyDevice() {

Review Comment:
   The invalid split options are already covered by 
`SentenceVectorsDLInferenceOptionsTest.java:169`. Please keep one test per rule.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/vectors/OnnxTextEmbedderProviderTest.java:
##########
@@ -82,6 +82,17 @@ void testDoesNotSupportForeignOptionsOrLocations() {
     assertThrows(IllegalArgumentException.class, () -> 
provider.supports(null));
   }
 
+  /**
+   * The provider has no option for a device yet, so a spec cannot select the 
GPU and the
+   * embedder it creates runs on the CPU.
+   */
+  @ParameterizedTest
+  @ValueSource(strings = {"gpu", "gpuDeviceId", "device", "inferenceOptions"})
+  void testSpecCannotSelectTheGpu(final String option) {

Review Comment:
   This test pins a missing feature using invented option names, and has to be 
removed as soon as the provider supports a device. Please document the 
limitation instead, or track it in an issue.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/GpuRequestTest.java:
##########
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtSession;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.dl.doccat.DocumentCategorizerDL;
+import opennlp.dl.doccat.scoring.AverageClassificationScoringStrategy;
+import opennlp.dl.namefinder.NameFinderDL;
+import opennlp.tools.sentdetect.NewlineSentenceDetector;
+
+import static opennlp.dl.vectors.TinyVectorModels.TINY_VECTORS;
+import static opennlp.dl.vectors.TinyVectorModels.copy;
+import static opennlp.dl.vectors.TinyVectorModels.gpu;
+import static opennlp.dl.vectors.TinyVectorModels.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests how a GPU request in {@link InferenceOptions} reaches ONNX Runtime: a 
request for a
+ * device that is not usable is rejected with an {@link OrtException}, never 
served by the CPU,
+ * and a CPU request does not ask for any device.
+ */
+class GpuRequestTest {
+
+  private static final int UNUSABLE_DEVICE_ID = 99;
+
+  private static final String PLAIN_VOCAB = "/opennlp/dl/vocab-plain.txt";
+
+  /** The device id is not read while {@code gpu} is off, so no provider is 
requested. */
+  @ParameterizedTest
+  @ValueSource(ints = {-1, 0, UNUSABLE_DEVICE_ID})
+  void testSessionOptionsWithoutGpuIgnoreTheDeviceId(final int deviceId) {

Review Comment:
   This tests the same rule as `SentenceVectorsDLInferenceOptionsTest.java:120` 
(the device id is ignored without GPU) with the same ids. Please keep one test 
per rule.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/GpuRequestTest.java:
##########
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtSession;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.dl.doccat.DocumentCategorizerDL;
+import opennlp.dl.doccat.scoring.AverageClassificationScoringStrategy;
+import opennlp.dl.namefinder.NameFinderDL;
+import opennlp.tools.sentdetect.NewlineSentenceDetector;
+
+import static opennlp.dl.vectors.TinyVectorModels.TINY_VECTORS;
+import static opennlp.dl.vectors.TinyVectorModels.copy;

Review Comment:
   A test in `opennlp.dl` imports its fixtures from `opennlp.dl.vectors`. The 
general helpers (`copy`, `gpu`, the provider check, the device id) belong in a 
test utility in `opennlp.dl`; `TinyVectorModels` should only keep what is 
specific to the tiny models.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/AbstractDLColdStartCudaTest.java:
##########
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import ai.onnxruntime.OrtEnvironment;
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtProvider;
+import ai.onnxruntime.OrtSession;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * Tests that {@link AbstractDL#sessionOptions(InferenceOptions)} can request 
the CUDA execution
+ * provider as the first ONNX Runtime call of a JVM. The request either adds 
the provider or is
+ * rejected because the CUDA provider library cannot be loaded, never because 
ONNX Runtime's
+ * default logger is missing.
+ *
+ * <p>Whether ONNX Runtime has been used is process-wide state that the other 
tests of the module
+ * change, so the request runs in a child JVM through {@link 
FirstCudaRequest}. The test runs
+ * where ONNX Runtime has the CUDA provider, which is {@code opennlp-dl-gpu}; 
a CUDA device is not
+ * needed.</p>
+ */
+class AbstractDLColdStartCudaTest {
+
+  private static final String ADDED = "ADDED";
+
+  private static final String REJECTED = "REJECTED ";
+
+  private static final String DEFAULT_LOGGER_FAILURE = "DefaultLogger";
+
+  private static final String CUDA_PROVIDER_FAILURE =

Review Comment:
   `CUDA_PROVIDER_FAILURE` depends on the exact error text of ONNX Runtime and 
will break with an upgrade. The test is about the missing default logger, so 
checking for `ADDED`, or for `REJECTED` without `DefaultLogger`, is sufficient.



##########
opennlp-docs/src/docbkx/machine-learning.xml:
##########
@@ -182,5 +182,21 @@ try (TextEmbedder embedder = 
providers.select(spec).create(spec)) {
                        until another one is installed. Existing 
<code>SentenceVectorsDL</code> constructors remain
                        available.
                </para>
+               <para>
+                       To compute the vectors on a GPU, depend on 
<code>opennlp-dl-gpu</code> in place of

Review Comment:
   The manual should state that `opennlp-dl-gpu` supports linux-x64 and win-x64 
only, and link the CUDA requirements of ONNX Runtime.



##########
opennlp-core/opennlp-ml/opennlp-dl-gpu/pom.xml:
##########
@@ -102,5 +109,31 @@
     </plugins>
   </build>
 
+  <!-- onnxruntime_gpu ships native libraries for linux-x64 and win-x64 only. 
-->
+  <profiles>
+    <profile>
+      <id>no-onnxruntime-gpu-on-mac</id>
+      <activation>
+        <os>
+          <family>mac</family>
+        </os>
+      </activation>
+      <properties>
+        <skipTests>true</skipTests>
+      </properties>
+    </profile>
+    <profile>
+      <id>no-onnxruntime-gpu-on-aarch64</id>

Review Comment:
   Please state in the comment that `onnxruntime_gpu` (1.30.0) ships native 
libraries for linux-x64 and win-x64 only, unlike the CPU jar, which also covers 
linux-aarch64 and osx-aarch64. Otherwise the two skip profiles look broader 
than necessary.



##########
opennlp-core/opennlp-ml/opennlp-dl/src/test/java/opennlp/dl/vectors/TinyVectorModels.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 opennlp.dl.vectors;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.Objects;
+
+import ai.onnxruntime.OrtEnvironment;
+import ai.onnxruntime.OrtException;
+import ai.onnxruntime.OrtProvider;
+
+import opennlp.dl.InferenceOptions;
+
+import static org.junit.jupiter.api.Assumptions.abort;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * Test fixtures for the bundled tiny ONNX models. {@code tiny-vectors.onnx} 
(see
+ * {@code gen_tiny_vectors_model.py}) computes {@code output[b][t] = 
float(input_ids[b][t]) * W}
+ * with {@code W = [0.5, -1, 2]}; {@code tiny-pooled.onnx} (see {@code 
gen_tiny_pooled_model.py})
+ * adds a {@code sentence_embedding} output holding the sum of the ids times 
{@code W}.
+ */
+public final class TinyVectorModels {
+
+  /** The model with a token output only. */
+  public static final String TINY_VECTORS = "tiny-vectors.onnx";
+
+  /** The model with an additional {@code sentence_embedding} output. */
+  public static final String TINY_POOLED = "tiny-pooled.onnx";
+
+  private static final String VECTORS_RESOURCES = "/opennlp/dl/vectors/";
+
+  private TinyVectorModels() {
+  }
+
+  /**
+   * Copies a classpath resource into a directory. The resource is copied 
rather than resolved in
+   * place, because inside the {@code opennlp-dl} test-jar, as used by {@code 
opennlp-dl-gpu}, it
+   * is not a file.
+   *
+   * @param dir The directory to copy into.
+   * @param resource The absolute classpath name of the resource.
+   * @param name The file name of the copy.
+   * @return The copied file.
+   * @throws IOException Thrown if the resource cannot be copied.
+   */
+  public static File copy(final Path dir, final String resource, final String 
name)

Review Comment:
   `copy(...)` is a second copy of `LoadVocabTest.getResource(...)` 
(`LoadVocabTest.java:85`). Please keep one helper and use it in both places.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to