Skip to content

Commit

Permalink
Add CUDA acceleration (#933)
Browse files Browse the repository at this point in the history
* Add CUDA support

* Add Socket.flagChanged()
No more socket.setValue(socket.getValue()) hacks

* Allow CUDA versions to be optionally specified with -Pcuda
Or -PWITH_CUDA

* Add special CUDA socket
Socket is disabled when CUDA is unavailable

* Exit when CUDA runtime is required but not available

* Make normalize operation CUDA-accelerated
Slight speedup versus CPU

* Add CUDA acceleration to basic CV operations

* Clean up flagChanged in OutputSocketImpl

* Append 'cuda' to versions of GRIP with CUDA acceleration

* Add CUDA classes to dedicated cuda module
This lets us create CudaDetectors etc. before loading OpenCV in the core module
Move logger setup to its own class, since the core module may not load before app exits

* Make SafeShutdown take an ExitCode enum instead of raw int

* Use JNI loading to locate CUDA install

* Use WriteProperties task to save CUDA runtime properties
  • Loading branch information
SamCarlberg authored Apr 17, 2019
1 parent 142b1de commit 34afc3d
Show file tree
Hide file tree
Showing 103 changed files with 2,413 additions and 506 deletions.
28 changes: 24 additions & 4 deletions core/core.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.google.common.io.Files
import java.nio.charset.StandardCharsets

plugins {
id("java")
Expand All @@ -15,15 +17,21 @@ application {
val os = osdetector.classifier.replace("osx", "macosx").replace("x86_32", "x86")
val arch = osdetector.arch.replace("x86_64", "x64")

val withCuda = project.hasProperty("cuda") || project.hasProperty("WITH_CUDA")

if (withCuda) {
version = "$version-cuda"
}

dependencies {
api(project(":annotation"))
annotationProcessor(project(":annotation"))
api(group = "com.google.code.findbugs", name = "jsr305", version = "3.0.1")
api(group = "org.bytedeco", name = "javacv", version = "1.1")
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.0.0-1.1")
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.0.0-1.1", classifier = os)
api(group = "org.bytedeco", name = "javacv", version = "1.3")
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.4.3-1.4.3")
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.4.3-1.4.3", classifier = if (withCuda) "$os-gpu" else os)
api(group = "org.bytedeco.javacpp-presets", name = "videoinput", version = "0.200-1.1", classifier = os)
api(group = "org.bytedeco.javacpp-presets", name = "ffmpeg", version = "0.200-1.1", classifier = os)
api(group = "org.bytedeco.javacpp-presets", name = "ffmpeg", version = "0.200-1.3", classifier = os)
api(group = "org.python", name = "jython", version = "2.7.0")
api(group = "com.thoughtworks.xstream", name = "xstream", version = "1.4.10")
api(group = "org.apache.commons", name = "commons-lang3", version = "3.5")
Expand Down Expand Up @@ -59,6 +67,18 @@ tasks.withType<Jar>().configureEach {
}
}

val writeCudaPropertiesTask = tasks.register<WriteProperties>("writeCudaProperties") {
description = "Generates a file to let the GRIP runtime know if its using CUDA-accelerated OpenCV."
outputFile = buildDir.resolve("resources/main/edu/wpi/grip/core/CUDA.properties")
comment = "Information about CUDA requirements for the GRIP runtime"
property("edu.wpi.grip.cuda.enabled", withCuda)
property("edu.wpi.grip.cuda.version", "10.0") // opencv-presets 3.4.3-1.4.3 is compiled with CUDA 10.0
}

tasks.withType<JavaCompile>().configureEach {
dependsOn(writeCudaPropertiesTask)
}

tasks.withType<ShadowJar>().configureEach {
/* The icudt54b directory in Jython takes up 9 megabytes and doesn"t seem to do anything useful. */
exclude("org/python/icu/impl/data/icudt54b/")
Expand Down
74 changes: 18 additions & 56 deletions core/src/main/java/edu/wpi/grip/core/GripCoreModule.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package edu.wpi.grip.core;

import edu.wpi.grip.core.cuda.AccelerationMode;
import edu.wpi.grip.core.cuda.CudaDetector;
import edu.wpi.grip.core.cuda.CudaVerifier;
import edu.wpi.grip.core.cuda.NullAccelerationMode;
import edu.wpi.grip.core.cuda.NullCudaDetector;
import edu.wpi.grip.core.events.EventLogger;
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
import edu.wpi.grip.core.metrics.BenchmarkRunner;
Expand All @@ -23,22 +28,18 @@
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionContext;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.matcher.Matchers;
import com.google.inject.name.Names;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;

import javax.annotation.Nullable;

Expand All @@ -53,55 +54,6 @@ public class GripCoreModule extends AbstractModule {

private static final Logger logger = Logger.getLogger(GripCoreModule.class.getName());

// This is in a static initialization block so that we don't create a ton of
// log files when running tests
static {
//Set up the global level logger. This handles IO for all loggers.
final Logger globalLogger = LogManager.getLogManager().getLogger("");

try {
// Remove the default handlers that stream to System.err
for (Handler handler : globalLogger.getHandlers()) {
globalLogger.removeHandler(handler);
}

GripFileManager.GRIP_DIRECTORY.mkdirs();
final Handler fileHandler
= new FileHandler(GripFileManager.GRIP_DIRECTORY.getPath() + "/GRIP.log");

//Set level to handler and logger
fileHandler.setLevel(Level.INFO);
globalLogger.setLevel(Level.INFO);

// We need to stream to System.out instead of System.err
final StreamHandler sh = new StreamHandler(System.out, new SimpleFormatter()) {

@Override
public synchronized void publish(final LogRecord record) {
super.publish(record);
// For some reason this doesn't happen automatically.
// This will ensure we get all of the logs printed to the console immediately
// when running on a remote device.
flush();
}
};
sh.setLevel(Level.CONFIG);

globalLogger.addHandler(sh); // Add stream handler

globalLogger.addHandler(fileHandler); //Add the handler to the global logger

fileHandler.setFormatter(new SimpleFormatter()); //log in text, not xml

globalLogger.config("Configuration done."); //Log that we are done setting up the logger
globalLogger.config("GRIP Version: " + edu.wpi.grip.core.Main.class.getPackage()
.getImplementationVersion());

} catch (IOException exception) { //Something happened setting up file IO
throw new IllegalStateException("Failed to configure the Logger", exception);
}
}

/*
* This class should not be used in tests. Use GRIPCoreTestModule for tests.
*/
Expand Down Expand Up @@ -138,6 +90,16 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
bind(ConnectionValidator.class).to(Pipeline.class);
bind(Source.SourceFactory.class).to(Source.SourceFactoryImpl.class);

// Bind CUDA-specific stuff to default values
// These will be overridden by the GripCudaModule at app runtime, but this lets
// automated tests assume CPU-only operation modes
bind(CudaDetector.class).to(NullCudaDetector.class);
bind(AccelerationMode.class).to(NullAccelerationMode.class);
bind(CudaVerifier.class).in(Scopes.SINGLETON);
bind(Properties.class)
.annotatedWith(Names.named("cudaProperties"))
.toInstance(new Properties());

bind(InputSocket.Factory.class).to(InputSocketImpl.FactoryImpl.class);
bind(OutputSocket.Factory.class).to(OutputSocketImpl.FactoryImpl.class);
install(new FactoryModuleBuilder()
Expand Down
54 changes: 54 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/GripCudaModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package edu.wpi.grip.core;

import edu.wpi.grip.core.cuda.AccelerationMode;
import edu.wpi.grip.core.cuda.CudaAccelerationMode;
import edu.wpi.grip.core.cuda.CudaDetector;
import edu.wpi.grip.core.cuda.CudaVerifier;
import edu.wpi.grip.core.cuda.LoadingCudaDetector;
import edu.wpi.grip.core.cuda.NullAccelerationMode;

import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.name.Names;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GripCudaModule extends AbstractModule {

private static final Logger logger = Logger.getLogger(GripCudaModule.class.getName());
private static final String CUDA_ENABLED_KEY = "edu.wpi.grip.cuda.enabled";

@Override
protected void configure() {
bind(CudaDetector.class).to(LoadingCudaDetector.class);

Properties cudaProperties = getCudaProperties();
bind(Properties.class)
.annotatedWith(Names.named("cudaProperties"))
.toInstance(cudaProperties);

if (Boolean.valueOf(cudaProperties.getProperty(CUDA_ENABLED_KEY, "false"))) {
bind(AccelerationMode.class).to(CudaAccelerationMode.class);
} else {
bind(AccelerationMode.class).to(NullAccelerationMode.class);
}

bind(CudaVerifier.class).in(Scopes.SINGLETON);
}

private Properties getCudaProperties() {
try (InputStream resourceAsStream = getClass().getResourceAsStream("CUDA.properties")) {
Properties cudaProps = new Properties();
cudaProps.load(resourceAsStream);
return cudaProps;
} catch (IOException e) {
logger.log(Level.WARNING, "Could not read CUDA properties", e);
return new Properties();
}
}

}
70 changes: 70 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/Loggers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package edu.wpi.grip.core;

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;

public final class Loggers {

private Loggers() {
throw new UnsupportedOperationException("This is a utility class!");
}

/**
* Sets up loggers to print to stdout and to ~/GRIP/GRIP.log. This should only be called once
* in the application lifecycle, at startup.
*/
public static void setupLoggers() {
// Set up the global level logger. This handles IO for all loggers.
final Logger globalLogger = LogManager.getLogManager().getLogger("");

try {
// Remove the default handlers that stream to System.err
for (Handler handler : globalLogger.getHandlers()) {
globalLogger.removeHandler(handler);
}

GripFileManager.GRIP_DIRECTORY.mkdirs();
final Handler fileHandler
= new FileHandler(GripFileManager.GRIP_DIRECTORY.getPath() + "/GRIP.log");

//Set level to handler and logger
fileHandler.setLevel(Level.INFO);
globalLogger.setLevel(Level.INFO);

// We need to stream to System.out instead of System.err
final StreamHandler sh = new StreamHandler(System.out, new SimpleFormatter()) {

@Override
public synchronized void publish(final LogRecord record) {
super.publish(record);
// For some reason this doesn't happen automatically.
// This will ensure we get all of the logs printed to the console immediately
// when running on a remote device.
flush();
}
};
sh.setLevel(Level.CONFIG);

globalLogger.addHandler(sh); // Add stream handler

globalLogger.addHandler(fileHandler); //Add the handler to the global logger

fileHandler.setFormatter(new SimpleFormatter()); //log in text, not xml

globalLogger.config("Configuration done."); //Log that we are done setting up the logger
globalLogger.config("GRIP Version: " + edu.wpi.grip.core.Main.class.getPackage()
.getImplementationVersion());

} catch (IOException exception) { //Something happened setting up file IO
throw new IllegalStateException("Failed to configure the Logger", exception);
}
}

}
19 changes: 16 additions & 3 deletions core/src/main/java/edu/wpi/grip/core/Main.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.wpi.grip.core;

import edu.wpi.grip.core.cuda.CudaVerifier;
import edu.wpi.grip.core.events.ExceptionClearedEvent;
import edu.wpi.grip.core.events.ExceptionEvent;
import edu.wpi.grip.core.exception.GripServerException;
Expand Down Expand Up @@ -55,8 +56,20 @@ public class Main {
@SuppressWarnings("JavadocMethod")
public static void main(String[] args) throws IOException, InterruptedException {
new CoreCommandLineHelper().parse(args); // Check for help or version before doing anything else
final Injector injector = Guice.createInjector(Modules.override(new GripCoreModule(),
new GripFileModule(), new GripSourcesHardwareModule()).with(new GripNetworkModule()));
Loggers.setupLoggers();

// Verify CUDA before using the core module, since that will cause OpenCV to be loaded,
// which will crash the app if we use CUDA and it's not available
GripCudaModule cudaModule = new GripCudaModule();
CudaVerifier cudaVerifier = Guice.createInjector(cudaModule).getInstance(CudaVerifier.class);
cudaVerifier.verifyCuda();

final Injector injector = Guice.createInjector(
Modules.override(
new GripCoreModule(),
new GripFileModule(),
new GripSourcesHardwareModule()
).with(new GripNetworkModule(), cudaModule));
injector.getInstance(Main.class).start(args);
}

Expand All @@ -81,7 +94,7 @@ public void start(String[] args) throws IOException, InterruptedException {
gripServer.start();
} catch (GripServerException e) {
logger.log(Level.SEVERE, "The HTTP server could not be started", e);
SafeShutdown.exit(1);
SafeShutdown.exit(SafeShutdown.ExitCode.HTTP_SERVER_COULD_NOT_START);
}

if (pipelineRunner.state() == Service.State.NEW) {
Expand Down
Loading

0 comments on commit 34afc3d

Please sign in to comment.