diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 93b65adfe..e5c9f3d4c 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -60,7 +60,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install ford + pip install --group docs - name: Build docs with FORD run: ford FTorch.md @@ -109,7 +109,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install ford + pip install --group docs - name: Build docs with FORD run: ford FTorch.md diff --git a/.github/workflows/preprocessing.yml b/.github/workflows/preprocessing.yml index ced37c29b..8a15e46f0 100644 --- a/.github/workflows/preprocessing.yml +++ b/.github/workflows/preprocessing.yml @@ -45,7 +45,7 @@ jobs: python-version: '3.13' # Use 3.13 as no PyTorch wheels for 3.14 yet. - name: Install fypp, the Fortran pre-processor - run: pip install fypp + run: pip install --group preproc - name: Check ftorch_tensor.fypp matches ftorch_tensor.f90 run: | diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index d5f52065f..a23a56108 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -20,7 +20,7 @@ on: - '**.py' - '**.sh' - '**CMakeLists.txt' - - 'requirements-dev.txt' + - 'pyproject.toml' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -65,8 +65,7 @@ jobs: python -m pip install --upgrade pip python -m venv ../ftorch_venv . ../ftorch_venv/bin/activate - pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu - pip install -r requirements-dev.txt + pip install . --extra-index-url https://download.pytorch.org/whl/cpu --group lint # Run CMake build to get compile commands for clang - name: FTorch CMake @@ -76,7 +75,7 @@ jobs: VN=$(python -c "import sys; print('.'.join(sys.version.split('.')[:2]))") export Torch_DIR=${VIRTUAL_ENV}/lib/python${VN}/site-packages export BUILD_DIR=$(pwd)/build - mkdir ${BUILD_DIR} + mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${BUILD_DIR} -DCMAKE_Fortran_FLAGS="-std=f2008" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON diff --git a/.github/workflows/test_suite_macos_cpu_clang.yml b/.github/workflows/test_suite_macos_cpu_clang.yml index ae551c205..edd1324e3 100644 --- a/.github/workflows/test_suite_macos_cpu_clang.yml +++ b/.github/workflows/test_suite_macos_cpu_clang.yml @@ -81,7 +81,7 @@ jobs: python -m pip install --upgrade pip python -m venv ftorch . ftorch/bin/activate - pip install torch torchvision + pip install . --extra-index-url https://download.pytorch.org/whl/cpu --group test - name: Install gfortran and OpenMPI # Install from source to avoid compiler mismatch between gfortran versions diff --git a/.github/workflows/test_suite_ubuntu_cpu_gnu.yml b/.github/workflows/test_suite_ubuntu_cpu_gnu.yml index 70eaa3729..896ce9797 100644 --- a/.github/workflows/test_suite_ubuntu_cpu_gnu.yml +++ b/.github/workflows/test_suite_ubuntu_cpu_gnu.yml @@ -117,12 +117,12 @@ jobs: with: python-version: '3.13' - - name: Install PyTorch + - name: Install ftorch_utils run: | python -m pip install --upgrade pip python -m venv ftorch . ftorch/bin/activate - pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu + pip install . --extra-index-url https://download.pytorch.org/whl/cpu --group test - name: Install OpenMPI run: | @@ -148,12 +148,12 @@ jobs: VN=$(python -c "import sys; print('.'.join(sys.version.split('.')[:2]))") export Torch_DIR=${VIRTUAL_ENV}/lib/python${VN}/site-packages export BUILD_DIR=$(pwd)/build + mkdir -p ${BUILD_DIR} # Install to /tmp/ to ensure independent from the CMake build in standalone check. export INSTALL_DIR=/tmp/ftorch-install mkdir -p ${INSTALL_DIR} # NOTE: The pFUnit version (pinned during installation above) is used in the install path. export PFUNIT_DIR=$(pwd)/pFUnit/build/installed/PFUNIT-4.12 - mkdir ${BUILD_DIR} cd ${BUILD_DIR} cmake .. \ diff --git a/.github/workflows/test_suite_ubuntu_cpu_intel.yml b/.github/workflows/test_suite_ubuntu_cpu_intel.yml index 24a779bcc..e4b6d33a3 100644 --- a/.github/workflows/test_suite_ubuntu_cpu_intel.yml +++ b/.github/workflows/test_suite_ubuntu_cpu_intel.yml @@ -61,12 +61,12 @@ jobs: with: python-version: '3.13' # Use 3.13 as no PyTorch wheels for 3.14 yet. - - name: Install PyTorch + - name: Install ftorch_utils run: | python -m pip install --upgrade pip python -m venv ftorch . ftorch/bin/activate - pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu + pip install . --extra-index-url https://download.pytorch.org/whl/cpu --group test - name: Install Intel tools uses: rscohn2/setup-oneapi@a5ae4e7697b9cd5dc8a616f94c1e56b87140999d @@ -107,7 +107,7 @@ jobs: mkdir -p ${INSTALL_DIR} # NOTE: The pFUnit version (pinned during installation above) is used in the install path. export PFUNIT_DIR=$(pwd)/pFUnit/build/installed/PFUNIT-4.12 - mkdir ${BUILD_DIR} + mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} cmake .. \ diff --git a/.github/workflows/test_suite_ubuntu_cuda_gnu.yml b/.github/workflows/test_suite_ubuntu_cuda_gnu.yml index c7ff3c81b..35d66efbc 100644 --- a/.github/workflows/test_suite_ubuntu_cuda_gnu.yml +++ b/.github/workflows/test_suite_ubuntu_cuda_gnu.yml @@ -67,14 +67,14 @@ jobs: with: python-version: '3.13' # Use 3.13 as no PyTorch wheels for 3.14 yet. - - name: Install PyTorch + - name: Install ftorch_utils run: | python -m pip install --upgrade pip python -m venv ftorch . ftorch/bin/activate # CUDA v12.9 chosen as latest supported by PyTorch # Check this is <= what GPU-runner has installed (backwards compatible) - pip install torch torchvision --index-url https://download.pytorch.org/whl/cu129 + pip install . --extra-index-url https://download.pytorch.org/whl/cu129 --group test - name: Install cmake and NVIDIA dev toolkit run: | @@ -110,7 +110,7 @@ jobs: mkdir -p ${INSTALL_DIR} # NOTE: The pFUnit version (pinned during installation above) is used in the install path. export PFUNIT_DIR=$(pwd)/pFUnit/build/installed/PFUNIT-4.12 - mkdir ${BUILD_DIR} + mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} cmake .. \ -DPython_EXECUTABLE="$(which python)" \ diff --git a/.github/workflows/test_suite_windows_cpu_intel.yml b/.github/workflows/test_suite_windows_cpu_intel.yml index 54d160f40..9f0a91ab4 100644 --- a/.github/workflows/test_suite_windows_cpu_intel.yml +++ b/.github/workflows/test_suite_windows_cpu_intel.yml @@ -59,18 +59,20 @@ jobs: with: python-version: '3.13' # Use 3.13 as no PyTorch wheels for 3.14 yet. - - name: Install PyTorch + - name: Install ftorch_utils shell: cmd run: | pip install --upgrade pip python -m venv ftorch call ftorch\Scripts\activate - pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu + pip install . --group test - name: Build FTorch shell: cmd run: | call ftorch\Scripts\activate + rem Find Python executable in venv + for /f %%i in ('python -c "import sys; print(sys.executable)"') do set PYTHON_EXECUTABLE=%%i rem Find torch location set TORCH_PATH= for /f "tokens=2*" %%i in ('pip show torch ^| findstr /R "^Location"') do set TORCH_PATH=%%i @@ -79,6 +81,7 @@ jobs: cmake ^ -Bbuild ^ -G "NMake Makefiles" ^ + -DPython_EXECUTABLE=%PYTHON_EXECUTABLE% ^ -DCMAKE_Fortran_FLAGS="/fpscomp:logicals" ^ -DCMAKE_CXX_FLAGS="/D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH" ^ -DCMAKE_PREFIX_PATH=%TORCH_PATH% ^ @@ -101,16 +104,17 @@ jobs: set PATH=%FTORCH_INSTALL_DIR%\bin;%PATH% set PATH=%TORCH_PATH%\torch\lib;%PATH% cd build - ctest --verbose --tests-regex example1 - ctest --verbose --tests-regex example2 - ctest --verbose --tests-regex example3 - ctest --verbose --tests-regex example4 - ctest --verbose --tests-regex example8 + ctest --verbose --tests-regex example_tensor + ctest --verbose --tests-regex example_simplenet + ctest --verbose --tests-regex example_resnet + ctest --verbose --tests-regex example_multiio + ctest --verbose --tests-regex example_autograd - name: Standalone SimpleNet example shell: cmd run: | call ftorch\Scripts\activate + for /f %%i in ('python -c "import sys; print(sys.executable)"') do set PYTHON_EXECUTABLE=%%i set TORCH_PATH= for /f "tokens=2*" %%i in ('pip show torch ^| findstr /R "^Location"') do set TORCH_PATH=%%i set FTORCH_INSTALL_DIR=%TEMP%\ftorch-install @@ -123,7 +127,7 @@ jobs: -G "NMake Makefiles" ^ -DCMAKE_Fortran_COMPILER=ifx ^ -DCMAKE_Fortran_FLAGS="/fpscomp:logicals" ^ - -DPython_EXECUTABLE=%python% ^ + -DPython_EXECUTABLE=%PYTHON_EXECUTABLE% ^ -DCMAKE_BUILD_TYPE=Release ^ -DCMAKE_PREFIX_PATH=%FTORCH_INSTALL_DIR%;%TORCH_PATH% ^ -DCMAKE_BUILD_TESTS=TRUE diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b1df8d6..096dc1168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,18 @@ For specific details see the [FTorch online documentation](https://cambridge-icc ### Added -- Provide worked example and documentation on differentiating through calls to - `torch_model_forward`. - [#486](https://github.com/Cambridge-ICCS/FTorch/pull/486) +- Introduced a central, installable `pt2ts` script + [#555](https://github.com/Cambridge-ICCS/FTorch/pull/555) ### Changed +- Renamed `utils` as `ftorch_utils` and made it a proper Python module + [#555](https://github.com/Cambridge-ICCS/FTorch/pull/555) +- Changed process for saving TorchScript models + [#555](https://github.com/Cambridge-ICCS/FTorch/pull/555) +- Provide worked example and documentation on differentiating through calls to + `torch_model_forward`. + [#486](https://github.com/Cambridge-ICCS/FTorch/pull/486) - Optimizer functionality added to FTorch. A new module ftorch_optim added which contains implementations of SGD, Adam, and AdamW as well as core optimizer methods. @@ -28,6 +34,9 @@ For specific details see the [FTorch online documentation](https://cambridge-icc ### Removed +- Removed `pt2ts.py` scripts from `utils` and all examples + [#555](https://github.com/Cambridge-ICCS/FTorch/pull/555) + ### Fixed ### Patch Releases diff --git a/README.md b/README.md index c41a71dfb..97c281a52 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ It is likely that you will need to provide at least `CMAKE_PREFIX_PATH`. If Torch has been [installed as LibTorch](https://pytorch.org/cppdocs/installing.html) then this should be the absolute path to the unzipped LibTorch distribution. If Torch has been installed as PyTorch in a Python [venv (virtual environment)](https://docs.python.org/3/library/venv.html), - e.g. with `pip install torch`, then this should be `lib/python<3.xx>/site-packages/torch/`. + e.g. with `pip`, then this should be `lib/python<3.xx>/site-packages/torch/`. ## Usage diff --git a/codespace/build_FTorch.sh b/codespace/build_FTorch.sh index 6a7883ea1..b77892f55 100755 --- a/codespace/build_FTorch.sh +++ b/codespace/build_FTorch.sh @@ -4,12 +4,13 @@ # It should be run from the top of the repository i.e. `FTorch/` # --- -# Set up a virtual environment an install neccessary Python dependencies +# Set up a virtual environment and install necessary Python dependencies # We will specify the cpu-only version of PyTorch to match the codespace hardware python3 -m venv venv # shellcheck source=/dev/null source venv/bin/activate -pip install torch --index-url https://download.pytorch.org/whl/cpu +# shellcheck disable=SC2102 +pip install .[examples] --extra-index-url https://download.pytorch.org/whl/cpu # Extract the location of the installed packages in the venv # This is typically `FTorch/build/venv/lib/python3.xx/site-packages/` diff --git a/examples/01_Tensor/README.md b/examples/01_Tensor/README.md index 8af27267d..397b2e230 100644 --- a/examples/01_Tensor/README.md +++ b/examples/01_Tensor/README.md @@ -24,7 +24,9 @@ To run this example requires: ## Running To run this example, first install FTorch as described in the main -documentation. +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. To compile the Fortran code, using the included `CMakeLists.txt`, execute the following code: diff --git a/examples/02_SimpleNet/CMakeLists.txt b/examples/02_SimpleNet/CMakeLists.txt index 76a8b22ac..72c21a9a1 100644 --- a/examples/02_SimpleNet/CMakeLists.txt +++ b/examples/02_SimpleNet/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example add_executable(simplenet_infer_fortran simplenet_infer_fortran.f90) @@ -37,17 +35,27 @@ target_link_libraries(simplenet_infer_fortran PRIVATE FTorch::ftorch) if(CMAKE_BUILD_TESTS) include(CTest) - # 1. Check the PyTorch model runs and its outputs meet expectations - add_test(NAME example_simplenet_simplenet - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py) + # 1. Write the PyTorch model out and check it runs and its outputs meet + # expectations + add_test( + NAME example_simplenet_simplenet + COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( NAME example_simplenet_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --filepath - ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + COMMAND pt2ts SimpleNet + --model_definition_file + ${PROJECT_SOURCE_DIR}/simplenet.py + --input_model_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_model_cpu.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_input_tensor_cpu.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_cpu.pt + --test) # 3. Check the model can be loaded from file and run in Python and that its # outputs meet expectations @@ -62,7 +70,8 @@ if(CMAKE_BUILD_TESTS) add_test( NAME example_simplenet_simplenet_infer_fortran COMMAND - simplenet_infer_fortran ${PROJECT_BINARY_DIR}/saved_simplenet_model_cpu.pt + simplenet_infer_fortran + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_cpu.pt # Command line argument: model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( diff --git a/examples/02_SimpleNet/README.md b/examples/02_SimpleNet/README.md index 432220dca..693ee3a86 100644 --- a/examples/02_SimpleNet/README.md +++ b/examples/02_SimpleNet/README.md @@ -9,10 +9,12 @@ covered in later examples. ## Description -A Python file `simplenet.py` is provided that defines a very simple PyTorch 'net' that takes an input -vector of length 5 and applies a single `Linear` layer to multiply it by 2. +A Python file `simplenet.py` is provided that defines a very simple PyTorch +'net' that takes an input vector of length 5 and applies a single `Linear` layer +to multiply it by 2. Running this file as a script will write the model out in +PyTorch's `.pt` file format. -A modified version of the `pt2ts.py` tool saves this simple net to TorchScript. +The `pt2ts` tool is used to convert the model file to the TorchScript format. A series of files `simplenet_infer_` then bind from other languages to run the TorchScript model in inference mode. @@ -28,14 +30,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. -Then from this directory create a virtual environment and install the necessary Python -modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. You can check that everything is working by running `simplenet.py`: ``` @@ -45,13 +43,17 @@ This defines the net and runs it with an input tensor [0.0, 1.0, 2.0, 3.0, 4.0] ``` Model output: tensor([[0., 2., 4., 6., 8.]]) ``` +You should find that a PyTorch model file `pytorch_simplenet_model_cpu.pt` is +created. -To save the SimpleNet model to TorchScript run the modified version of the -`pt2ts.py` tool: +To convert the SimpleNet model to TorchScript run the `pt2ts` script: ``` -python3 pt2ts.py +pt2ts SimpleNet \ + --model_definition_file simplenet.py \ + --input_model_file pytorch_simplenet_model_cpu.pt \ + --output_model_file torchscript_simplenet_model_cpu.pt ``` -which will generate `saved_simplenet_model_cpu.pt` - the TorchScript instance of the net. +This should produce `torchscript_simplenet_model_cpu.pt`. You can check that everything is working by running the `simplenet_infer_python.py` script: ``` @@ -93,7 +95,7 @@ and should match the compiler that was used to locally build FTorch.) To run the compiled code calling the saved SimpleNet TorchScript from Fortran run the executable with an argument of the saved model file: ``` -./simplenet_infer_fortran ../saved_simplenet_model_cpu.pt +./simplenet_infer_fortran ../torchscript_simplenet_model_cpu.pt ``` This runs the model with the array `[0.0, 1.0, 2.0, 3.0, 4.0]` should produce @@ -120,7 +122,7 @@ However, to do this you will need to modify `Makefile` to link to and include yo installation of FTorch as described in the main documentation. Also check that the compiler is the same as the one you built the Library with. ``` make -./simplenet_infer_fortran saved_simplenet_model_cpu.pt +./simplenet_infer_fortran torchscript_simplenet_model_cpu.pt ``` You will also likely need to add the location of the dynamic library files diff --git a/examples/02_SimpleNet/requirements.txt b/examples/02_SimpleNet/requirements.txt deleted file mode 100644 index af3149eb4..000000000 --- a/examples/02_SimpleNet/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -torch -numpy diff --git a/examples/02_SimpleNet/simplenet.py b/examples/02_SimpleNet/simplenet.py index e77459452..0e5f589a3 100644 --- a/examples/02_SimpleNet/simplenet.py +++ b/examples/02_SimpleNet/simplenet.py @@ -42,15 +42,39 @@ def forward(self, batch: torch.Tensor) -> torch.Tensor: if __name__ == "__main__": - model = SimpleNet() + import argparse + + # Parse user input + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--device_type", + help="Device type to run the inference on", + type=str, + choices=["cpu", "cuda", "hip", "xpu", "mps"], + default="cpu", + ) + parsed_args = parser.parse_args() + device_type = parsed_args.device_type + + # Construct an instance of the SimpleNet model on the specified device + model = SimpleNet().to(device_type) model.eval() - input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]) - with torch.inference_mode(): - output_tensor = model(input_tensor) + # Save the model in PyTorch format + torch.save(model.state_dict(), f"pytorch_simplenet_model_{device_type}.pt") + # Create an arbitrary input tensor and save it in PyTorch format + input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]).to(device_type) + torch.save(input_tensor, f"pytorch_simplenet_input_tensor_{device_type}.pt") + + # Propagate the input tensor through the model + with torch.inference_mode(): + output_tensor = model(input_tensor).to("cpu") print(f"Model output: {output_tensor}") + # Perform a basic check of the model output if not torch.allclose(output_tensor, 2 * input_tensor): result_error = ( f"result:\n{output_tensor}\ndoes not match expected value:\n" diff --git a/examples/02_SimpleNet/simplenet_infer_python.py b/examples/02_SimpleNet/simplenet_infer_python.py index a4ec58a2e..beca0590e 100644 --- a/examples/02_SimpleNet/simplenet_infer_python.py +++ b/examples/02_SimpleNet/simplenet_infer_python.py @@ -68,7 +68,7 @@ def deploy(saved_model: str, device: str, batch_size: int = 1) -> torch.Tensor: ) parsed_args = parser.parse_args() filepath = parsed_args.filepath - saved_model_file = os.path.join(filepath, "saved_simplenet_model_cpu.pt") + saved_model_file = os.path.join(filepath, "torchscript_simplenet_model_cpu.pt") device_to_run = "cpu" diff --git a/examples/03_ResNet/CMakeLists.txt b/examples/03_ResNet/CMakeLists.txt index 1b746422f..368c15b98 100644 --- a/examples/03_ResNet/CMakeLists.txt +++ b/examples/03_ResNet/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example add_executable(resnet_infer_fortran resnet_infer_fortran.f90) @@ -44,11 +42,11 @@ if(CMAKE_BUILD_TESTS) WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) # 2. Check the model is saved to file in the expected location with the - # pt2ts.py script + # write_torchscript.py script add_test( - NAME example_resnet_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --filepath - ${PROJECT_BINARY_DIR} + NAME example_resnet_write_torchscript + COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/write_torchscript.py + --filepath ${PROJECT_BINARY_DIR} WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) # 3. Check the model can be loaded from file and run in Fortran and that its @@ -56,7 +54,8 @@ if(CMAKE_BUILD_TESTS) add_test( NAME example_resnet_resnet_infer_fortran COMMAND - resnet_infer_fortran ${PROJECT_BINARY_DIR}/saved_resnet18_model_cpu.pt + resnet_infer_fortran + ${PROJECT_BINARY_DIR}/torchscript_resnet18_model_cpu.pt ${PROJECT_SOURCE_DIR}/data # Command line arguments: model file and data directory filepath WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) diff --git a/examples/03_ResNet/README.md b/examples/03_ResNet/README.md index 12b38928e..a69a9ae56 100644 --- a/examples/03_ResNet/README.md +++ b/examples/03_ResNet/README.md @@ -26,14 +26,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. -Then from this directory create a virtual environment an install the neccessary Python -modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. You can check that everything is working by running `resnet18.py`: @@ -80,7 +76,7 @@ and should match the compiler that was used to locally build FTorch.) To run the compiled code calling the saved ResNet-18 TorchScript from Fortran run the executable with an argument of the saved model file: ``` -./resnet_infer_fortran ../saved_resnet18_model_cpu.pt +./resnet_infer_fortran ../torchscript_resnet18_model_cpu.pt ``` This should produce the same top result: @@ -99,7 +95,7 @@ You will also likely need to add the location of the `.so` files to your `LD_LIB ``` make export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib -./resnet_infer_fortran saved_resnet18_model_cpu.pt +./resnet_infer_fortran torchscript_resnet18_model_cpu.pt ``` ## Further options diff --git a/examples/03_ResNet/requirements.txt b/examples/03_ResNet/requirements.txt deleted file mode 100644 index cef06d788..000000000 --- a/examples/03_ResNet/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -numpy -torch -torchvision diff --git a/examples/03_ResNet/resnet_infer_python.py b/examples/03_ResNet/resnet_infer_python.py index b17688b4c..6e3c3206b 100644 --- a/examples/03_ResNet/resnet_infer_python.py +++ b/examples/03_ResNet/resnet_infer_python.py @@ -90,7 +90,7 @@ def check_results(output: torch.Tensor) -> None: ) parsed_args = parser.parse_args() filepath = parsed_args.filepath - saved_model_file = os.path.join(filepath, "saved_resnet18_model_cpu.pt") + saved_model_file = os.path.join(filepath, "torchscript_resnet18_model_cpu.pt") device_to_run = "cpu" diff --git a/examples/06_Looping/pt2ts.py b/examples/03_ResNet/write_torchscript.py similarity index 74% rename from examples/06_Looping/pt2ts.py rename to examples/03_ResNet/write_torchscript.py index 9b566f6fd..32af48136 100644 --- a/examples/06_Looping/pt2ts.py +++ b/examples/03_ResNet/write_torchscript.py @@ -1,4 +1,10 @@ """Load a PyTorch model and convert it to TorchScript.""" +# NOTE: This script uses the old pt2ts approach of providing a template for users to +# modify for their use case. We are currently migrating to a more general approach +# using a centralised script. See +# https://github.com/Cambridge-ICCS/FTorch/blob/main/ftorch_utils/README.md +# for details. This example will be updated soon. +# # Throughout this script there are various `FTORCH-TODO` comments indicating where # the user needs to modify as appropriate for their model @@ -8,65 +14,14 @@ # FTORCH-TODO # Add a module import with your model here: # This example assumes the model architecture is in an adjacent module `my_ml_model.py` -import simplenet +import resnet18 import torch - -def script_to_torchscript( - model: torch.nn.Module, filename: Optional[str] = "scripted_model.pt" -) -> None: - """ - Save PyTorch model to TorchScript using scripting. - - Parameters - ---------- - model : torch.NN.Module - a PyTorch model - filename : str - name of file to save to - """ - scripted_model = torch.jit.script(model) - # print(scripted_model.code) - scripted_model.save(filename) - - -def trace_to_torchscript( - model: torch.nn.Module, - dummy_input: torch.Tensor, - filename: Optional[str] = "traced_model.pt", -) -> None: - """ - Save PyTorch model to TorchScript using tracing. - - Parameters - ---------- - model : torch.NN.Module - a PyTorch model - dummy_input : torch.Tensor - appropriate size Tensor to act as input to model - filename : str - name of file to save to - """ - traced_model = torch.jit.trace(model, dummy_input) - frozen_model = torch.jit.freeze(traced_model) - # print(frozen_model.graph) - # print(frozen_model.code) - frozen_model.save(filename) - - -def load_torchscript(filename: Optional[str] = "saved_model.pt") -> torch.nn.Module: - """ - Load a TorchScript from file. - - Parameters - ---------- - filename : str - name of file containing TorchScript model - """ - model = torch.jit.load(filename) - - return model - +from ftorch_utils.torchscript import ( + load_torchscript, + script_to_torchscript, + trace_to_torchscript, +) if __name__ == "__main__": import argparse @@ -95,12 +50,14 @@ def load_torchscript(filename: Optional[str] = "saved_model.pt") -> torch.nn.Mod # Load model and prepare for saving # ===================================================== + precision = torch.float32 + # FTORCH-TODO # Load a pre-trained PyTorch model # Insert code here to load your model as `trained_model`. # This example assumes my_ml_model has a method `initialize` to load # architecture, weights, and place in inference mode - trained_model = simplenet.SimpleNet() + trained_model = resnet18.initialize(precision) # Switch off specific layers/parts of the model that behave # differently during training and inference. @@ -113,8 +70,9 @@ def load_torchscript(filename: Optional[str] = "saved_model.pt") -> torch.nn.Mod # FTORCH-TODO # Generate a dummy input Tensor `dummy_input` to the model of appropriate size. - # This example assumes two inputs of size (512x40) and (512x1) - trained_model_dummy_input = torch.ones((5), dtype=torch.float32) + # This example assumes one input of size (1x3x244x244) -- one RGB (3) image + # of resolution 244x244 in a batch size of 1. + trained_model_dummy_input = torch.ones(1, 3, 224, 224) # Transfer the model and inputs to GPU device, if appropriate if device_type != "cpu": @@ -139,9 +97,10 @@ def load_torchscript(filename: Optional[str] = "saved_model.pt") -> torch.nn.Mod # FTORCH-TODO # Set the name of the file you want to save the torchscript model to: - saved_ts_filename = f"saved_simplenet_{device_type}.pt" + saved_ts_filename = f"torchscript_resnet18_model_{device_type}.pt" # A filepath may also be provided. To do this, pass the filepath as an argument to - # this script when it is run from the command line, i.e. `./pt2ts.py path/to/model`. + # this script when it is run from the command line, i.e. + # `./write_torchscript.py --filepath path/to/model`. # FTORCH-TODO # Save the PyTorch model using either scripting (recommended if possible) or tracing @@ -157,6 +116,8 @@ def load_torchscript(filename: Optional[str] = "saved_model.pt") -> torch.nn.Mod # trained_model, trained_model_dummy_input, filename=saved_ts_filename # ) + print(f"Saved model to TorchScript in '{saved_ts_filename}'.") + # ===================================================== # Check model saved OK # ===================================================== diff --git a/examples/04_Batching/CMakeLists.txt b/examples/04_Batching/CMakeLists.txt index 9aeea6789..ca15e54f3 100644 --- a/examples/04_Batching/CMakeLists.txt +++ b/examples/04_Batching/CMakeLists.txt @@ -21,44 +21,57 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example -add_executable(batchingnet_infer_fortran batchingnet_infer_fortran.f90) -target_link_libraries(batchingnet_infer_fortran PRIVATE FTorch::ftorch) +add_executable(batching_demo_fortran batching_demo_fortran.f90) +target_link_libraries(batching_demo_fortran PRIVATE FTorch::ftorch) # Integration testing if(CMAKE_BUILD_TESTS) include(CTest) # 1. Check the PyTorch model runs and its outputs meet expectations - add_test(NAME example_batching_batching - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/batching_demo.py) + add_test( + NAME example_batching_batchingnet + COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/batchingnet.py + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( NAME example_batching_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --filepath - ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + COMMAND pt2ts BatchingNet + --model_definition_file + ${PROJECT_SOURCE_DIR}/batchingnet.py + --input_model_file + ${PROJECT_BINARY_DIR}/pytorch_batchingnet_model_cpu.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_batchingnet_input_tensor_cpu.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_batchingnet_model_cpu.pt + --test) + + # 3. Load the model and run in Python checking that outputs meet expectations + add_test( + NAME example_batching_demo_python + COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/batching_demo_python.py + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) - # 3. Load the model and run in Fortran checking that outputs meet expectations + # 4. Load the model and run in Fortran checking that outputs meet expectations add_test( - NAME example_batching_batching_infer_fortran + NAME example_batching_demo_fortran COMMAND - batchingnet_infer_fortran - ${PROJECT_BINARY_DIR}/saved_batchingnet_model_cpu.pt + batching_demo_fortran + ${PROJECT_BINARY_DIR}/torchscript_batchingnet_model_cpu.pt # Command line argument: model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( - example_batching_batching_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION + example_batching_demo_fortran PROPERTIES PASS_REGULAR_EXPRESSION "BatchingNet Fortran example ran successfully") endif() diff --git a/examples/04_Batching/README.md b/examples/04_Batching/README.md index 104cfcfe1..afbea12d2 100644 --- a/examples/04_Batching/README.md +++ b/examples/04_Batching/README.md @@ -8,7 +8,7 @@ show how to handle more complex cases, such as time series or multi-dimensional A Python file `batchingnet.py` is provided that defines a simple PyTorch 'BatchingNet' that takes an input vector of length 5 and applies a single `Linear` layer to multiply each input feature by a different value (0, 1, 2, 3, 4). The demo script -`batching_demo.py` shows how to use this model for unbatched, batched, and +`batching_demo_python.py` shows how to use this model for unbatched, batched, and higher-dimensional inference, illustrating the effect of batching and the location of the batch dimension. All outputs are as described in the "Why Batching?" section below. @@ -130,34 +130,44 @@ tensor([[[ 0., 1., 2., 3., 4.], ## Running -To run this example, first install FTorch as described in the main documentation. Then, from this directory, create a virtual environment and install the necessary Python modules: +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. + +A Python file `batchingnet.py` is provided that defines the simple PyTorch +'net'. Running this file as a script will write the model out in PyTorch's +`.pt` file format. +``` +python3 batchingnet.py +``` +You should find that a PyTorch model file `pytorch_batchingnet_model_cpu.pt` is +created. + +To convert the BatchingNet model to TorchScript for use in Fortran, run: ``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt +pt2ts BatchingNet \ + --model_definition_file batchingnet.py \ + --input_model_file pytorch_batchingnet_model_cpu.pt \ + --output_model_file torchscript_batchingnet_model_cpu.pt ``` +This should produce `torchscript_batchingnet_model_cpu.pt`. You can see how you would perform batching in PyTorch by running the Python batching demo: ``` -python3 batching_demo.py +python3 batching_demo_python.py ``` This will run the BatchingNet model with various inputs and print the expected outputs as described above. -To save the BatchingNet model to TorchScript for use in Fortran, run: -``` -python3 pt2ts.py -``` -This will generate `saved_batchingnet_model_cpu.pt` in the current directory. - At this point you no longer require Python, so can deactivate the virtual environment: ``` deactivate ``` To call the saved BatchingNet model from Fortran, repeating the different batching -approaches in the Python demo compile the `batchingnet_infer_fortran.f90` file. +approaches in the Python demo compile the `batching_demo_fortran.f90` file. This can be done using the included `CMakeLists.txt` as follows: ``` mkdir build @@ -169,7 +179,7 @@ cmake --build . To run the compiled code calling the saved BatchingNet TorchScript from Fortran, run the executable with an argument of the saved model file: ``` -./batchingnet_infer_fortran ../saved_batchingnet_model_cpu.pt +./batching_demo_fortran ../torchscript_batchingnet_model_cpu.pt ``` This runs the model with single, batched, and multidimensional batched input arrays and should produce the output: diff --git a/examples/04_Batching/batchingnet_infer_fortran.f90 b/examples/04_Batching/batching_demo_fortran.f90 similarity index 91% rename from examples/04_Batching/batchingnet_infer_fortran.f90 rename to examples/04_Batching/batching_demo_fortran.f90 index 5f8649105..ad8da9e21 100644 --- a/examples/04_Batching/batchingnet_infer_fortran.f90 +++ b/examples/04_Batching/batching_demo_fortran.f90 @@ -4,7 +4,7 @@ program batchingnet_infer use, intrinsic :: iso_fortran_env, only : sp => real32 use ftorch, only : torch_model, torch_tensor, torch_kCPU, torch_delete, & torch_tensor_from_array, torch_model_load, torch_model_forward - use ftorch_test_utils, only : assert_allclose + use ftorch_test_utils, only : allclose implicit none integer, parameter :: wp = sp @@ -57,8 +57,8 @@ program batchingnet_infer write (*,*) expected_single = [0.0_wp, 1.0_wp, 2.0_wp, 3.0_wp, 4.0_wp] - test_pass_single = assert_allclose(out_data_single, expected_single, & - test_name="BatchingNet single", rtol=1e-5) + test_pass_single = allclose(out_data_single, expected_single, & + test_name="BatchingNet single", rtol=1e-5) call torch_delete(in_tensors_single) call torch_delete(out_tensors_single) @@ -80,8 +80,8 @@ program batchingnet_infer expected_batch(1,:) = [0.0_wp, 1.0_wp, 2.0_wp, 3.0_wp, 4.0_wp] expected_batch(2,:) = [0.0_wp, 2.0_wp, 4.0_wp, 6.0_wp, 8.0_wp] - test_pass_batch = assert_allclose(out_data_batch, expected_batch, & - test_name="BatchingNet batch", rtol=1e-5) + test_pass_batch = allclose(out_data_batch, expected_batch, & + test_name="BatchingNet batch", rtol=1e-5) call torch_delete(in_tensors_batch) call torch_delete(out_tensors_batch) @@ -113,8 +113,8 @@ program batchingnet_infer expected_multi(2,1,:) = [0.0_wp, 10.0_wp, 20.0_wp, 30.0_wp, 40.0_wp] expected_multi(2,2,:) = [0.0_wp, 20.0_wp, 40.0_wp, 60.0_wp, 80.0_wp] expected_multi(2,3,:) = [0.0_wp, 30.0_wp, 60.0_wp, 90.0_wp, 120.0_wp] - test_pass_multi = assert_allclose(out_data_multi, expected_multi, & - test_name="BatchingNet multidim batch", rtol=1e-5) + test_pass_multi = allclose(out_data_multi, expected_multi, & + test_name="BatchingNet multidim batch", rtol=1e-5) call torch_delete(in_tensors_multi) call torch_delete(out_tensors_multi) diff --git a/examples/04_Batching/batching_demo.py b/examples/04_Batching/batching_demo_python.py similarity index 100% rename from examples/04_Batching/batching_demo.py rename to examples/04_Batching/batching_demo_python.py diff --git a/examples/04_Batching/batchingnet.py b/examples/04_Batching/batchingnet.py index 20fac94c3..ba0d7cf79 100644 --- a/examples/04_Batching/batchingnet.py +++ b/examples/04_Batching/batchingnet.py @@ -51,13 +51,39 @@ def forward(self, batch: torch.Tensor) -> torch.Tensor: if __name__ == "__main__": - model = BatchingNet() + import argparse + + # Parse user input + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--device_type", + help="Device type to run the inference on", + type=str, + choices=["cpu", "cuda", "hip", "xpu", "mps"], + default="cpu", + ) + parsed_args = parser.parse_args() + device_type = parsed_args.device_type + + # Construct an instance of the BatchingNet model on the specified device + model = BatchingNet().to(device_type) model.eval() - input_tensor = torch.ones(5) + + # Save the model in PyTorch format + torch.save(model.state_dict(), f"pytorch_batchingnet_model_{device_type}.pt") + + # Create an arbitrary input tensor and save it in PyTorch format + input_tensor = torch.ones(5).to(device_type) + torch.save(input_tensor, f"pytorch_batchingnet_input_tensor_{device_type}.pt") + + # Propagate the input tensor through the model with torch.inference_mode(): output_tensor = model(input_tensor) + print(f"Model output: {output_tensor}") - print(output_tensor) + # Perform a basic check of the model output if not torch.allclose(output_tensor, torch.tensor([0.0, 1.0, 2.0, 3.0, 4.0])): result_error = ( f"result:\n{output_tensor}\ndoes not match expected value:\n" diff --git a/examples/04_Batching/requirements.txt b/examples/04_Batching/requirements.txt deleted file mode 100644 index af3149eb4..000000000 --- a/examples/04_Batching/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -torch -numpy diff --git a/examples/05_MultiIO/CMakeLists.txt b/examples/05_MultiIO/CMakeLists.txt index fd09df3c9..d2ea2f224 100644 --- a/examples/05_MultiIO/CMakeLists.txt +++ b/examples/05_MultiIO/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example add_executable(multiionet_infer_fortran multiionet_infer_fortran.f90) @@ -37,17 +35,26 @@ target_link_libraries(multiionet_infer_fortran PRIVATE FTorch::ftorch) if(CMAKE_BUILD_TESTS) include(CTest) - # 1. Check the PyTorch model runs and its outputs meet expectations + # 1. Write the PyTorch model out and check it runs and its outputs meet + # expectations add_test(NAME example_multiio_multiionet - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/multiionet.py) + COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/multiionet.py + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( NAME example_multiio_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --filepath - ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + COMMAND pt2ts MultiIONet + --model_definition_file + ${PROJECT_SOURCE_DIR}/multiionet.py + --input_model_file + ${PROJECT_BINARY_DIR}/pytorch_multiio_model_cpu.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_multiio_input_tensor_cpu.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_multiio_model_cpu.pt + --test) # 3. Check the model can be loaded from file and run in Python and that its # outputs meet expectations @@ -63,7 +70,8 @@ if(CMAKE_BUILD_TESTS) add_test( NAME example_multiio_multiionet_infer_fortran COMMAND - multiionet_infer_fortran ${PROJECT_BINARY_DIR}/saved_multiio_model_cpu.pt + multiionet_infer_fortran + ${PROJECT_BINARY_DIR}/torchscript_multiio_model_cpu.pt # Command line argument: model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( diff --git a/examples/05_MultiIO/README.md b/examples/05_MultiIO/README.md index f92a7e5f0..061c88259 100644 --- a/examples/05_MultiIO/README.md +++ b/examples/05_MultiIO/README.md @@ -20,7 +20,7 @@ is then split into two components. That is, we have ``` where $a$ and $b$ are inputs and $c$ and $d$ are outputs. -The same `pt2ts.py` tool as in the previous examples is used to save the +The same `pt2ts` tool as in the previous examples is used to save the network to TorchScript. Again, a series of files `multiionet_infer_` then bind from other languages to run the TorchScript model in inference mode. @@ -34,14 +34,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. Then -from this directory create a virtual environment and install the necessary -Python modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. You can check everything is working by running `multiionet.py`: ``` @@ -52,14 +48,17 @@ This defines the network and runs it with input tensors [0.0, 1.0, 2.0, 3.0] and ``` (tensor([0., 2., 4., 6.]), tensor([ 0., -3., -6., -9.])) ``` +You should find that a PyTorch model file `pytorch_multiio_model_cpu.pt` is +created. -To save the MultiIONet model to TorchScript, run the modified version of the -`pt2ts.py` tool: +To convert the MultiIONet model to TorchScript, run the `pt2ts` script: ``` -python3 pt2ts.py +pt2ts MultiIONet \ + --model_definition_file multiionet.py \ + --input_model_file pytorch_multiio_model_cpu.pt \ + --output_model_file torchscript_multiio_model_cpu.pt ``` -which will generate `saved_multiio_model_cpu.pt` - the TorchScript instance of -the network. +This should produce `torchscript_multiio_model_cpu.pt`. You can check everything is working by running the `multiionet_infer_python.py` script. This reads the model in from the TorchScript file and runs it with the @@ -90,7 +89,7 @@ and should match the compiler that was used to locally build FTorch.) To run the compiled code calling the saved MultiIONet TorchScript from Fortran, run the executable with an argument of the saved model file: ``` -./multiionet_infer_fortran ../saved_multiio_model_cpu.pt +./multiionet_infer_fortran ../torchscript_multiio_model_cpu.pt ``` This runs the model with the same inputs as above and should produce the output: ``` diff --git a/examples/05_MultiIO/multiionet.py b/examples/05_MultiIO/multiionet.py index 614f27805..cdf8eccc7 100644 --- a/examples/05_MultiIO/multiionet.py +++ b/examples/05_MultiIO/multiionet.py @@ -46,19 +46,42 @@ def forward(self, batch1: torch.Tensor, batch2: torch.Tensor): if __name__ == "__main__": - model = MultiIONet() + import argparse + + # Parse user input + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--device_type", + help="Device type to run the inference on", + type=str, + choices=["cpu", "cuda", "hip", "xpu", "mps"], + default="cpu", + ) + parsed_args = parser.parse_args() + device_type = parsed_args.device_type + + # Construct an instance of the SimpleNet model on the specified device + model = MultiIONet().to(device_type) model.eval() + # Save the model in PyTorch format + torch.save(model.state_dict(), f"pytorch_multiio_model_{device_type}.pt") + + # Create an arbitrary input tensor and save it in PyTorch format input_tensors = ( - torch.Tensor([0.0, 1.0, 2.0, 3.0]), - torch.Tensor([-0.0, -1.0, -2.0, -3.0]), + torch.Tensor([0.0, 1.0, 2.0, 3.0]).to(device_type), + torch.Tensor([-0.0, -1.0, -2.0, -3.0]).to(device_type), ) + torch.save(input_tensors, f"pytorch_multiio_input_tensor_{device_type}.pt") + # Propagate the input tensor through the model with torch.inference_mode(): output_tensors = model(*input_tensors) + print(f"Model output: {output_tensors}") - print(output_tensors) - + # Perform a basic check of the model output for output_i, input_i, scale_factor in zip(output_tensors, input_tensors, (2, 3)): if not torch.allclose(output_i, scale_factor * input_i): result_error = ( diff --git a/examples/05_MultiIO/multiionet_infer_python.py b/examples/05_MultiIO/multiionet_infer_python.py index d8475689e..ed515bade 100644 --- a/examples/05_MultiIO/multiionet_infer_python.py +++ b/examples/05_MultiIO/multiionet_infer_python.py @@ -66,7 +66,7 @@ def deploy(saved_model: str, device: str, batch_size: int = 1) -> torch.Tensor: ) parsed_args = parser.parse_args() filepath = parsed_args.filepath - saved_model_file = os.path.join(filepath, "saved_multiio_model_cpu.pt") + saved_model_file = os.path.join(filepath, "torchscript_multiio_model_cpu.pt") device_to_run = "cpu" diff --git a/examples/05_MultiIO/requirements.txt b/examples/05_MultiIO/requirements.txt deleted file mode 100644 index 12c6d5d5e..000000000 --- a/examples/05_MultiIO/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -torch diff --git a/examples/06_Looping/CMakeLists.txt b/examples/06_Looping/CMakeLists.txt index fd31dcadd..3ff87d852 100644 --- a/examples/06_Looping/CMakeLists.txt +++ b/examples/06_Looping/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example - bad add_executable(simplenet_infer_fortran_bad diff --git a/examples/06_Looping/README.md b/examples/06_Looping/README.md index 373ad1fd8..97855d61c 100644 --- a/examples/06_Looping/README.md +++ b/examples/06_Looping/README.md @@ -24,7 +24,7 @@ We sum the results of each forward pass and print the final result. There are two folders `bad/` and `good/` that show two different approaches. -The same `pt2ts.py` tool as in the previous examples is used to save the +The same `pt2ts` tool as in the previous examples is used to save the network to TorchScript. A `simplenet_infer_fortran.f90` file contains the main program that runs over the loop. A `fortran_ml_mod.f90` file contains a module with the FTorch code to load the TorchScript model, run it in inference mode, and clean up. @@ -66,14 +66,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. Then -from this directory create a virtual environment and install the necessary -Python modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. You can check everything is working by running `simplenet.py`: ``` @@ -82,16 +78,19 @@ python3 simplenet.py This defines the network and runs it with input tensor [0.0, 1.0, 2.0, 3.0, 4.0] to produce the result: ``` -(tensor([0., 2., 4., 6., 8.])) +Model output: (tensor([0., 2., 4., 6., 8.])) ``` +You should find that a PyTorch model file `pytorch_simplenet_model_cpu.pt` is +created. -To save the SimpleNet model to TorchScript, run the modified version of the -`pt2ts.py` tool: +To convert the SimpleNet model to TorchScript run the `pt2ts` script: ``` -python3 pt2ts.py +pt2ts SimpleNet \ + --model_definition_file simplenet.py \ + --input_model_file pytorch_simplenet_model_cpu.pt \ + --output_model_file torchscript_simplenet_model_cpu.pt ``` -which will generate `saved_simplenet_cpu.pt` - the TorchScript instance of -the network and perform a quick sanity check that it can be read. +This should produce `torchscript_simplenet_model_cpu.pt`. At this point we no longer require Python, so can deactivate the virtual environment: diff --git a/examples/06_Looping/bad/fortran_ml_mod.f90 b/examples/06_Looping/bad/fortran_ml_mod.f90 index 28505a407..5b0152e78 100644 --- a/examples/06_Looping/bad/fortran_ml_mod.f90 +++ b/examples/06_Looping/bad/fortran_ml_mod.f90 @@ -37,7 +37,7 @@ subroutine ml_routine(in_data, out_data) call torch_tensor_from_array(output_tensors(1), out_data, tensor_layout, torch_kCPU) ! Load ML model - model_torchscript_file = '../saved_simplenet_cpu.pt' + model_torchscript_file = '../torchscript_simplenet_cpu.pt' call torch_model_load(torch_net, model_torchscript_file, torch_kCPU) ! Infer diff --git a/examples/06_Looping/good/fortran_ml_mod.f90 b/examples/06_Looping/good/fortran_ml_mod.f90 index d4b1a5675..0d650bdf2 100644 --- a/examples/06_Looping/good/fortran_ml_mod.f90 +++ b/examples/06_Looping/good/fortran_ml_mod.f90 @@ -29,7 +29,7 @@ module ml_mod subroutine ml_init() ! Load ML model - model_torchscript_file = '../saved_simplenet_cpu.pt' + model_torchscript_file = '../torchscript_simplenet_cpu.pt' call torch_model_load(torch_net, model_torchscript_file, torch_kCPU) end subroutine ml_init diff --git a/examples/06_Looping/requirements.txt b/examples/06_Looping/requirements.txt deleted file mode 100644 index 12c6d5d5e..000000000 --- a/examples/06_Looping/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -torch diff --git a/examples/06_Looping/simplenet.py b/examples/06_Looping/simplenet.py index 33475ea6a..0e5f589a3 100644 --- a/examples/06_Looping/simplenet.py +++ b/examples/06_Looping/simplenet.py @@ -42,14 +42,42 @@ def forward(self, batch: torch.Tensor) -> torch.Tensor: if __name__ == "__main__": - model = SimpleNet() + import argparse + + # Parse user input + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--device_type", + help="Device type to run the inference on", + type=str, + choices=["cpu", "cuda", "hip", "xpu", "mps"], + default="cpu", + ) + parsed_args = parser.parse_args() + device_type = parsed_args.device_type + + # Construct an instance of the SimpleNet model on the specified device + model = SimpleNet().to(device_type) model.eval() - input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]) + # Save the model in PyTorch format + torch.save(model.state_dict(), f"pytorch_simplenet_model_{device_type}.pt") - print(f"Input: {input}") + # Create an arbitrary input tensor and save it in PyTorch format + input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]).to(device_type) + torch.save(input_tensor, f"pytorch_simplenet_input_tensor_{device_type}.pt") + # Propagate the input tensor through the model with torch.inference_mode(): - output_tensor = model(input_tensor) + output_tensor = model(input_tensor).to("cpu") + print(f"Model output: {output_tensor}") - print(f"Output: {output_tensor}") + # Perform a basic check of the model output + if not torch.allclose(output_tensor, 2 * input_tensor): + result_error = ( + f"result:\n{output_tensor}\ndoes not match expected value:\n" + f"{2 * input_tensor}" + ) + raise ValueError(result_error) diff --git a/examples/07_MultiGPU/CMakeLists.txt b/examples/07_MultiGPU/CMakeLists.txt index 1187f13d1..ebca1f777 100644 --- a/examples/07_MultiGPU/CMakeLists.txt +++ b/examples/07_MultiGPU/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) include(CheckLanguage) if("${GPU_DEVICE}" STREQUAL "CUDA") @@ -56,19 +54,25 @@ if(CMAKE_BUILD_TESTS) include(CTest) if("${GPU_DEVICE}" STREQUAL "CUDA") - # 1a. Check the PyTorch model runs on a CUDA device and its outputs meet - # expectations + # 1a. Write the PyTorch model out with the CUDA device type and check it + # runs on a CUDA device and its outputs meet expectations add_test(NAME example_multigpu_simplenet COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py - --device_type cuda) + --device_type cuda + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2a. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2a. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( NAME example_multigpu_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --device_type - cuda --filepath ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + COMMAND pt2ts ${PROJECT_SOURCE_DIR}/simplenet.py + SimpleNet + ${PROJECT_BINARY_DIR}/pytorch_simplenet_model_cuda.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_input_tensor_cuda.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_cuda.pt + --test) # 3a. Check the model can be loaded from file and run on two CUDA devices in # Python and that its outputs meet expectations @@ -82,30 +86,36 @@ if(CMAKE_BUILD_TESTS) # 4a. Check the model can be loaded from file and run on two CUDA devices in # Fortran and that its outputs meet expectations add_test( - NAME example7_multigpu_infer_fortran + NAME example_multigpu_infer_fortran COMMAND multigpu_infer_fortran cuda - ${PROJECT_BINARY_DIR}/saved_multigpu_model_cuda.pt + ${PROJECT_BINARY_DIR}/torchscript_multigpu_model_cuda.pt # Command line arguments for device type and model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( - example7_multigpu_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION + example_multigpu_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION "MultiGPU example ran successfully") endif() if("${GPU_DEVICE}" STREQUAL "HIP") - # 1b. Check the PyTorch model runs on a HIP device and its outputs meet - # expectations + # 1b. Write the PyTorch model out with the HIP device type and check it + # runs on a HIP device and its outputs meet expectations add_test(NAME simplenet COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py - --device_type hip) + --device_type hip + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2b. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2b. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( - NAME pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --device_type - hip --filepath ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + NAME example_multigpu_pt2ts + COMMAND pt2ts ${PROJECT_SOURCE_DIR}/simplenet.py + SimpleNet + ${PROJECT_BINARY_DIR}/pytorch_simplenet_model_hip.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_input_tensor_hip.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_hip.pt + --test) # 3b. Check the model can be loaded from file and run on two HIP devices in # Python and that its outputs meet expectations @@ -121,7 +131,7 @@ if(CMAKE_BUILD_TESTS) add_test( NAME multigpu_infer_fortran COMMAND multigpu_infer_fortran hip - ${PROJECT_BINARY_DIR}/saved_multigpu_model_hip.pt + ${PROJECT_BINARY_DIR}/torchscript_multigpu_model_hip.pt # Command line arguments for device type and model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( @@ -130,75 +140,90 @@ if(CMAKE_BUILD_TESTS) endif() if("${GPU_DEVICE}" STREQUAL "XPU") - # 1b. Check the PyTorch model runs on an XPU device and its outputs meet - # expectations - add_test(NAME example7_simplenet + # 1c. Write the PyTorch model out with the XPU device type and check it + # runs on a XPU device and its outputs meet expectations + add_test(NAME example_multigpu_simplenet COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py - --device_type xpu) + --device_type xpu + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2b. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2c. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( - NAME example7_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --device_type - xpu --filepath ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - - # 3b. Check the model can be loaded from file and run on two XPU devices in + NAME example_multigpu_pt2ts + COMMAND pt2ts ${PROJECT_SOURCE_DIR}/simplenet.py + SimpleNet + ${PROJECT_BINARY_DIR}/pytorch_simplenet_model_xpu.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_input_tensor_xpu.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_xpu.pt + --test) + + # 3c. Check the model can be loaded from file and run on two XPU devices in # Python and that its outputs meet expectations add_test( - NAME example7_multigpu_infer_python + NAME example_multigpu_infer_python COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/multigpu_infer_python.py --device_type xpu --filepath ${PROJECT_BINARY_DIR} WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 4b. Check the model can be loaded from file and run on two XPU devices in + # 4c. Check the model can be loaded from file and run on two XPU devices in # Fortran and that its outputs meet expectations add_test( - NAME example7_multigpu_infer_fortran + NAME example_multigpu_infer_fortran COMMAND multigpu_infer_fortran xpu - ${PROJECT_BINARY_DIR}/saved_multigpu_model_xpu.pt + ${PROJECT_BINARY_DIR}/torchscript_multigpu_model_xpu.pt # Command line arguments for device type and model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( - example7_multigpu_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION + example_multigpu_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION "MultiGPU example ran successfully") endif() if("${GPU_DEVICE}" STREQUAL "MPS") - # 1c. Check the PyTorch model runs on an MPS device and its outputs meet - # expectations - add_test(NAME example7_simplenet + # 1d. Write the PyTorch model out with the MPS device type and check it + # runs on a MPS device and its outputs meet expectations + add_test(NAME example_multigpu_simplenet COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py - --device_type mps) - # 2c. Check the model is saved to file in the expected location with the - # pt2ts.py script - add_test( - NAME example7_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --device_type - mps --filepath ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + --device_type mps + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 3c. Check the model can be loaded from file and run on one MPS device in + # 2d. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks + add_test( + NAME example_multigpu_pt2ts + COMMAND pt2ts SimpleNet + --model_definition_file + ${PROJECT_SOURCE_DIR}/simplenet.py + --input_model_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_model_mps.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_input_tensor_mps.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_mps.pt + --test) + + # 3d. Check the model can be loaded from file and run on one MPS device in # Python and that its outputs meet expectations add_test( - NAME example7_multigpu_infer_python + NAME example_multigpu_infer_python COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/multigpu_infer_python.py --device_type mps --filepath ${PROJECT_BINARY_DIR} WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 4c. Check the model can be loaded from file and run on one MPS device in + # 4d. Check the model can be loaded from file and run on one MPS device in # Fortran and that its outputs meet expectations add_test( - NAME example7_multigpu_infer_fortran + NAME example_multigpu_infer_fortran COMMAND multigpu_infer_fortran mps - ${PROJECT_BINARY_DIR}/saved_multigpu_model_mps.pt + ${PROJECT_BINARY_DIR}/torchscript_multigpu_model_mps.pt # Command line arguments for device type and model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( - example7_multigpu_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION + example_multigpu_infer_fortran PROPERTIES PASS_REGULAR_EXPRESSION "MultiGPU example ran successfully") endif() endif() diff --git a/examples/07_MultiGPU/README.md b/examples/07_MultiGPU/README.md index 9dbd2f56c..9d965f8d3 100644 --- a/examples/07_MultiGPU/README.md +++ b/examples/07_MultiGPU/README.md @@ -10,7 +10,7 @@ The same Python file `simplenet.py` is used from the earlier example. Recall that it defines a very simple PyTorch network that takes an input of length 5 and applies a single `Linear` layer to multiply it by 2. -The same `pt2ts.py` tool is used to save the simple network to TorchScript. +The same `pt2ts` tool is used to save the simple network to TorchScript. A series of files `multigpu_infer_` then bind from other languages to run the TorchScript model in inference mode. @@ -26,37 +26,36 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. Then from -this directory create a virtual environment and install the necessary Python modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. You can check that everything is working by running `simplenet.py`: ``` python3 simplenet.py --device_type ``` where `` is `cuda`/`xpu`/`mps` as appropriate for your device. - -As before, this defines the network and runs it with an input tensor -[0.0, 1.0, 2.0, 3.0, 4.0]. The difference is that the code will make use of the -default GPU device (index 0) to produce the result: +You should find that a PyTorch model file +`pytorch_simplenet_model_.pt` is created. As before, this defines +the network and runs it with an input tensor [0.0, 1.0, 2.0, 3.0, 4.0]. The only +difference with the earlier example is that the model is built to be run on GPU +devices rather than on CPU. The code will make use of the default GPU device +(index 0) to produce the result: ``` SimpleNet forward pass on CUDA device 0 -tensor([[0, 2, 4, 6, 8]]) +Model output: tensor([[0, 2, 4, 6, 8]]) ``` for CUDA, and similarly for other device types. -To save the `SimpleNet` model to TorchScript run the modified version of the -`pt2ts.py` tool: +To convert the SimpleNet model to TorchScript run the `pt2ts` script: ``` -python3 pt2ts.py --device_type +pt2ts SimpleNet \ + --model_definition_file simplenet.py \ + --input_model_file pytorch_simplenet_model_.pt \ + --output_model_file torchscript_simplenet_model_.pt ``` -which will generate `saved_multigpu_model_.pt` - the TorchScript -instance of the network. The only difference with the earlier example is that -the model is built to be run on GPU devices rather than on CPU. +This should produce `torchscript_simplenet_model_.pt`. You can check that everything is working by running the `multigpu_infer_python.py` script. It's set up such that it loops over two GPU @@ -95,7 +94,7 @@ and should match the compiler that was used to locally build FTorch.) To run the compiled code calling the saved `SimpleNet` TorchScript from Fortran, run the executable with arguments of device type and the saved model file: ``` -./multigpu_infer_fortran ../saved_multigpu_model_.pt +./multigpu_infer_fortran ../torchscript_multigpu_model_.pt ``` This runs the model with the same inputs as described above and should produce (some diff --git a/examples/07_MultiGPU/multigpu_infer_python.py b/examples/07_MultiGPU/multigpu_infer_python.py index ca9f2c1c3..ad4698a60 100644 --- a/examples/07_MultiGPU/multigpu_infer_python.py +++ b/examples/07_MultiGPU/multigpu_infer_python.py @@ -32,7 +32,7 @@ def deploy(saved_model: str, device: str, batch_size: int = 1) -> torch.Tensor: # Inference return model.forward(input_tensor) - if device.startswith("cuda") or device.startswith("hip"): + if device.startswith(("cuda", "hip")): pass elif device.startswith("xpu"): # XPU devices need to be initialised before use @@ -82,7 +82,9 @@ def deploy(saved_model: str, device: str, batch_size: int = 1) -> torch.Tensor: parsed_args = parser.parse_args() filepath = parsed_args.filepath device_type = parsed_args.device_type - saved_model_file = os.path.join(filepath, f"saved_multigpu_model_{device_type}.pt") + saved_model_file = os.path.join( + filepath, f"torchscript_multigpu_model_{device_type}.pt" + ) batch_size_to_run = 1 diff --git a/examples/07_MultiGPU/requirements.txt b/examples/07_MultiGPU/requirements.txt deleted file mode 100644 index 12c6d5d5e..000000000 --- a/examples/07_MultiGPU/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -torch diff --git a/examples/07_MultiGPU/simplenet.py b/examples/07_MultiGPU/simplenet.py index c0cb563b0..d279a0527 100644 --- a/examples/07_MultiGPU/simplenet.py +++ b/examples/07_MultiGPU/simplenet.py @@ -44,6 +44,7 @@ def forward(self, batch: torch.Tensor) -> torch.Tensor: if __name__ == "__main__": import argparse + # Parse user input parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) @@ -51,26 +52,33 @@ def forward(self, batch: torch.Tensor) -> torch.Tensor: "--device_type", help="Device type to run the inference on", type=str, - choices=["cpu", "cuda", "xpu", "mps"], + choices=["cpu", "cuda", "hip", "xpu", "mps"], default="cuda", ) parsed_args = parser.parse_args() device_type = parsed_args.device_type - model = SimpleNet().to(torch.device(device_type)) + # Construct an instance of the SimpleNet model on the specified device + model = SimpleNet().to(device_type) model.eval() - input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]) - input_tensor_gpu = input_tensor.to(torch.device(device_type)) + # Save the model in PyTorch format + torch.save(model.state_dict(), f"pytorch_simplenet_model_{device_type}.pt") + # Create an arbitrary input tensor and save it in PyTorch format + input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]).to(device_type) + torch.save(input_tensor, f"pytorch_simplenet_input_tensor_{device_type}.pt") + + # Propagate the input tensor through the model print( f"SimpleNet forward pass on {device_type.capitalize()} device" f" {input_tensor_gpu.get_device()}" ) with torch.inference_mode(): - output_tensor = model(input_tensor_gpu).to("cpu") + output_tensor = model(input_tensor).to("cpu") + print(f"Model output: {output_tensor}") - print(output_tensor) + # Perform a basic check of the model output if not torch.allclose(output_tensor, 2 * input_tensor): result_error = ( f"result:\n{output_tensor}\ndoes not match expected value:\n" diff --git a/examples/08_MPI/CMakeLists.txt b/examples/08_MPI/CMakeLists.txt index 168adc512..9fa05936f 100644 --- a/examples/08_MPI/CMakeLists.txt +++ b/examples/08_MPI/CMakeLists.txt @@ -22,13 +22,11 @@ endif() find_package(MPI REQUIRED Fortran) message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example add_executable(mpi_infer_fortran mpi_infer_fortran.f90) @@ -39,20 +37,30 @@ target_link_libraries(mpi_infer_fortran PRIVATE MPI::MPI_Fortran) if(CMAKE_BUILD_TESTS) include(CTest) - # 1. Check the PyTorch model runs and its outputs meet expectations - add_test(NAME example_mpi_simplenet COMMAND ${Python_EXECUTABLE} - ${PROJECT_SOURCE_DIR}/simplenet.py) + # 1. Write the PyTorch model out and check it runs and its outputs meet + # expectations + add_test( + NAME example_mpi_simplenet + COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/simplenet.py + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - # 2. Check the model is saved to file in the expected location with the - # pt2ts.py script + # 2. Run the pt2ts script to convert the model file to TorchScript format + # and run basic checks add_test( NAME example_mpi_pt2ts - COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/pt2ts.py --filepath - ${PROJECT_BINARY_DIR} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) + COMMAND pt2ts SimpleNet + --model_definition_file + ${PROJECT_SOURCE_DIR}/simplenet.py + --input_model_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_model_cpu.pt + --input_tensor_file + ${PROJECT_BINARY_DIR}/pytorch_simplenet_input_tensor_cpu.pt + --output_model_file + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_cpu.pt + --test) # 3. Check the model can be loaded from file and run with MPI in Python and - # that its outputs meet expectations + # that its outputs meet expectations add_test( NAME example_mpi_mpi_infer_python COMMAND @@ -64,12 +72,12 @@ if(CMAKE_BUILD_TESTS) "MPI Python example ran successfully") # 4. Check the model can be loaded from file and run with MPI in Fortran and - # that its outputs meet expectations + # that its outputs meet expectations add_test( NAME example_mpi_mpi_infer_fortran COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 2 ./mpi_infer_fortran - ${PROJECT_BINARY_DIR}/saved_simplenet_model_cpu.pt + ${PROJECT_BINARY_DIR}/torchscript_simplenet_model_cpu.pt # Command line argument: model file WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) set_tests_properties( diff --git a/examples/08_MPI/README.md b/examples/08_MPI/README.md index b2a2e394a..f225fc831 100644 --- a/examples/08_MPI/README.md +++ b/examples/08_MPI/README.md @@ -10,7 +10,7 @@ The Python file `simplenet.py` is copied from the earlier example. Recall that it defines a very simple PyTorch network that takes an input of length 5 and applies a single `Linear` layer to multiply it by 2. -The same `pt2ts.py` tool is used to save the simple network to TorchScript. +The same `pt2ts` tool is used to save the simple network to TorchScript. A series of files `mpi_infer_` then bind from other languages to run the TorchScript model in inference mode. @@ -26,14 +26,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. Then -from this directory create a virtual environment and install the necessary -Python modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. You can check the network is set up correctly by running `simplenet.py`: ``` @@ -42,16 +38,19 @@ python3 simplenet.py As before, this defines the network and runs it with an input tensor [0.0, 1.0, 2.0, 3.0, 4.0] to produce the result: ``` -tensor([[0, 2, 4, 6, 8]]) +Model output: tensor([[0, 2, 4, 6, 8]]) ``` +You should find that a PyTorch model file `pytorch_simplenet_model_cpu.pt` is +created. -To save the `SimpleNet`` model to TorchScript run the modified version of the -`pt2ts.py` tool: +To convert the SimpleNet model to TorchScript run the `pt2ts` script: ``` -python3 pt2ts.py +pt2ts SimpleNet \ + --model_definition_file simplenet.py \ + --input_model_file pytorch_simplenet_model_cpu.pt \ + --output_model_file torchscript_simplenet_model_cpu.pt ``` -which will generate `saved_simplenet_model_cpu.pt` - the TorchScript instance -of the network. +This should produce `torchscript_simplenet_model_cpu.pt`. You can check that everything is working by running the `mpi_infer_python.py` script. It's set up with MPI such that a different GPU device is associated @@ -96,7 +95,7 @@ To run the compiled code calling the saved `SimpleNet` TorchScript from Fortran, run the executable with an argument of the saved model file. Again, specify the number of MPI processes according to the desired number of GPUs: ``` -mpiexec -np ./mpi_infer_fortran ../saved_simplenet_model_cpu.pt +mpiexec -np ./mpi_infer_fortran ../torchscript_simplenet_model_cpu.pt ``` This runs the model with the same inputs as described above and should produce (some diff --git a/examples/08_MPI/mpi_infer_python.py b/examples/08_MPI/mpi_infer_python.py index 14de356e6..fa163c941 100644 --- a/examples/08_MPI/mpi_infer_python.py +++ b/examples/08_MPI/mpi_infer_python.py @@ -66,7 +66,7 @@ def deploy(saved_model: str, device: str, batch_size: int = 1) -> torch.Tensor: ) parsed_args = parser.parse_args() filepath = parsed_args.filepath - saved_model_file = os.path.join(filepath, "saved_simplenet_model_cpu.pt") + saved_model_file = os.path.join(filepath, "torchscript_simplenet_model_cpu.pt") comm = MPI.COMM_WORLD rank = comm.rank diff --git a/examples/08_MPI/requirements.txt b/examples/08_MPI/requirements.txt deleted file mode 100644 index 604f78d3c..000000000 --- a/examples/08_MPI/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -torch -mpi4py diff --git a/examples/08_MPI/simplenet.py b/examples/08_MPI/simplenet.py index 041b3b5b8..0e5f589a3 100644 --- a/examples/08_MPI/simplenet.py +++ b/examples/08_MPI/simplenet.py @@ -42,14 +42,39 @@ def forward(self, batch: torch.Tensor) -> torch.Tensor: if __name__ == "__main__": - model = SimpleNet() + import argparse + + # Parse user input + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--device_type", + help="Device type to run the inference on", + type=str, + choices=["cpu", "cuda", "hip", "xpu", "mps"], + default="cpu", + ) + parsed_args = parser.parse_args() + device_type = parsed_args.device_type + + # Construct an instance of the SimpleNet model on the specified device + model = SimpleNet().to(device_type) model.eval() - input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]) + # Save the model in PyTorch format + torch.save(model.state_dict(), f"pytorch_simplenet_model_{device_type}.pt") + + # Create an arbitrary input tensor and save it in PyTorch format + input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]).to(device_type) + torch.save(input_tensor, f"pytorch_simplenet_input_tensor_{device_type}.pt") + + # Propagate the input tensor through the model with torch.inference_mode(): - output_tensor = model(input_tensor) + output_tensor = model(input_tensor).to("cpu") + print(f"Model output: {output_tensor}") - print(output_tensor) + # Perform a basic check of the model output if not torch.allclose(output_tensor, 2 * input_tensor): result_error = ( f"result:\n{output_tensor}\ndoes not match expected value:\n" diff --git a/examples/09_Autograd/CMakeLists.txt b/examples/09_Autograd/CMakeLists.txt index 25dd219c4..b641ec0ca 100644 --- a/examples/09_Autograd/CMakeLists.txt +++ b/examples/09_Autograd/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran examples add_executable(tensor_manipulation_backward tensor_manipulation_backward.f90) diff --git a/examples/09_Autograd/README.md b/examples/09_Autograd/README.md index e077aa0a3..a454abd39 100644 --- a/examples/09_Autograd/README.md +++ b/examples/09_Autograd/README.md @@ -30,14 +30,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. -Then from this directory create a virtual environment and install the necessary -Python modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. ### Tensor manipulation demo diff --git a/examples/10_Optimizers/CMakeLists.txt b/examples/10_Optimizers/CMakeLists.txt index e12473d44..5a97fa89c 100644 --- a/examples/10_Optimizers/CMakeLists.txt +++ b/examples/10_Optimizers/CMakeLists.txt @@ -21,13 +21,11 @@ else() endif() message(STATUS "Building with Fortran PyTorch coupling") -# Install Python dependencies +# Check Python virtual environment is active find_package(Python COMPONENTS Interpreter REQUIRED) if(NOT DEFINED ENV{VIRTUAL_ENV} AND NOT DEFINED ENV{CONDA_PREFIX}) message(FATAL_ERROR "Please activate your virtualenv or conda environment") endif() -execute_process(COMMAND ${Python_EXECUTABLE} -m pip install -r - ${PROJECT_SOURCE_DIR}/requirements.txt) # Fortran example add_executable(optimizers optimizers.f90) diff --git a/examples/10_Optimizers/README.md b/examples/10_Optimizers/README.md index d2e331d76..d9132a271 100644 --- a/examples/10_Optimizers/README.md +++ b/examples/10_Optimizers/README.md @@ -25,14 +25,10 @@ To run this example requires: ## Running -To run this example install FTorch as described in the main documentation. -Then from this directory create a virtual environment and install the necessary -Python modules: -``` -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -``` +To run this example, first install FTorch as described in the main +documentation, making use of the `examples` optional dependencies. See the +[user guide section](https://cambridge-iccs.github.io/FTorch/page/installation/general.html#python-dependencies) +on Python dependencies for details. Run the Python version of the demo with ``` diff --git a/examples/10_Optimizers/requirements.txt b/examples/10_Optimizers/requirements.txt deleted file mode 100644 index 92de10b5a..000000000 --- a/examples/10_Optimizers/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib -numpy -torch diff --git a/ftorch_utils/README.md b/ftorch_utils/README.md new file mode 100644 index 000000000..1bfc9d506 --- /dev/null +++ b/ftorch_utils/README.md @@ -0,0 +1,44 @@ +# FTorch: Utils + +This directory contains useful Python utilities for users of the library. + +## `pt2ts.py` + +This is a Python script that can take a PyTorch model and convert it to +TorchScript. +It provides the user with the option to [jit.script](https://pytorch.org/docs/stable/generated/torch.jit.script.html#torch.jit.script) or [jit.trace](https://pytorch.org/docs/stable/generated/torch.jit.trace.html#torch.jit.trace). + +Dependencies: +- PyTorch + +### Usage + +1. Create and activate a virtual environment. +2. Install `ftorch_utils` and its dependencies, if they aren't already + installed. This can be achieved by running `pip install .` in the root + directory of FTorch. It will install the `pt2ts` script into your Python + environment allowing you to call it directly as `pt2ts` when the + virtual environment is active. +3. Run with `python3 -m pt2ts ` or simply `pt2ts `. + Run `pt2ts --help` (or below) to see the usage instructions for the script. + +The Torchscript model will be saved locally in the specified location. + +#### Command line arguments + +The `pt2ts.py` script is set up with the `argparse` Python module such that it +accepts several command line arguments: +* `model_name `, which is required to specify the name of the model + class (e.g. `SimpleNet`). +* `--model_definition_file `, which specifies the path of + the local file in which a PyTorch model is defined. +* `--input_model_file `, which is required to specify the + path to the directory in which the PyTorch model is saved. +* `--output_model_file `, which allows you to specify the + path to the directory in which the TorchScript model should be saved. +* `--trace`, which allows you to switch from scripting to tracing. +* `--test`, which allows you to run basic tests to check things are working as + expected. +* `--input_tensor_file `, which is required (if `--trace` + or `--test` was passed) to specify the path to the directory in which a + PyTorch tensor of appropriate input dimensions is saved. diff --git a/ftorch_utils/__init__.py b/ftorch_utils/__init__.py new file mode 100644 index 000000000..118a843ef --- /dev/null +++ b/ftorch_utils/__init__.py @@ -0,0 +1,13 @@ +from .torchscript import ( + load_pytorch, + load_torchscript, + script_to_torchscript, + trace_to_torchscript, +) + +__all__ = [ + "load_pytorch", + "load_torchscript", + "script_to_torchscript", + "trace_to_torchscript", +] diff --git a/ftorch_utils/pt2ts.py b/ftorch_utils/pt2ts.py new file mode 100755 index 000000000..4a9846738 --- /dev/null +++ b/ftorch_utils/pt2ts.py @@ -0,0 +1,246 @@ +#!/bin/env python3 +"""Convert a PyTorch model file to TorchScript format.""" + +import argparse +import os +from warnings import warn + +import torch + +from ftorch_utils.torchscript import ( + load_pytorch, + load_torchscript, + script_to_torchscript, + trace_to_torchscript, +) + + +def parse_user_input(): + """Retrieve command line options. + + Returns + ------- + Namespace + namespace containing known input arguments + """ + parser = argparse.ArgumentParser( + prog="pt2ts.py", + description="Convert a PyTorch model file to TorchScript format.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "model_name", + help="Name of the PyTorch model", + type=str, + ) + parser.add_argument( + "--model_definition_file", + help="Filename for the definition of the PyTorch model, including path", + type=str, + ) + # TODO: Accept pre-trained model name (needed for ResNet) + # Perhaps make model_definition_file optional? Need to allow weights + # specification, too + parser.add_argument( + "--input_model_file", + help="Filename for the model saved in PyTorch format, including path", + type=str, + ) + parser.add_argument( + "--output_model_file", + help="Filename for the model to be saved in TorchScript format, including path", + type=str, + ) + parser.add_argument( + "--input_tensor_file", + help=( + "Filename for the tensor(s) saved in PyTorch format, including path (only" + " required if running with --trace or --test). The tensor is used to" + " determine the dimensionality of the inputs so its values are ignored." + ), + type=str, + ) + parser.add_argument( + "--trace", + help=( + "Apply tracing rather than scripting.\n\n" + "If used then --input_tensor_file must also be provided." + ), + action="store_true", + ) + parser.add_argument( + "--test", + help=( + "Run basic testing to check everything is working as expected.\n\n" + "If used then --input_tensor_file must also be provided." + ), + action="store_true", + ) + parsed_args = parser.parse_args() + if parsed_args.input_tensor_file is None and parsed_args.trace: + value_error = "An input tensor must be provided to use --trace." + raise ValueError(value_error) + if parsed_args.input_tensor_file is None and parsed_args.test: + value_error = "An input tensor must be provided to use --test." + raise ValueError(value_error) + return parsed_args + + +def validate_input_model_file(input_model_file): + """Check the input model file exists and has the correct extension. + + Parameters + ---------- + input_model_file : str + Name of the input model file + """ + input_model_root, input_model_ext = os.path.splitext(input_model_file) + if input_model_ext != ".pt": + value_error = ( + f"PyTorch input file '{input_model_file}' has extension {input_model_ext}" + " but .pt was expected." + ) + raise ValueError(value_error) + if not os.path.exists(input_model_file): + input_file_error = f"PyTorch model file '{input_model_file}' cannot be found." + raise FileNotFoundError(input_file_error) + + +def validate_output_model_file(output_model_file, input_model_file): + """Check the output model file has the correct file extension. + + Also raise a warning if it would overwrite an existing file. + + Parameters + ---------- + output_model_file : str + Name of the output model file + input_model_file : str + Name of the input model file + """ + if output_model_file is None: + output_model_file = input_model_file + _, output_model_ext = os.path.splitext(output_model_file) + if output_model_ext != ".pt": + value_error = ( + f"TorchScript output file name '{output_model_file}' has extension" + f" {output_model_ext} but .pt was expected." + ) + raise ValueError(value_error) + if input_model_file == output_model_file: + value_err = ( + f"Output TorchScript file name '{output_model_file}' coincides with input" + f" PyTorch file name '{input_model_file}'. It would be overwritten." + ) + raise ValueError(value_err) + if os.path.exists(output_model_file): + warning = ( + "A file already exists with output TorchScript file name" + f" '{output_model_file}'. It will be overwritten." + ) + warn(warning, stacklevel=2) + + +def validate_input_tensor_file(input_tensor_file): + """Check the input tensor file exists and has the correct extension. + + Parameters + ---------- + input_tensor_file : str + Name of the input model file + """ + _, input_tensor_ext = os.path.splitext(input_tensor_file) + if input_tensor_ext != ".pt": + value_error = ( + f"PyTorch input tensor file '{input_tensor_file}' has extension" + f" {input_tensor_ext} but .pt was expected." + ) + raise ValueError(value_error) + if not os.path.exists(input_tensor_file): + input_file_error = f"PyTorch tensor file '{input_tensor_file}' cannot be found." + raise FileNotFoundError(input_file_error) + + +def main_cli(): + """Driver for command line interface to the `pt2ts` script. + + This function is required for the version that gets installed into the Python + environment's `bin` subdirectory. + """ + parsed_args = parse_user_input() + model_name = parsed_args.model_name + model_definition_file = parsed_args.model_definition_file + if model_definition_file is None: + value_error = ( + "pt2ts does not yet support pre-trained models and so currently requires a" + " model definition file." + ) + raise ValueError(value_error) + input_model_file = parsed_args.input_model_file + if input_model_file is None: + value_error = ( + "pt2ts does not yet support pre-trained models and so currently requires an" + " input model file." + ) + raise ValueError(value_error) + output_model_file = parsed_args.output_model_file + trace = parsed_args.trace + test = parsed_args.test + input_tensor_file = parsed_args.input_tensor_file + + validate_input_model_file(input_model_file) + validate_output_model_file(output_model_file, input_model_file) + + # Load the input PyTorch model + model = load_pytorch(model_definition_file, model_name, input_model_file) + + if test or trace: + validate_input_tensor_file(input_tensor_file) + + # Load the input tensor + input_tensors = torch.load(input_tensor_file) + if not isinstance(input_tensors, tuple): + input_tensors = (input_tensors,) + + if test: + # Propagate the input tensor through the model + # If something isn't working This will generate an error + pt_model_outputs = model(*input_tensors) + + # Apply scripting or tracing as requested, writing out to file + if trace: + trace_to_torchscript(model, input_tensors, output_model_file) + else: + script_to_torchscript(model, output_model_file) + print(f"Saved model to TorchScript in '{output_model_file}'.") + + if test: + # Check that the model file is created + if not os.path.exists(output_model_file): + torchscript_file_error = ( + f"Saved TorchScript file '{output_model_file}' cannot be found." + ) + raise FileNotFoundError(torchscript_file_error) + + # Load the TorchScript model and propagate the same input tensor + ts_model = load_torchscript(output_model_file) + ts_model_outputs = ts_model(*input_tensors) + if not isinstance(ts_model_outputs, tuple): + ts_model_outputs = (ts_model_outputs,) + if not isinstance(pt_model_outputs, tuple): + pt_model_outputs = (pt_model_outputs,) + for ts_output, pt_output in zip( + ts_model_outputs, pt_model_outputs, strict=True + ): + if not torch.all(ts_output.eq(pt_output)): + model_error = ( + "Saved Torchscript model is not performing as expected.\n" + "Consider using scripting if you used tracing, or investigate further." + ) + raise RuntimeError(model_error) + print("Saved TorchScript model working as expected in a basic test.") + print("Users should perform further validation as appropriate.") + + +if __name__ == "__main__": + main_cli() diff --git a/ftorch_utils/torchscript.py b/ftorch_utils/torchscript.py new file mode 100644 index 000000000..18d727147 --- /dev/null +++ b/ftorch_utils/torchscript.py @@ -0,0 +1,102 @@ +"""Module containing utilities for handling TorchScript files.""" + +import importlib.util +import os +import sys + +import torch + + +def script_to_torchscript(model: torch.nn.Module, filename: str) -> None: + """ + Save PyTorch model to TorchScript using scripting. + + Parameters + ---------- + model : torch.NN.Module + a PyTorch model + filename : str + name of file to save to + """ + # FIXME: Adopt torch.jit.optimize_for_inference() once + # https://github.com/pytorch/pytorch/issues/81085 is resolved + scripted_model = torch.jit.script(model) + # print(scripted_model.code) + scripted_model.save(filename) + + +def trace_to_torchscript( + model: torch.nn.Module, + input_tensor: torch.Tensor, + filename: str, +) -> None: + """ + Save PyTorch model to TorchScript using tracing. + + Parameters + ---------- + model : torch.NN.Module + a PyTorch model + input_tensor : torch.Tensor + appropriate size Tensor to act as input to model + filename : str + name of file to save to + """ + # FIXME: Adopt torch.jit.optimize_for_inference() once + # https://github.com/pytorch/pytorch/issues/81085 is resolved + traced_model = torch.jit.trace(model, input_tensor) + # traced_model.save(filename) + frozen_model = torch.jit.freeze(traced_model) + ## print(frozen_model.graph) + ## print(frozen_model.code) + frozen_model.save(filename) + + +def load_pytorch( + model_definition_file: str, model_name: str, saved_model_file: str +) -> torch.nn.Module: + """ + Load a PyTorch model from file. + + Parameters + ---------- + model_definition_file : str + name of file containing PyTorch model definition + model_name : str + name of the PyTorch model + saved_model_file : str + name of file containing saved PyTorch model + + Returns + ------- + model : torch.NN.Module + a PyTorch model + """ + # Import the module containing the model definition + module_name, _ = os.path.splitext(os.path.basename(model_definition_file)) + module_spec = importlib.util.spec_from_file_location( + module_name, model_definition_file + ) + module = importlib.util.module_from_spec(module_spec) + sys.modules[module_name] = module + module_spec.loader.exec_module(module) + + # Construct the PyTorch model and load its weights from file + cls = getattr(module, model_name) + model = cls() + with torch.inference_mode(): + model.load_state_dict(torch.load(saved_model_file, weights_only=True)) + model.eval() + return model + + +def load_torchscript(filename: str) -> torch.nn.Module: + """ + Load a TorchScript model from file. + + Parameters + ---------- + filename : str + name of file containing TorchScript model + """ + return torch.jit.load(filename) diff --git a/pages/developer/developer.md b/pages/developer/developer.md index 3b8fed15a..8df102800 100644 --- a/pages/developer/developer.md +++ b/pages/developer/developer.md @@ -23,11 +23,14 @@ expected for submissions. ### Developer requirements Development tools for [pre-processing](#fortran-source-and-fypp), -[code styling](#code-style) etc. are pip-installable using the -[requirements-dev file](https://github.com/Cambridge-ICCS/FTorch/blob/main/requirements-dev.txt): +[code styling](#code-style) etc. are pip-installable using the `dev` dependency +group in the +[pyproject.toml file](https://github.com/Cambridge-ICCS/FTorch/blob/main/pyproject.toml): ```sh -pip install -r requirements-dev.txt +pip install . --group dev ``` +This pulls in the packages in the `docs`, `lint`, `preproc`, and `test` +dependency groups, which can also be used individually. In order to streamline the process of uploading we provide a pre-commit hook in [`.githooks/pre-commit`](https://github.com/Cambridge-ICCS/FTorch/blob/main/.githooks/pre-commit). @@ -126,7 +129,7 @@ CUDA LibTorch installation then compile errors would arise from the use of the @note _The HIP/ROCm backend uses the same API as the CUDA backend, so FTorch treats HIP as CUDA in places when calling LibTorch or PyTorch. -This should not concern end-users as the FTorch and pt2ts.py APIs handle this. +This should not concern end-users as the FTorch and `pt2ts` APIs handle this. For further information see the [PyTorch HIP documentation](https://docs.pytorch.org/docs/stable/notes/hip.html)_ @endnote diff --git a/pages/developer/release_checklist.md b/pages/developer/release_checklist.md index 1d6077470..7e6ed995e 100644 --- a/pages/developer/release_checklist.md +++ b/pages/developer/release_checklist.md @@ -28,6 +28,9 @@ consistently and correctly. 1. Update the `CMakeLists.txt` 1. Change `set(PACKAGE_VERSION X.Y.Z)` 1. Add the `CMakeLists.txt` to the git staging index +1. Update the `pyproject.toml` + 1. Change `version = "X.Y.Z"` + 1. Add the `pyproject.toml` to the git staging index 1. Update code on the git remote: 1. Commit the updated version files with message: `Update version number for release vX.Y.Z` diff --git a/pages/installation/general.md b/pages/installation/general.md index 1d9f87533..297fb0930 100644 --- a/pages/installation/general.md +++ b/pages/installation/general.md @@ -21,11 +21,48 @@ To install FTorch requires the following to be installed on the system: - Fortran (2008 standard compliant), C++ (must fully support C++17), and C compilers - [LibTorch](https://pytorch.org/cppdocs/installing.html)[^1] or [PyTorch](https://pytorch.org/) +For instructions on how to install PyTorch and additional Python dependencies +for FTorch's examples, see the [Python dependencies](#python-dependencies) +section below. + [^1]: _The minimal example provided downloads the CPU-only Linux Nightly binary. [Alternative versions](https://pytorch.org/get-started/locally/) to match hardware may be required._ +#### Python dependencies + +If LibTorch is not installed then FTorch requires PyTorch to be installed. +FTorch's `pt2ts` utility script has PyTorch as a hard requirement, as well as +the `ftorch_utils` Python module included in the FTorch repo. To install +PyTorch and `ftorch_utils`, run +```sh +pip install . --extra-index-url +``` +where `` is determined based on the +[matrix](https://pytorch.org/get-started/locally/) on the PyTorch website. + +We recommend also installing the additional dependencies for FTorch's examples +(TorchVision, matplotlib, NumPy, and mpi4py) in the same command. This can be +achieved by including the `examples` optional dependencies, as follows: +```sh +pip install .[examples] --extra-index-url +``` + +If you don't want to install `ftorch_utils` then you can install FTorch's +`torch` and `torchvision` dependencies using +```sh +pip install torch torchvision --index-url +``` + +@note +We recommend installing `torch` and `torchvision` in the same command such as +would be done in the third command above. Doing so ensures that they are +configured in the same way. If you have installed the `ftorch_utils` module with +the `examples` optional dependencies as in the second command above then there +is no need to worry about this because they are both included as dependencies. +@endnote + #### Additional dependencies of the test suite FTorch's test suite has some additional dependencies. @@ -33,12 +70,11 @@ FTorch's test suite has some additional dependencies. - You will also need to install the unit testing framework [pFUnit](https://github.com/Goddard-Fortran-Ecosystem/pFUnit). - FTorch's test suite requires that [PyTorch](https://pytorch.org/) has been - installed, as opposed to LibTorch. We recommend installing `torchvision` in - the same command (e.g., `pip install torch torchvision`).[^2] Doing so - ensures that `torch` and `torchvision` are configured in the same way. -- Other Python modules are installed automatically upon building the tests. - -[^2]: _For more details, see [here](https://pytorch.org/get-started/locally/)._ + installed, as opposed to LibTorch. See the + [Python dependencies](#python-dependencies) section above for details on how + to do this. +- Other Python modules are installed automatically upon building specific tests, + namely `mpi4py` for the MPI example. ### Basic Installation Instructions diff --git a/pages/installation/gpu.md b/pages/installation/gpu.md index 2e50044a9..c0342bc8c 100644 --- a/pages/installation/gpu.md +++ b/pages/installation/gpu.md @@ -28,14 +28,22 @@ HIP with a LibTorch binary. #### Installation using pip -If installing using pip the appropriate version for the hardware can be specified by -using the `--index-url` option during `pip install`. - -Instructions for CPU, CUDA, and HIP/ROCm can be found in the installation matrix on -[pytorch.org](https://pytorch.org/). +To install `ftorch_utils`, its dependencies, and the additional dependencies +for the examples with GPU support, use +```sh +pip install .[examples] --extra-index-url +``` +or to install `torch` and `torchvision` directly use +```sh +pip install torch torchvision --index-url +``` -For XPU use `--index-url https://download.pytorch.org/whl/test/xpu`, whilst for MPS -pip should automatically detect the hardware and install the appropriate version. +In terms of the URL to use to download the appropriate wheel, instructions for +CPU, CUDA, and HIP/ROCm can be found in the installation matrix on +[pytorch.org](https://pytorch.org/). For XPU, use the +`https://download.pytorch.org/whl/test/xpu` wheel. For MPS, `pip` should +automatically detect the hardware and install the appropriate version so no +wheel needs to be specified. #### LibTorch binary @@ -60,23 +68,39 @@ cmake .. -DGPU_DEVICE=NONE ``` i.e., CPU-only. -**2) Save TorchScript models on the target device** -When saving a TorchScript model, ensure that it is on the GPU. -For example, when using -[`pt2ts.py`](https://github.com/Cambridge-ICCS/FTorch/blob/main/utils/pt2ts.py), -this can be done by passing the `--device_type ` argument. This -sets the `device_type` variable, which has the effect of transferring the model -(and any input arrays used in tracing/testing) to the specified GPU device in the -following lines: +**2) Save PyTorch models on the target device** +When saving a model in PyTorch format, ensure that it has the desired GPU device +type. For example, in +[`examples/2_SimpleNet/simplenet.py`](https://github.com/Cambridge-ICCS/FTorch/blob/main/examples/2_SimpleNet/simplenet.py), +this is done in the following lines: ```python -if device_type != "cpu": - trained_model = trained_model.to(device_type) - trained_model.eval() - trained_model_dummy_input_1 = trained_model_dummy_input_1.to(device_type) - trained_model_dummy_input_2 = trained_model_dummy_input_2.to(device_type) + model = SimpleNet().to(device_type) ``` +and +```python + input_tensor = torch.Tensor([0.0, 1.0, 2.0, 3.0, 4.0]).to(device_type) +``` +The first line transfers the model to the specified GPU device, while the second +line does the same for any input arrays used in tracing or testing. Having +transferred the model and any input tensors to the GPU device, write them out +using `torch.save`. In the SimpleNet example above, this is done with +```python + torch.save(model.state_dict(), f"saved_simplenet_model_{device_type}.pt") +``` +and +```python + torch.save(input_tensor, f"saved_simplenet_input_tensor_{device_type}.pt") +``` + +**3) Convert PyTorch model to TorchScript model** +When converting a PyTorch model to a TorchScript model using the `pt2ts` script, +the device type will be inherited. As such, if the PyTorch model is saved using +a particular device type then this will be preserved in the resulting +TorchScript model. For further details on the `pt2ts` script, call +`pt2ts --help` or read the +[ftorch-utils README](https://github.com/Cambridge-ICCS/FTorch/tree/main/ftorch_utils/README.md). -**3) Specify the target device from FTorch** +**4) Specify the target device from FTorch** When calling [[ftorch_tensor(module):torch_tensor_from_array(interface)]] and [[ftorch_model(module):torch_model_load(subroutine)]] in Fortran, the device type for the input tensor(s) and model should be set to the appropriate diff --git a/pages/installation/systems.md b/pages/installation/systems.md index dd5637ef8..8ca832b8a 100644 --- a/pages/installation/systems.md +++ b/pages/installation/systems.md @@ -71,8 +71,8 @@ python -m venv .ftorch rem Activate the virtual environment call .ftorch\Scripts\activate -rem Install torch -pip install torch torchvision torchaudio +rem Install dependencies (--extra-index-url not required on Windows) +pip install .[examples] rem Enable output ECHO ON diff --git a/pages/usage/generic_example.md b/pages/usage/generic_example.md index 1136416b0..87d98ec2e 100644 --- a/pages/usage/generic_example.md +++ b/pages/usage/generic_example.md @@ -19,8 +19,14 @@ In order to use FTorch users will typically need to follow these steps: These are outlined in detail below. +#### 1. Saving the model in PyTorch format -#### 1. Saving the model as TorchScript +The PyTorch model should be written out in the usual way using +[`torch.save`](https://docs.pytorch.org/docs/stable/generated/torch.save.html). +That is, using the pickle file format commonly used for PyTorch objects such as +models and tensors. The `.pt` file extension should be used. + +#### 2. Converting the model to TorchScript The trained PyTorch model needs to be exported to [TorchScript](https://pytorch.org/docs/stable/jit.html). @@ -31,12 +37,19 @@ or functionalities from within Python. If you are not familiar with these we provide a tool -[`pt2ts.py`](https://github.com/Cambridge-ICCS/FTorch/blob/main/utils/pt2ts.py) -as part of FTorch which contains an easily adaptable script to save your -PyTorch model as TorchScript. +[`pt2ts`](https://github.com/Cambridge-ICCS/FTorch/blob/main/ftorch_utils/pt2ts.py) +as part of FTorch which allows to convert a model in PyTorch file format into +one using the TorchScript format. To make use of this tool, install the +`ftorch-utils` Python package and its dependencies by running +```sh +pip install . +``` +in FTorch's root directory. This will install the `pt2ts` script into your Python +environment. For usage instructions, call `pt2ts --help` or read the +[ftorch-utils README](https://github.com/Cambridge-ICCS/FTorch/tree/main/ftorch_utils/README.md). -#### 2. Using the model from Fortran +#### 3. Using the model from Fortran To use the trained Torch model from within Fortran we need to import the `ftorch` module and use the binding routines to load the model, convert the data, @@ -97,7 +110,7 @@ call torch_delete(model_output_arr) ``` -#### 3. Building the code +#### 4. Building the code The code now needs to be compiled and linked against our installed library. Here we describe how to do this for two build systems, CMake and make. diff --git a/pages/usage/offline.md b/pages/usage/offline.md index 1fa086b8d..db858da0d 100644 --- a/pages/usage/offline.md +++ b/pages/usage/offline.md @@ -41,12 +41,16 @@ extension. For more information on how to do this, see the #### 4. pt2ts Having written a trained model to a file with `.pt` extension, use the -`pt2ts.py` utility Python script to convert it to TorchScript format. A template -`pt2ts.py` script can be found in the -[`utils`](https://github.com/Cambridge-ICCS/FTorch/tree/main/utils) -subdirectory. See the -[README](https://github.com/Cambridge-ICCS/FTorch/blob/main/utils/README.md) -there for more details on how to use the script. +[`pt2ts`](https://github.com/Cambridge-ICCS/FTorch/tree/main/ftorch_utils/pt2ts.py) +Python utility script to convert it to TorchScript format. Run +```sh +pt2ts --help +``` +or see the +[README](https://github.com/Cambridge-ICCS/FTorch/blob/main/ftorch_utils/README.md) +in the +[`ftorch_utils`](https://github.com/Cambridge-ICCS/FTorch/tree/main/ftorch_utils) +subdirectory for more details on how to use the script. #### 5. Fortran model with inference diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..a9131aefd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,95 @@ +[build-system] +requires = ["setuptools"] + +[project] +name = "ftorch-utils" +version = "1.1" +authors = [ + {name = "Jack Atkinson"}, + {name = "Joe Wallwork"}, +] +maintainers = [ + {name = "Jack Atkinson"}, + {name = "Joe Wallwork"}, +] +description = "Python utilities supporting the FTorch Fortran interface for PyTorch." +readme = "README.md" +license = {file = "LICENSE"} +keywords = [ + "python", + "machine-learning", + "deep-learning", + "fortran", + "torch", + "pytorch", + "interoperability", + "hacktoberfest", + "libtorch", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Fortran", + "Programming Language :: Python", + "Topic :: Scientific/Engineering", +] +dependencies = ["numpy", "torch"] +optional-dependencies = {examples = ["matplotlib", "mpi4py", "torchvision"]} + +[dependency-groups] +docs = ["ford>=7.0.13"] +lint = [ + "clang-format==19.1.3", + "clang-tidy==19.1.0", + "cmakelang", + "fortitude-lint==0.7.0", + "ruff==0.7.3", +] +preproc = ["fypp==3.2"] +test = [ + "matplotlib", + "mpi4py", + "pytest", + "torchvision", +] +dev = [ + {include-group = "docs"}, + {include-group = "lint"}, + {include-group = "preproc"}, + {include-group = "test"}, +] + +[project.scripts] +pt2ts = "ftorch_utils.pt2ts:main_cli" + +[project.urls] +Repository = "https://github.com/Cambridge-ICCS/FTorch" + +[tool.setuptools] +packages = ["ftorch_utils"] + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +# See https://docs.astral.sh/ruff/rules for full details of each ruleset. + +# Enable: D: `pydocstyle`, PL: `pylint`, I: `isort`, W: `pycodestyle whitespace` +# NPY: `numpy`, FLY: `flynt`, RUF: `ruff` +# From flake8: "ARG", "SLF", "S", "BLE", "B", "A", "C4", "EM", "ICN", +# "PIE", "Q", "RSE", "SIM", "TID" +select = ["D", "PL", "I", "E", "W", "NPY", "FLY", "RUF", + "ARG", "SLF", "S", "BLE","B", "A", "C4", "EM", "ICN", "PIE", "Q", "RSE", + "SIM", "TID"] + +# Enable D417 (Missing argument description) on top of the NumPy convention. +extend-select = ["D417"] + +# Ignore SIM108 (use ternary instead of if-else) as it can arguably obscure intent. +# Ignore RUF002 (ambiguous characters) as it does not allow apostrophes in strings. +ignore = ["SIM108", "RUF002"] + +[tool.ruff.lint.pydocstyle] +# Use NumPy convention for checking docstrings +convention = "numpy" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 425ea0f5f..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,7 +0,0 @@ -clang-format==19.1.3 -clang-tidy==19.1.0 -cmakelang -ford>=7.0.13 -fortitude-lint==0.7.0 -fypp==3.2 -ruff==0.7.3 diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index efeead725..000000000 --- a/ruff.toml +++ /dev/null @@ -1,24 +0,0 @@ -[format] -docstring-code-format = true - -[lint] -# See https://docs.astral.sh/ruff/rules for full details of each ruleset. - -# Enable: D: `pydocstyle`, PL: `pylint`, I: `isort`, W: `pycodestyle whitespace` -# NPY: `numpy`, FLY: `flynt`, RUF: `ruff` -# From flake8: "ARG", "SLF", "S", "BLE", "B", "A", "C4", "EM", "ICN", -# "PIE", "Q", "RSE", "SIM", "TID" -select = ["D", "PL", "I", "E", "W", "NPY", "FLY", "RUF", - "ARG", "SLF", "S", "BLE","B", "A", "C4", "EM", "ICN", "PIE", "Q", "RSE", - "SIM", "TID"] - -# Enable D417 (Missing argument description) on top of the NumPy convention. -extend-select = ["D417"] - -# Ignore SIM108 (use ternary instead of if-else) as it can arguably obscure intent. -# Ignore RUF002 (ambiguous characters) as it does not allow apostrophes in strings. -ignore = ["SIM108", "RUF002"] - -[lint.pydocstyle] -# Use NumPy convention for checking docstrings -convention = "numpy" diff --git a/utils/README.md b/utils/README.md deleted file mode 100644 index 5161bd21b..000000000 --- a/utils/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# FTorch: Utils - -This directory contains useful utilities for users of the library. - -## `pt2ts.py` - -This is a python script that can take a PyTorch model and convert it to -TorchScript. -It provides the user with the option to [jit.script](https://pytorch.org/docs/stable/generated/torch.jit.script.html#torch.jit.script) or [jit.trace](https://pytorch.org/docs/stable/generated/torch.jit.trace.html#torch.jit.trace) for both CPU and GPU. - -Dependencies: -- PyTorch - -### Usage - -1. Create and activate a virtual environment with PyTorch and any dependencies for your model. -2. Place the `pt2ts.py` script in the same folder as your model files. -3. Import your model into `pt2ts.py` and amend options as necessary (search for `FTORCH-TODO`). -4. Run with `python3 pt2ts.py`. - -The Torchscript model will be saved locally in the same location from which the `pt2ts.py` -script is being run. - -#### Command line arguments - -The `pt2ts.py` script is set up with the `argparse` Python module such that it -accepts two command line arguments: -* `--filepath `, which allows you to specify the path - to the directory in which the TorchScript model should be saved. -* `--device_type `, which allows you to specify the CPU or GPU - device type with which to save the model. (To read and make use of a model on - a particular architecture, it's important that the model was targeted for that - architecture when it was saved.) diff --git a/utils/pt2ts.py b/utils/pt2ts.py deleted file mode 100644 index 7af273b15..000000000 --- a/utils/pt2ts.py +++ /dev/null @@ -1,204 +0,0 @@ -"""Load a PyTorch model and convert it to TorchScript.""" -# Throughout this script there are various `FTORCH-TODO` comments indicating where -# the user needs to modify as appropriate for their model - -import os -from typing import Optional - -# FTORCH-TODO -# Add a module import with your model here: -# This example assumes the model architecture is in an adjacent module `my_ml_model.py` -import my_ml_model -import torch - - -def script_to_torchscript( - model: torch.nn.Module, filename: Optional[str] = "scripted_model.pt" -) -> None: - """ - Save PyTorch model to TorchScript using scripting. - - Parameters - ---------- - model : torch.NN.Module - a PyTorch model - filename : str - name of file to save to - """ - # FIXME: torch.jit.optimize_for_inference() when PyTorch issue #81085 is resolved - scripted_model = torch.jit.script(model) - # print(scripted_model.code) - scripted_model.save(filename) - - -def trace_to_torchscript( - model: torch.nn.Module, - dummy_input: torch.Tensor, - filename: Optional[str] = "traced_model.pt", -) -> None: - """ - Save PyTorch model to TorchScript using tracing. - - Parameters - ---------- - model : torch.NN.Module - a PyTorch model - dummy_input : torch.Tensor - appropriate size Tensor to act as input to model - filename : str - name of file to save to - """ - # FIXME: torch.jit.optimize_for_inference() when PyTorch issue #81085 is resolved - traced_model = torch.jit.trace(model, dummy_input) - # traced_model.save(filename) - frozen_model = torch.jit.freeze(traced_model) - ## print(frozen_model.graph) - ## print(frozen_model.code) - frozen_model.save(filename) - - -def load_torchscript(filename: Optional[str] = "saved_model.pt") -> torch.nn.Module: - """ - Load a TorchScript from file. - - Parameters - ---------- - filename : str - name of file containing TorchScript model - """ - model = torch.jit.load(filename) - - return model - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - parser.add_argument( - "--device_type", - help="Device type to run the inference on", - type=str, - choices=["cpu", "cuda", "hip", "xpu", "mps"], - default="cpu", - ) - parser.add_argument( - "--filepath", - help="Path to the file containing the PyTorch model", - type=str, - default=os.path.dirname(__file__), - ) - parsed_args = parser.parse_args() - device_type = parsed_args.device_type - filepath = parsed_args.filepath - - # ===================================================== - # Load model and prepare for saving - # ===================================================== - - # FTORCH-TODO - # Load a pre-trained PyTorch model - # Insert code here to load your model as `trained_model`. - # This example assumes my_ml_model has a method `initialize` to load - # architecture, weights, and place in inference mode - trained_model = my_ml_model.initialize() - - # Switch off specific layers/parts of the model that behave - # differently during training and inference. - # This may have been done by the user already, so just make sure here. - trained_model.eval() - - # ===================================================== - # Prepare dummy input and check model runs - # ===================================================== - - # FTORCH-TODO - # Generate a dummy input Tensor `dummy_input` to the model of appropriate size. - # This example assumes two inputs of size (512x40) and (512x1) - trained_model_dummy_input_1 = torch.ones((512, 40), dtype=torch.float64) - trained_model_dummy_input_2 = torch.ones((512, 1), dtype=torch.float64) - - # Transfer the model and inputs to GPU device, if appropriate - if device_type != "cpu": - if device_type == "hip": - device = torch.device("cuda") # NOTE: HIP is treated as CUDA in FTorch - else: - device = torch.device(device_type) - trained_model = trained_model.to(device) - trained_model.eval() - trained_model_dummy_input_1 = trained_model_dummy_input_1.to(device) - trained_model_dummy_input_2 = trained_model_dummy_input_2.to(device) - - # FTORCH-TODO - # Run model for dummy inputs - # If something isn't working This will generate an error - trained_model_dummy_outputs = trained_model( - trained_model_dummy_input_1, - trained_model_dummy_input_2, - ) - - # ===================================================== - # Save model - # ===================================================== - - # FTORCH-TODO - # Set the name of the file you want to save the torchscript model to: - saved_ts_filename = f"saved_model_{device_type}.pt" - # A filepath may also be provided. To do this, pass the filepath as an argument to - # this script when it is run from the command line, i.e. `./pt2ts.py path/to/model`. - - # FTORCH-TODO - # Save the PyTorch model using either scripting (recommended if possible) or tracing - # ----------- - # Scripting - # ----------- - script_to_torchscript(trained_model, filename=saved_ts_filename) - - # ----------- - # Tracing - # ----------- - # trace_to_torchscript( - # trained_model, trained_model_dummy_input, filename=saved_ts_filename - # ) - - # ===================================================== - # Check model saved OK - # ===================================================== - - # Load torchscript and run model as a test, scaling inputs as above - trained_model_dummy_input_1 = 2.0 * trained_model_dummy_input_1 - trained_model_dummy_input_2 = 2.0 * trained_model_dummy_input_2 - trained_model_testing_outputs = trained_model( - trained_model_dummy_input_1, - trained_model_dummy_input_2, - ) - ts_model = load_torchscript(filename=saved_ts_filename) - ts_model_outputs = ts_model( - trained_model_dummy_input_1, - trained_model_dummy_input_2, - ) - - if not isinstance(ts_model_outputs, tuple): - ts_model_outputs = (ts_model_outputs,) - if not isinstance(trained_model_testing_outputs, tuple): - trained_model_testing_outputs = (trained_model_testing_outputs,) - for ts_output, output in zip(ts_model_outputs, trained_model_testing_outputs): - if torch.all(ts_output.eq(output)): - print("Saved TorchScript model working as expected in a basic test.") - print("Users should perform further validation as appropriate.") - else: - model_error = ( - "Saved Torchscript model is not performing as expected.\n" - "Consider using scripting if you used tracing, or investigate further." - ) - raise RuntimeError(model_error) - - # Check that the model file is created - if not os.path.exists(os.path.join(filepath, saved_ts_filename)): - torchscript_file_error = ( - f"Saved TorchScript file {os.path.join(filepath, saved_ts_filename)} " - "cannot be found." - ) - raise FileNotFoundError(torchscript_file_error)