From 295ca85326a176495b290339ab2d80271c76263f Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 02:12:34 -0400 Subject: [PATCH 1/8] Stream compaction --- Project2-Stream-Compaction/README.md | 70 ++++++++- .../img/timeOverLength.png | Bin 0 -> 47059 bytes Project2-Stream-Compaction/src/main.cpp | 2 +- .../stream_compaction/CMakeLists.txt | 2 +- .../stream_compaction/common.cu | 21 ++- .../stream_compaction/cpu.cu | 56 ++++++- .../stream_compaction/cpu.h | 2 + .../stream_compaction/efficient.cu | 144 +++++++++++++++++- .../stream_compaction/naive.cu | 79 +++++++++- .../stream_compaction/thrust.cu | 6 + 10 files changed, 359 insertions(+), 23 deletions(-) create mode 100644 Project2-Stream-Compaction/img/timeOverLength.png diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 0e38ddb..c673098 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -3,12 +3,70 @@ CUDA Stream Compaction **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Joshua Nadel + * https://www.linkedin.com/in/joshua-nadel-379382136/, http://www.joshnadel.com/ +* Tested on: Windows 10, i7-6700HQ @ 2.60GHz 16GB, GTX 970M (Personal laptop) -### (TODO: Your README) +### -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +![](images/timeOverLength.png) +I do not know how the thrust implementation manages to be so efficient at such large array sizes. In fact, it seems to become increasingly efficient with array length. + +It is interesting to note that the efficient implementation is consistently slower than the naive implementation. This is likely due to memory access order. + +Predictably, the serial CPU version increases in runtime proportionally to the increase in array size. + +The program output reads: +```'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Users\Josh\Documents\School\UPenn\2019-2020\CIS 565\Project2-Number-Algorithms\Project2-Stream-Compaction\build\Release\cis565_stream_compaction_test.exe'. Module was built without symbols. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ntdll.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\kernel32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\KernelBase.dll'. Symbols loaded. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ucrtbase.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msvcp140.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\vcruntime140.dll'. Symbols loaded. +The thread 0x4ab4 has exited with code 0 (0x0). +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\advapi32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msvcrt.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\sechost.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\rpcrt4.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\gdi32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\gdi32full.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msvcp_win.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\user32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\win32u.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\imm32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\setupapi.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\cfgmgr32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\bcrypt.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\devobj.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\wintrust.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msasn1.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\crypt32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\shell32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\SHCore.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\combase.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\bcryptprimitives.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\windows.storage.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\profapi.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\powrprof.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\shlwapi.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\kernel.appcore.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\cryptsp.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\nvcuda.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\version.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\nvfatbinaryLoader.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ws2_32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\dwmapi.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\uxtheme.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Unloaded 'C:\Windows\System32\dwmapi.dll' +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\nvapi64.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ole32.dll'. Cannot find or open the PDB file. +'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\dxgi.dll'. Cannot find or open the PDB file. +The thread 0x61a0 has exited with code 0 (0x0). +The thread 0x3554 has exited with code 0 (0x0). +The thread 0x3f60 has exited with code 0 (0x0). +The thread 0x3ef0 has exited with code 0 (0x0). +The thread 0x19f4 has exited with code 0 (0x0). +The thread 0x4948 has exited with code 0 (0x0). +The program '[11188] cis565_stream_compaction_test.exe' has exited with code 0 (0x0).``` \ No newline at end of file diff --git a/Project2-Stream-Compaction/img/timeOverLength.png b/Project2-Stream-Compaction/img/timeOverLength.png new file mode 100644 index 0000000000000000000000000000000000000000..85b8946c35ad9e052b3fb8cd57fc9a3b287156ee GIT binary patch literal 47059 zcmeEucRbba8~3q8C@Li**@Oy3GD@=d%r40aS((SEC@CW=BO`kr%HBzlnQ@4WY==16 z9P7D1M}2?lc>aF=d3wFH&ga~p`@Z(|zOL&IP*s+pAfqFL!C(|}vNzOWFv5Ho41eYj zG58<0@!Y%M4?IV8nd`8;HhL8J2ch{jrE4%)K`{B&Z6fe*(g(7)9APkqr_c{x@Wg@{ z3|3+xcjKC-tI^Lvx43Yv>M6I~*YVNujOpob=4XmM=D)!=dy3}1mt<#GY3XX-N+mk` z{50A8GxFzW-~J%u`$1gl`}`f}JDyGMz@MAd=*?=WSJAAeLXVzmwi|UB4PZUXLj9_d zcQmejb7+Y>fa4;~#nGsCw`#XGbeYxH#A|gX=T9Cce*EFjALBD;YVZ8>U(2QPVC&$Q z5B*Ia=oH{@asU5s|4(ROm6D&Y%IG$JRA9L#boXrNvzBN98NCu)-d-b*+$~IRO=P>o zuEC=Zi8=Zu%JJ?qTuZMRJywu96Vn~ZLS9ti5fNWb+f_0v#R!JNH&@+)d-%6q_;0q?o?MUmRkZ5XV~4@M58iEH_hA^!jgB|T?{MYo5hqHt1h%~G4_y-zP~sCRAg z19_qjW!)#sca?xi+A`PTgFQUY;v1mBi}a_ybT+?{5$^;nG4H4e9*j>x$K+?LK&sB~9dN zax;TW`M3?7h=}Oj{l0h4tEHpddBgK^ZdJM#RBkOcFIEh1w^hbt?1RNI87TX)rHxAS z&urej+i1tBw&=_xn5rpLBfU~)Ow4>4jTvxkkFH7gn%hLz z82eM4j$CzCgJT${dl3nJtLeiUaKj2G)T>oSS%Z8XMwcNs^Un=5-?zDINM4~fEk8UW zih?Wgm-wlA_h?R;hsbZWd~J8x*>Z~(hr3Hl@Nbv6pohdJ#;Cn_>uY!}+^3ka(irtH z-0+Ax1b;@kUXz&>WRBT* zE7Vb9XDssY=j*j|5XZA2jz^`?mT06YX1@M8E1kSrC1-GB#;@`Dah^(Z<4WC*UF6hs zLy;9SYqajx<0BN;Uh&7guR3`RNlk0AHt3R2X3~xJnCMkDK5f(eMv=fT&#*$fQX8{? zxe(B+@|019l9Ae&X8|*9ovq)#Tqd!-9JZFDDR8gle8WoJg$1Lp?S0ugi06ppswlx9 z@&-R=hR14s2`0-j#BIv+R+5LMwvA@iG;2yQ=J1zK51%@lq}q6OEy~zS&aG<2d!?Pu z%Zh$-Bf(R>El$KVUSG>66-@=N zVI3j~K)hKOof&h#+SPuHf?F^LIfYsZ8&<)%QByPo1_mk#V48O4nY`9N(90l~S9;g3 zjZVqGHIJ!|(j1KoaaD$X<43Kpj|FR7 kHZ|@3GkWeVceaJ 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 k^eTk=cWJddTKu+KZ^2PEX-Kv+dteav?LOec_a #include "testing_helpers.hpp" -const int SIZE = 1 << 8; // feel free to change the size of array +const int SIZE = 1 << 10; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; diff --git a/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt b/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt index cdbef77..6ad13f6 100644 --- a/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt +++ b/Project2-Stream-Compaction/stream_compaction/CMakeLists.txt @@ -13,5 +13,5 @@ set(SOURCE_FILES cuda_add_library(stream_compaction ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_52 ) diff --git a/Project2-Stream-Compaction/stream_compaction/common.cu b/Project2-Stream-Compaction/stream_compaction/common.cu index 2ed6d63..8f9d82f 100644 --- a/Project2-Stream-Compaction/stream_compaction/common.cu +++ b/Project2-Stream-Compaction/stream_compaction/common.cu @@ -23,7 +23,17 @@ namespace StreamCompaction { * which map to 0 will be removed, and elements which map to 1 will be kept. */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { - // TODO + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index > n) { + return; + } + + if (idata[index] == 0) { + bools[index] = 0; + } + else { + bools[index] = 1; + } } /** @@ -32,7 +42,14 @@ namespace StreamCompaction { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index > n) { + return; + } + + if (bools[index] == 1) { + odata[indices[index]] = idata[index]; + } } } diff --git a/Project2-Stream-Compaction/stream_compaction/cpu.cu b/Project2-Stream-Compaction/stream_compaction/cpu.cu index a2d3e6c..cb00e33 100644 --- a/Project2-Stream-Compaction/stream_compaction/cpu.cu +++ b/Project2-Stream-Compaction/stream_compaction/cpu.cu @@ -19,7 +19,13 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + for (int i = 0; i < n; i++) { + int sum = 0; + for (int j = 0; j < i; j++) { + sum += idata[j]; + } + odata[i] = sum; + } timer().endCpuTimer(); } @@ -30,9 +36,16 @@ namespace StreamCompaction { */ int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int j = 0; + for (int i = 0; i < n; i++) { + int toPut = idata[i]; + if (toPut != 0) { + odata[j] = toPut; + j++; + } + } timer().endCpuTimer(); - return -1; + return j; } /** @@ -42,9 +55,42 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int *mappedArray = new int[n]; + for (int i = 0; i < n; i++) { + mappedArray[i] = 0; + } + for (int i = 0; i < n; i++) { + if (idata[i] == 0) mappedArray[i] = 0; + else mappedArray[i] = 1; + } + int *scannedArray = new int[n]; + for (int i = 0; i < n; i++) { + scannedArray[i] = 0; + } + scanNoTimer(n, scannedArray, mappedArray); + int count = 0; + for (int i = 0; i < n; i++) { + int toPut = idata[i]; + int indexToPut = scannedArray[i]; + if (toPut != 0) { + odata[indexToPut] = toPut; + count++; + } + } timer().endCpuTimer(); - return -1; + delete[] mappedArray; + delete[] scannedArray; + return count; } + + void scanNoTimer(int n, int *odata, const int *idata) { + for (int i = 0; i < n; i++) { + int sum = 0; + for (int j = 0; j < i; j++) { + sum += idata[j]; + } + odata[i] = sum; + } + } } } diff --git a/Project2-Stream-Compaction/stream_compaction/cpu.h b/Project2-Stream-Compaction/stream_compaction/cpu.h index 236ce11..79aeaba 100644 --- a/Project2-Stream-Compaction/stream_compaction/cpu.h +++ b/Project2-Stream-Compaction/stream_compaction/cpu.h @@ -11,5 +11,7 @@ namespace StreamCompaction { int compactWithoutScan(int n, int *odata, const int *idata); int compactWithScan(int n, int *odata, const int *idata); + + void scanNoTimer(int n, int *odata, const int *idata); } } diff --git a/Project2-Stream-Compaction/stream_compaction/efficient.cu b/Project2-Stream-Compaction/stream_compaction/efficient.cu index 2db346e..57fe766 100644 --- a/Project2-Stream-Compaction/stream_compaction/efficient.cu +++ b/Project2-Stream-Compaction/stream_compaction/efficient.cu @@ -3,6 +3,10 @@ #include "common.h" #include "efficient.h" +#define checkCUDAErrorWithLine(msg) checkCUDAError(msg, __LINE__) + +#define blockSize 128 + namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; @@ -12,15 +16,104 @@ namespace StreamCompaction { return timer; } + int *dev_data; + + __global__ void upSweep(int *data, int n, int d) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + int stride = powf(2, d + 1); + if (index >= n || index % stride != 0) { + return; + } + + int index2 = index + powf(2, d) - 1; + data[index + stride - 1] += data[index2]; + } + + __global__ void downSweep(int *data, int n, int d) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + int stride = powf(2, d + 1); + if (index >= n || index % stride != 0) { + return; + } + + int index2 = index + powf(2, d) - 1; + int index3 = index + powf(2, d + 1) - 1; + int t = data[index2]; + data[index2] = data[index3]; + data[index3] += t; + } + + __global__ void copyBuffer(const int *source, int *dest, int n) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + dest[index] = source[index]; + } + + __global__ void kern0LastElement(int *data, int n) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index != n - 1) { + return; + } + + data[index] = 0; + } + + __global__ void kernReduction(int *data, int n, int d) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + int stride = powf(2, d + 1); + if (index >= n || index % stride != 0) { + return; + } + + int index2 = index + powf(2, d) - 1; + data[index + stride - 1] += data[index2]; + } + + void printArray(const int *array, int n) { + printf("["); + for (int i = 0; i < n; i++) { + printf("%d, ", array[i]); + } + printf("]\n"); + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - // TODO - timer().endGpuTimer(); + int n2 = pow(2, ceil(log2(n))); + + dim3 fullBlocksPerGrid((n2 + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_data, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_data failed!"); + cudaMemcpy(dev_data, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + + timer().startGpuTimer(); + + for (int d = 0; d < log2(n2); d++) { + upSweep << > > (dev_data, n2, d); + checkCUDAErrorWithLine("Up sweep failed!"); + } + kern0LastElement << > > (dev_data, n2); + for (int d = log2(n2) - 1; d >= 0; d--) { + downSweep << > > (dev_data, n2, d); + checkCUDAErrorWithLine("Down sweep failed!"); + } + + timer().endGpuTimer(); + cudaMemcpy(odata, dev_data, sizeof(int) * n, cudaMemcpyDeviceToHost); + cudaFree(dev_data); } + int *dev_bools; + int *dev_idata; + int *dev_odata; + int *dev_scanned; + /** * Performs stream compaction on idata, storing the result into odata. * All zeroes are discarded. @@ -31,10 +124,51 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { + int n2 = pow(2, ceil(log2(n))); + + dim3 fullBlocksPerGrid((n2 + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_bools, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_bools failed!"); + cudaMalloc((void**)&dev_idata, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_idata failed!"); + cudaMalloc((void**)&dev_odata, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_odata failed!"); + cudaMalloc((void**)&dev_scanned, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_scanned failed!"); + cudaMemcpy(dev_idata, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + timer().startGpuTimer(); - // TODO + Common::kernMapToBoolean << > > (n2, dev_bools, dev_idata); + cudaMemcpy(dev_scanned, dev_bools, sizeof(int) * n2, cudaMemcpyDeviceToDevice); + for (int d = 0; d < log2(n2); d++) { + upSweep << > > (dev_scanned, n2, d); + checkCUDAErrorWithLine("Up sweep failed!"); + } + kern0LastElement << > > (dev_scanned, n2); + for (int d = log2(n2) - 1; d >= 0; d--) { + downSweep << > > (dev_scanned, n2, d); + checkCUDAErrorWithLine("Down sweep failed!"); + } + Common::kernScatter << > > (n2, dev_odata, dev_idata, dev_bools, dev_scanned); + for (int d = 0; d < log2(n2); d++) { + kernReduction << > > (dev_bools, n2, d); + checkCUDAErrorWithLine("Reduction failed!"); + } timer().endGpuTimer(); - return -1; + + int *summedBools = new int[n2]; + cudaMemcpy(odata, dev_odata, sizeof(int) * n, cudaMemcpyDeviceToHost); + cudaMemcpy(summedBools, dev_bools, sizeof(int) * n2, cudaMemcpyDeviceToHost); + int toReturn = summedBools[n2 - 1]; + + cudaFree(dev_bools); + cudaFree(dev_idata); + cudaFree(dev_odata); + cudaFree(dev_scanned); + delete[] summedBools; + + return toReturn; } } } diff --git a/Project2-Stream-Compaction/stream_compaction/naive.cu b/Project2-Stream-Compaction/stream_compaction/naive.cu index 4308876..2c7795c 100644 --- a/Project2-Stream-Compaction/stream_compaction/naive.cu +++ b/Project2-Stream-Compaction/stream_compaction/naive.cu @@ -3,6 +3,10 @@ #include "common.h" #include "naive.h" +#define checkCUDAErrorWithLine(msg) checkCUDAError(msg, __LINE__) + +#define blockSize 128 + namespace StreamCompaction { namespace Naive { using StreamCompaction::Common::PerformanceTimer; @@ -11,15 +15,84 @@ namespace StreamCompaction { static PerformanceTimer timer; return timer; } - // TODO: __global__ + + int *dev_odata; + int *dev_idata; + + __global__ void scanHelper(int *odata, int *idata, int n, int d) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + if (index >= powf(2, d - 1)) { + int dataIndex = index - powf(2, d - 1); + odata[index] = idata[dataIndex] + idata[index]; + } + else { + odata[index] = idata[index]; + } + } + + __global__ void copyBuffer(const int *source, int *dest, int n) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + dest[index] = source[index]; + } + + __global__ void shiftBuffer(const int *source, int *dest, int n) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index > n) { + return; + } + + if (index == 0) { + dest[index] = 0; + } + else { + dest[index] = source[index - 1]; + } + } + + void printArray(const int *array, int n) { + printf("["); + for (int i = 0; i < n; i++) { + printf("%d, ", array[i]); + } + printf("]\n"); + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int n2 = pow(2, ceil(log2(n))); + + dim3 fullBlocksPerGrid((n2 + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_idata, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_idata failed!"); + cudaMalloc((void**)&dev_odata, n2 * sizeof(int)); + checkCUDAErrorWithLine("cudaMalloc dev_odata failed!"); + cudaMemcpy(dev_idata, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + cudaMemcpy(dev_odata, odata, sizeof(int) * n, cudaMemcpyHostToDevice); + timer().startGpuTimer(); - // TODO - timer().endGpuTimer(); + + for (int d = 1; d <= log2(n2); d++) { + scanHelper << > > (dev_odata, dev_idata, n2, d); + checkCUDAErrorWithLine("Scan helper failed!"); + copyBuffer << > > (dev_odata, dev_idata, n2); + checkCUDAErrorWithLine("Copy buffer failed!"); + } + shiftBuffer << > > (dev_idata, dev_odata, n2); + timer().endGpuTimer(); + cudaMemcpy(odata, dev_odata, sizeof(int) * n, cudaMemcpyDeviceToHost); + cudaFree(dev_idata); + cudaFree(dev_odata); } } } diff --git a/Project2-Stream-Compaction/stream_compaction/thrust.cu b/Project2-Stream-Compaction/stream_compaction/thrust.cu index 1def45e..cb858b5 100644 --- a/Project2-Stream-Compaction/stream_compaction/thrust.cu +++ b/Project2-Stream-Compaction/stream_compaction/thrust.cu @@ -18,11 +18,17 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + thrust::host_vector h_in(idata, idata + n); + thrust::host_vector h_out(odata, odata + n); + thrust::device_vector dv_in = h_in; + thrust::device_vector dv_out = h_out; timer().startGpuTimer(); // TODO use `thrust::exclusive_scan` // example: for device_vectors dv_in and dv_out: // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); timer().endGpuTimer(); + thrust::copy(dv_out.begin(), dv_out.end(), odata); } } } From 81ebd569f6802a4ff044b6e3acb2c44ce7e27840 Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 02:13:58 -0400 Subject: [PATCH 2/8] fix image --- Project2-Stream-Compaction/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index c673098..9515d76 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -9,7 +9,7 @@ CUDA Stream Compaction ### -![](images/timeOverLength.png) +![](img/timeOverLength.png) I do not know how the thrust implementation manages to be so efficient at such large array sizes. In fact, it seems to become increasingly efficient with array length. From b837e3462a6cadf200d13f7f523fba141dfabe86 Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 11:41:24 -0400 Subject: [PATCH 3/8] readme update --- .../character_recognition/CMakeLists.txt | 2 +- Project2-Character-Recognition/src/main.cpp | 20 ++++++++++++++----- Project2-Stream-Compaction/README.md | 10 +++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/CMakeLists.txt b/Project2-Character-Recognition/character_recognition/CMakeLists.txt index 7446175..556196a 100644 --- a/Project2-Character-Recognition/character_recognition/CMakeLists.txt +++ b/Project2-Character-Recognition/character_recognition/CMakeLists.txt @@ -7,5 +7,5 @@ set(SOURCE_FILES cuda_add_library(character_recognition ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_52 ) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 11dd534..fcc2afb 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -11,14 +11,24 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 8; // feel free to change the size of array +/*const int SIZE = 1 << 8; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; -int *c = new int[SIZE]; +int *c = new int[SIZE];*/ + +int *dev_input; +int *dev_hidden; +int *dev_output; +float *dev_w_kj; +float *dev_w_ki; + +float sigmoid(float x) { + return 1 / (1 + exp(-x)); +} int main(int argc, char* argv[]) { - // Scan tests + /*// Scan tests printf("\n"); printf("****************\n"); @@ -58,7 +68,7 @@ int main(int argc, char* argv[]) { StreamCompaction::Naive::scan(SIZE, c, a); printArray(SIZE, c, true); */ - zeroArray(SIZE, c); + /*zeroArray(SIZE, c); printDesc("naive scan, non-power-of-two"); StreamCompaction::Naive::scan(NPOT, c, a); printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); @@ -148,5 +158,5 @@ int main(int argc, char* argv[]) { system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; - delete[] c; + delete[] c;*/ } diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 9515d76..d0d3476 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -7,7 +7,15 @@ CUDA Stream Compaction * https://www.linkedin.com/in/joshua-nadel-379382136/, http://www.joshnadel.com/ * Tested on: Windows 10, i7-6700HQ @ 2.60GHz 16GB, GTX 970M (Personal laptop) -### +### Scan / Stream Compaction + +This project demonstrates the ability of the GPU to quickly complete algorithms that are, in serial, quite slow. It contains both serial and parallel implementations of scan and stream compaction algorithms, and uses timers to compare their performances on large quantities of data. +The list of features includes: +* Serial scan implementation on the CPU +* Serial compact implementation on the CPU +* Naive scan implementation on the GPU +* Work-efficient scan implementation on the GPU +* Work-efficient compact implementation on the GPU ![](img/timeOverLength.png) From b4f974d82609456734870c1d6fe366b3693f21d2 Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 11:50:38 -0400 Subject: [PATCH 4/8] readme update 2 --- Project2-Character-Recognition/src/main.cpp | 17 +++++++++++++++++ Project2-Stream-Compaction/README.md | 1 + 2 files changed, 18 insertions(+) diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index fcc2afb..76cf1df 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -11,6 +11,8 @@ #include #include "testing_helpers.hpp" +#define DIM = 101; + /*const int SIZE = 1 << 8; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; @@ -27,7 +29,22 @@ float sigmoid(float x) { return 1 / (1 + exp(-x)); } +void train() { + +} + +void evaluate() { + +} + +void init() { + +} + int main(int argc, char* argv[]) { + + + /*// Scan tests printf("\n"); diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index d0d3476..5bac633 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -16,6 +16,7 @@ The list of features includes: * Naive scan implementation on the GPU * Work-efficient scan implementation on the GPU * Work-efficient compact implementation on the GPU +* Wrapper for thrust's scan implementation to compare performance ![](img/timeOverLength.png) From 872741c4274102d53fece1244029c013302195fa Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 20:50:24 -0400 Subject: [PATCH 5/8] readme update 3 --- .../character_recognition/mlp.cu | 126 ++++++++++-- .../character_recognition/mlp.h | 5 +- Project2-Character-Recognition/src/main.cpp | 183 ++---------------- Project2-Stream-Compaction/README.md | 106 +++++----- 4 files changed, 191 insertions(+), 229 deletions(-) diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index 5a3ed7f..33c84b3 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -3,6 +3,13 @@ #include "common.h" #include "mlp.h" +#define DIM 101 +#define LABELS 52 + +#define checkCUDAErrorWithLine(msg) checkCUDAError(msg, __LINE__) + +#define blockSize 128 + namespace CharacterRecognition { using Common::PerformanceTimer; PerformanceTimer& timer() @@ -10,18 +17,113 @@ namespace CharacterRecognition { static PerformanceTimer timer; return timer; } + + float *dev_input; + float *dev_hidden; + float *dev_output; + float *dev_w_kj; + float *dev_w_ki; + + int inputDims = DIM * DIM; + int hiddenDims = inputDims; + int outputDims = LABELS; + + __global__ void backprop() { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n_output) { + return; + } + } - // TODO: __global__ - - /** - * Example of use case (follow how you did it in stream compaction) - */ - /*void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - // TODO - timer().endGpuTimer(); - } - */ + void train() { + + + -PRODUCT(C7 - R9, (1 / (1 + EXP(-R9))), (1 - 1 / (1 + EXP(-R9))), O5) + -PRODUCT(J5, 1 / (1 + EXP(-O5)), 1 - 1 / (1 + EXP(-O5)), C7 - R9, 1 / (1 + EXP(-R9)), 1 - 1 / (1 + EXP(-R9)), P8) + } + + __global__ void kernComputeLayer(float *inputLr, float *outputLr, int n_input, int n_output, float *weights) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n_output) { + return; + } + + // Weighted sum of previous layer inputs + for (int i = 0; i < n_input; i++) { + int weightIndex = i + index * n_input; + outputLr[index] += inputLr[i] * weights[weightIndex]; + } + + // Activation function + outputLr[index] = 1 / (1 + expf(-outputLr[index])); + } + + void printArray(const float *array, int n) { + printf("["); + for (int i = 0; i < n; i++) { + printf("%f, ", array[i]); + } + printf("]\n"); + } + + void evaluate(float *input) { + float *results = new float[outputDims]; + + dim3 fullBlocksPerGridInToHid((inputDims * hiddenDims + blockSize - 1) / blockSize); + dim3 fullBlocksPerGridHidToOut((hiddenDims * outputDims + blockSize - 1) / blockSize); + + cudaMemcpy(dev_input, input, sizeof(float) * inputDims, cudaMemcpyHostToDevice); + + kernComputeLayer << > > (dev_input, dev_hidden, inputDims, hiddenDims, dev_w_kj); + kernComputeLayer << > > (dev_hidden, dev_output, hiddenDims, outputDims, dev_w_ki); + + cudaMemcpy(results, dev_output, sizeof(float) * outputDims, cudaMemcpyDeviceToHost); + + printArray(results, outputDims); + delete[] results; + } + + void init() { + dim3 fullBlocksPerGridInToHid((inputDims * hiddenDims + blockSize - 1) / blockSize); + dim3 fullBlocksPerGridHidToOut((hiddenDims * outputDims + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_input, inputDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_input failed!"); + + cudaMalloc((void**)&dev_hidden, hiddenDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_hidden failed!"); + + cudaMalloc((void**)&dev_output, outputDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_output failed!"); + + cudaMalloc((void**)&dev_w_kj, inputDims * hiddenDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_w_kj failed!"); + + cudaMalloc((void**)&dev_w_ki, hiddenDims * outputDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_w_ki failed!"); + + float *weights1 = new float[inputDims * hiddenDims]; + for (int i = 0; i < inputDims * hiddenDims; i++) { + float r = ((double)rand() / (RAND_MAX)); + weights1[i] = r * 2.0f - 1.0f; + } + float *weights2 = new float[hiddenDims * outputDims]; + for (int i = 0; i < hiddenDims * outputDims; i++) { + float r = ((double)rand() / (RAND_MAX)); + weights2[i] = r * 2.0f - 1.0f; + } + cudaMemcpy(dev_w_kj, weights1, sizeof(float) * inputDims * hiddenDims, cudaMemcpyHostToDevice); + cudaMemcpy(dev_w_ki, weights2, sizeof(float) * hiddenDims * outputDims, cudaMemcpyHostToDevice); + + delete[] weights1; + delete[] weights2; + } - // TODO: implement required elements for MLP sections 1 and 2 here + void end() { + cudaFree(dev_input); + cudaFree(dev_hidden); + cudaFree(dev_output); + cudaFree(dev_w_kj); + cudaFree(dev_w_ki); + } } diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index 2096228..1ee8661 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -5,5 +5,8 @@ namespace CharacterRecognition { Common::PerformanceTimer& timer(); - // TODO: implement required elements for MLP sections 1 and 2 here + void init(); + void train(); + void evaluate(float *input); + void end(); } diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 76cf1df..7528655 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -11,169 +11,24 @@ #include #include "testing_helpers.hpp" -#define DIM = 101; - -/*const int SIZE = 1 << 8; // feel free to change the size of array -const int NPOT = SIZE - 3; // Non-Power-Of-Two -int *a = new int[SIZE]; -int *b = new int[SIZE]; -int *c = new int[SIZE];*/ - -int *dev_input; -int *dev_hidden; -int *dev_output; -float *dev_w_kj; -float *dev_w_ki; - -float sigmoid(float x) { - return 1 / (1 + exp(-x)); -} - -void train() { - -} - -void evaluate() { - -} - -void init() { - -} - int main(int argc, char* argv[]) { - - - - /*// Scan tests - - printf("\n"); - printf("****************\n"); - printf("** SCAN TESTS **\n"); - printf("****************\n"); - - genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; - printArray(SIZE, a, true); - - // initialize b using StreamCompaction::CPU::scan you implement - // We use b for further comparison. Make sure your StreamCompaction::CPU::scan is correct. - // At first all cases passed because b && c are all zeroes. - zeroArray(SIZE, b); - printDesc("cpu scan, power-of-two"); - StreamCompaction::CPU::scan(SIZE, b, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(SIZE, b, true); - - zeroArray(SIZE, c); - printDesc("cpu scan, non-power-of-two"); - StreamCompaction::CPU::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(NPOT, b, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("naive scan, power-of-two"); - StreamCompaction::Naive::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - /* For bug-finding only: Array of 1s to help find bugs in stream compaction or scan - onesArray(SIZE, c); - printDesc("1s array for finding bugs"); - StreamCompaction::Naive::scan(SIZE, c, a); - printArray(SIZE, c, true); */ - - /*zeroArray(SIZE, c); - printDesc("naive scan, non-power-of-two"); - StreamCompaction::Naive::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient scan, power-of-two"); - StreamCompaction::Efficient::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient scan, non-power-of-two"); - StreamCompaction::Efficient::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("thrust scan, power-of-two"); - StreamCompaction::Thrust::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("thrust scan, non-power-of-two"); - StreamCompaction::Thrust::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); - - printf("\n"); - printf("*****************************\n"); - printf("** STREAM COMPACTION TESTS **\n"); - printf("*****************************\n"); - - // Compaction tests - - genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; - printArray(SIZE, a, true); - - int count, expectedCount, expectedNPOT; - - // initialize b using StreamCompaction::CPU::compactWithoutScan you implement - // We use b for further comparison. Make sure your StreamCompaction::CPU::compactWithoutScan is correct. - zeroArray(SIZE, b); - printDesc("cpu compact without scan, power-of-two"); - count = StreamCompaction::CPU::compactWithoutScan(SIZE, b, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - expectedCount = count; - printArray(count, b, true); - printCmpLenResult(count, expectedCount, b, b); - - zeroArray(SIZE, c); - printDesc("cpu compact without scan, non-power-of-two"); - count = StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - expectedNPOT = count; - printArray(count, c, true); - printCmpLenResult(count, expectedNPOT, b, c); - - zeroArray(SIZE, c); - printDesc("cpu compact with scan"); - count = StreamCompaction::CPU::compactWithScan(SIZE, c, a); - printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(count, c, true); - printCmpLenResult(count, expectedCount, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient compact, power-of-two"); - count = StreamCompaction::Efficient::compact(SIZE, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(count, c, true); - printCmpLenResult(count, expectedCount, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient compact, non-power-of-two"); - count = StreamCompaction::Efficient::compact(NPOT, c, a); - printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(count, c, true); - printCmpLenResult(count, expectedNPOT, b, c); - - system("pause"); // stop Win32 console from closing on exit - delete[] a; - delete[] b; - delete[] c;*/ + CharacterRecognition::init(); + //CharacterRecognition::train(); + + FILE * image = std::fopen("../data-set/02info.txt", "r"); + int label; + int dimensions; + fscanf(image, "%d", &label); + fscanf(image, "%d", &dimensions); + float *colors = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + int color; + fscanf(image, "%d", &color); + colors[i] = color; + } + CharacterRecognition::evaluate(colors); + fclose(image); + delete[] colors; + + CharacterRecognition::end(); } diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 5bac633..29b3932 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -27,55 +27,57 @@ It is interesting to note that the efficient implementation is consistently slow Predictably, the serial CPU version increases in runtime proportionally to the increase in array size. The program output reads: -```'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Users\Josh\Documents\School\UPenn\2019-2020\CIS 565\Project2-Number-Algorithms\Project2-Stream-Compaction\build\Release\cis565_stream_compaction_test.exe'. Module was built without symbols. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ntdll.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\kernel32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\KernelBase.dll'. Symbols loaded. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ucrtbase.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msvcp140.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\vcruntime140.dll'. Symbols loaded. -The thread 0x4ab4 has exited with code 0 (0x0). -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\advapi32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msvcrt.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\sechost.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\rpcrt4.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\gdi32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\gdi32full.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msvcp_win.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\user32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\win32u.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\imm32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\setupapi.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\cfgmgr32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\bcrypt.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\devobj.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\wintrust.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\msasn1.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\crypt32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\shell32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\SHCore.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\combase.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\bcryptprimitives.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\windows.storage.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\profapi.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\powrprof.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\shlwapi.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\kernel.appcore.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\cryptsp.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\nvcuda.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\version.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\nvfatbinaryLoader.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ws2_32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\dwmapi.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\uxtheme.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Unloaded 'C:\Windows\System32\dwmapi.dll' -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\nvapi64.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\ole32.dll'. Cannot find or open the PDB file. -'cis565_stream_compaction_test.exe' (Win32): Loaded 'C:\Windows\System32\dxgi.dll'. Cannot find or open the PDB file. -The thread 0x61a0 has exited with code 0 (0x0). -The thread 0x3554 has exited with code 0 (0x0). -The thread 0x3f60 has exited with code 0 (0x0). -The thread 0x3ef0 has exited with code 0 (0x0). -The thread 0x19f4 has exited with code 0 (0x0). -The thread 0x4948 has exited with code 0 (0x0). -The program '[11188] cis565_stream_compaction_test.exe' has exited with code 0 (0x0).``` \ No newline at end of file +``` +**************** +** SCAN TESTS ** +**************** + [ 25 18 37 41 29 8 10 48 21 22 19 18 6 ... 39 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 0.0588ms (std::chrono Measured) + [ 0 25 43 80 121 150 158 168 216 237 259 278 296 ... 25536 25575 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 0.0569ms (std::chrono Measured) + [ 0 25 43 80 121 150 158 168 216 237 259 278 296 ... 25478 25497 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 0.10656ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 0.105216ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 0.116672ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 0.13984ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 0.119264ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 0.071648ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 2 2 1 0 2 1 1 0 1 0 1 0 1 ... 1 0 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 0.0038ms (std::chrono Measured) + [ 2 2 1 2 1 1 1 1 1 3 2 1 2 ... 2 1 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 0.0032ms (std::chrono Measured) + [ 2 2 1 2 1 1 1 1 1 3 2 1 2 ... 1 1 ] + passed +==== cpu compact with scan ==== + elapsed time: 0.069ms (std::chrono Measured) + [ 2 2 1 2 1 1 1 1 1 3 2 1 2 ... 2 1 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 0.199904ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 0.1856ms (CUDA Measured) + passed +Press any key to continue . . .``` \ No newline at end of file From a35041877ead64b9093639ed6fbdd87f2170846a Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 23:44:17 -0400 Subject: [PATCH 6/8] done --- Project2-Character-Recognition/README.md | 16 ++- .../character_recognition/mlp.cu | 111 ++++++++++++++++-- .../character_recognition/mlp.h | 4 +- Project2-Character-Recognition/img/output.png | Bin 0 -> 31058 bytes Project2-Character-Recognition/src/main.cpp | 47 ++++++-- Project2-Stream-Compaction/README.md | 3 + README.md | 11 +- 7 files changed, 160 insertions(+), 32 deletions(-) create mode 100644 Project2-Character-Recognition/img/output.png diff --git a/Project2-Character-Recognition/README.md b/Project2-Character-Recognition/README.md index 4503fac..1522358 100644 --- a/Project2-Character-Recognition/README.md +++ b/Project2-Character-Recognition/README.md @@ -3,12 +3,16 @@ CUDA Character Recognition **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Joshua Nadel + * https://www.linkedin.com/in/joshua-nadel-379382136/, http://www.joshnadel.com/ +* Tested on: Windows 10, i7-6700HQ @ 2.60GHz 16GB, GTX 970M (Personal laptop) -### (TODO: Your README) +### Character Recognition -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +![](img/output.png) +The program outputs the total error before training and total error after training. As you can see, total error decreases after optimizing the network's weights on training data. + +My implementation is hard-coded at 3 layers. I cannot test framerates at other network complexities. + +I am limited to a data set of 101 by 101 images. I have no other training or testing data to compare performance on image size with. diff --git a/Project2-Character-Recognition/character_recognition/mlp.cu b/Project2-Character-Recognition/character_recognition/mlp.cu index 33c84b3..198e5a1 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.cu +++ b/Project2-Character-Recognition/character_recognition/mlp.cu @@ -2,6 +2,7 @@ #include #include "common.h" #include "mlp.h" +#include #define DIM 101 #define LABELS 52 @@ -28,18 +29,104 @@ namespace CharacterRecognition { int hiddenDims = inputDims; int outputDims = LABELS; - __global__ void backprop() { + __global__ void backprop(float *inputLr, float *hiddenLr, float *outputLr, int n_input, int n_hidden, int n_output, float *weightsIH, float *weightsHO, float *d_weightsIH, float *d_weightsHO, float label) { int index = threadIdx.x + (blockIdx.x * blockDim.x); - if (index >= n_output) { + if (index >= n_input) { + return; + } + + for (int i = 0; i < n_hidden; i++) { + float totalError = 0.0f; + float gradientProd = 1.0f; + float weightProd = 1.0f; + for (int j = 0; j < n_output; j++) { + float expected = label == j ? 1 : 0; + float error = expected - outputLr[j]; + + int weight2Index = i + j * n_output; + d_weightsHO[weight2Index] += hiddenLr[i] * outputLr[j] * (1 - outputLr[j]) * -error; + totalError += error; + gradientProd *= outputLr[j] * (1 - outputLr[j]); + weightProd *= weightsHO[weight2Index]; + } + int weight1Index = index + i * n_hidden; + d_weightsIH[weight1Index] += inputLr[index] * hiddenLr[i] * (1 - hiddenLr[i]) * -totalError * gradientProd * weightProd; + } + } + + __global__ void zeroBuffer(float *buffer, int n) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + buffer[index] = 0; + } + + __global__ void addTwoBuffers(float *addTo, float *addFrom, float lambda, int n) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { return; } + + addTo[index] += addFrom[index] * lambda; } - void train() { + void train(float lambda) { + dim3 fullBlocksPerGrid((inputDims + blockSize - 1) / blockSize); + dim3 fullBlocksPerGridInToHid((inputDims * hiddenDims + blockSize - 1) / blockSize); + dim3 fullBlocksPerGridHidToOut((hiddenDims * outputDims + blockSize - 1) / blockSize); + float totalError = 0; + + float *dev_d_w_kj; + float *dev_d_w_ki; + cudaMalloc((void**)&dev_d_w_kj, inputDims * hiddenDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_d_w_kj failed!"); + cudaMalloc((void**)&dev_d_w_ki, hiddenDims * outputDims * sizeof(float)); + checkCUDAErrorWithLine("cudaMalloc dev_d_w_ki failed!"); + + for (int i = 0; i < LABELS; i++) { + + std::string filename = "../data-set/"; + std::string number = std::to_string(i + 1); + if (number.length() == 1) { + number = std::string("0").append(number); + } + filename.append(number); + filename.append("info.txt"); + FILE * image = std::fopen(filename.c_str(), "r"); + int label; + int dimensions; + fscanf(image, "%d", &label); + fscanf(image, "%d", &dimensions); + float *colors = new float[dimensions]; + for (int j = 0; j < dimensions; j++) { + int color; + fscanf(image, "%d", &color); + colors[j] = color; + } + + float *output = new float[outputDims]; + evaluate(colors, output); + backprop << > > (dev_input, dev_hidden, dev_output, inputDims, hiddenDims, outputDims, dev_w_kj, dev_w_ki, dev_d_w_kj, dev_d_w_ki, i); + for (int j = 0; j < outputDims; j++) { + float expected = i == j ? 1 : 0; + float error = expected - output[j]; + totalError += error * error; + } + delete[] colors; + delete[] output; + } + totalError /= 2.f; + + addTwoBuffers << > > (dev_w_kj, dev_d_w_kj, -lambda * totalError, inputDims * hiddenDims); + addTwoBuffers << > > (dev_w_ki, dev_d_w_ki, -lambda * totalError, hiddenDims * outputDims); + + cudaFree(dev_d_w_kj); + cudaFree(dev_d_w_ki); - -PRODUCT(C7 - R9, (1 / (1 + EXP(-R9))), (1 - 1 / (1 + EXP(-R9))), O5) - -PRODUCT(J5, 1 / (1 + EXP(-O5)), 1 - 1 / (1 + EXP(-O5)), C7 - R9, 1 / (1 + EXP(-R9)), 1 - 1 / (1 + EXP(-R9)), P8) + printf("Total error is %f\n", totalError); } __global__ void kernComputeLayer(float *inputLr, float *outputLr, int n_input, int n_output, float *weights) { @@ -66,21 +153,21 @@ namespace CharacterRecognition { printf("]\n"); } - void evaluate(float *input) { - float *results = new float[outputDims]; - + void evaluate(float *input, float *output) { dim3 fullBlocksPerGridInToHid((inputDims * hiddenDims + blockSize - 1) / blockSize); dim3 fullBlocksPerGridHidToOut((hiddenDims * outputDims + blockSize - 1) / blockSize); + dim3 fullBlocksPerGrid((inputDims + blockSize - 1) / blockSize); + + zeroBuffer << > > (dev_input, inputDims); + zeroBuffer << > > (dev_hidden, hiddenDims); + zeroBuffer << > > (dev_output, outputDims); cudaMemcpy(dev_input, input, sizeof(float) * inputDims, cudaMemcpyHostToDevice); kernComputeLayer << > > (dev_input, dev_hidden, inputDims, hiddenDims, dev_w_kj); kernComputeLayer << > > (dev_hidden, dev_output, hiddenDims, outputDims, dev_w_ki); - cudaMemcpy(results, dev_output, sizeof(float) * outputDims, cudaMemcpyDeviceToHost); - - printArray(results, outputDims); - delete[] results; + cudaMemcpy(output, dev_output, sizeof(float) * outputDims, cudaMemcpyDeviceToHost); } void init() { diff --git a/Project2-Character-Recognition/character_recognition/mlp.h b/Project2-Character-Recognition/character_recognition/mlp.h index 1ee8661..2a79d18 100644 --- a/Project2-Character-Recognition/character_recognition/mlp.h +++ b/Project2-Character-Recognition/character_recognition/mlp.h @@ -6,7 +6,7 @@ namespace CharacterRecognition { Common::PerformanceTimer& timer(); void init(); - void train(); - void evaluate(float *input); + void train(float lambda); + void evaluate(float *input, float *output); void end(); } diff --git a/Project2-Character-Recognition/img/output.png b/Project2-Character-Recognition/img/output.png new file mode 100644 index 0000000000000000000000000000000000000000..17d5abc40a0789e719d99aa296fb66adbbbcf6a4 GIT binary patch literal 31058 zcmeFZdsNct`ZwG@O?yu>H60wMG@YGOmNq$NrU>euX_TpHG$(;n#NT9!iHDFp0HQOy zUFMPP7-JfTDVj787(-AJbY`bwsE|`YDnhA%fkJ{viihXx>}mg=XT57Z>s{|!@3Y=N zUe@yPRk;t>eH}jc^|>y~PsKpreskBG8#Zis`}k*{ez{@8#y2)>_}%aR41C^@cll>2 z@a1>7FQK1o;L1H;01tmefBePA8#c6(H!XklC*b)n7d}hCZP>88Z2jYRWc^&)h7Co= znur>RIult{FL9Shqow}am`L5zae}DT3?r(;iq?DDvy~?uGZrJ0M zyVlKEv#$LC81dh)mvgep*WYj05b-&F>wlhpLLUhD{ePZb9n?qt=V1fm2l1HU7>8Sl%c`rW%94KzX>;XV;XJlS05qF%w4tKXDpUX0gq>Jdy=hcf2~eIhP^T3|fL z6jq>!rG}nqW$we)Z`qz4VWavaB$M%aOvdewY4-z5?S9?W?w5Y@4k+-v?|$(R8nw{2 zipb6MNckQ{>aD-i3ej#ScLd}PCTty(WoZ2h*Oo$53Q~1XiKnvI-#zL&lEu&dAb*A-!dI%2Lc$Z5@lB~YB@zY)b+9v8-|9mv&9U%c($1H-F17E6Lz`F|(0aDn? z!LDnpiG|`QlHU<0OWaF*eB<<>q>F2cKijFqJE(FHU78s#|G}icmxZntt!a^bns6?H z^}mYK*|5<(x3{7zhvM+c?9|FG<%WNXAPI)HJejP#328dfl0wmKOZoj2z84gaC+lmT zLJz9d?V~KZj{@`^j97Ts@&%U4f|zD&Wf!vzU1#+DXnc<|)2c1UBtFCqj+w-SoI%hP z=W)lArd#U5wnh)e;?vR@W_{DRP+K+<{Q7Jk!GW`N4R8AAt5e|aPg`H*-E=&Y5dNzq z^9Mz!l(qcsP}HytX5ITSm3fO${%qCWH7g7s*VTb^JB)D@sDFNLIbNlfUA9j=a>7=N zPCK1E<-wpP8iPew*S)_`PAWuzyX%c z1CIK~{^fhjOmCjzX*ThGx1!%%MZ>o(d@ede`X=ZwI1(D+@ombs1M?C?=e^Z4g>BZR z0;A{Hl%$`rskLRB88aA1gJ_$VZ_ggo55!SkJF}Nf6SusNkWp`GSnc++A9Z;)drkA^ zMYf4^>KE_%&K8c*E!H4g*yK4cLIRV&LviD*b8k%Ag8rYzk4DLZX7BM$fwAmVjj9nh zNHO&st~6&TTN_rF6YlLYxo&3%*PrlA{bBcS8+_*B=AINlxHnRtTXOT}5^OOm@?CF( zZaU~!6Tgbn;)_cETeKa)&I(}ZrKvGrZ2y7Hp+ z`BMf+y0sd{tS<>7#x;oA&z2OZ-K?U=nt6k#aa8u;1h>Gxt2bgkAJ(M5%>m^wj=xqG zU%x}9-`Qs_rK@4n-;^Az-13S@>Mffjh(5A^Ervzb+%0Nyzl6MR*;OyzIzS!8QksrHL7r>M`0Aiw?etxG6;&+`U=WPJ@+b1 z;Jg(6S#J)go!VASY-%>WR+^UxV4G76QrtV#w-+GzK~ocKqF`FwFDe$&OibpP2CA`zR7;O`Jzi0qLmfHhy+O#k!iKilw(f6e{1kK3@J_&eL4|NQD}#h0i*{^#js!v9kM@qc@=<*c>k;R;s7 zf6wWnxJP;fywN?-UAlkO-fjKQO1r*Aeb>_CgW@hvmZ7v_Q|}kWac3x#PFcV=YYryM znR6spw>|KpgazJyUy(O97!c>CJkvfSPjIzw;{;C=6{ZaSYUKe)ZLw&k}vB_b5XDYJB;g8D_*>M%m^q$7xE9q3`o1)8(XK)rXz9uDP=p z3_;ccdhBS8{2#a4+g}g0eD{Av7=^zfj90IgEt<$obA620lgW7 z)T-UbVD_*E`|+_?XTkKc;o{dje$_d#C*X~5jJOtUp>F`q(<`Wp8s9Bi{m>WZ6~}m! z#GN_}#bnR%-|xN_4!k{Sh?&1{V(ssFhP{V>ThFsGH}^u+Tc(?B-u>WtlH)=|SMl|7 z)q{%;<|4I$yL%F4?cD5g7RbX2xF3MgdH%3z$5*S7GPIvy?n zJ$Hu$BKRzb;+Kw0t5t9mDlQp&NF`2a5&9a{C}B3%u-xp3z;wSB1>+VViaq>*Y}=-5 zo{L`<6<-hEu6!`x5RvZvFDL2^&Aa~w5od4yUqHlQz>7pIvFlcZTO$#s-EK!xcvoF* zLG=kCMo$o#grO$9iDi3Ys+Y79X7XO zGF~5-OOC&T33B%OK_do bZ%w237~t8a)Qi|_J$Kx`2+FxtZM_qhFr;KoLq1 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 Ru`tS>T_NAozx(Oe{{;hJ{wn|g literal 0 HcmV?d00001 diff --git a/Project2-Character-Recognition/src/main.cpp b/Project2-Character-Recognition/src/main.cpp index 7528655..34780f7 100644 --- a/Project2-Character-Recognition/src/main.cpp +++ b/Project2-Character-Recognition/src/main.cpp @@ -11,24 +11,57 @@ #include #include "testing_helpers.hpp" -int main(int argc, char* argv[]) { - CharacterRecognition::init(); - //CharacterRecognition::train(); +void printArray(const float *array, int n) { + printf("["); + for (int i = 0; i < n; i++) { + printf("%f, ", array[i]); + } + printf("]\n"); +} - FILE * image = std::fopen("../data-set/02info.txt", "r"); +void testImage(int i, float *output) { + std::string filename = "../data-set/"; + std::string number = std::to_string(i + 1); + if (number.length() == 1) { + number = std::string("0").append(number); + } + filename.append(number); + filename.append("info.txt"); + FILE * image = std::fopen(filename.c_str(), "r"); int label; int dimensions; fscanf(image, "%d", &label); fscanf(image, "%d", &dimensions); float *colors = new float[dimensions]; - for (int i = 0; i < dimensions; i++) { + for (int j = 0; j < dimensions; j++) { int color; fscanf(image, "%d", &color); - colors[i] = color; + colors[j] = color; } - CharacterRecognition::evaluate(colors); + + CharacterRecognition::evaluate(colors, output); fclose(image); delete[] colors; +} + +int main(int argc, char* argv[]) { + float *output = new float[52]; + + CharacterRecognition::init(); + + testImage(0, output); + //printArray(output, 52); + + CharacterRecognition::train(0.2f); + + testImage(0, output); + //printArray(output, 52); + + CharacterRecognition::train(0.2f); + + //printArray(output, 52); + + delete[] output; CharacterRecognition::end(); } diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 29b3932..7139e08 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -18,7 +18,10 @@ The list of features includes: * Work-efficient compact implementation on the GPU * Wrapper for thrust's scan implementation to compare performance +Tested at block size of 128. + ![](img/timeOverLength.png) +Less runtime means a more optimal implementation I do not know how the thrust implementation manages to be so efficient at such large array sizes. In fact, it seems to become increasingly efficient with array length. diff --git a/README.md b/README.md index 3a0b2fe..6797222 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,14 @@ CUDA Number Algorithms **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Joshua Nadel + * https://www.linkedin.com/in/joshua-nadel-379382136/, http://www.joshnadel.com/ +* Tested on: Windows 10, i7-6700HQ @ 2.60GHz 16GB, GTX 970M (Personal laptop) -### (TODO: Your README) +### Number Algorithms -Link to the readmes of the other two subprojects. +[Stream Compaction](Project2-Stream-Compaction/README.md) +[Character Recognition](Project2-Character-Recognition/README.md) Add anything else you think is relevant up to this point. (Remember, this is public, so don't put anything here that you don't want to share with the world.) From f735e4c45a673af7f26ce3ea1d52e728c61576ad Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 23:45:18 -0400 Subject: [PATCH 7/8] done 2 --- Project2-Character-Recognition/README.md | 1 + Project2-Stream-Compaction/README.md | 1 + README.md | 1 + 3 files changed, 3 insertions(+) diff --git a/Project2-Character-Recognition/README.md b/Project2-Character-Recognition/README.md index 1522358..cb1b35b 100644 --- a/Project2-Character-Recognition/README.md +++ b/Project2-Character-Recognition/README.md @@ -10,6 +10,7 @@ CUDA Character Recognition ### Character Recognition ![](img/output.png) + The program outputs the total error before training and total error after training. As you can see, total error decreases after optimizing the network's weights on training data. My implementation is hard-coded at 3 layers. I cannot test framerates at other network complexities. diff --git a/Project2-Stream-Compaction/README.md b/Project2-Stream-Compaction/README.md index 7139e08..0d61941 100644 --- a/Project2-Stream-Compaction/README.md +++ b/Project2-Stream-Compaction/README.md @@ -21,6 +21,7 @@ The list of features includes: Tested at block size of 128. ![](img/timeOverLength.png) + Less runtime means a more optimal implementation I do not know how the thrust implementation manages to be so efficient at such large array sizes. In fact, it seems to become increasingly efficient with array length. diff --git a/README.md b/README.md index 6797222..f260cf6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ CUDA Number Algorithms ### Number Algorithms [Stream Compaction](Project2-Stream-Compaction/README.md) + [Character Recognition](Project2-Character-Recognition/README.md) Add anything else you think is relevant up to this point. From 3b696286f60351aa7713e1416418b9169fbdbef2 Mon Sep 17 00:00:00 2001 From: BobMowzie Date: Tue, 17 Sep 2019 23:47:37 -0400 Subject: [PATCH 8/8] done 3 --- Project2-Character-Recognition/img/output.png | Bin 31058 -> 4129 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Project2-Character-Recognition/img/output.png b/Project2-Character-Recognition/img/output.png index 17d5abc40a0789e719d99aa296fb66adbbbcf6a4..033d17c784b853272d2ce0057ba38fcd832ec226 100644 GIT binary patch literal 4129 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 ZyUa9wzMTpX$zJesZZ4kAFP%cJ{u8Q8CVT(@ literal 31058 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 bZ%w237~t8a)Qi|_J$Kx`2+FxtZM_qhFr;KoLq1 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 Ru`tS>T_NAozx(Oe{{;hJ{wn|g