packages feed

fei-base-1.0.0: c-apis/MXNet/Base/Raw/NDArray.chs

{-# LANGUAGE ScopedTypeVariables #-}
module MXNet.Base.Raw.NDArray where

import RIO
import RIO.List (unzip)
import Foreign.Marshal (alloca, withArray, peekArray)
import Foreign.Storable (Storable(..))
import Foreign.Concurrent (newForeignPtr)
import Foreign.ForeignPtr (touchForeignPtr)
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import Foreign.C.Types
import Foreign.C.String (CString)
import Foreign.Ptr
import GHC.Generics (Generic)
import Control.DeepSeq (NFData(..), rwhnf)
import Control.Monad ((>=>))

{# import MXNet.Base.Raw.Common #}

#include <mxnet/c_api.h>

{# typedef size_t CSize#}
{# default in `CSize' [size_t] fromIntegral #}
{# typedef mx_uint MX_UINT#}
{# default in `MX_UINT' [mx_uint] id #}

-- NDArray
{#
pointer NDArrayHandle foreign newtype
#}

deriving instance Generic NDArrayHandle
deriving instance Show NDArrayHandle
instance NFData NDArrayHandle where
    rnf = rwhnf

type NDArrayHandlePtr = Ptr NDArrayHandle

touchNDArrayHandle :: NDArrayHandle -> IO ()
touchNDArrayHandle (NDArrayHandle fptr) = touchForeignPtr fptr

newNDArrayHandle :: NDArrayHandlePtr -> IO NDArrayHandle
newNDArrayHandle ptr = do
    hdl <- newForeignPtr ptr (mxNDArrayFree ptr)
    return $ NDArrayHandle hdl

peekNDArrayHandle :: Ptr NDArrayHandlePtr -> IO NDArrayHandle
peekNDArrayHandle = peek >=> newNDArrayHandle

withNDArrayHandleArray :: [NDArrayHandle] -> (Ptr NDArrayHandlePtr -> IO r) -> IO r
withNDArrayHandleArray array io = do
    let unNDArrayHandle (NDArrayHandle fptr) = fptr
    r <- withArray (map (unsafeForeignPtrToPtr . unNDArrayHandle) array) io
    mapM_ (touchForeignPtr . unNDArrayHandle) array
    return r

{#
fun MXNDArrayFree as mxNDArrayFree_
    {
        id `NDArrayHandlePtr'
    } -> `CInt'
#}

mxNDArrayFree :: NDArrayHandlePtr -> IO ()
mxNDArrayFree = checked . mxNDArrayFree_

{#
fun MXNDArrayCreateNone as mxNDArrayCreateNone_
    {
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}

mxNDArrayCreateNone :: IO NDArrayHandle
mxNDArrayCreateNone = checked mxNDArrayCreateNone_

{#
fun MXNDArrayCreate as mxNDArrayCreate_
    {
        withArray* `[MX_UINT]',
        id `MX_UINT',
        `CInt',
        `CInt',
        `CInt',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}

mxNDArrayCreate :: [Int] -> Int -> Int -> Bool -> IO NDArrayHandle
mxNDArrayCreate shape devtype devid delay_alloc = do
    let shape_   = fromIntegral <$> shape
        dim_     = fromIntegral (length shape_)
        devtype_ = fromIntegral devtype
        devid_   = fromIntegral devid
    checked $ mxNDArrayCreate_ shape_ dim_ devtype_ devid_ (if delay_alloc then 1 else 0)

{#
fun MXNDArrayCreateEx as mxNDArrayCreateEx_
    {
        withArray* `[MX_UINT]',
        id `MX_UINT',
        `CInt',
        `CInt',
        `CInt',
        `CInt',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}

mxNDArrayCreateEx :: [Int] -> Int -> Int -> Bool -> Int -> IO NDArrayHandle
mxNDArrayCreateEx shape devtype devid delay_alloc dtype = do
    let shape_   = fromIntegral <$> shape
        dim_     = fromIntegral (length shape_)
        devtype_ = fromIntegral devtype
        devid_   = fromIntegral devid
        dtype_   = fromIntegral dtype
    checked $ mxNDArrayCreateEx_ shape_ dim_ devtype_ devid_ (if delay_alloc then 1 else 0) dtype_


{#
fun MXNDArraySyncCopyFromCPU as mxNDArraySyncCopyFromCPU_
    {
        `NDArrayHandle',
        id `Ptr ()',
        `CSize'
    } -> `CInt'
#}

mxNDArraySyncCopyFromCPU :: NDArrayHandle -> Ptr () -> Int -> IO ()
mxNDArraySyncCopyFromCPU array ptr size =
    checked $ mxNDArraySyncCopyFromCPU_ array ptr (fromIntegral size)

{#
fun MXNDArraySyncCopyToCPU as mxNDArraySyncCopyToCPU_
    {
        `NDArrayHandle',
        id `Ptr ()',
        `CSize'
    } -> `CInt'
#}

mxNDArraySyncCopyToCPU :: NDArrayHandle -> Ptr () -> Int -> IO ()
mxNDArraySyncCopyToCPU array ptr size =
    checked $ mxNDArraySyncCopyToCPU_ array ptr (fromIntegral size)

{#
fun MXNDArraySyncCopyFromNDArray as mxNDArraySyncCopyFromNDArray_
    {
        `NDArrayHandle',
        `NDArrayHandle',
        `CInt'
    } -> `CInt'
#}

mxNDArraySyncCopyFromNDArray :: NDArrayHandle -> NDArrayHandle -> Int -> IO ()
mxNDArraySyncCopyFromNDArray array_dst array_src blob =
    checked $ mxNDArraySyncCopyFromNDArray_ array_dst array_src (fromIntegral blob)

{#
fun MXNDArrayWaitToRead as mxNDArrayWaitToRead_
    {
        `NDArrayHandle'
    } -> `CInt'
#}

mxNDArrayWaitToRead :: NDArrayHandle -> IO ()
mxNDArrayWaitToRead = checked . mxNDArrayWaitToRead_

{#
fun MXNDArrayWaitToWrite as mxNDArrayWaitToWrite_
    {
        `NDArrayHandle'
    } -> `CInt'
#}

mxNDArrayWaitToWrite :: NDArrayHandle -> IO ()
mxNDArrayWaitToWrite = checked . mxNDArrayWaitToWrite_

{#
fun MXNDArrayWaitAll as mxNDArrayWaitAll_
    {
    } -> `CInt'
#}

mxNDArrayWaitAll :: IO ()
mxNDArrayWaitAll = checked mxNDArrayWaitAll_

#if MXNet_MAJOR==1 && MXNet_MINOR<6
{#
fun MXNDArraySlice as mxNDArraySlice_
    {
        `NDArrayHandle',
        `MX_UINT',
        `MX_UINT',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}
#else
{#
fun MXNDArraySlice as mxNDArraySlice_
    {
        `NDArrayHandle',
        `CUInt',
        `CUInt',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}
#endif

mxNDArraySlice :: NDArrayHandle -> Int -> Int -> IO NDArrayHandle
mxNDArraySlice array begin end = do
    let begin_ = fromIntegral begin
        end_   = fromIntegral end
    checked $ mxNDArraySlice_ array begin_ end_


#if MXNet_MAJOR==1 && MXNet_MINOR<6
{#
fun MXNDArrayAt as mxNDArrayAt_
    {
        `NDArrayHandle',
        `MX_UINT',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}
#else
{#
fun MXNDArrayAt as mxNDArrayAt_
    {
        `NDArrayHandle',
        `CUInt',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}
#endif

mxNDArrayAt :: NDArrayHandle -> Int -> IO NDArrayHandle
mxNDArrayAt array index = do
    checked $ mxNDArrayAt_ array (fromIntegral index)

{#
fun MXNDArrayGetStorageType as mxNDArrayGetStorageType_
    {
        `NDArrayHandle',
        alloca- `CInt' peek*
    } -> `CInt'
#}

mxNDArrayGetStorageType :: NDArrayHandle -> IO Int
mxNDArrayGetStorageType array = do
    storageType <- checked $ mxNDArrayGetStorageType_ array
    return $ fromIntegral storageType

{#
fun MXNDArrayReshape as mxNDArrayReshape_
    {
        `NDArrayHandle',
        `CInt',
        withArray* `[CInt]',
        alloca- `NDArrayHandle' peekNDArrayHandle*
    } -> `CInt'
#}

mxNDArrayReshape :: NDArrayHandle -> [Int] -> IO NDArrayHandle
mxNDArrayReshape array shape = do
    let shape_ = fromIntegral <$> shape
        num_   = fromIntegral $ length shape_
    checked $ mxNDArrayReshape_ array num_ shape_

{#
fun MXNDArrayGetShape as mxNDArrayGetShape_
    {
        `NDArrayHandle',
        alloca- `MX_UINT' peek*,
        alloca- `Ptr MX_UINT' peek*
    } -> `CInt'
#}

mxNDArrayGetShape :: NDArrayHandle -> IO [Int]
mxNDArrayGetShape array = do
    (size, ptr) <- checked $ mxNDArrayGetShape_ array
    shape <- peekArray (fromIntegral size) ptr
    return $ fromIntegral <$> shape

-- MXImperativeInvoke is hacky.
-- num-outputs
--   0: create new NDArrayHandler in the array-of-NDArrayHandle
--   length of array-of-NDArrayHandle (non-0): reuse NDArrayHandler in the array-of-NDArrayHandle
{#
fun MXImperativeInvoke as mxImperativeInvoke_
    {
        `AtomicSymbolCreator',
        `CInt',
        withNDArrayHandleArray* `[NDArrayHandle]',
        id `Ptr CInt',                    -- num-outputs
        id `Ptr (Ptr NDArrayHandlePtr)',  -- array-of-NDArrayHandle
        `CInt',
        withCStringArrayT* `[Text]',
        withCStringArrayT* `[Text]'
    } -> `CInt'
#}

-- | Invoke a nnvm op and imperative function.
mxImperativeInvoke :: HasCallStack
                   => AtomicSymbolCreator
                   -> [NDArrayHandle]
                   -> [(Text, Text)]
                   -> Maybe [NDArrayHandle]
                   -> IO [NDArrayHandle]
mxImperativeInvoke creator inputs params outputs = do
    let (keys, values) = unzip params
        ninput = fromIntegral $ length inputs
        nparam = fromIntegral $ length params
    case outputs of
        Nothing -> alloca $ \(pn :: Ptr CInt) ->
            alloca $ \(pp :: Ptr (Ptr NDArrayHandlePtr)) -> do
                poke pn 0
                poke pp C2HSImp.nullPtr
                checked $ mxImperativeInvoke_ creator ninput inputs pn pp nparam keys values
                n' <- peek pn
                p' <- peek pp
                if n' == 0
                    then return []
                    else do
                        pa <- peekArray (fromIntegral n') (p' :: Ptr NDArrayHandlePtr)
                        mapM newNDArrayHandle pa
        Just out -> alloca $ \(pn :: Ptr CInt) ->
            alloca $ \(pp :: Ptr (Ptr NDArrayHandlePtr)) -> do
                n' <- withNDArrayHandleArray out $ \p' -> do
                    poke pn (fromIntegral $ length out)
                    poke pp p'
                    checked $ mxImperativeInvoke_ creator ninput inputs pn pp nparam keys values
                    peek pn
                return $ take (fromIntegral n') out

{#
fun MXNDArrayGetContext as mxNDArrayGetContext_
    {
        `NDArrayHandle',
        alloca- `CInt' peek*,
        alloca- `CInt' peek*
    } -> `CInt'
#}

mxNDArrayGetContext :: NDArrayHandle -> IO (Int, Int)
mxNDArrayGetContext handle = do
    (devtyp, devidx) <- checked $ mxNDArrayGetContext_ handle
    return (fromIntegral devtyp, fromIntegral devidx)


#if MXNet_MAJOR==1 && MXNet_MINOR<6
{#
fun MXNDArraySave as mxNDArraySave_
    {
        withCStringT* `Text',
        `MX_UINT',
        withNDArrayHandleArray* `[NDArrayHandle]',
        withCStringArrayT* `[Text]'
    } -> `CInt'
#}
#else
{#
fun MXNDArraySave as mxNDArraySave_
    {
        withCStringT* `Text',
        `CUInt',
        withNDArrayHandleArray* `[NDArrayHandle]',
        withCStringArrayT* `[Text]'
    } -> `CInt'
#}
#endif

mxNDArraySave ::  Text -> [(Text, NDArrayHandle)] -> IO ()
mxNDArraySave filename keyvals = do
    let num = length keyvals
        (keys, vals) = unzip keyvals
    checked $ mxNDArraySave_ filename (fromIntegral num) vals keys

{#
fun MXNDArrayLoad as mxNDArrayLoad_
    {
        withCStringT* `Text',
        alloca- `MX_UINT' peek*,
        alloca- `Ptr NDArrayHandlePtr' peek*,
        alloca- `MX_UINT' peek*,
        alloca- `Ptr CString' peek*
    } -> `CInt'
#}

mxNDArrayLoad :: Text -> IO [(Text, NDArrayHandle)]
mxNDArrayLoad path = do
    (numArrays, ptrArrays, numNames, ptrNames) <- checked $ mxNDArrayLoad_ path
    pa <- peekArray (fromIntegral numArrays) ptrArrays
    arrays <- mapM newNDArrayHandle pa
    names <- peekCStringArrayT (fromIntegral numNames) ptrNames
    return $ zip names arrays