Repa dizilerinde paralel mapM


90

Benim son olarak çalışması ile Gibbs sampling, ben iyi şekilde yararlanmayı sağlayan oldum RVarbana göre, rasgele sayı üretimi için ideale yakın arayüz sağlar. Maalesef haritalarda monadik eylemleri kullanamadığım için Repa'yı kullanamadım.

Açıkça monadik haritalar genel olarak paralelleştirilemezken, bana öyle geliyor ki bu RVar, etkilerin güvenli bir şekilde paralelleştirilebileceği bir monad örneği olabilir (en azından prensipte; iç işleyişine çok aşina değilim RVar) . Yani aşağıdaki gibi bir şey yazmak istiyorum,

drawClass :: Sample -> RVar Class
drawClass = ...

drawClasses :: Array U DIM1 Sample -> RVar (Array U DIM1 Class)
drawClasses samples = A.mapM drawClass samples

bir A.mapMşey nerede görünürdü

mapM :: ParallelMonad m => (a -> m b) -> Array r sh a -> m (Array r sh b)

Bunun nasıl çalışacağı açık bir şekilde uygulamasına RVarve temeline bağlı olsa da RandomSource, ilke olarak, bunun ortaya çıkan her iplik için yeni bir rastgele tohum çizmeyi ve her zamanki gibi ilerlemeyi içereceğini düşünebiliriz.

Sezgisel olarak, bu aynı fikir diğer bazı monadlara da genellenebilir gibi görünüyor.

Öyleyse sorum şu: ParallelMonadEtkilerinin güvenli bir şekilde paralelleştirilebileceği bir monadlar sınıfı inşa edilebilir mi (muhtemelen en azından burada yaşıyor RVar)?

Nasıl görünebilir? Bu sınıfta başka hangi monadlar yaşayabilir? Başkaları bunun Repa'da nasıl işleyebileceğini düşündü mü?

Son olarak, bu paralel monadik eylemler kavramı genelleştirilemezse, RVar(çok yararlı olacağı) özel durumda bu işi yapmanın güzel bir yolunu gören var mı? RVarParalellikten vazgeçmek çok zor bir değiş tokuş.


1
Sanırım bağlantı noktası "ortaya çıkan her iplik için yeni bir rastgele tohum çizmek" - bu adım nasıl çalışmalı ve tüm iplikler geri döndüğünde tohumlar nasıl yeniden birleştirilmelidir?
Daniel Wagner

1
RVar arayüzü, belirli bir tohumla yeni bir jeneratör üretmeyi sağlamak için neredeyse kesinlikle bazı eklemelere ihtiyaç duyacaktır. Kuşkusuz, bu mekaniğin nasıl çalıştığı belli değil ve oldukça RandomSourcespesifik görünüyor . Bir tohum çizmeye yönelik saf girişimim, basit ve muhtemelen çok yanlış bir şey yapmak olurdu, örneğin bir element vektörü çizmek (durumunda mwc-random) ve ilk işçi için bir tohum üretmek için her bir öğeye 1 ekleyin, ikincisi için 2 ekleyin. işçi, vb. Kriptografik kalitede entropiye ihtiyacınız varsa, ne yazık ki yetersiz; Sadece rastgele bir yürüyüşe ihtiyacınız varsa umarım iyidir.
bgamari

3
Benzer bir sorunu çözmeye çalışırken bu soruyla karşılaştım. Paralel olarak monadik rastgele hesaplamalar için MonadRandom ve System.Random kullanıyorum. Bu yalnızca System.Random splitişlevi ile mümkündür . Farklı sonuçlar üretme dezavantajı var (doğası gereği splitama işe yarıyor. Ancak bunu Repa dizilerine genişletmeye çalışıyorum ve pek şansım olmuyor. Bununla bir ilerleme kaydettiniz mi yoksa ölü mü? end?
Tom Savage

1
Sıralamasız Monad ve hesaplamalar arasındaki bağımlılıklar bana daha çok uygulanabilir gibi geliyor.
John Tyree

1
Tereddüt ediyorum Tom Savage'ın belirttiği gibi, splitgerekli bir temel sağlar, ancak nasıl splituygulandığına ilişkin kaynak hakkındaki yorumu not edin : "- bunun için istatistiksel bir temel yok!". Bir PRNG'yi bölmenin herhangi bir yönteminin şubeleri arasında kullanılabilir bir korelasyon bırakacağını düşünmeye meyilliyim, ancak bunu kanıtlayacak istatistiksel geçmişe sahip değilim. Genel soruya gelince, emin değilim
sağlam

Yanıtlar:


7

Bu sorunun sorulmasının üzerinden 7 yıl geçti ve hala kimse bu soruna iyi bir çözüm bulamadı gibi görünüyor. Repa'nın paralelleştirme olmadan çalışabilen bir mapM/ traversebenzeri işlevi yoktur. Dahası, son birkaç yılda kaydedilen ilerleme miktarı düşünüldüğünde, bunun da gerçekleşmesi olası görünmüyor.

Haskell'deki birçok dizi kütüphanesinin bayat durumu ve özellik setlerinden genel olarak memnuniyetsizliğim nedeniyle massiv, Repa'dan bazı kavramları ödünç alan, ancak onu tamamen farklı bir düzeye taşıyan bir dizi kitaplığına birkaç yıllık çalışmayı koydum . Giriş için bu kadar yeter.

Bugünden önce, içinde üç monadik harita benzeri işlev vardı massiv(işlevler gibi eşanlamlıları saymazsak:, imapMvb forM.):

  • mapM- rasgele olağan haritalama Monad. Bariz nedenlerden dolayı paralelleştirilemez ve aynı zamanda biraz yavaştır ( mapMbir liste üzerinde olağan çizgileri boyunca yavaş)
  • traversePrim- burada PrimMonadönemli ölçüde daha hızlı olan ile sınırlıyız mapM, ancak bunun nedeni bu tartışma için önemli değil.
  • mapIO- bu, adından da anlaşılacağı gibi, sınırlıdır IO(veya daha doğrusu MonadUnliftIO, ancak bu alakasızdır). İçinde bulunduğumuz için, IOdiziyi çekirdekler olduğu kadar çok sayıda parçaya otomatik olarak bölebiliriz ve IObu parçalardaki her bir öğe üzerindeki eylemi eşlemek için ayrı çalışan iş parçacıkları kullanabiliriz . fmapAynı zamanda paralelleştirilebilir olan saftan farklı IOolarak, haritalama eylemimizin yan etkileri ile birlikte çizelgelemenin determinizminin olmaması nedeniyle burada olmak zorundayız .

Bu soruyu okuduktan sonra, kendi kendime sorunun pratikte çözüldüğünü düşündüm massiv, ama o kadar hızlı değil. İçinde mwc-randomve diğerleri gibi rastgele sayı üreteçleri, random-fubirçok iş parçacığında aynı oluşturucuyu kullanamaz. Bunun anlamı, bulmacanın tek eksik parçasının: "ortaya çıkan her iş parçacığı için yeni bir rastgele tohum çizmek ve her zamanki gibi ilerlemek" idi. Başka bir deyişle, iki şeye ihtiyacım vardı:

  • Çalışan iş parçacığı olacak kadar üreteci başlatan bir işlev
  • ve eylemin hangi iş parçacığında çalıştığına bağlı olarak eşleme işlevine sorunsuz bir şekilde doğru üreteci verecek bir soyutlama.

Ben de aynen öyle yaptım.

Öncelikle soruyla daha alakalı oldukları ve daha sonra daha genel monadik haritaya geçtikleri için özel olarak hazırlanmış randomArrayWSve initWorkerStatesişlevleri kullanarak örnekler vereceğim . İşte tip imzaları:

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)

Aşina olmayanlar massiviçin Compargüman, kullanılacak bir hesaplama stratejisidir, dikkate değer kurucular şunlardır:

  • Seq - herhangi bir iş parçacığını çatallamadan hesaplamayı sırayla çalıştırın
  • Par - Yetenekleriniz kadar iş parçacığı açın ve işi yapmak için bunları kullanın.

mwc-randomPaketi başlangıçta örnek olarak kullanacağım ve daha sonra şu adrese geçeceğim RVarT:

λ> import Data.Massiv.Array
λ> import System.Random.MWC (createSystemRandom, uniformR)
λ> import System.Random.MWC.Distributions (standard)
λ> gens <- initWorkerStates Par (\_ -> createSystemRandom)

Yukarıda, sistem rasgeleliğini kullanarak iş parçacığı başına ayrı bir oluşturucu başlattık, ancak iş parçacığının WorkerIdsalt bir Intindeksi olan argümandan türeterek iş parçacığı başına benzersiz bir tohum da kullanabilirdik . Ve şimdi bu üreteçleri rastgele değerlerle bir dizi oluşturmak için kullanabiliriz:

λ> 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 ]
  ]

ParStrateji kullanarak , schedulerkütüphane üretim işini mevcut işçiler arasında eşit bir şekilde paylaştıracak ve her işçi kendi jeneratörünü kullanacak ve böylece iş parçacığını güvenli hale getirecektir. WorkerStatesEşzamanlı olarak yapılmadığı sürece, hiçbir şey aynı keyfi sayıda tekrar kullanmamızı engellemez , aksi takdirde bir istisna ile sonuçlanır:

λ> 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 ]

Şimdi bir mwc-randomkenara koyarsak, aynı kavramı diğer olası kullanım durumları için aşağıdaki gibi işlevleri kullanarak yeniden kullanabiliriz generateArrayWS:

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)

ve 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)

İşte bununla işlevselliği nasıl kullanılacağı konusunda söz örnektir rvar, random-fuve mersenne-random-pure64kütüphaneler. Burada da kullanabilirdik randomArrayWS, ancak örnek olarak, farklı s'lere sahip bir dizimiz olduğunu RVarTvarsayalım, bu durumda a mapWS:

λ> 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 ]
  ]

Yukarıdaki örnekte saf Mersenne Twister uygulamasının kullanılmasına rağmen, IO'dan kaçamayacağımızı belirtmek önemlidir. Bunun nedeni, deterministik olmayan zamanlamadan kaynaklanmaktadır; bu, hangi çalışanların dizinin hangi parçasını işleyeceğini ve dolayısıyla dizinin hangi bölümü için hangi üretecin kullanılacağını asla bilemeyeceğimiz anlamına gelir. Üst tarafta, eğer jeneratör saf ve bölünebilir ise, örneğin splitmix, o zaman saf, deterministik ve paralelleştirilebilir üretim fonksiyonunu kullanabiliriz: randomArrayama bu zaten ayrı bir hikaye.


Bazı kriterleri görmek isterseniz: alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks
lehins

4

PRNG'lerin doğası gereği sıralı yapısı nedeniyle bunu yapmak muhtemelen iyi bir fikir değildir. Bunun yerine, kodunuzun geçişini aşağıdaki gibi yapmak isteyebilirsiniz:

  1. Bir IO işlevi ( mainveya sizde ne var) bildirin .
  2. İhtiyaç duyduğunuz kadar rastgele sayı okuyun.
  3. (Artık saf olan) sayıları repa işlevlerinize aktarın.

İstatistiksel bağımsızlık oluşturmak için her bir paralel iş parçacığındaki her PRNG'yi yakmak mümkün olabilir mi?
J. Abrahamson

@ J.Abrahamson evet, bu mümkün olacaktır. Cevabımı gör.
lehins
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.