This is an automated email from the ASF dual-hosted git repository.

mawiesne pushed a commit to branch opennlp-2.x
in repository https://gitbox.apache.org/repos/asf/opennlp.git


The following commit(s) were added to refs/heads/opennlp-2.x by this push:
     new 078377921 [2.x]: GH-1390: Let SentenceVectorsDL run on a GPU (#1496)
078377921 is described below

commit 078377921e0220613a085e636bf9bd6a34b89518
Author: Richard Zowalla <[email protected]>
AuthorDate: Sun Oct 18 17:06:24 2026 +0200

    [2.x]: GH-1390: Let SentenceVectorsDL run on a GPU (#1496)
---
 .gitignore                                         |   2 +
 opennlp-dl-gpu/pom.xml                             |  31 +++++++
 .../java/opennlp/dl/vectors/SentenceVectorsDL.java |  25 +++++-
 .../SentenceVectorsDLInferenceOptionsTest.java     |  90 +++++++++++++++++++++
 .../opennlp/dl/vectors/gen_tiny_vectors_model.py   |  57 +++++++++++++
 .../resources/opennlp/dl/vectors/tiny-vectors.onnx | Bin 0 -> 373 bytes
 rat-excludes                                       |   3 +
 7 files changed, 207 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 965000f40..e976fdc77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,6 @@ nb-configuration.xml
 *.DS_Store
 .checkstyle
 *.onnx
+# Committed test fixture, generated once by the script beside it, not build 
output.
+!opennlp-dl/src/test/resources/opennlp/dl/vectors/tiny-vectors.onnx
 vocab.txt
diff --git a/opennlp-dl-gpu/pom.xml b/opennlp-dl-gpu/pom.xml
index e66f0f62b..e617868b7 100644
--- a/opennlp-dl-gpu/pom.xml
+++ b/opennlp-dl-gpu/pom.xml
@@ -69,6 +69,13 @@
       <version>${project.version}</version>
       <type>test-jar</type>
       <scope>test</scope>
+      <!-- Keep the CPU onnxruntime off the test classpath; this module uses 
onnxruntime_gpu. -->
+      <exclusions>
+        <exclusion>
+          <groupId>com.microsoft.onnxruntime</groupId>
+          <artifactId>onnxruntime</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
   </dependencies>
 
@@ -86,7 +93,31 @@
     </plugins>
   </build>
 
+  <!-- onnxruntime_gpu ships native libraries for linux-x64 and win-x64 only, 
unlike the
+       CPU onnxruntime jar, which also covers macOS and Linux aarch64. The 
tests cannot run there. -->
   <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>
+      <activation>
+        <os>
+          <arch>aarch64</arch>
+        </os>
+      </activation>
+      <properties>
+        <skipTests>true</skipTests>
+      </properties>
+    </profile>
     <profile>
       <id>eval-tests</id>
       <properties>
diff --git a/opennlp-dl/src/main/java/opennlp/dl/vectors/SentenceVectorsDL.java 
b/opennlp-dl/src/main/java/opennlp/dl/vectors/SentenceVectorsDL.java
index 06ba5cbd6..93658b8d6 100644
--- a/opennlp-dl/src/main/java/opennlp/dl/vectors/SentenceVectorsDL.java
+++ b/opennlp-dl/src/main/java/opennlp/dl/vectors/SentenceVectorsDL.java
@@ -30,6 +30,7 @@ import ai.onnxruntime.OrtException;
 import ai.onnxruntime.OrtSession;
 
 import opennlp.dl.AbstractDL;
+import opennlp.dl.InferenceOptions;
 import opennlp.dl.Tokens;
 import opennlp.tools.tokenize.Tokenizer;
 
@@ -62,9 +63,31 @@ public class SentenceVectorsDL extends AbstractDL {
    */
   public SentenceVectorsDL(final File model, final File vocabulary)
       throws OrtException, IOException {
+    this(model, vocabulary, new InferenceOptions());
+  }
+
+  /**
+   * Instantiates a {@link SentenceVectorsDL sentence vector generator} using 
ONNX models.
+   * With {@link InferenceOptions#isGpu()} set, the model runs on the CUDA 
device
+   * {@link InferenceOptions#getGpuDeviceId()}; the other options are not used.
+   *
+   * @param model The file name of a sentence vectors ONNX model.
+   * @param vocabulary The file name of the vocabulary file for the model.
+   * @param inferenceOptions {@link InferenceOptions} to control the inference.
+   *
+   * @throws OrtException Thrown if the {@code model} cannot be loaded, or if 
a GPU is requested
+   *     and the CUDA execution provider or the device is not available.
+   * @throws IOException Thrown if errors occurred loading the {@code model} 
or {@code vocabulary}.
+   */
+  public SentenceVectorsDL(final File model, final File vocabulary,
+      final InferenceOptions inferenceOptions) throws OrtException, 
IOException {
 
     env = OrtEnvironment.getEnvironment();
-    session = env.createSession(model.getPath(), new 
OrtSession.SessionOptions());
+    final OrtSession.SessionOptions sessionOptions = new 
OrtSession.SessionOptions();
+    if (inferenceOptions.isGpu()) {
+      sessionOptions.addCUDA(inferenceOptions.getGpuDeviceId());
+    }
+    session = env.createSession(model.getPath(), sessionOptions);
     vocab = loadVocab(vocabulary);
     tokenizer = createTokenizer(vocab);
 
diff --git 
a/opennlp-dl/src/test/java/opennlp/dl/vectors/SentenceVectorsDLInferenceOptionsTest.java
 
b/opennlp-dl/src/test/java/opennlp/dl/vectors/SentenceVectorsDLInferenceOptionsTest.java
new file mode 100644
index 000000000..44928fa31
--- /dev/null
+++ 
b/opennlp-dl/src/test/java/opennlp/dl/vectors/SentenceVectorsDLInferenceOptionsTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 ai.onnxruntime.OrtException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import opennlp.dl.InferenceOptions;
+
+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 InferenceOptions} constructor of {@link SentenceVectorsDL} 
with
+ * {@code tiny-vectors.onnx} (see {@code gen_tiny_vectors_model.py}), whose 
output vector of a
+ * token is its vocabulary id times {@code [0.5, -1, 2]}.
+ */
+class SentenceVectorsDLInferenceOptionsTest {
+
+  /** A CUDA device id that no machine has. */
+  private static final int UNUSABLE_DEVICE_ID = 99;
+
+  private static final String SENTENCE = "the dog";
+
+  @TempDir
+  Path dir;
+
+  /** Without a GPU request the options constructor computes the vectors of 
the default one. */
+  @Test
+  void testCpuOptionsMatchTheDefaultConstructor() throws IOException, 
OrtException {
+    final File model = copy("/opennlp/dl/vectors/tiny-vectors.onnx", 
"tiny-vectors.onnx");
+    final File vocab = copy("/opennlp/dl/vocab-plain.txt", "vocab.txt");
+    try (SentenceVectorsDL defaults = new SentenceVectorsDL(model, vocab);
+         SentenceVectorsDL options = new SentenceVectorsDL(model, vocab, new 
InferenceOptions())) {
+      final float[] expected = defaults.getVectors(SENTENCE);
+      assertEquals(3, expected.length);
+      assertArrayEquals(expected, options.getVectors(SENTENCE));
+    }
+  }
+
+  /** A GPU request fails when CUDA or the device is not available instead of 
using the CPU. */
+  @Test
+  void testGpuRequestOnAnUnusableDeviceFails() throws IOException {
+    final File model = copy("/opennlp/dl/vectors/tiny-vectors.onnx", 
"tiny-vectors.onnx");
+    final File vocab = copy("/opennlp/dl/vocab-plain.txt", "vocab.txt");
+    final InferenceOptions options = new InferenceOptions();
+    options.setGpu(true);
+    options.setGpuDeviceId(UNUSABLE_DEVICE_ID);
+    assertThrows(OrtException.class, () -> new SentenceVectorsDL(model, vocab, 
options).close());
+  }
+
+  /**
+   * Copies a test resource into the temporary directory.
+   *
+   * @param resource The classpath resource.
+   * @param name The file name in the temporary directory.
+   * @return The copied file.
+   * @throws IOException Thrown if the resource cannot be read or written.
+   */
+  private File copy(final String resource, final String name) throws 
IOException {
+    final Path target = dir.resolve(name);
+    try (InputStream in = getClass().getResourceAsStream(resource)) {
+      Files.copy(in, target);
+    }
+    return target.toFile();
+  }
+}
diff --git 
a/opennlp-dl/src/test/resources/opennlp/dl/vectors/gen_tiny_vectors_model.py 
b/opennlp-dl/src/test/resources/opennlp/dl/vectors/gen_tiny_vectors_model.py
new file mode 100644
index 000000000..d2d79b4ad
--- /dev/null
+++ b/opennlp-dl/src/test/resources/opennlp/dl/vectors/gen_tiny_vectors_model.py
@@ -0,0 +1,57 @@
+# 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.
+
+# Generates tiny-vectors.onnx, the deterministic model behind 
SentenceVectorsDLInferenceOptionsTest.
+#
+# The graph computes output[b][t][d] = float(input_ids[b][t]) * W[0][d] with
+# W = [[0.5, -1.0, 2.0]], so the vector at any token position is that token's 
vocabulary id
+# times W, hand-computable in the test. It declares the same three inputs a 
BERT-style
+# encoder declares (input_ids, attention_mask, token_type_ids; the latter two 
are accepted
+# and ignored) and one output of shape [batch, tokens, 3] so the hidden 
dimension is static
+# in the model metadata.
+#
+# Regenerate with: python3 gen_tiny_vectors_model.py   (requires the onnx 
package)
+
+import numpy as np
+import onnx
+from onnx import TensorProto, helper, numpy_helper
+
+W = np.array([[0.5, -1.0, 2.0]], dtype=np.float32)
+
+cast = helper.make_node("Cast", ["input_ids"], ["ids_float"], 
to=TensorProto.FLOAT)
+unsqueeze = helper.make_node("Unsqueeze", ["ids_float", "axes"], ["ids_3d"])
+matmul = helper.make_node("MatMul", ["ids_3d", "w"], ["last_hidden_state"])
+
+
+def encoder_input(name):
+    return helper.make_tensor_value_info(name, TensorProto.INT64, ["batch", 
"tokens"])
+
+
+graph = helper.make_graph(
+    [cast, unsqueeze, matmul],
+    "tiny-vectors",
+    [encoder_input("input_ids"), encoder_input("attention_mask"),
+     encoder_input("token_type_ids")],
+    [helper.make_tensor_value_info(
+        "last_hidden_state", TensorProto.FLOAT, ["batch", "tokens", 3])],
+    [numpy_helper.from_array(np.array([2], dtype=np.int64), name="axes"),
+     numpy_helper.from_array(W, name="w")],
+)
+
+model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)])
+model.ir_version = 8
+onnx.checker.check_model(model)
+onnx.save(model, "tiny-vectors.onnx")
+print("wrote tiny-vectors.onnx,", len(model.SerializeToString()), "bytes")
diff --git a/opennlp-dl/src/test/resources/opennlp/dl/vectors/tiny-vectors.onnx 
b/opennlp-dl/src/test/resources/opennlp/dl/vectors/tiny-vectors.onnx
new file mode 100644
index 000000000..7d63c9132
Binary files /dev/null and 
b/opennlp-dl/src/test/resources/opennlp/dl/vectors/tiny-vectors.onnx differ
diff --git a/rat-excludes b/rat-excludes
index 3ad3ae4df..ed5b4bd2d 100644
--- a/rat-excludes
+++ b/rat-excludes
@@ -58,3 +58,6 @@ src/test/resources/*.info
 
 <!-- other licence -->
 src/main/java/opennlp/tools/stemmer/snowball/*.java
+
+<!-- opennlp-dl tiny ONNX test model (project-generated binary, see 
gen_tiny_vectors_model.py next to it) -->
+src/test/resources/opennlp/dl/vectors/tiny-vectors.onnx

Reply via email to