Custom serialization functions may be registered to handle external pointer type reference objects.
This allows tensors from the torch
package to be used seamlessly in ‘mirai’ computations.
Set up dameons.
Create the serialization configuration, specifying ‘class’ as ‘torch_tensor’ and ‘vec’ as TRUE.
Use everywhere()
, supplying the configuration to the ‘.serial’ argument, and (optionally) making the torch
package available on all daemons for convenience.
library(mirai)
library(torch)
daemons(1)
#> [1] 1
cfg <- serial_config(
class = "torch_tensor",
sfunc = torch:::torch_serialize,
ufunc = torch::torch_load,
vec = TRUE
)
everywhere(library(torch), .serial = cfg)
The below example creates a convolutional neural network using torch::nn_module()
.
A set of model parameters is also specified.
The model specification and parameters are then passed to and initialized within a ‘mirai’.
model <- nn_module(
initialize = function(in_size, out_size) {
self$conv1 <- nn_conv2d(in_size, out_size, 5)
self$conv2 <- nn_conv2d(in_size, out_size, 5)
},
forward = function(x) {
x <- self$conv1(x)
x <- nnf_relu(x)
x <- self$conv2(x)
x <- nnf_relu(x)
x
}
)
params <- list(in_size = 1, out_size = 20)
m <- mirai(do.call(model, params), model = model, params = params)
m[]
#> An `nn_module` containing 1,040 parameters.
#>
#> ── Modules ────────────────────────────────────────────────────────────────────────────────────────────────────────────
#> • conv1: <nn_conv2d> #520 parameters
#> • conv2: <nn_conv2d> #520 parameters
The returned model is an object containing many tensor elements.
m$data$parameters$conv1.weight
#> torch_tensor
#> (1,1,.,.) =
#> 0.1090 0.0691 -0.0591 -0.0461 -0.0532
#> -0.0153 0.1228 0.1182 -0.0665 0.0505
#> -0.0303 0.0163 -0.0647 -0.1798 -0.0441
#> -0.0588 0.0846 0.0857 0.0327 0.1972
#> 0.1442 0.0955 -0.1682 -0.0183 0.0960
#>
#> (2,1,.,.) =
#> -0.0650 0.0633 0.0921 -0.0372 0.1392
#> -0.0493 -0.0742 -0.1552 -0.0638 -0.0708
#> -0.0113 -0.1114 -0.0013 -0.0260 -0.0838
#> -0.0292 0.0165 -0.1340 -0.0556 0.0925
#> -0.0394 0.0905 0.1140 -0.1017 0.0363
#>
#> (3,1,.,.) =
#> -0.1762 0.0509 -0.1795 0.1617 -0.1282
#> 0.1735 -0.1951 -0.1044 0.1623 -0.1978
#> 0.1982 -0.1127 -0.1133 -0.0947 -0.0160
#> 0.1135 0.0198 -0.0254 0.0281 -0.0520
#> 0.0794 -0.0114 -0.1520 0.0267 -0.1980
#>
#> (4,1,.,.) =
#> 0.0412 0.0476 0.1843 -0.0444 0.0996
#> 0.0813 0.1186 0.1490 -0.1211 -0.0169
#> 0.0239 0.0793 -0.0484 0.0478 0.1343
#> 0.1461 0.1949 0.0382 0.0634 0.1292
#> 0.0958 0.0273 0.1933 0.0691 0.0905
#>
#> (5,1,.,.) =
#> -0.0724 0.1578 -0.0650 -0.0328 0.1338
#> ... [the output was truncated (use n=-1 to disable)]
#> [ CPUFloatType{20,1,5,5} ][ requires_grad = TRUE ]
It is usual for model parameters to then be passed to an optimiser.
This can also be initialized within a ‘mirai’ process.
optim <- mirai(optim_rmsprop(params = params), params = m$data$parameters)
optim[]
#> <optim_rmsprop>
#> Inherits from: <torch_optimizer>
#> Public:
#> add_param_group: function (param_group)
#> clone: function (deep = FALSE)
#> defaults: list
#> initialize: function (params, lr = 0.01, alpha = 0.99, eps = 1e-08, weight_decay = 0,
#> load_state_dict: function (state_dict, ..., .refer_to_state_dict = FALSE)
#> param_groups: list
#> state: State, R6
#> state_dict: function ()
#> step: function (closure = NULL)
#> zero_grad: function ()
#> Private:
#> step_helper: function (closure, loop_fun)
daemons(0)
#> [1] 0
Above, tensors and complex objects containing tensors were passed seamlessly between host and daemon processes, in the same way as any other R object.
The custom serialization in mirai
leverages R’s own native ‘refhook’ mechanism to allow such completely transparent usage. Designed to be fast and efficient, data copies are minimised and the ‘official’ serialization methods from the torch
package are used directly.