Initial commit of GPU code.

This commit is contained in:
Pedro Martínez Mediano 2016-03-13 01:15:19 +00:00
parent 13952302c1
commit 5074c1b6f7
15 changed files with 2681 additions and 815 deletions

BIN
.gitignore vendored Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="Java Information Dynamics Toolkit">
<project basedir="." default="build" name="Java Information Dynamics Toolkit" xmlns:if="ant:if">
<description>
Build file for the Java Information Dynamics Toolkit
</description>
@ -19,18 +19,25 @@
<property name="reports.tests" location="${unittestsouttoplevel}/reports"/>
<property name="javadocsdir" location="javadocs"/>
<property name="versionfile" value="version-${version}.txt"/>
<!-- To enable GPU code, uncomment from here... -->
<property name="enablegpu" value="true"/>
<!-- ...to here -->
<path id="project.classpath">
<pathelement location="bin"/>
</path>
<path id="apache-classpath">
<pathelement path="./share/commons-math3-3.5.jar"/>
</path>
<!-- Make required directories -->
<target name="init" description="Create the compiled code directories">
<mkdir dir="${bin}"/>
<mkdir dir="${unittestsbin}"/>
<mkdir dir="${reports.tests}"/>
</target>
<!-- Compile the java toolkit -->
<target name="compile" depends="init" description="compile the source and unittests">
<!-- Compile to Java 6 to provide compatibility for users with older JREs.
@ -43,7 +50,13 @@
ignore the warning, and I don't want to bother installing Java 6 just to compile
like this. I'll endeavour not to use JDK 7 libraries so as not to cause
any issues here ... -->
<javac srcdir="${src}" destdir="${bin}" includeAntRuntime="false" target="1.6" source="1.6"/>
<javac srcdir="${src}" destdir="${bin}" includeAntRuntime="false" target="1.6" source="1.6">
<classpath refid="apache-classpath"/>
</javac>
<!-- Compiling Cpp code -->
<exec executable="make" dir="${src}/infodynamics/measures/continuous/kraskov/cuda/" if:set="enablegpu"/>
</target>
<!-- Jar the toolkit -->
@ -55,17 +68,19 @@
<!-- Compile and run the JUnit tests -->
<target name="junit" depends="compile" description="Run the junit tests and make sure they compile">
<!-- Compile the junit tests first -->
<javac destdir="${unittestsbin}" includeAntRuntime="true">
<javac destdir="${unittestsbin}" includeAntRuntime="true" debug="true">
<!-- Need includeAntRuntime=true if you want to pick up junit.jar and ant-junit.jar in ANT_HOME/lib;
Otherwise you will need to set the classpath to include these here. -->
<src path="${unittestssrc}"/>
<classpath refid="project.classpath"/>
<classpath refid="apache-classpath"/>
</javac>
<!-- Run the junit tests and make sure they complete ok -->
<junit printsummary="yes" showoutput="yes" haltonfailure="yes" haltonerror="yes" includeantruntime="true">
<junit printsummary="yes" showoutput="yes" fork="true" forkmode="once" haltonfailure="yes" haltonerror="yes" includeantruntime="true">
<classpath>
<path refid="project.classpath"/>
<pathelement path="${unittestsbin}"/>
<pathelement location="${basedir}/clover.jar"/>
</classpath>
<formatter type="plain"/>
<batchtest todir="${reports.tests}"> <!-- Writes full reports with stdout and stderr to ${reports.tests} -->
@ -138,6 +153,21 @@
***********************************
-->
<!-- Compile and jar the toolkit with debug symbols -->
<target name="debug" depends="init" description="compile and jar with debug symbols">
<echo message="Compiling for debug"/>
<javac srcdir="${src}" destdir="${bin}" includeAntRuntime="false" target="1.6" source="1.6" debug="true">
<classpath refid="apache-classpath"/>
</javac>
<!-- Compiling Cpp code -->
<exec executable="make" dir="${src}/infodynamics/measures/continuous/kraskov/cuda/" if:set="enablegpu">
<env key="DEBUG" value="1"/>
</exec>
<jar jarfile="${jarplainname}" basedir="${bin}"/>
</target>
<!-- Developer build - creates readme and version files -->
<target name="readmefiles" description="developer: create the readme and version files from templates">
<tstamp>

View File

@ -0,0 +1,18 @@
#!/bin/bash
# Make sure the latest example source file is compiled:
# Note: this does not nulify our claim that you can compile the code
# once and then only change the properties file - we only compile here
# every time this script is run so that we can capture any changes the user
# made to the demo source code. The demo as written should be compiled once
# then one can make dynamic changes to the props file and simply
# run the class file without recompiling.
javac -classpath "../../infodynamics.jar" "infodynamics/demos/Example10GPUBenchmark.java"
# Run the example, feeding in the file names as the command line argument
file1=WhiteNoise.txt
file2=Correlated2D.txt
java -classpath ".:../../infodynamics.jar" infodynamics.demos.Example10GPUBenchmark $file1 $file2
# Plot the results
python plotExample10BenchmarkResults.py $file1 $file2

View File

@ -0,0 +1,216 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2012, Joseph T. Lizier
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package infodynamics.demos;
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
import infodynamics.measures.continuous.kraskov.MutualInfoCalculatorMultiVariateKraskov1;
import infodynamics.measures.continuous.kraskov.MutualInfoCalculatorMultiVariateKraskov2;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.RandomGenerator;
import infodynamics.utils.ParsedProperties;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
import java.lang.Math;
/**
* = Example 10 - GPU benchmark script =
*
* This class is used to demonstrate how the GPU module is activated and
* includes a benchmark to test how fast it is, in comparison with its CPU
* counterpart.
*
* If run from Linux, the script <code>plotExample10BenchmarkResults.py</code>
* will use the Python library matplotlib to plot the results of the benchmark.
*
* @author Pedro AM Mediano
*
*/
public class Example10GPUBenchmark {
/**
* Calculate MI for the given multivariate time series using the Kraskov
* algorithm, either with CPU or GPU implementation.
*
* @param src Source variable for MI calculation
* @param tgt Target variable for MI calculation
* @param useGPU Whether or not to use the GPU for this calculation
* @return An array with two doubles. The first component is the time in ms
* it took to run the calculation and the second component is the actual
* value of the calculated MI.
*/
public static double[] runEstimator(double[][] src, double[][] tgt, boolean useGPU) throws Exception {
MutualInfoCalculatorMultiVariate miCalc = new MutualInfoCalculatorMultiVariateKraskov1();
if (useGPU)
miCalc.setProperty("USE_GPU", "true");
miCalc.setProperty("NOISE_LEVEL_TO_ADD", "0");
miCalc.setProperty("NORMALISE", "false");
miCalc.setProperty("k", "4");
miCalc.initialise(src[0].length, tgt[0].length);
miCalc.setObservations(src, tgt);
double[] timeAndValue = new double[2];
long startTime = System.nanoTime();
timeAndValue[1] = miCalc.computeAverageLocalOfObservations();
timeAndValue[0] = (System.nanoTime() - startTime)/1000000.0;
return timeAndValue;
}
/**
* Benchmark on a small predefined dataset, for debugging purposes.
*/
public static void Benchmark0() throws Exception {
double[][] src = new double[][] {{0}, {1}, {2}, {3}, {4}, {5}, {6}};
double[][] tgt = new double[][] {{0}, {0}, {0}, {0}, {0}, {0}, {0}};
System.out.printf("CPU value: %f\n", runEstimator(src, tgt, false)[1]);
System.out.printf("GPU value: %f\n", runEstimator(src, tgt, true)[1]);
}
/**
* Benchmark on white Gaussian data of arbitrary dimension.
*/
public static void Benchmark1(String filename) throws Exception {
boolean append_to_file = false;
FileWriter write = new FileWriter(filename, append_to_file);
PrintWriter print_line = new PrintWriter(write);
int[] N_vec = new int[] {500, 1000, 2000, 4000};
int[] D_vec = new int[] {1, 3, 5};
RandomGenerator rg = new RandomGenerator();
int nb_repetitions = 5;
for (int i = 0; i < N_vec.length; i++) {
for (int j = 0; j < D_vec.length; j++) {
int N = N_vec[i];
int D = D_vec[j];
double[][] src = rg.generateNormalData(N, D, 0, 1);
double[][] tgt = rg.generateNormalData(N, D, 0, 1);
double cpuTime = 0, gpuTime = 0;
for (int r = 0; r < nb_repetitions; r++) {
// CPU calculation
double[] cpuVals = runEstimator(src, tgt, false);
cpuTime += cpuVals[0];
// GPU calculation
double[] gpuVals = runEstimator(src, tgt, true);
gpuTime += gpuVals[0];
if (Math.abs(cpuVals[1] - gpuVals[1]) > 1e-4) {
System.out.printf("Values differ. CPU: %f, GPU: %f\n", cpuVals[1], gpuVals[1]);
}
}
print_line.printf("%d\t%d\t%f\t%f\n", N, D,
cpuTime/nb_repetitions, gpuTime/nb_repetitions);
}
}
print_line.close();
return;
}
/**
* Benchmark on 2D correlated Gaussian.
*
* Reference:
*
* http://math.stackexchange.com/questions/446093/generate-correlated-normal-random-variables
*
*/
public static void Benchmark2(String filename) throws Exception {
boolean append_to_file = false;
FileWriter write = new FileWriter(filename, append_to_file);
PrintWriter print_line = new PrintWriter(write);
int[] N_vec = new int[] {500, 1000, 2000, 4000};
double[] corr_vec = new double[] {0, 0.2, 0.4, 0.6};
RandomGenerator rg = new RandomGenerator();
int nb_repetitions = 5;
for (int i = 0; i < N_vec.length; i++) {
for (int j = 0; j < corr_vec.length; j++) {
int N = N_vec[i];
double corr = corr_vec[j];
double[] r1 = rg.generateNormalData(N, 0, 1);
double[] r2 = rg.generateNormalData(N, 0, 1);
double[][] src = new double[N][1];
double[][] tgt = new double[N][1];
for (int t = 0; t < N; t++) {
src[t][0] = r1[t];
tgt[t][0] = corr*src[t][0] + Math.sqrt(1 - corr*corr)*r2[t];
}
double cpuTime = 0, gpuTime = 0;
for (int r = 0; r < nb_repetitions; r++) {
// CPU calculation
double[] cpuVals = runEstimator(src, tgt, false);
cpuTime += cpuVals[0];
// GPU calculation
double[] gpuVals = runEstimator(src, tgt, true);
gpuTime += gpuVals[0];
if (Math.abs(cpuVals[1] - gpuVals[1]) > 1e-4) {
System.out.printf("Values differ. CPU: %f, GPU: %f\n", cpuVals[1], gpuVals[1]);
}
}
// Save time in milliseconds
print_line.printf("%d\t%f\t%f\t%f\n", N, corr,
cpuTime/nb_repetitions, gpuTime/nb_repetitions);
}
}
print_line.close();
return;
}
/**
* Run several of the benchmarks above and write the results to a
* .txt file.
*
* @param args List of file names to save the results of the benchmark.
*/
public static void main(String[] args) throws Exception {
// Benchmark0();
Benchmark1(args[0]);
Benchmark2(args[1]);
return;
}
}

View File

@ -0,0 +1,47 @@
#!/usr/bin/python
"""
plotExample10BenchmarkResults.py: Show 3D plots with the result of benchmarking
the CPU and GPU implementations of the KSG algorithm for mutual information.
"""
__author__ = "Pedro AM Mediano"
__email__ = "pmediano@imperial.ac.uk"
__license__ = "GPL"
import sys
import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits.mplot3d import Axes3D
## Load files
filename1 = sys.argv[1]
filename2 = sys.argv[2]
res1 = np.loadtxt(filename1)
res2 = np.loadtxt(filename2)
## There is a bug with the legends of 3D scatterplots, so we have to save
# the plot style for the legend separately
img1 = pl.Line2D([0],[0], linestyle="none", c='r', marker='o')
img2 = pl.Line2D([0],[0], linestyle="none", c='b', marker='^')
## Plot times for first benchmark
fig1 = pl.figure()
ax1 = fig1.add_subplot(111, projection='3d')
ax1.scatter(np.log10(res1[:,0]), res1[:,1], res1[:,2], c='r', marker='o')
ax1.scatter(np.log10(res1[:,0]), res1[:,1], res1[:,3], c='b', marker='^')
ax1.legend([img1, img2], ['CPU', 'GPU'], numpoints = 1)
ax1.set_xlabel(r'$\log_{10} N$'); ax1.set_ylabel('D'); ax1.set_zlabel('Time [ms]');
ax1.set_title(filename1.partition(".")[0])
## Plot times for second benchmark
fig2 = pl.figure()
ax2 = fig2.add_subplot(111, projection='3d')
ax2.scatter(np.log10(res2[:,0]), res2[:,1], res2[:,2], c='r', marker='o')
ax2.scatter(np.log10(res2[:,0]), res2[:,1], res2[:,3], c='b', marker='^')
ax2.legend([img1, img2], ['CPU', 'GPU'], numpoints = 1)
ax2.set_xlabel(r'$\log_{10} N$'); ax2.set_ylabel(r'$\rho$'); ax2.set_zlabel('Time [ms]');
ax2.set_title(filename2.partition(".")[0])
pl.show()

View File

@ -23,7 +23,9 @@ import java.util.PriorityQueue;
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
import infodynamics.utils.MathsUtils;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.NeighbourNodeData;
import infodynamics.utils.EuclideanUtils;
/**
* <p>Computes the differential mutual information of two given multivariate sets of
@ -80,7 +82,7 @@ public class MutualInfoCalculatorMultiVariateKraskov1
// First element in the PQ is the kth NN,
// and epsilon = kthNnData.distance
NeighbourNodeData kthNnData = nnPQ.poll();
// Count the number of points whose x distance is less
// than eps, and whose y distance is less than
// epsilon = kthNnData.distance
@ -88,7 +90,7 @@ public class MutualInfoCalculatorMultiVariateKraskov1
t, kthNnData.distance, dynCorrExclTime);
int n_y = nnSearcherDest.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
sumNx += n_x;
sumNy += n_y;
// And take the digammas:
@ -103,7 +105,7 @@ public class MutualInfoCalculatorMultiVariateKraskov1
localMi[t-startTimePoint] = digammaK - digammaNxPlusOne - digammaNyPlusOne + digammaN;
}
}
if (debug) {
Calendar rightNow2 = Calendar.getInstance();
long endTime = rightNow2.getTimeInMillis();
@ -119,4 +121,5 @@ public class MutualInfoCalculatorMultiVariateKraskov1
return new double[] {sumDiGammas, sumNx, sumNy};
}
}
}

View File

@ -129,4 +129,5 @@ public class MutualInfoCalculatorMultiVariateKraskov2
return new double[] {sumDiGammas, sumNx, sumNy};
}
}
}

View File

@ -0,0 +1,39 @@
## Set these if the CUDA Toolkit is not in your PATH
# Location of the CUDA Toolkit binaries and libraries
# CUDA_PATH ?= /usr/local/cuda
# CUDA_INC_PATH ?= $(CUDA_PATH)/include
# CUDA_LIB_PATH ?= $(CUDA_PATH)/lib
# CUDA_BIN_PATH ?= $(CUDA_PATH)/bin
# Common binaries and flags. TODO: the architecture flag may not be necessary
NVCC ?= nvcc
GCC ?= g++
CCFLAGS ?= -O3
# NVCCFLAGS ?= -O3 -arch=sm_30
NVCCFLAGS ?= -O3
ifdef DEBUG
CCFLAGS += -g
NVCCFLAGS += -g -G
endif
# Common includes and paths for CUDA. This assumes the CUDA toolkit is in PATH
INCLUDES := -I. -I/usr/lib/jvm/java-7-openjdk-amd64/include
LDFLAGS += -L. -lcuda -lcudart
# Target rules
all: libKraskov.so
gpuKnnLibrary.o: gpuKnnLibrary.cu helperfunctions.cu gpuKnnBF_kernel.cu
${NVCC} ${NVCCFLAGS} ${INCLUDES} -Xcompiler -fPIC -c gpuKnnLibrary.cu
libgpuKnnLibrary.a: gpuKnnLibrary.o
${AR} -r libgpuKnnLibrary.a gpuKnnLibrary.o
libKraskov.so: libgpuKnnLibrary.a kraskovCuda.c
${NVCC} ${NVCCFLAGS} ${INCLUDES} -Xcompiler -fPIC -shared -o libKraskov.so kraskovCuda.c ${LDFLAGS} -lgpuKnnLibrary
clean:
rm -f *.o *.so *.a

View File

@ -0,0 +1,120 @@
/*************************************
An ANSI-C implementation of the digamma-function for real arguments based
on the Chebyshev expansion proposed in appendix E of
http://arXiv.org/abs/math.CA/0403344 . This is identical to the implementation
by Jet Wimp, Math. Comp. vol 15 no 74 (1961) pp 174 (see Table 1).
For other implementations see
the GSL implementation for Psi(Digamma) in
http://www.gnu.org/software/gsl/manual/html_node/Psi-_0028Digamma_0029-Function.html
Richard J. Mathar, 2005-11-24
***************************************/
#include <math.h>
#ifndef M_PIl
/** The constant Pi in high precision */
#define M_PIl 3.1415926535897932384626433832795029L
#endif
#ifndef M_GAMMAl
/** Euler's constant in high precision */
#define M_GAMMAl 0.5772156649015328606065120900824024L
#endif
#ifndef M_LN2l
/** the natural logarithm of 2 in high precision */
#define M_LN2l 0.6931471805599453094172321214581766L
#endif
/** The digamma function in long double precision.
* @param x the real value of the argument
* @return the value of the digamma (psi) function at that point
* @author Richard J. Mathar
* @since 2005-11-24
*/
#ifdef __cplusplus
extern "C" {
#endif
long double cpuDigamma(long double x)
{
int n;
/* force into the interval 1..3 */
if( x < 0.0L )
return cpuDigamma(1.0L-x)+M_PIl/tanl(M_PIl*(1.0L-x)) ; /* reflection formula */
else if( x < 1.0L )
return cpuDigamma(1.0L+x)-1.0L/x ;
else if ( x == 1.0L)
return -M_GAMMAl ;
else if ( x == 2.0L)
return 1.0L-M_GAMMAl ;
else if ( x == 3.0L)
return 1.5L-M_GAMMAl ;
else if ( x > 3.0L)
/* duplication formula */
return 0.5L*(cpuDigamma(x/2.0L)+cpuDigamma((x+1.0L)/2.0L))+M_LN2l ;
else {
/* Just for your information, the following lines contain
* the Maple source code to re-generate the table that is
* eventually becoming the Kncoe[] array below
* interface(prettyprint=0) :
* Digits := 63 :
* r := 0 :
*
* for l from 1 to 60 do
* d := binomial(-1/2,l) :
* r := r+d*(-1)^l*(Zeta(2*l+1) -1) ;
* evalf(r) ;
* print(%,evalf(1+Psi(1)-r)) ;
*o d :
*
* for N from 1 to 28 do
* r := 0 :
* n := N-1 :
*
* for l from iquo(n+3,2) to 70 do
* d := 0 :
* for s from 0 to n+1 do
* d := d+(-1)^s*binomial(n+1,s)*binomial((s-1)/2,l) :
* od :
* if 2*l-n > 1 then
* r := r+d*(-1)^l*(Zeta(2*l-n) -1) :
* fi :
* od :
* print(evalf((-1)^n*2*r)) ;
*od :
*quit :
*/
static long double Kncoe[] = { .30459198558715155634315638246624251L,
.72037977439182833573548891941219706L, -.12454959243861367729528855995001087L,
.27769457331927827002810119567456810e-1L, -.67762371439822456447373550186163070e-2L,
.17238755142247705209823876688592170e-2L, -.44817699064252933515310345718960928e-3L,
.11793660000155572716272710617753373e-3L, -.31253894280980134452125172274246963e-4L,
.83173997012173283398932708991137488e-5L, -.22191427643780045431149221890172210e-5L,
.59302266729329346291029599913617915e-6L, -.15863051191470655433559920279603632e-6L,
.42459203983193603241777510648681429e-7L, -.11369129616951114238848106591780146e-7L,
.304502217295931698401459168423403510e-8L, -.81568455080753152802915013641723686e-9L,
.21852324749975455125936715817306383e-9L, -.58546491441689515680751900276454407e-10L,
.15686348450871204869813586459513648e-10L, -.42029496273143231373796179302482033e-11L,
.11261435719264907097227520956710754e-11L, -.30174353636860279765375177200637590e-12L,
.80850955256389526647406571868193768e-13L, -.21663779809421233144009565199997351e-13L,
.58047634271339391495076374966835526e-14L, -.15553767189204733561108869588173845e-14L,
.41676108598040807753707828039353330e-15L, -.11167065064221317094734023242188463e-15L } ;
register long double Tn_1 = 1.0L ; /* T_{n-1}(x), started at n=1 */
register long double Tn = x-2.0L ; /* T_{n}(x) , started at n=1 */
register long double resul = Kncoe[0] + Kncoe[1]*Tn ;
x -= 2.0L ;
for(n = 2 ; n < sizeof(Kncoe)/sizeof(long double) ;n++)
{
const long double Tn1 = 2.0L * x * Tn - Tn_1 ; /* Chebyshev recursion, Eq. 22.7.4 Abramowitz-Stegun */
resul += Kncoe[n]*Tn1 ;
Tn_1 = Tn ;
Tn = Tn1 ;
}
return resul ;
}
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,455 @@
#ifndef _TEMPLATE_KERNEL_H_
#define _TEMPLATE_KERNEL_H_
//#include <cpugpuKnn_common.h>
#ifndef INFINITY
#define INFINITY 0x7F800000
#endif
/**
* Typedef function pointer for arbitrary norm functions.
*/
typedef float (*normFunction_t)(const float* g_uquery, const float* g_vpoint,
int pointdim, int signallength);
/**
* Calculate max norm (L-inf) between two points.
*/
__device__ __host__ float
maxMetricPoints(const float* g_uquery, const float* g_vpoint, int pointdim, int signallength){
float r_u1;
float r_v1;
float r_d1,r_dim=0;
r_dim=0;
for(int d=0; d<pointdim; d++){
r_u1 = *(g_uquery+d*signallength);
r_v1 = *(g_vpoint+d*signallength);
r_d1 = r_v1 - r_u1;
r_d1 = r_d1 < 0? -r_d1: r_d1; //abs
r_dim= r_dim < r_d1? r_d1: r_dim;
}
return r_dim;
}
/**
* Calculate squared Euclidean norm (L2) between two points (note we keep the
* distance squared to avoid taking sqrt all the time).
*/
__device__ __host__ float
squareMetricPoints(const float* g_uquery, const float* g_vpoint, int pointdim, int signallength){
float r_u1;
float r_v1;
float r_d1, r_dim = 0.0f;
for (int d = 0; d < pointdim; d++) {
r_u1 = *(g_uquery+d*signallength);
r_v1 = *(g_vpoint+d*signallength);
r_d1 = r_v1 - r_u1;
r_dim += r_d1 * r_d1;
}
return r_dim;
}
__device__ normFunction_t pMaxNorm = maxMetricPoints;
__device__ normFunction_t pSquareNorm = squareMetricPoints;
/**
* Insert point in current list of nearest neighbours.
*/
__device__ float
insertPointKlist(int kth, float distance, int indexv,float* kdistances, int* kindexes){
int k=0;
while( (distance>*(kdistances+k)) && (k<kth-1)){k++;}
//Move value to the next
for(int k2=kth-1;k2>k;k2--){
*(kdistances+k2)=*(kdistances+k2-1);
*(kindexes+k2)=*(kindexes+k2-1);
}
//Replace
*(kdistances+k)=distance;
*(kindexes+k)=indexv;
//printf("\n -> Modificacion pila: %.f %.f. New max distance: %.f", *kdistances, *(kdistances+1), *(kdistances+kth-1));
return *(kdistances+kth-1);
}
/*
* Main KNN kernel. Find the nearest k neighbours to each point according
* to the supplied norm function.
*/
__global__ void
kernelKNNshared(const float* g_uquery, const float* g_vpointset,
int *g_indexes, float* g_distances, const int pointdim,
const int triallength, const int signallength, const int kth,
const int exclude, normFunction_t *normFunction) {
// Shared memory
extern __shared__ char array[];
float *kdistances;
int *kindexes;
kdistances = (float*)array;
kindexes = (int*)array+kth*blockDim.x;
const unsigned int tid = threadIdx.x + blockDim.x*blockIdx.x;
const unsigned int itrial = tid / triallength; // indextrial
if(tid<signallength){
for(int k=0;k<kth;k++){
kdistances[threadIdx.x*kth+k] = INFINITY;
}
__syncthreads();
float r_kdist=INFINITY;
unsigned int indexi = tid-triallength*itrial;
for(int t=0; t<triallength; t++){
int indexu = tid;
int indexv = (t + itrial*triallength);
int condition1=indexi-exclude;
int condition2=indexi+exclude;
if((t<condition1)||(t>condition2)){
float temp_dist = normFunction[0](g_uquery+indexu, g_vpointset+indexv,pointdim, signallength);
if(temp_dist <= r_kdist){
r_kdist = insertPointKlist(kth,temp_dist,t,kdistances+threadIdx.x*kth,kindexes+threadIdx.x*kth);
}
}
//printf("tid:%d indexes: %d, %d distances: %.f %.f\n",tid, *kindexes, *(kindexes+1), *kdistances, *(kdistances+1));
}
__syncthreads();
// COPY TO GLOBAL MEMORY
for (int k = 0; k < kth; k++) {
g_indexes[tid+k*signallength] = kindexes[threadIdx.x*kth+k];
g_distances[tid+k*signallength] = kdistances[threadIdx.x*kth+k];//*(kdistances+k);
}
}
}
/*
* Range search for one data point using bruteforce.
*/
__global__ void
kernelBFRSshared(const float* g_uquery, const float* g_vpointset, int *g_npoints, int pointdim, int triallength, int signallength, int exclude, float radius)
{
// Shared memory
extern __shared__ char array[];
int *s_npointsrange;
s_npointsrange = (int*)array;
const unsigned int tid = threadIdx.x + blockDim.x*blockIdx.x;
const unsigned int itrial = tid / triallength; // indextrial
if(tid<signallength){
s_npointsrange[threadIdx.x] = 0;
__syncthreads();
unsigned int indexi = tid-triallength*itrial;
for(int t=0; t<triallength; t++){
int indexu = tid;
int indexv = (t + itrial*triallength);
int condition1=indexi-exclude;
int condition2=indexi+exclude;
if((t<condition1)||(t>condition2)){
float temp_dist = maxMetricPoints(g_uquery+indexu, g_vpointset+indexv,pointdim, signallength);
if(temp_dist <= radius){
s_npointsrange[threadIdx.x]++;
}
}
}
__syncthreads();
// COPY TO GLOBAL MEMORY
g_npoints[tid] = s_npointsrange[threadIdx.x];
}
}
/*
* Range search using bruteforce in multiple GPUs
*/
__global__ void
kernelBFRSMultishared(const float* g_uquery, const float* g_vpointset, int *g_npoints, int pointdim, int triallength, int signallength, int exclude, const float* vecradius)
{
// shared memory
extern __shared__ char array[];
int *s_npointsrange;
s_npointsrange = (int*)array;
float radius=0;
const unsigned int tid = threadIdx.x + blockDim.x*blockIdx.x;
const unsigned int itrial = tid / triallength; // indextrial
if(tid<signallength){
s_npointsrange[threadIdx.x] = 0;
__syncthreads();
radius = *(vecradius+itrial);
unsigned int indexi = tid-triallength*itrial;
for(int t=0; t<triallength; t++){
int indexu = tid;
int indexv = (t + itrial*triallength);
int condition1=indexi-exclude;
int condition2=indexi+exclude;
if((t<condition1)||(t>condition2)){
float temp_dist = maxMetricPoints(g_uquery+indexu, g_vpointset+indexv,pointdim, signallength);
if(temp_dist <= radius){
s_npointsrange[threadIdx.x]++;
}
}
}
__syncthreads();
//printf("\ntid:%d npoints: %d\n",tid, s_npointsrange[threadIdx.x]);
//COPY TO GLOBAL MEMORY
g_npoints[tid] = s_npointsrange[threadIdx.x];
}
}
/*
* Range search for all data points using bruteforce.
*/
__global__ void
kernelBFRSAllshared(const float* g_uquery, const float* g_vpointset,
int *g_npoints, int pointdim, int triallength, int signallength,
int exclude, const float* vecradius, normFunction_t *normFunction) {
// Shared memory
extern __shared__ char array[];
int *s_npointsrange;
s_npointsrange = (int *) array;
float radius = 0;
const unsigned int tid = threadIdx.x + blockDim.x*blockIdx.x;
const unsigned int itrial = tid / triallength; // indextrial
if(tid<signallength){
s_npointsrange[threadIdx.x] = 0;
__syncthreads();
radius = *(vecradius+tid);
unsigned int indexi = tid-triallength*itrial;
for (int t=0; t<triallength; t++){
int indexu = tid;
int indexv = (t + itrial*triallength);
int condition1=indexi-exclude;
int condition2=indexi+exclude;
if((t<condition1)||(t>condition2)){
float temp_dist = normFunction[0](g_uquery+indexu, g_vpointset+indexv,pointdim, signallength);
// PEDRO: For KSG algorithm 1 this should be strictly less than R, and in TRENTOOL code it's less or equal. It's a float comparison, so I don't think it matters anyway.
if(temp_dist < radius){
s_npointsrange[threadIdx.x]++;
}
}
}
__syncthreads();
//COPY TO GLOBAL MEMORY
g_npoints[tid] = s_npointsrange[threadIdx.x];
}
}
__device__ void digammaXp1(double *y);
/**
* Optimized reduction kernel, obtained from Mark Harris' lecture on
* GPU optimization:
* https://docs.nvidia.com/cuda/samples/6_Advanced/reduction/doc/reduction.pdf
*/
template <unsigned int blockSize>
// __device__ void warpReduce(volatile int *sdata, unsigned int tid) {
__device__ void warpReduce(volatile float *sdata, unsigned int tid) {
if (blockSize >= 64) sdata[tid] += sdata[tid + 32];
if (blockSize >= 32) sdata[tid] += sdata[tid + 16];
if (blockSize >= 16) sdata[tid] += sdata[tid + 8];
if (blockSize >= 8) sdata[tid] += sdata[tid + 4];
if (blockSize >= 4) sdata[tid] += sdata[tid + 2];
if (blockSize >= 2) sdata[tid] += sdata[tid + 1];
}
// template <unsigned int blockSize>
// __global__ void reduce6(int *g_idata, int *g_odata, unsigned int n) {
__global__ void reduce6(int *g_nx, int *g_ny, float *g_odata, unsigned int n) {
// extern __shared__ int sdata[];
extern __shared__ float sdata[];
unsigned int tid = threadIdx.x;
const unsigned int blockSize = 512;
unsigned int i = blockIdx.x*(blockSize*2) + tid;
unsigned int gridSize = blockSize*2*gridDim.x;
sdata[tid] = 0;
while (i<n) {
double dgX1 = (double) g_nx[i];
double dgY1 = (double) g_ny[i];
double dgX2 = (double) g_nx[i+blockSize];
double dgY2 = (double) g_ny[i+blockSize];
digammaXp1(&dgX1);
digammaXp1(&dgY1);
digammaXp1(&dgX2);
digammaXp1(&dgY2);
sdata[tid] = dgX1 + dgY1 + dgX2 + dgY2;
i += gridSize;
}
__syncthreads();
if (blockSize >= 512) { if (tid < 256) { sdata[tid] += sdata[tid + 256]; } __syncthreads(); }
if (blockSize >= 256) { if (tid < 128) { sdata[tid] += sdata[tid + 128]; } __syncthreads(); }
if (blockSize >= 128) { if (tid < 64) { sdata[tid] += sdata[tid + 64]; } __syncthreads(); }
if (tid < 32) warpReduce<blockSize>(sdata, tid);
if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}
__global__ void reduce1(int *g_nx, int *g_ny, float *g_odata, unsigned int N) {
extern __shared__ float sdata2[];
// each thread loads one element from global to shared mem
unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;
if (i < N) {
double dgX = g_nx[i];
double dgY = g_ny[i];
digammaXp1(&dgX);
digammaXp1(&dgY);
sdata2[tid] = dgX + dgY;
} else {
sdata2[tid] = 0;
}
__syncthreads();
// do reduction in shared mem
for (unsigned int s=1; s < blockDim.x; s *= 2) {
if (tid % (2*s) == 0) {
sdata2[tid] += sdata2[tid + s];
}
__syncthreads();
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = sdata2[0];
}
/**
* Taken from NVIDIA dev forum:
* https://devtalk.nvidia.com/default/topic/516516/kernel-launch-failure-in-matlab/
*/
__device__ void digammaXp1(double *y) {
// double x = *y;
double x = *y + 1;
double neginf = -INFINITY;
const double c = 12,
digamma1 = -0.57721566490153286,
trigamma1 = 1.6449340668482264365, /* pi^2/6 */
s = 1e-6,
s3 = 1./12,
s4 = 1./120,
s5 = 1./252,
s6 = 1./240,
s7 = 1./132;
// s8 = 691./32760,
// s9 = 1./12,
// s10 = 3617./8160;
double result;
/* Illegal arguments */
if((x == neginf) || isnan(x)) {
*y = NAN;
return;
}
/* Singularities */
if((x <= 0) && (floor(x) == x)) {
*y = neginf;
return;
}
/* Negative values */
/* Use the reflection formula (Jeffrey 11.1.6):
* digamma(-x) = digamma(x+1) + pi*cot(pi*x)
*
* This is related to the identity
* digamma(-x) = digamma(x+1) - digamma(z) + digamma(1-z)
* where z is the fractional part of x
* For example:
* digamma(-3.1) = 1/3.1 + 1/2.1 + 1/1.1 + 1/0.1 + digamma(1-0.1)
* = digamma(4.1) - digamma(0.1) + digamma(1-0.1)
* Then we use
* digamma(1-z) - digamma(z) = pi*cot(pi*z)
*
if(x < 0) {
*p = digamma(p,1-x) + M_PI/tan(-M_PI*x);
return;
}
*/
/* Use Taylor series if argument <= S */
if(x <= s) {
*y = digamma1 - 1/x + trigamma1*x;
return;
}
/* Reduce to digamma(X + N) where (X + N) >= C */
result = 0;
while(x < c) {
result -= 1/x;
x++;
}
/* Use de Moivre's expansion if argument >= C */
/* This expansion can be computed in Maple via asympt(Psi(x),x) */
if(x >= c) {
double r = 1/x, t;
result += log(x) - 0.5*r;
r *= r;
#if 0
result -= r * (s3 - r * (s4 - r * (s5 - r * (s6 - r * s7))));
#else
/* this version for lame compilers */
t = (s5 - r * (s6 - r * s7));
result -= r * (s3 - r * (s4 - r * t));
#endif
}
/* assign the result to the pointer*/
*y = result;
return;
}
#endif // #ifndef _TEMPLATE_KERNEL_H_

View File

@ -0,0 +1,278 @@
#include "gpuKnnBF_kernel.cu"
#include <stdio.h>
#include "helperfunctions.cu"
#ifdef __cplusplus
extern "C" {
#endif
int cudaFindKnn(int* h_bf_indexes, float* h_bf_distances, float* h_pointset,
float* h_query, int kth, int thelier, int nchunks, int pointdim,
int signallength, unsigned int useMaxNorm) {
float *d_bf_pointset, *d_bf_query;
int *d_bf_indexes;
float *d_bf_distances;
unsigned int meminputsignalquerypointset= pointdim * signallength * sizeof(float);
unsigned int mem_bfcl_outputsignaldistances= kth * signallength * sizeof(float);
unsigned int mem_bfcl_outputsignalindexes = kth * signallength * sizeof(int);
checkCudaErrors( cudaMalloc( (void**) &(d_bf_query), meminputsignalquerypointset));
checkCudaErrors( cudaMalloc( (void**) &(d_bf_pointset), meminputsignalquerypointset));
//GPU output
checkCudaErrors( cudaMalloc( (void**) &(d_bf_distances), mem_bfcl_outputsignaldistances ));
checkCudaErrors( cudaMalloc( (void**) &(d_bf_indexes), mem_bfcl_outputsignalindexes ));
cudaError_t error = cudaGetLastError();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
//Upload input data
checkCudaErrors( cudaMemcpy(d_bf_query, h_query, meminputsignalquerypointset, cudaMemcpyHostToDevice ));
checkCudaErrors( cudaMemcpy(d_bf_pointset, h_pointset, meminputsignalquerypointset, cudaMemcpyHostToDevice ));
error = cudaGetLastError();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
// Kernel parameters
dim3 threads(1,1,1);
dim3 grid(1,1,1);
threads.x = 512;
grid.x = (signallength-1)/threads.x + 1;
int memkernel = kth*sizeof(float)*threads.x+\
kth*sizeof(int)*threads.x;
int triallength = signallength / nchunks;
// Pointer to the function used to calculate norms
normFunction_t *normFunction;
cudaMalloc( (void **) &normFunction, sizeof(normFunction_t) );
if (useMaxNorm) {
cudaMemcpyFromSymbol(normFunction, pMaxNorm, sizeof(normFunction_t));
} else {
cudaMemcpyFromSymbol(normFunction, pSquareNorm, sizeof(normFunction_t));
}
// Launch kernel
kernelKNNshared<<<grid.x, threads.x, memkernel>>>(d_bf_query, d_bf_pointset,
d_bf_indexes, d_bf_distances, pointdim, triallength, signallength, kth,
thelier, normFunction);
checkCudaErrors( cudaDeviceSynchronize() );
//Download result
checkCudaErrors( cudaMemcpy( h_bf_distances, d_bf_distances, mem_bfcl_outputsignaldistances, cudaMemcpyDeviceToHost) );
checkCudaErrors( cudaMemcpy( h_bf_indexes, d_bf_indexes, mem_bfcl_outputsignalindexes, cudaMemcpyDeviceToHost) );
error = cudaGetLastError();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
//Free resources
checkCudaErrors(cudaFree(d_bf_query));
checkCudaErrors(cudaFree(d_bf_pointset));
checkCudaErrors(cudaFree(d_bf_distances));
checkCudaErrors(cudaFree(d_bf_indexes));
cudaFree(normFunction);
// cudaDeviceReset();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
return 1;
}
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
extern "C" {
#endif
int cudaFindKnnSetGPU(int* h_bf_indexes, float* h_bf_distances,
float* h_pointset, float* h_query, int kth, int thelier, int nchunks,
int pointdim, int signallength, unsigned int useMaxNorm, int deviceid) {
cudaSetDevice(deviceid);
return cudaFindKnn(h_bf_indexes, h_bf_distances, h_pointset, h_query,
kth, thelier, nchunks, pointdim, signallength, useMaxNorm);
}
#ifdef __cplusplus
}
#endif
/*
* Range search being radius a vector of length number points in queryset/pointset
*/
#ifdef __cplusplus
extern "C" {
#endif
int cudaFindRSAll(int* h_bf_npointsrange, float* h_pointset, float* h_query,
float* h_vecradius, int thelier, int nchunks, int pointdim,
int signallength, unsigned int useMaxNorm) {
float *d_bf_pointset, *d_bf_query, *d_bf_vecradius;
int *d_bf_npointsrange;
unsigned int meminputsignalquerypointset= pointdim * signallength * sizeof(float);
unsigned int mem_bfcl_outputsignalnpointsrange= signallength * sizeof(int);
unsigned int mem_bfcl_inputvecradius = signallength * sizeof(float);
checkCudaErrors( cudaMalloc( (void**) &(d_bf_query), meminputsignalquerypointset));
checkCudaErrors( cudaMalloc( (void**) &(d_bf_pointset), meminputsignalquerypointset));
checkCudaErrors( cudaMalloc( (void**) &(d_bf_npointsrange), mem_bfcl_outputsignalnpointsrange ));
checkCudaErrors( cudaMalloc( (void**) &(d_bf_vecradius), mem_bfcl_inputvecradius ));
cudaError_t error = cudaGetLastError();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
//Upload input data
checkCudaErrors( cudaMemcpy(d_bf_query, h_query, meminputsignalquerypointset, cudaMemcpyHostToDevice ));
checkCudaErrors( cudaMemcpy(d_bf_pointset, h_pointset, meminputsignalquerypointset, cudaMemcpyHostToDevice ));
checkCudaErrors( cudaMemcpy(d_bf_vecradius, h_vecradius, mem_bfcl_inputvecradius, cudaMemcpyHostToDevice ));
error = cudaGetLastError();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
// Kernel parameters
dim3 threads(1,1,1);
dim3 grid(1,1,1);
threads.x = 512;
grid.x = (signallength-1)/threads.x + 1;
int memkernel = sizeof(int)*threads.x;
int triallength = signallength / nchunks;
// Pointer to the function used to calculate norms
normFunction_t *normFunction;
cudaMalloc( (void **) &normFunction, sizeof(normFunction_t) );
if (useMaxNorm) {
cudaMemcpyFromSymbol(normFunction, pMaxNorm, sizeof(normFunction_t));
} else {
cudaMemcpyFromSymbol(normFunction, pSquareNorm, sizeof(normFunction_t));
}
// Launch kernel
kernelBFRSAllshared<<< grid.x, threads.x, memkernel>>>(
d_bf_query, d_bf_pointset, d_bf_npointsrange, pointdim,
triallength, signallength, thelier, d_bf_vecradius, normFunction);
checkCudaErrors(cudaDeviceSynchronize());
checkCudaErrors( cudaMemcpy( h_bf_npointsrange, d_bf_npointsrange,mem_bfcl_outputsignalnpointsrange, cudaMemcpyDeviceToHost) );
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
// Free resources
checkCudaErrors(cudaFree(d_bf_query));
checkCudaErrors(cudaFree(d_bf_pointset));
checkCudaErrors(cudaFree(d_bf_npointsrange));
checkCudaErrors(cudaFree(d_bf_vecradius));
checkCudaErrors(cudaFree(normFunction));
cudaDeviceReset();
if(error!=cudaSuccess){
fprintf(stderr,"%s",cudaGetErrorString(error));
return 0;
}
return 1;
}
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
extern "C" {
#endif
int cudaFindRSAllSetGPU(int* h_bf_npointsrange, float* h_pointset,
float* h_query, float* h_vecradius, int thelier, int nchunks,
int pointdim, int signallength, unsigned int useMaxNorm, int deviceid) {
cudaSetDevice(deviceid);
return cudaFindRSAll(h_bf_npointsrange, h_pointset, h_query, h_vecradius,
thelier, nchunks, pointdim, signallength, useMaxNorm);
}
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
extern "C" {
#endif
int findRadiiAlgorithm2(float *radii, const float *data, const int *indexes,
unsigned int k, unsigned int dim, unsigned int N) {
unsigned int i, j;
for (j = 0; j < N; j++) {
radii[j] = 0.0f;
for (i = 0; i < k; i++) {
float d = maxMetricPoints(data + j, data + indexes[j + i*N], dim, N);
if (d > radii[j]) {
radii[j] = d;
}
}
}
return 1;
}
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
extern "C" {
#endif
int computeSumDigammas(float *sumDiGammas, int *nx, int *ny, unsigned int N) {
int *d_nx, *d_ny;
float *d_sumDiGammas;
checkCudaErrors( cudaMalloc((void **) &d_nx, N * sizeof(int)) );
checkCudaErrors( cudaMalloc((void **) &d_ny, N * sizeof(int)) );
checkCudaErrors( cudaMalloc((void **) &d_sumDiGammas, sizeof(float)) );
checkCudaErrors( cudaMemcpy(d_nx, nx, N*sizeof(int), cudaMemcpyHostToDevice) );
checkCudaErrors( cudaMemcpy(d_ny, ny, N*sizeof(int), cudaMemcpyHostToDevice) );
unsigned int threads_per_block = 512;
dim3 n_blocks, n_threads;
n_blocks.x = ((N % threads_per_block) != 0) ? (N / threads_per_block + 1) : (N / threads_per_block);
n_threads.x = threads_per_block;
printf("blocks = %i, threads = %i\n", n_blocks.x, n_threads.x);
// reduce6<<<n_blocks, n_threads>>>(d_nx, d_ny, d_sumDiGammas, N);
reduce1<<<n_blocks, n_threads>>>(d_nx, d_ny, d_sumDiGammas, N);
checkCudaErrors( cudaDeviceSynchronize() );
checkCudaErrors( cudaMemcpy(sumDiGammas, d_sumDiGammas, sizeof(float), cudaMemcpyDeviceToHost) );
cudaFree(d_nx);
cudaFree(d_ny);
cudaFree(d_sumDiGammas);
*sumDiGammas = 0.0;
return 1;
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,26 @@
#ifndef GPUKNNLIBRARY_H
#define GPUKNNLIBRARY_H
int cudaFindKnn(int* h_bf_indexes, float* h_bf_distances, float* h_pointset,
float* h_query, int kth, int thelier, int nchunks, int pointdim,
int signallength, unsigned int useMaxNorm);
int cudaFindKnnSetGPU(int* h_bf_indexes, float* h_bf_distances,
float* h_pointset, float* h_query, int kth, int thelier, int nchunks,
int pointdim, int signallength, unsigned int useMaxNorm, int deviceid);
int cudaFindRSAll(int* h_bf_npointsrange, float* h_pointset,
float* h_query, float* h_vecradius, int thelier, int nchunks,
int pointdim, int signallength, unsigned int useMaxNorm);
int cudaFindRSAllSetGPU(int* h_bf_npointsrange, float* h_pointset,
float* h_query, float* h_vecradius, int thelier, int nchunks,
int pointdim, int signallength, unsigned int useMaxNorm, int deviceid);
int findRadiiAlgorithm2(float *radii, const float *data, const int *indexes,
unsigned int k, unsigned int dim, unsigned int N);
int computeSumDigammas(float *sumDiGammas, int *nx, int *ny, unsigned int N);
#endif

View File

@ -0,0 +1,230 @@
////////////////////////////////////////////////////////////////////////////////
// These are CUDA Helper functions
#include <stdlib.h>
#include <stdio.h>
// This will output the proper CUDA error strings in the event that a CUDA host call returns an error
#define checkCudaErrors(err) __checkCudaErrors (err, __FILE__, __LINE__)
inline void __checkCudaErrors(cudaError err, const char *file, const int line)
{
if (cudaSuccess != err)
{
fprintf(stderr, "%s(%i) : CUDA Runtime API error %d: %s.\n",file, line, (int)err, cudaGetErrorString(err));
exit(-1);
}
}
// This will output the proper error string when calling cudaGetLastError
#define getLastCudaError(msg) __getLastCudaError (msg, __FILE__, __LINE__)
inline void __getLastCudaError(const char *errorMessage, const char *file, const int line)
{
cudaError_t err = cudaGetLastError();
if (cudaSuccess != err)
{
fprintf(stderr, "%s(%i) : getLastCudaError() CUDA error : %s : (%d) %s.\n",
file, line, errorMessage, (int)err, cudaGetErrorString(err));
exit(-1);
}
}
// General GPU Device CUDA Initialization
int gpuDeviceInit(int devID)
{
int deviceCount;
checkCudaErrors(cudaGetDeviceCount(&deviceCount));
if (deviceCount == 0)
{
fprintf(stderr, "gpuDeviceInit() CUDA error: no devices supporting CUDA.\n");
exit(-1);
}
if (devID < 0)
{
devID = 0;
}
if (devID > deviceCount-1)
{
fprintf(stderr, "\n");
fprintf(stderr, ">> %d CUDA capable GPU device(s) detected. <<\n", deviceCount);
fprintf(stderr, ">> gpuDeviceInit (-device=%d) is not a valid GPU device. <<\n", devID);
fprintf(stderr, "\n");
return -devID;
}
cudaDeviceProp deviceProp;
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, devID));
if(!deviceProp.deviceOverlap){
printf("\n Device will not handle overlaps, so no speed up from streams \n");
}
if (deviceProp.major < 1)
{
fprintf(stderr, "gpuDeviceInit(): GPU device does not support CUDA.\n");
exit(-1);
}
checkCudaErrors(cudaSetDevice(devID));
printf("gpuDeviceInit() CUDA Device [%d]: \"%s\n", devID, deviceProp.name);
return devID;
}
#ifndef MAX
#define MAX(a,b) (a > b ? a : b)
#endif
// Beginning of GPU Architecture definitions
inline int _ConvertSMVer2Cores(int major, int minor)
{
// Defines for GPU Architecture types (using the SM version to determine the # of cores per SM
typedef struct
{
int SM; // 0xMm (hexidecimal notation), M = SM Major version, and m = SM minor version
int Cores;
} sSMtoCores;
sSMtoCores nGpuArchCoresPerSM[] =
{
{ 0x10, 8 }, // Tesla Generation (SM 1.0) G80 class
{ 0x11, 8 }, // Tesla Generation (SM 1.1) G8x class
{ 0x12, 8 }, // Tesla Generation (SM 1.2) G9x class
{ 0x13, 8 }, // Tesla Generation (SM 1.3) GT200 class
{ 0x20, 32 }, // Fermi Generation (SM 2.0) GF100 class
{ 0x21, 48 }, // Fermi Generation (SM 2.1) GF10x class
{ 0x30, 192}, // Fermi Generation (SM 3.0) GK10x class
{ -1, -1 }
};
int index = 0;
while (nGpuArchCoresPerSM[index].SM != -1)
{
if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor))
{
return nGpuArchCoresPerSM[index].Cores;
}
index++;
}
printf("MapSMtoCores undefined SM %d.%d is undefined (please update to the latest SDK)!\n", major, minor);
return -1;
}
// end of GPU Architecture definitions
// This function returns the best GPU (with maximum GFLOPS)
int gpuGetMaxGflopsDeviceId()
{
int current_device = 0, sm_per_multiproc = 0;
int max_compute_perf = 0, max_perf_device = 0;
int device_count = 0, best_SM_arch = 0;
cudaDeviceProp deviceProp;
cudaGetDeviceCount(&device_count);
// Find the best major SM Architecture GPU device
while (current_device < device_count)
{
cudaGetDeviceProperties(&deviceProp, current_device);
if (deviceProp.major > 0 && deviceProp.major < 9999)
{
best_SM_arch = MAX(best_SM_arch, deviceProp.major);
}
current_device++;
}
// Find the best CUDA capable GPU device
current_device = 0;
while (current_device < device_count)
{
cudaGetDeviceProperties(&deviceProp, current_device);
if (deviceProp.major == 9999 && deviceProp.minor == 9999)
{
sm_per_multiproc = 1;
}
else
{
sm_per_multiproc = _ConvertSMVer2Cores(deviceProp.major, deviceProp.minor);
}
int compute_perf = deviceProp.multiProcessorCount * sm_per_multiproc * deviceProp.clockRate;
if (compute_perf > max_compute_perf)
{
// If we find GPU with SM major > 2, search only these
if (best_SM_arch > 2)
{
// If our device==dest_SM_arch, choose this, or else pass
if (deviceProp.major == best_SM_arch)
{
max_compute_perf = compute_perf;
max_perf_device = current_device;
}
}
else
{
max_compute_perf = compute_perf;
max_perf_device = current_device;
}
}
++current_device;
}
return max_perf_device;
}
// Initialization code to find the best CUDA Device
int findCudaDevice(int argc, const char **argv)
{
cudaDeviceProp deviceProp;
int devID = 0;
// If the command-line has a device number specified, use it
/*if (checkCmdLineFlag(argc, argv, "device"))
{
devID = getCmdLineArgumentInt(argc, argv, "device=");
if (devID < 0)
{
printf("Invalid command line parameter\n ");
exit(-1);
}
else
{
devID = gpuDeviceInit(devID);
if (devID < 0)
{
printf("exiting...\n");
//shrQAFinishExit(argc, (const char **)argv, QA_FAILED);
exit(-1);
}
}
}
else
{*/
// Otherwise pick the device with highest Gflops/s
devID = gpuGetMaxGflopsDeviceId();
checkCudaErrors(cudaSetDevice(devID));
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, devID));
printf("GPU Device %d: \"%s\" with compute capability %d.%d\n\n", devID, deviceProp.name, deviceProp.major, deviceProp.minor);
//}
return devID;
}
// end of CUDA Helper Functions

View File

@ -0,0 +1,316 @@
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include "digamma.c"
#include "gpuKnnLibrary.h"
#define check(ans) { _check((ans), __FILE__, __LINE__); }
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: infodynamics_measures_continuous_kraskov_MutualInfoCalculatorMultiVariateKraskov
* Method: MIKraskov
* Signature: (I[DI[DIIZZZ)[D
*/
JNIEXPORT jdoubleArray JNICALL
Java_infodynamics_measures_continuous_kraskov_MutualInfoCalculatorMultiVariateKraskov_MIKraskov(
JNIEnv *env, jobject thisObj, jint j_N,
jobjectArray j_sourceArray, jint j_dimx,
jobjectArray j_destArray, jint j_dimy,
jint j_k, jboolean j_returnLocals, jboolean j_useMaxNorm, jboolean j_isAlgorithm1) {
// Declare variables
// Variables starting with j_ are Java types
float *source, *dest, *pointset, *radii, *distances;
int *nx, *ny, *indexes;
jdouble *jd_source, *jd_dest, *result;
unsigned int N, dimx, dimy, dims, k, returnLocals, useMaxNorm, isAlgorithm1, result_size;
float sumNx, sumNy, sumDiGammas;
unsigned int i, j;
long double digammaK, digammaN;
int thelier = 0, nchunks = 1, err;
const int verbose = 0;
if (verbose) printf("Verbose is on.\n");
// Check that incoming data has correct size
jsize sourceLength = (*env)->GetArrayLength(env, j_sourceArray);
jsize destLength = (*env)->GetArrayLength(env, j_destArray);
if (sourceLength != j_N || destLength != j_N) {
cudaDeviceReset();
jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
(*env)->ThrowNew(env, Exception, "Data has wrong length.");
}
// 1. Load data into GPU
// =====================
// 1a. Copy atomic variables from Java
N = j_N;
k = j_k;
dimx = j_dimx;
dimy = j_dimy;
dims = dimx + dimy;
returnLocals = j_returnLocals ? 1 : 0;
useMaxNorm = j_useMaxNorm ? 1 : 0;
isAlgorithm1 = j_isAlgorithm1 ? 1 : 0;
if (verbose > 0) {
printf("Loading data from Java to C.\n");
printf("N = %i, dimx = %i, dimy = %i\n", N, dimx, dimy);
}
// 1b. Get arrays from Java
source = (float *) malloc(N * dimx * sizeof(float));
dest = (float *) malloc(N * dimy * sizeof(float));
pointset = (float *) malloc(N * dims * sizeof(float));
if (NULL == source || NULL == dest || NULL == pointset) {
printf("Error allocating data.\n");
cudaDeviceReset();
jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
(*env)->ThrowNew(env, Exception, "Error allocating data.");
}
for (i = 0; i < N; i++) {
jdoubleArray j_sourceRow = (*env)->GetObjectArrayElement(env, j_sourceArray, i);
jdoubleArray j_destRow = (*env)->GetObjectArrayElement(env, j_destArray, i);
jdouble *sourceRow = (*env)->GetDoubleArrayElements(env, j_sourceRow, NULL);
jdouble *destRow = (*env)->GetDoubleArrayElements(env, j_destRow, NULL);
// Data in java are doubles, but GPUs need floats.
// We have to cast them manually, so we can't memcopy
// The following two for-loops get two matrices in T-by-D indexing (i.e.
// first dimension is time, second is variable) and return the data in
// column-major form
for (j = 0; j < dimx; j++) {
register float x = (float) sourceRow[j];
source[N*j + i] = x;
pointset[N*j + i] = x;
}
for (j = 0; j < dimy; j++) {
register float x = (float) destRow[j];
dest[N*j + i] = x;
pointset[N*dimx + N*j + i] = x;
}
// // The following two for-loops get the same matrices but return arrays
// // in row-major form. Code is left here for future development purposes.
// for (j = 0; j < dimx; j++) {
// register float x = (float) sourceRow[j];
// source[dimx*i + j] = x;
// pointset[dims*i + j] = x;
// }
// for (j = 0; j < dimy; j++) {
// register float x = (float) destRow[j];
// dest[dimy*i + j] = x
// pointset[i*dims + dimx + j] = x;
// }
(*env)->ReleaseDoubleArrayElements(env, j_sourceRow, sourceRow, 0);
(*env)->ReleaseDoubleArrayElements(env, j_destRow, destRow, 0);
(*env)->DeleteLocalRef(env, j_sourceRow);
(*env)->DeleteLocalRef(env, j_destRow);
// FIXME: I'm not entirely sure I'm freeing all the memory here. I should
// check for memory leaks more carefully.
}
if (verbose > 1) {
printf("Flat source array: ");
for (i = 0; i < N*dimx; i++) printf("%f, ", source[i]);
printf("\n");
printf("Flat dest array: ");
for (i = 0; i < N*dimy; i++) printf("%f, ", dest[i]);
printf("\n");
printf("Flat pointset: ");
for (i = 0; i < N*dims; i++) printf("%f, ", pointset[i]);
printf("\n");
}
// 2. Get indices and distances to the k NN of each point
// ======================
indexes = (int *) malloc(N * k * sizeof(int));
distances = (float *) malloc(N * k * sizeof(float));
err = cudaFindKnn(indexes, distances, pointset, pointset, k,
thelier, nchunks, dims, N, useMaxNorm);
if (err != 1) {
cudaDeviceReset();
jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
(*env)->ThrowNew(env, Exception, "Cuda error finding nearest neighbours.");
}
if (verbose > 1) {
printf("Distances: \n");
for (j = 0; j < N; j++) {
for (i = 0; i < k; i++) {
printf("%f\t", distances[j + i*N]);
}
printf("\n");
}
printf("Indexes: \n");
for (j = 0; j < N; j++) {
for (i = 0; i < k; i++) {
printf("%i\t", indexes[j + i*N]);
}
printf("\n");
}
}
// 3. Find distance R from each point to its k-th neighbour in joint space
// ======================
if (verbose > 0) {
printf("Getting radii from calculated dists.\n");
}
radii = (float *) malloc(N * sizeof(float));
for (i = 0; i < N; i++) {
radii[i] = distances[N*(k-1) + i];
}
if (verbose > 1) {
for (i = 0; i < N; i++) {
printf("%f\t", radii[i]);
}
printf("\n");
}
// 4. Count points strictly within R in the X-space
// ======================
if (verbose > 0) {
printf("Counting points within radius.\n");
}
// If we're using algorithm 2 then we need the radius in the marginal space,
// not the joint (as calculated above)
if (!isAlgorithm1) {
findRadiiAlgorithm2(radii, source, indexes, k, dimx, N);
}
nx = (int *) malloc(N * sizeof(int));
err = cudaFindRSAll(nx, source, source, radii, thelier, nchunks, dimx, N, useMaxNorm);
if (err != 1) {
cudaDeviceReset();
jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
(*env)->ThrowNew(env, Exception, "Cuda error during source range search.");
}
if (verbose > 0) {
printf("X count: ");
for (i = 0; i < N; i++) printf("%i, ", nx[i]);
printf("\n\n");
}
// 4. Count points strictly within R in the X-space
// ======================
if (!isAlgorithm1) {
findRadiiAlgorithm2(radii, dest, indexes, k, dimy, N);
}
ny = (int *) malloc(N * sizeof(int));
err = cudaFindRSAll(ny, dest, dest, radii, thelier, nchunks, dimy, N, useMaxNorm);
if (err != 1) {
cudaDeviceReset();
jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
(*env)->ThrowNew(env, Exception, "Cuda error during dest range search.");
}
if (verbose > 0) {
printf("Y count: ");
for (i = 0; i < N; i++) printf("%i, ", ny[i]);
printf("\n\n");
}
// 6. Set locals or digammas for return
// ======================
if (verbose > 0) {
printf("Start calculating digammas\n");
}
if (returnLocals) {
result = (jdouble *) malloc(N * sizeof(jdouble));
result_size = N;
digammaK = cpuDigamma(k);
digammaN = cpuDigamma(N);
for (i = 0; i < N; i++) {
result[i] = digammaK + digammaN - cpuDigamma(nx[i] + 1) - cpuDigamma(ny[i] + 1);
}
} else {
result = (jdouble *) malloc(3 * sizeof(jdouble));
result_size = 3;
sumNx = 0;
sumNy = 0;
sumDiGammas = 0;
for (i = 0; i < N; i++) {
sumNx += nx[i];
sumNy += ny[i];
sumDiGammas += cpuDigamma(nx[i] + 1) + cpuDigamma(ny[i] + 1);
}
result[0] = (jdouble) sumDiGammas;
result[1] = (jdouble) sumNx;
result[2] = (jdouble) sumNy;
}
// 7. Free memory and return
// =========================
if (verbose > 0) {
printf("Free memory\n");
}
if (source) free(source);
if (dest) free(dest);
if (pointset) free(pointset);
if (radii) free(radii);
if (distances) free(distances);
if (indexes) free(indexes);
if (nx) free(nx);
if (ny) free(ny);
// Set Java array for return
jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, result_size); // allocate
if (NULL == outJNIArray) {
cudaDeviceReset();
jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
(*env)->ThrowNew(env, Exception, "Error creating return array.");
}
(*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , result_size, result); // copy
if (result) free(result);
return outJNIArray;
} // End of function MIKraskov1
#ifdef __cplusplus
}
#endif