packages feed

hanalyze-0.1.0.0: bench/haskell/BenchMemVI.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes        #-}
-- | Memory audit Q2-B-1: Stat.VI ADVI.
--
-- Looking for the chained-thunk leak in @Stat/VI.hs:176, 184@:
--
-- > writeIORef muRef (zipWith (+) mu dxMu)
--
-- After T iterations, @muRef@ holds @T@ levels of chained @zipWith@ that
-- are only forced post-loop. Expectation: alloc grows roughly linearly
-- with T, peak residency too.
--
-- Run e.g.:
--   ./bench-mem-vi 500  +RTS -s -t -M256m
--   ./bench-mem-vi 5000 +RTS -s -t -M512m
--
-- We use a synthetic flat-prior model with K parameters so the unconstrained
-- vector size is large enough to make any per-iter leak visible.
module Main where

import           Control.Monad         (forM_)
import qualified Data.Map.Strict       as Map
import qualified Data.Text             as T
import           Data.Time.Clock       (getCurrentTime, diffUTCTime)
import           System.Environment    (getArgs)
import           System.IO             (hSetBuffering, BufferMode (..), stdout)
import           System.Random.MWC     (createSystemRandom)

import           Hanalyze.Model.HBM
import           Hanalyze.Stat.Distribution ()
import           Hanalyze.Stat.VI

-- | Synthetic model: K independent Normal latents with one Normal observation
-- each (data fixed at the prior mean). Larger K = larger variational vector
-- per iteration → leak is more visible per iter.
flatModel :: Int -> ModelP ()
flatModel k = do
  forM_ [1 .. k] $ \i -> do
    let nm = T.pack ("p" ++ show i)
    pi_ <- sample nm (Normal 0 1)
    observe (T.pack ("y" ++ show i)) (Normal pi_ 1) [0.0]

main :: IO ()
main = do
  hSetBuffering stdout NoBuffering
  args <- getArgs
  let (iters, kParams) = case args of
        [it]      -> (read it, 20)
        [it, kk]  -> (read it, read kk)
        _         -> (500, 20)
  putStrLn $ "BenchMemVI  iters=" ++ show iters
                       ++ "  K="  ++ show kParams
  gen <- createSystemRandom
  let initP = Map.fromList [ (T.pack ("p" ++ show i), 0.0)
                           | i <- [1 .. kParams] ]
      cfg   = defaultVIConfig
                { viIterations = iters
                , viSamples    = 5
                , viNumDraws   = 100
                }
  t0 <- getCurrentTime
  res <- advi (flatModel kParams) cfg initP gen
  t1 <- getCurrentTime
  let elboLast = case viElboHistory res of [] -> 0/0; xs -> last xs
      muLen    = length (viMuU res)
  putStrLn $ "  done elbo_last=" ++ show elboLast
                  ++ "  |mu|=" ++ show muLen
                  ++ "  elapsed=" ++ show (diffUTCTime t1 t0)