En mi trabajo reciente con Gibbs sampling
, he estado haciendo un gran uso del RVar
cual, en mi opinión, proporciona una interfaz casi ideal para la generación de números aleatorios. Lamentablemente, no he podido utilizar Repa debido a la imposibilidad de utilizar acciones monádicas en los mapas.
Si bien los mapas claramente monádicos no se pueden paralelizar en general, me parece que RVar
puede ser al menos un ejemplo de una mónada donde los efectos se pueden paralelizar de forma segura (al menos en principio; no estoy muy familiarizado con el funcionamiento interno de RVar
) . Es decir, quiero escribir algo como lo siguiente,
drawClass :: Sample -> RVar Class
drawClass = ...
drawClasses :: Array U DIM1 Sample -> RVar (Array U DIM1 Class)
drawClasses samples = A.mapM drawClass samples
donde A.mapM
se vería algo como,
mapM :: ParallelMonad m => (a -> m b) -> Array r sh a -> m (Array r sh b)
Si bien es evidente que cómo funcionaría esto depende de manera crucial de la implementación RVar
y su base RandomSource
, en principio uno pensaría que esto implicaría dibujar una nueva semilla aleatoria para cada hilo generado y proceder como de costumbre.
Intuitivamente, parece que esta misma idea podría generalizarse a algunas otras mónadas.
Entonces, mi pregunta es: ¿Podría uno construir una clase ParallelMonad
de mónadas para las cuales los efectos se puedan paralelizar de manera segura (presumiblemente habitada por, al menos, RVar
)?
¿A qué se podría parecer? ¿Qué otras mónadas podrían habitar esta clase? ¿Otros han considerado la posibilidad de cómo podría funcionar esto en Repa?
Finalmente, si esta noción de acciones monádicas paralelas no se puede generalizar, ¿alguien ve alguna manera agradable de hacer que esto funcione en el caso específico de RVar
(donde sería muy útil)? Renunciar al RVar
paralelismo es un compromiso muy difícil.
fuente
RandomSource
específico. Mi intento ingenuo de dibujar una semilla sería hacer algo simple y probablemente muy incorrecto, como dibujar un vector de elementos (en el caso demwc-random
) y agregar 1 a cada elemento para producir una semilla para el primer trabajador, agregar 2 para el segundo trabajador, etc. Lamentablemente inadecuado si necesita entropía de calidad criptográfica; espero que esté bien si solo necesita un paseo al azar.split
función de System.Random . Tiene la desventaja de producir resultados diferentes (debido a la naturaleza de,split
pero funciona. Sin embargo, estoy tratando de extender esto a las matrices Repa y no tengo mucha suerte. ¿Has progresado en esto o es un error muerto? ¿Fin?split
proporciona una base necesaria, pero tenga en cuenta el comentario sobre la fuente sobre cómosplit
se implementa: "¡No hay base estadística para esto!". Me inclino a pensar que cualquier método para dividir un PRNG dejará una correlación explotable entre sus ramas, pero no tengo los antecedentes estadísticos para demostrarlo. En cuanto a la pregunta general, no estoy seguro de queRespuestas:
Han pasado 7 años desde que se hizo esta pregunta, y todavía parece que a nadie se le ocurrió una buena solución a este problema. Repa no tiene una función
mapM
/traverse
like, ni siquiera una que pueda ejecutarse sin paralelización. Además, considerando la cantidad de progreso que hubo en los últimos años, parece poco probable que suceda.Debido al estado obsoleto de muchas bibliotecas de matrices en Haskell y a mi insatisfacción general con sus conjuntos de funciones, he realizado un par de años de trabajo en una biblioteca de matrices
massiv
, que toma prestados algunos conceptos de Repa, pero lo lleva a un nivel completamente diferente. Suficiente con la intro.Antes de hoy, hubo tres funciones mapa monádica como en
massiv
(sin contar el sinónimo como funciones:imapM
,forM
et al.):mapM
- el mapeo habitual de forma arbitrariaMonad
. No se puede paralelizar por razones obvias y también es un poco lento (en la línea de lo habitualmapM
en una lista lenta)traversePrim
- aquí estamos restringidos aPrimMonad
, que es significativamente más rápido quemapM
, pero la razón de esto no es importante para esta discusión.mapIO
- este, como su nombre indica, está restringido aIO
(o más bienMonadUnliftIO
, pero eso es irrelevante). Debido a que estamos enIO
, podemos dividir automáticamente la matriz en tantos fragmentos como núcleos y usar subprocesos de trabajo separados para mapear laIO
acción sobre cada elemento en esos fragmentos. A diferencia de purefmap
, que también es paralelizable, tenemos que estarIO
aquí debido al no determinismo de la programación combinado con los efectos secundarios de nuestra acción de mapeo.Entonces, una vez que leí esta pregunta, pensé que el problema está prácticamente resuelto
massiv
, pero no tan rápido. Los generadores de números aleatorios, como inmwc-random
y otros enrandom-fu
, no pueden usar el mismo generador en muchos subprocesos. Lo que significa que la única pieza del rompecabezas que me faltaba era: "dibujar una nueva semilla aleatoria para cada hilo generado y proceder como de costumbre". En otras palabras, necesitaba dos cosas:Así que eso es exactamente lo que hice.
Primero daré ejemplos usando las funciones
randomArrayWS
y especialmente diseñadasinitWorkerStates
, ya que son más relevantes para la pregunta y luego pasaré al mapa monádico más general. Aquí están sus firmas de tipo:Para aquellos que no están familiarizados con
massiv
elComp
argumento es una estrategia de cálculo para usar, los constructores notables son:Seq
- ejecutar el cálculo secuencialmente, sin bifurcar ningún hiloPar
- Gire tantos hilos como capacidades haya y utilícelos para hacer el trabajo.Usaré el
mwc-random
paquete como ejemplo inicialmente y luego pasaré aRVarT
:Arriba inicializamos un generador separado por hilo usando la aleatoriedad del sistema, pero también podríamos haber usado una semilla única por hilo derivándola del
WorkerId
argumento, que es un meroInt
índice del trabajador. Y ahora podemos usar esos generadores para crear una matriz con valores aleatorios:Al usar la
Par
estrategia, lascheduler
biblioteca dividirá uniformemente el trabajo de generación entre los trabajadores disponibles y cada trabajador usará su propio generador, lo que lo hará seguro para subprocesos. Nada nos impide reutilizar la mismaWorkerStates
cantidad arbitraria de veces siempre que no se haga al mismo tiempo, lo que de lo contrario resultaría en una excepción:Ahora dejando
mwc-random
de lado podemos reutilizar el mismo concepto para otros posibles casos de uso usando funciones comogenerateArrayWS
:y
mapWS
:Aquí está el ejemplo prometido sobre cómo utilizar esta funcionalidad con
rvar
,random-fu
ymersenne-random-pure64
bibliotecas. También podríamos haber usadorandomArrayWS
aquí, pero por ejemplo, digamos que ya tenemos una matriz con diferentesRVarT
s, en cuyo caso necesitamosmapWS
:Es importante tener en cuenta que, a pesar de que en el ejemplo anterior se está utilizando la implementación pura de Mersenne Twister, no podemos escapar del IO. Esto se debe a la programación no determinista, lo que significa que nunca sabemos cuál de los trabajadores manejará qué parte de la matriz y, en consecuencia, qué generador se utilizará para qué parte de la matriz. Por el lado positivo, si el generador es puro y divisible, como
splitmix
, entonces podemos usar la función de generación pura, determinista y paralelizable:,randomArray
pero eso ya es una historia separada.fuente
Probablemente no sea una buena idea hacer esto debido a la naturaleza inherentemente secuencial de los PRNG. En su lugar, es posible que desee realizar la transición de su código de la siguiente manera:
main
o lo que sea).fuente