Repa 배열의 병렬 mapM


90

최근 작업 에서 Gibbs sampling, 나는 RVar난수 생성에 거의 이상적인 인터페이스를 제공하는 것을 잘 활용했습니다 . 안타깝게도지도에서 모나 딕 액션을 사용할 수 없어서 Repa를 사용할 수 없었습니다.

명확하게 모나 딕 맵은 일반적으로 병렬화 될 수 없지만 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있습니까? (아마도 적어도에 의해 거주 할 수 있습니다 )?

어떤 모습일까요? 이 클래스에 어떤 다른 모나드가있을 수 있습니까? 다른 사람들이 이것이 Repa에서 어떻게 작동 할 수 있는지에 대한 가능성을 고려 했습니까?

마지막으로, 병렬 모나 딕 동작의 개념을 일반화 할 수없는 경우, RVar(매우 유용한 경우) 특정 경우에이 작업을 수행 할 수있는 좋은 방법이 있습니까? RVar병렬 처리를 포기 하는 것은 매우 어려운 트레이드 오프입니다.


1
스티 킹 포인트는 "생성 된 각 스레드에 대해 새로운 임의의 시드를 그리는 것"이라고 생각합니다.이 단계는 어떻게 작동해야하며 모든 스레드가 반환되면 시드를 어떻게 다시 병합해야합니까?
Daniel Wagner

1
RVar 인터페이스는 주어진 시드로 새 생성기를 생성하는 것을 수용하기 위해 거의 확실히 몇 가지 추가가 필요합니다. 물론,이 작동 원리가 어떻게 작동하는지 명확하지 않고 매우 RandomSource구체적으로 보입니다 . 시드를 그리는 내 순진한 시도는 요소의 벡터를 그리고 (의 경우 mwc-random) 각 요소에 1을 추가하여 첫 번째 작업자에 대한 시드를 생성하고 두 번째에 2를 추가 하는 등 간단하고 매우 잘못된 작업을 수행하는 것 입니다. 작업자 등. 암호화 품질의 엔트로피가 필요한 경우 매우 부적절합니다. 임의의 산책이 필요하다면 좋을 것입니다.
bgamari

3
비슷한 문제를 해결하려고 노력하면서이 질문을 보았습니다. MonadRandom과 System.Random을 병렬로 모나 딕 랜덤 계산에 사용하고 있습니다. 이것은 System.Random의 split기능으로 만 가능 합니다. 그것은 다른 결과를 생성하는 단점이 있습니다 (특성상 split작동하지만 작동합니다. 그러나 나는 이것을 Repa 어레이로 확장하려고 노력하고 있으며 많은 운이 없습니다. 이것으로 진전을 이루었습니까 아니면 죽었습니까? 끝?
Tom Savage

1
시퀀싱과 계산 사이의 종속성이없는 모나드는 나에게 더 실용적으로 들립니다.
John Tyree 2013 년

1
나는 망설 인다. Tom Savage가 지적했듯이, split필요한 기반을 제공하지만 split구현 방법에 대한 소스에 대한 설명에 유의하십시오 . "-이것에 대한 통계적 기반이 없습니다!". PRNG를 분할하는 모든 방법이 분기간에 악용 가능한 상관 관계를 남길 것이라고 생각하지만이를 증명할 통계적 배경이 없습니다. 일반적인 질문에 대해서는 확실하지 않습니다
isturdy

답변:


7

이 질문을받은 지 7 년이 지났지 만 아직 아무도이 문제에 대한 좋은 해결책을 찾지 못한 것 같습니다. Repa에는 mapM/ traverselike 기능이 없으며 병렬화없이 실행할 수있는 기능도 있습니다. 더욱이 지난 몇 년 동안의 진전 정도를 고려할 때도 그렇게 될 것 같지 않습니다.

Haskell에있는 많은 어레이 라이브러리의 오래된 상태와 기능 세트에 대한 전반적인 불만족으로 인해 저는 massivRepa에서 일부 개념을 차용했지만 완전히 다른 수준으로 가져가는 어레이 라이브러리에 몇 년 동안 작업 했습니다. 인트로로 충분합니다.

오늘 이전에는 함수와 같은 세 개의 모나드 맵이있었습니다 massiv(함수와 같은 동의어는 포함하지 않음 : imapM, forMet al.).

  • mapM-임의의 Monad. 명백한 이유 때문에 병렬화 할 수없고 약간 느립니다 ( mapM목록에 비해 일반적인 줄을 따라 느림)
  • traversePrim-여기서 우리는로 제한되어 있습니다 PrimMonad. 이는보다 훨씬 빠르지 만이 mapM논의에서는 그 이유가 중요하지 않습니다.
  • mapIO-이것은 이름에서 알 수 있듯이로 제한됩니다 IO(또는 오히려 MonadUnliftIO,하지만 관련성이 없음). 우리는 IO코어 수만큼 자동으로 배열을 분할하고 별도의 작업자 스레드를 사용 IO하여 해당 청크의 각 요소에 대한 작업 을 매핑 할 수 있습니다. fmap병렬화도 가능한 pure와 달리 , 우리는 IO매핑 작업의 부작용과 결합 된 스케줄링의 비결 정성 때문에 여기에 있어야합니다 .

그래서이 질문을 읽었을 때 문제는 실제로에서 해결 massiv되었지만 그렇게 빠르지 는 않다고 생각했습니다 . in mwc-random및 in 과 random-fu같은 난수 생성기는 여러 스레드에서 동일한 생성기를 사용할 수 없습니다. 즉, 내가 놓친 유일한 퍼즐 조각은 "생성 된 각 스레드에 대해 새로운 임의의 시드를 그리고 평소대로 진행하는 것"이었습니다. 즉, 두 가지가 필요했습니다.

  • 작업자 스레드 수만큼 생성기를 초기화하는 함수
  • 액션이 실행되는 스레드에 따라 매핑 함수에 올바른 생성기를 원활하게 제공하는 추상화.

그게 바로 제가 한 일입니다.

먼저 질문과 더 관련이 있는 특수 제작 randomArrayWSinitWorkerStates기능을 사용하여 예제를 제공 하고 나중에보다 일반적인 모나드 맵으로 이동합니다. 다음은 유형 서명입니다.

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

Mersenne Twister의 순수한 구현이 위의 예에서 사용된다는 사실에도 불구하고 IO를 벗어날 수 없다는 점에 유의해야합니다. 이는 비 결정적 스케줄링 때문입니다. 즉, 작업자 중 어느 하나가 어레이의 어떤 청크를 처리 할 것인지, 결과적으로 어떤 생성기가 어레이의 어느 부분에 사용될 것인지 알 수 없습니다. 위쪽으로, 생성기가 순수하고 분할 가능한 경우 (예 :) splitmix순수하고 결정적이며 병렬화 가능한 생성 함수를 사용할 수 randomArray있지만 이는 이미 별도의 이야기입니다.


벤치 마크를보고 싶은 경우 : alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks
lehins 19

4

PRNG의 본질적으로 순차적 인 특성으로 인해이 작업을 수행하는 것은 좋은 생각이 아닙니다. 대신 다음과 같이 코드를 전환 할 수 있습니다.

  1. IO 함수를 선언합니다 ( main또는 무엇을 가지고 있는지).
  2. 필요한만큼 난수를 읽으십시오.
  3. repa 함수에 (이제 순수한) 숫자를 전달하십시오.

통계적 독립성을 만들기 위해 각 병렬 스레드에서 각 PRNG를 번인 할 수 있습니까?
J. Abrahamson 2013 년

@ J.Abrahamson 예, 가능할 것입니다. 내 대답을 참조하십시오.
lehins
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.