Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fcc8a1b
Build a minimal test write program using h5fortran
milancurcic May 17, 2022
ebe4267
HDF5 build and link rules are now pulled from h5fortran's manifest
milancurcic May 19, 2022
99c4f2d
Merge branch 'main' into hdf5-reader
milancurcic May 24, 2022
995aced
Add function to read an HDF5 varying-length string attribute
milancurcic May 25, 2022
da1f273
Exclude c_null_char
milancurcic May 26, 2022
1eec7da
Add json-fortran to dependencies; begin parsing a Keras model
milancurcic May 26, 2022
01dac87
Make download_and_unpack public and more generally useful
milancurcic May 27, 2022
9f47395
Download test data files from repo
milancurcic May 27, 2022
3f5068e
Small fix to tests
milancurcic May 27, 2022
8013d0d
Ignore HDF5 files
milancurcic May 27, 2022
472164e
Add softmax function
milancurcic May 31, 2022
7cad8ca
Instantiating a network from h5 with correct size
milancurcic May 31, 2022
48889c5
Reorganize io modules
milancurcic Jun 1, 2022
e027b13
Begin work on the Keras adapter module
milancurcic Jun 1, 2022
2b71b76
Parsing Input and Dense layers and their sizes from Keras h5 works
milancurcic Jun 1, 2022
ee3f8a5
Rename functions for consistency
milancurcic Jun 2, 2022
25d3b14
Reading HDF5 datasets
milancurcic Jun 2, 2022
ed3e6d6
Rename test source file; update CMakeLists
milancurcic Jun 2, 2022
a526a39
Test reading 1-d and 2-d HDF5 datasets
milancurcic Jun 3, 2022
e20fb0a
Read Keras layer names
milancurcic Jun 3, 2022
e98cb11
Create a network from Keras file; setting weights and biases remains …
milancurcic Jun 3, 2022
7d87dd3
Implement subroutines to read 1-d and 2-d real32 datasets from HDF5
milancurcic Jun 6, 2022
1648f92
Load weights and biases from file and test the accuracy of the pre-tr…
milancurcic Jun 6, 2022
69237b3
Add example about loading a Keras model from file
milancurcic Jun 6, 2022
69e2dbe
First attempt including h5fortran in CMake build
milancurcic Jun 7, 2022
93049eb
CMake build rules for h5fortran
milancurcic Jun 8, 2022
5af4651
Add json-fortran to CMakeLists.txt; outstanding linker errors
milancurcic Jun 8, 2022
daf9361
cmake default to Release, explicit SERIAL option
scivision Jun 8, 2022
412a4b5
fix compiler options and link to fetchcontent
scivision Jun 8, 2022
eac72ab
fix linking for GNU Make
scivision Jun 8, 2022
db83a2c
speed up git clone with shallow
scivision Jun 8, 2022
d6753e8
build jsonfortran ourselves
scivision Jun 8, 2022
bd7bda5
cmake: build tests,examples option (default on)
scivision Jun 8, 2022
12ba73a
jsonfortran: add UTF8 feature check
scivision Jun 8, 2022
61e8e5b
Revert "jsonfortran: add UTF8 feature check"
scivision Jun 8, 2022
7ae9b08
test: avoid download clash
scivision Jun 8, 2022
b923736
Merge pull request #1 from scivision/hdf5-reader
milancurcic Jun 8, 2022
574e84d
Update dependency notes
milancurcic Jun 9, 2022
971f849
Intel needs -coarray flag to the linker as well
milancurcic Jun 9, 2022
202e272
Add note about building with Intel and HDF5
milancurcic Jun 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
*.o
*.mod
*.dat
*.h5
build
doc
100 changes: 31 additions & 69 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,66 +1,24 @@
# cmake version, project name, language
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(neural-fortran Fortran)
# CMake version, project name, language
cmake_minimum_required(VERSION 3.20)

# set output paths for modules, archives, and executables
set(CMAKE_Fortran_MODULE_DIRECTORY ${PROJECT_BINARY_DIR}/include)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# if build type not specified, default to release
# If build type not specified, default to release
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "release")
endif()

if(SERIAL)
message(STATUS "Configuring build for serial execution")
else()
message(STATUS "Configuring build for parallel execution")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build Release")
endif()

# compiler flags for gfortran
if(CMAKE_Fortran_COMPILER_ID MATCHES GNU)

if(SERIAL)
message(STATUS "Configuring to build with -fcoarray=single")
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fcoarray=single")
endif()

if(BLAS)
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fexternal-blas ${BLAS}")
set(LIBS "${LIBS} blas")
message(STATUS "Configuring build to use BLAS from ${BLAS}")
endif()

set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -fcheck=bounds -fbacktrace")
set(CMAKE_Fortran_FLAGS_RELEASE "-Ofast -fno-frontend-optimize")
endif()

# compiler flags for ifort
if(CMAKE_Fortran_COMPILER_ID MATCHES Intel)

if(SERIAL)
message(STATUS "Configuring to build with -coarray=single")
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -coarray=single")
endif()
project(neural-fortran
LANGUAGES C Fortran
)

set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -assume byterecl")
set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -C -traceback")
set(CMAKE_Fortran_FLAGS_RELEASE "-O3")
enable_testing()

if(NOT SERIAL)
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -coarray=shared")
endif()
include(FetchContent)

endif()
include(cmake/options.cmake)
include(cmake/compilers.cmake)

# compiler flags for Cray ftn
if(CMAKE_Fortran_COMPILER_ID MATCHES Cray)
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -h noomp")
set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g")
set(CMAKE_Fortran_FLAGS_RELEASE "-O3")
endif()
include(cmake/h5fortran.cmake)
include(cmake/json.cmake)

# library to archive (libneural.a)
add_library(neural
Expand All @@ -70,6 +28,8 @@ add_library(neural
src/nf/nf_base_layer_submodule.f90
src/nf/nf_conv2d_layer.f90
src/nf/nf_conv2d_layer_submodule.f90
src/nf/nf_datasets.f90
src/nf/nf_datasets_submodule.f90
src/nf/nf_datasets_mnist.f90
src/nf/nf_datasets_mnist_submodule.f90
src/nf/nf_dense_layer.f90
Expand All @@ -80,8 +40,8 @@ add_library(neural
src/nf/nf_input1d_layer_submodule.f90
src/nf/nf_input3d_layer.f90
src/nf/nf_input3d_layer_submodule.f90
src/nf/nf_io.f90
src/nf/nf_io_submodule.f90
src/nf/nf_keras.f90
src/nf/nf_keras_submodule.f90
src/nf/nf_layer_constructors.f90
src/nf/nf_layer_constructors_submodule.f90
src/nf/nf_layer.f90
Expand All @@ -97,20 +57,22 @@ add_library(neural
src/nf/nf_parallel_submodule.f90
src/nf/nf_random.f90
src/nf/nf_random_submodule.f90
src/nf/io/nf_io_binary.f90
src/nf/io/nf_io_binary_submodule.f90
src/nf/io/nf_io_hdf5.f90
src/nf/io/nf_io_hdf5_submodule.f90
)
target_link_libraries(neural PRIVATE h5fortran::h5fortran HDF5::HDF5 jsonfortran::jsonfortran)

install(TARGETS neural)

# Remove leading or trailing whitespace
string(REGEX REPLACE "^ | $" "" LIBS "${LIBS}")

# tests
enable_testing()
foreach(execid input1d_layer input3d_layer dense_layer conv2d_layer maxpool2d_layer flatten_layer dense_network conv2d_network)
add_executable(test_${execid} test/test_${execid}.f90)
target_link_libraries(test_${execid} neural ${LIBS})
add_test(test_${execid} bin/test_${execid})
endforeach()

foreach(execid cnn mnist simple sine)
add_executable(${execid} example/${execid}.f90)
target_link_libraries(${execid} neural ${LIBS})
endforeach()
if(${PROJECT_NAME}_BUILD_TESTING)
add_subdirectory(test)
endif()

if(${PROJECT_NAME}_BUILD_EXAMPLES)
add_subdirectory(example)
endif()
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,22 @@ git clone https://github.com/modern-fortran/neural-fortran
cd neural-fortran
```

Dependencies:
### Dependencies

* Fortran 2018-compatible compiler
* OpenCoarrays (optional, for parallel execution, GFortran only)
Required dependencies are:

* A Fortran compiler
* [HDF5](https://www.hdfgroup.org/downloads/hdf5/)
(must be provided by the OS package manager or your own build from source)
* [h5fortran](https://github.com/geospace-code/h5fortran),
[json-fortran](https://github.com/jacobwilliams/json-fortran)
(both handled by neural-fortran's build systems, no need for a manual install)
* [fpm](https://github.com/fortran-lang/fpm) or
[CMake](https://cmake.org) for building the code

Optional dependencies are:

* OpenCoarrays (for parallel execution with GFortran)
* BLAS, MKL (optional)

Compilers tested include:
Expand Down Expand Up @@ -131,12 +143,21 @@ cafrun -n 4 bin/mnist # run MNIST example on 4 cores
#### Building with a different compiler

If you want to build with a different compiler, such as Intel Fortran,
specify `FC` when issuing `cmake`:
set the `HDF5_ROOT` environment variable to the root path of your
Intel HDF5 build, and specify `FC` when issuing `cmake`:

```
FC=ifort cmake ..
```

for a parallel build of neural-fortran, or

```
FC=ifort cmake .. -DSERIAL=1
```

for a serial build.

#### Building with BLAS or MKL

To use an external BLAS or MKL library for `matmul` calls,
Expand Down Expand Up @@ -180,6 +201,8 @@ examples, in increasing level of complexity:
dataset
4. [cnn](example/cnn.f90): Creating and running forward a simple CNN using
`input`, `conv2d`, `maxpool2d`, `flatten`, and `dense` layers.
5. [mnist_from_keras](example/mnist_from_keras.f90): Creating a pre-trained
model from a Keras HDF5 file.

The examples also show you the extent of the public API that's meant to be
used in applications, i.e. anything from the `nf` module.
Expand Down
55 changes: 55 additions & 0 deletions cmake/compilers.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# compiler flags for gfortran
if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")

if(SERIAL)
message(STATUS "Configuring to build with -fcoarray=single")
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-fcoarray=single>")
else()
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-fcoarray=lib>")
endif()

if(BLAS)
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-fexternal-blas;${BLAS}>")
list(APPEND LIBS "blas")
message(STATUS "Configuring build to use BLAS from ${BLAS}")
endif()

add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Debug>>:-fcheck=bounds;-fbacktrace>")
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Release>>:-Ofast;-fno-frontend-optimize;-fno-backtrace>")

elseif(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel")
# compiler flags for ifort

if(SERIAL)
message(STATUS "Configuring to build with -coarray=single")
if(WIN32)
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:single>")
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:single>")
else()
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=single>")
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=single>")
endif()
else()
if(WIN32)
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:shared>")
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:/Qcoarray:shared>")
else()
add_compile_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=shared>")
add_link_options("$<$<COMPILE_LANGUAGE:Fortran>:-coarray=shared>")
endif()
endif()

if(WIN32)
string(APPEND CMAKE_Fortran_FLAGS " /assume:byterecl")
else()
string(APPEND CMAKE_Fortran_FLAGS " -assume byterecl")
endif()
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Debug>>:-check;-traceback>")
# add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Release>>:-O3>")

elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "Cray")
# compiler flags for Cray ftn
string(APPEND CMAKE_Fortran_FLAGS " -h noomp")
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Debug>>:-O0;-g>")
add_compile_options("$<$<AND:$<COMPILE_LANGUAGE:Fortran>,$<CONFIG:Release>>:-O3>")
endif()
15 changes: 15 additions & 0 deletions cmake/h5fortran.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
set(h5fortran_BUILD_TESTING false)

FetchContent_Declare(h5fortran
GIT_REPOSITORY https://github.com/geospace-code/h5fortran
GIT_TAG v4.6.3
GIT_SHALLOW true
)

FetchContent_MakeAvailable(h5fortran)

file(MAKE_DIRECTORY ${h5fortran_BINARY_DIR}/include)


list(APPEND CMAKE_MODULE_PATH ${h5fortran_SOURCE_DIR}/cmake/Modules)
find_package(HDF5 COMPONENTS Fortran REQUIRED)
35 changes: 35 additions & 0 deletions cmake/json.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# use our own CMake script to build jsonfortran instead of jsonfortran/CMakelists.txt

FetchContent_Declare(jsonfortran
GIT_REPOSITORY https://github.com/jacobwilliams/json-fortran
GIT_TAG 8.3.0
GIT_SHALLOW true
)

FetchContent_Populate(jsonfortran)

SET(JSON_REAL_KIND "REAL64")
SET(JSON_INT_KIND "INT32")

set(_src ${jsonfortran_SOURCE_DIR}/src)

set (JF_LIB_SRCS
${_src}/json_kinds.F90
${_src}/json_parameters.F90
${_src}/json_string_utilities.F90
${_src}/json_value_module.F90
${_src}/json_file_module.F90
${_src}/json_module.F90
)

add_library(jsonfortran ${JF_LIB_SRCS})
target_compile_definitions(jsonfortran PRIVATE ${JSON_REAL_KIND} ${JSON_INT_KIND})
target_include_directories(jsonfortran PUBLIC
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
$<INSTALL_INTERFACE:include>
)

add_library(jsonfortran::jsonfortran INTERFACE IMPORTED GLOBAL)
target_link_libraries(jsonfortran::jsonfortran INTERFACE jsonfortran)

install(TARGETS jsonfortran)
30 changes: 30 additions & 0 deletions cmake/options.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
option(SERIAL "Serial execution")
option(${PROJECT_NAME}_BUILD_TESTING "build ${PROJECT_NAME} tests" true)
option(${PROJECT_NAME}_BUILD_EXAMPLES "build ${PROJECT_NAME} examples" true)

# Set output paths for modules, archives, and executables
set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/include)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

if(SERIAL)
message(STATUS "Configuring build for serial execution")
else()
message(STATUS "Configuring build for parallel execution")
endif()

# --- Generally useful CMake project options

# Rpath options necessary for shared library install to work correctly in user projects
set(CMAKE_INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib)
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH true)

# Necessary for shared library with Visual Studio / Windows oneAPI
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true)

# --- auto-ignore build directory
if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore)
file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*")
endif()
4 changes: 4 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foreach(execid cnn mnist mnist_from_keras simple sine)
add_executable(${execid} ${execid}.f90)
target_link_libraries(${execid} PRIVATE neural h5fortran::h5fortran jsonfortran::jsonfortran ${LIBS})
endforeach()
51 changes: 51 additions & 0 deletions example/mnist_from_keras.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
program mnist_from_keras

! This example demonstrates loading a pre-trained MNIST model from Keras
! from an HDF5 file and running an inferrence on the testing dataset.

use nf, only: network, label_digits, load_mnist
use nf_datasets, only: download_and_unpack, keras_model_dense_mnist_url

implicit none

type(network) :: net
real, allocatable :: training_images(:,:), training_labels(:)
real, allocatable :: validation_images(:,:), validation_labels(:)
real, allocatable :: testing_images(:,:), testing_labels(:)
character(*), parameter :: test_data_path = 'keras_dense_mnist.h5'
logical :: file_exists

inquire(file=test_data_path, exist=file_exists)
if (.not. file_exists) call download_and_unpack(keras_model_dense_mnist_url)

call load_mnist(training_images, training_labels, &
validation_images, validation_labels, &
testing_images, testing_labels)

print '("Loading a pre-trained MNIST model from Keras")'
print '(60("="))'

net = network(test_data_path)

call net % print_info()

if (this_image() == 1) &
print '(a,f5.2,a)', 'Accuracy: ', accuracy( &
net, testing_images, label_digits(testing_labels)) * 100, ' %'

contains

real function accuracy(net, x, y)
type(network), intent(in out) :: net
real, intent(in) :: x(:,:), y(:,:)
integer :: i, good
good = 0
do i = 1, size(x, dim=2)
if (all(maxloc(net % output(x(:,i))) == maxloc(y(:,i)))) then
good = good + 1
end if
end do
accuracy = real(good) / size(x, dim=2)
end function accuracy

end program mnist_from_keras
Loading