packages feed

hhlo-0.1.0.0: src/HHLO/Runtime/Device.hs

{-# LANGUAGE ForeignFunctionInterface #-}

module HHLO.Runtime.Device
    ( addressableDevices
    , deviceId
    , deviceKind
    , defaultGPUDevice
    ) where

import Data.Char (toLower)
import Foreign.C
import Foreign.Marshal.Alloc (alloca)
import Foreign.Marshal.Array (peekArray)
import Foreign.Ptr
import Foreign.Storable (peek)

import HHLO.Runtime.PJRT.FFI
import HHLO.Runtime.PJRT.Types
import HHLO.Runtime.PJRT.Error

-- | List all devices that this client can directly execute on.
addressableDevices :: PJRTApi -> PJRTClient -> IO [PJRTDevice]
addressableDevices api client = do
    count <- alloca $ \countPtr -> do
        checkError (unApi api) $
            c_pjrtClientAddressableDeviceCount (unApi api) (unClient client) countPtr
        peek countPtr
    mapM (getDevice api client . fromIntegral) [0 .. count - 1]
  where
    getDevice :: PJRTApi -> PJRTClient -> Int -> IO PJRTDevice
    getDevice a c idx =
        alloca $ \devPtrPtr -> do
            checkError (unApi a) $
                c_pjrtClientAddressableDevice (unApi a) (unClient c) (fromIntegral idx) devPtrPtr
            PJRTDevice <$> peek devPtrPtr

-- | Get the stable ID of a device.
deviceId :: PJRTApi -> PJRTDevice -> IO Int
deviceId api dev =
    alloca $ \idPtr -> do
        checkError (unApi api) $
            c_pjrtDeviceId (unApi api) (unDevice dev) idPtr
        fromIntegral <$> peek idPtr

-- | Get the platform kind of a device, e.g. @"CPU"@ or @"CUDA"@.
deviceKind :: PJRTApi -> PJRTDevice -> IO String
deviceKind api dev =
    alloca $ \kindPtr -> do
        alloca $ \lenPtr -> do
            checkError (unApi api) $
                c_pjrtDeviceKind (unApi api) (unDevice dev) kindPtr lenPtr
            cstr <- peek kindPtr
            len  <- peek lenPtr
            peekCStringLen (cstr, fromIntegral len)

-- | Return the first non-CPU device, or 'Nothing' if only CPUs exist.
-- For NVIDIA GPU plugins the kind string is the GPU name (e.g.
-- @"NVIDIA GeForce RTX 5090"@), so we detect CPU by checking for the
-- substring @"cpu"@ (case-insensitive).
defaultGPUDevice :: PJRTApi -> PJRTClient -> IO (Maybe PJRTDevice)
defaultGPUDevice api client = do
    devs <- addressableDevices api client
    let findGPU [] = return Nothing
        findGPU (d:ds) = do
            kindStr <- deviceKind api d
            if map toLower kindStr == "cpu"
                then findGPU ds
                else return (Just d)
    findGPU devs

unApi :: PJRTApi -> Ptr PJRTApi
unApi (PJRTApi p) = p

unClient :: PJRTClient -> Ptr PJRTClient
unClient (PJRTClient p) = p

unDevice :: PJRTDevice -> Ptr PJRTDevice
unDevice (PJRTDevice p) = p