Напишите параллельный массив выражения Haskell один раз, запустите на процессорах и графических процессорах с repa и ускорьте
Repa и ускорение сходства API
Библиотека репозитория Haskell предназначена для автоматического параллельного вычисления массива на процессорах. Библиотека ускорения - это автоматический параллелизм данных на графических процессорах. API довольно похожи, с идентичными представлениями N-мерных массивов. Можно даже переключаться между ускорением и восстановлением массивов с fromRepa
а также toRepa
в Data.Array.Accelerate.IO
:
fromRepa :: (Shapes sh sh', Elt e) => Array A sh e -> Array sh' e
toRepa :: Shapes sh sh' => Array sh' e -> Array A sh e
Существует несколько бэкэндов для ускорения, в том числе LLVM, CUDA и FPGA (см. Рисунок 2 http://www.cse.unsw.edu.au/~keller/Papers/acc-cuda.pdf). Я заметил бэкэнд для ускорения, хотя библиотека, похоже, не поддерживается. Учитывая, что модели программирования repa и ускорения схожи, я надеюсь, что существует элегантный способ переключения между ними, то есть функции, написанные один раз, могут быть выполнены с помощью RpaputeP repa или с одним из бэкэндов ускорения, например, с функцией запуска CUDA.
Две очень похожие функции: Repa и Accelerate on the Pumpkin
Возьмите простую функцию обработки порогов изображения. Если значение пикселя в градациях серого меньше 50, оно устанавливается равным 0, в противном случае оно сохраняет свое значение. Вот что это делает с тыквой:
В следующем коде представлены реализации repa и ускорения:
module Main where
import qualified Data.Array.Repa as R
import qualified Data.Array.Repa.IO.BMP as R
import qualified Data.Array.Accelerate as A
import qualified Data.Array.Accelerate.IO as A
import qualified Data.Array.Accelerate.Interpreter as A
import Data.Word
-- Apply threshold over image using accelerate (interpreter)
thresholdAccelerate :: IO ()
thresholdAccelerate = do
img <- either (error . show) id `fmap` A.readImageFromBMP "pumpkin-in.bmp"
let newImg = A.run $ A.map evalPixel (A.use img)
A.writeImageToBMP "pumpkin-out.bmp" newImg
where
-- *** Exception: Prelude.Ord.compare applied to EDSL types
evalPixel :: A.Exp A.Word32 -> A.Exp A.Word32
evalPixel p = if p > 50 then p else 0
-- Apply threshold over image using repa
thresholdRepa :: IO ()
thresholdRepa = do
let arr :: IO (R.Array R.U R.DIM2 (Word8,Word8,Word8))
arr = either (error . show) id `fmap` R.readImageFromBMP "pumpkin-in.bmp"
img <- arr
newImg <- R.computeP (R.map applyAtPoint img)
R.writeImageToBMP "pumpkin-out.bmp" newImg
where
applyAtPoint :: (Word8,Word8,Word8) -> (Word8,Word8,Word8)
applyAtPoint (r,g,b) =
let [r',g',b'] = map applyThresholdOnPixel [r,g,b]
in (r',g',b')
applyThresholdOnPixel x = if x > 50 then x else 0
data BackendChoice = Repa | Accelerate
main :: IO ()
main = do
let userChoice = Repa -- pretend this command line flag
case userChoice of
Repa -> thresholdRepa
Accelerate -> thresholdAccelerate
Вопрос: могу ли я написать это только один раз?
Реализации thresholdAccelerate
а также thresholdRepa
очень похожи Есть ли элегантный способ написать функции обработки массива один раз, а затем выбрать программный многоядерный процессор (repa) или GPU (ускорение)? Я могу думать о выборе своего импорта в соответствии с тем, хочу ли я CPU или GPU, т.е. импортировать либо Data.Array.Accelerate.CUDA
или же Data.Array.Repa
выполнить действие типа Acc a
с:
run :: Arrays a => Acc a -> a
Или использовать класс типа, например, что-то вроде:
main :: IO ()
main = do
let userChoice = Repa -- pretend this is a command line flag
action <- case userChoice of
Repa -> applyThreshold :: RepaBackend ()
Accelerate -> applyThreshold :: CudaBackend ()
action
Или это тот случай, когда для каждой функции параллельного массива, которую я хочу выразить как для CPU, так и для GPU, я должен реализовать ее дважды - один раз с библиотекой repa и снова с библиотекой ускорения?
2 ответа
Краткий ответ: на данный момент вам, к сожалению, нужно написать обе версии.
Тем не менее, мы работаем над поддержкой процессоров для Accelerate, что устранит необходимость в версии кода Repa. В частности, Accelerate совсем недавно получил новый бэкэнд на основе LLVM, предназначенный как для GPU, так и для CPU: https://github.com/AccelerateHS/accelerate-llvm
Этот новый бэкэнд все еще неполон, глючит и экспериментален, но мы планируем превратить его в жизнеспособную альтернативу текущему бэкенду CUDA.
Я думал об этом год и несколько месяцев назад во время проектирования yarr
, В то время были серьезные проблемы с выводом семейства типов или чем-то вроде этого (я точно не помню), что мешало реализовать такую объединяющую оболочку vector
, repa
, yarr
, accelerate
и т. д. как эффективно, так и позволяя не писать слишком много явных сигнатур типов или реализовывать их в принципе (я не помню).
Это был GHC 7.6. Я не знаю, есть ли существенные улучшения в GHC 7.8 в этой области. Теоретически я не видел никаких проблем, поэтому мы можем ожидать такие вещи когда-нибудь, в короткие или длинные сроки, когда GHC будет готов.