Пишем на Rust + CUDA C +57




Всем привет!

В данном руководстве хочу рассказать как подружить CUDA C/С++ и Rust. И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU с использованием CUDA C.

Кому интересно под кат!

CUDA C


Первым делом необходимо поставить компилятор CUDA — nvcc. Что такое CUDA и для чего это нужно описывать не буду, об этом можно почитать например тут. Скажу лишь что с её помощью можно писать код, который будет запускаться на NVIDIA видеокартах (в дальнейшем — GPU) и использовать всю их мощь для параллельных вычислений и обработки графики. Еще раз повторюсь, данный туториал не о том как писать код на CUDA, а о том как и из кода на Rust пользоваться её преимуществами и писать параллельные вычисления на GPU.

Итак устанавливаем nvcc и CUDA Toolkit. С этим сложностей не должно возникнуть подробная инструкция: на офф сайте.

RUST + CUDA C


В ходе данного туториала, как уже говорилось ранее, будем писать программу на Rust для нахождения скалярного произведения двух векторов, сам процесс вычисления будет происходить на GPU.

Скалярное произведение двух векторов.
Пусть у нас есть два вектора: $a=[a_1,a_2,...a_n]$ и $b=[b_1,b_2,...,b_n]$, скалярное произведение этих векторов:

$a \cdot b = \sum_{i=1}^{n}{a_ib_i}$



Начнем создание нашей программы. Далее я предполагаю, что nvcc успешно установлен, так же стоят rustc и cargo для компиляции rust кода.

Первым делом создадим папку проекта. В папке проекта создадим файл Cargo.toml, в котором находятся инструкции для сборщика cargo. Файл выглядит таким образом:

[package]
name = "rust_cuda" # название программы
version = "0.1.0" # версия программы
authors = ["MoonL1ght <ixav1@icloud.com>"] # информация об авторе
build = "build.rs" # скрипт для сборки rust
links = "cudart" # библиотека cuda, которая линкуется динамически

[dependencies]
libc = "0.2" # библиотека rust для работы С кодом 
rand = "0.5.5" # библиотека rust для работы с случайными величинами

[build-dependencies]
cc = "1.0" # rust пакет для сборки С кода

Так же в корневой папке проекта создаем файл build.rs в котором будут находится инструкции для сборки программы на rust и компиляции CUDA C кода.

В корень проекта добавим папку src в которую поместим файлы с исходным кодом. В папке src создадим четыре файла: main.rs — код основной программы, dot.cpp — С++ binding (обертка для CUDA C), dot_gpu.h, dot_gpu.cu — файл в котором содержится код выполняемый на GPU.

Итого имеем такую структура проекта:

rust-cuda/
    src/
        main.rs
        dot.cpp
        dot_gpu.h
        dot_gpu.cu
    Cargo.toml
    build.rs

В файле build.rs самое главное прописать это:

println!("cargo:rustc-link-search=native=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-link-search=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-env=LD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-link-lib=dylib=cudart");

где /Developer/NVIDIA/CUDA-10.1/lib — путь к исполняемым файлам CUDA, в unix подобной системе этот путь можно узнать например командой:

which nvcc

Помимо этого в файле build.rs нужно указать путь к файлам dot.cpp и dot_gpu.cpp:

.files(&["./src/dot.cpp", "./src/dot_gpu.cu"])

Весь код файла build.rs
extern crate cc;

fn main() {
  cc::Build::new()
    .cuda(true)
    .cpp(true)
    .flag("-cudart=shared")
    .files(&["./src/dot.cpp", "./src/dot_gpu.cu"])
    .compile("dot.a");
  println!("cargo:rustc-link-search=native=/Developer/NVIDIA/CUDA-10.1/lib");
  println!("cargo:rustc-link-search=/Developer/NVIDIA/CUDA-10.1/lib");
  println!("cargo:rustc-env=LD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-10.1/lib");
  println!("cargo:rustc-link-lib=dylib=cudart");
}


Теперь можно приступать к написанию основного кода программы. В файле main.rs нужно создать интерфейс С/С++ функции для вызова непосредственно из кода на Rust. Более подробно об это можно почитать в официальной документации в разделе FFI.

extern "C" {
  // интерфейс C функции для расчета скалярного произведения двух векторов
  fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float;
}

Для её вызова надо использовать unsafe блок кода, в качестве аргументов передаем mutable pointer на тип Vec:

unsafe {
  gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
}

Полный код файла main.rs
extern crate libc;
extern crate rand;

use libc::{c_float, size_t};
use rand::Rng;

const VEC_SIZE: usize = 10;
const MAX: f32 = 10.;
const MIN: f32 = 0.;


extern "C" {
  fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float;
}

fn cpu_dot(v1: Vec<f32>, v2: Vec<f32>) -> f32 {
  let mut res: f32 = 0.;
  for i in 0..v1.len() {
    res += v1[i] * v2[i];
  }
  return res;
}

fn main() {
  let mut v1: Vec<f32> = Vec::new();
  let mut v2: Vec<f32> = Vec::new();
  let mut gpu_res: c_float;
  let mut cpu_res: f32 = 0.;

  let mut rng = rand::thread_rng();
  for _ in 0..VEC_SIZE {
    v1.push(rng.gen_range(MIN, MAX));
    v2.push(rng.gen_range(MIN, MAX));
  }

  println!("{:?}", v1);
  println!("{:?}", v2);

  println!("GPU computing started");
  unsafe {
    gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
  }
  println!("GPU computing finished");
  println!("GPU dot product result: {}", gpu_res);
  
  cpu_res = cpu_dot(v1, v2);
  println!("CPU dot product result: {}", cpu_res);
}


Теперь приступаем к написанию обвязки на C++, а так же кода для вычисления скалярного произведения векторов на CUDA C.

В файле dot.cpp напишем функцию обвязку, собственно эту функцию мы и вызываем из Rust кода:

extern "C" {
  float dot(float *v1, float *v2, size_t N) {
    float *gpu_res;
    float res = 0.0;
    gpu_res = gpu_dot(v1, v2, N); // вычисление на GPU
    for (int i = 0; i < blocksPerGrid; i++) {
      res += gpu_res[i];
    }
    free(gpu_res);
    return res;
  }
}

Полный код файла dot.cpp
#include <iostream>
#include "dot_gpu.h"

using namespace std;

void display_vector(float *v, size_t N) {
  cout << "[";
  for (size_t i = 0; i < N; i++) {
    cout << v[i];
    if (i != N - 1) {
      cout << ", ";
    }
  }
  cout << "]" << endl;
}

extern "C" {
  float dot(float *v1, float *v2, size_t N) {
    cout << "Calling gpu dot product" << endl;
    cout << "Got two vectors from rust:" << endl;
    display_vector(v1, N);
    display_vector(v2, N);
    float *gpu_res;
    float res = 0.0;
    gpu_res = gpu_dot(v1, v2, N);
    for (int i = 0; i < blocksPerGrid; i++) {
      res += gpu_res[i];
    }
    free(gpu_res);
    return res;
  }
}


Далее представлен код из файла dot_gpu.cu в котором производится основное вычисление, объяснять сам код в данном туториале не буду, так как он не посвящен программированию на CUDA.

dot_gpu.cu
#include "dot_gpu.h"

__global__ void dot__(float *v1, float *v2, float *res, int N) {
  __shared__ float cache [threadsPerBlock];
  int tid = threadIdx.x + blockIdx.x * blockDim.x;
  int cacheIndex = threadIdx.x;
  float temp = 0.0;
  while (tid < N) {
    temp += v1[tid] * v2[tid];
    tid += blockDim.x * gridDim.x;
  }
  cache[cacheIndex] = temp;

  __syncthreads();

  int i = blockDim.x / 2;
  while (i != 0) {
    if (cacheIndex < i) {
      cache[cacheIndex] += cache[cacheIndex + i];
    }
    __syncthreads();
    i /= 2;   
  }

  if (cacheIndex == 0) {
    res[blockIdx.x] = cache[0];
  }
}

float * gpu_dot (float *v1, float *v2, size_t N) {
	float *dev_v1, *dev_v2, *dev_res, *res;

	res = new float[blocksPerGrid];

	cudaMalloc((void**)&dev_v1, N * sizeof(float));
  cudaMalloc((void**)&dev_v2, N * sizeof(float));
	cudaMalloc((void**)&dev_res, blocksPerGrid * sizeof(float));
	
	cudaMemcpy(dev_v1, v1, N * sizeof(float), cudaMemcpyHostToDevice);
	cudaMemcpy(dev_v2, v2, N * sizeof(float), cudaMemcpyHostToDevice);
	
	dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N);
	cudaMemcpy(res, dev_res, blocksPerGrid * sizeof(float), cudaMemcpyDeviceToHost);

	cudaFree(dev_v1);
  cudaFree(dev_v2);
	cudaFree(dev_res);
	
	return res;
}


Все наша маленькая программа написана и готова к сборке. Для того что бы её собрать в консоли вызовем команду:

cargo build

Для запуска:

cargo run

После сборки программы в основной директории проекта появится папка target. Исполняемый файл нашей программы будет находится в папке: ./target/debug/

При этом если мы просто запустим наш исполняемый файл то получим ошибку: dyld library not loaded. То есть он не может найти путь к динамической библиотеки cuda. Для решения данной проблемы можно перед запуском исполняемого файла в консоли прописать переменную окружения LD_LIBRARY_PATH=path_to_CUDA_lib_directory/ или же создать символьные линки в папке rust toolchain для CUDA:

ln -s /Developer/NVIDIA/CUDA-10.1/lib/* /Users/Alexander/.rustup/toolchains/nightly-x86_64-apple-darwin/lib

где /Users/Alexander/.rustup/toolchains/nightly-x86_64-apple-darwin/lib — мой путь к установленному rust toolchain-у, у вас он может немного отличаться.

При запуске программы через cargo run такой ошибки не возникало, потому что мы прописали переменную окружения LD_LIBRARY_PATH в файле build.rs.

В итоге


Мы имеем возможность запускать код CUDA C прямиком из кода на Rust. Для того, что бы проверить это, мы создали небольшую программу, она работает с векторами и все вычисления производит на GPU. Полный код так же можно посмотреть на github.

Вы можете помочь и перевести немного средств на развитие сайта

Теги:



Комментарии (41):

  1. Gymmasssorla
    /#20029858 / +2

    Полезная статья, однозначно плюсую.

  2. tongohiti
    /#20030520

    Логичным продолжением был бы пост про, собственно, программирование с использованием CUDA…

    • rstm-sf
      /#20030850 / +1

      В корень проекта добавим папку src в которую поместим файлы с исходным кодом. В папке src создадим четыре файла: main.rs — код основной программы, dot.cpp — С++ binding (обертка для CUDA C), dot_gpu.h, dot_gpu.cu — файл в котором содержится код выполняемый на GPU.

      По сути, CUDA используется, как и раньше использовалась. Статей, как использовать, на хабре достаточно.

    • M00nL1ght
      /#20031022 / +3

      Одного поста не хватит, что бы описать программирование с использованием CUDA. В данной статье я лишь показал как использовать CUDA и Rust вместе. И как заметили выше сама по себе CUDA используется как обычно. А так, возможно, в дальнейшем еще опишу некоторые интересные моменты именно программирования CUDA в связке с Rust.

  3. masai
    /#20030746 / +1

    А почему .cpp, если все равно всё в extern "C"?

    • rstm-sf
      /#20030822

      Потому-что функция написана на языке C.
      What is the effect of extern “C” in C++?
      Is extern “C” no longer needed anymore in cuda?

      • masai
        /#20031242 / +1

        Мой вопрос всё же был не о том, что такое extern "C". Просто C++ тут только ради <iostream> и cout. Можно было и на C всё написать вполне.

        • M00nL1ght
          /#20031588

          .cpp не ради iostream и cout, .cpp вообще здесь не ради чего то, в нем нет глубокого смысла и необходимости, можно вообще все запихнуть в один файл .c и написать в нем и функцию обертку которая будет вызываться из Rust и CUDA кернел. Я это сделал просто так, дабы показать возможность линковки нескольких исходных файлов (.cpp в частности) в добавок ко всему.

        • emerald_isle
          /#20034314

          Не совсем так даже. Скорее дело в том, что nvcc на самом деле компилятор (расширенный) C++, а не C, по каким-то причинам.

  4. balsoft
    /#20031016 / +1

    Этот туториал на самом деле подойдёт для большинства других сишных библиотек.

    • M00nL1ght
      /#20031032 / +1

      По сути да. И не только сишных, и с++ — шных тоже. В случае с CUDA самое главное это прописать пути к исполняемым файлам, что бы Rust корректно подгрузил их как динамическую либу.

      • veslemay
        /#20033554

        что бы Rust корректно подгрузил их как динамическую либу.

        Раст ничего никуда не подгружает. Проблема в отсутствии прописанных путей/кривой линковке. С растом это никак не связано.

  5. pronvis
    /#20031092 / +2

    1. То есть непосредственно логику вычисления на GPU на расте написать не получится?
    2. Есть ли под макось что-нибудь вроде стаба/мока для cuda? Я хочу разрабатывать и тестить на маке, но запускать на убунте где у меня 1080TI

    • M00nL1ght
      /#20031574

      1. Нет, не получится. Код для вычисления на GPU все так же пишется на CUDA C. Да и честно говоря не вижу смысла переносить CUDA на Rust если достаточно в данном случае написать кернел для необходимых вычислений, вызвать этот кернел из Rust и результат вернуть обратно.

      2. Не совсем понял при чем тут stub/mock и макось. Если это то о чем я подумал, то оно вроде вообще не имеет привязки к операционной системе. Поэтому все что вы хотите сделать, все возможно. На Rust/С/С++/CUDA можно писать и запускать на разных платформах, главное собрать правильно.

      • antage
        /#20031642

        1. Уверены? Rust умеет компилировать в nvptx.

        • M00nL1ght
          /#20031690

          Вообщем да, вы правы, видел подобное, например тут и тут. Но честно говоря, я так не делал, мне проще и понятней написать кернел код на чистом CUDA C. Да и даже в статье, что я выше скинул говорится, что на данный момент поддержка nvptx и CUDA в частности делается с большими костылями и не совсем стабильная. Если есть еще какая то новая инфа по этому поводу буду рад ее услышать.

          • Aldrog
            /#20033388

            Интересно, насколько реально сделать безкостыльное GPGPU с помощью AMDGPU бэкенда LLVM?

            • M00nL1ght
              /#20033732 / +1

              Не могу ничего сказать по поводу AMDGPU, посмотрите в сторону OpenCL если вам нужно более платформо-независимое решение.

            • Gorthauer87
              /#20034448

              Вообще есть попытки прикрутить SPIRV
              https://github.com/MaikKlein/rlsl

              • Aldrog
                /#20037532

                Я имел в виду аналог амдшных hcc и HIP для C++, только для Rust.
                SPIR-V интереснее, т.к. более переносимо, но должно быть сложнее в реализации (hcc, как и rustc использует LLVM).

      • veslemay
        /#20032024

        вызвать этот кернел из Rust и результат вернуть обратно.

        А где у вас вызов кернела из rust?

        • M00nL1ght
          /#20032030

          Здесь:

          unsafe {
            gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
          }
          

          • cosmrc
            /#20032320

            Раз уж на то пошло, почему было не сделать безопасную функцию, которая принимает 2 вектора, проверяет их длины и потом вызывает unsafe функцию?
            В данной реализации это выглядит как пишем на С на Расте.

            • M00nL1ght
              /#20032412

              Так и надо сделать если писать полноценное приложение. Код из данного туториала конечно нельзя использовать в реальных задачах, да и зачем, ведь он просто считает произведение векторов, ничего полезного по сути он не делает. Это всего лишь небольшой пример, инструкция, как соединить Rust и CUDA. Далее на основе этого уже можно брать и писать «правильный» и «безопасный» unsafe код, оборачивать еще дополнительно во все то, что нужно.

          • veslemay
            /#20033500

            Это не вызов кернела. Это вызов си-функции из раста. К куде, кернелу и прочему не имеет никакого отношения.

            А вызов кернела вот:

            	dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N);
            

            • M00nL1ght
              /#20033598 / +1

              Конечно, формально это вызов си-функции которая вызывает кернел, из раста нарямую нет смысла вызывать функцию кернела, просто потому, что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU, копирование переменных в GPU и тд, это все уже относится к СUDA.

              Так что вызов си-функции обвязки из раста в которой находится CUDA-зависимый код в том числе вызов самого кернела и которая возвращает результат работы кернела, можно грубо назвать «вызовом кернела из раста».

              • veslemay
                /#20033650

                формально это вызов си-функци

                Это просто вызов си-функции. Какая она — это неважно.

                что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU

                Я знаю, строчка упомянутая мною именно оттуда.

                это все уже относится к СUDA.

                Это обычный сишный рантайм. К тому же это неважно, ведь даже если мы определяем по границе cuda, то границей является gpu_dot. И даже если принять вашу логику — кернелом будет gpu_dot, но вы её не вызываете — вы вызываете обёртку над gpu_dot.

                Так что вызов си-функции обвязки из раста в которой находится CUDA-зависимый код в том числе вызов самого кернела и которая возвращает результат работы кернела, можно грубо назвать «вызовом кернела из раста».

                Нельзя. Это вызов обёртки dot, которая вызывает обёртку, которая вызывает кернел. dot никоим образом не вызвает кернел.

                Поэтому как это не называй — это обычный вызов сишной функции из раста. Этот вызов никак не связан с cuda, вообще никак.

                • M00nL1ght
                  /#20033700

                  Нельзя. Это вызов обёртки dot, которая вызывает обёртку, которая вызывает кернел. dot никоим образом не вызвает кернел.


                  Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.

                  Поэтому как это не называй — это обычный вызов сишной функции из раста. Этот вызов никак не связан с cuda, вообще никак.


                  Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)

                  • veslemay
                    /#20033826

                    Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.

                    Это ничего не изменит.

                    Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)

                    Ну это принципиальный момент. По-сути из статьи можно выкинуть cuda и ничего не изменится, т.к. статья чисто про вызов сишной функции из раста.

                    • M00nL1ght
                      /#20033976 / +1

                      Это ничего не изменит.


                      Изменит)

                      И даже если принять вашу логику — кернелом будет gpu_dot, но вы её не вызываете — вы вызываете обёртку над gpu_dot.


                      Если принять мою логику то gpu_dot будет кернелом а уже эту функцию я могу вызвать из раст) Так что если принять мою логику то это изменит кое что.

                      Ну это принципиальный момент. По-сути из статьи можно выкинуть cuda и ничего не изменится, т.к. статья чисто про вызов сишной функции из раста.


                      Этот туториал в том числе можно использовать как инструкцию для вызова обычного си кода из раст, в комментариях это уже упоминалось, посмотрите выше. В статье рассказывается как писать и собирать Rust + С + CUDA C.

                      • veslemay
                        /#20034338

                        Изменит)

                        Нет, это не будет вызовом кернела. Это доказано мною выше.

                        Если принять мою логику то gpu_dot будет кернелом а уже эту функцию я могу вызвать из раст) Так что если принять мою логику то это изменит кое что.

                        Это логика несостоятельна. К тому же, зачем задним числом пытаться что-то изменять? Пока у вас gpu_dot не вызвается из раста — всё эти рассуждения не имеют смысла, т.к. вы показывали не это.

                        Этот туториал в том числе можно использовать как инструкцию для вызова обычного си кода из раст, в комментариях это уже упоминалось, посмотрите выше. В статье рассказывается как писать и собирать Rust + С + CUDA C.


                        Какое отношение к вызову си кода из раст имеет куда? Никакого. Как минимум вы должны написать «вызов си кода из раста НА ПРИМЕРЕ куды», но опять же — это будет полной глупостью, т.к. куда тут вообще не при делах.

                        Тоже самое и со сборкой. «сборка С++ кода через cargo на примере куда», но опять же — куда тут никаким образом не относится к теме.

                        В статье рассказывается как писать и собирать Rust + С + CUDA C.

                        Опять же, неверно. В статье не рассказывается как писать на С++ + cuda, а даже если бы и рассказывалось — причём тут раст? Так и пишите «как писать на С++ + cuda».

                        Я вам дам правильный заголовок. «пишем на С++ + cuda c» + «вызываем написанный таким образом код из раста». То, что написано у вас — неверно.

                        • M00nL1ght
                          /#20034410

                          Когда будете писать свою статью назовете её как захотите. Я больше спорить с вами не буду, потому что у меня сложилось впечатление, что вы просто придираетесь к словам и к постановке предложений, ищите какой то тайный смысл в них.

                          • veslemay
                            /#20034424 / -1

                            Ну т.е. статья — это чисто желтуха для «похайпить» на популярном базворде. Никакой куды нет, никакого раста нет, никакого вызова кернелов нет. Статье «вызываем си-фукцию из раста» никто бы 100 плюсов не наставил бы.

                            • M00nL1ght
                              /#20034474

                              Все с вами ясно, пустил в коменты на свою голову) вам заняться нечем? Так пишите свои статьи на чисто «похайпить», я вам что мешаю? Мне на это абсолютно все равно, не судите по себе других. Повторюсь, статья о том, как собрать Rust + С + CUDA и прописать зависимости, что бы все работало, а не о том как писать на CUDA, это все написано в самом начале. Это даже не статья, это небольшой туториал, все это так же отмечено в самом начале под заголовком, прочтите внимательно еще раз.

                              • veslemay
                                /#20034744

                                Хорошо, про желтый заголовок я уже сказал — ответа не было. Пошло игнорирование. Теперь касательно текста:

                                И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU с использованием CUDA C.

                                Где вы написали на rust программу для «вычисления скалярного произведения векторов»? Я отвечу — нигде. Вы написали программу вызова си-функции из rust.

                                Я слушаю про внимательность и тому подобное.

                                • M00nL1ght
                                  /#20034944

                                  Где вы написали на rust программу для «вычисления скалярного произведения векторов»?

                                  Открываем файл main.rs (.rs — означает, что он написан на языке Rust). Коментируем unsafe блок кода. Получаем такой файл:
                                  main.rs
                                  extern crate libc;
                                  extern crate rand;
                                  
                                  use libc::{c_float, size_t};
                                  use rand::Rng;
                                  
                                  const VEC_SIZE: usize = 10;
                                  const MAX: f32 = 10.;
                                  const MIN: f32 = 0.;
                                  
                                  
                                  extern "C" {
                                    fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float;
                                  }
                                  
                                  fn cpu_dot(v1: Vec<f32>, v2: Vec<f32>) -> f32 {
                                    let mut res: f32 = 0.;
                                    for i in 0..v1.len() {
                                      res += v1[i] * v2[i];
                                    }
                                    return res;
                                  }
                                  
                                  fn main() {
                                    let mut v1: Vec<f32> = Vec::new();
                                    let mut v2: Vec<f32> = Vec::new();
                                    let mut gpu_res: c_float;
                                    let mut cpu_res: f32 = 0.;
                                  
                                    let mut rng = rand::thread_rng();
                                    for _ in 0..VEC_SIZE {
                                      v1.push(rng.gen_range(MIN, MAX));
                                      v2.push(rng.gen_range(MIN, MAX));
                                    }
                                  
                                    println!("{:?}", v1);
                                    println!("{:?}", v2);
                                  
                                    println!("GPU computing started");
                                   // unsafe {
                                      //gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
                                  //  }
                                    println!("GPU computing finished");
                                    println!("GPU dot product result: {}", gpu_res);
                                    
                                    cpu_res = cpu_dot(v1, v2);
                                    println!("CPU dot product result: {}", cpu_res);
                                  }

                                  • veslemay
                                    /#20034970 / -1

                                    Какая наивность, какое враньё.

                                    в нем ищем функцию cpu_dot, которая вычисляет скалярное произведение двух векторов на языке Rust


                                    И получаем файл, т.к. спастил я цитату не полностью:

                                    И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU


                                    вычисление скалярного произведения будет производиться на GPU

                                    Здесь уточнение, как именно будет происходить вычисление «скалярного произведения» в «программу на Rust».

                                    А что вы пытаетесь мне подсунуть?
                                    cpu_dot

                                    Который вы только что придумали, но самом деле вы то имели ввиду другое, и я это знаю, и я это доказал выше.

                                    • M00nL1ght
                                      /#20034984

                                      и я это доказал выше.

                                      поздравляю) удачи вам, обсуждение можно считать закрытым.

                                      • veslemay
                                        /#20035170 / -1

                                        Удобно — наврал, на вранье поймали и «закрыто».

    • emerald_isle
      /#20034356 / +2

      Так просто — не получится. Это надо компилятор переписывать. На GPU вычисляется только часть кода, который ещё туда надо как-то загрузить. CUDA — только один из вариантов, есть ещё OpenCL, который чуть более переносимый, но тоже со своими проблемами.

      Архитектура GPU немного другая, соответственно, код там нужен другой. Цикл примерно такой, host-приложение пишется на любом языке (чаще C), подготавливает данные, программу для GPU — отправляет туда, ждёт результата, потом вытягивает результат, и, например, печатает в терминал (или GUI/веб-сервис, или куда ещё вам надо). Цикл может повторяться несколько раз, и во время ожидания результата с GPU можно фактически ещё чем-то полезным заниматься в других потоках… тут не уверен — на CUDA не писал ничего сложнее простеньких приложений, скидывающих результат в консоль или csv-табличку, или рисующих график, например — поэтому необходимости такой не было.

      • 0xd34df00d
        /#20044352

        Это надо компилятор переписывать.

        Бекенд, на самом деле.


        Я году в 2016-м или 2017-м игрался с оффлоадингом вычислений из кода на плюсах на видеокарту, ну чтобы я писал вот обычный какой-нибудь цикл для перемножения двух векторов, а clang мне генерировал бинарь для CPU + код для GPU + код для связи между ними. Там даже что-то получалось и уходило на видеокарту, но производительность у этого решения была отвратная, я и забил. Может, сейчас получше стало.