hhlo-0.5.0.0: src/HHLO/Autograd/Core.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module HHLO.Autograd.Core
( BTensor(..)
, CotangentMap
, bconstant
, badd
, bsubtract
, bmultiply
, bdivide
, bnegate
, bexp
, blog
, bsqrt
, bsin
, bcos
, btranspose
, breshape
, bbroadcastInDim
, breduceSum
, bdot
, babs
, btanh
, bmaximum
, bminimum
, bselect
, bslice
, bpad
, bconcatenate
, bconvert
, bcompareGE
, btoTyped
, bfromTyped
, reifyShape
, accumulate
) where
import Data.Int (Int64)
import Data.Proxy
import qualified Data.Text as T
import GHC.TypeLits
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import HHLO.Core.Types
import HHLO.IR.AST
import HHLO.IR.Builder
-- ---------------------------------------------------------------------------
-- Backward tensor: runtime-typed tensor handle for the backward pass.
-- ---------------------------------------------------------------------------
-- | A tensor value used during the backward pass, carrying only runtime
-- shape and dtype information.
data BTensor = BTensor
{ btVid :: !ValueId
, btType :: !TensorType
} deriving (Eq, Show)
-- | Map from forward value ID to its accumulated cotangent.
type CotangentMap = Map ValueId BTensor
-- ---------------------------------------------------------------------------
-- Existential shape reification
-- ---------------------------------------------------------------------------
-- | Reify a runtime shape list into a type-level 'KnownShape' constraint.
reifyShape :: [Integer] -> (forall (s :: Shape). KnownShape s => Proxy s -> r) -> r
reifyShape [] k = k (Proxy @'[])
reifyShape (n : ns) k =
case someNatVal (fromIntegral n) of
Just (SomeNat (_ :: Proxy n)) ->
reifyShape ns $ \(_ :: Proxy ns) ->
k (Proxy @(n ': ns))
Nothing -> error "autograd-hhlo: negative dimension in reifyShape"
-- ---------------------------------------------------------------------------
-- Conversions between BTensor and typed Tensor
-- ---------------------------------------------------------------------------
-- | Convert a typed 'Tensor' to a runtime-typed 'BTensor'.
bfromTyped :: forall s d. (KnownShape s, KnownDType d) => Tensor s d -> BTensor
bfromTyped (Tensor vid) = BTensor vid (tensorType (Proxy @s) (Proxy @d))
-- | Convert a 'BTensor' to a typed 'Tensor', checking that the runtime
-- type matches the expected compile-time type.
btoTyped :: forall s d. (KnownShape s, KnownDType d) => BTensor -> Tensor s d
btoTyped (BTensor vid ttype) =
let expected = tensorType (Proxy @s) (Proxy @d)
in if ttype == expected
then Tensor vid
else error $ T.unpack $
"autograd-hhlo: type mismatch in btoTyped. Expected "
<> T.pack (show expected) <> ", got " <> T.pack (show ttype)
-- ---------------------------------------------------------------------------
-- Cotangent accumulation
-- ---------------------------------------------------------------------------
-- | Add a new cotangent to an existing entry in the map, or insert it
-- if none exists yet.
accumulate :: CotangentMap -> ValueId -> BTensor -> Builder CotangentMap
accumulate cmap vid newBar =
case Map.lookup vid cmap of
Just existingBar -> do
summed <- badd existingBar newBar
return $! Map.insert vid summed cmap
Nothing ->
return $! Map.insert vid newBar cmap
-- ---------------------------------------------------------------------------
-- Primitive backward operations (emit raw StableHLO)
-- ---------------------------------------------------------------------------
bconstant :: TensorType -> Double -> Builder BTensor
bconstant t val = do
let shp = ttShape t
dt = ttDType t
numElems = product shp
vid <- emitOp "stablehlo.constant" [] []
[AttrDenseElements shp dt (replicate (fromIntegral numElems) val)] t
return (BTensor vid t)
badd :: BTensor -> BTensor -> Builder BTensor
badd (BTensor x t) (BTensor y _) = do
vid <- emitOp "stablehlo.add" [x, y] [t, t] [] t
return (BTensor vid t)
bsubtract :: BTensor -> BTensor -> Builder BTensor
bsubtract (BTensor x t) (BTensor y _) = do
vid <- emitOp "stablehlo.subtract" [x, y] [t, t] [] t
return (BTensor vid t)
bmultiply :: BTensor -> BTensor -> Builder BTensor
bmultiply (BTensor x t) (BTensor y _) = do
vid <- emitOp "stablehlo.multiply" [x, y] [t, t] [] t
return (BTensor vid t)
bdivide :: BTensor -> BTensor -> Builder BTensor
bdivide (BTensor x t) (BTensor y _) = do
vid <- emitOp "stablehlo.divide" [x, y] [t, t] [] t
return (BTensor vid t)
bnegate :: BTensor -> Builder BTensor
bnegate (BTensor x t) = do
vid <- emitOp "stablehlo.negate" [x] [t] [] t
return (BTensor vid t)
bexp :: BTensor -> Builder BTensor
bexp (BTensor x t) = do
vid <- emitOp "stablehlo.exponential" [x] [t] [] t
return (BTensor vid t)
blog :: BTensor -> Builder BTensor
blog (BTensor x t) = do
vid <- emitOp "stablehlo.log" [x] [t] [] t
return (BTensor vid t)
bsqrt :: BTensor -> Builder BTensor
bsqrt (BTensor x t) = do
vid <- emitOp "stablehlo.sqrt" [x] [t] [] t
return (BTensor vid t)
bsin :: BTensor -> Builder BTensor
bsin (BTensor x t) = do
vid <- emitOp "stablehlo.sine" [x] [t] [] t
return (BTensor vid t)
bcos :: BTensor -> Builder BTensor
bcos (BTensor x t) = do
vid <- emitOp "stablehlo.cosine" [x] [t] [] t
return (BTensor vid t)
-- | Transpose a BTensor using a permutation.
btranspose :: BTensor -> [Int64] -> TensorType -> Builder BTensor
btranspose (BTensor x inType) perm outType = do
let permAttr = AttrIntList "permutation" perm
vid <- emitOp "stablehlo.transpose" [x] [inType] [permAttr] outType
return (BTensor vid outType)
-- | Reshape a BTensor.
breshape :: BTensor -> TensorType -> Builder BTensor
breshape (BTensor x inType) outType = do
vid <- emitOp "stablehlo.reshape" [x] [inType] [] outType
return (BTensor vid outType)
-- | Broadcast a BTensor to a new shape with explicit dimension mapping.
bbroadcastInDim :: BTensor -> [Int64] -> TensorType -> Builder BTensor
bbroadcastInDim (BTensor x inType) dims outType = do
vid <- emitOp "stablehlo.broadcast_in_dim" [x] [inType]
[AttrIntList "dims" (fromIntegral <$> dims)] outType
return (BTensor vid outType)
-- | Element-wise selection between two BTensors based on a boolean predicate.
bselect :: BTensor -> BTensor -> BTensor -> TensorType -> Builder BTensor
bselect (BTensor p predType) (BTensor t _) (BTensor f _) outType = do
vid <- emitOp "stablehlo.select" [p, t, f] [predType, outType, outType] [] outType
return (BTensor vid outType)
-- | Slice a BTensor (forward operation wrapper).
bslice :: BTensor -> [Int64] -> [Int64] -> [Int64] -> TensorType -> Builder BTensor
bslice (BTensor x inType) start limit stride outType = do
let startAttr = AttrRaw $ "start_indices = array<i64: " <> T.intercalate ", " ((T.pack . show) <$> start) <> ">"
limitAttr = AttrRaw $ "limit_indices = array<i64: " <> T.intercalate ", " ((T.pack . show) <$> limit) <> ">"
strideAttr = AttrRaw $ "strides = array<i64: " <> T.intercalate ", " ((T.pack . show) <$> stride) <> ">"
vid <- emitOp "stablehlo.slice" [x] [inType] [startAttr, limitAttr, strideAttr] outType
return (BTensor vid outType)
-- | Pad a BTensor with edge and interior padding.
bpad :: BTensor -> BTensor -> [Int64] -> [Int64] -> [Int64] -> TensorType -> Builder BTensor
bpad (BTensor x inType) (BTensor padVal padType) low high interior outType = do
let lowAttr = AttrRaw $ "edge_padding_low = array<i64: " <> T.intercalate ", " ((T.pack . show) <$> low) <> ">"
highAttr = AttrRaw $ "edge_padding_high = array<i64: " <> T.intercalate ", " ((T.pack . show) <$> high) <> ">"
intAttr = AttrRaw $ "interior_padding = array<i64: " <> T.intercalate ", " ((T.pack . show) <$> interior) <> ">"
vid <- emitOp "stablehlo.pad" [x, padVal] [inType, padType] [lowAttr, highAttr, intAttr] outType
return (BTensor vid outType)
-- | Concatenate multiple BTensors along a dimension.
bconcatenate :: [BTensor] -> Int64 -> TensorType -> Builder BTensor
bconcatenate inputs dim outType = do
let vids = map btVid inputs
types = map btType inputs
dimAttr = AttrInt "dimension" (fromIntegral dim)
vid <- emitOp "stablehlo.concatenate" vids types [dimAttr] outType
return (BTensor vid outType)
-- | Reduce-sum a BTensor over specified dimensions.
breduceSum :: BTensor -> [Int] -> TensorType -> Builder BTensor
breduceSum (BTensor x inType) dims outType = do
let elemType = TensorType [] (ttDType inType)
zeroVid <- emitOp "stablehlo.constant" [] []
[AttrDenseElements [] (ttDType inType) [0.0]] elemType
redBlock <- runBlockBuilder [elemType, elemType] $ do
a <- arg @'[] @( 'F32)
b <- arg @'[] @( 'F32)
sumVid <- emitOp "stablehlo.add"
[tensorValue a, tensorValue b]
[elemType, elemType] [] elemType
emitReturn [sumVid] [elemType]
vid <- emitOpRegions "stablehlo.reduce"
[x, zeroVid]
[inType, elemType]
[AttrRaw $ "dimensions = array<i64: " <> T.intercalate ", " [T.pack (show d) | d <- dims] <> ">"]
[Region [redBlock]]
outType
return (BTensor vid outType)
-- | Matrix multiplication of two BTensors.
bdot :: BTensor -> BTensor -> TensorType -> Builder BTensor
bdot (BTensor x t1) (BTensor y t2) outType = do
vid <- emitOp "stablehlo.dot" [x, y] [t1, t2] [] outType
return (BTensor vid outType)
-- | Element-wise absolute value.
babs :: BTensor -> Builder BTensor
babs (BTensor x t) = do
vid <- emitOp "stablehlo.abs" [x] [t] [] t
return (BTensor vid t)
-- | Element-wise hyperbolic tangent.
btanh :: BTensor -> Builder BTensor
btanh (BTensor x t) = do
vid <- emitOp "stablehlo.tanh" [x] [t] [] t
return (BTensor vid t)
-- | Element-wise maximum.
bmaximum :: BTensor -> BTensor -> Builder BTensor
bmaximum (BTensor x t1) (BTensor y t2) = do
vid <- emitOp "stablehlo.maximum" [x, y] [t1, t2] [] t1
return (BTensor vid t1)
-- | Element-wise minimum.
bminimum :: BTensor -> BTensor -> Builder BTensor
bminimum (BTensor x t1) (BTensor y t2) = do
vid <- emitOp "stablehlo.minimum" [x, y] [t1, t2] [] t1
return (BTensor vid t1)
-- | Type conversion.
bconvert :: BTensor -> TensorType -> Builder BTensor
bconvert (BTensor x inType) outType = do
vid <- emitOp "stablehlo.convert" [x] [inType] [] outType
return (BTensor vid outType)
-- | Greater-than-or-equal comparison (returns boolean tensor).
bcompareGE :: BTensor -> BTensor -> Builder BTensor
bcompareGE (BTensor x t1) (BTensor y t2) = do
let boolType = TensorType (ttShape t1) Bool
vid <- emitOp "stablehlo.compare" [x, y] [t1, t2]
[AttrRaw "comparison_direction = #stablehlo<comparison_direction GE>"] boolType
return (BTensor vid boolType)