Skip to content

Commit 3eef414

Browse files
authored
Dev/py unit tests and refactor ci (#1)
* Refactor pipeline and structure for pytests * Fixed issue, updated gitignore * Fixed potential issue in toposort cycle detection * Fixed gradient for getter_node, updated unit tests * Brought tensor construction and new ctors into python binding * removed unnecessary file * Moving towards bringing the right functions into Python interface * Reorder readme * Changed ctors, added graph creation to python binding * Added static creation methods, cleaned up macros, removed unnecessary func ptrs * Added unit tests, prepared proper structure for module, created python unit tests, fixed bugs and exposed new functions * Fix CI
1 parent efe61ab commit 3eef414

30 files changed

Lines changed: 974 additions & 493 deletions

.github/workflows/ci.yml

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ on:
66
branches: [main]
77

88
jobs:
9-
build:
10-
name: Build project
9+
test-debug:
10+
name: Build project and test in debug mode
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
@@ -20,13 +20,16 @@ jobs:
2020
uses: actions/checkout@v4
2121

2222
- name: Setup Python 3.10
23-
uses: actions/setup-python@v4
23+
uses: actions/setup-python@v5
2424
with:
2525
python-version: '3.10'
2626

27+
- name: Install pytest
28+
run: pip install pytest
29+
2730
- name: Install Boost Python and Python dev headers
2831
run: |
29-
sudo apt-get install -y \
32+
sudo apt-get update && sudo apt-get install -y \
3033
libboost-python-dev \
3134
python3-dev
3235
@@ -49,37 +52,78 @@ jobs:
4952
5053
- name: Build in Debug Mode
5154
run: |
52-
mkdir build && cd build && cmake -DCMake_Build_Type=Debug ..
55+
mkdir build && cd build
56+
cmake -DCMake_Build_Type=Debug -DBUILD_TESTS=ON ..
5357
make
5458
59+
- name: Run Unit tests
60+
run: cd build && ctest --output-on-failure --verbose
61+
62+
- name: Upload artifacts
63+
if: failure()
64+
uses: actions/upload-artifact@v4
65+
with:
66+
name: debug-build-output
67+
path: build/
68+
retention-days: 2
69+
70+
test-release:
71+
name: Build project and test in relese mode
72+
runs-on: ubuntu-latest
73+
strategy:
74+
matrix:
75+
gcc-version: [13]
76+
cmake-version: ['3.31.3']
77+
fail-fast: true
78+
79+
steps:
80+
- name: Checkout
81+
uses: actions/checkout@v4
82+
83+
- name: Setup Python 3.10
84+
uses: actions/setup-python@v5
85+
with:
86+
python-version: '3.10'
87+
88+
- name: Install pytest
89+
run: pip install pytest
90+
91+
- name: Install Boost Python and Python dev headers
92+
run: |
93+
sudo apt-get update && sudo apt-get install -y \
94+
libboost-python-dev \
95+
python3-dev
96+
97+
- name: Setup CMake
98+
uses: jwlawson/actions-setup-cmake@v2
99+
with:
100+
cmake-version: ${{ matrix.cmake-version }}
101+
102+
- name: Setup GCC
103+
run: |
104+
sudo apt-get update
105+
sudo apt-get install -y gcc-${{ matrix.gcc-version }} g++-${{ matrix.gcc-version }}
106+
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ matrix.gcc-version }} 100
107+
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${{ matrix.gcc-version }} 100
108+
109+
- name: Verify versions
110+
run: |
111+
gcc --version
112+
cmake --version
113+
55114
- name: Build in Release Mode
56115
run: |
57-
cd build
58-
echo "Deleting the debug build files in $(pwd)/build"
59-
rm -rf *
60-
cmake -DCMake_Build_Type=Release ..
116+
mkdir build && cd build
117+
cmake -DCMake_Build_Type=Release -DBUILD_TESTS=ON ..
61118
make
62119
120+
- name: Run Unit tests
121+
run: cd build && ctest --output-on-failure --verbose
122+
63123
- name: Upload artifacts
124+
if: failure()
64125
uses: actions/upload-artifact@v4
65126
with:
66-
name: build-output
127+
name: release-build-output
67128
path: build/
68-
retention-days: 1
69-
70-
# example of artifacts
71-
# - name: Upload artifacts
72-
# uses: actions/upload-artifact@v4
73-
# with:
74-
# name: build-${{ matrix.gcc-version }}-${{ github.run_id }}
75-
# path: output/
76-
#
77-
#test:
78-
# needs: build # Runs after build job
79-
# runs-on: ubuntu-latest
80-
# steps:
81-
# - name: Download artifact
82-
# uses: actions/download-artifact@v4
83-
# with:
84-
# name: build-output
85-
# path: output/
129+
retention-days: 2

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
build
22
.vscode
33
unit_tests_backend
4-
*.txt
4+
*.txt
5+
python_lib/dl_lib/_compiled
6+
*__pycache__*

CMakeLists.txt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ project(dllib VERSION 1.0.0 LANGUAGES CXX)
55
set(CMAKE_CXX_STANDARD 20)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77

8-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
9-
set(LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
108

119
# to link shared libs. TODO: can we get rid of this, as it may cost runtime?
1210
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
11+
set(PYTHON_MODULE_DIR "${CMAKE_SOURCE_DIR}/python_lib/dl_lib/_compiled")
1312

1413
# to enable find boost, see https://stackoverflow.com/a/79147222
1514
if(POLICY CMP0167)
@@ -104,10 +103,9 @@ include_directories("${PROJECT_SOURCE_DIR}/src"
104103
"${PROJECT_SOURCE_DIR}/examples")
105104

106105
add_subdirectory(src)
107-
add_subdirectory(examples)
108106

109-
option(BUILD_TESTING "Build tests" ON)
110-
if(BUILD_TESTING)
107+
option(BUILD_TESTS "Build tests" OFF)
108+
if(BUILD_TESTS)
111109
enable_testing()
112110
add_subdirectory(tests)
113111
endif()

python_lib/dl_lib/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from ._compiled._core import Tensor, Dimension, Device, Ones, Zeros, Gaussian
2+
3+
__all__ = ['Tensor', 'Device', 'Dimension']

python_lib/dl_lib/nn/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#from .._compiled._layers import FfLayer, ReLU
2+
#from .._compiled._core import Tensor # re-export if needed
3+
4+
#__all__ = ['FfLayer', 'ReLU']

readme.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
A from-scratch deep learning framework in modern C++ with Python bindings.
44

5+
## Motivation
6+
7+
Built to understand deep learning frameworks from first principles - from computational graphs to gradient computation to optimization algorithms.
8+
59
## Features
610

711
- **Computational Graph**: Dynamic graph construction with automatic differentiation
@@ -38,15 +42,16 @@ Roadmap:
3842
mkdir build && cd build
3943
cmake ..
4044
make
41-
./run_tests
45+
ctest
4246
```
4347

4448
## Required
4549

46-
- Python 3 (we test with 3.10, but it should work with any version)
50+
- Compiler capable of C++20 at least (we test with gcc 12.3.0)
4751
- Boost Python
4852
- Cmake > 3.24
49-
- Compiler capable of C++20 at least (we test with gcc 12.3.0)
53+
- Python 3 (we test with 3.10, but it should work with any version)
54+
- pytest for unit tests (we use 9.0.2)
5055

5156
## Troubleshooting
5257

@@ -55,10 +60,6 @@ make
5560

5661
The implementation of the Python wrapper does not work on MSVC6/7 in its current form. This is due to an issue that arises from Boost Python in combination with these compilers. Workarounds are proposed, but not implemented. More information here [here](https://beta.boost.org/doc/libs/develop/libs/python/doc/html/tutorial/tutorial/exposing.html).
5762

58-
## Motivation
59-
60-
Built to understand deep learning frameworks from first principles - from computational graphs to gradient computation to optimization algorithms.
61-
6263
## License
6364

6465
MIT

src/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
add_subdirectory(backend)
33
add_subdirectory(python)
44

5-
target_link_libraries(py_data_modeling
5+
target_link_libraries(_core
66
${Boost_LIBRARIES}
77
${PYTHON_LIBRARIES}
88
BackendCore)
99

10-
target_include_directories(py_data_modeling PRIVATE
10+
target_include_directories(_core PRIVATE
1111
${PYTHON_INCLUDE_DIRS}
1212
${Boost_INCLUDE_DIRS})
1313

src/backend/computational_graph/getter_node.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ using namespace std;
1515
using namespace graph;
1616

1717
vector< shared_ptr<Tensor> > GetterNode::backward(const Tensor& upstreamGrad) {
18-
assert(!upstreamGrad.getRequiresGrad());
19-
return { make_shared<Tensor>(upstreamGrad.createDeepCopy()) };
18+
// upstreamGrad is scalar by definition
19+
assert(!upstreamGrad.getRequiresGrad() && upstreamGrad.getDims().nDims()==1);
20+
21+
auto res = make_shared<Tensor>(parents[0]->getDims(), parents[0]->getDevice(), false);
22+
for(tensorSize_t i=0; i<res->getSize(); i++){
23+
res->setItem(0, i);
24+
}
25+
26+
if(std::holds_alternative<tensorSize_t>(idx)){
27+
res->setItem(upstreamGrad.getItem(0), std::get<tensorSize_t>(idx));
28+
}
29+
else if(std::holds_alternative<multiDimIdx_t>(idx)){
30+
res->setItem(upstreamGrad.getItem(0), std::get<multiDimIdx_t>(idx));
31+
}
32+
else{
33+
__throw_runtime_error("Idx variant in unexpected state");
34+
}
35+
36+
return { std::move(res) };
2037
}

src/backend/computational_graph/getter_node.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
#include "graph_node.h"
1515

16+
#include <vector>
17+
#include <variant>
18+
1619
namespace graph{
1720
/**
1821
* @brief When calling a get function, say as in
@@ -21,9 +24,17 @@ namespace graph{
2124
*
2225
*/
2326
class GetterNode final : public GraphNode {
27+
using multiDimIdx_t = std::vector<tensorDim_t>;
28+
29+
private:
30+
const std::variant<tensorSize_t, multiDimIdx_t> idx;
31+
2432
public:
25-
explicit GetterNode(std::shared_ptr<Tensor> t)
26-
: GraphNode({std::move(t)}) {}
33+
explicit GetterNode(std::shared_ptr<Tensor> t, const tensorSize_t idx)
34+
: GraphNode({std::move(t)}), idx{idx} {}
35+
36+
explicit GetterNode(std::shared_ptr<Tensor> t, const multiDimIdx_t& idx)
37+
: GraphNode({std::move(t)}), idx{idx} {}
2738

2839
GetterNode(const GetterNode& other) = delete;
2940
GetterNode& operator=(const GetterNode& other) = delete;

src/backend/computational_graph/graph_creation.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ shared_ptr<Tensor> graph::get(const shared_ptr<Tensor>& t, tensorSize_t idx) {
103103
t->getDevice());
104104

105105
if(t->getRequiresGrad()){
106-
res->setCgNode(std::make_shared<graph::GetterNode>(t));
106+
res->setCgNode(std::make_shared<graph::GetterNode>(t, idx));
107107
assert(res->getRequiresGrad());
108108
}
109109
return res;
@@ -115,13 +115,25 @@ shared_ptr<Tensor> graph::get(const shared_ptr<Tensor>& t, tensorSize_t idx) {
115115
*
116116
* loss = loss + other.get(i), we need to make sure get(i) can map to computational graph.
117117
*/
118-
shared_ptr<Tensor> graph::get(const shared_ptr<Tensor>& t, vector<tensorDim_t>&& idx) {
118+
shared_ptr<Tensor> graph::get(const shared_ptr<Tensor>& t, const vector<tensorDim_t>& idx) {
119119
ftype val = t->getItem(std::move(idx));
120120
auto res = make_shared<Tensor>(std::vector<tensorDim_t>{1}, std::vector<ftype>{val},
121121
t->getDevice());
122122
if(t->getRequiresGrad()){
123-
res->setCgNode(std::make_shared<graph::GetterNode>(t));
123+
res->setCgNode(std::make_shared<graph::GetterNode>(t, idx));
124124
assert(res->getRequiresGrad());
125125
}
126126
return res;
127+
}
128+
129+
/**
130+
* @brief Takes the sum of the whole tensor, then returns result as vector.
131+
*/
132+
shared_ptr<Tensor> graph::sumTensor(const shared_ptr<Tensor> t) {
133+
auto res = make_shared<Tensor>(std::vector<tensorDim_t>{1}, std::vector<ftype>{0.0},
134+
t->getDevice(), t->getRequiresGrad());
135+
for(tensorSize_t i=0; i<t->getSize(); i++){
136+
res = graph::add(res, graph::get(t, i));
137+
}
138+
return res;
127139
}

0 commit comments

Comments
 (0)