Repa सरणियों पर समानांतर mapM


90

मेरे हालिया काम के साथ Gibbs sampling, मैं इसका बहुत उपयोग कर रहा हूं RVar, मेरे विचार में, यादृच्छिक संख्या पीढ़ी के पास एक आदर्श इंटरफ़ेस प्रदान करता है। अफसोस की बात है, मैं मानचित्रों में अकर्मण्य कार्यों का उपयोग करने में असमर्थता के कारण रेपा का उपयोग करने में असमर्थ रहा हूं।

हालांकि स्पष्ट रूप से मोनैडिक मानचित्रों को सामान्य रूप से समानांतर नहीं किया जा सकता है, यह मुझे लगता है कि RVarकम से कम एक मठ का एक उदाहरण हो सकता है जहां प्रभाव सुरक्षित रूप से समानांतर रूप से हो सकता है (कम से कम सिद्धांत रूप में; मैं आंतरिक कार्यों से बहुत परिचित नहीं हूं RVar) । अर्थात्, मैं निम्नलिखित जैसे कुछ लिखना चाहता हूं,

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

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

जहां A.mapMकुछ ऐसा दिखेगा,

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

जबकि स्पष्ट रूप से यह कैसे काम करेगा , RVarइसके क्रियान्वयन और इसके अंतर्निहित पर महत्वपूर्ण रूप से निर्भर करता है RandomSource, सिद्धांत रूप में किसी को लगता है कि इसमें प्रत्येक थ्रेड के लिए एक नया यादृच्छिक बीज ड्राइंग और सामान्य रूप से आगे बढ़ना शामिल होगा।

सहज रूप से, ऐसा लगता है कि यह एक ही विचार कुछ अन्य साधुओं को सामान्य कर सकता है।

तो, मेरा सवाल यह है: क्या कोई एक ParallelMonadभिक्षु वर्ग का निर्माण कर सकता है जिसके लिए प्रभाव को सुरक्षित रूप से समानांतर किया जा सकता है (संभवत: सबसे कम, कम से कम RVar)?

यह कैसा दिख सकता है? इस वर्ग में अन्य कौन से संन्यासी वास कर सकते हैं? क्या दूसरों ने इस संभावना पर विचार किया है कि यह रेपा में कैसे काम कर सकता है?

अंत में, यदि समानांतर मौद्रिक कार्यों की इस धारणा को सामान्यीकृत नहीं किया जा सकता है, तो क्या किसी को इस मामले में RVar(जहां यह बहुत उपयोगी होगा) इस काम को करने का कोई अच्छा तरीका दिखाई देता है ? RVarसमानता के लिए देना एक बहुत ही कठिन व्यापार है।


1
मुझे लगता है कि चिपके हुए बिंदु "प्रत्येक थ्रेड के लिए एक नया यादृच्छिक बीज खींचना" है - यह कदम कैसे काम करना चाहिए, और सभी थ्रेड वापस आने पर बीज को फिर से कैसे मिलाया जाना चाहिए?
डैनियल वैगनर

1
RVar इंटरफ़ेस में निश्चित रूप से एक दिए गए बीज के साथ एक नया जनरेटर पैदा करने को समायोजित करने के लिए कुछ अतिरिक्त की आवश्यकता होगी। जाहिर है, यह स्पष्ट नहीं है कि इस काम के मैकेनिक्स कैसे हैं और यह काफी RandomSourceविशिष्ट प्रतीत होता है । एक बीज खींचने की मेरी भोली कोशिश कुछ सरल और संभावित रूप से बहुत गलत होगी जैसे तत्वों का सदिश (मामले में mwc-random) और पहले कार्यकर्ता के लिए एक बीज का उत्पादन करने के लिए प्रत्येक तत्व में 1 जोड़ना, दूसरे के लिए 2 जोड़ना कार्यकर्ता, आदि यदि आप क्रिप्टोग्राफिक-गुणवत्ता एन्ट्रापी की आवश्यकता है, तो अपर्याप्त रूप से अपर्याप्त; उम्मीद है कि ठीक है अगर आप सिर्फ एक यादृच्छिक चलने की जरूरत है।
बगमरी

3
मैं इसी तरह की समस्या को हल करने की कोशिश करते हुए इस सवाल पर आया हूं। मैं समानांतर में मोनैडिक यादृच्छिक संगणना के लिए MonadRandom और System.Random का उपयोग कर रहा हूँ। यह केवल System.Random के splitफ़ंक्शन के साथ ही संभव है। इसके विभिन्न परिणामों के उत्पादन का नुकसान है ( splitलेकिन इसकी प्रकृति के कारण यह काम करता है। हालांकि, मैं इसे रेपा सरणियों तक फैलाने की कोशिश कर रहा हूं और बहुत अधिक भाग्य नहीं है। क्या आपने इसके साथ कोई प्रगति की है या यह एक मृत है- अंत?
टॉम सेवज

1
अनुक्रमणों के बीच अनुक्रमण और निर्भरता के बिना मोनाड मेरे लिए अधिक उपयुक्त लगता है।
जॉन टायरी

1
मुझे झिझक हो रही है। टॉम सैवेज नोट्स के रूप में, splitएक आवश्यक आधार प्रदान करता है, लेकिन splitइसे कैसे लागू किया जाता है, इसके लिए स्रोत पर टिप्पणी पर ध्यान दें : "- इसके लिए कोई सांख्यिकीय आधार नहीं!"। मैं यह सोचने के लिए इच्छुक हूं कि PRNG को विभाजित करने का कोई भी तरीका इसकी शाखाओं के बीच शोषक सहसंबंध को छोड़ देगा, लेकिन यह साबित करने के लिए सांख्यिकीय पृष्ठभूमि नहीं है। सामान्य प्रश्न के संबंध में, मैं निश्चित नहीं हूँ कि
19

जवाबों:


7

यह प्रश्न पूछे जाने के बाद 7 साल हो गए हैं, और यह अभी भी ऐसा लगता है जैसे कोई भी इस समस्या का एक अच्छा समाधान लेकर नहीं आया है। Repa एक नहीं है mapM/ traverseसमारोह की तरह भी एक है कि साथ में चलाना बिना चला सकता है। इसके अलावा, पिछले कुछ वर्षों में प्रगति की मात्रा को देखते हुए ऐसा लगता है कि यह संभव नहीं है।

हास्केल में कई सरणी पुस्तकालयों की बासी स्थिति और उनके फ़ीचर सेटों के साथ मेरे संपूर्ण असंतोष के कारण मैंने कुछ वर्षों के काम को एक सरणी पुस्तकालय में रखा है massiv, जो रेपा से कुछ अवधारणाओं को उधार लेता है, लेकिन इसे पूरी तरह से अलग स्तर पर ले जाता है। इंट्रो के साथ पर्याप्त।

आज से पहले, कार्यों में massiv(जैसे कार्यों के पर्यायवाची की गिनती नहीं :) imapM, forMएट अल।) की तरह तीन मानद मानचित्र थे ।

  • mapM- एक मनमाना में सामान्य मानचित्रण Monad। स्पष्ट कारणों के लिए समानांतर नहीं है और यह थोड़ा धीमा है ( mapMकिसी सूची की सामान्य रेखाओं के साथ धीमा)
  • traversePrim- यहां हम प्रतिबंधित हैं PrimMonad, जो कि इससे काफी तेज है mapM, लेकिन इसका कारण इस चर्चा के लिए महत्वपूर्ण नहीं है।
  • mapIO- यह एक, जैसा कि नाम से पता चलता है, यह प्रतिबंधित है IO(या बल्कि MonadUnliftIO, लेकिन यह अप्रासंगिक है)। क्योंकि हम हैं कि IOहम स्वचालित रूप से सरणी को विभाजित कर सकते हैं जैसे कि कई कोर हैं और IOउन टुकड़ों में प्रत्येक तत्व पर कार्रवाई को मैप करने के लिए अलग-अलग कार्यकर्ता थ्रेड्स का उपयोग करें । शुद्ध के विपरीत fmap, जो कि समानांतर भी है, हमें IOअपनी मैपिंग कार्रवाई के दुष्प्रभावों के साथ संयुक्त निर्धारण के गैर-निर्धारणवाद के कारण यहां होना चाहिए ।

इसलिए, एक बार जब मैंने इस प्रश्न को पढ़ा, तो मैंने खुद से सोचा कि समस्या व्यावहारिक रूप से हल हो गई है massiv, लेकिन इतनी जल्दी नहीं। रैंडम नंबर जेनरेटर, जैसे कि mwc-randomऔर अन्य random-fuकई थ्रेड में एक ही जनरेटर का उपयोग नहीं कर सकते हैं। जिसका मतलब है, पहेली का एकमात्र टुकड़ा जो मुझे याद आ रहा था: "प्रत्येक थ्रेड के लिए एक नया यादृच्छिक बीज खींचना और सामान्य रूप से आगे बढ़ना"। दूसरे शब्दों में, मुझे दो चीजों की आवश्यकता थी:

  • एक ऐसा कार्य जो कई जनरेटरों को इनिशियलाइज़ करेगा, क्योंकि इसमें वर्कर थ्रेड्स होंगे
  • और एक अमूर्त जो मूल रूप से मैपिंग फ़ंक्शन के लिए सही जनरेटर देगा जो उस थ्रेड पर निर्भर करता है कि कार्रवाई चल रही है।

तो ठीक यही मैंने किया।

पहले मैं विशेष रूप से तैयार किए गए randomArrayWSऔर initWorkerStatesकार्यों का उपयोग करके उदाहरण दूंगा , क्योंकि वे प्रश्न के लिए अधिक प्रासंगिक हैं और बाद में अधिक सामान्य मोनडिक मानचित्र पर चले जाते हैं। यहां उनके प्रकार हस्ताक्षर हैं:

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)

जो लोग परिचित नहीं हैं massiv, उनके लिए Compतर्क का उपयोग करने के लिए एक संगणना रणनीति है, उल्लेखनीय निर्माता हैं:

  • Seq क्रमिक रूप से चलाने के लिए, किसी भी धागे को फोर्क किए बिना
  • Par - उतने ही धागे उतारे जितने में क्षमताएं हों और काम करने के लिए उनका इस्तेमाल करें।

मैं mwc-randomशुरुआत में एक उदाहरण के रूप में पैकेज का उपयोग करूंगा और बाद में आगे बढ़ूंगा RVarT:

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

ऊपर हमने सिस्टम यादृच्छिकता का उपयोग करते हुए एक अलग जनरेटर प्रति थ्रेड को इनिशियलाइज़ किया, लेकिन हम इसे WorkerIdतर्क से प्राप्त करके एक थ्रेड प्रति अद्वितीय बीज का उपयोग कर सकते थे , जो Intकि कार्यकर्ता का एक मात्र सूचकांक है। और अब हम उन जनरेटर का उपयोग यादृच्छिक मूल्यों के साथ एक सरणी बनाने के लिए कर सकते हैं:

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

Parरणनीति का उपयोग करके schedulerपुस्तकालय समान रूप से उपलब्ध श्रमिकों के बीच पीढ़ी के काम को विभाजित करेगा और प्रत्येक कार्यकर्ता अपने स्वयं के जनरेटर का उपयोग करेगा, इस प्रकार यह धागे को सुरक्षित बनाता है। WorkerStatesजब तक यह समवर्ती रूप से नहीं किया जाता है, तब तक कोई भी हमें एक ही तरह की मनमानी संख्या का पुन: उपयोग करने से रोकता है , अन्यथा इसका परिणाम होगा:

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

अब mwc-randomपक्ष में रखते हुए हम अन्य संभावित उपयोग मामलों के लिए समान अवधारणा का पुन: उपयोग कर सकते हैं जैसे कार्यों का उपयोग करके 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)

और 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)

यहाँ इस वादे का उदाहरण दिया गया है कि इस कार्यक्षमता का उपयोग कैसे करें rvar, random-fuऔर mersenne-random-pure64पुस्तकालयों के साथ। हम randomArrayWSयहां भी इस्तेमाल कर सकते थे , लेकिन उदाहरण के लिए मान लें कि हमारे पास पहले से ही अलग-अलग RVarTएस के साथ एक सरणी है , जिस स्थिति में हमें इसकी आवश्यकता है 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 ]
  ]

यह ध्यान रखना महत्वपूर्ण है, कि इस तथ्य के बावजूद कि उपरोक्त उदाहरण में मेरसेन ट्विस्टर के शुद्ध कार्यान्वयन का उपयोग किया जा रहा है, हम IO से बच नहीं सकते। यह गैर-नियतात्मक समय-निर्धारण के कारण है, जिसका अर्थ है कि हम कभी भी यह नहीं जानते हैं कि कौन सा कार्यकर्ता किस सार को संभाल रहा है और परिणामस्वरूप सरणी के किस भाग के लिए किस जनरेटर का उपयोग किया जाएगा। ऊपर की तरफ, यदि जनरेटर शुद्ध और शानदार है, जैसे कि splitmix, तो हम शुद्ध, निर्धारक और समानांतर पीढ़ी के कार्य का उपयोग कर सकते हैं: randomArrayलेकिन वह पहले से ही एक अलग कहानी है।


मामले में आप कुछ बेंचमार्क देखना चाहेंगे: alexey.kuleshevi.ch/blog/2019/12/12/21/random-benchmark
lehins

4

PRNGs की स्वाभाविक रूप से अनुक्रमिक प्रकृति के कारण ऐसा करना शायद अच्छा विचार नहीं है। इसके बजाय, आप अपने कोड को इस प्रकार बदलना चाहते हैं:

  1. एक IO फ़ंक्शन घोषित करें (और main, या आपके पास क्या है)।
  2. जितनी जरूरत हो उतने रैंडम नंबर पढ़ें।
  3. अपने रेपा फ़ंक्शंस पर (अब शुद्ध) नंबर पास करें।

क्या सांख्यिकीय स्वतंत्रता बनाने के लिए प्रत्येक समानांतर धागे में प्रत्येक PRNG को जलाना संभव होगा?
जे। अब्राहमसन

@ J.Abrahamson हाँ, यह संभव होगा। मेरा जवाब देखिए।
लेहिन्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.