libraryextensions/gpu/lib/Gpu.xtl

Gpu: XIR programs run on OpenCL devices (or the reference interpreter) from X_eTaL

Import: "gp:" u_se< "Gpu" Run with xetal-x (X_eTaL-extensions' bridge host): "scripts/xx run prog.xtl" puts this package and this repository's libraries in reach.

The first half binds the native functions (extensions/gpu/rust), one line each, with X_eTaL-extensions' binding macro (Ffi.xtlm): a session per thread, loaded, bound, run, read. The second half is ordinary X_eTaL over them: functions that write XIR programs (the text is data, so these are functions, not macros: docs/macros-and-extensions.md, X4), a product and a dense layer in one call each, and a check for f32 results.

source · imports ffi: work/xetal-extensions/lib/Ffi.xtlm

Native functions

ˡd̲evices : Unit -> Char

function · line 19

The OpenCL devices, one line each (opencl:N).

"d_evices : unit -> text"      ᶠᶠⁱb̲ind< "gpu/devices"
ᶠᶠⁱb̲ind< expands to
ˡd̲evices ← { @ →
  ⎕N̲GET "ext:gpu/devices"
}

ˡl̲oad : Char -> Int

function · line 21

Load an XIR program, by its text; the number of inputs.

"l_oad : text -> int"          ᶠᶠⁱb̲ind< "gpu/load"
ᶠᶠⁱb̲ind< expands to
ˡl̲oad ← { a →
  pa ← a ⎕N̲PUT "ext:gpu/load?text"
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/load"
}

ˡl̲ower : Char -> Char -> Int

function · line 24

A function from its X_eTaL text and "FILE|KINDS" (what gp:a_ccel< expands to; the file gives its imports, the kinds say int or float per argument); its parameter count.

"l_ower : text text -> int"    ᶠᶠⁱb̲ind< "gpu/lower"
ᶠᶠⁱb̲ind< expands to
ˡl̲ower ← { a b →
  pa ← a ⎕N̲PUT "ext:gpu/lower?text"
  pb ← b ⎕N̲PUT "ext:gpu/lower?text"
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/lower"
}

ˡb̲ind : (Num a, Num b) => a -> b -> Int

function · line 26

n gp:b_ind a: input n (from 1) gets the array a, of its declared shape.

"b_ind : int float -> int"     ᶠᶠⁱb̲ind< "gpu/bind"
ᶠᶠⁱb̲ind< expands to
ˡb̲ind ← { a b →
  pa ← (f̲ormat r̲avel f̲loor a) ⎕N̲PUT "ext:gpu/bind?int=" c̲at f̲ormat s̲hape a
  pb ← (f̲ormat r̲avel f̲loat b) ⎕N̲PUT "ext:gpu/bind?float=" c̲at f̲ormat s̲hape b
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/bind"
}

ˡb̲indFile : Num a => a -> Char -> Int

function · line 28

n gp:b_indFile path: bind a binary array file; only its path crosses the bridge.

"b_indFile : int text -> int"  ᶠᶠⁱb̲ind< "gpu/bind_file"
ᶠᶠⁱb̲ind< expands to
ˡb̲indFile ← { a b →
  pa ← (f̲ormat r̲avel f̲loor a) ⎕N̲PUT "ext:gpu/bind_file?int=" c̲at f̲ormat s̲hape a
  pb ← b ⎕N̲PUT "ext:gpu/bind_file?text"
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/bind_file"
}

ˡs̲chedule : Char -> Int

function · line 30

The schedule for later runs, as a schedules/*.toml text ("" for the defaults).

"s_chedule : text -> int"      ᶠᶠⁱb̲ind< "gpu/schedule"
ᶠᶠⁱb̲ind< expands to
ˡs̲chedule ← { a →
  pa ← a ⎕N̲PUT "ext:gpu/schedule?text"
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/schedule"
}

ˡr̲un : Num a => a -> Int

function · line 41

Run on opencl:d, or on the reference interpreter for -1; the number of outputs.

      ᵍᵖ⁼u̲se< "Gpu"
      ᵍᵖl̲oad "%a = input i64 [3]\n%k = const i64 [] 2\n%b = map mul %k %a\noutput %b\n"
1
      1 ᵍᵖb̲ind 1 2 3
1
      ᵍᵖr̲un -1
1
      ᵍᵖo̲utputInts 1
2 4 6
"r_un : int -> int"            ᶠᶠⁱb̲ind< "gpu/run"
ᶠᶠⁱb̲ind< expands to
ˡr̲un ← { a →
  pa ← (f̲ormat r̲avel f̲loor a) ⎕N̲PUT "ext:gpu/run?int=" c̲at f̲ormat s̲hape a
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/run"
}

ˡr̲erun : Num a => a -> Int

function · line 44

Rerun on opencl:d with resident kernels and inputs; only rebound inputs upload. Changing the program, schedule or device prepares a new session.

"r_erun : int -> int"          ᶠᶠⁱb̲ind< "gpu/rerun"
ᶠᶠⁱb̲ind< expands to
ˡr̲erun ← { a →
  pa ← (f̲ormat r̲avel f̲loor a) ⎕N̲PUT "ext:gpu/rerun?int=" c̲at f̲ormat s̲hape a
  f̲loor f̲irst n̲umbers ⎕N̲GET "ext:gpu/rerun"
}

ˡo̲utput : Num a => a -> Float

function · line 46

Output i (from 1) of the last run, as Floats.

"o_utput : int -> floats"      ᶠᶠⁱb̲ind< "gpu/output"
ᶠᶠⁱb̲ind< expands to
ˡo̲utput ← { a →
  pa ← (f̲ormat r̲avel f̲loor a) ⎕N̲PUT "ext:gpu/output?int=" c̲at f̲ormat s̲hape a
  v ← n̲umbers ⎕N̲GET "ext:gpu/output"
  (f̲loor n̲umbers ⎕N̲GET "ext:gpu/output?shape") r̲eshape v
}

ˡo̲utputInts : Num a => a -> Int

function · line 48

Output i (from 1) of the last run, as Ints (exact to 2^53).

"o_utputInts : int -> ints"    ᶠᶠⁱb̲ind< "gpu/output"
ᶠᶠⁱb̲ind< expands to
ˡo̲utputInts ← { a →
  pa ← (f̲ormat r̲avel f̲loor a) ⎕N̲PUT "ext:gpu/output?int=" c̲at f̲ormat s̲hape a
  v ← f̲loor n̲umbers ⎕N̲GET "ext:gpu/output"
  (f̲loor n̲umbers ⎕N̲GET "ext:gpu/output?shape") r̲eshape v
}

ˡe̲xplain : Unit -> Char

function · line 50

The last run's plan in words.

"e_xplain : unit -> text"      ᶠᶠⁱb̲ind< "gpu/explain"
ᶠᶠⁱb̲ind< expands to
ˡe̲xplain ← { @ →
  ⎕N̲GET "ext:gpu/explain"
}

ˡk̲ernel : Unit -> Char

function · line 52

The last run's OpenCL C.

"k_ernel : unit -> text"       ᶠᶠⁱb̲ind< "gpu/kernel"
ᶠᶠⁱb̲ind< expands to
ˡk̲ernel ← { @ →
  ⎕N̲GET "ext:gpu/kernel"
}

Programs, as text

ʰn̲l : Unit -> Char

function (private) · line 57

A newline (private to this file).

ʰn̲l ← { @ → f̲irst "\n" }

ˡp̲roductProgram : a -> b -> Char

function · line 68

sa gp:p_roductProgram sb: the XIR text of a times b ('+ '* i_nner) for an a of the shape sa and a b of the shape sb (Floats).

      ᵍᵖ⁼u̲se< "Gpu"
      (2 3) ᵍᵖp̲roductProgram 3
%a = input f64 [2 3]
%b = input f64 [3]
%c = matmul %a %b
output %c
ˡp̲roductProgram ← { sa sb →
  a ← "%a = input f64 [" c̲at (f̲ormat sa) c̲at "]" c̲at ʰn̲l @
  b ← "%b = input f64 [" c̲at (f̲ormat sb) c̲at "]" c̲at ʰn̲l @
  a c̲at b c̲at "%c = matmul %a %b" c̲at (ʰn̲l @) c̲at "output %c" c̲at ʰn̲l @
}

ˡd̲enseProgram : Int -> a -> Char

function · line 77

sx gp:d_enseProgram swb: the XIR text of the layer x W + b (Accel's ac:d_ense, the bias the last row of wb) for an x of the shape sx and a wb of the shape swb.

ˡd̲enseProgram ← { sx swb →
  nl ← ʰn̲l @
  rows ← f̲irst sx
  inputs ← "%x = input f64 [" c̲at (f̲ormat sx) c̲at "]" c̲at nl c̲at "%wb = input f64 [" c̲at (f̲ormat swb) c̲at "]" c̲at nl
  split ← "%w = drop -1 %wb" c̲at nl c̲at "%t = take -1 %wb" c̲at nl c̲at "%v = ravel %t" c̲at nl
  bias ← "%o = const i64 [" c̲at (f̲ormat rows) c̲at "] " c̲at (f̲ormat o̲ffsets rows) c̲at nl c̲at "%bias = table right %o %v" c̲at nl
  inputs c̲at split c̲at bias c̲at "%xw = matmul %x %w" c̲at nl c̲at "%y = map add %xw %bias" c̲at nl c̲at "output %y" c̲at nl
}
Used in: ˡd̲ense

ˡa̲fter : (Any a, Any b) => a -> b -> a

function · line 92

a gp:a_fter b: a, evaluated after b (the right side first, as X_eTaL evaluates); what gp:a_ccel<'s expansion sequences its calls with, so it binds no names it never reads.

      ᵍᵖ⁼u̲se< "Gpu"
      1 ᵍᵖa̲fter 2
1
ˡa̲fter ← { a b → a }

In one call

ˡp̲roduct : (Num a, Num b) => a -> b -> Float

function · line 97

a gp:p_roduct b: a '+ '* i_nner b computed on opencl:0.

ˡp̲roduct ← { a b →
  n ← ˡl̲oad (s̲hape a) ˡp̲roductProgram s̲hape b
  ba ← 1 ˡb̲ind a
  bb ← 2 ˡb̲ind b
  r ← ˡr̲un 0
  ˡo̲utput 1
}

ˡd̲ense : (Num a, Num b) => a -> b -> Float

function · line 106

x gp:d_ense wb: the layer x W + b (ac:d_ense) computed on opencl:0.

ˡd̲ense ← { x wb →
  n ← ˡl̲oad (s̲hape x) ˡd̲enseProgram s̲hape wb
  bx ← 1 ˡb̲ind x
  bw ← 2 ˡb̲ind wb
  r ← ˡr̲un 0
  ˡo̲utput 1
}

ˡn̲ear : (Num a, Num b) => a -> b -> Char

function · line 122

want gp:n_ear got: "ok" when the shapes match and every item agrees within a relative 1e-5 (absolute below 1), as an f32 device's result agrees with the evaluator's f64; else what differs.

      ᵍᵖ⁼u̲se< "Gpu"
      (1 2 3) ᵍᵖn̲ear 1.000001 2.000001 3.000001
ok
      (1 2 3) ᵍᵖn̲ear 1 2
FAIL: shapes 3 and 2
ˡn̲ear ← { w g →
  n̲ot (s̲hape w) m̲atch s̲hape g ? "FAIL: shapes " c̲at (f̲ormat s̲hape w) c̲at " and " c̲at f̲ormat s̲hape g
  d ← a̲bs (f̲loat w) − f̲loat g
  '∧ r̲/ r̲avel d ≤ 0.00001 × 1.0 m̲ax a̲bs f̲loat w ? "ok"
  "FAIL: the largest difference is " c̲at f̲ormat 'm̲ax r̲/ r̲avel d
}

ˡpackage : Char

value · line 131

The native package behind these functions (a literal l: name also makes this file a library to "xetal type": X_eTaL-extensions' ask E7).

ˡpackage ← "gpu"