Skip to content

Commit af9f1a6

Browse files
Initial fully working and unit tested code with documentation
1 parent 6b8ba58 commit af9f1a6

File tree

4 files changed

+689
-0
lines changed

4 files changed

+689
-0
lines changed

README.md

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# Matrix Log
2+
3+
A matrix log dependency utility. Useful for converting and testing algorithm behavior of CPU code to GPU code.
4+
5+
## Why?
6+
GPU Principle: Your can only _write to_ or _read from_ Arrays (Textures).
7+
CPU Principle: You can read or right to arrays as you see fit.
8+
9+
CPU Problem: Sometimes you'd like to convert your CPU code to GPU code.
10+
By strategically using MatrixLog as a logger in your CPU algorithms, you can see which points are dependent from your source arrays to your target arrays.
11+
This makes converting CPU code to run on GPU, using a utility like [GPU.js](http://gpu.rocks), much easier.
12+
13+
## API
14+
```js
15+
import * as MatrixLog from 'matrix-log';
16+
17+
18+
// create a matrix log that will have dependencies called
19+
const matrixLog = new MatrixLog(parentMatrixName, width, height);
20+
21+
22+
// in your algorithm, perhaps many times
23+
matrixLog.add(childMatrixName, parentX, parentY, childX, childY, childWidth, childHeight);
24+
25+
26+
// after your algorithm
27+
const childMatrixLog = matrixLog.toString(childMatrixName);
28+
```
29+
30+
31+
## What does a log (output of `.toString(string)`) look like?
32+
33+
```
34+
test-matrix x=0,y=0 child-matrix
35+
width=2,height=2 width=4,height=4
36+
[*][ ] [*][*][ ][ ]
37+
[ ][ ] [*][*][ ][ ]
38+
[ ][ ][ ][ ]
39+
[ ][ ][ ][ ]
40+
41+
test-matrix x=1,y=0 child-matrix
42+
width=2,height=2 width=4,height=4
43+
[ ][*] [ ][ ][*][*]
44+
[ ][ ] [ ][ ][*][*]
45+
[ ][ ][ ][ ]
46+
[ ][ ][ ][ ]
47+
48+
test-matrix x=0,y=1 child-matrix
49+
width=2,height=2 width=4,height=4
50+
[ ][ ] [ ][ ][ ][ ]
51+
[*][ ] [ ][ ][ ][ ]
52+
[*][*][ ][ ]
53+
[*][*][ ][ ]
54+
55+
test-matrix x=1,y=1 child-matrix
56+
width=2,height=2 width=4,height=4
57+
[ ][ ] [ ][ ][ ][ ]
58+
[ ][*] [ ][ ][ ][ ]
59+
[ ][ ][*][*]
60+
[ ][ ][*][*]
61+
62+
```
63+
64+
## What does this log mean?
65+
1. Suppose we had a (contrived) block of CPU code we'd like to run on the GPU:
66+
```js
67+
const filters = [
68+
[0,0],
69+
[0,0]
70+
];
71+
72+
const weights = [
73+
[1,2,3,4],
74+
[5,6,7,8],
75+
[9,10,11,12],
76+
[13,14,15,16]
77+
];
78+
79+
for (let y = 0; y < 4; y++) {
80+
let filterY = y < 2 ? 0 : 1;
81+
for (let x = 0; x < 4; x++) {
82+
let filterX = x < filter.length ? 0 : 1;
83+
filters[filterY][filterX] += weights[y][x];
84+
}
85+
}
86+
87+
console.log(filters); // -> [ [ 14, 22 ], [ 46, 54 ] ]
88+
```
89+
90+
This code doesn't directly work on the GPU, because we can only _either_ read or write to arrays. Currently `filters` violates this. However, we could use MatrixLog to _help us_ (note, there is a little thinking that needs to take place) solve this issue by finding the algorithm that `filters` needs.
91+
92+
2. Convert the code as follows and see the output below:
93+
```js
94+
const MatrixLog = require('./index');
95+
96+
const filters = [
97+
[0,0],
98+
[0,0]
99+
];
100+
const weights = [
101+
[1,2,3,4],
102+
[5,6,7,8],
103+
[9,10,11,12],
104+
[13,14,15,16]
105+
];
106+
107+
const matrixLog = new MatrixLog('filters', 2, 2);
108+
for (let y = 0; y < 4; y++) {
109+
let filterY = y < 2 ? 0 : 1;
110+
for (let x = 0; x < 4; x++) {
111+
let filterX = x < filter.length ? 0 : 1;
112+
filters[filterY][filterX] += weights[y][x];
113+
matrixLog.add('weights', filterX, filterY, x, y, weights[0].length, weights.length);
114+
}
115+
}
116+
117+
console.log(matrixLog.toString('weights'));
118+
```
119+
120+
Gives us the output:
121+
```
122+
filters x=0,y=0 weights
123+
width=2,height=2 width=4,height=4
124+
[*][ ] [*][*][ ][ ]
125+
[ ][ ] [*][*][ ][ ]
126+
[ ][ ][ ][ ]
127+
[ ][ ][ ][ ]
128+
129+
filters x=1,y=0 weights
130+
width=2,height=2 width=4,height=4
131+
[ ][*] [ ][ ][*][*]
132+
[ ][ ] [ ][ ][*][*]
133+
[ ][ ][ ][ ]
134+
[ ][ ][ ][ ]
135+
136+
filters x=0,y=1 weights
137+
width=2,height=2 width=4,height=4
138+
[ ][ ] [ ][ ][ ][ ]
139+
[*][ ] [ ][ ][ ][ ]
140+
[*][*][ ][ ]
141+
[*][*][ ][ ]
142+
143+
filters x=1,y=1 weights
144+
width=2,height=2 width=4,height=4
145+
[ ][ ] [ ][ ][ ][ ]
146+
[ ][*] [ ][ ][ ][ ]
147+
[ ][ ][*][*]
148+
[ ][ ][*][*]
149+
```
150+
151+
3. Now we have enough logic to visibly see how to build out our algorythm that will work on the GPU. For `filters`@`x=0,y=0` we can see we need the values from `weights`@`x=0,y=0`,`x=1,y=0`,`x=0,y=1`, and `x=1,y=1`. Then to get `filters`@`x=1,y=0`, we seem to increment by to on `weights`. If we were to write a loop that emilates this behaviour, it'd look something like this:
152+
153+
```js
154+
const filterHeight = 2;
155+
const filterWidth = 2;
156+
157+
for (let filterY = 0; filterY < filterHeight; filterY++) {
158+
for (let filterX = 0; filterX < filterWidth; filterX++) {
159+
// NOTE: += filters!
160+
let sum = filters[filterY][filterX];
161+
162+
const yMin = filterHeight * filterY;
163+
const yMax = yMin + filterHeight;
164+
const xMin = filterWidth * filterX;
165+
const xMax = xMin + filterWidth;
166+
167+
for (let y = yMin; y < yMax; y++) {
168+
for (let x = xMin; x < xMax; x++) {
169+
sum += weights[y][x];
170+
}
171+
}
172+
173+
// single assignment
174+
filters[filterY][filterX] = sum;
175+
}
176+
}
177+
178+
console.log(filters); // -> [ [ 14, 22 ], [ 46, 54 ] ]
179+
```
180+
181+
4. On the GPU we are writing from a kernel, which acts like the `filters` loop already, so we can ommit that and pretend that the function will run in its own "fragment" (like iteration of the inner most loops for building the value). If that function was just simple Javascript that we imagined might work on a GPU kernel, it'd looks something like this:
182+
183+
```js
184+
function filterKernel(filters, filterX, filterY, filterWidth, filterHeight, weights) {
185+
// NOTE: += filters!
186+
let sum = filters[filterY][filterX];
187+
188+
const yMin = filterHeight * filterY;
189+
const yMax = yMin + filterHeight;
190+
const xMin = filterWidth * filterX;
191+
const xMax = xMin + filterWidth;
192+
193+
for (let y = yMin; y < yMax; y++) {
194+
for (let x = xMin; x < xMax; x++) {
195+
sum += weights[y][x];
196+
}
197+
}
198+
199+
return sum;
200+
}
201+
202+
console.log(filterKernel(filters, 0, 0, 2, 2, weights)); // -> 14
203+
console.log(filterKernel(filters, 1, 0, 2, 2, weights)); // -> 22
204+
console.log(filterKernel(filters, 0, 1, 2, 2, weights)); // -> 46
205+
console.log(filterKernel(filters, 1, 1, 2, 2, weights)); // -> 54
206+
```
207+
208+
5. If we use a GPU environment, such as GPU.js, we could then then convert the kernel so that our algorithm actually works for setting the value of `filters` like this:
209+
210+
```js
211+
import GPU from 'gpu.js';
212+
const gpu = new GPU();
213+
214+
const filterKernel = gpu.createKernel(function(filters, weights) {
215+
let sum = filters[this.thread.y][this.thread.x];
216+
217+
const yMin = this.output.y * this.thread.y;
218+
const yMax = yMin + this.output.y;
219+
const xMin = this.output.x * this.thread.x;
220+
const xMax = xMin + this.output.x;
221+
222+
for (let y = yMin; y < yMax; y++) {
223+
for (let x = xMin; x < xMax; x++) {
224+
sum += weights[y][x];
225+
}
226+
}
227+
return sum;
228+
}, {
229+
output: [2, 2]
230+
});
231+
232+
console.log(filterKernel(filters, weights));// -> [ [ 14, 22 ], [ 46, 54 ] ]
233+
```
234+
235+
## What benefits are there to using MatrixLog?
236+
1. Seeing is understanding
237+
2. Automated unit testing new GPU algorithm against already existing CPU code with an output that can be understood by humans.

0 commit comments

Comments
 (0)