diff --git a/README.md b/README.md index 3d7bb2546..0e0675d5e 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,39 @@ Introductory tutorials for JVector are available in [docs/tutorials](./docs/tuto The older step-by-step guide for JV can be found [here](./docs/legacy/jvector-step-by-step.md). New users should start with the tutorials mentioned earler, but the step-by-step guide contains useful commentary for advanced users. +## SIMD provider selection + +On Java 22+ with the Vector API enabled, JVector automatically prefers its Google +Highway native provider when the native library is available. Native libraries are +bundled for Linux x86-64 (SSE4.2 or newer) and Linux AArch64. Use these JVM options: + +```text +--add-modules jdk.incubator.vector +--enable-native-access=ALL-UNNAMED +``` + +Operations implemented by Highway use native kernels; remaining operations use the +inherited Panama/Java Vector API implementation with native vector storage. Native +ISA selection remains automatic and respects the existing `JVECTOR_MAX_ISA` cap. + +| JVM property | Effect | +| --- | --- | +| No provider property | Prefer native on Java 22+; use Panama when native is unavailable | +| `-Djvector.enable_native_vectorization=false` | Disable automatic native selection | +| `-Djvector.vectorization_provider=panama` | Explicitly select Panama | +| `-Djvector.vectorization_provider=native` | Explicitly require native; fail if it cannot load | +| `-Djvector.vectorization_provider=default` | Explicitly select the scalar provider | + +The explicit `jvector.vectorization_provider` property takes precedence over the +native-enable property. The former +`jvector.experimental.enable_native_vectorization` name is a deprecated compatibility +alias; the new `jvector.enable_native_vectorization` name takes precedence when both +are supplied. Both names default to `true` when neither is supplied. Provider +selection happens once per JVM. Java 20/21 uses +Panama when available. Older runtimes, or runtimes without the Vector API module, +retain the scalar fallback. Neither Panama nor native is enabled by these defaults +when the existing runtime compatibility checks reject SIMD support. + ## The research behind the algorithms * Foundational work: [HNSW](https://ieeexplore.ieee.org/abstract/document/8594636) and [DiskANN](https://suhasjs.github.io/files/diskann_neurips19.pdf) papers, and [a higher level explainer](https://www.datastax.com/guides/hierarchical-navigable-small-worlds) diff --git a/docs/release notes/4.0.2/751.feature.md b/docs/release notes/4.0.2/751.feature.md new file mode 100644 index 000000000..b415e36fa --- /dev/null +++ b/docs/release notes/4.0.2/751.feature.md @@ -0,0 +1,3 @@ +Google Highway native SIMD is no longer experimental and is preferred by default +on supported Java 22+ runtimes. `jvector.enable_native_vectorization` is the supported +flag; the former name remains a deprecated compatibility alias. diff --git a/jvector-base/src/main/java/io/github/jbellis/jvector/vector/VectorizationProvider.java b/jvector-base/src/main/java/io/github/jbellis/jvector/vector/VectorizationProvider.java index c25458b67..c29f092b8 100644 --- a/jvector-base/src/main/java/io/github/jbellis/jvector/vector/VectorizationProvider.java +++ b/jvector-base/src/main/java/io/github/jbellis/jvector/vector/VectorizationProvider.java @@ -120,9 +120,10 @@ static VectorizationProvider lookup(boolean testMode) { return new DefaultVectorizationProvider(); } - // check for jvector.experimental.enable_native_vectorization - // this is an experimental feature subject to change - if (Boolean.getBoolean("jvector.experimental.enable_native_vectorization")) { + // Native uses Java 22 FFM. The new setting takes precedence over its deprecated alias. + if (runtimeVersion >= 22 && Boolean.parseBoolean(System.getProperty( + "jvector.enable_native_vectorization", + System.getProperty("jvector.experimental.enable_native_vectorization", "true")))) { try { Class> clazz = Class.forName("io.github.jbellis.jvector.vector.NativeVectorizationProvider"); Constructor> ctor = clazz.getConstructor(); @@ -141,7 +142,7 @@ static VectorizationProvider lookup(boolean testMode) { } catch (UnsupportedOperationException uoe) { LOG.warning("Native vector API was not enabled. " + uoe.getMessage()); } catch (ClassNotFoundException e) { - LOG.warning("Java version does not support native vector API"); + LOG.fine("Native vector provider is not on the classpath; trying the Java Vector API."); } catch (InvocationTargetException e) { // unwrap, check for UnsupportedOperationException if (e.getCause() instanceof UnsupportedOperationException) { diff --git a/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorUtilSupport.java b/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorUtilSupport.java index 4b627a244..9230c2f9a 100644 --- a/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorUtilSupport.java +++ b/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorUtilSupport.java @@ -19,7 +19,6 @@ import java.lang.foreign.MemorySegment; import java.nio.ByteOrder; -import io.github.jbellis.jvector.annotations.Experimental; import io.github.jbellis.jvector.vector.cnative.NativeSimdOps; import io.github.jbellis.jvector.vector.types.ByteSequence; import io.github.jbellis.jvector.vector.types.VectorFloat; @@ -29,10 +28,8 @@ import jdk.incubator.vector.VectorSpecies; /** - * Experimental! - * VectorUtilSupport implementation that prefers native/Panama SIMD. + * VectorUtilSupport using Highway kernels and inherited Panama operations. */ -@Experimental final class NativeVectorUtilSupport extends PanamaVectorUtilSupport { public NativeVectorUtilSupport() {} diff --git a/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorizationProvider.java b/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorizationProvider.java index 213e53f9a..4d5e68182 100644 --- a/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorizationProvider.java +++ b/jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorizationProvider.java @@ -16,15 +16,13 @@ package io.github.jbellis.jvector.vector; -import io.github.jbellis.jvector.annotations.Experimental; import io.github.jbellis.jvector.vector.cnative.LibraryLoader; import io.github.jbellis.jvector.vector.types.VectorTypeSupport; /** - * Experimental! - * VectorizationProvider implementation that uses MemorySegment vectors and prefers native/Panama SIMD. + * VectorizationProvider using MemorySegment vectors, Highway kernels, and Panama + * for operations without native implementations. */ -@Experimental public class NativeVectorizationProvider extends VectorizationProvider { private final VectorUtilSupport vectorUtilSupport; private final VectorTypeSupport vectorTypeSupport; diff --git a/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProvider.java b/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProvider.java index 81a99aafc..b8d0b5c3e 100644 --- a/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProvider.java +++ b/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProvider.java @@ -98,13 +98,13 @@ public void testAssembleAndSum() { * -ea * --add-modules jdk.incubator.vector * --enable-native-access=ALL-UNNAMED - * -Djvector.experimental.enable_native_vectorization=true * } * * To run with panama support, use *
{@code
- * --ea
+ * -ea
* --add-modules jdk.incubator.vector
+ * -Djvector.vectorization_provider=panama
* }
*
* If {@code -DTest_RequireSpecificVectorizationProvider=}
diff --git a/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProviderSelection.java b/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProviderSelection.java
new file mode 100644
index 000000000..7919f8e6b
--- /dev/null
+++ b/jvector-tests/src/test/java/io/github/jbellis/jvector/vector/TestVectorizationProviderSelection.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * Licensed 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 io.github.jbellis.jvector.vector;
+
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/** Fresh JVMs exercise the real singleton without leaking provider properties between tests. */
+public class TestVectorizationProviderSelection {
+ private static final String ENABLE_NATIVE = "-Djvector.enable_native_vectorization=";
+ private static final String LEGACY_ENABLE_NATIVE = "-Djvector.experimental.enable_native_vectorization=";
+ private static final boolean HAS_VECTOR_MODULE = VectorizationProvider.vectorModulePresentAndReadable();
+
+ private static String classpath() {
+ return System.getProperty("surefire.test.class.path", System.getProperty("java.class.path"));
+ }
+
+ private static String withoutNativeArtifact() {
+ return Arrays.stream(classpath().split(java.util.regex.Pattern.quote(File.pathSeparator)))
+ .filter(entry -> !entry.replace('\\', '/').contains("/jvector-native/"))
+ .filter(entry -> !(Path.of(entry).getFileName().toString().startsWith("jvector-native-")
+ && entry.endsWith(".jar")))
+ .collect(Collectors.joining(File.pathSeparator));
+ }
+
+ private static boolean nativeAvailable() throws Exception {
+ if (Runtime.version().feature() < 22 || !HAS_VECTOR_MODULE
+ || !System.getProperty("os.name").startsWith("Linux")) return false;
+ String arch = System.getProperty("os.arch");
+ String resource = arch.equals("aarch64") ? "/libjvector-aarch64.so"
+ : (arch.equals("amd64") || arch.equals("x86_64")) ? "/libjvector-x86_64.so" : null;
+ if (resource == null) return false;
+ try {
+ return Class.forName("io.github.jbellis.jvector.vector.NativeVectorizationProvider", false,
+ TestVectorizationProviderSelection.class.getClassLoader()).getResource(resource) != null;
+ } catch (ClassNotFoundException e) { return false; }
+ }
+
+ private static String javaProvider() {
+ return HAS_VECTOR_MODULE ? "PanamaVectorizationProvider" : "DefaultVectorizationProvider";
+ }
+
+ private static String probe(String cp, boolean vectorModule, boolean success, String... properties) throws Exception {
+ var tmp = Files.createTempDirectory("provider-selection-");
+ Process child = null;
+ try {
+ var command = new ArrayList