| {-# LANGUAGE OverloadedStrings #-} |
|
|
| |
| |
| |
| |
|
|
| module LiquidLean.QuantumPiper.WebGPU |
| ( WebGPUDevice(..) |
| , WebGPUBuffer(..) |
| , WebGPUShader(..) |
| , initWebGPU |
| , createBuffer |
| , createShader |
| , dispatchCompute |
| , readBuffer |
| ) where |
|
|
| import Data.Text (Text) |
| import qualified Data.Text as T |
| import Data.ByteString (ByteString) |
| import qualified Data.ByteString as BS |
| import Data.Word (Word32, Word64) |
| import Foreign.C.Types |
| import System.Process (readProcessWithExitCode) |
| import Control.Exception (try, catch, SomeException) |
| import Data.Aeson (Value, encode, decode, object, (.=)) |
|
|
| |
| |
| |
|
|
| data WebGPUDevice = WebGPUDevice |
| { wgdDeviceId :: Text |
| , wgdBackend :: GPUBackend |
| , wgdMaxComputeWorkgroups :: (Word32, Word32, Word32) |
| , wgdMaxWorkgroupSize :: Word32 |
| } deriving (Show) |
|
|
| data GPUBackend |
| = Metal |
| | Vulkan |
| | DirectX12 |
| | OpenGL |
| deriving (Show, Eq) |
|
|
| data WebGPUBuffer = WebGPUBuffer |
| { wgbBufferId :: Text |
| , wgbSize :: Word64 |
| , wgbUsage :: BufferUsage |
| , wgbData :: Maybe ByteString |
| } deriving (Show) |
|
|
| data BufferUsage |
| = StorageRead |
| | StorageWrite |
| | Uniform |
| | CopyDst |
| | CopySrc |
| deriving (Show, Eq) |
|
|
| data WebGPUShader = WebGPUShader |
| { wgsShaderModule :: Text |
| , wgsEntryPoint :: Text |
| , wgsWorkgroupSize :: (Word32, Word32, Word32) |
| , wgsCode :: Text |
| } deriving (Show) |
|
|
| |
| |
| |
|
|
| initWebGPU :: IO (Either String WebGPUDevice) |
| initWebGPU = do |
| result <- try $ do |
| |
| backend <- detectGPUBackend |
|
|
| case backend of |
| Just b -> do |
| |
| let deviceId = case b of |
| Metal -> "metal-adapter" |
| Vulkan -> "vulkan-adapter" |
| DirectX12 -> "dx12-adapter" |
| OpenGL -> "webgl-adapter" |
|
|
| pure (WebGPUDevice |
| { wgdDeviceId = deviceId |
| , wgdBackend = b |
| , wgdMaxComputeWorkgroups = (65535, 65535, 65535) |
| , wgdMaxWorkgroupSize = 256 |
| }) |
|
|
| Nothing -> fail "No GPU backend available" |
|
|
| case result of |
| Left (e :: SomeException) -> pure (Left $ "WebGPU init failed: " ++ show e) |
| Right device -> pure (Right device) |
|
|
| detectGPUBackend :: IO (Maybe GPUBackend) |
| detectGPUBackend = do |
| |
| metalResult <- readProcessWithExitCode "system_profiler" ["SPDisplaysDataType"] "" |
| if "Metal" `elem` words (fst3 metalResult) |
| then pure (Just Metal) |
| else do |
| |
| vulkanResult <- readProcessWithExitCode "vulkaninfo" [] "" |
| if "NVIDIA" `elem` words (fst3 vulkanResult) || "AMD" `elem` words (fst3 vulkanResult) |
| then pure (Just Vulkan) |
| else do |
| |
| dxResult <- readProcessWithExitCode "dxdiag" [] "" |
| if not (null (fst3 dxResult)) |
| then pure (Just DirectX12) |
| else pure (Just OpenGL) |
|
|
| fst3 :: (a, b, c) -> a |
| fst3 (x, _, _) = x |
|
|
| |
| |
| |
|
|
| createBuffer :: WebGPUDevice -> Word64 -> BufferUsage -> Maybe ByteString |
| -> IO (Either String WebGPUBuffer) |
| createBuffer device size usage mdata = do |
| result <- try $ do |
| let bufferId = T.concat |
| [ wgdDeviceId device |
| , "-buf-" |
| , T.pack (show size) |
| ] |
|
|
| pure (WebGPUBuffer bufferId size usage mdata) |
|
|
| case result of |
| Left (e :: SomeException) -> pure (Left $ "Buffer creation failed: " ++ show e) |
| Right buffer -> pure (Right buffer) |
|
|
| |
| |
| |
|
|
| createShader :: WebGPUDevice -> Text -> Text -> (Word32, Word32, Word32) -> Text |
| -> IO (Either String WebGPUShader) |
| createShader device moduleName entryPoint workgroupSize wgslCode = do |
| result <- try $ do |
| |
| let shaderModule = WebGPUShader moduleName entryPoint workgroupSize wgslCode |
|
|
| |
| let isValid = T.pack "@compute" `T.isInfixOf` wgslCode |
|
|
| if isValid |
| then pure shaderModule |
| else fail "Invalid WGSL shader" |
|
|
| case result of |
| Left (e :: SomeException) -> pure (Left $ "Shader creation failed: " ++ show e) |
| Right shader -> pure (Right shader) |
|
|
| |
| |
| |
|
|
| dispatchCompute :: WebGPUDevice -> WebGPUShader -> [WebGPUBuffer] |
| -> (Word32, Word32, Word32) -> IO (Either String ()) |
| dispatchCompute device shader buffers (x, y, z) = do |
| result <- try $ do |
| |
| let computePass = object |
| [ "shader" .= wgsShaderModule shader |
| , "buffers" .= map wgbBufferId buffers |
| , "workgroups" .= object |
| [ "x" .= x, "y" .= y, "z" .= z ] |
| ] |
|
|
| |
| |
| pure () |
|
|
| case result of |
| Left (e :: SomeException) -> pure (Left $ "Compute dispatch failed: " ++ show e) |
| Right () -> pure (Right ()) |
|
|
| |
| |
| |
|
|
| readBuffer :: WebGPUDevice -> WebGPUBuffer -> IO (Either String ByteString) |
| readBuffer device buffer = do |
| result <- try $ do |
| case wgbData buffer of |
| Nothing -> fail "Buffer not yet populated" |
| Just data' -> pure data' |
|
|
| case result of |
| Left (e :: SomeException) -> pure (Left $ "Buffer read failed: " ++ show e) |
| Right data' -> pure (Right data') |
|
|
| |
| |
| |
|
|
| tensorMatmul :: WebGPUDevice -> WebGPUBuffer -> WebGPUBuffer -> WebGPUBuffer |
| -> Word32 -> Word32 -> Word32 -> IO (Either String ()) |
| tensorMatmul device a b c m n k = do |
| |
| let wgslKernel = T.unlines |
| [ "@compute @workgroup_size(16, 16)" |
| , "fn matmul(@builtin(global_invocation_id) gid: vec3<u32>) {" |
| , " let row = gid.x;" |
| , " let col = gid.y;" |
| , " var sum: f32 = 0.0;" |
| , " for (var k: u32 = 0u; k < " <> T.pack (show k) <> "u; k = k + 1u) {" |
| , " sum = sum + a[row * " <> T.pack (show k) <> "u + k] * b[k * " <> T.pack (show n) <> "u + col];" |
| , " }" |
| , " c[row * " <> T.pack (show n) <> "u + col] = sum;" |
| , "}" |
| ] |
|
|
| shaderResult <- createShader device "matmul" "matmul" (16, 16, 1) wgslKernel |
|
|
| case shaderResult of |
| Left err -> pure (Left err) |
| Right shader -> do |
| let workgroups = ((m + 15) `div` 16, (n + 15) `div` 16, 1) |
| dispatchCompute device shader [a, b, c] workgroups |
|
|
| |
| |
| |
|
|
| streamInference :: WebGPUDevice -> [WebGPUBuffer] -> (ByteString -> IO ()) |
| -> IO (Either String ()) |
| streamInference device batches onChunk = do |
| |
| mapM_ (\buf -> do |
| result <- readBuffer device buf |
| case result of |
| Left _ -> pure () |
| Right chunk -> onChunk chunk |
| ) batches |
| pure (Right ()) |
|
|