sourceextensions/gpu/lib/Gpu.xtl

1⍝# Gpu: XIR programs run on OpenCL devices (or the reference interpreter) from X_eTaL 2⍝# 3⍝# Import: "gp:" u_se< "Gpu" 4⍝# Run with xetal-x (X_eTaL-extensions' bridge host): "scripts/xx run prog.xtl" 5⍝# puts this package and this repository's libraries in reach. 6⍝# 7⍝# The first half binds the native functions (extensions/gpu/rust), 8⍝# one line each, with X_eTaL-extensions' binding macro (Ffi.xtlm): 9⍝# a session per thread, loaded, bound, run, read. The second half is 10⍝# ordinary X_eTaL over them: functions that write XIR programs (the 11⍝# text is data, so these are functions, not macros: 12⍝# docs/macros-and-extensions.md, X4), a product and a dense layer in 13⍝# one call each, and a check for f32 results. 14ᶠᶠⁱ⁼u̲se< "Ffi" 15 16⍝## Native functions 17 18⍝# The OpenCL devices, one line each (opencl:N). 19"d_evices : unit -> text" ᶠᶠⁱb̲ind< "gpu/devices"
ᶠᶠⁱb̲ind< expands to
ˡd̲evices ← { @ →
  ⎕N̲GET "ext:gpu/devices"
}
20⍝# Load an XIR program, by its text; the number of inputs. 21"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"
}
22⍝# A function from its X_eTaL text and "FILE|KINDS" (what gp:a_ccel< expands to; the 23⍝# file gives its imports, the kinds say int or float per argument); its parameter count. 24"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"
}
25⍝# n gp:b_ind a: input n (from 1) gets the array a, of its declared shape. 26"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"
}
27⍝# n gp:b_indFile path: bind a binary array file; only its path crosses the bridge. 28"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"
}
29⍝# The schedule for later runs, as a schedules/*.toml text ("" for the defaults). 30"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"
}
31⍝# Run on opencl:d, or on the reference interpreter for -1; the number of outputs. 32⍝# >> "gp:" u_se< "Gpu" 33⍝# >> gp:l_oad "%a = input i64 [3]\n%k = const i64 [] 2\n%b = map mul %k %a\noutput %b\n" 34⍝# 1 35⍝# >> 1 gp:b_ind 1 2 3 36⍝# 1 37⍝# >> gp:r_un -1 38⍝# 1 39⍝# >> gp:o_utputInts 1 40⍝# 2 4 6 41"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"
}
42⍝# Rerun on opencl:d with resident kernels and inputs; only rebound inputs upload. 43⍝# Changing the program, schedule or device prepares a new session. 44"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"
}
45⍝# Output i (from 1) of the last run, as Floats. 46"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
}
47⍝# Output i (from 1) of the last run, as Ints (exact to 2^53). 48"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
}
49⍝# The last run's plan in words. 50"e_xplain : unit -> text" ᶠᶠⁱb̲ind< "gpu/explain"
ᶠᶠⁱb̲ind< expands to
ˡe̲xplain ← { @ →
  ⎕N̲GET "ext:gpu/explain"
}
51⍝# The last run's OpenCL C. 52"k_ernel : unit -> text" ᶠᶠⁱb̲ind< "gpu/kernel"
ᶠᶠⁱb̲ind< expands to
ˡk̲ernel ← { @ →
  ⎕N̲GET "ext:gpu/kernel"
}
53 54⍝## Programs, as text 55 56⍝# A newline (private to this file). 57ʰn̲l ← { @ → f̲irst "\n" } 58 59⍝# sa gp:p_roductProgram sb: the XIR text of a times b ('+ '* i_nner) 60⍝# for an a of the shape sa and a b of the shape sb (Floats). 61⍝# >> "gp:" u_se< "Gpu" 62⍝# >> (2 3) gp:p_roductProgram 3 63⍝# %a = input f64 [2 3] 64⍝# %b = input f64 [3] 65⍝# %c = matmul %a %b 66⍝# output %c 67⍝# 68ˡp̲roductProgram ← { sa sb → 69 a ← "%a = input f64 [" c̲at (f̲ormat sa) c̲at "]" c̲at ʰn̲l @ 70 b ← "%b = input f64 [" c̲at (f̲ormat sb) c̲at "]" c̲at ʰn̲l @ 71 a c̲at b c̲at "%c = matmul %a %b" c̲at (ʰn̲l @) c̲at "output %c" c̲at ʰn̲l @ 72} 73 74⍝# sx gp:d_enseProgram swb: the XIR text of the layer x W + b (Accel's 75⍝# ac:d_ense, the bias the last row of wb) for an x of the shape sx and 76⍝# a wb of the shape swb. 77ˡd̲enseProgram ← { sx swb → 78 nl ← ʰn̲l @ 79 rows ← f̲irst sx 80 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 81 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 82 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 83 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 84} 85 86⍝# a gp:a_fter b: a, evaluated after b (the right side first, as X_eTaL 87⍝# evaluates); what gp:a_ccel<'s expansion sequences its calls with, so 88⍝# it binds no names it never reads. 89⍝# >> "gp:" u_se< "Gpu" 90⍝# >> 1 gp:a_fter 2 91⍝# 1 92ˡa̲fter ← { a b → a } 93 94⍝## In one call 95 96⍝# a gp:p_roduct b: a '+ '* i_nner b computed on opencl:0. 97ˡp̲roduct ← { a b → 98 n ← ˡl̲oad (s̲hape a) ˡp̲roductProgram s̲hape b 99 ba ← 1 ˡb̲ind a 100 bb ← 2 ˡb̲ind b 101 r ← ˡr̲un 0 102 ˡo̲utput 1 103} 104 105⍝# x gp:d_ense wb: the layer x W + b (ac:d_ense) computed on opencl:0. 106ˡd̲ense ← { x wb → 107 n ← ˡl̲oad (s̲hape x) ˡd̲enseProgram s̲hape wb 108 bx ← 1 ˡb̲ind x 109 bw ← 2 ˡb̲ind wb 110 r ← ˡr̲un 0 111 ˡo̲utput 1 112} 113 114⍝# want gp:n_ear got: "ok" when the shapes match and every item agrees 115⍝# within a relative 1e-5 (absolute below 1), as an f32 device's result 116⍝# agrees with the evaluator's f64; else what differs. 117⍝# >> "gp:" u_se< "Gpu" 118⍝# >> (1 2 3) gp:n_ear 1.000001 2.000001 3.000001 119⍝# ok 120⍝# >> (1 2 3) gp:n_ear 1 2 121⍝# FAIL: shapes 3 and 2 122ˡn̲ear ← { w g → 123 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 124 d ← a̲bs (f̲loat w) − f̲loat g 125 '∧ r̲/ r̲avel d ≤ 0.00001 × 1.0 m̲ax a̲bs f̲loat w ? "ok" 126 "FAIL: the largest difference is " c̲at f̲ormat 'm̲ax r̲/ r̲avel d 127} 128 129⍝# The native package behind these functions (a literal l: name also 130⍝# makes this file a library to "xetal type": X_eTaL-extensions' ask E7). 131ˡpackage ← "gpu"