├── .gitmodules ├── fortran-tf-lib ├── my_model │ ├── saved_model.pb │ ├── keras_metadata.pb │ └── variables │ │ ├── variables.index │ │ └── variables.data-00000-of-00001 ├── tests │ ├── load_model_py.py │ ├── gen_model.py │ ├── generated_code_test │ │ ├── CMakeLists.txt │ │ └── test_stub.F90 │ ├── load_model_f.F90 │ ├── load_model_c.c │ └── load_wavenet.F90 ├── README.md ├── CMakeLists.txt └── src │ └── fortran_tensorflow_lib.F90 ├── LICENSE ├── .github └── workflows │ └── tests.yml ├── .gitignore └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "process_model"] 2 | path = process_model 3 | url = https://github.com/Cambridge-ICCS/process_model.git 4 | -------------------------------------------------------------------------------- /fortran-tf-lib/my_model/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cambridge-ICCS/fortran-tf-lib/HEAD/fortran-tf-lib/my_model/saved_model.pb -------------------------------------------------------------------------------- /fortran-tf-lib/my_model/keras_metadata.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cambridge-ICCS/fortran-tf-lib/HEAD/fortran-tf-lib/my_model/keras_metadata.pb -------------------------------------------------------------------------------- /fortran-tf-lib/my_model/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cambridge-ICCS/fortran-tf-lib/HEAD/fortran-tf-lib/my_model/variables/variables.index -------------------------------------------------------------------------------- /fortran-tf-lib/my_model/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cambridge-ICCS/fortran-tf-lib/HEAD/fortran-tf-lib/my_model/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /fortran-tf-lib/tests/load_model_py.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | from tensorflow import keras 4 | 5 | reconstructed_model = keras.models.load_model("../my_model") 6 | 7 | test_raw = [[ 8 | 0.71332126, 0.81275973, 0.66596436, 0.79570779, 0.83973302, 0.76604397, 9 | 0.84371391, 0.92582056, 0.32038017, 0.0732005, 0.80589203, 0.75226581, 10 | 0.81602784, 0.59698078, 0.32991729, 0.43125108, 0.4368422, 0.88550326, 11 | 0.7131253, 0.14951148, 0.22084413, 0.70801317, 0.69433906, 0.62496564, 12 | 0.50744999, 0.94047845, 0.18191579, 0.2599102, 0.53161889, 0.57402205, 13 | 0.50751284, 0.65207096]] 14 | 15 | test_input = np.random.random((1, 32)) 16 | print( test_input ) 17 | output_value = reconstructed_model.predict(test_raw) 18 | print( '\n\nFinished, output= {}\n'.format(output_value) ) 19 | if (output_value - -0.479371) > 1e-6: 20 | print( 'Output does not match, FAILURE!\n') 21 | else: 22 | print( 'Output is correct, SUCCESS!\n') 23 | -------------------------------------------------------------------------------- /fortran-tf-lib/tests/gen_model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | from tensorflow import keras 4 | 5 | # Create a simple model. 6 | #inputs = keras.Input(shape=(32,), name="the_input_xxyyz") 7 | inputs = keras.Input(shape=(32,)) 8 | #outputs = keras.layers.Dense(1, name="the_output_blarg")(inputs) 9 | outputs = keras.layers.Dense(1)(inputs) 10 | model = keras.Model(inputs, outputs) 11 | model.compile(optimizer="adam", loss="mean_squared_error") 12 | 13 | # Train the model. 14 | np.random.seed(0) 15 | test_input = np.random.random((128, 32)) 16 | test_target = np.random.random((128, 1)) 17 | model.fit(test_input, test_target) 18 | 19 | # Calling `save('my_model')` creates a SavedModel folder `my_model`. 20 | # this overwrites any existing model. The random.seed(0) still doesn't 21 | # create deterministic models, presumably there's enough variation in 22 | # the initial weights to make each one unique. Bless. 23 | #model.save("../my_model") 24 | 25 | print( model.predict(test_input) ) 26 | -------------------------------------------------------------------------------- /fortran-tf-lib/tests/generated_code_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Compile and test the generated code from process_model 2 | # that links against fortran-tf-lib 3 | cmake_minimum_required(VERSION 3.7 FATAL_ERROR) 4 | set(PROJECT_NAME GeneratedCodeTest) 5 | project(${PROJECT_NAME} LANGUAGES Fortran) 6 | enable_testing() 7 | 8 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 9 | 10 | #find_library(FORTRAN_TF_LIB 11 | # NAMES ${FORTRAN_TF_LIB_NAME} 12 | # HINTS ${FORTRAN_TF_LIB_DIR} 13 | # DOC "Location of fortran-tf-lib" 14 | # REQUIRED 15 | #) 16 | 17 | add_executable(test_fortran_tf_lib test_stub.F90) 18 | target_link_libraries(test_fortran_tf_lib PRIVATE ${FORTRAN_TF_LIB}) 19 | target_include_directories(test_fortran_tf_lib PRIVATE ${FORTRAN_TF_LIB_DIR}/modules) 20 | target_sources(test_fortran_tf_lib PRIVATE ${GENERATED_CODE_FILE}) 21 | 22 | add_test( 23 | NAME generated_code_1 24 | COMMAND test_fortran_tf_lib 25 | ) 26 | set_tests_properties(generated_code_1 PROPERTIES 27 | PASS_REGULAR_EXPRESSION "SUCCESS") 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Institute of Computing for Climate Science 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Runtests 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the "main" branch 8 | push: 9 | branches: [ "tensorflow" ] 10 | pull_request: 11 | branches: [ "tensorflow" ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | branches: [ "tensorflow" ] 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | # This workflow contains a single job called "build" 20 | build: 21 | # The type of runner that the job will run on 22 | runs-on: ubuntu-latest 23 | 24 | # Steps represent a sequence of tasks that will be executed as part of the job 25 | steps: 26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 27 | - uses: actions/checkout@v3 28 | - uses: actions/setup-python@v4 29 | 30 | # Install dependencies 31 | - name: apt install deps 32 | run: sudo apt install gfortran 33 | 34 | # Runs a set of commands using the runners shell 35 | - name: Run the pytest tests in tools 36 | working-directory: $GITHUB_WORKSPACE/fortran-tf-lib/tools 37 | run: | 38 | pytest tests 39 | 40 | -------------------------------------------------------------------------------- /fortran-tf-lib/tests/generated_code_test/test_stub.F90: -------------------------------------------------------------------------------- 1 | program test_program 2 | use ml_module 3 | use TF_Types 4 | use iso_c_binding 5 | implicit none 6 | 7 | type(TF_Tensor), dimension(1) :: input_tensors, output_tensors 8 | 9 | real, dimension(32,1), target :: raw_data 10 | real, dimension(1), target :: answers 11 | real, dimension(:,:), pointer :: input_data_ptr 12 | real, dimension(:), pointer :: output_data_ptr 13 | integer i 14 | type(c_ptr) :: raw_data_ptr 15 | type(c_ptr) :: output_c_data_ptr 16 | 17 | raw_data = reshape ([ & 18 | 0.71332126, 0.81275973, 0.66596436, 0.79570779, 0.83973302, 0.76604397, & 19 | 0.84371391, 0.92582056, 0.32038017, 0.0732005, 0.80589203, 0.75226581, & 20 | 0.81602784, 0.59698078, 0.32991729, 0.43125108, 0.4368422, 0.88550326, & 21 | 0.7131253, 0.14951148, 0.22084413, 0.70801317, 0.69433906, 0.62496564, & 22 | 0.50744999, 0.94047845, 0.18191579, 0.2599102, 0.53161889, 0.57402205, & 23 | 0.50751284, 0.65207096 & 24 | ], shape(raw_data)) 25 | answers = [-0.479371] 26 | 27 | input_tensors(1) = associate_tensor(raw_data) 28 | ! Check tensor contents 29 | call c_f_pointer(TF_TensorData(input_tensors(1)), input_data_ptr, shape(raw_data)) 30 | write(*,*)'input_tensors(1)' 31 | do i = 1, 32 32 | write(*,*) input_data_ptr(i,1) 33 | enddo 34 | 35 | call ml_module_init() 36 | 37 | call ml_module_calc(model_session_1, inputs_1, input_tensors, outputs_1, output_tensors) 38 | 39 | 40 | call c_f_pointer(TF_TensorData(output_tensors(1)), output_data_ptr, shape(answers)) 41 | if ((output_data_ptr(1) - answers(1)) .gt. 1e-6) then 42 | write(*,*)'Output does not match, FAILED!' 43 | else 44 | write(*,*)'Output is correct, SUCCESS!' 45 | endif 46 | 47 | 48 | call TF_DeleteTensor( input_tensors(1) ) 49 | call TF_DeleteTensor( output_tensors(1) ) 50 | 51 | end program test_program 52 | -------------------------------------------------------------------------------- /fortran-tf-lib/tests/load_model_f.F90: -------------------------------------------------------------------------------- 1 | program test_program 2 | use TF_Types 3 | use TF_Interface 4 | use iso_c_binding 5 | implicit none 6 | 7 | type(TF_Session) :: session 8 | type(TF_SessionOptions) :: sessionoptions 9 | type(TF_Graph) :: graph 10 | type(TF_Status) :: stat 11 | type(TF_Output), dimension(1) :: input_tfoutput, output_tfoutput 12 | character(100) :: vers 13 | character(100), dimension(1) :: tags 14 | type(TF_Tensor), dimension(1) :: input_tensors, output_tensors, test_tensor 15 | type(TF_Operation), dimension(1) :: target_opers 16 | 17 | real, dimension(32), target :: raw_data 18 | real, dimension(:), pointer :: output_data_ptr 19 | integer(kind=c_int64_t), dimension(2) :: input_dims 20 | integer(kind=c_int64_t), dimension(2) :: output_dims 21 | type(c_ptr) :: raw_data_ptr 22 | type(c_ptr) :: output_c_data_ptr 23 | 24 | raw_data = (/ & 25 | 0.71332126, 0.81275973, 0.66596436, 0.79570779, 0.83973302, 0.76604397, & 26 | 0.84371391, 0.92582056, 0.32038017, 0.0732005, 0.80589203, 0.75226581, & 27 | 0.81602784, 0.59698078, 0.32991729, 0.43125108, 0.4368422, 0.88550326, & 28 | 0.7131253, 0.14951148, 0.22084413, 0.70801317, 0.69433906, 0.62496564, & 29 | 0.50744999, 0.94047845, 0.18191579, 0.2599102, 0.53161889, 0.57402205, & 30 | 0.50751284, 0.65207096 & 31 | /) 32 | 33 | 34 | input_dims = (/ 1, 32 /) 35 | output_dims = (/ 1, 1 /) 36 | tags(1) = 'serve' 37 | 38 | ! Print TensorFlow library version 39 | call TF_Version(vers) 40 | write(*,*)'Tensorflow version', vers 41 | 42 | sessionoptions = TF_NewSessionOptions() 43 | graph = TF_NewGraph() 44 | stat = TF_NewStatus() 45 | 46 | ! Load session (also populates graph) 47 | session = TF_LoadSessionFromSavedModel(sessionoptions, '/path/to/model', tags, 1, & 48 | graph, stat) 49 | 50 | if (TF_GetCode( stat ) .ne. TF_OK) then 51 | call TF_Message( stat, vers ) 52 | write(*,*)'woops', TF_GetCode( stat ), vers 53 | call abort 54 | endif 55 | 56 | call TF_DeleteSessionOptions(sessionoptions) 57 | 58 | input_tfoutput(1)%oper = TF_GraphOperationByName( graph, "serving_default_input_1" ) 59 | input_tfoutput(1)%index = 0 60 | if (.not.c_associated(input_tfoutput(1)%oper%p)) then 61 | write(*,*)'input not associated' 62 | stop 63 | endif 64 | 65 | output_tfoutput(1)%oper = TF_GraphOperationByName( graph, "StatefulPartitionedCall" ) 66 | output_tfoutput(1)%index = 0 67 | if (.not.c_associated(output_tfoutput(1)%oper%p)) then 68 | write(*,*)'output not associated' 69 | stop 70 | endif 71 | 72 | ! Bind the input tensor 73 | raw_data_ptr = c_loc(raw_data) 74 | input_tensors(1) = TF_NewTensor( TF_FLOAT, input_dims, 2, raw_data_ptr, int(128, kind=c_size_t) ) 75 | 76 | ! Run inference 77 | call TF_SessionRun( session, input_tfoutput, input_tensors, 1, output_tfoutput, output_tensors, 1, & 78 | target_opers, 0, stat ) 79 | if (TF_GetCode( stat ) .ne. TF_OK) then 80 | call TF_Message( stat, vers ) 81 | write(*,*) TF_GetCode( stat ), vers 82 | call abort 83 | endif 84 | 85 | ! Bind output tensor 86 | call c_f_pointer( TF_TensorData( output_tensors(1)), output_data_ptr, shape(output_data_ptr) ) 87 | write(*,*)'output data', output_data_ptr(1) 88 | 89 | if ((output_data_ptr(1) - -0.479371) .gt. 1e-6) then 90 | write(*,*)'Output does not match, FAILED!' 91 | else 92 | write(*,*)'Output is correct, SUCCESS!' 93 | endif 94 | 95 | 96 | ! Clean up 97 | call TF_DeleteTensor( input_tensors(1) ) 98 | call TF_DeleteTensor( output_tensors(1) ) 99 | call TF_DeleteGraph( graph ) 100 | call TF_DeleteSession( session, stat ) 101 | call TF_DeleteStatus( stat ) 102 | 103 | end program test_program 104 | -------------------------------------------------------------------------------- /fortran-tf-lib/README.md: -------------------------------------------------------------------------------- 1 | # The Fortran to TensorFlow library 2 | 3 | A Fortran to tensorflow library implementing the bare minimum of the TensorFlow 4 | C API. There is enough to load and infer any TensorFlow model from Fortran. 5 | 6 | ## Building 7 | 8 | You'll need the TensorFlow C API, download from 9 | https://www.tensorflow.org/install/lang_c. I've only tested the CPU one. 10 | Newer versions may be available if you change the download URL. Install this 11 | somewhere (e.g. `/path/to/tf_c_api`) such that: 12 | 13 | ``` 14 | $ ls 15 | include lib LICENSE THIRD_PARTY_TF_C_LICENSES 16 | ``` 17 | 18 | The build system uses [CMake](https://cmake.org/). Create a build directory, `cd` to it 19 | and run `cmake `. A common pattern is to create the build directory in the same directory 20 | as the `CMakeLists.txt` file, `cd` to it, and run `cmake ..`. 21 | 22 | ``` 23 | $ ls 24 | CMakeLists.txt my_model README.md src tests 25 | $ mkdir build 26 | $ cd build 27 | $ cmake .. 28 | ``` 29 | 30 | CMake will attempt to find Fortran and C compilers, and the TensorFlow library. 31 | You will probably need to help it find the latter by passing the 32 | `-DTENSORFLOW_LOCATION` variable to cmake. You can also override its choice of 33 | compilers with `-DCMAKE_Fortran_COMPILER` and `-DCMAKE_C_COMPILER`. It's best 34 | to not mix compilers from different vendors, so if you plan on linking this 35 | code to one built with a particular compiler set, use that. You may also 36 | specify where the library is to be installed with `-DCMAKE_INSTALL_PREFIX`. So 37 | a full invocation of `cmake` might look like this: 38 | 39 | ``` 40 | cmake .. -DTENSORFLOW_LOCATION=/path/to/tf_c_api -DCMAKE_Fortran_COMPILER=ifort -DCMAKE_C_COMPILER=icc -DCMAKE_INSTALL_PREFIX=/path/to/fortran-tf-lib 41 | ``` 42 | 43 | By default the build will be a Debug one. You can set one of the other CMake 44 | standard build types, such as Release or RelWithDebInfo (Release with Debug 45 | info) with e.g. `-DCMAKE_BUILD_TYPE=Release`. 46 | 47 | ## Using the library 48 | 49 | ### Using `process_model` 50 | 51 | There are some issues with the TensorFlow C API. In particular, it is not 52 | possible to load a model from disk without knowing certain parameters of the 53 | model. It is also necessary to know other parameters to infer. The API seems 54 | to expect the user to be using `protobuf` to query the saved model directly to 55 | determine the parameters. This would add a large level of complexity to a 56 | Fortran library. Alternatively the user can get the parameters from the model 57 | using the TensorFlow `saved_model_cli` tool to query it for the tags and input 58 | and output operation names and indices. The user would then hard-code these 59 | values into their calls into the library. 60 | 61 | To ease this process we provide a utility `process_model` that examines a saved 62 | TensorFlow model and outputs a Fortran module to interface to it. The module 63 | exports an `init` procedure, a `calc` procedure, a `finish` procedure, and 64 | a set of routines to associate TensorFlow tensors with Fortran arrays. 65 | 66 | ### Using the library directly 67 | 68 | Currently this is only documented in the test case. 69 | 70 | ## Add the library to a CMake build system 71 | Make sure the `CMAKE_PREFIX_PATH` points to wherever you installed the 72 | library. Alternatively you can set pass an option to cmake 73 | `-DFortranTensorFlow_DIR=`, where path is the location where the 74 | `FortranTensorFlowConfig.cmake` file is located. This is usually in 75 | `CMAKE_INSTALL_PREFIX/lib64/cmake`. 76 | 77 | Then in the `CMakeLists.txt` file of the project you want to add the library to 78 | add lines like: 79 | ``` 80 | find_package(FortranTensorFlow) 81 | target_link_libraries(foo FortranTensorFlow::fortran-tf) 82 | ``` 83 | This should add the library to the target (`foo` here) and automatically add 84 | the Fortran module directory to its compile steps. 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Byte-compiled / optimized / DLL files 35 | __pycache__/ 36 | *.py[cod] 37 | *$py.class 38 | 39 | # C extensions 40 | *.so 41 | 42 | # Distribution / packaging 43 | .Python 44 | build/ 45 | develop-eggs/ 46 | dist/ 47 | downloads/ 48 | eggs/ 49 | .eggs/ 50 | lib/ 51 | lib64/ 52 | parts/ 53 | sdist/ 54 | var/ 55 | wheels/ 56 | share/python-wheels/ 57 | *.egg-info/ 58 | .installed.cfg 59 | *.egg 60 | MANIFEST 61 | 62 | # PyInstaller 63 | # Usually these files are written by a python script from a template 64 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 65 | *.manifest 66 | *.spec 67 | 68 | # Installer logs 69 | pip-log.txt 70 | pip-delete-this-directory.txt 71 | 72 | # Unit test / coverage reports 73 | htmlcov/ 74 | .tox/ 75 | .nox/ 76 | .coverage 77 | .coverage.* 78 | .cache 79 | nosetests.xml 80 | coverage.xml 81 | *.cover 82 | *.py,cover 83 | .hypothesis/ 84 | .pytest_cache/ 85 | cover/ 86 | 87 | # Translations 88 | *.mo 89 | *.pot 90 | 91 | # Django stuff: 92 | *.log 93 | local_settings.py 94 | db.sqlite3 95 | db.sqlite3-journal 96 | 97 | # Flask stuff: 98 | instance/ 99 | .webassets-cache 100 | 101 | # Scrapy stuff: 102 | .scrapy 103 | 104 | # Sphinx documentation 105 | docs/_build/ 106 | 107 | # PyBuilder 108 | .pybuilder/ 109 | target/ 110 | 111 | # Jupyter Notebook 112 | .ipynb_checkpoints 113 | 114 | # IPython 115 | profile_default/ 116 | ipython_config.py 117 | 118 | # pyenv 119 | # For a library or package, you might want to ignore these files since the code is 120 | # intended to run in multiple environments; otherwise, check them in: 121 | # .python-version 122 | 123 | # pipenv 124 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 125 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 126 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 127 | # install all needed dependencies. 128 | #Pipfile.lock 129 | 130 | # poetry 131 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 132 | # This is especially recommended for binary packages to ensure reproducibility, and is more 133 | # commonly ignored for libraries. 134 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 135 | #poetry.lock 136 | 137 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 138 | __pypackages__/ 139 | 140 | # Celery stuff 141 | celerybeat-schedule 142 | celerybeat.pid 143 | 144 | # SageMath parsed files 145 | *.sage.py 146 | 147 | # Environments 148 | .env 149 | .venv 150 | env/ 151 | venv/ 152 | ENV/ 153 | env.bak/ 154 | venv.bak/ 155 | 156 | # Spyder project settings 157 | .spyderproject 158 | .spyproject 159 | 160 | # Rope project settings 161 | .ropeproject 162 | 163 | # mkdocs documentation 164 | /site 165 | 166 | # mypy 167 | .mypy_cache/ 168 | .dmypy.json 169 | dmypy.json 170 | 171 | # Pyre type checker 172 | .pyre/ 173 | 174 | # pytype static type analyzer 175 | .pytype/ 176 | 177 | # Cython debug symbols 178 | cython_debug/ 179 | 180 | # PyCharm 181 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 182 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 183 | # and can be added to the global gitignore or merged into this file. For a more nuclear 184 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 185 | #.idea/ 186 | -------------------------------------------------------------------------------- /fortran-tf-lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Test fixtures require CMake >= 3.7 2 | cmake_minimum_required(VERSION 3.7 FATAL_ERROR) 3 | set(PROJECT_NAME FortranTensorFlow) 4 | set(LIB_NAME fortran-tf) 5 | set(PACKAGE_VERSION 0.1) 6 | 7 | # The C is needed here so the FortranCInterface check can occur. It's worth 8 | # checking because we are linking to the Tensorflow C library 9 | project(${PROJECT_NAME} VERSION ${PACKAGE_VERSION} LANGUAGES Fortran C) 10 | enable_testing() 11 | 12 | # Set default build type to Debug. 13 | if(NOT CMAKE_BUILD_TYPE) 14 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) 15 | endif() 16 | 17 | message(STATUS "Compiler is ${CMAKE_Fortran_COMPILER_ID}") 18 | message(STATUS "Compiler is ${CMAKE_Fortran_COMPILER_VERSION}") 19 | # require at least gfortran 9.0 20 | # the library seems to *crash* gfortran 8.5.0 during compilation 21 | # no known failures with Intel (tested down to 2016) 22 | if("${CMAKE_Fortran_COMPILER_ID}" STREQUAL "GNU") 23 | if (CMAKE_Fortran_COMPILER_VERSION VERSION_LESS 9.0) 24 | message(FATAL_ERROR "gfortran version must be at least 9.0!") 25 | endif() 26 | endif() 27 | 28 | include(FortranCInterface) 29 | FortranCInterface_VERIFY(QUIET) 30 | 31 | # Set RPATH behaviour 32 | set(CMAKE_SKIP_RPATH FALSE) 33 | set(CMAKE_SKIP_BUILD_RPATH FALSE) 34 | set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 35 | # Embed absolute paths to external libraries that are not part of 36 | # the project, (they are expected to be at the same location on all 37 | # machines the project will be deployed to 38 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 39 | 40 | # Follow GNU conventions for installing directories 41 | include(GNUInstallDirs) 42 | 43 | # Define RPATH for executables via a relative expression to enable a 44 | # fully relocatable package 45 | file(RELATIVE_PATH relDir 46 | ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} 47 | ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 48 | set(CMAKE_INSTALL_RPATH $ORIGIN/${relDir}) 49 | 50 | # Tensorflow libraries 51 | find_library(TF_LIB 52 | NAMES tensorflow 53 | HINTS ${TENSORFLOW_LOCATION} ${TENSORFLOW_LOCATION}/lib 54 | DOC "Location of tensorflow library" 55 | ) 56 | if(NOT TF_LIB) 57 | message(FATAL_ERROR "Could not find libtensorflow.so") 58 | endif() 59 | list(APPEND TENSORFLOW_LIBRARIES ${TF_LIB}) 60 | 61 | find_library(TF_FRAMEWORK 62 | NAMES tensorflow_framework 63 | HINTS ${TENSORFLOW_LOCATION} ${TENSORFLOW_LOCATION}/lib 64 | DOC "Location of tensorflow framework library" 65 | ) 66 | if(NOT TF_FRAMEWORK) 67 | message(FATAL_ERROR "Could not find libtensorflow_framework.so") 68 | endif() 69 | list(APPEND TENSORFLOW_LIBRARIES ${TF_FRAMEWORK}) 70 | 71 | # The source file(s). 72 | add_library(${LIB_NAME} SHARED src/fortran_tensorflow_lib.F90) 73 | 74 | add_library(${PROJECT_NAME}::${LIB_NAME} ALIAS ${LIB_NAME}) 75 | set_target_properties(${LIB_NAME} PROPERTIES 76 | VERSION ${PACKAGE_VERSION} 77 | Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules" 78 | ) 79 | 80 | target_link_libraries(${LIB_NAME} PRIVATE ${TENSORFLOW_LIBRARIES}) 81 | target_include_directories(${LIB_NAME} 82 | PUBLIC 83 | $ 84 | # $ 85 | ) 86 | # 87 | # Install library, create target file 88 | install(TARGETS "${LIB_NAME}" 89 | EXPORT ${PROJECT_NAME} 90 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 91 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 92 | PRIVATE_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 93 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${LIB_NAME} 94 | ) 95 | 96 | # Install target file 97 | install(EXPORT ${PROJECT_NAME} 98 | FILE ${PROJECT_NAME}Config.cmake 99 | NAMESPACE ${PROJECT_NAME}:: 100 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake 101 | ) 102 | 103 | # Install Fortran module files 104 | install(FILES "${CMAKE_BINARY_DIR}/modules/tf_interface.mod" 105 | "${CMAKE_BINARY_DIR}/modules/tf_types.mod" 106 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIB_NAME}" 107 | ) 108 | 109 | # Fixture for tests 110 | add_test( 111 | NAME process_model_exists 112 | COMMAND process_model --help 113 | ) 114 | set_tests_properties( 115 | process_model_exists 116 | PROPERTIES FIXTURES_SETUP process_model 117 | ) 118 | 119 | add_test( 120 | NAME process_model_output 121 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 122 | COMMAND process_model -o ${CMAKE_BINARY_DIR}/test_fortran_gen.F90 ${CMAKE_CURRENT_LIST_DIR}/my_model 123 | ) 124 | set_tests_properties( 125 | process_model_output 126 | PROPERTIES FIXTURES_REQUIRED process_model 127 | ) 128 | set_tests_properties( 129 | process_model_output 130 | PROPERTIES FIXTURES_SETUP process_model_output 131 | ) 132 | 133 | # Executables for tests 134 | add_executable(test_load_model_f tests/load_model_f.F90) 135 | target_link_libraries(test_load_model_f PRIVATE ${PROJECT_NAME}::${LIB_NAME}) 136 | 137 | add_test( 138 | NAME load_model_f 139 | COMMAND test_load_model_f 140 | ) 141 | set_tests_properties(load_model_f PROPERTIES 142 | PASS_REGULAR_EXPRESSION "SUCCESS") 143 | 144 | # Tests that require process_model_output 145 | add_test( 146 | NAME process_model_1 147 | COMMAND ${CMAKE_CTEST_COMMAND} 148 | --build-and-test ${CMAKE_CURRENT_LIST_DIR}/tests/generated_code_test 149 | ${CMAKE_CURRENT_BINARY_DIR}/tests/generated_code_test/build 150 | --build-generator ${CMAKE_GENERATOR} 151 | --test-command ${CMAKE_CTEST_COMMAND} 152 | --output-on-failure 153 | --build-options -DFORTRAN_TF_LIB=$ 154 | -DFORTRAN_TF_LIB_DIR=${CMAKE_BINARY_DIR} 155 | -DGENERATED_CODE_FILE=${CMAKE_BINARY_DIR}/test_fortran_gen.F90 156 | -DCMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER} 157 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 158 | ) 159 | set_tests_properties( 160 | process_model_1 161 | PROPERTIES FIXTURES_REQUIRED process_model_output 162 | ) 163 | -------------------------------------------------------------------------------- /fortran-tf-lib/tests/load_model_c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* 6 | TF_CAPI_EXPORT extern TF_Session* TF_LoadSessionFromSavedModel( 7 | const TF_SessionOptions* session_options, const TF_Buffer* run_options, 8 | const char* export_dir, const char* const* tags, int tags_len, 9 | TF_Graph* graph, TF_Buffer* meta_graph_def, TF_Status* status); 10 | 11 | TF_CAPI_EXPORT extern void TF_SessionRun( 12 | TF_Session* session, 13 | // RunOptions 14 | const TF_Buffer* run_options, 15 | // Input tensors 16 | const TF_Output* inputs, TF_Tensor* const* input_values, int ninputs, 17 | // Output tensors 18 | const TF_Output* outputs, TF_Tensor** output_values, int noutputs, 19 | // Target operations 20 | const TF_Operation* const* target_opers, int ntargets, 21 | // RunMetadata 22 | TF_Buffer* run_metadata, 23 | // Output status 24 | TF_Status*); 25 | 26 | [[0.71332126 0.81275973 0.66596436 0.79570779 0.83973302 0.76604397 27 | 0.84371391 0.92582056 0.32038017 0.0732005 0.80589203 0.75226581 28 | 0.81602784 0.59698078 0.32991729 0.43125108 0.4368422 0.88550326 29 | 0.7131253 0.14951148 0.22084413 0.70801317 0.69433906 0.62496564 30 | 0.50744999 0.94047845 0.18191579 0.2599102 0.53161889 0.57402205 31 | 0.50751284 0.65207096]] 32 | 33 | [[-0.7109489]] 34 | */ 35 | 36 | void null_dealloc(void* data, size_t len, void* arg) { 37 | /*do nothing */ 38 | } 39 | 40 | int main() { 41 | 42 | TF_SessionOptions* session_options; 43 | TF_Session* session; 44 | TF_Status* status; 45 | TF_Graph* graph; 46 | TF_Output input_tfoutput, output_tfoutput; 47 | TF_Tensor* input; 48 | TF_Tensor* input_values[1]; 49 | TF_Tensor* output_values[1]; 50 | void* input_data_ptr; 51 | void* output_data_ptr; 52 | 53 | const char* const tags[] = {"serve"}; 54 | float raw_inp[] = {0.71332126, 0.81275973, 0.66596436, 0.79570779, 0.83973302, 0.76604397, 55 | 0.84371391, 0.92582056, 0.32038017, 0.0732005, 0.80589203, 0.75226581, 56 | 0.81602784, 0.59698078, 0.32991729, 0.43125108, 0.4368422, 0.88550326, 57 | 0.7131253, 0.14951148, 0.22084413, 0.70801317, 0.69433906, 0.62496564, 58 | 0.50744999, 0.94047845, 0.18191579, 0.2599102, 0.53161889, 0.57402205, 59 | 0.50751284, 0.65207096}; 60 | const int64_t input_dims[] = {1, 32}; 61 | const int64_t output_dims[] = {1}; 62 | float output_value; 63 | 64 | 65 | printf("Hello from TensorFlow C library version %s\n", TF_Version()); 66 | 67 | session_options = TF_NewSessionOptions(); 68 | graph = TF_NewGraph(); 69 | status = TF_NewStatus(); 70 | 71 | session = TF_LoadSessionFromSavedModel( 72 | session_options, // TF_SessionOptions* session_options 73 | NULL, // TF_Buffer* run_options 74 | "../my_model", // const char* export_dir 75 | tags, // const char* const* tags 76 | 1, // int tags_len 77 | graph, // TF_Graph* graph 78 | NULL, // TF_Buffer* meta_graph_def 79 | status // TF_Status* status 80 | ); 81 | if (TF_OK != TF_GetCode(status)) { 82 | printf("load error %s\n", TF_Message(status)); 83 | } 84 | 85 | // Get these names from e.g.: 86 | // `saved_model_cli show --dir my_model/ --tag serve --signature_def serving_default` 87 | // It will return something like: 88 | /* 89 | The given SavedModel SignatureDef contains the following input(s): 90 | inputs['input_1'] tensor_info: 91 | dtype: DT_FLOAT 92 | shape: (-1, 32) 93 | name: serving_default_input_1:0 94 | The given SavedModel SignatureDef contains the following output(s): 95 | outputs['dense'] tensor_info: 96 | dtype: DT_FLOAT 97 | shape: (-1, 1) 98 | name: StatefulPartitionedCall:0 99 | Method name is: tensorflow/serving/predict 100 | */ 101 | // The names you want are under "name:", so "name: serving_default_input_1:0" 102 | // means the TF_Operation is named "serving_default_input_1" and the 103 | // index in the TF_Output is 0 for this input. 104 | 105 | input_tfoutput.oper = TF_GraphOperationByName(graph, "serving_default_input_1"); 106 | input_tfoutput.index = 0; 107 | if (input_tfoutput.oper == NULL) { 108 | printf("input_oper null\n"); 109 | exit(1); 110 | } 111 | output_tfoutput.oper = TF_GraphOperationByName(graph, "StatefulPartitionedCall"), 0; 112 | output_tfoutput.index = 0; 113 | 114 | if (output_tfoutput.oper == NULL) { 115 | printf("output_oper null\n"); 116 | exit(1); 117 | } 118 | 119 | /* 120 | input = TF_AllocateTensor( TF_FLOAT, input_dims, 2, 32*sizeof(TF_FLOAT) ); 121 | if (input == NULL) { 122 | printf("allocate error\n"); 123 | } 124 | input_data_ptr = TF_TensorData(input); 125 | memcpy(input_data_ptr, raw_inp, 32*sizeof(TF_FLOAT)); 126 | */ 127 | /* TF_CAPI_EXPORT extern TF_Tensor* TF_NewTensor( 128 | TF_DataType, const int64_t* dims, int num_dims, void* data, size_t len, 129 | void (*deallocator)(void* data, size_t len, void* arg), 130 | void* deallocator_arg); 131 | */ 132 | input = TF_NewTensor( 133 | TF_FLOAT, input_dims, 2, raw_inp, 32*sizeof(TF_FLOAT), 134 | &null_dealloc, NULL 135 | ); 136 | 137 | input_values[0] = input; 138 | output_values[0] = NULL; 139 | 140 | 141 | TF_SessionRun( 142 | session, // TF_Session* session, 143 | // RunOptions 144 | NULL, // const TF_Buffer* run_options, 145 | // Input tensors 146 | &input_tfoutput, // const TF_Output* inputs 147 | input_values, // TF_Tensor* const* input_values 148 | 1, // int ninputs 149 | // Output tensors 150 | &output_tfoutput, // const TF_Output* outputs 151 | output_values, // TF_Tensor** output_values 152 | 1, // int noutputs 153 | // Target operations 154 | NULL, // const TF_Operation* const* target_opers 155 | 0, // int ntargets 156 | // RunMetadata 157 | NULL, // TF_Buffer* run_metadata 158 | // Output status 159 | status // TF_Status* status 160 | ); 161 | if (TF_OK != TF_GetCode(status)) { 162 | printf("run error %s\n", TF_Message(status)); 163 | } 164 | 165 | output_data_ptr = TF_TensorData(output_values[0]); 166 | output_value = *((float*)output_data_ptr); 167 | 168 | printf("\n\nFinished, output= %f\n", output_value); 169 | if ((output_value - -0.479371) > 1e-6) { 170 | printf("Output does not match, FAILED!\n"); 171 | } else { 172 | printf("Output is correct, SUCCESS!\n"); 173 | } 174 | 175 | 176 | /* Do stuff */ 177 | 178 | return 0; 179 | } 180 | -------------------------------------------------------------------------------- /fortran-tf-lib/tests/load_wavenet.F90: -------------------------------------------------------------------------------- 1 | program load_wavenet 2 | use TF_Types 3 | use TF_Interface 4 | use iso_c_binding 5 | implicit none 6 | 7 | type(TF_Session) :: session 8 | type(TF_SessionOptions) :: sessionoptions 9 | type(TF_Graph) :: graph 10 | type(TF_Status) :: stat 11 | type(TF_Buffer) :: buff 12 | type(TF_Output), dimension(1) :: input_tfoutput 13 | type(TF_Output), dimension(33) :: output_tfoutput 14 | type(TF_Output) :: output_temp 15 | character(100) :: vers 16 | character(100), dimension(1) :: tags 17 | type(TF_Tensor), dimension(1) :: input_tensors 18 | type(TF_Tensor), dimension(33) :: output_tensors 19 | type(TF_Operation), dimension(1) :: target_opers 20 | 21 | real, dimension(80,2), target :: raw_data 22 | real, dimension(33,2) :: answers 23 | real, dimension(:), pointer :: output_data_ptr 24 | real, dimension(:,:), pointer :: input_data_ptr 25 | integer(kind=c_int64_t), dimension(2) :: input_dims 26 | !integer(kind=c_int64_t), dimension(2) :: output_dims 27 | type(c_ptr) :: raw_data_ptr 28 | type(c_ptr) :: output_c_data_ptr 29 | integer :: i, j 30 | integer, dimension(33) :: arses 31 | 32 | arses = (/ 0, 1, 12, 23, 27, 28, 29, 30, 31, 32, & 33 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, & 34 | 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, & 35 | 24, 25, 26 /) 36 | 37 | raw_data = reshape( [ & 38 | -0.50056173, -0.43191799, -0.45814849, -0.42308065, -0.39608876, & 39 | -0.42058786, -0.48943563, -0.55914003, -0.59548319, -0.59470056, & 40 | -0.56057665, -0.49053039, -0.41273755, -0.36022401, -0.33099403, & 41 | -0.32663929, -0.34909558, -0.38657446, -0.42591572, -0.45876062, & 42 | -0.48707832, -0.51761326, -0.55789608, -0.61785765, -0.67222452, & 43 | -0.70736123, -0.72341793, -0.71667408, -0.6970197 , -0.6718729 , & 44 | -0.64062628, -0.59585244, -0.53097871, -0.45619551, -0.37970914, & 45 | -0.29086245, -0.19137345, -0.15862776, -0.07856159, -0.0519871 , & 46 | 5.6096781 , 0.51163358, 0.1616316 , -0.02448044, 0.05035503, & 47 | 0.2469078 , 0.50501318, 0.81789669, 1.17244124, 1.57535735, & 48 | 1.97959384, 2.33531016, 2.61409588, 2.83465703, 3.07406614, & 49 | 3.34655064, 3.67657978, 4.04060131, 4.46902248, 4.89025021, & 50 | 5.25250829, 5.42881022, 5.29634559, 5.09095451, 5.22459565, & 51 | 6.09452842, 7.91161833, 8.96641697, 6.98748466, 4.96716228, & 52 | 3.60061822, 2.61833073, 1.83748635, 1.17997637, 0.58276169, & 53 | 0.07409358, -0.35991023, -0.73380562, -1.09849017, -1.39652227, & ! end of second 54 | -0.50056173, -0.43191799, -0.45814849, -0.42308065, -0.39608876, & 55 | -0.42058786, -0.48943563, -0.55914003, -0.59548319, -0.59470056, & 56 | -0.56057665, -0.49053039, -0.41273755, -0.36022401, -0.33099403, & 57 | -0.32663929, -0.34909558, -0.38657446, -0.42591572, -0.45876062, & 58 | -0.48707832, -0.51761326, -0.55789608, -0.61785765, -0.67222452, & 59 | -0.70736123, -0.72341793, -0.71667408, -0.6970197 , -0.6718729 , & 60 | -0.64062628, -0.59585244, -0.53097871, -0.45619551, -0.37970914, & 61 | -0.29086245, -0.19137345, -0.15286976, -0.07272775, -0.04584531, & 62 | 5.6096781 , 0.51163358, 0.1616316 , -0.02448044, 0.05035503, & 63 | 0.2469078 , 0.50501318, 0.81789669, 1.17244124, 1.57535735, & 64 | 1.97959384, 2.33531016, 2.61409588, 2.83465703, 3.07406614, & 65 | 3.34655064, 3.67657978, 4.04060131, 4.46902248, 4.89025021, & 66 | 5.25250829, 5.42881022, 5.29634559, 5.09095451, 5.22459565, & 67 | 6.09452842, 7.91161833, 8.96641697, 6.98748466, 4.96716228, & 68 | 3.60061822, 2.61833073, 1.83748635, 1.17997637, 0.58276169, & 69 | 0.07409358, -0.35991023, -0.73380562, -1.09849017, -1.39652227 & ! end of first 70 | ], shape(raw_data) ) 71 | 72 | answers = reshape( [ & 73 | 0.28690988, 0.3936974 , 0.42413083, -0.36170343, 0.05769337, & 74 | -0.49545187, 0.09840355, 0.05283355, 0.35415295, 0.46001926, & 75 | -1.7458353 , -0.33428577, 0.7925055 , 0.09023142, -0.2546197 , & 76 | 0.78985447, 0.4075164 , 0.4059543 , 0.30803442, -0.49953395, & 77 | -0.21440051, -0.00722447, 0.34737316, 0.10773647, 0.670053, & 78 | 0.30601332, 0.02975837, 0.00956418, 0.62304896, -1.0564077, & 79 | 0.11049131, 0.53186846, -0.5389675, & 80 | 0.28733265, 0.39368165, 0.42472696, -0.3621848, 0.05769337, -0.49498397, & 81 | 0.09840355, 0.05352264, 0.35404724, 0.46033886, -1.7465684, -0.33426875, & 82 | 0.7924648, 0.09228674, -0.25490162, 0.7896581, 0.4081997, 0.40644962, & 83 | 0.30809644, -0.49818873, -0.2132362, -0.00673803, 0.35211396, 0.10806128, & 84 | 0.6702236, 0.3061434, 0.03039804, 0.01004943, 0.6235992, -1.0581621, & 85 | 0.11002517, 0.5317495, -0.5416618 & 86 | ], shape(answers) ) 87 | 88 | input_dims = (/ 2, 80 /) 89 | !output_dims = (/ 33, 1 /) 90 | tags(1) = 'serve' 91 | 92 | call TF_Version(vers) 93 | write(*,*)'hello from Tensorflow version', vers 94 | 95 | sessionoptions = TF_NewSessionOptions() 96 | graph = TF_NewGraph() 97 | !buff = TF_NewBuffer() 98 | stat = TF_NewStatus() 99 | 100 | session = TF_LoadSessionFromSavedModel(sessionoptions, & 101 | '../wavenet/wavenet_gwfu.savedmodel', & 102 | tags, 1, graph, stat) 103 | 104 | if (TF_GetCode( stat ) .ne. TF_OK) then 105 | call TF_Message( stat, vers ) 106 | write(*,*) TF_GetCode( stat ), vers 107 | stop 108 | endif 109 | 110 | call TF_DeleteSessionOptions(sessionoptions) 111 | call TF_DeleteBuffer(buff) 112 | 113 | ! now can use session 114 | input_tfoutput%oper = TF_GraphOperationByName( graph, "serving_default_input_1" ) 115 | input_tfoutput%index = 0 116 | if (.not.c_associated(input_tfoutput(1)%oper%p)) then 117 | write(*,*)'input not associated' 118 | stop 119 | endif 120 | 121 | do i = 1, 33 122 | output_tfoutput(i)%oper = TF_GraphOperationByName( graph, "StatefulPartitionedCall" ) 123 | output_tfoutput(i)%index = arses(i) 124 | if (.not.c_associated(output_tfoutput(i)%oper%p)) then 125 | write(*,*)'output ', i, ' not associated' 126 | stop 127 | endif 128 | end do 129 | 130 | raw_data_ptr = c_loc(raw_data) 131 | input_tensors(1) = TF_NewTensor( TF_FLOAT, input_dims, 2, raw_data_ptr, int(2*80*4, kind=c_size_t) ) 132 | call c_f_pointer( TF_TensorData( input_tensors(1)), input_data_ptr, shape(raw_data) ) 133 | do j = 1, 2 134 | do i = 1, 80 135 | write(*,*)'input data', i, j, input_data_ptr(i,j), input_data_ptr(i,j) - raw_data(i,j) 136 | end do 137 | end do 138 | 139 | call TF_SessionRun( session, input_tfoutput, input_tensors, 1, output_tfoutput, output_tensors, 33, & 140 | target_opers, 0, stat ) 141 | if (TF_GetCode( stat ) .ne. TF_OK) then 142 | call TF_Message( stat, vers ) 143 | write(*,*) TF_GetCode( stat ), vers 144 | stop 145 | endif 146 | 147 | !call c_f_pointer( output_c_data_ptr, output_data_ptr, shape(output_data_ptr) ) 148 | do j = 1, 2 149 | do i = 1, 33 150 | call c_f_pointer( TF_TensorData( output_tensors(i)), output_data_ptr, shape(output_data_ptr) ) 151 | write(*,*)'output data', output_data_ptr(j), output_data_ptr(j) - answers(i,j) 152 | end do 153 | end do 154 | call TF_DeleteTensor( input_tensors(1) ) 155 | call TF_DeleteTensor( output_tensors(1) ) 156 | call TF_DeleteGraph( graph ) 157 | call TF_DeleteSession( session, stat ) 158 | call TF_DeleteStatus( stat ) 159 | 160 | end program load_wavenet 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fortran-TF-lib 2 | 3 | ![GitHub](https://img.shields.io/github/license/Cambridge-ICCS/fortran-tf-lib) 4 | 5 | Code and examples for directly calling Tensorflow ML models from Fortran. 6 | For calling *PyTorch* from Fortran see the [FTorch repository](https://github.com/Cambridge-ICCS/fortran-pytorch-lib). 7 | 8 | ## Contents 9 | - [Description](#description) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Examples](#examples) 13 | - [License](#license) 14 | - [Contributions](#contributions) 15 | - [Authors and Acknowledgment](#authors-and-acknowledgment) 16 | - [Users](#used-by) 17 | 18 | ## Description 19 | 20 | It is desirable be able to run machine learning (ML) models directly in Fortran. 21 | Such models are often trained in some other language (say Python) using popular frameworks (say TensorFlow) and saved. 22 | We want to run inference on this model without having to call a Python executable. 23 | To achieve this we use the existing TensorFlow C interface. 24 | 25 | This project provides a library enabling a user to directly couple their TensorFlow models to Fortran code. 26 | We provide installation instructions for the library as well as instructions and examples for performing coupling. 27 | This library implements only enough of the TensorFlow C API to allow inference, no training. 28 | 29 | Project status: This project is currently in pre-release with documentation and code being prepared for a first release. 30 | As such breaking changes may be made. 31 | If you are interested in using this library please get in touch. 32 | 33 | 34 | ## Installation 35 | 36 | ### Dependencies 37 | 38 | To install the library requires the following to be installed on the system: 39 | 40 | * cmake >= 3.1 41 | * TensorFlow C API, download from 1 42 | * Fortran and C compilers 43 | 44 | 1 Note that this page sometimes does not list the latest version of 45 | the library. You can try altering the library download URLs on the page to 46 | reflect the newest version. E.g. if the URL ends `...-2.11.tar.gz` try 47 | changing it to `...-2.13.tar.gz`. 48 | 49 | ### Library installation 50 | 51 | To build and install the library: 52 | 53 | 1. Navigate to the location in which you wish to install the source and run: 54 | ``` 55 | git clone git@github.com:Cambridge-ICCS/fortran-tf-lib.git 56 | ``` 57 | to clone via ssh, or 58 | ``` 59 | git clone https://github.com/Cambridge-ICCS/fortran-tf-lib.git 60 | ``` 61 | to clone via https. 62 | 2. Navigate into the library directory by running: 63 | ``` 64 | cd fortran-tf-lib/fortran-tf-lib/ 65 | ``` 66 | 3. Create a `build` directory and execute cmake from within it using the relevant flags: 67 | ``` 68 | mkdir build 69 | cd build 70 | cmake .. -DCMAKE_BUILD_TYPE=Release 71 | ``` 72 | It is likely that you will need to provide at least the `TENSORFLOW_LOCATION` flag. 73 | The Fortran compiler must be the same one that you are planning to compile your Fortran 74 | code with. It is advisable to use C and Fortran compilers from the same provider. 75 | 76 | The following CMake flags are available and can be passed as arguments through `-D` | Location of TensorFlow C API installation1. | 82 | | [`CMAKE_INSTALL_PREFIX`](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html) | `` | Location at which the library files should be installed. By default this is `/usr/local`. | 83 | | [`CMAKE_BUILD_TYPE`](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html) | `Release` / `Debug` | Specifies build type. The default is `Debug`, use `Release` for production code.| 84 | 85 | 2 This should be the absolute path to where the TensorFlow C API mentioned in step 1 has been installed. CMake will look in `TENSORFLOW_LOCATION` and `TENSORFLOW_LOCATION/lib` for the TensorFlow library `libtensorflow.so`. 86 | 4. Make and install the code to the chosen location with: 87 | ``` 88 | make 89 | make install 90 | ``` 91 | This will place the following directories at the install location: 92 | * `CMAKE_INSTALL_PREFIX/include/` - contains mod files 93 | * `CMAKE_INSTALL_PREFIX/lib64/` - contains `cmake` directory and `.so` files 94 | 95 | 96 | ## Usage 97 | 98 | In order to use fortran-tf users will typically need to follow these steps: 99 | 100 | 1. Save a TensorFlow model in the Keras SavedModel format. 101 | 2. Write Fortran using the fortran-tf-lib bindings to use the model from within Fortran. 102 | 3. Build and compile the code, linking against fortran-tf-lib. 103 | 104 | 105 | ### 1. Saving the model 106 | 107 | The trained model needs to be exported. This can be done from within your code 108 | using the 109 | [`model.save`](https://www.tensorflow.org/guide/keras/serialization_and_saving) 110 | functionality from within python. Note that the TensorFlow C API currently 111 | (version 2.13) only supports the Keras "v2" format so you must specify `format='tf'`: 112 | ``` 113 | import tensorflow as tf 114 | # construct model (e.g. model=tf.keras.Model(inputs, outputs)) 115 | # or load one (e.g. model=tf.keras.models.load_model('/path/to/model')) 116 | model.save("my_model", format='tf') 117 | ``` 118 | 119 | ### 2. Using the model from Fortran 120 | 121 | To use the trained TensorFlow model from within Fortran we need to import the 122 | `TF_Interface` module and use the binding routines to load the model, construct 123 | the tensors, and run inference. 124 | 125 | A very simple example is given below. For more detailed documentation please 126 | consult the API documentation, source code, and examples. 127 | 128 | This minimal snippet loads a saved TensorFlow model, creates an input consisting of 129 | a `1x32` matrix (with arbitrary values), and runs the model to infer the 130 | output. If you use the model provided in the test case this code will produce 131 | the indicated output value. 132 | 133 | ```fortran 134 | program test_program 135 | use TF_Types 136 | use TF_Interface 137 | use iso_c_binding 138 | implicit none 139 | 140 | type(TF_Session) :: session 141 | type(TF_SessionOptions) :: sessionoptions 142 | type(TF_Graph) :: graph 143 | type(TF_Status) :: stat 144 | type(TF_Output), dimension(1) :: input_tfoutput, output_tfoutput 145 | character(100) :: vers 146 | character(100), dimension(1) :: tags 147 | type(TF_Tensor), dimension(1) :: input_tensors, output_tensors, test_tensor 148 | type(TF_Operation), dimension(1) :: target_opers 149 | 150 | real, dimension(32), target :: raw_data 151 | real, dimension(:), pointer :: output_data_ptr 152 | integer(kind=c_int64_t), dimension(2) :: input_dims 153 | integer(kind=c_int64_t), dimension(2) :: output_dims 154 | type(c_ptr) :: raw_data_ptr 155 | type(c_ptr) :: output_c_data_ptr 156 | 157 | raw_data = (/ & 158 | 0.71332126, 0.81275973, 0.66596436, 0.79570779, 0.83973302, 0.76604397, & 159 | 0.84371391, 0.92582056, 0.32038017, 0.0732005, 0.80589203, 0.75226581, & 160 | 0.81602784, 0.59698078, 0.32991729, 0.43125108, 0.4368422, 0.88550326, & 161 | 0.7131253, 0.14951148, 0.22084413, 0.70801317, 0.69433906, 0.62496564, & 162 | 0.50744999, 0.94047845, 0.18191579, 0.2599102, 0.53161889, 0.57402205, & 163 | 0.50751284, 0.65207096 & 164 | /) 165 | 166 | 167 | input_dims = (/ 1, 32 /) 168 | output_dims = (/ 1, 1 /) 169 | tags(1) = 'serve' 170 | 171 | ! Print TensorFlow library version 172 | call TF_Version(vers) 173 | write(*,*)'Tensorflow version', vers 174 | 175 | sessionoptions = TF_NewSessionOptions() 176 | graph = TF_NewGraph() 177 | stat = TF_NewStatus() 178 | 179 | ! Load session (also populates graph) 180 | session = TF_LoadSessionFromSavedModel(sessionoptions, '/path/to/model', tags, 1, & 181 | graph, stat) 182 | 183 | if (TF_GetCode( stat ) .ne. TF_OK) then 184 | call TF_Message( stat, vers ) 185 | write(*,*)'woops', TF_GetCode( stat ), vers 186 | call abort 187 | endif 188 | 189 | call TF_DeleteSessionOptions(sessionoptions) 190 | 191 | input_tfoutput(1)%oper = TF_GraphOperationByName( graph, "serving_default_input_1" ) 192 | input_tfoutput(1)%index = 0 193 | if (.not.c_associated(input_tfoutput(1)%oper%p)) then 194 | write(*,*)'input not associated' 195 | stop 196 | endif 197 | 198 | output_tfoutput(1)%oper = TF_GraphOperationByName( graph, "StatefulPartitionedCall" ) 199 | output_tfoutput(1)%index = 0 200 | if (.not.c_associated(output_tfoutput(1)%oper%p)) then 201 | write(*,*)'output not associated' 202 | stop 203 | endif 204 | 205 | ! Bind the input tensor 206 | raw_data_ptr = c_loc(raw_data) 207 | input_tensors(1) = TF_NewTensor( TF_FLOAT, input_dims, 2, raw_data_ptr, int(128, kind=c_size_t) ) 208 | 209 | ! Run inference 210 | call TF_SessionRun( session, input_tfoutput, input_tensors, 1, output_tfoutput, output_tensors, 1, & 211 | target_opers, 0, stat ) 212 | if (TF_GetCode( stat ) .ne. TF_OK) then 213 | call TF_Message( stat, vers ) 214 | write(*,*) TF_GetCode( stat ), vers 215 | call abort 216 | endif 217 | 218 | ! Bind output tensor 219 | call c_f_pointer( TF_TensorData( output_tensors(1)), output_data_ptr, shape(output_data_ptr) ) 220 | write(*,*)'output data', output_data_ptr(1) 221 | 222 | if ((output_data_ptr(1) - -0.479371) .gt. 1e-6) then 223 | write(*,*)'Output does not match, FAILED!' 224 | else 225 | write(*,*)'Output is correct, SUCCESS!' 226 | endif 227 | 228 | 229 | ! Clean up 230 | call TF_DeleteTensor( input_tensors(1) ) 231 | call TF_DeleteTensor( output_tensors(1) ) 232 | call TF_DeleteGraph( graph ) 233 | call TF_DeleteSession( session, stat ) 234 | call TF_DeleteStatus( stat ) 235 | 236 | end program test_program 237 | ``` 238 | #### Generating code with `process_model` 239 | The example code above illustrates a problem with the TensorFlow C API 240 | that our Fortran wrapper cannot fix. To load a model, the library requires 241 | that the caller knows certain rather opaque model parameters beforehand. 242 | Often, the values in the example above will work for the `tags` parameter 243 | to `TF_LoadSessionFromSavedModel`. However, the values needed for 244 | `TF_GraphOperationByName` (in this case `serving_default_input_1`, etc) 245 | are more likely to be different. 246 | 247 | To address this, we provide a Python script, `process_model` that will 248 | read a Keras SavedModel and output a simple Fortran module intended to 249 | provide a base for the user to start from. The appropriate values will 250 | be read from the model and hard-coded into the Fortran code. 251 | 252 | E.g. 253 | ``` 254 | process_model -o fortran_code.f90 my_model 255 | ``` 256 | 257 | ### 3. Build the code 258 | 259 | The code now needs to be compiled and linked against our installed library. 260 | 261 | #### CMake 262 | If our project were using cmake we would need the following in the 263 | `CMakeLists.txt` file to find the the tf-lib installation and link it to the 264 | executable. 265 | 266 | This can be done by adding the following to the `CMakeLists.txt` file: 267 | ``` 268 | find_package(FortranTensorFlow) 269 | target_link_libraries( PRIVATE FortranTensorFlow::fortran-tf ) 270 | message(STATUS "Building with Fortran TensorFlow coupling") 271 | ``` 272 | and using the `-DCMAKE_PREFIX=` flag when running cmake. 273 | 274 | When running the generated code you may also need to add the location of the 275 | `.so` files to your `LD_LIBRARY_PATH` unless installing in a default location: 276 | ``` 277 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib64 278 | ``` 279 | 280 | ## Examples 281 | 282 | Examples of how to use this library will be provided in the [examples directory](examples/). 283 | They demonstrate different functionalities and are provided with instructions to modify, build, and run as necessary. 284 | 285 | ## License 286 | 287 | Copyright © ICCS 288 | 289 | *Fortran-TF-Lib* is distributed under the [MIT Licence](https://github.com/Cambridge-ICCS/fortran-tf-lib/blob/main/LICENSE). 290 | 291 | 292 | ## Contributions 293 | 294 | Contributions and collaborations are welcome. 295 | 296 | For bugs, feature requests, and clear suggestions for improvement please 297 | [open an issue](https://github.com/Cambridge-ICCS/fortran-tf-lib/issues). 298 | 299 | If you have built something upon _Fortran-TF-Lib_ that would be useful to others, or can 300 | address an [open issue](https://github.com/Cambridge-ICCS/fortran-tf-lib/issues), please 301 | [fork the repository](https://github.com/Cambridge-ICCS/fortran-tf-lib/fork) and open a 302 | pull request. 303 | 304 | 305 | ### Code of Conduct 306 | Everyone participating in the _Fortran-TF-Lib_ project, and in particular in the 307 | issue tracker, pull requests, and social media activity, is expected to treat other 308 | people with respect and, more generally, to follow the guidelines articulated in the 309 | [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/). 310 | 311 | 312 | ## Authors and Acknowledgment 313 | 314 | *Fortran-TF-Lib* is written and maintained by the [ICCS](https://github.com/Cambridge-ICCS) 315 | 316 | Notable contributors to this project are: 317 | 318 | * [**@SimonClifford**](https://github.com/SimonClifford) 319 | 320 | See [Contributors](https://github.com/Cambridge-ICCS/fortran-tf-lib/graphs/contributors) 321 | for a full list. 322 | 323 | 324 | ## Used by 325 | The following projects make use of this code or derivatives in some way: 326 | 327 | * [DataWave - MiMA ML](https://github.com/DataWaveProject/MiMA-machine-learning) 328 | 329 | Are we missing anyone? Let us know. 330 | 331 | 332 | 333 | -------------------------------------------------------------------------------- /fortran-tf-lib/src/fortran_tensorflow_lib.F90: -------------------------------------------------------------------------------- 1 | module TF_Types 2 | use iso_c_binding 3 | type TF_SessionOptions 4 | type(c_ptr) :: p = c_null_ptr 5 | end type TF_SessionOptions 6 | 7 | type TF_Graph 8 | type(c_ptr) :: p = c_null_ptr 9 | end type TF_Graph 10 | 11 | type TF_Status 12 | type(c_ptr) :: p = c_null_ptr 13 | end type TF_Status 14 | 15 | type TF_Session 16 | type(c_ptr) :: p = c_null_ptr 17 | end type TF_Session 18 | 19 | type TF_Buffer 20 | type(c_ptr) :: p = c_null_ptr 21 | end type TF_Buffer 22 | 23 | ! From tensorflow/c/tf_status.h 24 | enum, bind(c) 25 | enumerator :: TF_OK = 0 26 | enumerator :: TF_CANCELLED = 1 27 | enumerator :: TF_UNKNOWN = 2 28 | enumerator :: TF_INVALID_ARGUMENT = 3 29 | enumerator :: TF_DEADLINE_EXCEEDED = 4 30 | enumerator :: TF_NOT_FOUND = 5 31 | enumerator :: TF_ALREADY_EXISTS = 6 32 | enumerator :: TF_PERMISSION_DENIED = 7 33 | enumerator :: TF_UNAUTHENTICATED = 16 34 | enumerator :: TF_RESOURCE_EXHAUSTED = 8 35 | enumerator :: TF_FAILED_PRECONDITION = 9 36 | enumerator :: TF_ABORTED = 10 37 | enumerator :: TF_OUT_OF_RANGE = 11 38 | enumerator :: TF_UNIMPLEMENTED = 12 39 | enumerator :: TF_INTERNAL = 13 40 | enumerator :: TF_UNAVAILABLE = 14 41 | enumerator :: TF_DATA_LOSS = 15 42 | end enum 43 | 44 | type TF_Operation 45 | type(c_ptr) :: p = c_null_ptr 46 | end type TF_Operation 47 | 48 | type TF_Output 49 | type(TF_Operation) :: oper 50 | integer(kind=c_int) :: index 51 | end type TF_Output 52 | 53 | !private 54 | type, bind(c) :: TF_Output_Actual 55 | type(c_ptr) :: oper_ptr 56 | integer(kind=c_int) :: index 57 | end type TF_Output_Actual 58 | 59 | type TF_Tensor 60 | type(c_ptr) :: p = c_null_ptr 61 | end type TF_Tensor 62 | 63 | ! From tensorflow/c/tf_datatype.h 64 | enum, bind(c) 65 | enumerator :: TF_FLOAT = 1 66 | enumerator :: TF_DOUBLE = 2 67 | enumerator :: TF_INT32 = 3 ! Int32 tensors are always in 'host' memory. 68 | enumerator :: TF_UINT8 = 4 69 | enumerator :: TF_INT16 = 5 70 | enumerator :: TF_INT8 = 6 71 | enumerator :: TF_STRING = 7 72 | enumerator :: TF_COMPLEX64 = 8 ! Single-precision complex 73 | enumerator :: TF_COMPLEX = 8 ! Old identifier kept for API backwards compatibility 74 | enumerator :: TF_INT64 = 9 75 | enumerator :: TF_BOOL = 10 76 | enumerator :: TF_QINT8 = 11 ! Quantized int8 77 | enumerator :: TF_QUINT8 = 12 ! Quantized uint8 78 | enumerator :: TF_QINT32 = 13 ! Quantized int32 79 | enumerator :: TF_BFLOAT16 = 14 ! Float32 truncated to 16 bits. Only for cast ops. 80 | enumerator :: TF_QINT16 = 15 ! Quantized int16 81 | enumerator :: TF_QUINT16 = 16 ! Quantized uint16 82 | enumerator :: TF_UINT16 = 17 83 | enumerator :: TF_COMPLEX128 = 18 ! Double-precision complex 84 | enumerator :: TF_HALF = 19 85 | enumerator :: TF_RESOURCE = 20 86 | enumerator :: TF_VARIANT = 21 87 | enumerator :: TF_UINT32 = 22 88 | enumerator :: TF_UINT64 = 23 89 | end enum 90 | 91 | end module TF_Types 92 | 93 | module TF_Interface 94 | implicit none 95 | private :: fstring 96 | private :: cstring 97 | private :: null_dealloc 98 | 99 | interface TF_NewTensor 100 | #ifdef USE_F2018 101 | module procedure TF_NewTensor_2018 102 | #endif 103 | module procedure TF_NewTensor_cptr 104 | end interface TF_NewTensor 105 | 106 | contains 107 | 108 | subroutine fstring( s ) 109 | use iso_c_binding 110 | character(*) :: s 111 | integer :: ind 112 | 113 | ind = index(s, c_null_char) 114 | if (ind > 0) then 115 | s(ind:) = ' ' 116 | endif 117 | end subroutine fstring 118 | 119 | subroutine cstring( s ) 120 | use iso_c_binding 121 | character(*) :: s 122 | integer :: ind 123 | 124 | ind = len_trim(s) + 1 125 | s(ind:ind) = c_null_char 126 | end subroutine cstring 127 | 128 | ! Pass in vers_str large enough to accept the version string. 129 | ! If it's too short the string will be truncated. 130 | subroutine TF_Version( vers_str ) 131 | use iso_c_binding 132 | character(*) :: vers_str 133 | character(len=len(vers_str)), pointer :: p => null() 134 | type(c_ptr) :: output 135 | interface 136 | function TF_Version_c() bind(c, name="TF_Version") 137 | use iso_c_binding 138 | type(c_ptr) :: TF_Version_c 139 | end function TF_Version_c 140 | end interface 141 | 142 | output = TF_Version_c() 143 | call c_f_pointer(output, p) 144 | vers_str = p 145 | call fstring( vers_str ) 146 | end subroutine TF_Version 147 | 148 | function TF_NewBuffer() 149 | use TF_Types 150 | type(TF_Buffer) :: TF_NewBuffer 151 | interface 152 | function TF_NewBuffer_c() bind(c, name="TF_NewBuffer") 153 | use iso_c_binding 154 | type(c_ptr) :: TF_NewBuffer_c 155 | end function TF_NewBuffer_c 156 | end interface 157 | 158 | TF_NewBuffer%p = TF_NewBuffer_c() 159 | end function TF_NewBuffer 160 | 161 | subroutine TF_DeleteBuffer( buffer ) 162 | use TF_Types 163 | type(TF_Buffer) :: buffer 164 | interface 165 | subroutine TF_DeleteBuffer_c( buffer ) bind(c, name="TF_DeleteBuffer") 166 | use iso_c_binding 167 | type(c_ptr), value :: buffer 168 | end subroutine TF_DeleteBuffer_c 169 | end interface 170 | 171 | call TF_DeleteBuffer_c( buffer%p ) 172 | end subroutine TF_DeleteBuffer 173 | 174 | function TF_NewSessionOptions() 175 | use TF_Types 176 | type(TF_SessionOptions) :: TF_NewSessionOptions 177 | interface 178 | function TF_NewSessionOptions_c() bind(c, name="TF_NewSessionOptions") 179 | use iso_c_binding 180 | type(c_ptr) :: TF_NewSessionOptions_c 181 | end function TF_NewSessionOptions_c 182 | end interface 183 | 184 | TF_NewSessionOptions%p = TF_NewSessionOptions_c() 185 | end function TF_NewSessionOptions 186 | 187 | subroutine TF_DeleteSessionOptions( sessionoptions ) 188 | use TF_Types 189 | type(TF_SessionOptions) :: sessionoptions 190 | interface 191 | subroutine TF_DeleteSessionOptions_c( sessionoptions ) bind(c, name="TF_DeleteSessionOptions") 192 | use iso_c_binding 193 | type(c_ptr), value :: sessionoptions 194 | end subroutine TF_DeleteSessionOptions_c 195 | end interface 196 | 197 | call TF_DeleteSessionOptions_c( sessionoptions%p ) 198 | end subroutine TF_DeleteSessionOptions 199 | 200 | function TF_NewGraph() 201 | use TF_Types 202 | type(TF_Graph) :: TF_NewGraph 203 | interface 204 | function TF_NewGraph_c() bind(c, name="TF_NewGraph") 205 | use iso_c_binding 206 | type(c_ptr) :: TF_NewGraph_c 207 | end function TF_NewGraph_c 208 | end interface 209 | 210 | TF_NewGraph%p = TF_NewGraph_c() 211 | end function TF_NewGraph 212 | 213 | subroutine TF_DeleteGraph( graph ) 214 | use TF_Types 215 | type(TF_Graph) :: graph 216 | interface 217 | subroutine TF_DeleteGraph_c( graph ) bind(c, name="TF_DeleteGraph") 218 | use iso_c_binding 219 | type(c_ptr), value :: graph 220 | end subroutine TF_DeleteGraph_c 221 | end interface 222 | 223 | call TF_DeleteGraph_c( graph%p ) 224 | end subroutine TF_DeleteGraph 225 | 226 | function TF_NewStatus() 227 | use TF_Types 228 | type(TF_Status) :: TF_NewStatus 229 | interface 230 | function TF_NewStatus_c() bind(c, name="TF_NewStatus") 231 | use iso_c_binding 232 | type(c_ptr) :: TF_NewStatus_c 233 | end function TF_NewStatus_c 234 | end interface 235 | 236 | TF_NewStatus%p = TF_NewStatus_c() 237 | end function TF_NewStatus 238 | 239 | subroutine TF_DeleteStatus( stat ) 240 | use TF_Types 241 | type(TF_Status) :: stat 242 | interface 243 | subroutine TF_DeleteStatus_c( stat ) bind(c, name="TF_DeleteStatus") 244 | use iso_c_binding 245 | type(c_ptr), value :: stat 246 | end subroutine TF_DeleteStatus_c 247 | end interface 248 | 249 | call TF_DeleteStatus_c( stat%p ) 250 | end subroutine TF_DeleteStatus 251 | 252 | function TF_LoadSessionFromSavedModel( session_options, export_dir, tags, tags_len, & 253 | graph, stat, run_options_opt, meta_graph_def_opt ) 254 | use TF_Types 255 | type(TF_Session) :: TF_LoadSessionFromSavedModel 256 | type(TF_SessionOptions) :: session_options 257 | character(len=*) :: export_dir 258 | character(*), dimension(:) :: tags 259 | integer :: tags_len 260 | type(TF_Graph) :: graph 261 | type(TF_Status) :: stat 262 | type(TF_Buffer), optional :: run_options_opt 263 | type(TF_Buffer) :: run_options 264 | type(TF_Buffer), optional :: meta_graph_def_opt 265 | type(TF_Buffer) :: meta_graph_def 266 | 267 | character(len=len(export_dir)+1), target :: export_dir_temp 268 | type(c_ptr) :: export_dir_ptr 269 | type(c_ptr), dimension(tags_len), target :: tag_ptrs 270 | integer :: i 271 | character(len=len(tags)+1), dimension(tags_len), target :: tags_temp 272 | 273 | interface 274 | function TF_LoadSessionFromSavedModel_c( & 275 | session_options, run_options, export_dir, & 276 | tags, tags_len, graph, meta_graph_def, & 277 | stat & 278 | ) bind(c, name="TF_LoadSessionFromSavedModel") 279 | use iso_c_binding 280 | type(c_ptr) :: TF_LoadSessionFromSavedModel_c 281 | type(c_ptr), value :: session_options 282 | type(c_ptr), value :: run_options 283 | type(c_ptr), value :: export_dir 284 | type(c_ptr), value :: tags 285 | integer(kind=c_int), value :: tags_len 286 | type(c_ptr), value :: graph 287 | type(c_ptr), value :: meta_graph_def 288 | type(c_ptr), value :: stat 289 | end function TF_LoadSessionFromSavedModel_c 290 | end interface 291 | 292 | do i = 1, tags_len 293 | tags_temp(i) = tags(i) 294 | call cstring(tags_temp(i)) 295 | tag_ptrs(i) = c_loc(tags_temp(i)) 296 | end do 297 | export_dir_temp = export_dir 298 | call cstring(export_dir_temp) 299 | export_dir_ptr = c_loc(export_dir_temp) 300 | 301 | if (present(run_options_opt)) then 302 | run_options = run_options_opt 303 | else 304 | run_options%p = c_null_ptr 305 | endif 306 | if (present(meta_graph_def_opt)) then 307 | meta_graph_def = meta_graph_def_opt 308 | else 309 | meta_graph_def%p = c_null_ptr 310 | endif 311 | 312 | TF_LoadSessionFromSavedModel%p = & 313 | TF_LoadSessionFromSavedModel_c( & 314 | session_options%p, run_options%p, export_dir_ptr, & 315 | c_loc(tag_ptrs), tags_len, graph%p, meta_graph_def%p, & 316 | stat%p & 317 | ) 318 | end function TF_LoadSessionFromSavedModel 319 | 320 | subroutine TF_DeleteSession( session, stat ) 321 | use TF_Types 322 | type(TF_Session) :: session 323 | type(TF_Status) :: stat 324 | interface 325 | subroutine TF_DeleteSession_c( session, stat ) bind(c, name="TF_DeleteSession") 326 | use iso_c_binding 327 | type(c_ptr), value :: session 328 | type(c_ptr), value :: stat 329 | end subroutine TF_DeleteSession_c 330 | end interface 331 | 332 | call TF_DeleteSession_c( session%p, stat%p ) 333 | end subroutine TF_DeleteSession 334 | 335 | function TF_GetCode( stat ) 336 | use TF_Types 337 | integer(kind(TF_OK)) :: TF_GetCode 338 | type(TF_Status) :: stat 339 | interface 340 | function TF_GetCode_c( stat ) bind(c, name="TF_GetCode") 341 | use iso_c_binding 342 | integer(kind(TF_OK)) :: TF_GetCode_c 343 | type(c_ptr), value :: stat 344 | end function TF_GetCode_c 345 | end interface 346 | 347 | TF_GetCode = TF_GetCode_c( stat%p ) 348 | end function TF_GetCode 349 | 350 | subroutine TF_Message( stat, message ) 351 | use TF_Types 352 | character(*) :: message 353 | type(TF_Status) :: stat 354 | character(len=len(message)), pointer :: p => null() 355 | type(c_ptr) :: output 356 | interface 357 | function TF_Message_c( stat ) bind(c, name="TF_Message") 358 | use iso_c_binding 359 | type(c_ptr) :: TF_Message_c 360 | type(c_ptr), value :: stat 361 | end function TF_Message_c 362 | end interface 363 | 364 | output = TF_Message_c( stat%p ) 365 | call c_f_pointer(output, p) 366 | message = p 367 | call fstring( message ) 368 | end subroutine TF_Message 369 | 370 | !TF_CAPI_EXPORT extern TF_Operation* TF_GraphOperationByName(TF_Graph* graph, const char* oper_name); 371 | function TF_GraphOperationByName( graph, name ) 372 | use TF_Types 373 | type(TF_Operation) :: TF_GraphOperationByName 374 | type(TF_Graph) :: graph 375 | character(*) :: name 376 | 377 | character(len=len(name)+1), target :: name_temp 378 | type(c_ptr) :: name_temp_ptr 379 | interface 380 | function TF_GraphOperationByName_c( graph, name ) bind(c, name="TF_GraphOperationByName") 381 | use iso_c_binding 382 | type(c_ptr) :: TF_GraphOperationByName_c 383 | type(c_ptr), value :: graph 384 | type(c_ptr), value :: name 385 | end function TF_GraphOperationByName_c 386 | end interface 387 | 388 | name_temp = name 389 | call cstring(name_temp) 390 | name_temp_ptr = c_loc(name_temp) 391 | TF_GraphOperationByName%p = TF_GraphOperationByName_c( graph%p, name_temp_ptr ) 392 | end function TF_GraphOperationByName 393 | 394 | ! private 395 | subroutine null_dealloc( data, len, arg ) 396 | use iso_c_binding 397 | type(c_ptr) :: data 398 | integer(kind=c_size_t) :: len 399 | type(c_ptr) :: arg 400 | 401 | ! do nothing 402 | 403 | end subroutine null_dealloc 404 | 405 | #ifdef USE_F2018 406 | ! This function relies on Fortran 2018 features: assumed type and assumed rank. 407 | function TF_NewTensor_2018( datatype, dims, num_dims, data, len ) 408 | use TF_Types 409 | type(TF_Tensor) :: TF_NewTensor_2018 410 | integer(kind(TF_FLOAT)) :: datatype 411 | integer(kind=c_int64_t), dimension(:) :: dims 412 | integer :: num_dims 413 | type(*), dimension(..), target, contiguous :: data 414 | integer(kind=c_size_t) :: len 415 | 416 | type(c_ptr) :: data_ptr 417 | 418 | data_ptr = c_loc(data) 419 | TF_NewTensor_2018 = TF_NewTensor_cptr( datatype, dims, num_dims, data_ptr, len ) 420 | end function TF_NewTensor_2018 421 | #endif 422 | ! This function does not use F2018 features but requires caller to create a c_ptr for data 423 | ! and to check that the array pointed to is contiguous. 424 | function TF_NewTensor_cptr( datatype, dims, num_dims, data, len ) 425 | use TF_Types 426 | type(TF_Tensor) :: TF_NewTensor_cptr 427 | integer(kind(TF_FLOAT)) :: datatype 428 | integer(kind=c_int64_t), dimension(num_dims), target :: dims 429 | integer :: num_dims 430 | type(c_ptr) :: data 431 | integer(kind=c_size_t) :: len 432 | 433 | type(c_funptr) :: dealloc_ptr 434 | 435 | interface 436 | !TF_CAPI_EXPORT extern TF_Tensor* TF_NewTensor( 437 | !TF_DataType, const int64_t* dims, int num_dims, void* data, size_t len, 438 | !void (*deallocator)(void* data, size_t len, void* arg), 439 | !void* deallocator_arg); 440 | function TF_NewTensor_c( datatype, dims, num_dims, data, len, deallocator, deallocator_arg ) & 441 | bind(c, name="TF_NewTensor") 442 | use iso_c_binding 443 | type(c_ptr) :: TF_NewTensor_c 444 | integer(kind(TF_FLOAT)), value :: datatype 445 | type(c_ptr), value:: dims 446 | integer(kind=c_int), value :: num_dims 447 | type(c_ptr), value :: data 448 | integer(kind=c_size_t), value :: len 449 | type(c_funptr), value :: deallocator 450 | type(c_ptr), value :: deallocator_arg 451 | 452 | end function TF_NewTensor_c 453 | end interface 454 | 455 | dealloc_ptr = c_funloc(null_dealloc) 456 | TF_NewTensor_cptr%p = TF_NewTensor_c( datatype, c_loc(dims), num_dims, data, len, dealloc_ptr, c_null_ptr ) 457 | end function TF_NewTensor_cptr 458 | 459 | subroutine TF_SessionRun( session, inputs, input_values, ninputs, outputs, output_values, noutputs, & 460 | target_opers, ntargets, stat, run_options_opt, run_metadata_opt ) 461 | use TF_Types 462 | type(TF_Session) :: session 463 | type(TF_Output), dimension(ninputs) :: inputs 464 | type(TF_Tensor), dimension(ninputs) :: input_values 465 | integer(kind=c_int) :: ninputs 466 | type(TF_Output), dimension(noutputs) :: outputs 467 | type(TF_Tensor), dimension(noutputs) :: output_values 468 | integer(kind=c_int) :: noutputs 469 | type(TF_Operation), dimension(ntargets):: target_opers 470 | integer(kind=c_int) :: ntargets 471 | type(TF_Status) :: stat 472 | type(TF_Buffer), optional :: run_options_opt 473 | type(TF_Buffer) :: run_options 474 | type(TF_Buffer), optional :: run_metadata_opt 475 | type(TF_Buffer) :: run_metadata 476 | 477 | type(c_ptr), dimension(ninputs), target :: input_value_ptrs 478 | type(c_ptr), dimension(noutputs), target :: output_value_ptrs 479 | type(c_ptr), dimension(ntargets), target :: target_oper_ptrs 480 | integer :: i 481 | type(TF_Output_Actual), dimension(ninputs), target :: input_act 482 | type(TF_Output_Actual), dimension(noutputs), target :: output_act 483 | type(c_ptr) :: input_act_ptr, output_act_ptr 484 | interface 485 | !TF_CAPI_EXPORT extern void TF_SessionRun( 486 | ! TF_Session* session, 487 | ! // RunOptions 488 | ! const TF_Buffer* run_options, 489 | ! // Input tensors 490 | ! const TF_Output* inputs, TF_Tensor* const* input_values, int ninputs, 491 | ! // Output tensors 492 | ! const TF_Output* outputs, TF_Tensor** output_values, int noutputs, 493 | ! // Target operations 494 | ! const TF_Operation* const* target_opers, int ntargets, 495 | ! // RunMetadata 496 | ! TF_Buffer* run_metadata, 497 | ! // Output status 498 | ! TF_Status*); 499 | subroutine TF_SessionRun_c( session, run_options, inputs, input_values, ninputs, outputs, & 500 | output_values, noutputs, target_opers, ntargets, run_metadata, stat) & 501 | bind(c, name="TF_SessionRun") 502 | use iso_c_binding 503 | type(c_ptr), value :: session 504 | type(c_ptr), value :: run_options 505 | type(c_ptr), value :: inputs 506 | type(c_ptr), value :: input_values 507 | integer(kind=c_int), value :: ninputs 508 | type(c_ptr), value :: outputs 509 | type(c_ptr), value :: output_values 510 | integer(kind=c_int), value :: noutputs 511 | type(c_ptr), value :: target_opers 512 | integer(kind=c_int), value :: ntargets 513 | type(c_ptr), value :: run_metadata 514 | type(c_ptr), value :: stat 515 | end subroutine TF_SessionRun_c 516 | end interface 517 | 518 | if (present(run_options_opt)) then 519 | run_options = run_options_opt 520 | else 521 | run_options%p = c_null_ptr 522 | endif 523 | if (present(run_metadata_opt)) then 524 | run_metadata = run_metadata_opt 525 | else 526 | run_metadata%p = c_null_ptr 527 | endif 528 | 529 | do i = 1, ninputs 530 | input_value_ptrs(i) = input_values(i)%p 531 | input_act(i)%oper_ptr = inputs(i)%oper%p 532 | input_act(i)%index = inputs(i)%index 533 | end do 534 | input_act_ptr = c_loc(input_act) 535 | 536 | do i = 1, noutputs 537 | output_value_ptrs(i) = output_values(i)%p 538 | output_act(i)%oper_ptr = outputs(i)%oper%p 539 | output_act(i)%index = outputs(i)%index 540 | end do 541 | output_act_ptr = c_loc(output_act) 542 | 543 | do i = 1, ntargets 544 | target_oper_ptrs(i) = target_opers(i)%p 545 | end do 546 | 547 | 548 | call TF_SessionRun_c( session%p, run_options%p, input_act_ptr, c_loc(input_value_ptrs), ninputs, & 549 | output_act_ptr, c_loc(output_value_ptrs), noutputs, c_loc(target_oper_ptrs), ntargets, run_metadata%p, & 550 | stat%p ) 551 | 552 | ! is there a potential memory leak here? Only inasmuch as there's already one in the API. 553 | ! On entry, TF_SessionRun_c will set all output_value Tensors to nullptr, without calling 554 | ! DeleteTensor or anything on them. So only call this function with unassigned Tensors 555 | ! in output_values 556 | do i = 1, noutputs 557 | output_values(i)%p = output_value_ptrs(i) 558 | end do 559 | end subroutine TF_SessionRun 560 | 561 | subroutine TF_DeleteTensor( tensor ) 562 | use TF_Types 563 | type(TF_Tensor) :: tensor 564 | interface 565 | subroutine TF_DeleteTensor_c( tensor ) bind(c, name="TF_DeleteTensor") 566 | use iso_c_binding 567 | type(c_ptr), value :: tensor 568 | end subroutine TF_DeleteTensor_c 569 | end interface 570 | if (.not.c_associated(tensor%p)) then 571 | return 572 | endif 573 | call TF_DeleteTensor_c( tensor%p ) 574 | end subroutine TF_DeleteTensor 575 | 576 | function TF_TensorData( tensor ) 577 | use TF_Types 578 | type(c_ptr) :: TF_TensorData 579 | type(TF_Tensor) :: tensor 580 | 581 | type(c_ptr) :: temp_c_ptr 582 | interface 583 | function TF_TensorData_c( tensor ) bind(c, name="TF_TensorData") 584 | use iso_c_binding 585 | type(c_ptr) :: TF_TensorData_c 586 | type(c_ptr), value :: tensor 587 | end function TF_TensorData_c 588 | end interface 589 | 590 | TF_TensorData = TF_TensorData_c( tensor%p ) 591 | end function TF_TensorData 592 | 593 | function TF_NumDims( tensor ) 594 | use TF_Types 595 | integer(kind=c_int) :: TF_NumDims 596 | type(TF_Tensor) :: tensor 597 | 598 | interface 599 | function TF_NumDims_c( tensor ) bind(c, name="TF_NumDims") 600 | use iso_c_binding 601 | integer(kind=c_int) :: TF_TensorData_c 602 | type(c_ptr), value :: tensor 603 | end function TF_NumDims_c 604 | end interface 605 | 606 | TF_NumDims = TF_NumDims_c( tensor%p ) 607 | end function TF_NumDims 608 | 609 | function TF_Dim( tensor, indx ) 610 | use TF_Types 611 | integer(kind=c_int64_t) :: TF_Dim 612 | type(TF_Tensor) :: tensor 613 | integer(kind=c_int) :: indx 614 | 615 | interface 616 | function TF_Dim_c( tensor, indx ) bind(c, name="TF_Dim") 617 | use iso_c_binding 618 | integer(kind=c_int64_t) :: TF_TensorData_c 619 | type(c_ptr), value :: tensor 620 | integer(kind=c_int) :: indx 621 | end function TF_Dim_c 622 | end interface 623 | 624 | TF_Dim = TF_Dim_c( tensor%p, indx ) 625 | end function TF_Dim 626 | 627 | end module TF_Interface 628 | --------------------------------------------------------------------------------