Skip to content

Commit 5257d5d

Browse files
authored
Merge pull request #61 from GPUEngineering/feature/60-python-api
New Python package: gputils_api
2 parents 2d4f412 + 036ba13 commit 5257d5d

File tree

15 files changed

+293
-35
lines changed

15 files changed

+293
-35
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ jobs:
1414
steps:
1515
- name: checkout code
1616
uses: actions/checkout@v4
17-
1817
- name: run test script
1918
run: bash ./ci/script.sh
2019

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,5 +260,10 @@ paket-files/
260260
# Python Tools for Visual Studio (PTVS)
261261
__pycache__/
262262
*.pyc
263+
*.egg-info/
264+
*/venv/
265+
*/dist/
266+
*/build/
267+
263268

264269
cmake-*

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
<!-- ---------------------
9+
v1.7.0
10+
--------------------- -->
11+
## v1.7.0 - 3-12-2024
12+
13+
### Added
14+
15+
- The project now uses the GNU General Public License (GPL) v3; license file added
16+
- Introduces new Python package for storing and loading numpy arrays; in can be installed with `pip install gputils-api`;
17+
unit tests and documentation
18+
19+
### Fixed
20+
21+
- When compiling with `cmake`, the unit tests will not be compiled by default unless the flag `GPUTILS_BUILD_TEST` is set
22+
- Clang clippy recommendations applied
23+
- Proper error handling when binary tensor file is not found
24+
25+
826
<!-- ---------------------
927
v1.6.0
1028
--------------------- -->

CMakeLists.txt

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
22
# GPUtils
33
# ====================================================================
44
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
5-
65
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.29")
76
cmake_policy(SET CMP0135 NEW)
87
endif()
9-
8+
# ----
109
# Set C++ version and SM architecture
1110
if (NOT DEFINED CPPVERSION)
1211
set(CPPVERSION 20) # A40: 20, Orin: 17
1312
endif()
1413
if (NOT DEFINED SM_ARCH)
1514
set(SM_ARCH 86)# A40: 86, Orin: 87
1615
endif()
17-
18-
16+
# ----
1917
project(GPUtils
2018
DESCRIPTION "Easy use of vectors and matrices on GPGPU devices."
2119
HOMEPAGE_URL "https://github.com/GPUEngineering/GPUtils"
@@ -31,8 +29,8 @@ set(CMAKE_CUDA_FLAGS "-std=c++${CPPVERSION}")
3129
set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS}; "-std=c++${CPPVERSION}")
3230
enable_language(CUDA)
3331
# ----
34-
add_library(device_compiler_flags INTERFACE)
35-
target_compile_features(device_compiler_flags INTERFACE cxx_std_${CPPVERSION})
32+
add_library(gputils_compiler_flags INTERFACE)
33+
target_compile_features(gputils_compiler_flags INTERFACE cxx_std_${CPPVERSION})
3634
set(CMAKE_CXX_EXTENSIONS OFF)
3735
# ----
3836
add_library(developer_flags INTERFACE)
@@ -45,30 +43,31 @@ target_compile_options(developer_flags
4543
# flags for CUDA builds
4644
$<$<COMPILE_LANGUAGE:CUDA>:${cuda_flags}>
4745
)
48-
target_link_libraries(device_compiler_flags INTERFACE $<BUILD_INTERFACE:developer_flags>)
46+
target_link_libraries(gputils_compiler_flags INTERFACE $<BUILD_INTERFACE:developer_flags>)
4947
# ----
50-
51-
52-
# ====================================================================
53-
# comment out for release
54-
# ====================================================================
55-
add_executable(main)
56-
target_sources(main
48+
add_executable(gputils_main)
49+
target_sources(gputils_main
5750
PRIVATE
5851
main.cu
5952
)
60-
target_link_libraries(main
53+
target_link_libraries(gputils_main
6154
PRIVATE
62-
device_compiler_flags
55+
gputils_compiler_flags
6356
cublas
6457
cusolver
6558
cudadevrt
6659
)
67-
target_include_directories(main
60+
target_include_directories(gputils_main
6861
PRIVATE
6962
"${PROJECT_BINARY_DIR}"
7063
"${PROJECT_SOURCE_DIR}/include"
7164
)
7265
# ----
73-
add_subdirectory(test)
66+
if(NOT GPUTILS_BUILD_TEST)
67+
set(GPUTILS_BUILD_TEST OFF) # Set to ON for local testing (or add `-DGPUTILS_BUILD_TEST=ON` to your CMake profile)
68+
endif()
69+
if (GPUTILS_BUILD_TEST)
70+
add_subdirectory(test)
71+
endif()
72+
unset(GPUTILS_BUILD_TEST CACHE)
7473
# ----

ci/script.sh

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ tests() {
77
cpp_version=17 # default
88
sm_arch=86 # default
99
hwInfoOrin=`lshw | grep Orin` ||
10-
if [ ! -z "${hwInfoOrin}" ]; then
10+
if [ -n "${hwInfoOrin}" ]; then
1111
echo "Running on Orin";
1212
sm_arch=87
1313
cpp_version=17
@@ -17,12 +17,26 @@ tests() {
1717
cpp_version=20
1818
fi
1919

20+
21+
# ------------------------------------
22+
# Run Python tests first
23+
# ------------------------------------
24+
pushd python
25+
export PYTHONPATH=.
26+
python -m venv venv
27+
source venv/bin/activate
28+
pip install --upgrade pip
29+
pip install .
30+
python -W ignore test/test.py -v
31+
deactivate
32+
popd
33+
2034
# ------------------------------------
2135
# Run tensor gtests
2236
# ------------------------------------
2337

2438
# -- create build files
25-
cmake -DCPPVERSION=${cpp_version} -DSM_ARCH=${sm_arch} -S . -B ./build -Wno-dev
39+
cmake -DCPPVERSION=${cpp_version} -DSM_ARCH=${sm_arch} -DGPUTILS_BUILD_TEST=ON -S . -B ./build -Wno-dev
2640

2741
# -- build files in build folder
2842
cmake --build ./build
@@ -34,7 +48,7 @@ tests() {
3448

3549
# -- run compute sanitizer
3650
pushd ./build/test
37-
mem=$(/usr/local/cuda/bin/compute-sanitizer --tool memcheck --leak-check=full ./device_test)
51+
mem=$(/usr/local/cuda/bin/compute-sanitizer --tool memcheck --leak-check=full ./gputils_test)
3852
grep "0 errors" <<< "$mem"
3953
popd
4054

@@ -44,7 +58,7 @@ tests() {
4458

4559
# -- create build files
4660
cd example
47-
cmake -DCPPVERSION=${cpp_version} -DSM_ARCH=${sm_arch} -S . -B ./build -Wno-dev
61+
cmake -DCPPVERSION=${cpp_version} -DSM_ARCH=${sm_arch} -S . -B ./build -Wno-dev
4862

4963
# -- build files in build folder
5064
cmake --build ./build

example/CMakeLists.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ target_compile_options(example_developer_flags
2828
# flags for CUDA builds
2929
$<$<COMPILE_LANGUAGE:CUDA>:${cuda_flags}>
3030
)
31-
target_link_libraries(example_compiler_flags INTERFACE $<BUILD_INTERFACE:example_developer_flags>
32-
)
31+
target_link_libraries(example_compiler_flags INTERFACE $<BUILD_INTERFACE:example_developer_flags>)
3332
# ----
34-
set(GPUTILS_BUILD_TESTING OFF)
3533
include(FetchContent)
3634
FetchContent_Declare(
3735
gputils

include/tensor.cuh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ data_t<T> vectorFromTextFile(std::string path_to_file) {
608608
data_t<T> dataStruct;
609609
std::ifstream file;
610610
file.open(path_to_file, std::ios::in);
611-
if (!file.is_open()) { throw std::invalid_argument("the file you provided does not exist"); };
611+
if (!file.is_open()) { throw std::invalid_argument("[vectorFromTextFile] the file does not exist"); }
612612

613613
std::string line;
614614
getline(file, line); dataStruct.numRows = atoi(line.c_str());
@@ -655,6 +655,7 @@ data_t<T> vectorFromBinaryFile(std::string path_to_file) {
655655
/* Read from binary file */
656656
std::ifstream inFile;
657657
inFile.open(path_to_file, std::ios::binary);
658+
if (!inFile.is_open()) { throw std::invalid_argument("[vectorFromBinaryFile] the file does not exist"); }
658659
inFile.read(reinterpret_cast<char *>(&(dataStruct.numRows)), sizeof(uint64_t));
659660
inFile.read(reinterpret_cast<char *>(&(dataStruct.numCols)), sizeof(uint64_t));
660661
inFile.read(reinterpret_cast<char *>(&(dataStruct.numMats)), sizeof(uint64_t));
@@ -723,7 +724,7 @@ void DTensor<T>::reshape(size_t newNumRows, size_t newNumCols, size_t newNumMats
723724
char errMessage[256];
724725
sprintf(errMessage,
725726
"DTensor[%lu x %lu x %lu] with %lu elements cannot be reshaped into DTensor[%lu x %lu x %lu] (%lu elements)",
726-
numRows(), numRows(), numMats(), numEl(), newNumRows, newNumCols, newNumMats, newNumElements);
727+
numRows(), numCols(), numMats(), numEl(), newNumRows, newNumCols, newNumMats, newNumElements);
727728
throw std::invalid_argument(errMessage);
728729
}
729730

python/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## GPUtils API
2+
3+
### Installation
4+
5+
As simple as...
6+
```bash
7+
pip install gputils-api
8+
```
9+
of course, preferably from within a virtual environment.
10+
11+
### Write to file
12+
13+
```python
14+
import numpy as np
15+
import gputils_api as g
16+
a = np.eye(3)
17+
g.write_array_to_gputils_binary_file(a, 'my_data.bt')
18+
```
19+
20+
### Read from file
21+
22+
```python
23+
import numpy as np
24+
import gputils_api as g
25+
x = g.read_array_from_gputils_binary_file('my_data.bt')
26+
```
27+
28+

python/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.7.0

python/gputils_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .gputils_api import *

python/gputils_api/gputils_api.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import numpy as np
2+
3+
def read_array_from_gputils_binary_file(path, dt=np.dtype('d')):
4+
"""
5+
Reads an array from a bt file
6+
:param path: path to file
7+
:param dt: numpy-compatible data type
8+
:raises ValueError: if the file name specified `path` does not have the .bt extension
9+
"""
10+
if not path.endswith(".bt"):
11+
raise ValueError("The file must have the .bt extension")
12+
with open(path, 'rb') as f:
13+
nr = int.from_bytes(f.read(8), byteorder='little', signed=False) # read number of rows
14+
nc = int.from_bytes(f.read(8), byteorder='little', signed=False) # read number of columns
15+
nm = int.from_bytes(f.read(8), byteorder='little', signed=False) # read number of matrices
16+
dat = np.fromfile(f, dtype=np.dtype(dt)) # read data
17+
dat = dat.reshape((nr, nc, nm)) # reshape
18+
return dat
19+
20+
21+
def write_array_to_gputils_binary_file(x, path):
22+
"""
23+
Writes a numpy array into a bt file
24+
25+
:param x: numpy array to save to file
26+
:param path: path to file
27+
:raises ValueError: if `x` has more than 3 dimensions
28+
:raises ValueError: if the file name specified `path` does not have the .bt extension
29+
"""
30+
if not path.endswith(".bt"):
31+
raise ValueError("The file must have the .bt extension")
32+
x_shape = x.shape
33+
x_dims = len(x_shape)
34+
if x_dims >= 4:
35+
raise ValueError("given array cannot have more than 3 dimensions")
36+
nr = x_shape[0]
37+
nc = x_shape[1] if x_dims >= 2 else 1
38+
nm = x_shape[2] if x_dims == 3 else 1
39+
with open(path, 'wb') as f:
40+
f.write(nr.to_bytes(8, 'little')) # write number of rows
41+
f.write(nc.to_bytes(8, 'little')) # write number of columns
42+
f.write(nm.to_bytes(8, 'little')) # write number of matrices
43+
x.reshape(nr*nc*nm, 1).tofile(f) # write data

python/setup.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python
2+
3+
from setuptools import setup, find_packages
4+
import io
5+
import os
6+
7+
# To publish to pypi, run:
8+
# rm -rf ./build ./dist opengen.egg-info ; pip install . ; python setup.py sdist bdist_wheel; twine upload dist/*
9+
10+
here = os.path.abspath(os.path.dirname(__file__))
11+
12+
NAME = 'gputils_api'
13+
14+
# Import version from file
15+
version_file = open(os.path.join(here, 'VERSION'))
16+
VERSION = version_file.read().strip()
17+
18+
DESCRIPTION = 'Python API for GPUtils'
19+
20+
21+
# Import the README and use it as the long-description.
22+
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
23+
try:
24+
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
25+
long_description = '\n' + f.read()
26+
except FileNotFoundError:
27+
long_description = DESCRIPTION
28+
29+
setup(name=NAME,
30+
version=VERSION,
31+
description=DESCRIPTION,
32+
long_description=long_description,
33+
long_description_content_type='text/markdown',
34+
author=['Pantelis Sopasakis', 'Ruairi Moran'],
35+
author_email='p.sopasakis@gmail.com',
36+
license='GNU General Public License v3 (GPLv3)',
37+
packages=find_packages(
38+
exclude=["private"]),
39+
include_package_data=True,
40+
install_requires=[
41+
'numpy', 'setuptools'
42+
],
43+
classifiers=[
44+
'Development Status :: 2 - Pre-Alpha',
45+
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
46+
'Programming Language :: Python',
47+
'Environment :: GPU :: NVIDIA CUDA',
48+
'Intended Audience :: Developers',
49+
'Topic :: Software Development :: Libraries'
50+
],
51+
keywords=['api', 'GPU'],
52+
url=(
53+
'https://github.com/GPUEngineering/GPUtils'
54+
),
55+
zip_safe=False)

0 commit comments

Comments
 (0)