knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.path = "man/figures/README-", out.width = "100%" )
The goal of lltm is to be a minimal implementation of an extension for torch that interfaces with the underlying C++ interface, called LibTorch.
In this pakage we provide an implementation of a new recurrent unit that is similar to a LSTM but it lacks a forget gate and uses an Exponential Linear Unit (ELU) as its internal activation function. Because this unit never forgets, we’ll call it LLTM, or Long-Long-Term-Memory unit.
The example implemented here is a port of the official PyTorch tutorial on custom C++ and CUDA extensions.
Writing C++ extensions for torch requires us to coordinate the communication between multiple agents in the torch ecossytem. The following diagram is a high-level overview on how they communicate in this package.
On the torch package side the agents that appear are:
In the extension side the actors are:
csrc
here is the equivalent to Lantern in the
torch project. It's a C interface for calling functions from LibTorch that
implement the desidered extension functionality. The library produced here
must also be compiled with MSVC on Windows thus the C interface is required.csrc
functionality. Here, in general, we want to use the
torchpkg.so
features to manage memory instead of re-implementing that functionality. csrc
for details.csrc
library and exports functionality
to the R API.csrc
library as well as the C++ library.CMakeLists.txt: The first important file that you should get familiar with in this directory is the CMakeLists.txt file. This is the CMake configuration file defining how the project must be compiled and its dependencies. You can refer to comments in the file for almost line by line explanation of definitions.
csrc/src/lltm.cpp: In this file we define the LibTorch implementation of the
operations we want to export. We can use as many functions as we want in the implementation
and we mark the functions we want to make available in the R package with // [[torch::export]]
, similar to what we do when exporting functions with Rcpp. For example
we define the lltm_forward
implementation with: (For details on the lltm_forward
implementation refer to the official guide.)
The // [[torch::export]]
marks will allow torchexport that is called during when
building with cmake to autogenerate C wrappers necessary to handle errors and
to correctly pass data between this library and the R package.
```cpp
// [[torch::export]]
std::vector
auto gate_weights = torch::addmm(bias, X, weights.transpose(0, 1)); auto gates = gate_weights.chunk(3, /*dim=*/1); auto input_gate = torch::sigmoid(gates[0]); auto output_gate = torch::sigmoid(gates[1]); auto candidate_cell = torch::elu(gates[2], /*alpha=*/1.0); auto new_cell = old_cell + candidate_cell * input_gate; auto new_h = torch::tanh(new_cell) * output_gate; return {new_h, new_cell, input_gate, output_gate, candidate_cell, X, gate_weights};
} ```
csrc/src/exports.cpp: This file is autogenerated by torchexport
and
should not be modified manually. It wrapps the function that uses LibTorch's
API into C API functions that can be called in the R/Rcpp side.
csrc/include/lltm/exports.h This file includes declarations used by
functions defined in exports.cpp
. It should always be included in lltm.h
.
Note that this file is also autogenerated.
csrc/src/lltm.def: This file is automaticaly generated by a custom CMake command.
It lists the functions from lltm.cpp
that we
want to export. This is only required for Windows, but it's a good practice to
keep it up to date. See more information on Module definition files in this
link
For example, the current definition is:
LIBRARY LLTM
EXPORTS
_lltm_forward
_lltm_backward
lltm/exports.h
headers that is auto-generated by
torchexport
.
You might want add other function declarations here, if for some reason
you had to bypass the code autogeneration.The library implemented in csrc
can be compiled with CMake. We use the following
commands to compile and install it locally:
cd csrc && mkdir build cmake .. && cmake --build . --target install --config Release
Now that we implemented the operators that we wanted to call from R, we can now implement the Rcpp wrappers that will allow us to call those operators from R.
[[torch::export]]
in your library.
The wrappers defined in this file take R objects
and convert them to the correct C type that we need to pass to the C library.
Remember that the C library return void*
pointers and we need to make sure to
free this objects when they are no longer in use, otherwise we will leak memory.
The torch.h
headers provides Rcpp extension types that act like smart pointers
and make sure that the objects created in the C library are correctly freed when
they are no longer in use. The types implemented in torch.h
also implement
convertion from and to SEXP
s so we don't need to implement them on our own.You can find all the available types in the torch
namespace available when
you include <torch.h>
.
host_exception_handler
that is used to correctly raise exceptions from your
C library to the R runtime - in general you don't need to modify the one that's
already defined in this template.cpp
#include <Rcpp.h>
#define LLTM_HEADERS_ONLY // should only be defined in a single file
#include <lltm/lltm.h>
#define TORCH_IMPL // should only be defined in a single file
#define IMPORT_TORCH // should only be defined in a single file
#include <torch.h>
_lltm_forward
(as it
only sees the headers), so we convert the .def
file created in csrc
to a .lib
file and use this as an argument to the linker. That's what Makevars.win
implements.
In most cases you won't need to modify this file.Now the Rcpp wrappers are implemented and exported you have now access to lltm_forward
in the R side.
nn_module
that uses it and we implemented it in this file. This is normal
R code and we won't discuss the actual implementation. It's not trivial to package torch extensions because they can't be entirely built on CRAN machines. We would need to include pre-built binaries in the package tarball but for security reasons that's not accepted on CRAN.
In this package we implement a suggested way of packaging torch extensions that makes it really easy for users to install your package without having to use custom installation steps or building libraries from source. The diagram below shows an overview of the packaging process.
R/package.R: implements the suggested installation logic - including downloading from GitHub Releases and dynamically loading the shared libraries.
.github/workflows/R-CMD-check.yaml: the job called Build-Libs implements
the logic for building the binaries from csrc
for each operating system and
uploading to GH Releases.
~~You can install the released version of lltm from CRAN with:~~
install.packages("lltm")
And the development version from GitHub with:
# install.packages("devtools") devtools::install_github("mlverse/lltm")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.