├── .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 | 
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 |
--------------------------------------------------------------------------------