Dalam baru-baru ini saya bekerja dengan Gibbs sampling
, saya telah membuat penggunaan besar dari RVar
yang, dalam pandangan saya, menyediakan antarmuka yang ideal dekat ke generasi nomor acak. Sayangnya, saya tidak dapat menggunakan Repa karena ketidakmampuan untuk menggunakan tindakan monadik di peta.
Sementara peta monad yang jelas tidak dapat diparalelkan secara umum, menurut saya itu RVar
mungkin setidaknya satu contoh monad di mana efek dapat diparalelkan dengan aman (setidaknya pada prinsipnya; Saya tidak terlalu akrab dengan cara kerja bagian dalam RVar
) . Yakni, saya ingin menulis sesuatu seperti berikut ini,
drawClass :: Sample -> RVar Class
drawClass = ...
drawClasses :: Array U DIM1 Sample -> RVar (Array U DIM1 Class)
drawClasses samples = A.mapM drawClass samples
dimana A.mapM
akan terlihat seperti,
mapM :: ParallelMonad m => (a -> m b) -> Array r sh a -> m (Array r sh b)
Sementara jelas bagaimana ini akan bekerja sangat tergantung pada implementasi RVar
dan yang mendasarinya RandomSource
, pada prinsipnya orang akan berpikir bahwa ini akan melibatkan penarikan benih acak baru untuk setiap utas yang muncul dan dilanjutkan seperti biasa.
Secara intuitif, tampaknya gagasan yang sama ini mungkin digeneralisasikan ke beberapa monad lain.
Jadi, pertanyaan saya adalah: Bisakah seseorang membangun kelas ParallelMonad
monad yang efeknya dapat diparalelkan dengan aman (mungkin dihuni oleh, setidaknya, RVar
)?
Akan terlihat seperti apa? Monad lain apa yang mungkin menghuni kelas ini? Apakah orang lain telah mempertimbangkan kemungkinan bagaimana hal ini dapat berhasil di Repa?
Akhirnya, jika gagasan aksi monadik paralel ini tidak dapat digeneralisasikan, apakah ada yang melihat cara yang bagus untuk membuat ini bekerja dalam kasus tertentu RVar
(di mana akan sangat berguna)? Menyerah RVar
karena paralelisme adalah pertukaran yang sangat sulit.
sumber
RandomSource
spesifik. Upaya naif saya dalam menggambar benih adalah melakukan sesuatu yang sederhana dan kemungkinan besar sangat salah seperti menggambar vektor elemen (dalam kasusmwc-random
) dan menambahkan 1 ke setiap elemen untuk menghasilkan benih untuk pekerja pertama, tambahkan 2 untuk yang kedua pekerja, dll. Sangat tidak memadai jika Anda membutuhkan entropi berkualitas kriptografi; semoga baik-baik saja jika Anda hanya perlu jalan-jalan acak.split
fungsi System.Random . Ini memiliki kelemahan untuk menghasilkan hasil yang berbeda (karena sifatsplit
tetapi berhasil. Namun, saya mencoba untuk memperluas ini ke array Repa dan tidak memiliki banyak keberuntungan. Apakah Anda membuat kemajuan dengan ini atau sudah mati- berakhir?split
dicatat oleh Tom Savage, memberikan dasar yang diperlukan, tetapi perhatikan komentar pada sumber bagaimanasplit
penerapannya: "- tidak ada dasar statistik untuk ini!". Saya cenderung berpikir bahwa metode pemisahan PRNG akan meninggalkan korelasi yang dapat dieksploitasi antara cabang-cabangnya, tetapi tidak memiliki latar belakang statistik untuk membuktikannya. Mengenai pertanyaan umum, saya tidak yakin ituJawaban:
Sudah 7 tahun sejak pertanyaan ini diajukan, dan sepertinya masih belum ada yang menemukan solusi yang baik untuk masalah ini. Repa tidak memiliki fungsi
mapM
/traverse
like, bahkan yang dapat berjalan tanpa paralelisasi. Terlebih lagi, mengingat jumlah kemajuan yang terjadi dalam beberapa tahun terakhir, tampaknya hal itu juga tidak mungkin terjadi.Karena keadaan basi dari banyak perpustakaan array di Haskell dan ketidakpuasan saya secara keseluruhan dengan set fitur mereka, saya telah mengajukan beberapa tahun pekerjaan ke dalam perpustakaan array
massiv
, yang meminjam beberapa konsep dari Repa, tetapi membawanya ke tingkat yang sama sekali berbeda. Cukup dengan intro.Sebelum hari ini, ada tiga peta monadik seperti fungsi dalam
massiv
(tidak termasuk sinonim seperti fungsi:imapM
,forM
. Et al):mapM
- pemetaan biasa secara sembaranganMonad
. Tidak dapat diparalelkan karena alasan yang jelas dan juga agak lambat (seperti biasanya dimapM
atas daftar lambat)traversePrim
- Di sini kami dibatasiPrimMonad
, yang secara signifikan lebih cepat daripadamapM
, tetapi alasan untuk ini tidak penting untuk diskusi ini.mapIO
- yang ini, seperti namanya, dibatasi untukIO
(atau lebih tepatnyaMonadUnliftIO
, tapi itu tidak relevan). Karena kita berada di dalam,IO
kita dapat secara otomatis membagi array dalam banyak potongan karena ada inti dan menggunakan utas pekerja terpisah untuk memetakanIO
tindakan atas setiap elemen dalam potongan tersebut. Tidak seperti murnifmap
, yang juga dapat diparalelkan, kami harus berada diIO
sini karena penjadwalan yang tidak ditentukan dikombinasikan dengan efek samping dari tindakan pemetaan kami.Jadi, begitu saya membaca pertanyaan ini, saya berpikir bahwa masalahnya secara praktis sudah diselesaikan
massiv
, tetapi tidak secepat itu. Generator nomor acak, seperti inmwc-random
dan lainnya dirandom-fu
tidak dapat menggunakan generator yang sama di banyak utas. Artinya, satu-satunya bagian dari teka-teki yang saya lewatkan adalah: "menggambar benih acak baru untuk setiap utas yang muncul dan melanjutkan seperti biasa". Dengan kata lain, saya membutuhkan dua hal:Jadi itulah yang saya lakukan.
Pertama saya akan memberikan contoh penggunaan yang dibuat khusus
randomArrayWS
daninitWorkerStates
fungsinya, karena lebih relevan dengan pertanyaan dan kemudian pindah ke peta monadik yang lebih umum. Berikut adalah jenis tanda tangan mereka:randomArrayWS :: (Mutable r ix e, MonadUnliftIO m, PrimMonad m) => WorkerStates g -- ^ Use `initWorkerStates` to initialize you per thread generators -> Sz ix -- ^ Resulting size of the array -> (g -> m e) -- ^ Generate the value using the per thread generator. -> m (Array r ix e)
initWorkerStates :: MonadIO m => Comp -> (WorkerId -> m s) -> m (WorkerStates s)
Bagi yang belum terbiasa
massiv
,Comp
argumennya adalah strategi komputasi yang akan digunakan, konstruktor terkenal adalah:Seq
- menjalankan komputasi secara berurutan, tanpa membagi utas apa punPar
- putar utas sebanyak mungkin dan gunakan utas itu untuk melakukan pekerjaan.Saya akan menggunakan
mwc-random
paket sebagai contoh pada awalnya dan kemudian pindah keRVarT
:λ> import Data.Massiv.Array λ> import System.Random.MWC (createSystemRandom, uniformR) λ> import System.Random.MWC.Distributions (standard) λ> gens <- initWorkerStates Par (\_ -> createSystemRandom)
Di atas kami menginisialisasi generator terpisah per utas menggunakan keacakan sistem, tetapi kami dapat juga menggunakan benih per utas unik dengan mengambilnya dari
WorkerId
argumen, yang hanya merupakanInt
indeks pekerja. Dan sekarang kita dapat menggunakan generator tersebut untuk membuat array dengan nilai acak:λ> randomArrayWS gens (Sz2 2 3) standard :: IO (Array P Ix2 Double) Array P Par (Sz (2 :. 3)) [ [ -0.9066144845415213, 0.5264323240310042, -1.320943607597422 ] , [ -0.6837929005619592, -0.3041255565826211, 6.53353089112833e-2 ] ]
Dengan menggunakan
Par
strategi,scheduler
pustaka akan membagi pekerjaan generasi secara merata di antara pekerja yang tersedia dan setiap pekerja akan menggunakan generatornya sendiri, sehingga membuatnya aman untuk thread. Tidak ada yang mencegah kami untuk menggunakan kembali hal yang samaWorkerStates
jumlah acak yang selama itu tidak dilakukan secara bersamaan, yang jika tidak akan mengakibatkan pengecualian:λ> randomArrayWS gens (Sz1 10) (uniformR (0, 9)) :: IO (Array P Ix1 Int) Array P Par (Sz1 10) [ 3, 6, 1, 2, 1, 7, 6, 0, 8, 8 ]
Sekarang mengesampingkan
mwc-random
kita dapat menggunakan kembali konsep yang sama untuk kasus penggunaan lain yang mungkin dengan menggunakan fungsi sepertigenerateArrayWS
:generateArrayWS :: (Mutable r ix e, MonadUnliftIO m, PrimMonad m) => WorkerStates s -> Sz ix -- ^ size of new array -> (ix -> s -> m e) -- ^ element generating action -> m (Array r ix e)
dan
mapWS
:mapWS :: (Source r' ix a, Mutable r ix b, MonadUnliftIO m, PrimMonad m) => WorkerStates s -> (a -> s -> m b) -- ^ Mapping action -> Array r' ix a -- ^ Source array -> m (Array r ix b)
Berikut adalah contoh yang dijanjikan tentang cara menggunakan fungsionalitas ini dengan
rvar
,random-fu
danmersenne-random-pure64
pustaka. Kita juga bisa menggunakannya dirandomArrayWS
sini, tetapi sebagai contoh katakanlah kita sudah memiliki array denganRVarT
s yang berbeda , dalam hal ini kita membutuhkanmapWS
:λ> import Data.Massiv.Array λ> import Control.Scheduler (WorkerId(..), initWorkerStates) λ> import Data.IORef λ> import System.Random.Mersenne.Pure64 as MT λ> import Data.RVar as RVar λ> import Data.Random as Fu λ> rvarArray = makeArrayR D Par (Sz2 3 9) (\ (i :. j) -> Fu.uniformT i j) λ> mtState <- initWorkerStates Par (newIORef . MT.pureMT . fromIntegral . getWorkerId) λ> mapWS mtState RVar.runRVarT rvarArray :: IO (Array P Ix2 Int) Array P Par (Sz (3 :. 9)) [ [ 0, 1, 2, 2, 2, 4, 5, 0, 3 ] , [ 1, 1, 1, 2, 3, 2, 6, 6, 2 ] , [ 0, 1, 2, 3, 4, 4, 6, 7, 7 ] ]
Penting untuk dicatat, bahwa meskipun implementasi murni dari Mersenne Twister digunakan dalam contoh di atas, kami tidak dapat lepas dari IO. Ini karena penjadwalan non-deterministik, yang berarti kita tidak pernah tahu pekerja mana yang akan menangani bagian mana dari larik dan akibatnya generator mana yang akan digunakan untuk bagian larik mana. Di sisi atas, jika generator itu murni dan dapat dipisahkan, seperti
splitmix
, maka kita dapat menggunakan fungsi pembangkitan yang murni, deterministik, dan dapat diparalelkan :,randomArray
tetapi itu sudah menjadi cerita yang terpisah.sumber
Mungkin bukan ide yang baik untuk melakukan ini karena sifat PRNG yang berurutan secara inheren. Sebaliknya, Anda mungkin ingin mentransisikan kode Anda sebagai berikut:
main
, atau apa pun yang Anda miliki).sumber