Пишем на Rust + CUDA C

  • Tutorial

Всем привет!

В данном руководстве хочу рассказать как подружить 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.
Поделиться публикацией

Комментарии 43

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

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

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

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

            0

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

              +3

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

                +1
                .cpp не ради iostream и cout, .cpp вообще здесь не ради чего то, в нем нет глубокого смысла и необходимости, можно вообще все запихнуть в один файл .c и написать в нем и функцию обертку которая будет вызываться из Rust и CUDA кернел. Я это сделал просто так, дабы показать возможность линковки нескольких исходных файлов (.cpp в частности) в добавок ко всему.
                  +1
                  Не совсем так даже. Скорее дело в том, что nvcc на самом деле компилятор (расширенный) C++, а не C, по каким-то причинам.
              +1
              Этот туториал на самом деле подойдёт для большинства других сишных библиотек.
                +1
                По сути да. И не только сишных, и с++ — шных тоже. В случае с CUDA самое главное это прописать пути к исполняемым файлам, что бы Rust корректно подгрузил их как динамическую либу.
                  0
                  что бы Rust корректно подгрузил их как динамическую либу.

                  Раст ничего никуда не подгружает. Проблема в отсутствии прописанных путей/кривой линковке. С растом это никак не связано.
                +2
                1. То есть непосредственно логику вычисления на GPU на расте написать не получится?
                2. Есть ли под макось что-нибудь вроде стаба/мока для cuda? Я хочу разрабатывать и тестить на маке, но запускать на убунте где у меня 1080TI
                  0
                  1. Нет, не получится. Код для вычисления на GPU все так же пишется на CUDA C. Да и честно говоря не вижу смысла переносить CUDA на Rust если достаточно в данном случае написать кернел для необходимых вычислений, вызвать этот кернел из Rust и результат вернуть обратно.

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

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

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

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

                              0

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

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

                        А где у вас вызов кернела из rust?
                          +1
                          Здесь:
                          unsafe {
                            gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
                          }
                          
                            +3
                            Раз уж на то пошло, почему было не сделать безопасную функцию, которая принимает 2 вектора, проверяет их длины и потом вызывает unsafe функцию?
                            В данной реализации это выглядит как пишем на С на Расте.
                              +1
                              Так и надо сделать если писать полноценное приложение. Код из данного туториала конечно нельзя использовать в реальных задачах, да и зачем, ведь он просто считает произведение векторов, ничего полезного по сути он не делает. Это всего лишь небольшой пример, инструкция, как соединить Rust и CUDA. Далее на основе этого уже можно брать и писать «правильный» и «безопасный» unsafe код, оборачивать еще дополнительно во все то, что нужно.
                              0
                              Это не вызов кернела. Это вызов си-функции из раста. К куде, кернелу и прочему не имеет никакого отношения.

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

                              	dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N);
                              
                                +1
                                Конечно, формально это вызов си-функции которая вызывает кернел, из раста нарямую нет смысла вызывать функцию кернела, просто потому, что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU, копирование переменных в GPU и тд, это все уже относится к СUDA.

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

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

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

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

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

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

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

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

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


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

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


                                    Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)
                                      0
                                      Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.

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

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

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


                                        Изменит)

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


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

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


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

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

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

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

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


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

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

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

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

                                          Я вам дам правильный заголовок. «пишем на С++ + cuda c» + «вызываем написанный таким образом код из раста». То, что написано у вас — неверно.
                                            0
                                            Когда будете писать свою статью назовете её как захотите. Я больше спорить с вами не буду, потому что у меня сложилось впечатление, что вы просто придираетесь к словам и к постановке предложений, ищите какой то тайный смысл в них.
                                              –1
                                              Ну т.е. статья — это чисто желтуха для «похайпить» на популярном базворде. Никакой куды нет, никакого раста нет, никакого вызова кернелов нет. Статье «вызываем си-фукцию из раста» никто бы 100 плюсов не наставил бы.
                                                0
                                                Все с вами ясно, пустил в коменты на свою голову) вам заняться нечем? Так пишите свои статьи на чисто «похайпить», я вам что мешаю? Мне на это абсолютно все равно, не судите по себе других. Повторюсь, статья о том, как собрать Rust + С + CUDA и прописать зависимости, что бы все работало, а не о том как писать на CUDA, это все написано в самом начале. Это даже не статья, это небольшой туториал, все это так же отмечено в самом начале под заголовком, прочтите внимательно еще раз.
                                                  0
                                                  Хорошо, про желтый заголовок я уже сказал — ответа не было. Пошло игнорирование. Теперь касательно текста:

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

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

                                                  Я слушаю про внимательность и тому подобное.
                                                    0
                                                    Где вы написали на 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);
                                                    }


                                                    в нем ищем функцию cpu_dot, которая вычисляет скалярное произведение двух векторов на языке Rust (dot product на английском означает скалярное произведение), формулу скалярного произведения я тоже привел.
                                                    Компилируем это и получаем простую программу для вычисления скалярного произведения на Rust.
                                                    Я отвечу — нигде.

                                                    Уверены? Берете свои слова обратно? Или сейчас вы скажете, что ее я не сам написал?
                                                    Я слушаю про внимательность и тому подобное.

                                                    Слушайте: читайте пожалуйста внимательно. Не надо придираться к словам.

                                                    И может хватит уже? Мне надоело повторять одно и то же, вы меня все равно не хотите слушать.
                                                      –1
                                                      Какая наивность, какое враньё.

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


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

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


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

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

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

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

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

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

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

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


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

                              0
                              Возможно, вектора слишком маленькие, а результат слишком большой. Может банально больше времени тратиться на пересылку данных на GPU + обратно, и смешное время на собственно перемножение. Перемножение (или сложение) векторов идёт как пример hello world, потому что его проще написать.
                                0
                                Я, увы, уже не помню подробностей, но масштабы размерностей там были порядка миллиона. Ручками написанный хелловорлд отрабатывал несопоставимо быстрее.

                                Собственно, это призвано проиллюстрировать, что эффективная автовекторизация, автопараллелизация, автооффлоадинг — это сложно.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое