diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f81ecb0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: main + paths: ['**.cu','**.c','**.cpp', '**.h', '**CMakeLists.txt'] + pull_request: + branches: main + paths: ['**.cu','**.c','**.cpp', '**.h', '**CMakeLists.txt'] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + pip install pandas + + - name: Build project + run: | + make build + + - name: Run test suite + run: | + make test \ No newline at end of file diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml new file mode 100644 index 0000000..7a6d342 --- /dev/null +++ b/.github/workflows/cpp-linter.yml @@ -0,0 +1,33 @@ +name: cpp-linter +on: + pull_request: + branches: main + paths: ['**.cu','**.cpp','**.c', '**.h', '**CMakeLists.txt'] + push: + branches: main + paths: ['**.cu','**.cpp','**.c', '**.h', '**CMakeLists.txt'] + +permissions: + contents: write + pull-requests: write + actions: write + +jobs: + cpp-linter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: cpp-linter/cpp-linter-action@v2 + id: linter + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + style: 'file' # Use .clang-format config file. + tidy-checks: '-*' # disable clang-tidy checks. + version: 17 + thread-comments: true + format-review: true + + - name: Run clang-format + if: steps.linter.outputs.clang-format-checks-failed > 0 + run: exit 1 diff --git a/.gitignore b/.gitignore index 259148f..ab5ee96 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,8 @@ *.exe *.out *.app + +# misc +.vscode +build +results.csv diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bc6a5b..91ac814 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.16) # Set the project name project(ichida-algo) @@ -7,4 +7,15 @@ set(CMAKE_CXX_FLAGS "-O3 -Wall -Wextra") set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED True) -add_executable(speed_cpu src/main.c) \ No newline at end of file +set(SRC_DIR src) +set(INC_DIR include) + +# Source files +file(GLOB_RECURSE SOURCE_FILES ${SRC_DIR}/*.c) + +include_directories(include) +add_executable(speed_cpu ${SOURCE_FILES}) +target_link_libraries(speed_cpu m) + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4d977cd --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ + +.PHONY: all test clean run build run_test + +clean: + rm -rf build + rm speed_cpu + +build: + cmake -Bbuild + $(MAKE) -C ./build + mv ./build/speed_cpu ./ + +run: build + ./speed_demo_cpu.sh ./weights_and_biases.txt ./tensors + +run_test: build + ./speed_cpu ./weights_and_biases.txt ./tensors + +test: build + ./speed_demo_cpu.sh ./weights_and_biases.txt ./tensors + mv ./results.csv ./test + python3 ./test/verify_csv.py + + diff --git a/include/matrix.h b/include/matrix.h new file mode 100644 index 0000000..a7a6741 --- /dev/null +++ b/include/matrix.h @@ -0,0 +1,17 @@ +#pragma once + +typedef struct { + int rows; + int cols; + float** data; +} matrix; + +matrix* createMatrix(int rows, int cols); + +void multiplyMatrices(const matrix* a, const matrix* b, const matrix* result); + +void addMatrix(matrix* a, const matrix* b); + +void ReLU(matrix* a); + +void softmax(matrix* a); diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..b911c6f --- /dev/null +++ b/include/util.h @@ -0,0 +1,5 @@ +#pragma once + +char letters[52] = {'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f', 'G', 'g', 'H', 'h', 'I', 'i', + 'J', 'j', 'K', 'k', 'L', 'l', 'M', 'm', 'N', 'n', 'O', 'o', 'P', 'p', 'Q', 'q', 'R', 'r', + 'S', 's', 'T', 't', 'U', 'u', 'V', 'v', 'W', 'w', 'X', 'x', 'Y', 'y', 'Z', 'z'}; diff --git a/speed_demo_cpu.sh b/speed_demo_cpu.sh old mode 100644 new mode 100755 diff --git a/src/main.c b/src/main.c index 01e208c..a91e884 100644 --- a/src/main.c +++ b/src/main.c @@ -1,71 +1,13 @@ -#include +#include "matrix.h" +#include "util.h" +#include #include #include #include -typedef struct { - int rows; - int cols; - float** data; -} matrix; - matrix* weight[7]; matrix* biase[7]; -matrix* createMatrix(int rows, int cols) { - matrix* res = (matrix*)malloc(sizeof(matrix)); - res->rows = rows; - res->cols = cols; - res->data = (float**)malloc(rows * sizeof(float*)); - for (int i = 0; i < rows; i++) { - res->data[i] = (float*)malloc(cols * sizeof(float)); - } - return res; -} - -void multiplyMatrices(const matrix* a, const matrix* b, const matrix* result) { - for (int i = 0; i < result->rows; i++) { - for (int j = 0; j < result->cols; j++) { - float sum = 0; - for (int k = 0; k < a->cols; k++) { - sum += (a->data)[i][k] * ((b->data)[k][j]); - } - (result->data)[i][j] = sum; - } - } -} - -void addMatrix(matrix* a, const matrix* b) { - for (int i = 0; i < a->rows; i++) { - for (int j = 0; j < a->cols; j++) { - (a->data)[i][j] += (b->data)[i][j]; - } - } -} - -void ReLU(matrix* a) { - for (int i = 0; i < a->rows; i++) { - for (int j = 0; j < a->cols; j++) { - if ((a->data)[i][j] < (float)0) - (a->data)[i][j] = (float)0; - } - } -} - -void softmax(matrix* a) { - float res = (float)0; - for (int i = 0; i < a->rows; i++) { - for (int j = 0; j < a->cols; j++) { - res += exp((a->data)[i][j]); - } - } - for (int i = 0; i < a->rows; i++) { - for (int j = 0; j < a->cols; j++) { - (a->data)[i][j] /= res; - } - } -} - void processWeight(char* line, int layer) { char* token; float value; @@ -95,8 +37,8 @@ void processBiase(char* line, int layer) { } } -void readModel() { - FILE* file = fopen("../weights_and_biases.txt", "r"); +void readModel(const char* fileName) { + FILE* file = fopen(fileName, "r"); char* line = NULL; size_t len = 0; @@ -117,9 +59,8 @@ void readModel() { fclose(file); } -void readInput(matrix* a, char* fileName) { +void readInput(matrix* a, const char* fileName) { FILE* file = fopen(fileName, "r"); - char* line = NULL; size_t len = 0; ssize_t read; @@ -145,9 +86,10 @@ void propagateForward(const matrix* weight, const matrix* input, matrix* nextLay addMatrix(nextLayer, biase); } +// Get result from output layer int getResult(matrix* a) { int idx = 0; - float res = INT32_MIN; + float res = (a->data)[0][0]; for (int i = 0; i < a->rows; i++) { if (res < (a->data)[i][0]) { res = (a->data)[i][0]; @@ -182,11 +124,12 @@ int inference(matrix* input) { propagateForward(weight[6], layer[5], layer[6], biase[6]); softmax(layer[6]); + return getResult(layer[6]); } -int main() { - +int main(int argc, char* argv[]) { + // TODO: find a way to load static weights and biases // Load model (The memory of those code should be initialize during compile time to enchance the speed) weight[0] = createMatrix(98, 225); weight[1] = createMatrix(65, 98); @@ -204,30 +147,53 @@ int main() { biase[5] = createMatrix(40, 1); biase[6] = createMatrix(52, 1); - readModel(); + readModel(argv[1]); - char str[10]; - char fileName[100]; - matrix* input = createMatrix(255, 1); + // Run program + const char* directory_path = argv[2]; + struct dirent* entry; + DIR* dir = opendir(directory_path); - for (int i = 1; i <= 52; i++) { - if (i < 10) { - sprintf(str, "0%d", i); - } else { - sprintf(str, "%d", i); + matrix* input = createMatrix(225, 1); + + // Read and process inputs + char* fileName = (char*)malloc((100) * sizeof(char)); + char* fileNumStr = (char*)malloc((100) * sizeof(char)); + + int fileNum; + int size = 0; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_REG) { + size++; } - strcpy(fileName, "../tensors/"); - strcat(fileName, str); - strcat(fileName, "out.txt"); - - // Read txt file - readInput(input, fileName); - - // Check result - if (inference(input) + 1 == i) { - printf("Test %d correct ✅\n", i); - } else { - printf("Test %d incorrect ❌\n", i); + } + + int* results = (int*)malloc((size + 1) * sizeof(int)); + dir = opendir(directory_path); + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_REG) { + strcpy(fileNumStr, entry->d_name); + fileNumStr[strlen(entry->d_name) - 7] = '\0'; + fileNum = atoi(entry->d_name); + strcpy(fileName, directory_path); + strcat(fileName, "/"); + strcat(fileName, entry->d_name); + readInput(input, fileName); + results[fileNum] = inference(input); } } + + free(fileName); + free(fileNumStr); + closedir(dir); + + // Write to csv file + FILE* fpt; + fpt = fopen("results.csv", "w+"); + fprintf(fpt, "image_number, guess\n"); + for (int i = 1; i <= size; i++) { + fprintf(fpt, "%d, %c\n", i, letters[results[i]]); + } + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/matrix.c b/src/matrix.c new file mode 100644 index 0000000..206dedb --- /dev/null +++ b/src/matrix.c @@ -0,0 +1,58 @@ +#include "matrix.h" +#include "math.h" +#include "stdio.h" +#include "stdlib.h" + +matrix* createMatrix(int rows, int cols) { + matrix* res = (matrix*)malloc(sizeof(matrix)); + res->rows = rows; + res->cols = cols; + res->data = (float**)malloc(rows * sizeof(float*)); + for (int i = 0; i < rows; i++) { + res->data[i] = (float*)malloc(cols * sizeof(float)); + } + return res; +} + +void multiplyMatrices(const matrix* a, const matrix* b, const matrix* result) { + for (int i = 0; i < result->rows; i++) { + for (int j = 0; j < result->cols; j++) { + float sum = 0; + for (int k = 0; k < a->cols; k++) { + sum += (a->data)[i][k] * ((b->data)[k][j]); + } + (result->data)[i][j] = sum; + } + } +} + +void addMatrix(matrix* a, const matrix* b) { + for (int i = 0; i < a->rows; i++) { + for (int j = 0; j < a->cols; j++) { + (a->data)[i][j] += (b->data)[i][j]; + } + } +} + +void ReLU(matrix* a) { + for (int i = 0; i < a->rows; i++) { + for (int j = 0; j < a->cols; j++) { + if ((a->data)[i][j] < (float)0) + (a->data)[i][j] = (float)0; + } + } +} + +void softmax(matrix* a) { + float res = (float)0; + for (int i = 0; i < a->rows; i++) { + for (int j = 0; j < a->cols; j++) { + res += exp((a->data)[i][j]); + } + } + for (int i = 0; i < a->rows; i++) { + for (int j = 0; j < a->cols; j++) { + (a->data)[i][j] /= res; + } + } +} diff --git a/test/expected_results.csv b/test/expected_results.csv new file mode 100644 index 0000000..997579e --- /dev/null +++ b/test/expected_results.csv @@ -0,0 +1,53 @@ +image_number, guess +1, A +2, a +3, B +4, b +5, C +6, c +7, D +8, d +9, E +10, e +11, F +12, f +13, G +14, g +15, H +16, h +17, I +18, i +19, J +20, j +21, K +22, k +23, L +24, l +25, M +26, m +27, N +28, n +29, O +30, o +31, P +32, p +33, Q +34, q +35, R +36, r +37, S +38, s +39, T +40, t +41, U +42, u +43, V +44, v +45, W +46, w +47, X +48, x +49, Y +50, y +51, Z +52, z diff --git a/test/verify_csv.py b/test/verify_csv.py new file mode 100644 index 0000000..59a90d9 --- /dev/null +++ b/test/verify_csv.py @@ -0,0 +1,15 @@ +import pandas as pd + +# Load the generated CSV +generated_csv = pd.read_csv('./test/results.csv') + +# Load the expected CSV +expected_csv = pd.read_csv('./test/expected_results.csv') + +# Check if the generated CSV matches the expected CSV +if generated_csv.equals(expected_csv): + print("CSV output is correct.") + exit(0) +else: + print("CSV output is incorrect.") + exit(1) \ No newline at end of file