Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overload elementary operations #139

Merged
merged 51 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
cc9c76c
Overload =,+,*; consistent variable names
jwallwork23 Jun 21, 2024
366ffda
Update existing examples
jwallwork23 Jun 21, 2024
852b154
Add autograd example
jwallwork23 Jun 21, 2024
f7bca7e
Apply Black
jwallwork23 Jun 21, 2024
5232193
Overload - and test
jwallwork23 Jun 21, 2024
a730a40
Overload / and test
jwallwork23 Jun 21, 2024
7abeba7
Overload ** and test
jwallwork23 Jun 21, 2024
c650342
Overload scalar premultiply and test
jwallwork23 Jun 21, 2024
c0c5507
Overload scalar postmultiply and test
jwallwork23 Jun 21, 2024
6aa3768
Tidy overloading test - FIXME
jwallwork23 Jun 21, 2024
7b9126e
Formatting
jwallwork23 Jun 21, 2024
e6c7675
Reformatting
jwallwork23 Jun 21, 2024
9ac1087
Merge branch 'main' into autograd
jwallwork23 Jun 27, 2024
528b4f9
Fix merge issue
jwallwork23 Jun 27, 2024
dfaba4d
Include example 3 for consistency
jwallwork23 Jun 27, 2024
202f77f
DO NOT MERGE (debugging)
jwallwork23 Jun 27, 2024
1eb6d52
Implement torch_to_blob on C++ side
jwallwork23 Jul 15, 2024
60ce4a6
Implement Fortran interface
jwallwork23 Jul 22, 2024
684a711
Use torch_tensor_to_array in example 1
jwallwork23 Jul 22, 2024
d35218e
Use correct data types; raise errors for unsupported cases
jwallwork23 Jul 23, 2024
b5f53f2
Revert changes to example 1
jwallwork23 Jul 23, 2024
cf9a006
Add beginnings of autograd demo
jwallwork23 Jul 23, 2024
b8b4840
Docs for example 5
jwallwork23 Jul 23, 2024
806730c
More detail on uint8 and float16 not being supported
jwallwork23 Jul 23, 2024
0f6ee05
Add notes on float types
jwallwork23 Jul 23, 2024
e2f6a9d
Handle allocation of pointer array
jwallwork23 Jul 24, 2024
5881a3b
Merge branch 'torch_tensor_to_array' into autograd_toarray
jwallwork23 Jul 29, 2024
cdd4433
Merge fixes
jwallwork23 Jul 29, 2024
f3bf975
Merge branch 'main' into autograd
jwallwork23 Oct 31, 2024
6223f36
Update autograd example
jwallwork23 Oct 31, 2024
e055d8b
Merge branch 'main' into autograd
jwallwork23 Nov 11, 2024
5364800
Use assert_allclose in new code
jwallwork23 Nov 11, 2024
80e5a71
Merge branch 'main' into autograd
jwallwork23 Dec 6, 2024
59ffdc4
Use bare import for autograd
jwallwork23 Dec 6, 2024
f504f5e
Lint
jwallwork23 Dec 6, 2024
8379001
Lint
jwallwork23 Dec 9, 2024
a590955
Apply clang-format
jwallwork23 Dec 9, 2024
c12d5c3
Apply clang-format to header
jwallwork23 Dec 9, 2024
cc884c6
Merge branch 'main' into autograd
jwallwork23 Dec 10, 2024
d9000e1
Post-merge fixes
jwallwork23 Dec 10, 2024
c87fd97
test: make windows CI more robust
TomMelt Dec 16, 2024
98917d3
chore: rename variable to torch_path
TomMelt Dec 16, 2024
f0ed978
Reformulate autograd example to test multiply and divide
jwallwork23 Dec 18, 2024
b804d54
Implement postdivide
jwallwork23 Dec 18, 2024
b239207
Merge branch 'test-simple-change' into autograd
jwallwork23 Dec 18, 2024
c0a3fe4
Point to Torch C++ API
jwallwork23 Dec 18, 2024
1e852b4
Update docs on autograd; add reference to looping example
jwallwork23 Dec 18, 2024
8dfa92c
Revert adding example 3 to build
jwallwork23 Dec 19, 2024
70f28a4
Merge branch 'main' into autograd
jwallwork23 Dec 19, 2024
4406fd8
Write as LibTorch in pages/autograd.md
jwallwork23 Dec 20, 2024
e82a9db
Use better links for Torch C++ docs
jwallwork23 Dec 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/test_suite_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ jobs:
shell: cmd
run: |
cd src
rem find torch location
for /f "tokens=2*" %%i in ('pip show torch ^| findstr /R "^Location"') do set torch_path=%%i
cmake ^
-Bbuild ^
-G "NMake Makefiles" ^
-DCMAKE_Fortran_FLAGS="/fpscomp:logicals" ^
-DCMAKE_PREFIX_PATH="C:\hostedtoolcache\windows\Python\3.12.7\x64\Lib\site-packages" ^
-DCMAKE_PREFIX_PATH=%torch_path% ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_Fortran_COMPILER=ifx ^
-DCMAKE_C_COMPILER=icx ^
Expand All @@ -85,7 +87,7 @@ jobs:
- name: Integration tests
shell: cmd
run: |
set PATH=C:\hostedtoolcache\windows\Python\3.12.7\x64\Lib\site-packages;%PATH%
for /f "tokens=2*" %%i in ('pip show torch ^| findstr /R "^Location"') do set torch_path=%%i
set PATH=C:\Program Files (x86)\FTorch\bin;%PATH%
set PATH=C:\hostedtoolcache\windows\Python\3.12.7\x64\Lib\site-packages\torch\lib;%PATH%
set PATH=%torch_path%\torch\lib;%PATH%
run_integration_tests.bat
79 changes: 57 additions & 22 deletions examples/6_Autograd/autograd.f90
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ program example
use, intrinsic :: iso_fortran_env, only : sp => real32

! Import our library for interfacing with PyTorch's Autograd module
use ftorch, only : torch_tensor, torch_kCPU, &
torch_tensor_from_array, torch_tensor_to_array, torch_tensor_delete
use ftorch, only: assignment(=), operator(+), operator(-), operator(*), &
operator(/), operator(**), torch_kCPU, torch_tensor, torch_tensor_delete, &
torch_tensor_from_array, torch_tensor_to_array

! Import our tools module for testing utils
use ftorch_test_utils, only : assert_allclose
Expand All @@ -16,8 +17,9 @@ program example
integer, parameter :: wp = sp

! Set up Fortran data structures
integer, parameter :: n=2, m=5
real(wp), dimension(n,m), target :: in_data
integer, parameter :: n=2, m=1
real(wp), dimension(n,m), target :: in_data1
real(wp), dimension(n,m), target :: in_data2
real(wp), dimension(:,:), pointer :: out_data
real(wp), dimension(n,m) :: expected
integer :: tensor_layout(2) = [1, 2]
Expand All @@ -27,45 +29,78 @@ program example
logical :: test_pass

! Set up Torch data structures
type(torch_tensor) :: tensor
type(torch_tensor) :: a, b, Q

! initialize in_data with some fake data
do j = 1, m
do i = 1, n
in_data(i,j) = ((i-1)*m + j) * 1.0_wp
end do
end do
! Initialise input arrays as in Python example
in_data1(:,1) = [2.0_wp, 3.0_wp]
in_data2(:,1) = [6.0_wp, 4.0_wp]

! Construct a Torch Tensor from a Fortran array
call torch_tensor_from_array(tensor, in_data, tensor_layout, torch_kCPU)
! TODO: Implement requires_grad=.true.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO won't be addressed in this PR.

call torch_tensor_from_array(a, in_data1, tensor_layout, torch_kCPU)
call torch_tensor_from_array(b, in_data2, tensor_layout, torch_kCPU)

! check tensor rank and shape match those of in_data
if (tensor%get_rank() /= 2) then
if ((a%get_rank() /= 2) .or. (b%get_rank() /= 2)) then
print *, "Error :: rank should be 2"
stop 1
end if
if (any(tensor%get_shape() /= [2, 5])) then
print *, "Error :: shape should be (2, 5)"
if (any(a%get_shape() /= [n, m]) .or. any(b%get_shape() /= [n, m])) then
write(6,"('Error :: shape should be (',i1,', ',i1,')')") n, m
stop 1
end if

! Check arithmetic operations work for torch_tensors
write (*,*) "a = ", in_data1(:,1)
write (*,*) "b = ", in_data2(:,1)
Q = 3 * (a**3 - b * b / 3)

! Extract a Fortran array from a Torch tensor
call torch_tensor_to_array(tensor, out_data, shape(in_data))
call torch_tensor_to_array(Q, out_data, shape(in_data1))
write (*,*) "Q = 3 * (a ** 3 - b * b / 2) =", out_data(:,1)

! Check output tensor matches expected value
expected(:,:) = in_data
expected(:,1) = [-12.0_wp, 65.0_wp]
test_pass = assert_allclose(out_data, expected, test_name="torch_tensor_to_array", rtol=1e-5)
if (.not. test_pass) then
call clean_up()
print *, "Error :: out_data does not match expected value"
stop 999
end if

! Check that the data match
! Check first input array is unchanged by the arithmetic operations
expected(:,1) = [2.0_wp, 3.0_wp]
test_pass = assert_allclose(in_data1, expected, test_name="torch_tensor_to_array", rtol=1e-5)
if (.not. test_pass) then
print *, "Error :: in_data does not match out_data"
call clean_up()
print *, "Error :: in_data1 was changed during arithmetic operations"
stop 999
end if

! Cleanup
nullify(out_data)
call torch_tensor_delete(tensor)
! Check second input array is unchanged by the arithmetic operations
expected(:,1) = [6.0_wp, 4.0_wp]
test_pass = assert_allclose(in_data2, expected, test_name="torch_tensor_to_array", rtol=1e-5)
if (.not. test_pass) then
call clean_up()
print *, "Error :: in_data2 was changed during arithmetic operations"
stop 999
end if

! Back-propagation
! TODO: Requires API extension
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither will this one. (See #158.)


! Cleanup
call clean_up()
write (*,*) "Autograd example ran successfully"

contains

! Subroutine for freeing memory and nullifying pointers used in the example
subroutine clean_up()
nullify(out_data)
call torch_tensor_delete(a)
call torch_tensor_delete(b)
call torch_tensor_delete(Q)
end subroutine clean_up

jatkinson1000 marked this conversation as resolved.
Show resolved Hide resolved
end program example
2 changes: 1 addition & 1 deletion examples/6_Autograd/autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
a = torch.tensor([2.0, 3.0], requires_grad=True)
b = torch.tensor([6.0, 4.0], requires_grad=True)

Q = 3 * a**3 - b**2
Q = 3 * (a**3 - b * b / 3)
print(Q)
expect = torch.tensor([-12.0, 65.0])
if not torch.allclose(Q, expect):
Expand Down
4 changes: 3 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
if(CMAKE_BUILD_TESTS)
add_subdirectory(1_SimpleNet)
add_subdirectory(2_ResNet18)
# add_subdirectory(3_MultiGPU)
if(ENABLE_CUDA)
add_subdirectory(3_MultiGPU)
endif()
TomMelt marked this conversation as resolved.
Show resolved Hide resolved
add_subdirectory(4_MultiIO)
# add_subdirectory(5_Looping)
add_subdirectory(6_Autograd)
Expand Down
42 changes: 42 additions & 0 deletions pages/autograd.md
TomMelt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
title: Online training

[TOC]

## Current state

FTorch has supported offline training of ML models for some time. We are
currently working on extending its functionality to support online training,
too. This will involve exposing the automatic differentiation and
back-propagation functionality in PyTorch/libtorch.
jwallwork23 marked this conversation as resolved.
Show resolved Hide resolved

In the following, we document a workplan of the related functionality. Each step
below will be updated upon completion.

### Operator overloading

Mathematical operators involving Tensors are overloaded, so that we can compute
expressions involving outputs from one or more ML models.

Whilst it's possible to import such functionality with a bare
```fortran
use ftorch
```
statement, the best practice is to import specifically the operators that you
wish to use. Note that the assignment operator `=` has a slightly different
notation:
```
use ftorch, only: assignment(=), operator(+), operator(-), operator(*), &
operator(/), operator(**)
```

For a concrete example of how to compute mathematical expressions involving
Torch tensors, see the associated
[worked example](https://github.com/Cambridge-ICCS/FTorch/tree/main/examples/6_Autograd).

### The `requires_grad` property

*Not yet implemented.*

### The `backward` operator

*Not yet implemented.*
8 changes: 8 additions & 0 deletions pages/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ and many of our users wish to _"clone-and-go"_ rather than develop, we provide b
Development should only take place in `ftorch.fypp`, however._


### Torch C++ API

When extending or modifying functionality related to C++ header and/or source
files `src/ctorch.h` and `src/ctorch.cpp`, we refer to the Torch C++
[API documentation](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#exhale-class-classat-1-1-tensor)
jatkinson1000 marked this conversation as resolved.
Show resolved Hide resolved
page on the PyTorch website for details.


### git hook

In order to streamline the process of uploading we provide a pre-commit hook in
Expand Down
13 changes: 10 additions & 3 deletions pages/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,16 @@ data to multiple GPU devices.
considers a variant of the SimpleNet demo, which demonstrates how to account for
multiple input tensors and multiple output tensors.

#### 7) Autograd
#### 5) Looping

[This worked example](https://github.com/Cambridge-ICCS/FTorch/tree/main/examples/5_Autograd)
[This worked example](https://github.com/Cambridge-ICCS/FTorch/tree/main/examples/5_Looping)
demonstrates best practices for performing inference on the same network with
different input multiple times in the same workflow.

jatkinson1000 marked this conversation as resolved.
Show resolved Hide resolved
#### 6) Autograd

[This worked example](https://github.com/Cambridge-ICCS/FTorch/tree/main/examples/6_Autograd)
is currently under development. Eventually, it will demonstrate how to perform
automatic differentiation in FTorch by leveraging PyTorch's Autograd module.
Currently, it just demonstrates how to use `torch_tensor_to_array`.
Currently, it just demonstrates how to use `torch_tensor_to_array` and compute
mathematical expressions involving Torch tensors.
7 changes: 6 additions & 1 deletion run_integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
set -eu

CTEST_ARGS=$@
EXAMPLES="1_SimpleNet 2_ResNet18 4_MultiIO 6_Autograd"
EXAMPLES="
1_SimpleNet
2_ResNet18
4_MultiIO
6_Autograd
"
BUILD_DIR=src/build

for EXAMPLE in ${EXAMPLES}; do
Expand Down
10 changes: 6 additions & 4 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ if(CMAKE_BUILD_TESTS)
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/test/examples)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/2_ResNet18
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/test/examples)
# file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/3_MultiGPU DESTINATION
# ${CMAKE_CURRENT_SOURCE_DIR}/test/examples )
if(ENABLE_CUDA)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/3_MultiGPU
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/test/examples)
endif()
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/4_MultiIO
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/test/examples)
# file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/5_Looping DESTINATION
# ${CMAKE_CURRENT_SOURCE_DIR}/test/examples )
# file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/5_Looping
# DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/test/examples)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../examples/6_Autograd
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/test/examples)
add_subdirectory(test/examples)
Expand Down
90 changes: 90 additions & 0 deletions src/ctorch.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* See
* https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#exhale-class-classat-1-1-tensor
* for more details on the Torch Tensor C++ API.
*/
jatkinson1000 marked this conversation as resolved.
Show resolved Hide resolved
#include <torch/script.h>
#include <torch/torch.h>

Expand Down Expand Up @@ -233,6 +238,91 @@ void torch_tensor_delete(torch_tensor_t tensor) {
delete t;
}

torch_tensor_t torch_tensor_assign(const torch_tensor_t input) {
auto in = reinterpret_cast<torch::Tensor *const>(input);
torch::AutoGradMode enable_grad(in->requires_grad());
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = in->detach().clone();
return output;
}

torch_tensor_t torch_tensor_add(const torch_tensor_t tensor1,
const torch_tensor_t tensor2) {
auto t1 = reinterpret_cast<torch::Tensor *const>(tensor1);
auto t2 = reinterpret_cast<torch::Tensor *const>(tensor2);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = *t1 + *t2;
return output;
}

torch_tensor_t torch_tensor_subtract(const torch_tensor_t tensor1,
const torch_tensor_t tensor2) {
auto t1 = reinterpret_cast<torch::Tensor *const>(tensor1);
auto t2 = reinterpret_cast<torch::Tensor *const>(tensor2);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = *t1 - *t2;
return output;
}

torch_tensor_t torch_tensor_multiply(const torch_tensor_t tensor1,
const torch_tensor_t tensor2) {
auto t1 = reinterpret_cast<torch::Tensor *const>(tensor1);
auto t2 = reinterpret_cast<torch::Tensor *const>(tensor2);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = *t1 * *t2;
return output;
}

torch_tensor_t torch_tensor_premultiply(const torch_data_t scalar,
const torch_tensor_t tensor) {
auto t = reinterpret_cast<torch::Tensor *const>(tensor);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = scalar * *t;
return output;
}

torch_tensor_t torch_tensor_postmultiply(const torch_tensor_t tensor,
const torch_data_t scalar) {
auto t = reinterpret_cast<torch::Tensor *const>(tensor);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = *t * scalar;
return output;
}

torch_tensor_t torch_tensor_divide(const torch_tensor_t tensor1,
const torch_tensor_t tensor2) {
auto t1 = reinterpret_cast<torch::Tensor *const>(tensor1);
auto t2 = reinterpret_cast<torch::Tensor *const>(tensor2);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = *t1 / *t2;
return output;
}

torch_tensor_t torch_tensor_postdivide(const torch_tensor_t tensor,
const torch_data_t scalar) {
auto t = reinterpret_cast<torch::Tensor *const>(tensor);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = *t / scalar;
return output;
}

torch_tensor_t torch_tensor_power(const torch_tensor_t tensor,
const torch_data_t exponent) {
auto t = reinterpret_cast<torch::Tensor *const>(tensor);
torch::Tensor *output = nullptr;
output = new torch::Tensor;
*output = pow(*t, exponent);
return output;
}

torch_jit_script_module_t torch_jit_load(const char *filename,
const torch_device_t device_type = torch_kCPU,
const int device_index = -1,
Expand Down
Loading
Loading