Line data Source code
1 : /*----------------------------------------------------------------------------*/
2 : /* CP2K: A general program to perform molecular dynamics simulations */
3 : /* Copyright 2000-2026 CP2K developers group <https://cp2k.org> */
4 : /* */
5 : /* SPDX-License-Identifier: GPL-2.0-or-later */
6 : /*----------------------------------------------------------------------------*/
7 :
8 : #if defined(__LIBTORCH)
9 :
10 : #include <ATen/Parallel.h>
11 : #if defined(__LIBTORCH_CUDA)
12 : #include <ATen/cuda/CUDAContextLight.h>
13 : #include <c10/cuda/CUDAAllocatorConfig.h>
14 : #endif
15 : #include <c10/core/DeviceGuard.h>
16 : #include <torch/csrc/api/include/torch/cuda.h>
17 : #include <torch/csrc/jit/passes/freeze_module.h>
18 : #include <torch/csrc/jit/passes/inliner.h>
19 : #include <torch/script.h>
20 :
21 : #include "offload/offload_library.h"
22 :
23 : #if defined(__OPENBLAS)
24 : #include <cblas.h>
25 : #endif
26 :
27 : #include <cassert>
28 :
29 : #include <cfenv>
30 : #include <climits>
31 : #include <cstdlib>
32 : #include <cstring>
33 : #include <mutex>
34 : #include <string>
35 : #include <unordered_map>
36 : #include <vector>
37 :
38 : #if defined(__OPENBLAS)
39 : // PyTorch's oneMKL batch ABI is not compatible with OpenBLAS's same-named
40 : // entry points. Expand grouped GEMMs into the portable CBLAS interface.
41 : extern "C" void
42 3888 : cblas_sgemm_batch(const CBLAS_ORDER order, const CBLAS_TRANSPOSE *trans_a,
43 : const CBLAS_TRANSPOSE *trans_b, const int *m, const int *n,
44 : const int *k, const float *alpha, const float **a,
45 : const int *lda, const float **b, const int *ldb,
46 : const float *beta, float **c, const int *ldc,
47 : const int group_count, const int *group_size) {
48 3888 : int offset = 0;
49 7776 : for (int group = 0; group < group_count; ++group) {
50 84888 : for (int operation = 0; operation < group_size[group]; ++operation) {
51 81000 : const int index = offset + operation;
52 81000 : cblas_sgemm(order, trans_a[group], trans_b[group], m[group], n[group],
53 81000 : k[group], alpha[group], a[index], lda[group], b[index],
54 81000 : ldb[group], beta[group], c[index], ldc[group]);
55 : }
56 3888 : offset += group_size[group];
57 : }
58 3888 : }
59 :
60 : extern "C" void
61 15726 : cblas_dgemm_batch(const CBLAS_ORDER order, const CBLAS_TRANSPOSE *trans_a,
62 : const CBLAS_TRANSPOSE *trans_b, const int *m, const int *n,
63 : const int *k, const double *alpha, const double **a,
64 : const int *lda, const double **b, const int *ldb,
65 : const double *beta, double **c, const int *ldc,
66 : const int group_count, const int *group_size) {
67 15726 : int offset = 0;
68 31452 : for (int group = 0; group < group_count; ++group) {
69 271152 : for (int operation = 0; operation < group_size[group]; ++operation) {
70 255426 : const int index = offset + operation;
71 255426 : cblas_dgemm(order, trans_a[group], trans_b[group], m[group], n[group],
72 255426 : k[group], alpha[group], a[index], lda[group], b[index],
73 255426 : ldb[group], beta[group], c[index], ldc[group]);
74 : }
75 15726 : offset += group_size[group];
76 : }
77 15726 : }
78 : #endif
79 :
80 : typedef torch::Tensor torch_c_tensor_t;
81 : typedef c10::Dict<std::string, torch::Tensor> torch_c_dict_t;
82 : typedef torch::jit::Module torch_c_model_t;
83 :
84 : class TorchFloatingPointMaskGuard {
85 : public:
86 369 : TorchFloatingPointMaskGuard() : active_(std::feholdexcept(&env_) == 0) {}
87 369 : ~TorchFloatingPointMaskGuard() {
88 369 : if (active_) {
89 369 : std::feclearexcept(FE_ALL_EXCEPT);
90 369 : std::fesetenv(&env_);
91 : }
92 369 : }
93 :
94 : private:
95 : std::fenv_t env_;
96 : bool active_;
97 : };
98 :
99 : /*******************************************************************************
100 : * \brief Internal helper for selecting the CUDA device when available.
101 : * \author Ole Schuett
102 : ******************************************************************************/
103 : static bool use_cuda_if_available = true;
104 :
105 0 : static void enable_expandable_cuda_segments_if_unconfigured() {
106 : #if defined(__LIBTORCH_CUDA)
107 : static const bool initialized = []() {
108 : const char *legacy_config = std::getenv("PYTORCH_CUDA_ALLOC_CONF");
109 : const char *config = std::getenv("PYTORCH_ALLOC_CONF");
110 : if ((legacy_config == nullptr || legacy_config[0] == '\0') &&
111 : (config == nullptr || config[0] == '\0')) {
112 : c10::cuda::CUDACachingAllocator::setAllocatorSettings(
113 : "expandable_segments:True");
114 : }
115 : return true;
116 : }();
117 : (void)initialized;
118 : #endif
119 0 : }
120 :
121 54 : static bool get_positive_int_env(const char *name, int &value) {
122 54 : const char *raw = std::getenv(name);
123 54 : if (raw == nullptr || raw[0] == '\0') {
124 : return false;
125 : }
126 0 : char *end = nullptr;
127 0 : const long parsed = std::strtol(raw, &end, 10);
128 0 : if (end == raw || *end != '\0' || parsed <= 0 || parsed > INT_MAX) {
129 : return false;
130 : }
131 0 : value = static_cast<int>(parsed);
132 0 : return true;
133 : }
134 :
135 11820 : static void initialize_torch_threads_from_env() {
136 11820 : static bool initialized = false;
137 11820 : if (initialized) {
138 11793 : return;
139 : }
140 27 : initialized = true;
141 :
142 27 : int num_threads = 0;
143 27 : if (get_positive_int_env("CP2K_TORCH_NUM_THREADS", num_threads)) {
144 0 : at::set_num_threads(num_threads);
145 : }
146 27 : if (get_positive_int_env("CP2K_TORCH_NUM_INTEROP_THREADS", num_threads)) {
147 0 : at::set_num_interop_threads(num_threads);
148 : }
149 : }
150 :
151 9138 : static torch::Device get_device() {
152 9138 : initialize_torch_threads_from_env();
153 9138 : if (!use_cuda_if_available || !torch::cuda::is_available()) {
154 9138 : return torch::kCPU;
155 : }
156 0 : enable_expandable_cuda_segments_if_unconfigured();
157 0 : const auto device_count = torch::cuda::device_count();
158 0 : if (device_count <= 0) {
159 0 : return torch::kCPU;
160 : }
161 0 : const int chosen_device = offload_get_chosen_device();
162 0 : const int device = (chosen_device >= 0) ? chosen_device : 0;
163 0 : assert(device < device_count);
164 0 : return torch::Device(torch::kCUDA, device);
165 : }
166 :
167 9138 : static torch::Device get_device_with_guard(c10::OptionalDeviceGuard &guard) {
168 9138 : const auto device = get_device();
169 9138 : if (device.is_cuda()) {
170 0 : guard.reset_device(device);
171 : }
172 9138 : return device;
173 : }
174 :
175 282 : static bool use_batched_gradient_readback(const torch::Tensor &tensor) {
176 : #if defined(__LIBTORCH_CUDA)
177 : return tensor.is_cuda() &&
178 : at::cuda::getDeviceProperties(tensor.device().index())->integrated;
179 : #else
180 282 : (void)tensor;
181 282 : return false;
182 : #endif
183 : }
184 :
185 33 : static void set_jit_fusion_strategy() {
186 : // JIT Fusion strategy optimization, hardcode dynamic 10, see also
187 : // https://github.com/mir-group/pair_nequip_allegro.git
188 33 : torch::jit::FusionStrategy strategy = {
189 33 : {torch::jit::FusionBehavior::DYNAMIC, 10}};
190 66 : torch::jit::setFusionStrategy(strategy);
191 33 : }
192 :
193 74 : static void copy_string_to_c_buffer(const std::string &source, char **content,
194 : int *length) {
195 74 : *length = source.length();
196 74 : *content = (char *)malloc(source.length() + 1); // +1 for null terminator
197 74 : strcpy(*content, source.c_str());
198 74 : }
199 :
200 33 : static bool can_load_directly_to_device(const torch::Device &device) {
201 33 : return !device.is_cuda() || device.index() == 0 ||
202 0 : torch::cuda::device_count() == 1;
203 : }
204 :
205 33 : static torch::jit::Module load_module_for_device(
206 : const char *filename, const torch::Device &device,
207 : std::unordered_map<std::string, std::string> *extra_files = nullptr) {
208 33 : if (can_load_directly_to_device(device)) {
209 33 : if (extra_files != nullptr) {
210 34 : return torch::jit::load(filename, device, *extra_files);
211 : }
212 32 : return torch::jit::load(filename, device);
213 : }
214 0 : auto model = (extra_files != nullptr)
215 0 : ? torch::jit::load(filename, torch::kCPU, *extra_files)
216 0 : : torch::jit::load(filename, torch::kCPU);
217 0 : model.to(device);
218 0 : return model;
219 0 : }
220 :
221 0 : static void remap_device_constants(torch::jit::Block *block,
222 : const torch::Device &device) {
223 0 : for (torch::jit::Node *node : block->nodes()) {
224 0 : if (node->kind() == torch::jit::prim::Constant &&
225 0 : node->outputs().size() == 1 &&
226 0 : node->output()->type()->kind() == c10::TypeKind::DeviceObjType &&
227 0 : node->hasAttribute(torch::jit::attr::value) &&
228 0 : node->kindOf(torch::jit::attr::value) == torch::jit::AttributeKind::s) {
229 0 : node->s_(torch::jit::attr::value, device.str());
230 : }
231 0 : bool has_tensor_input = false;
232 0 : for (const torch::jit::Value *input : node->inputs()) {
233 0 : has_tensor_input |= input->type()->kind() == c10::TypeKind::TensorType;
234 : }
235 0 : const auto *schema = node->maybeSchema();
236 0 : if (!has_tensor_input && schema != nullptr) {
237 : const auto &arguments = schema->arguments();
238 0 : for (size_t i = 0; i < arguments.size() && i < node->inputs().size();
239 : ++i) {
240 0 : const auto input_value = torch::jit::toIValue(node->input(i));
241 0 : if (arguments[i].name() == "device" && input_value.has_value() &&
242 0 : input_value->isNone()) {
243 0 : torch::jit::WithInsertPoint insertion_guard(node);
244 0 : auto *device_value =
245 0 : node->owningGraph()->insertConstant(torch::jit::IValue(device));
246 0 : node->replaceInput(i, device_value);
247 0 : }
248 0 : }
249 : }
250 0 : for (torch::jit::Block *nested_block : node->blocks()) {
251 0 : remap_device_constants(nested_block, device);
252 : }
253 : }
254 0 : }
255 :
256 0 : static void remap_model_device_constants(torch::jit::Module &model,
257 : const torch::Device &device) {
258 0 : for (const auto &attribute : model.named_attributes(false)) {
259 0 : const auto &value = attribute.value;
260 0 : if (value.isTensor()) {
261 0 : model.setattr(attribute.name, value.toTensor().to(device));
262 0 : } else if (value.isTensorList()) {
263 0 : auto tensors = value.toTensorList();
264 0 : for (size_t i = 0; i < tensors.size(); ++i) {
265 0 : tensors.set(i, tensors.get(i).to(device));
266 : }
267 0 : model.setattr(attribute.name, tensors);
268 0 : }
269 0 : }
270 0 : for (const auto &method : model.get_methods()) {
271 0 : remap_device_constants(method.graph()->block(), device);
272 0 : }
273 0 : for (auto child : model.children()) {
274 0 : remap_model_device_constants(child, device);
275 0 : }
276 0 : }
277 :
278 4454 : static void promote_float32_constants(torch::jit::Block *block) {
279 172703 : for (torch::jit::Node *node : block->nodes()) {
280 201926 : if (node->kind() == torch::jit::prim::Constant &&
281 198186 : node->hasAttribute(torch::jit::attr::value) &&
282 29937 : node->kindOf(torch::jit::attr::value) == torch::jit::AttributeKind::t) {
283 1802 : const auto tensor = node->t(torch::jit::attr::value);
284 1802 : if (tensor.defined() && tensor.scalar_type() == torch::kFloat32) {
285 0 : const auto promoted = tensor.to(torch::kFloat64);
286 0 : node->t_(torch::jit::attr::value, promoted);
287 0 : node->output()->setType(c10::TensorType::create(promoted));
288 0 : }
289 1802 : }
290 168249 : const auto *schema = node->maybeSchema();
291 168249 : if (schema != nullptr) {
292 : const auto &arguments = schema->arguments();
293 429896 : for (size_t i = 0; i < arguments.size() && i < node->inputs().size();
294 : ++i) {
295 181543 : const auto value = torch::jit::toIValue(node->input(i));
296 181543 : if (arguments[i].name() == "dtype" && value.has_value() &&
297 186116 : value->isInt() &&
298 1870 : value->toInt() == static_cast<int64_t>(torch::kFloat32)) {
299 : // A dtype constant can also be used as a dimension or an index.
300 : // Replace this edge only; leave other uses and integer tensors
301 : // intact.
302 1224 : torch::jit::WithInsertPoint insertion_guard(node);
303 1224 : auto *dtype = node->owningGraph()->insertConstant(
304 2448 : static_cast<int64_t>(torch::kFloat64));
305 1224 : node->replaceInput(i, dtype);
306 1224 : }
307 181543 : }
308 : }
309 168249 : for (torch::jit::Block *nested : node->blocks()) {
310 0 : promote_float32_constants(nested);
311 : }
312 : }
313 4454 : }
314 :
315 2907 : static void promote_model_float32(torch::jit::Module &model) {
316 13923 : for (const auto &attribute : model.named_attributes(false)) {
317 11016 : const auto &value = attribute.value;
318 13328 : if (value.isTensor() && value.toTensor().defined() &&
319 2312 : value.toTensor().scalar_type() == torch::kFloat32) {
320 4182 : model.setattr(attribute.name, value.toTensor().to(torch::kFloat64));
321 8925 : } else if (value.isTensorList()) {
322 0 : auto tensors = value.toTensorList();
323 0 : for (size_t i = 0; i < tensors.size(); ++i) {
324 0 : if (tensors.get(i).defined() &&
325 0 : tensors.get(i).scalar_type() == torch::kFloat32) {
326 0 : tensors.set(i, tensors.get(i).to(torch::kFloat64));
327 : }
328 : }
329 0 : model.setattr(attribute.name, tensors);
330 0 : }
331 13923 : }
332 7361 : for (const auto &method : model.get_methods()) {
333 4454 : torch::jit::Inline(*method.graph());
334 8908 : promote_float32_constants(method.graph()->block());
335 2907 : }
336 8687 : for (auto child : model.children()) {
337 2890 : promote_model_float32(child);
338 5797 : }
339 2907 : }
340 :
341 0 : static void initialize_cuda_linalg(const torch::Device &device) {
342 0 : static std::once_flag flag;
343 0 : std::call_once(flag, [&]() {
344 0 : const auto options =
345 0 : torch::TensorOptions().dtype(torch::kFloat32).device(device);
346 0 : static_cast<void>(at::linalg_eigh(torch::eye(1, options)));
347 0 : });
348 0 : }
349 :
350 : /*******************************************************************************
351 : * \brief Internal helper for creating a Torch tensor from an array.
352 : * \author Ole Schuett
353 : ******************************************************************************/
354 2682 : static torch_c_tensor_t *tensor_from_array(const torch::Dtype dtype,
355 : const bool req_grad, const int ndims,
356 : const int64_t sizes[],
357 : void *source) {
358 2682 : initialize_torch_threads_from_env();
359 2682 : const auto opts = torch::TensorOptions().dtype(dtype).requires_grad(req_grad);
360 2682 : const auto sizes_ref = c10::IntArrayRef(sizes, ndims);
361 2682 : return new torch_c_tensor_t(torch::from_blob(source, sizes_ref, opts));
362 : }
363 :
364 144 : static bool tensor_matches(const torch_c_tensor_t *tensor,
365 : const torch::Dtype dtype,
366 : const torch::Device &device, const int ndims,
367 : const int64_t sizes[]) {
368 48 : if (tensor == nullptr || !tensor->defined() ||
369 192 : tensor->scalar_type() != dtype || tensor->device() != device ||
370 24 : tensor->ndimension() != ndims) {
371 120 : return false;
372 : }
373 80 : for (int i = 0; i < ndims; i++) {
374 56 : if (tensor->size(i) != sizes[i]) {
375 : return false;
376 : }
377 : }
378 24 : return tensor->is_contiguous();
379 : }
380 :
381 144 : static void reset_tensor_from_array_double(torch_c_tensor_t **tensor,
382 : const bool req_grad, const int ndims,
383 : const int64_t sizes[],
384 : double source[]) {
385 144 : c10::OptionalDeviceGuard guard;
386 144 : const auto device = get_device_with_guard(guard);
387 144 : const auto sizes_ref = c10::IntArrayRef(sizes, ndims);
388 144 : if (!tensor_matches(*tensor, torch::kFloat64, device, ndims, sizes)) {
389 120 : delete (*tensor);
390 120 : const auto opts =
391 120 : torch::TensorOptions().dtype(torch::kFloat64).device(device);
392 240 : *tensor = new torch_c_tensor_t(torch::empty(sizes_ref, opts).detach());
393 : }
394 144 : const auto source_tensor = torch::from_blob(
395 144 : source, sizes_ref, torch::TensorOptions().dtype(torch::kFloat64));
396 144 : {
397 144 : torch::NoGradGuard no_grad;
398 144 : (*tensor)->copy_(source_tensor);
399 144 : (*tensor)->mutable_grad() = torch::Tensor();
400 0 : }
401 288 : (*tensor)->set_requires_grad(req_grad);
402 144 : }
403 :
404 : /*******************************************************************************
405 : * \brief Internal helper for getting the data_ptr and sizes of a Torch tensor.
406 : * \author Ole Schuett
407 : ******************************************************************************/
408 1180 : static void *get_data_ptr(const torch_c_tensor_t *tensor,
409 : const torch::Dtype dtype, const int ndims,
410 : int64_t sizes[]) {
411 1180 : assert(tensor->scalar_type() == dtype);
412 1180 : assert(tensor->ndimension() == ndims);
413 3746 : for (int i = 0; i < ndims; i++) {
414 2566 : sizes[i] = tensor->size(i);
415 : }
416 :
417 1180 : assert(tensor->is_contiguous());
418 1180 : return tensor->data_ptr();
419 : };
420 :
421 : #ifdef __cplusplus
422 : extern "C" {
423 : #endif
424 :
425 : /*******************************************************************************
426 : * \brief Creates a Torch tensor from an array of int32s.
427 : * The passed array has to outlive the tensor!
428 : * \author Ole Schuett
429 : ******************************************************************************/
430 0 : void torch_c_tensor_from_array_int32(torch_c_tensor_t **tensor,
431 : const bool req_grad, const int ndims,
432 : const int64_t sizes[], int32_t source[]) {
433 0 : *tensor = tensor_from_array(torch::kInt32, req_grad, ndims, sizes, source);
434 0 : }
435 :
436 : /*******************************************************************************
437 : * \brief Creates a Torch tensor from an array of floats.
438 : * The passed array has to outlive the tensor!
439 : * \author Ole Schuett
440 : ******************************************************************************/
441 66 : void torch_c_tensor_from_array_float(torch_c_tensor_t **tensor,
442 : const bool req_grad, const int ndims,
443 : const int64_t sizes[], float source[]) {
444 66 : *tensor = tensor_from_array(torch::kFloat32, req_grad, ndims, sizes, source);
445 66 : }
446 :
447 : /*******************************************************************************
448 : * \brief Creates a Torch tensor from an array of int64s.
449 : * The passed array has to outlive the tensor!
450 : * \author Ole Schuett
451 : ******************************************************************************/
452 770 : void torch_c_tensor_from_array_int64(torch_c_tensor_t **tensor,
453 : const bool req_grad, const int ndims,
454 : const int64_t sizes[], int64_t source[]) {
455 770 : *tensor = tensor_from_array(torch::kInt64, req_grad, ndims, sizes, source);
456 770 : }
457 :
458 : /*******************************************************************************
459 : * \brief Creates a Torch tensor from an array of doubles.
460 : * The passed array has to outlive the tensor!
461 : * \author Ole Schuett
462 : ******************************************************************************/
463 1846 : void torch_c_tensor_from_array_double(torch_c_tensor_t **tensor,
464 : const bool req_grad, const int ndims,
465 : const int64_t sizes[], double source[]) {
466 1846 : *tensor = tensor_from_array(torch::kFloat64, req_grad, ndims, sizes, source);
467 1846 : }
468 :
469 : /*******************************************************************************
470 : * \brief Releases a string returned from the Torch C API.
471 : ******************************************************************************/
472 74 : void torch_c_free_string(char *content) { free(content); }
473 :
474 : /*******************************************************************************
475 : * \brief Reuses or creates a device tensor and copies double data into it.
476 : ******************************************************************************/
477 144 : void torch_c_tensor_reset_from_array_double(torch_c_tensor_t **tensor,
478 : const bool req_grad,
479 : const int ndims,
480 : const int64_t sizes[],
481 : double source[]) {
482 144 : reset_tensor_from_array_double(tensor, req_grad, ndims, sizes, source);
483 144 : }
484 :
485 : /*******************************************************************************
486 : * \brief Creates an expanded tensor view along one singleton dimension.
487 : ******************************************************************************/
488 96 : void torch_c_tensor_expand_dim(const torch_c_tensor_t *tensor,
489 : const int64_t dim, const int64_t size,
490 : torch_c_tensor_t **result) {
491 96 : c10::OptionalDeviceGuard guard;
492 96 : get_device_with_guard(guard);
493 96 : assert(*result == NULL);
494 96 : assert(dim >= 0);
495 96 : assert(dim < tensor->dim());
496 96 : std::vector<int64_t> sizes(tensor->sizes().begin(), tensor->sizes().end());
497 96 : assert(sizes[dim] == 1);
498 96 : sizes[dim] = size;
499 96 : *result = new torch_c_tensor_t(tensor->expand(sizes));
500 96 : }
501 :
502 : /*******************************************************************************
503 : * \brief Creates a tensor view narrowed along one dimension.
504 : ******************************************************************************/
505 1872 : void torch_c_tensor_narrow(const torch_c_tensor_t *tensor, const int64_t dim,
506 : const int64_t start_index, const int64_t length,
507 : torch_c_tensor_t **result) {
508 1872 : c10::OptionalDeviceGuard guard;
509 1872 : const auto device = get_device_with_guard(guard);
510 1872 : assert(*result == NULL);
511 1872 : assert(dim >= 0);
512 1872 : assert(start_index >= 0);
513 1872 : assert(length >= 0);
514 1872 : assert(dim < tensor->ndimension());
515 1872 : assert(start_index + length <= tensor->size(dim));
516 3744 : *result =
517 1872 : new torch_c_tensor_t(tensor->narrow(dim, start_index, length).to(device));
518 1872 : }
519 :
520 : /*******************************************************************************
521 : * \brief Returns the data_ptr and sizes of a Torch tensor of int32s.
522 : * The returned pointer is only valide during the tensor's live time!
523 : * \author Ole Schuett
524 : ******************************************************************************/
525 0 : void torch_c_tensor_data_ptr_int32(const torch_c_tensor_t *tensor,
526 : const int ndims, int64_t sizes[],
527 : int32_t **data_ptr) {
528 0 : *data_ptr = (int32_t *)get_data_ptr(tensor, torch::kInt32, ndims, sizes);
529 0 : }
530 :
531 : /*******************************************************************************
532 : * \brief Returns the data_ptr and sizes of a Torch tensor of floats.
533 : * The returned pointer is only valide during the tensor's lifetime!
534 : * \author Ole Schuett
535 : ******************************************************************************/
536 66 : void torch_c_tensor_data_ptr_float(const torch_c_tensor_t *tensor,
537 : const int ndims, int64_t sizes[],
538 : float **data_ptr) {
539 66 : *data_ptr = (float *)get_data_ptr(tensor, torch::kFloat32, ndims, sizes);
540 66 : }
541 :
542 : /*******************************************************************************
543 : * \brief Returns the data_ptr and sizes of a Torch tensor of int64s.
544 : * The returned pointer is only valide during the tensor's live time!
545 : * \author Ole Schuett
546 : ******************************************************************************/
547 0 : void torch_c_tensor_data_ptr_int64(const torch_c_tensor_t *tensor,
548 : const int ndims, int64_t sizes[],
549 : int64_t **data_ptr) {
550 0 : *data_ptr = (int64_t *)get_data_ptr(tensor, torch::kInt64, ndims, sizes);
551 0 : }
552 :
553 : /*******************************************************************************
554 : * \brief Returns the data_ptr and sizes of a Torch tensor of doubles.
555 : * The returned pointer is only valide during the tensor's live time!
556 : * \author Ole Schuett
557 : ******************************************************************************/
558 1114 : void torch_c_tensor_data_ptr_double(const torch_c_tensor_t *tensor,
559 : const int ndims, int64_t sizes[],
560 : double **data_ptr) {
561 1114 : *data_ptr = (double *)get_data_ptr(tensor, torch::kFloat64, ndims, sizes);
562 1114 : }
563 :
564 : /*******************************************************************************
565 : * \brief Runs autograd on a Torch tensor.
566 : * \author Ole Schuett
567 : ******************************************************************************/
568 6 : void torch_c_tensor_backward(const torch_c_tensor_t *tensor,
569 : const torch_c_tensor_t *outer_grad) {
570 6 : TorchFloatingPointMaskGuard fpe_guard;
571 6 : c10::OptionalDeviceGuard guard;
572 6 : get_device_with_guard(guard);
573 6 : tensor->backward(*outer_grad);
574 6 : }
575 :
576 : /*******************************************************************************
577 : * \brief Runs autograd on a scalar Torch tensor.
578 : ******************************************************************************/
579 284 : void torch_c_tensor_backward_scalar(const torch_c_tensor_t *tensor) {
580 284 : TorchFloatingPointMaskGuard fpe_guard;
581 284 : c10::OptionalDeviceGuard guard;
582 284 : get_device_with_guard(guard);
583 568 : tensor->backward();
584 284 : }
585 :
586 : /*******************************************************************************
587 : * \brief Moves a tensor to the active device and makes it an autograd leaf.
588 : ******************************************************************************/
589 2420 : void torch_c_tensor_to_device_leaf(torch_c_tensor_t **tensor,
590 : const bool req_grad) {
591 2420 : c10::OptionalDeviceGuard guard;
592 2420 : const auto device = get_device_with_guard(guard);
593 4840 : auto moved = (*tensor)->to(device).detach();
594 2420 : moved.set_requires_grad(req_grad);
595 4840 : delete (*tensor);
596 4840 : *tensor = new torch_c_tensor_t(moved);
597 2420 : }
598 :
599 : /*******************************************************************************
600 : * \brief Select whether Torch wrappers should use CUDA when available.
601 : ******************************************************************************/
602 608 : void torch_c_use_cuda(const bool use_cuda) { use_cuda_if_available = use_cuda; }
603 :
604 : /*******************************************************************************
605 : * \brief Returns the gradient of a Torch tensor which was computed by autograd.
606 : * \author Ole Schuett
607 : ******************************************************************************/
608 256 : void torch_c_tensor_grad(const torch_c_tensor_t *tensor,
609 : torch_c_tensor_t **grad) {
610 256 : c10::OptionalDeviceGuard guard;
611 256 : get_device_with_guard(guard);
612 256 : const torch::Tensor maybe_grad = tensor->grad();
613 256 : assert(maybe_grad.defined());
614 512 : torch::Tensor host_grad = maybe_grad.detach().cpu().contiguous();
615 256 : if (maybe_grad.is_cpu()) {
616 256 : host_grad = host_grad.clone();
617 : }
618 256 : *grad = new torch_c_tensor_t(std::move(host_grad));
619 256 : }
620 :
621 : /*******************************************************************************
622 : * \brief Copies three autograd gradients to CPU memory.
623 : ******************************************************************************/
624 282 : void torch_c_tensor_grad_batch3(const torch_c_tensor_t *tensor1,
625 : const torch_c_tensor_t *tensor2,
626 : const torch_c_tensor_t *tensor3,
627 : torch_c_tensor_t **grad1,
628 : torch_c_tensor_t **grad2,
629 : torch_c_tensor_t **grad3) {
630 282 : c10::OptionalDeviceGuard guard;
631 282 : const auto device = get_device_with_guard(guard);
632 282 : assert(*grad1 == nullptr && *grad2 == nullptr && *grad3 == nullptr);
633 :
634 282 : const torch::Tensor source1 = tensor1->grad();
635 282 : const torch::Tensor source2 = tensor2->grad();
636 282 : const torch::Tensor source3 = tensor3->grad();
637 282 : assert(source1.defined() && source2.defined() && source3.defined());
638 282 : assert(source1.device() == source2.device());
639 282 : assert(source1.device() == source3.device());
640 282 : if (use_batched_gradient_readback(source1)) {
641 0 : auto host1 =
642 : torch::empty(source1.sizes(),
643 0 : source1.options().device(torch::kCPU).pinned_memory(true));
644 0 : auto host2 =
645 : torch::empty(source2.sizes(),
646 0 : source2.options().device(torch::kCPU).pinned_memory(true));
647 0 : auto host3 =
648 : torch::empty(source3.sizes(),
649 0 : source3.options().device(torch::kCPU).pinned_memory(true));
650 0 : host1.copy_(source1, true);
651 0 : host2.copy_(source2, true);
652 0 : host3.copy_(source3, true);
653 0 : torch::cuda::synchronize(device.index());
654 0 : *grad1 = new torch_c_tensor_t(std::move(host1));
655 0 : *grad2 = new torch_c_tensor_t(std::move(host2));
656 0 : *grad3 = new torch_c_tensor_t(std::move(host3));
657 0 : } else {
658 : // Materialize independent host buffers instead of aliasing gradients owned
659 : // by the autograd graph when they are already contiguous CPU tensors.
660 282 : *grad1 = new torch_c_tensor_t(source1.detach().cpu().contiguous().clone());
661 282 : *grad2 = new torch_c_tensor_t(source2.detach().cpu().contiguous().clone());
662 282 : *grad3 = new torch_c_tensor_t(source3.detach().cpu().contiguous().clone());
663 : }
664 282 : }
665 :
666 : /*******************************************************************************
667 : * \brief Releases a Torch tensor and all its ressources.
668 : * \author Ole Schuett
669 : ******************************************************************************/
670 12756 : void torch_c_tensor_release(torch_c_tensor_t *tensor) { delete (tensor); }
671 :
672 : /*******************************************************************************
673 : * \brief Creates an empty Torch dictionary.
674 : * \author Ole Schuett
675 : ******************************************************************************/
676 410 : void torch_c_dict_create(torch_c_dict_t **dict_out) {
677 410 : assert(*dict_out == NULL);
678 410 : *dict_out = new c10::Dict<std::string, torch::Tensor>();
679 410 : }
680 :
681 : /*******************************************************************************
682 : * \brief Clones a Torch dictionary.
683 : ******************************************************************************/
684 34 : void torch_c_dict_clone(const torch_c_dict_t *dict, torch_c_dict_t **dict_out) {
685 34 : assert(*dict_out == NULL);
686 34 : torch_c_dict_t *clone = new c10::Dict<std::string, torch::Tensor>();
687 204 : for (const auto &entry : *dict) {
688 170 : clone->insert(entry.key(), entry.value());
689 : }
690 34 : *dict_out = clone;
691 34 : }
692 :
693 : /*******************************************************************************
694 : * \brief Inserts a Torch tensor into a Torch dictionary.
695 : * \author Ole Schuett
696 : ******************************************************************************/
697 2806 : void torch_c_dict_insert(const torch_c_dict_t *dict, const char *key,
698 : const torch_c_tensor_t *tensor) {
699 2806 : c10::OptionalDeviceGuard guard;
700 2806 : const auto device = get_device_with_guard(guard);
701 5612 : dict->insert(key, tensor->to(device));
702 2806 : }
703 :
704 : /*******************************************************************************
705 : * \brief Retrieves a Torch tensor from a Torch dictionary.
706 : * \author Ole Schuett
707 : ******************************************************************************/
708 76 : void torch_c_dict_get(const torch_c_dict_t *dict, const char *key,
709 : torch_c_tensor_t **tensor) {
710 76 : assert(dict->contains(key));
711 76 : *tensor = new torch_c_tensor_t(dict->at(key).cpu().contiguous());
712 76 : }
713 :
714 : /*******************************************************************************
715 : * \brief Releases a Torch dictionary and all its ressources.
716 : * \author Ole Schuett
717 : ******************************************************************************/
718 844 : void torch_c_dict_release(torch_c_dict_t *dict) { delete (dict); }
719 :
720 : /*******************************************************************************
721 : * \brief Loads a Torch model from given "*.pth" file.
722 : * In Torch lingo models are called modules.
723 : * \author Ole Schuett
724 : ******************************************************************************/
725 16 : void torch_c_model_load(torch_c_model_t **model_out, const char *filename) {
726 16 : assert(*model_out == NULL);
727 16 : c10::OptionalDeviceGuard guard;
728 16 : const auto device = get_device_with_guard(guard);
729 16 : set_jit_fusion_strategy();
730 16 : torch::jit::Module *model = new torch::jit::Module();
731 16 : *model = load_module_for_device(filename, device);
732 16 : model->eval(); // Set inference behavior for modules such as dropout.
733 16 : *model_out = model;
734 16 : }
735 :
736 : /*******************************************************************************
737 : * \brief Loads a Torch model and reads two metadata entries.
738 : ******************************************************************************/
739 17 : void torch_c_model_load_with_metadata(torch_c_model_t **model_out,
740 : const char *filename, const char *key1,
741 : const char *key2, char **content1,
742 : int *length1, char **content2,
743 : int *length2) {
744 17 : assert(*model_out == NULL);
745 17 : c10::OptionalDeviceGuard guard;
746 17 : const auto device = get_device_with_guard(guard);
747 17 : std::unordered_map<std::string, std::string> extra_files = {{key1, ""},
748 68 : {key2, ""}};
749 17 : set_jit_fusion_strategy();
750 17 : torch::jit::Module *model = new torch::jit::Module();
751 17 : *model = load_module_for_device(filename, device, &extra_files);
752 17 : model->eval(); // Set inference behavior for modules such as dropout.
753 17 : *model_out = model;
754 34 : copy_string_to_c_buffer(extra_files[key1], content1, length1);
755 34 : copy_string_to_c_buffer(extra_files[key2], content2, length2);
756 34 : }
757 :
758 : /*******************************************************************************
759 : * \brief Maps serialized TorchScript device constants to the active device.
760 : ******************************************************************************/
761 25 : void torch_c_model_remap_device_constants(torch_c_model_t *model) {
762 25 : c10::OptionalDeviceGuard guard;
763 25 : const auto device = get_device_with_guard(guard);
764 25 : if (device.is_cuda()) {
765 0 : remap_model_device_constants(*model, device);
766 0 : initialize_cuda_linalg(device);
767 : }
768 25 : }
769 :
770 : /*******************************************************************************
771 : * \brief Promote float32 model state and explicit numerical casts to float64.
772 : * Trained values are retained exactly; integer/index operations are
773 : * kept.
774 : ******************************************************************************/
775 17 : void torch_c_model_promote_float32(torch_c_model_t *model) {
776 17 : TorchFloatingPointMaskGuard fpe_guard;
777 17 : torch::NoGradGuard no_grad;
778 17 : promote_model_float32(*model);
779 17 : }
780 :
781 : /*******************************************************************************
782 : * \brief Disables gradients for inference-only model parameters.
783 : ******************************************************************************/
784 17 : void torch_c_model_disable_parameter_gradients(torch_c_model_t *model) {
785 17 : torch::NoGradGuard no_grad;
786 1377 : for (auto parameter : model->parameters()) {
787 2720 : parameter.set_requires_grad(false);
788 1377 : }
789 17 : }
790 :
791 : /*******************************************************************************
792 : * \brief Evaluates the given Torch model.
793 : * \author Ole Schuett
794 : ******************************************************************************/
795 62 : void torch_c_model_forward(torch_c_model_t *model, const torch_c_dict_t *inputs,
796 : torch_c_dict_t *outputs) {
797 :
798 62 : TorchFloatingPointMaskGuard fpe_guard;
799 62 : c10::OptionalDeviceGuard guard;
800 62 : get_device_with_guard(guard);
801 310 : auto untyped_output = model->forward({*inputs}).toGenericDict();
802 62 : outputs->clear();
803 232 : for (const auto &entry : untyped_output) {
804 170 : outputs->insert(entry.key().toStringView(), entry.value().toTensor());
805 : }
806 186 : }
807 :
808 : /*******************************************************************************
809 : * \brief Evaluates a TorchScript model method expecting argument "mol".
810 : ******************************************************************************/
811 284 : void torch_c_model_forward_mol_tensor(torch_c_model_t *model,
812 : const char *method_name,
813 : const torch_c_dict_t *inputs,
814 : torch_c_tensor_t **output) {
815 :
816 284 : c10::OptionalDeviceGuard guard;
817 284 : get_device_with_guard(guard);
818 284 : assert(*output == NULL);
819 284 : *output = new torch_c_tensor_t(
820 1420 : model->get_method(method_name)({*inputs}).toTensor());
821 852 : }
822 :
823 : /*******************************************************************************
824 : * \brief Returns the weighted sum of two Torch tensors.
825 : ******************************************************************************/
826 284 : void torch_c_tensor_weighted_sum(const torch_c_tensor_t *values,
827 : const torch_c_tensor_t *weights,
828 : torch_c_tensor_t **result) {
829 284 : c10::OptionalDeviceGuard guard;
830 284 : get_device_with_guard(guard);
831 284 : const auto weights_on_device = weights->to(values->device());
832 568 : *result = new torch_c_tensor_t((*values * weights_on_device).sum());
833 284 : }
834 :
835 : /*******************************************************************************
836 : * \brief Returns a scalar double value from a Torch tensor.
837 : ******************************************************************************/
838 284 : double torch_c_tensor_item_double(const torch_c_tensor_t *tensor) {
839 284 : c10::OptionalDeviceGuard guard;
840 284 : get_device_with_guard(guard);
841 284 : return tensor->item<double>();
842 284 : }
843 :
844 : /*******************************************************************************
845 : * \brief Releases a Torch model and all its ressources.
846 : * \author Ole Schuett
847 : ******************************************************************************/
848 16 : void torch_c_model_release(torch_c_model_t *model) { delete (model); }
849 :
850 : /*******************************************************************************
851 : * \brief Reads metadata entry from given "*.pth" file.
852 : * In Torch lingo they are called extra files.
853 : * The returned char array has to be deallocated by caller!
854 : * \author Ole Schuett
855 : ******************************************************************************/
856 40 : void torch_c_model_read_metadata(const char *filename, const char *key,
857 : char **content, int *length) {
858 :
859 120 : std::unordered_map<std::string, std::string> extra_files = {{key, ""}};
860 40 : torch::jit::load(filename, torch::kCPU, extra_files);
861 80 : const std::string &content_str = extra_files[key];
862 40 : copy_string_to_c_buffer(content_str, content, length);
863 80 : }
864 :
865 : /*******************************************************************************
866 : * \brief Returns true iff the Torch CUDA backend is available.
867 : * \author Ole Schuett
868 : ******************************************************************************/
869 2 : bool torch_c_cuda_is_available() { return torch::cuda::is_available(); }
870 :
871 : /*******************************************************************************
872 : * \brief Return the number of CUDA devices visible to Torch.
873 : ******************************************************************************/
874 0 : int torch_c_cuda_device_count() {
875 0 : return torch::cuda::is_available() ? torch::cuda::device_count() : 0;
876 : }
877 :
878 : /*******************************************************************************
879 : * \brief Set whether to allow TF32.
880 : * Needed due to changes in defaults from pytorch 1.7 to 1.11 to >=1.12
881 : * See https://pytorch.org/docs/stable/notes/cuda.html
882 : * \author Gabriele Tocci
883 : ******************************************************************************/
884 6 : void torch_c_allow_tf32(const bool allow_tf32) {
885 :
886 6 : at::globalContext().setAllowTF32CuBLAS(allow_tf32);
887 6 : at::globalContext().setAllowTF32CuDNN(allow_tf32);
888 6 : }
889 :
890 : /******************************************************************************
891 : * \brief Freeze the Torch model: generic optimization that speeds up model.
892 : * See https://pytorch.org/docs/stable/generated/torch.jit.freeze.html
893 : * \author Gabriele Tocci
894 : ******************************************************************************/
895 6 : void torch_c_model_freeze(torch_c_model_t *model) {
896 :
897 6 : *model = torch::jit::freeze(*model);
898 6 : }
899 :
900 : /******************************************************************************
901 : * \brief Freeze a Torch model while preserving one exported method.
902 : ******************************************************************************/
903 17 : void torch_c_model_freeze_preserving_method(torch_c_model_t *model,
904 : const char *method_name) {
905 :
906 34 : const std::vector<std::string> preserved_methods = {method_name};
907 17 : torch::jit::freeze_module_inplace(model, preserved_methods);
908 34 : }
909 :
910 : /*******************************************************************************
911 : * \brief Retrieves an int64 attribute. Must be called before model freeze.
912 : * \author Ole Schuett
913 : ******************************************************************************/
914 40 : void torch_c_model_get_attr_int64(const torch_c_model_t *model, const char *key,
915 : int64_t *dest) {
916 40 : *dest = model->attr(key).toInt();
917 40 : }
918 :
919 : /*******************************************************************************
920 : * \brief Retrieves a double attribute. Must be called before model freeze.
921 : * \author Ole Schuett
922 : ******************************************************************************/
923 8 : void torch_c_model_get_attr_double(const torch_c_model_t *model,
924 : const char *key, double *dest) {
925 8 : *dest = model->attr(key).toDouble();
926 8 : }
927 :
928 : /*******************************************************************************
929 : * \brief Retrieves a string attribute. Must be called before model freeze.
930 : * \author Ole Schuett
931 : ******************************************************************************/
932 16 : void torch_c_model_get_attr_string(const torch_c_model_t *model,
933 : const char *key, char *dest) {
934 16 : const std::string &str = model->attr(key).toStringRef();
935 16 : assert(str.size() < 80); // default_string_length
936 144 : for (int i = 0; i < str.size(); i++) {
937 128 : dest[i] = str[i];
938 : }
939 16 : }
940 :
941 : /*******************************************************************************
942 : * \brief Retrieves a list attribute's size. Must be called before model freeze.
943 : * \author Ole Schuett
944 : ******************************************************************************/
945 8 : void torch_c_model_get_attr_list_size(const torch_c_model_t *model,
946 : const char *key, int *size) {
947 8 : *size = model->attr(key).toList().size();
948 8 : }
949 :
950 : /*******************************************************************************
951 : * \brief Retrieves a single item from a string list attribute.
952 : * \author Ole Schuett
953 : ******************************************************************************/
954 16 : void torch_c_model_get_attr_strlist(const torch_c_model_t *model,
955 : const char *key, const int index,
956 : char *dest) {
957 32 : const auto list = model->attr(key).toList();
958 16 : const std::string &str = list[index].toStringRef();
959 16 : assert(str.size() < 80); // default_string_length
960 32 : for (int i = 0; i < str.size(); i++) {
961 16 : dest[i] = str[i];
962 : }
963 16 : }
964 :
965 : #ifdef __cplusplus
966 : }
967 : #endif
968 :
969 : #endif // defined(__LIBTORCH)
970 :
971 : // EOF
|