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"
20⍝# Load an XIR program, by its text; the number of inputs.
21"l_oad : text -> int" ᶠᶠⁱb̲ind< "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"
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"
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"
29⍝# The schedule for later runs, as a schedules/*.toml text ("" for the defaults).
30"s_chedule : text -> int" ᶠᶠⁱb̲ind< "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"
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"
45⍝# Output i (from 1) of the last run, as Floats.
46"o_utput : int -> floats" ᶠᶠⁱb̲ind< "gpu/output"
47⍝# Output i (from 1) of the last run, as Ints (exact to 2^53).
48"o_utputInts : int -> ints" ᶠᶠⁱb̲ind< "gpu/output"
49⍝# The last run's plan in words.
50"e_xplain : unit -> text" ᶠᶠⁱb̲ind< "gpu/explain"
51⍝# The last run's OpenCL C.
52"k_ernel : unit -> text" ᶠᶠⁱb̲ind< "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"
ᶠᶠⁱ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" }
ᶠᶠⁱ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< 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̲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" }
ᶠᶠⁱ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" }
ᶠᶠⁱ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" }
ᶠᶠⁱ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" }
ᶠᶠⁱ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 }
ᶠᶠⁱ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 }