Bản đồ song songM trên mảng Repa


90

Trong gần đây của tôi làm việc với Gibbs sampling, tôi đã tận dụng rất tốt trong những RVarmà, theo quan điểm của tôi, cung cấp một giao diện lý tưởng gần này sang thế hệ số ngẫu nhiên. Đáng buồn là tôi không thể sử dụng Repa do không thể sử dụng các hành động đơn lẻ trong bản đồ.

Mặc dù nhìn chung các bản đồ đơn nguyên rõ ràng không thể được song song hóa, nhưng đối với tôi, có vẻ như RVarít nhất một ví dụ về đơn nguyên nơi các hiệu ứng có thể được song song một cách an toàn (ít nhất là về nguyên tắc; tôi không quen lắm với hoạt động bên trong của RVar) . Cụ thể, tôi muốn viết một cái gì đó như sau,

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

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

nơi A.mapMsẽ trông giống như,

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

Mặc dù rõ ràng điều này sẽ hoạt động như thế nào phụ thuộc chủ yếu vào việc thực hiện RVarvà cơ bản của nó RandomSource, về nguyên tắc, người ta sẽ nghĩ rằng điều này sẽ liên quan đến việc vẽ một hạt giống ngẫu nhiên mới cho mỗi luồng được sinh ra và tiến hành như bình thường.

Theo trực giác, có vẻ như ý tưởng tương tự này có thể tổng quát hóa cho một số đơn nguyên khác.

Vì vậy, câu hỏi của tôi là: Liệu người ta có thể xây dựng một lớp ParallelMonadmonads để các hiệu ứng có thể được song song một cách an toàn (ít nhất có thể là người sinh sống RVar)?

Nó có thể trông như thế nào? Những monads nào khác có thể sống trong lớp này? Những người khác đã xem xét khả năng làm thế nào điều này có thể hoạt động trong Repa?

Cuối cùng, nếu khái niệm về các hành động đơn nguyên song song này không thể được khái quát hóa, thì có ai thấy cách nào hay ho để làm cho điều này hoạt động trong trường hợp cụ thể của RVar(nơi nó sẽ rất hữu ích) không? Từ bỏ RVarđể song hành là một sự đánh đổi rất khó khăn.


1
Tôi đoán điểm gắn bó là "vẽ một hạt ngẫu nhiên mới cho mỗi sợi được tạo ra" - bước này nên hoạt động như thế nào và làm thế nào để các hạt được hợp nhất lại khi tất cả các sợi trở lại?
Daniel Wagner

1
Giao diện RVar gần như chắc chắn sẽ cần một số bổ sung để phù hợp với việc tạo ra một trình tạo mới với một hạt giống nhất định. Phải thừa nhận rằng không rõ cơ chế hoạt động của công việc này như thế nào và nó có vẻ khá RandomSourcecụ thể. Nỗ lực ngây thơ của tôi khi vẽ một hạt giống sẽ là làm một việc đơn giản và có thể rất sai, chẳng hạn như vẽ một vectơ gồm các phần tử (trong trường hợp của mwc-random) và thêm 1 vào mỗi phần tử để tạo ra một hạt giống cho công nhân đầu tiên, thêm 2 cho công nhân thứ hai công nhân, v.v. Thật không may nếu bạn cần entropy chất lượng mật mã; hy vọng tốt nếu bạn chỉ cần đi bộ ngẫu nhiên.
bgamari

3
Tôi đã gặp câu hỏi này trong khi cố gắng giải quyết một vấn đề tương tự. Tôi đang sử dụng MonadRandom và System.Random để tính toán ngẫu nhiên đơn lẻ song song. Điều này chỉ có thể thực hiện được với splitchức năng của System.Random . Nó có nhược điểm là tạo ra các kết quả khác nhau (do bản chất của splitnhưng nó hoạt động. Tuy nhiên, tôi đang cố gắng mở rộng điều này cho các mảng Repa và không gặp nhiều may mắn. Bạn có tiến bộ gì với điều này không hay nó đã chết- kết thúc?
Tom Savage

1
Đơn nguyên không có trình tự và sự phụ thuộc giữa các phép tính nghe có vẻ giống với ứng dụng hơn đối với tôi.
John Tyree

1
Tôi đang do dự. Như Tom Savage lưu ý, splitcung cấp một nền tảng cần thiết, nhưng lưu ý nhận xét về nguồn để biết cách splitthực hiện: "- không có nền tảng thống kê cho điều này!". Tôi có khuynh hướng nghĩ rằng bất kỳ phương pháp tách PRNG nào sẽ để lại mối tương quan có thể khai thác được giữa các nhánh của nó, nhưng không có cơ sở thống kê để chứng minh điều đó. Về câu hỏi chung, tôi không chắc chắn rằng
isturdy

Câu trả lời:


7

Đã 7 năm trôi qua kể từ khi câu hỏi này được đặt ra, và dường như vẫn chưa có ai nghĩ ra giải pháp tốt cho vấn đề này. Repa không có chức năng mapM/ traverselike, thậm chí có thể chạy mà không cần song song hóa. Hơn nữa, xem xét mức độ tiến bộ đã có trong vài năm qua, có vẻ như điều đó cũng khó xảy ra.

Do tình trạng cũ kỹ của nhiều thư viện mảng trong Haskell và sự không hài lòng chung của tôi với các bộ tính năng của chúng, tôi đã đặt một vài năm làm việc vào thư viện mảng massiv, nó mượn một số khái niệm từ Repa, nhưng đưa nó lên một cấp độ hoàn toàn khác. Quá đủ với phần giới thiệu.

Trước khi đến ngày hôm nay, đã có ba bản đồ monadic như chức năng trong massiv(không kể các từ đồng nghĩa như chức năng: imapM, forMet al.):

  • mapM- ánh xạ thông thường trong một tùy ý Monad. Không thể song song hóa vì những lý do rõ ràng và cũng hơi chậm (dọc theo dòng thông thường mapMtrên danh sách chậm)
  • traversePrim- ở đây chúng tôi bị hạn chế PrimMonad, nhanh hơn đáng kể mapM, nhưng lý do cho điều này không quan trọng đối với cuộc thảo luận này.
  • mapIO- điều này, như tên cho thấy, bị hạn chế IO(hoặc đúng hơn MonadUnliftIO, nhưng điều đó không liên quan). Bởi vì chúng tôi đang ở trong, IOchúng tôi có thể tự động chia mảng thành nhiều phần khi có lõi và sử dụng các luồng công nhân riêng biệt để ánh xạ IOhành động trên từng phần tử trong các phần đó. Không giống như thuần túy fmap, cũng có thể song song hóa, chúng tôi phải ở IOđây vì tính không xác định của lập lịch kết hợp với các tác dụng phụ của hành động lập bản đồ của chúng tôi.

Vì vậy, khi tôi đọc câu hỏi này, tôi tự nghĩ rằng vấn đề thực tế đã được giải quyết trong đó massiv, nhưng không quá nhanh. Các trình tạo số ngẫu nhiên, chẳng hạn như in mwc-randomvà các trình tạo số khác random-fukhông thể sử dụng cùng một trình tạo số trên nhiều luồng. Có nghĩa là, mảnh ghép duy nhất mà tôi còn thiếu là: "vẽ một hạt ngẫu nhiên mới cho mỗi sợi được sinh ra và tiến hành như bình thường". Nói cách khác, tôi cần hai thứ:

  • Một chức năng sẽ khởi tạo càng nhiều trình tạo càng có nhiều luồng công nhân
  • và một phần trừu tượng sẽ liên tục cung cấp trình tạo chính xác cho chức năng ánh xạ tùy thuộc vào luồng mà hành động đang chạy.

Vì vậy, đó chính xác là những gì tôi đã làm.

Trước tiên, tôi sẽ đưa ra các ví dụ bằng cách sử dụng các hàm randomArrayWSvà được chế tạo đặc biệt initWorkerStates, vì chúng phù hợp hơn với câu hỏi và sau đó chuyển sang bản đồ đơn nguyên tổng quát hơn. Đây là loại chữ ký của họ:

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)

Đối với những người không quen thuộc massiv, Compđối số là một chiến lược tính toán để sử dụng, các hàm tạo đáng chú ý là:

  • Seq - chạy tính toán một cách tuần tự, không có bất kỳ luồng nào
  • Par - tạo ra càng nhiều chủ đề càng tốt và sử dụng chúng để thực hiện công việc.

Tôi sẽ sử dụng mwc-randomgói làm ví dụ ban đầu và sau đó chuyển sang RVarT:

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

Ở trên, chúng tôi đã khởi tạo một trình tạo riêng cho mỗi luồng bằng cách sử dụng tính ngẫu nhiên của hệ thống, nhưng chúng tôi cũng có thể sử dụng một trình tạo duy nhất cho mỗi hạt luồng bằng cách lấy nó từ WorkerIdđối số, chỉ là một chỉ Intmục của worker. Và bây giờ chúng ta có thể sử dụng các trình tạo đó để tạo một mảng với các giá trị ngẫu nhiên:

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

Bằng cách sử dụng Parchiến lược, schedulerthư viện sẽ chia đều công việc tạo ra giữa các công nhân có sẵn và mỗi công nhân sẽ sử dụng bộ tạo riêng của nó, do đó làm cho nó an toàn. Không có gì ngăn cản chúng tôi sử dụng lại cùng WorkerStatesmột số lần tùy ý miễn là nó không được thực hiện đồng thời, nếu không sẽ dẫn đến ngoại lệ:

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

Bây giờ đặt mwc-randomsang một bên, chúng ta có thể sử dụng lại khái niệm tương tự cho các trường hợp sử dụng khả thi khác bằng cách sử dụng các hàm như 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)

Dưới đây là ví dụ hứa về cách sử dụng chức năng này với rvar, random-fumersenne-random-pure64thư viện. Chúng tôi cũng có thể sử dụng randomArrayWSở đây, nhưng vì lợi ích của ví dụ, giả sử chúng tôi đã có một mảng với các RVarTs khác nhau , trong trường hợp đó chúng tôi cần một 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 ]
  ]

Điều quan trọng cần lưu ý là mặc dù thực tế là việc triển khai thuần túy Mersenne Twister đang được sử dụng trong ví dụ trên, chúng ta không thể thoát khỏi IO. Điều này là do lập lịch không xác định, có nghĩa là chúng ta không bao giờ biết được nhân viên nào sẽ xử lý phần nào của mảng và do đó trình tạo nào sẽ được sử dụng cho phần nào của mảng. Ở phía trên, nếu trình tạo thuần túy và có thể phân tách, chẳng hạn như splitmix, thì chúng ta có thể sử dụng hàm tạo thuần túy, xác định và có thể song song hóa:, randomArraynhưng đó đã là một câu chuyện riêng.


Trong trường hợp bạn muốn xem một số điểm chuẩn: alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks
lehins

4

Có lẽ không nên làm điều này do tính chất tuần tự vốn có của PRNG. Thay vào đó, bạn có thể muốn chuyển đổi mã của mình như sau:

  1. Khai báo một hàm IO ( mainhoặc những gì có bạn).
  2. Đọc nhiều số ngẫu nhiên nếu bạn cần.
  3. Chuyển các số (bây giờ thuần túy) vào các hàm repa của bạn.

Có thể ghi từng PRNG trong mỗi luồng song song để tạo ra sự độc lập về mặt thống kê không?
J. Abrahamson

@ J.Abrahamson vâng, nó sẽ có thể. Hãy xem câu trả lời của tôi.
lehins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.