/*-
*
* * Copyright 2015 Skymind,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 org.nd4j.linalg.factory;
import org.nd4j.context.Nd4jContext;
import org.nd4j.linalg.io.Resource;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.PrivilegedActionException;
import java.util.*;
/**
* An ND4j backend.
*
* A "backend" is also described here: http://nd4j.org/backend.html
*
* A backend also has 2 variables to be aware of.
* 1 is the environment variable, ND4J_DYNAMIC_LOAD_CLASSPATH
* This will define a uri path separated by ; where jars will be
* loaded from the path and dynamically loaded.
*
* The other is the system property:
* org.nd4j.backend.dynamicbackend
*
* This has the same use case but is for system properties.
* Of note here is that the system property takes loading precedence over
* the environment variable. If you want to just use the environment variable,
* don't define the system property.
*
* Both of these variables are for dynamically loading a backend relative to a path.
* The main idea here is for distributed environments like spark where
* you have multiple worker nodes with some having gpus and others not.
*
* When you define an environment variable on the server, you can
* have a hardware jar file load with respect to the node nd4j is installed on.
* The system property is mainly for flexibility and probably shouldn't be
* used in practice.
*
* @author eronwright
* @author Adam Gibson
* @author saudet
*
*/
public abstract class Nd4jBackend {
public static final int BACKEND_PRIORITY_CPU;
public static final int BACKEND_PRIORITY_GPU;
public final static String DYNAMIC_LOAD_CLASSPATH = "ND4J_DYNAMIC_LOAD_CLASSPATH";
public final static String DYNAMIC_LOAD_CLASSPATH_PROPERTY = "org.nd4j.backend.dynamicbackend";
private static final Logger log = LoggerFactory.getLogger(Nd4jBackend.class);
private static boolean triedDynamicLoad = false;
static {
int n = 0;
String s = System.getenv("BACKEND_PRIORITY_CPU");
if (s != null && s.length() > 0) {
try {
n = Integer.parseInt(s);
} catch (NumberFormatException e) {
throw new RuntimeException(e);
}
}
BACKEND_PRIORITY_CPU = n;
}
static {
int n = 100;
String s = System.getenv("BACKEND_PRIORITY_GPU");
if (s != null && s.length() > 0) {
try {
n = Integer.parseInt(s);
} catch (NumberFormatException e) {
throw new RuntimeException(e);
}
}
BACKEND_PRIORITY_GPU = n;
}
/**
* Returns true if the
* backend allows order to be specified
* on blas operations (cblas)
* @return true if the backend allows
* order to be specified on blas operations
*/
public abstract boolean allowsOrder();
/**
* Gets a priority number for the backend.
*
* Backends are loaded in priority order (highest first).
* @return a priority number.
*/
public abstract int getPriority();
/**
* Determines whether a given backend is available in the current environment.
* @return true if the backend is available; false otherwise.
*/
public abstract boolean isAvailable();
/**
* Returns true if the backend can
* run on the os or not
* @return
*/
public abstract boolean canRun();
/**
* Get the configuration resource
* @return
*/
public abstract Resource getConfigurationResource();
/**
* Get the actual (concrete/implementation) class for standard INDArrays for this backend
*/
public abstract Class getNDArrayClass();
/**
* Get the actual (concrete/implementation) class for complex INDArrays for this backend
*/
public abstract Class getComplexNDArrayClass();
/**
* Loads the best available backend.
* @return
*/
public static Nd4jBackend load() throws NoAvailableBackendException {
List<Nd4jBackend> backends = new ArrayList<>(1);
ServiceLoader<Nd4jBackend> loader = ServiceLoader.load(Nd4jBackend.class);
try {
Iterator<Nd4jBackend> backendIterator = loader.iterator();
while (backendIterator.hasNext())
backends.add(backendIterator.next());
} catch (ServiceConfigurationError serviceError) {
// a fatal error due to a syntax or provider construction error.
// backends mustn't throw an exception during construction.
throw new RuntimeException("failed to process available backends", serviceError);
}
Collections.sort(backends, new Comparator<Nd4jBackend>() {
@Override
public int compare(Nd4jBackend o1, Nd4jBackend o2) {
// high-priority first
return o2.getPriority() - o1.getPriority();
}
});
for (Nd4jBackend backend : backends) {
boolean available = false;
String error = null;
try {
available = backend.isAvailable();
} catch (Exception e) {
error = e.getMessage();
}
if (!available) {
log.warn("Skipped [{}] backend (unavailable): {}", backend.getClass().getSimpleName(), error);
continue;
}
try {
Nd4jContext.getInstance().updateProperties(backend.getConfigurationResource().getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
log.info("Loaded [{}] backend", backend.getClass().getSimpleName());
return backend;
}
log.trace("Service loader failed...falling back to reflection");
Set<Class<? extends Nd4jBackend>> clazzes = new Reflections("org.nd4j").getSubTypesOf(Nd4jBackend.class);
List<Nd4jBackend> reflectionBackends = new ArrayList<>();
for (Class<? extends Nd4jBackend> backend : clazzes) {
try {
Nd4jBackend load = backend.newInstance();
reflectionBackends.add(load);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Collections.sort(reflectionBackends, new Comparator<Nd4jBackend>() {
@Override
public int compare(Nd4jBackend o1, Nd4jBackend o2) {
// high-priority first
return o2.getPriority() - o1.getPriority();
}
});
for (Nd4jBackend backend : reflectionBackends) {
boolean available = false;
String error = null;
try {
available = backend.isAvailable();
} catch (Exception e) {
error = e.getMessage();
}
if (!available) {
log.warn("Skipped [{}] backend (unavailable): {}", backend.getClass().getSimpleName(), error);
continue;
}
try {
Nd4jContext.getInstance().updateProperties(backend.getConfigurationResource().getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
log.info("Loaded [{}] backend", backend.getClass().getSimpleName());
return backend;
}
//need to dynamically load jars and recall, note that we do this right before the backend loads.
//An existing backend should take precedence over
//ones being dynamically discovered.
//Note that we prioritize jvm properties first, followed by environment variables.
String[] jarUris;
if (System.getProperties().containsKey(DYNAMIC_LOAD_CLASSPATH_PROPERTY) && !triedDynamicLoad) {
jarUris = System.getProperties().getProperty(DYNAMIC_LOAD_CLASSPATH_PROPERTY).split(";");
} else if (System.getenv().containsKey(DYNAMIC_LOAD_CLASSPATH) && !triedDynamicLoad) {
jarUris = System.getenv(DYNAMIC_LOAD_CLASSPATH).split(";");
}
else
throw new NoAvailableBackendException(
"Please ensure that you have an nd4j backend on your classpath. Please see: http://nd4j.org/getstarted.html");
triedDynamicLoad = true;
//load all the discoverable uris and try to load the backend again
for (String uri : jarUris) {
loadLibrary(new File(uri));
}
return load();
}
/**
* Adds the supplied Java Archive library to java.class.path. This is benign
* if the library is already loaded.
* @param jar the jar file to add
* @throws NoAvailableBackendException
*/
public static synchronized void loadLibrary(File jar) throws NoAvailableBackendException {
try {
/*We are using reflection here to circumvent encapsulation; addURL is not public*/
java.net.URLClassLoader loader = (java.net.URLClassLoader) ClassLoader.getSystemClassLoader();
java.net.URL url = jar.toURI().toURL();
/*Disallow if already loaded*/
for (java.net.URL it : java.util.Arrays.asList(loader.getURLs())) {
if (it.equals(url)) {
return;
}
}
java.lang.reflect.Method method =
java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(loader, new Object[] {url});
} catch (final java.lang.NoSuchMethodException | java.lang.IllegalAccessException
| java.net.MalformedURLException | java.lang.reflect.InvocationTargetException e) {
throw new NoAvailableBackendException(e);
}
}
/**
*
* @return
* @throws IOException
*/
public Properties getProperties() throws IOException {
return getContext().getConf();
}
/**
*
* @return
* @throws IOException
*/
public Nd4jContext getContext() throws IOException {
return Nd4jContext.getInstance();
}
@Override
public String toString() {
return getClass().getName();
}
@SuppressWarnings("serial")
public static class NoAvailableBackendException extends Exception {
public NoAvailableBackendException(String s) {
super(s);
}
/**
* Constructs a new exception with the specified cause and a detail
* message of <tt>(cause==null ? null : cause.toString())</tt> (which
* typically contains the class and detail message of <tt>cause</tt>).
* This constructor is useful for exceptions that are little more than
* wrappers for other throwables (for example, {@link
* PrivilegedActionException}).
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public NoAvailableBackendException(Throwable cause) {
super(cause);
}
}
}