forked from bhosmer/mm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ref.html
1528 lines (1239 loc) · 79.1 KB
/
ref.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>mm ref</title>
<style>
body,
html {
touch-action: none;
font-family: Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
margin: 0px;
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
overflow: hidden;
}
.viz {
position: relative;
height: 50%;
width: 100%;
}
.doc {
margin-top: 12px;
margin-bottom: 0px;
margin-left: 16px;
padding-right: 10px;
height: 50%;
overflow-y: auto;
}
.mm_container {
height: 100%;
width: 100%;
}
.mm {
border: 0px;
height: 100%;
width: 100%;
}
#popout {
text-decoration: none;
position: absolute;
right: 10px;
bottom: 0;
font-style: italic;
text-align: right;
color: #fff;
}
#sep {
position: relative;
display: flex;
height: 8px;
width: 100%;
background-color: #ccc;
cursor: ew-resize;
}
#sep::before {
content: "";
width: 30px;
height: 100%;
background: linear-gradient(to right, #666 50%, transparent 50%);
background-size: 10px 10px;
position: absolute;
left: calc(50% - 15px);
top: 0;
}
@media (orientation: portrait) {
body {
flex-direction: column;
}
#sep::before {
width: 30px;
height: 100%;
background: linear-gradient(to right, #666 50%, transparent 50%);
background-size: 10px 10px;
left: calc(50% - 15px);
top: 0;
}
#sep {
height: 8px;
width: 100%;
cursor: ns-resize;
}
.viz {
height: 50%;
width: 100%;
}
.doc {
height: 50%;
}
}
@media (orientation: landscape) {
body {
flex-direction: row;
}
#sep::before {
width: 100%;
height: 30px;
background: linear-gradient(to bottom, #666 50%, transparent 50%);
background-size: 10px 10px;
top: calc(50% - 15px);
left: 0;
}
#sep {
width: 8px;
height: 100%;
cursor: ew-resize;
}
.viz {
height: 100%;
width: 54%;
}
.doc {
height: 100%;
width: 50%;
}
}
</style>
</head>
<script type="module" src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@1/src/zero-md.min.js"></script>
<!-- <script type="module" src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@2/dist/zero-md.min.js"></script> -->
<script>
"use strict"
const getDoc = () => document.querySelector('.doc')
let mmconfig
function setSearchParams() {
const prefix = window.location.origin + window.location.pathname
const params = {
...(mmconfig ? { mm: mmconfig } : {}),
doc: JSON.stringify({ scroll: getDoc().scrollTop })
}
const search_params = new URLSearchParams(params)
window.history.pushState(params, '', prefix + '?' + search_params)
}
const RESPONDERS = {
search_params: search_params => {
mmconfig = search_params
setSearchParams()
}
}
window.addEventListener('message', event => {
Object.entries(event.data).forEach(([k, v]) => {
const r = RESPONDERS[k]
r && r(v)
})
})
function withResponse(msg, resp, f) {
const cleanup = () => {
delete RESPONDERS[resp]
}
const timeout = setTimeout(cleanup, 1000)
RESPONDERS[resp] = r => {
f(r)
clearTimeout(timeout)
cleanup()
}
mm.contentWindow.postMessage(msg)
}
function popout() {
withResponse({ getUrlInfo: undefined }, 'url_info', info => window.open(info.url, '_blank'))
}
function update(f, clear = false, focus = true) {
withResponse({ getParams: undefined }, 'params', params => set(f(params), clear))
}
function set(props = {}, reset = false, focus = true) {
mm.contentWindow.postMessage({ setParams: { props, reset } })
focus && mm.focus()
}
function openFolders(names = []) {
return {
folder: "open",
anim: { folder: "closed" },
block: { folder: "closed" },
layout: { folder: "closed" },
left: { folder: "closed" },
right: { folder: "closed" },
deco: { folder: "closed" },
viz: { folder: "closed" },
diag: { folder: "closed" },
...names.reduce((acc, f) => ({ ...acc, [f]: { folder: "open" } }), {})
}
}
const openFolder = name => openFolders([name])
function jumpTo(id) {
const dest = document.getElementById(id)
dest && (dest.parentNode.scrollTop = dest.offsetTop - dest.parentNode.offsetTop)
}
const matmap = (p, lf, rf = undefined) => ({
...p,
left: p.left.matmul ? matmap(p.left, lf, rf) : lf(p.left),
right: p.right.matmul ? matmap(p.right, lf, rf) : (rf || lf)(p.right),
})
const mmsiz = p => {
const info = p => {
const lf = p.left.matmul ? info(p.left) : { h: p.left.h, n: p.left.h * p.left.w }
const rt = p.right.matmul ? info(p.right) : { w: p.right.w, n: p.right.h * p.right.w }
return { h: lf.h, w: rt.w, n: lf.n + rt.n + lf.h * rt.w }
}
return info(p).n
}
const faster = p => ({ anim: { speed: p.anim.speed * 2 } })
const slower = p => ({ anim: { speed: p.anim.speed / 2 } })
const up = n => Math.round(n * 2)
const bigger = p => mmsiz(p) > 49152 ? {} : matmap(p, m => ({ h: up(m.h), w: up(m.w) }))
const dn = n => Math.round(n / 2)
const smaller = p => matmap(p, m => ({ h: dn(m.h), w: dn(m.w) }))
const uniform = p => matmap(p, _ => ({ init: 'uniform' }))
const rowcol = p => matmap(p, _ => ({ init: 'rows' }), _ => ({ init: 'cols' }))
const mlp = {
expr: 'batch @ w0 @ w1 @ w2',
sync_expr: true,
epilog: 'softmax',
viz: { sensitivity: 'global' },
left: {
epilog: 'relu',
left: {
epilog: 'relu',
left: { h: 64, w: 32 },
right: { h: 32, w: 64, init: 'pt linear' }
},
right: { h: 64, w: 64, init: 'pt linear' }
},
right: { h: 64, w: 32, init: 'pt linear' }
}
const mlp_named = {
...mlp,
expr: 'out = (x1 = (x0 = batch @ w0) @ w1) @ w2'
}
const mlp_lr = {
expr: 'out = batch @ (w0 = w0_L @ w0_R) @ (w1 = w1_L @ w1_R) @ (w2 = w2_L @ w2_R)',
sync_expr: true,
epilog: 'softmax',
viz: { sensitivity: 'global' },
left: {
'epilog': 'relu',
left: {
epilog: 'relu',
left: { h: 64, w: 32 },
right: {
left: { h: 32, w: 8, init: 'pt linear' },
right: { h: 8, w: 64, init: 'pt linear' }
}
},
right: {
left: { h: 64, w: 8, init: 'pt linear' },
right: { h: 8, w: 64, init: 'pt linear' }
}
},
right: {
left: { h: 64, w: 8, init: 'pt linear' },
right: { h: 8, w: 32, init: 'pt linear' }
}
}
const mlp_lr_named = {
...mlp_lr,
expr: 'out = (x1 = (x0 = batch @ (w0 = w0_L @ w0_R)) @ (w1 = w1_L @ w1_R)) @ (w2 = w2_L @ w2_R)'
}
const attn = {
expr: 'out = (attn = Q @ K) @ V',
sync_expr: true,
epilog: 'none',
left: {
epilog: 'softmax(tril(x/sqrt(k)))',
left: { h: 64, w: 16, init: 'gaussian' },
right: { h: 16, w: 64, init: 'gaussian' }
},
right: { 'h': 64, 'w': 16, 'init': 'gaussian' },
viz: { sensitivity: 'local' }
}
const attn_proj = {
expr: 'out = (attn = (Q = input @ wQ) @ (K = wK @ input_t)) @ (V = input @ wV) @ wO',
sync_expr: true,
epilog: 'none',
left: {
left: {
epilog: 'softmax(tril(x/sqrt(k)))',
left: {
left: { h: 64, w: 64, init: 'gaussian' },
right: { h: 64, w: 16, init: 'gaussian' },
},
right: {
left: { h: 16, w: 64, init: 'gaussian' },
right: { h: 64, w: 64, init: 'gaussian' },
}
},
right: {
left: { h: 64, w: 64, init: 'gaussian' },
right: { h: 64, w: 16, init: 'gaussian' },
},
},
right: { h: 16, w: 64, init: 'gaussian' },
viz: { sensitivity: 'local' }
}
const legends_only = { deco: { shape: true, legends: 6, 'row guides': 0, 'flow guides': 0 } }
const guides_only = { deco: { shape: false, legends: 0, 'row guides': 0.5, 'flow guides': 0.5 } }
const undeco = { deco: { shape: false, legends: 0, 'row guides': 0, 'flow guides': 0 } }
const default_deco = { deco: { shape: true, legends: 6, 'row guides': 0.5, 'flow guides': 0.5 } }
const reset = () => {
window.location.href = window.location.origin + window.location.pathname
}
</script>
<body>
<div id="upper" class="viz">
<iframe class="mm" id="mm" src="index.html"></iframe>
<a id="popout" href="javascript:popout();">open↗</a>
</div>
<div id="sep"></div>
<div id="lower" class="doc">
<zero-md src="">
<script type="text/markdown">
# mm - 3D matmul visualizer
Example pane is interactive and session state is saved in URL (<a href="javascript:reset();">reset</a>)
Open the current visualization in its own window at any time with the
<a href="javascript:popout();" style="font-style: italic;">open↗</a> caption link.
## Interaction
| | Laptop | Mobile |
| - | ------ | ------ |
| Zoom | Two finger vertical drag, up/down arrows | Pinch |
| Spin | Click + drag, shift-left/right arrows | Tap + drag |
| Pan | Command-click + drag, left/right arrows | Two-finger drag |
| Magnify/reveal | Long click or ctrl-click + drag | Long tap + drag |
## Saving/Sharing
* URL is a complete, sharable configuration
* note: Safari address bar truncates very long URLs - full URL is also available in the **diag** submenu
* JSON for the corresponding `parameters` configuration object also available in **diag**
## Undo
* **The tool** (use <a href="javascript:popout();" style="font-style: italic;">open↗</a> link)
records all changes in browser history, back/forward is usable for full undo/redo
* **This reference** saves session state in the URL but undo capability is limited
## Start state
The <a href="javascript:set({}, true);">default visualization</a>
(no saved URL params) visualizes a single matmul `L @ R` of two 32x32 matrices:
* Left argument `L` is initialized in *row major* order. Evenly spaced values
range from `-1` (red) at top left to `1` (blue) at bottom right.
* Right argument `R` is initialized in *column major* order with the same range of values.
* The result matrix (labeled `L @ R`) contains the result of the matmul.
* Guide lines run parallel to matrix rows, with a triangle at the `(0, 0)` corner.
* The guide arrow in the cube's interior points to the result matrix.
The ***R***ed vane points to the ***R***ight argument and the blue vane
points to the left argument.
This is just a starting point. We can
* visualize compound expressions
* model any valid argument shapes and sizes up to practical limits
* specify values using preset initializers, custom expressions or loaded data
* animate a variety of algorithms
* visualize block partitioning and fusion
* customize more or less everything about the visualization itself.
## Quick intuition refresher
* rotate, zoom, and inspect values (see **Interaction**, above)
* animate
<a href="javascript:set({anim:{alg:'dotprod (row major)'}})">pairwise dot product</a>,
<a href="javascript:set({anim:{alg:'mvprod'}})">matrix-vector product</a>,
<a href="javascript:set({anim:{alg:'vmprod'}})">vector-matrix product</a>,
<a href="javascript:set({anim:{alg:'vvprod'}})">summed outer products</a>
(<a href="javascript:update(faster)">faster</a>,
<a href="javascript:update(slower)">slower</a>,
<a href="javascript:set({anim:{alg:'none'}})">stop</a>)
* <a href="javascript:update(uniform)">initialize with random values</a> (<a href="javascript:update(rowcol)">back to rows and columns</a>)
* some common shapes:
<a href="javascript:set({expr:'L @ R', sync_expr:true, left:{h:32,w:48}, right:{h:48,w:12}})">wide to narrow</a> (e.g. q/k/v projection),
<a href="javascript:set({expr:'L @ R', sync_expr:true, left:{h:32,w:12}, right:{h:12,w:48}})">narrow to wide</a> (e.g. low rank factorization),
<a href="javascript:set({expr:'L @ R', sync_expr:true, left:{h:48,w:32}, right:{h:32,w:32}})">big batch</a>,
<a href="javascript:set({expr:'L @ R', sync_expr:true, left:{h:16,w:32}, right:{h:32,w:32}})">little batch</a>
(<a href="javascript:set({expr:'L @ R', sync_expr:true, left:{h:32,w:32}, right:{h:32,w:32}})">back to cube</a>)
* <a href="javascript:update(bigger)">bigger</a>, <a href="javascript:update(smaller)">smaller</a>
* split matmul <a href="javascript:set({block:{'i blocks':2}})">along i</a>,
<a href="javascript:set({block:{'j blocks':2}})">along j</a>,
<a href="javascript:set({block:{'k blocks':2}})">along k</a>
(<a href="javascript:set({block:{'i blocks':1, 'k blocks':1, 'j blocks': 1}})">unsplit</a>)
* visualize values with <a href="javascript:set({viz: { sensitivity: 'local' }})">local sensitivity</a> - sizes and colors
normalized per-matrix rather than across the entire visualization
(<a href="javascript:set({viz: { sensitivity: 'global' }})">back to global sensitivity</a>)
* <a href="javascript:set(legends_only)">hide guides</a>, <a href="javascript:set(undeco)">hide all deco</a> (<a href="javascript:set(default_deco)">back to default deco</a>)
A handful of slightly less basic examples, built in a few steps using the menu options described
in the rest of this guide (links above work on these examples as well, except the shape resets):
* double matmul a la attention: <a href="javascript:set(attn)">out = (attn = Q @ K) @ V</a>
* attention with input/output projections:
<a href="javascript:set(attn_proj)">out = (attn = (Q = input @ wQ) @ (K = wK @ input_t)) @ (V = input @ wV) @ wO</a>
* left-associative chain a la MLP:
<a href="javascript:set(mlp_named)">out = (x1 = (x0 = batch @ w0) @ w1) @ w2</a>
* MLP with low rank decompositions for weights:
<a href="javascript:set(mlp_lr_named)">out = (x1 = (x0 = batch @ (w0 = w0_L @ w0_R)) @ (w1 = w1_L @ w1_R)) @ (w2 = w2_L @ w2_R)</a>
# mm Menu
Configuration is specified interactively via the **mm** menu at upper right.
All options map directly to properties encoded in the URL.
* <a href="javascript: set(openFolders()); jumpTo('toplevel-submenu')">**top level** options</a>
configure global properties, mainly the structure of the matmul expression itself
* <a href="javascript: set(openFolders(['left', 'right'])); jumpTo('lr-submenu')">**left** and **right** submenus</a>
configure the left and right arguments of the top level matmul, including initialization and shape
* If either argument is a subexpression, the corresponding submenu will have its own left and right submenus
* <a href="javascript: set(openFolders(['anim'])); jumpTo('animation-submenu')">**animation** submenu</a>
controls how the matmul computation is animated
* <a href="javascript: set(openFolders(['block'])); jumpTo('blocking-submenu')">**blocking** submenu</a>
configures how the expression is partitioned into blocks
* <a href="javascript: set(openFolders(['layout'])); jumpTo('layout-submenu')">**layout** submenu</a>
controls the orientation and spacing of the expression in 3D space
* <a href="javascript: set(openFolders(['deco'])); jumpTo('deco-submenu')">**deco** submenu</a>
configures decorations like legends and orientation guides, and options for showing numerical values
* <a href="javascript: set(openFolders(['viz'])); jumpTo('cs-submenu')">**colors and sizes** submenu</a>
configures the color and brightness details of how values are mapped
* <a href="javascript: set(openFolders(['diag'])); jumpTo('diag-submenu')">**diag** submenu</a>
contains stats and data, e.g. the current URL, JSON config object, etc.
</script>
</zero-md>
<script>
const deep_binary = { expr: '(L = (LL = LLL @ LLR) @ (LR = LRL @ LRR)) @ (R = (RL = RLL @ RLR) @ (RR = (RRL @ RRR)))', sync_expr: true, folder: 'open' }
</script>
<zero-md src="" id="toplevel-submenu">
<script type="text/markdown">
#
# <a href="javascript:set(openFolders())">Top Level</a>
## expr
**expr** can be edited directly to specify the structure of the matmul being visualized.
(Alternatively, the structure can be built interactively via the **left** and **right**
submenus, which configure the properties of the matmul's constituents in any case.)
An expression is of the form `left @ right` or `result = left @ right`, where
* `left` and `right` can be subexpressions
* `result` names the visualized result matrix (if not provided, the expression itself is used)
* parens are used to group right-associative subexpressions and subexpression assignments
When **expr** is edited, existing settings are preserved on a best-efforts basis - in particular when names are reused.
These examples show the effect of simply editing the **expr** of the current visualization, with no other modifications:
* double matmul a la attention: <a href="javascript:set({expr:'out = (attn = Q @ K) @ V', sync_expr:true, folder:'open'})">out = (attn = Q @ K) @ V</a>
* left-associative chain: <a href="javascript:set({expr:'A @ B @ C @ D', sync_expr:true, folder:'open'})">A @ B @ C @ D</a>
* with named results: <a href="javascript:set({expr:'X = (ABC = (AB = A @ B) @ C) @ D', sync_expr:true, folder:'open'})">X = (ABC = (AB = A @ B) @ C) @ D</a>
* right-associative chain: <a href="javascript:set({ expr:'A @ (B @ (C @ D))', sync_expr:true, folder:'open'})">X = A @ (B @ (C @ D))</a>
* with named results: <a href="javascript:set({ expr:'X = A @ (BCD = B @ (CD = C @ D))', sync_expr:true, folder:'open'})">X = A @ (BCD = B @ (CD = C @ D))</a>
* left and right subexpressions: <a href="javascript:set({ expr:'(L = LL @ LR) @ (R = RL @ RR)', sync_expr:true, folder:'open'})">(L = LL @ LR) @ (R = RL @ RR)</a>
* children beget grandchildren: <a href="javascript:set(deep_binary)">(L = (LL = LLL @ LLR) @ (LR = LRL @ LRR)) @ (R = (RL = RLL @ RLR) @ (RR = (RRL @ RRR)))</a>
* note: the <a href="javascript:jumpTo('layout-submenu')">**layout**</a> submenu contains various options
that can be used to let some air into visualizations of deeply nested saturated expressions like this one -
for example <a href="javascript:update(scatter(10))">adding space</a> between subexpressions and their parents,
using <a href="javascript:set({layout:{gap:8,molecule:1,folder:'open'}})">scattered</a>
or <a href="javascript:set({layout:{gap:1,molecule:3,folder:'open'}})">dense</a> subexpression layouts.
(<a href="javascript:set({layout:{gap:4,scatter:0,molecule:1,folder:'open'}})">back to default</a>)
* also, for expressions like this one which contain both very small and very large values, it can be useful to set
sensitivity to <a href="javascript:set({viz:{sensitivity:'local'}})">local</a> in the
<a href="javascript:jumpTo('viz-submenu')">**visualization**</a> submenu. This normalizes
element color and size within each matrix, rather than across the entire expression.
(<a href="javascript:set({viz:{sensitivity:'global'}})">back to global</a>)
## name
Names the result matrix of the matmul being computed. Can be edited directly,
and/or is synchronized with **expr**.
## epilog
Specifies a function to run on the result matrix after the top level matmul has been calculated.
(Child matmuls will have their own choice of epilogs, in the **left** and **right** submenus.)
* <a href="javascript:set({epilog:'none',folder:'open'})">none</a>: no epilog, result values are unmodified
* <a href="javascript:set({epilog:'relu',folder:'open'})">relu</a>: relu is applied to result values
* <a href="javascript:set({epilog:'gelu',folder:'open'})">gelu</a>: <a href="https://arxiv.org/pdf/1606.08415.pdf">gelu</a> is applied to result values
* <a href="javascript:set({epilog:'sigmoid',folder:'open'})">sigmoid</a>: <a href="https://paperswithcode.com/method/sigmoid-activation">sigmoid</a> is applied to result values
* <a href="javascript:set({epilog:'silu',folder:'open'})">silu</a>: <a href="https://arxiv.org/pdf/1702.03118v3.pdf">silu</a> is applied to result values
* <a href="javascript:set({epilog:'tanh',folder:'open'})">tanh</a>: tanh is applied to result values
* <a href="javascript:set({epilog:'layernorm',folder:'open'})">layernorm</a>: matrix-wide layernorm is applied to result values
* <a href="javascript:set({epilog:'softmax',folder:'open'})">softmax</a>: values are softmaxed by row
* <a href="javascript:set({epilog:'softmax(x/sqrt(k))',folder:'open'})">softmax(x/sqrt(k))</a>: values are scaled by the square root of matmul depth,
then softmaxed by row (a la scaled dot product attention)
* <a href="javascript:set({epilog:'softmax(tril(x/sqrt(k)))',folder:'open'})">softmax(tril(x/sqrt(k)))</a>: values are scaled by the square root of depth,
then the upper triangle is masked, then softmax is applied by row (causal SDPA)
</script>
</zero-md>
<script>
const matmap_openf = (p, lf, rf = undefined) => ({
...p,
folder: 'open',
left: p.left.matmul ? matmap_openf(p.left, lf, rf) : { folder: 'open', ...lf(p.left) },
right: p.right.matmul ? matmap_openf(p.right, lf, rf) : { folder: 'open', ...(rf || lf)(p.right) },
})
const leftinit = init => p => matmap_openf(p, _ => ({ init }), _ => ({}))
const rightinit = init => p => matmap_openf(p, _ => ({}), _ => ({ init }))
const bothinit = init => p => matmap_openf(p, _ => ({ init }))
const lrinit = (linit, rinit) => p => matmap_openf(p, _ => ({ init: linit }), _ => ({ init: rinit }))
const urlinit = (l, r) => p => matmap_openf(p, _ => ({ init: 'url', url: l }), _ => ({ init: 'url', url: r }))
const dropout = dropout => p => matmap_openf(p, _ => ({ dropout }))
</script>
<zero-md src="" id="lr-submenu">
<script type="text/markdown">
#
# <a href="javascript:set(openFolders(['left', 'right']))">Left and Right</a>
Left and right submenus configure the left and right arguments of the matmul
being visualized.
Menu options vary depending on whether the argument is itself a matmul, as described below:
subexpressions can nest to arbitrary depth, and left and right submenus will nest accordingly.
## name
Names this argument. Can be edited directly, and/or is synchronized with **expr**.
## matmul
This checkbox specifies whether the argument is itself a matmul, or just a matrix.
Subsequent options in the menu depend on the value of this option.
## *Left/Right matrix options (matmul unchecked)*
## h, w
Matrix height and width. The width of the left argument and height of the right argument
are synchronized.
## init
Specifies how the values of the matrix are initialized. Various init functions are available,
or you can load external data from CSV files.
#### Position-based
* **rows**: elements in each row are initialized to the same value, with values spread evenly across a range specified
by **min** and **max** options. <a href="javascript:update(leftinit('rows'))">Set left init to rows</a>
* **cols**: elements in each column are initialized to the same value, with values spread evenly across a range specified
by **min** and **max** options. <a href="javascript:update(rightinit('cols'))">Set right init to cols</a>
* **row major**: elements are initialized from top left to bottom right in row major order, with values spread evenly across
a range specified by **min** and **max** options. <a href="javascript:update(leftinit('row major'))">Set left init to row major</a>
* **col major**: elements are initialized from top left to bottom right in column major order, with values spread evenly across
a range specified by **min** and **max** options. <a href="javascript:update(rightinit('col major'))">Set right init to col major</a>
#### Random
* **pt linear**: elements are drawn from a shape-dependent uniform distribution as described
[here](https://pytorch.org/docs/stable/generated/torch.nn.Linear.html).
<a href="javascript:update(bothinit('pt linear'))">Set left and right init to pt linear</a>
* **uniform**: elements are drawn from a uniform distribution as specified by **min** and **max** options.
<a href="javascript:update(bothinit('uniform'))">Set left and right init to uniform</a>
* **gaussian**: elements are drawn from a gaussian distribution as specified by **min** and **max** options.
<a href="javascript:update(bothinit('gaussian'))">Set left and right init to gaussian</a>
#### Algebraic
* **tril mask**: lower triangular elements are initialized to one, others are initialized to zero.
<a href="javascript:update(leftinit('tril mask'))">Set left init to tril mask</a>
* **triu mask**: upper triangular elements are initialized to one, others are initialized to zero.
<a href="javascript:update(leftinit('triu mask'))">Set left init to triu mask</a>
* **eye**: elements on the diagonal are initialized to one, others are initialized to zero.
<a href="javascript:update(leftinit('eye'))">Set left init to eye</a>
* **diff**: elements on the diagonal are initialized to one; elements immediately left of the diagonal are initialized to -1,
all others are initialized to zero. <a href="javascript:update(leftinit('diff'))">Set left init to diff</a>
#### Custom
* **url**: shows a **url** field which can be used to specify a URL pointing to CSV
initialization data. Loaded data is consumed row by row, rolling over both columnwise and rowwise if
matrix shape exceeds available data.
<a href="javascript:update(urlinit(
'https://raw.githubusercontent.com/bhosmer/testdata/main/weights/gpt2/layer0_wq0_768_64.csv',
'https://raw.githubusercontent.com/bhosmer/testdata/main/weights/gpt2/layer0_wk_t0_64_768.csv'
))">Set left and right init to sample loaded weights</a>
* **expr**: show an **expr** field which can be used to specify a Javascript initialization expression.
Available variables are `i` (row index), `j` (column index), `h` (matrix height) and `w` (matrix width).
## dropout
Specifies a level of sparsity between 0 (0%) and 1 (100%) for row/col and random initializations.
* <a href="javascript:update(dropout(0.5))">Set left and right dropout to 0.5</a> (if applicable to current init choices)
## *Left/Right matmul options (matmul checked)*
When the **matmul** option is checked in the **left** and/or **right** submenu, the matrix options
(h/w, init, etc.) are removed and matmul options are added: left/right sub-submenus, epilog, animation
and (depending on global options) layout.
</script>
</zero-md>
<script>
//
const left_chain_vmprod_fuse = {
expr: 'X = A @ B @ C @ D @ E', sync_expr: true, folder: 'open',
anim: { fuse: 'sync', alg: 'vmprod', folder: 'open' },
left: {
anim: { alg: 'inherit' },
left: {
anim: { alg: 'inherit' },
left: {
anim: { alg: 'inherit' },
},
},
},
}
const right_chain_mvprod_fuse = {
expr: 'X = A @ (B @ (C @ (D @ E)))', sync_expr: true, folder: 'open',
anim: { fuse: 'sync', alg: 'mvprod', folder: 'open' },
right: {
anim: { alg: 'inherit' },
right: {
anim: { alg: 'inherit' },
right: {
anim: { alg: 'inherit' },
}
}
}
}
const binary_fuse = {
expr: 'X = (A @ B) @ (C @ D)', sync_expr: true, folder: 'open',
anim: { fuse: 'sync', 'alg': 'vvprod', 'folder': 'open' },
left: { anim: { alg: 'mvprod', 'folder': 'open' }, },
right: { anim: { alg: 'vmprod', 'folder': 'open' }, }
}
const left_right_chain_fuse = {
expr: 'X = (A @ (B @ C)) @ ((D @ E) @ F)', sync_expr: true, folder: 'open',
anim: { fuse: 'sync', 'alg': 'vvprod', 'folder': 'open' },
left: {
folder: 'open',
anim: { alg: 'mvprod', 'folder': 'open' },
right: { anim: { alg: 'inherit' } },
},
right: {
folder: 'open',
anim: { alg: 'vmprod', 'folder': 'open' },
left: { anim: { alg: 'inherit' } },
}
}
</script>
<zero-md src="" id="animation-submenu">
<script type="text/markdown">
#
# <a href="javascript:set(openFolders(['anim']))">Animation</a>
Various matmul algorithms can be animated. In a compound expression
animation proceeds leaf-to-root in dependency order, with mutually independent
subexpressions proceeding in parallel.
Algorithms are also parallelized within <a href="javascript:jumpTo('blocking-submenu')">blocking</a>
partitions.
Fusion across matmuls occurs automatically as geometry permits - see the **fuse**
option below for details.
## alg
Specifies the matmul algorithm to be animated.
When a compound expression is animated, each child matmul chooses its own animation
algorithm - or chooses to inherit that of its parent - via an **animation** submenu.
When a matmul specifies algorithm `none`, the expression tree rooted at that matmul
is not animated: a choice of `none` at the top level disables animation globally,
whereas `none` in a child disables animation of that subexpression.
* <a href="javascript:set({anim:{alg:'none'}})">none</a>: no animation of this matmul or its children
* <a href="javascript:set({anim:{alg:'dotprod (row major)'}})">dotprod (row major)</a>: elements of the result matrix are computed in **row major order** via **pairwise dot product**
* <a href="javascript:set({anim:{alg:'dotprod (col major)'}})">dotprod (col major)</a>: elements of the result matrix are computed in **column major order** via **pairwise dot product**
* <a href="javascript:set({anim:{alg:'axpy'}})">axpy</a>: columns of the result matrix are computed are computed by depthwise accumulation of **vector-scalar products**
* <a href="javascript:set({anim:{alg:'vmprod'}})">vmprod</a>: rows of the result matrix are computed via **vector-matrix products** over rows of the left argument
* <a href="javascript:set({anim:{alg:'mvprod'}})">mvprod</a>: columns of the result matrix are computed via **matrix-vector products** over columns of the right argument
* <a href="javascript:set({anim:{alg:'vvprod'}})">vvprod</a>: the result matrix is computed via depthwise accumulation of **vector-vector outer products**
* **inherit** (child matmuls only): inherits the algorithm of its parent. This is the default setting for
newly created child matmuls
## speed
Specifies animation speed. Values of this setting are not calibrated precisely: 1 is very slow and 100
is essentally as fast as the browser can animate frames (for complicated visualizations, animation speed
will top out well before 100).
Note that animation speed is *not* calibrated to reflect the relative amounts of work performed: each
frame advances a single step, regardless of algorithm or argument shapes.
Choose an algorithm above and run it <a href="javascript:update(faster)">faster</a> or <a href="javascript:update(slower)">slower</a>.
## pause
Pauses animation when checked.
## step
Advances paused animation one step. Useful for inspecting intermediate values in conjunction with
**interior spotlight** (see <a href="javascript:jumpTo('deco-submenu')">deco</a> menu).
## fuse
In compound matmul expressions, particular combinations of adjacent planar algorithms
(<a href="javascript:set({anim:{alg:'vmprod'}})">vector-matrix</a>,
<a href="javascript:set({anim:{alg:'mvprod'}})">matrix-vector</a>,
<a href="javascript:set({anim:{alg:'vvprod'}})">vector-vector</a>)
can be **fused** when they generate parallel planes.
Parallel planes visually indicate a shared *sweep axis* along which
the data dependency between adjacent matmuls can be partitioned,
meaning the coplanar algorithms can be fused and parallelized.
(Partitions can be also visualized explicitly, irrespective of fusion, in the <a href="javascript:jumpTo('blocking-submenu')">**blocking**</a> submenu.)
### Fused pairs
The following combinations of two-matmul expression shape and algorithm generate parallel planes:
#### Left-associative: `A @ B @ C`
| `A @ B` (left child) | `_ @ C` (top level) | |
| - | - | - |
| vector-matrix product (fusion along **i**) | vector-matrix product (fusion along **i**) | <a href="javascript:set({ expr: 'A @ B @ C', sync_expr: true, folder: 'open', anim: { alg: 'vmprod', fuse: 'sync', folder: 'open' }, left: { folder: 'open', anim: { alg: 'inherit', folder: 'open' } } })">Show</a> |
| matrix-vector product (fusion along **k**) | vector-vector product (fusion along **j**) | <a href="javascript:set({ expr: 'A @ B @ C', sync_expr: true, folder: 'open', anim: { alg: 'vvprod', fuse: 'sync', folder: 'open' }, left: { folder: 'open', anim: { alg: 'mvprod', folder: 'open' } } })">Show</a> |
#### Right-associative: `A @ (B @ C)`
| `B @ C` (right child) | `A @ _` (top level) | |
| - | - | - |
| vector-matrix product (fusion along **i**) | vector-vector product (fusion along **j**) | <a href="javascript:set({ expr: 'A @ (B @ C)', sync_expr: true, folder: 'open', anim: { alg: 'vvprod', fuse: 'sync', folder: 'open' }, right: { folder: 'open', anim: { alg: 'vmprod', folder: 'open' } } })">Show</a> |
| matrix-vector product (fusion along **k**) | matrix-vector product (fusion along **k**) | <a href="javascript:set({ expr: 'A @ (B @ C)', sync_expr: true, folder: 'open', anim: { alg: 'mvprod', fuse: 'sync', folder: 'open' }, right: { folder: 'open', anim: { alg: 'inherit', folder: 'open' } } })">Show</a> |
### Fused chains
We can adjoin multiple pairs with common parallel planes to create extended fusions. Some examples:
* **`A @ B @ C @ ...`**: Generalizing the first left-associative pattern above,
left-associative chains of any length can be fused along **i** (**vector-matrix** product).
Here the plane slicing through the compound corresponds to e.g. the path of computation for a single
batch item as it travels through an <a href="https://pytorch.org/docs/stable/generated/torch.nn.Linear.html">MLP</a>.
This laminar geometry underlies <a href="https://pytorch.org/docs/stable/generated/torch.nn.DataParallel.html">data parallelism</a>
(splitting a batch across multiple nodes):
* Example: <a href="javascript:set(left_chain_vmprod_fuse)">`X = A @ B @ C @ D @ E`</a>
* **`A @ (B @ (C @ (...)))`**: Similarly, the second right-associative pattern above generalizes
to right-associative chains of any length fused along **k** (**matrix-vector** product).
* Example: <a href="javascript:set(right_chain_mvprod_fuse)">`X = A @ (B @ (C @ (D @ E)))`</a>
* **`(A @ B) @ (C @ D)`**: Binary expressions may be fused by computing the left subexpression along **k**
(**matrix-vector** product) and the right subexpression along **i** (**vector-matrix** product), and joining them
along **j** (**vector-vector** product).
* Example: <a href="javascript:set(binary_fuse)">`X = (A @ B) @ (C @ D)`</a>
* **`(A @ (...)) @ ((...) @ Z)`**: More generally, binary expressions with a *right*-associative chain of arbitrary
length on the *left*, and a *left*-associative chain of arbitrary length on the *right*, may be fused into a single
unit by fusing the chained subexpressions as in the preceding examples, then joining them along **j**
(**vector-vector** product) at the root.
* Example: <a href="javascript:set(left_right_chain_fuse)">`X = (A @ (B @ C)) @ ((D @ E) @ F)`</a>
### *sync vs async*
Options for **fuse** are `none` (no fusion), `sync` and `async`. The difference between `sync` and `async` is
subtle, and only apparent when large compound matmuls are animated. When **fuse** is set to `sync`, animations
are only fused when all participants begin can computation simultaneously (i.e., are not waiting for subcomputations
to complete), whereas `async` animations will fuse even when participants begin at different times, dependency
order permitting.
For most expressions, `sync` and `async` will behave identically.
## hide inputs
Hiding inputs during animation can make it easier to inspect visualizations of intermediate results
rendered in the matmul cube's interior, in particular when **deco** menu options are adjusted to
minimize decoration and enable interior spotlighting (the latter reveals values in intermediate
visualizations - see **deco** for details).
## spin
Spinning a visualization can be useful in developing geometric intuition for the operations being visualized.
* <a href="javascript:set({anim:{spin:-1, folder:'open'}, folder:'open'})">spin clockwise</a>,
<a href="javascript:set({anim:{spin:1, folder:'open'}, folder:'open'})">spin counterclockwise</a>
* <a href="javascript:update(spin_faster)">faster</a>,
<a href="javascript:update(spin_slower)">slower</a>,
<a href="javascript:set({anim:{spin:0, folder:'open'}, folder:'open'})">stop</a>
</script>
</zero-md>
<script>
const spin_faster = p => ({ anim: { spin: Math.sign(p.anim.spin) * Math.min(10, Math.abs(p.anim.spin) * 2) } })
const spin_slower = p => ({ anim: { spin: Math.sign(p.anim.spin) * Math.max(0, Math.abs(p.anim.spin) / 2) } })
const blocks = (expr, n) => ({
expr, sync_expr: true, folder: 'open',
block: { 'i blocks': n[0], 'k blocks': n[1], 'j blocks': n[2], folder: 'open' },
anim: { alg: 'none', fuse: 'none' }
})
const i_blocks = (expr, n) => blocks(expr, [n, 1, 1])
const k_blocks = (expr, n) => blocks(expr, [1, n, 1])
const j_blocks = (expr, n) => blocks(expr, [1, 1, n])
const add_k_child_blocks = n =>
p => ({
...(p.left.matmul ? { left: { folder: 'open', block: { 'k blocks': n } } } : {}),
...(p.right.matmul ? { right: { folder: 'open', block: { 'k blocks': n } } } : {})
})
const kids = (la, ra) => ({
...(la ? { left: { folder: la == 'inherit' ? 'closed' : 'open', anim: { folder: 'open', alg: la } } } : {}),
...(ra ? { right: { folder: ra == 'inherit' ? 'closed' : 'open', anim: { folder: 'open', alg: ra } } } : {})
})
const dp = (fuse = 'none', la, ra) => ({ anim: { folder: 'open', alg: 'dotprod (row major)', fuse }, ...kids(la, ra) })
const mvp = (fuse = 'none', la, ra) => ({ anim: { folder: 'open', alg: 'mvprod', fuse }, ...kids(la, ra) })
const vmp = (fuse = 'none', la, ra) => ({ anim: { folder: 'open', alg: 'vmprod', fuse }, ...kids(la, ra) })
const vvp = (fuse = 'none', la, ra) => ({ anim: { folder: 'open', alg: 'vvprod', fuse }, ...kids(la, ra) })
const stop = { anim: { folder: 'closed', alg: 'none' } }
</script>
<zero-md src="" id="blocking-submenu">
<script type="text/markdown">
#
# <a href="javascript:set(openFolders(['block']))">Blocking</a>
Both simple and compound expressions can be partitioned into blocks in 3 dimensions.
Partitions are *global*, in the sense that planes of partition cut across subexpressions,
representing parallelization of the entire compound expression.
The rules of partition propagation are straightforward (but note the subtlety around `k`):
* a partition along the `i` axis extends along contiguous chains of left arguments
* a partition along the `j` axis extends along contiguous chains of right arguments
* a partition along the `k` axis becomes a partition along the `j` axis for its left child,
and a partition along the `i` axis for its right child (at which point it is subject to the `i`/`j` rules above)
Partitions in all dimensions can be freely mixed.
Partitioning in each dimension corresponds to a different parallelization approach, with
distinctive geometric properties.
Animations are parallelized within the currently configured partitions.
Blocks are equal sized, with a short final block if the number of blocks is not an even divisor.
## i blocks
Partitions the expression into the specified number of blocks along the **i** axis.
**i** is the height dimension (rows) of the root matmul and its chain
of left descendants. Partitioning it cuts the expression into horizontal slices within the
height bounds of the root matmul: i.e., the root matmul and its left descendants are
partitioned, but not any matmul rooted in a right descendant.
Examples partitioned into 4 blocks along **i**
(<a href="javascript:set({block:{'i blocks':1}})">back to 1</a>):
* <a href="javascript:set(i_blocks('X = A @ B', 4))">X = A @ B</a> (animate:
<a href="javascript:set({...i_blocks('X = A @ B', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B', 4), ...mvp()})">mvprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B', 4), ...vmp()})">vmprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B', 4), ...vvp()})">vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(i_blocks('X = A @ B @ C', 4))">X = A @ B @ C</a> (animate:
<a href="javascript:set({...i_blocks('X = A @ B @ C', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B @ C', 4), ...mvp('none', 'inherit')})">mvprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B @ C', 4), ...vmp('none', 'inherit')})">vmprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B @ C', 4), ...vvp('none', 'inherit')})">vvprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B @ C', 4), ...vmp('sync', 'inherit')})">fused vmprod</a>,
<a href="javascript:set({...i_blocks('X = A @ B @ C', 4), ...vvp('sync', 'mvprod')})">fused left mvprod/parent vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(i_blocks('X = A @ (B @ C)', 4))">X = A @ (B @ C)</a> (animate:
<a href="javascript:set({...i_blocks('X = A @ (B @ C)', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...i_blocks('X = A @ (B @ C)', 4), ...mvp('none', undefined, 'inherit')})">mvprod</a>,
<a href="javascript:set({...i_blocks('X = A @ (B @ C)', 4), ...vmp('none', undefined, 'inherit')})">vmprod</a>,
<a href="javascript:set({...i_blocks('X = A @ (B @ C)', 4), ...vvp('none', undefined, 'inherit')})">vvprod</a>,
<a href="javascript:set({...i_blocks('X = A @ (B @ C)', 4), ...mvp('sync', undefined, 'inherit')})">fused mvprod</a>,
<a href="javascript:set({...i_blocks('X = A @ (B @ C)', 4), ...vvp('sync', undefined, 'vmprod')})">fused right vmprod/parent vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(i_blocks('X = (A @ B) @ (C @ D)', 4))">X = (A @ B) @ (C @ D)</a> (animate:
<a href="javascript:set({...i_blocks('X = (A @ B) @ (C @ D)', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...i_blocks('X = (A @ B) @ (C @ D)', 4), ...mvp('none', 'inherit', 'inherit')})">mvprod</a>,
<a href="javascript:set({...i_blocks('X = (A @ B) @ (C @ D)', 4), ...vmp('none', 'inherit', 'inherit')})">vmprod</a>,
<a href="javascript:set({...i_blocks('X = (A @ B) @ (C @ D)', 4), ...vvp('none', 'inherit', 'inherit')})">vvprod</a>,
<a href="javascript:set({...i_blocks('X = (A @ B) @ (C @ D)', 4), ...vvp('sync', 'mvprod', 'vmprod')})">fused left mvprod/right vmprod/parent vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
## k blocks
Partitions the expression into the specified number of blocks along the **k** axis.
**k** is the matmul's "reduction" dimension, shared by the width of the left argument
and height of the right argument.
Although not left- or right-transitive like partitions along **i** or **j** axes (respectively),
the geometry of partitioning along **k** is unique in extending to *both* left and right child
matmuls via rotation:
* a partition along **k** in a parent becomes a partition along **j** in a left child
* a partition along **k** in a parent becomes a partition along **i** in a right child
Examples partitioned into 4 blocks along **j**
(<a href="javascript:set({block:{'k blocks':1}})">back to 1</a>):
* <a href="javascript:set(k_blocks('X = A @ B', 4))">X = A @ B</a> (animate:
<a href="javascript:set({...k_blocks('X = A @ B', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B', 4), ...mvp()})">mvprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B', 4), ...vmp()})">vmprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B', 4), ...vvp()})">vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(k_blocks('X = A @ B @ C', 4))">X = A @ B @ C</a> (animate:
<a href="javascript:set({...k_blocks('X = A @ B @ C', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B @ C', 4), ...mvp('none', 'inherit')})">mvprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B @ C', 4), ...vmp('none', 'inherit')})">vmprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B @ C', 4), ...vvp('none', 'inherit')})">vvprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B @ C', 4), ...vmp('sync', 'inherit')})">fused vmprod</a>,
<a href="javascript:set({...k_blocks('X = A @ B @ C', 4), ...vvp('sync', 'mvprod')})">fused left mvprod/parent vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(k_blocks('X = A @ (B @ C)', 4))">X = A @ (B @ C)</a> (animate:
<a href="javascript:set({...k_blocks('X = A @ (B @ C)', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...k_blocks('X = A @ (B @ C)', 4), ...mvp('none', undefined, 'inherit')})">mvprod</a>,
<a href="javascript:set({...k_blocks('X = A @ (B @ C)', 4), ...vmp('none', undefined, 'inherit')})">vmprod</a>,
<a href="javascript:set({...k_blocks('X = A @ (B @ C)', 4), ...vvp('none', undefined, 'inherit')})">vvprod</a>,
<a href="javascript:set({...k_blocks('X = A @ (B @ C)', 4), ...mvp('sync', undefined, 'inherit')})">fused mvprod</a>,
<a href="javascript:set({...k_blocks('X = A @ (B @ C)', 4), ...vvp('sync', undefined, 'vmprod')})">fused right vmprod/parent vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(k_blocks('X = (A @ B) @ (C @ D)', 4))">X = (A @ B) @ (C @ D)</a> (animate:
<a href="javascript:set({...k_blocks('X = (A @ B) @ (C @ D)', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...k_blocks('X = (A @ B) @ (C @ D)', 4), ...mvp('none', 'inherit', 'inherit')})">mvprod</a>,
<a href="javascript:set({...k_blocks('X = (A @ B) @ (C @ D)', 4), ...vmp('none', 'inherit', 'inherit')})">vmprod</a>,
<a href="javascript:set({...k_blocks('X = (A @ B) @ (C @ D)', 4), ...vvp('none', 'inherit', 'inherit')})">vvprod</a>,
<a href="javascript:set({...k_blocks('X = (A @ B) @ (C @ D)', 4), ...vvp('sync', 'mvprod', 'vmprod')})">fused left mvprod/right vmprod/parent vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
## j blocks
Partitions the expression into the specified number of blocks along the **j** axis.
**j** is the width dimension (columns) of the root matmul and its chain
of right descendants. Partitioning it cuts the expression into vertical slices within the
width bounds of the root matmul: i.e., the root matmul and its right descendants are
partitioned, but not any matmul rooted in a left descendant.
Examples partitioned into 4 blocks along **j**
(<a href="javascript:set({block:{'j blocks':1}})">back to 1</a>):
* <a href="javascript:set(j_blocks('X = A @ B', 4))">X = A @ B</a> (animate:
<a href="javascript:set({...j_blocks('X = A @ B', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...j_blocks('X = A @ B', 4), ...mvp()})">mvprod</a>,
<a href="javascript:set({...j_blocks('X = A @ B', 4), ...vmp()})">vmprod</a>,
<a href="javascript:set({...j_blocks('X = A @ B', 4), ...vvp()})">vvprod</a> -
<a href="javascript:update(faster)">faster</a>, <a href="javascript:update(slower)">slower</a>, <a href="javascript:set(stop)">stop</a>)
* <a href="javascript:set(j_blocks('X = A @ B @ C', 4))">X = A @ B @ C</a> (animate:
<a href="javascript:set({...j_blocks('X = A @ B @ C', 4), ...dp()})">dotprod</a>,
<a href="javascript:set({...j_blocks('X = A @ B @ C', 4), ...mvp('none', 'inherit')})">mvprod</a>,
<a href="javascript:set({...j_blocks('X = A @ B @ C', 4), ...vmp('none', 'inherit')})">vmprod</a>,
<a href="javascript:set({...j_blocks('X = A @ B @ C', 4), ...vvp('none', 'inherit')})">vvprod</a>,