@driftengine/capture 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +29 -0
  3. package/README.md +68 -0
  4. package/dist/browserFrames.d.ts +31 -0
  5. package/dist/browserFrames.js +105 -0
  6. package/dist/collide.d.ts +59 -0
  7. package/dist/collide.js +92 -0
  8. package/dist/decimate.d.ts +42 -0
  9. package/dist/decimate.js +439 -0
  10. package/dist/delight.d.ts +42 -0
  11. package/dist/delight.js +351 -0
  12. package/dist/delightScene.d.ts +37 -0
  13. package/dist/delightScene.js +183 -0
  14. package/dist/depth.d.ts +44 -0
  15. package/dist/depth.js +53 -0
  16. package/dist/features.d.ts +33 -0
  17. package/dist/features.js +260 -0
  18. package/dist/frames.d.ts +47 -0
  19. package/dist/frames.js +87 -0
  20. package/dist/fusion.d.ts +77 -0
  21. package/dist/fusion.js +152 -0
  22. package/dist/gaussians/adam.d.ts +57 -0
  23. package/dist/gaussians/adam.js +88 -0
  24. package/dist/gaussians/depth.d.ts +8 -0
  25. package/dist/gaussians/depth.js +73 -0
  26. package/dist/gaussians/gradients.d.ts +36 -0
  27. package/dist/gaussians/gradients.js +228 -0
  28. package/dist/gaussians/harmonics.d.ts +46 -0
  29. package/dist/gaussians/harmonics.js +99 -0
  30. package/dist/gaussians/optimise.d.ts +50 -0
  31. package/dist/gaussians/optimise.js +125 -0
  32. package/dist/gaussians/parameters.d.ts +35 -0
  33. package/dist/gaussians/parameters.js +165 -0
  34. package/dist/gaussians/project.d.ts +72 -0
  35. package/dist/gaussians/project.js +137 -0
  36. package/dist/gaussians/projectGradient.d.ts +15 -0
  37. package/dist/gaussians/projectGradient.js +213 -0
  38. package/dist/gaussians/rasterise.d.ts +13 -0
  39. package/dist/gaussians/rasterise.js +65 -0
  40. package/dist/gaussians/refine.d.ts +36 -0
  41. package/dist/gaussians/refine.js +136 -0
  42. package/dist/gaussians/similarity.d.ts +25 -0
  43. package/dist/gaussians/similarity.js +80 -0
  44. package/dist/index.d.ts +101 -0
  45. package/dist/index.js +96 -0
  46. package/dist/marching.d.ts +38 -0
  47. package/dist/marching.js +258 -0
  48. package/dist/masks.d.ts +59 -0
  49. package/dist/masks.js +165 -0
  50. package/dist/math/dense.d.ts +68 -0
  51. package/dist/math/dense.js +294 -0
  52. package/dist/math/schur.d.ts +23 -0
  53. package/dist/math/schur.js +140 -0
  54. package/dist/models/batchNorm.d.ts +17 -0
  55. package/dist/models/batchNorm.js +24 -0
  56. package/dist/models/clipTokenizer.d.ts +40 -0
  57. package/dist/models/clipTokenizer.js +122 -0
  58. package/dist/models/clipTower.d.ts +27 -0
  59. package/dist/models/clipTower.js +45 -0
  60. package/dist/models/depthAnything2.d.ts +38 -0
  61. package/dist/models/depthAnything2.js +163 -0
  62. package/dist/models/depthAnything3.d.ts +26 -0
  63. package/dist/models/depthAnything3.js +171 -0
  64. package/dist/models/dualDpt.d.ts +19 -0
  65. package/dist/models/dualDpt.js +138 -0
  66. package/dist/models/hiera.d.ts +49 -0
  67. package/dist/models/hiera.js +120 -0
  68. package/dist/models/hieraAttention.d.ts +16 -0
  69. package/dist/models/hieraAttention.js +68 -0
  70. package/dist/models/miniature.d.ts +36 -0
  71. package/dist/models/miniature.js +216 -0
  72. package/dist/models/mobileSam.d.ts +41 -0
  73. package/dist/models/mobileSam.js +88 -0
  74. package/dist/models/owlv2.d.ts +49 -0
  75. package/dist/models/owlv2.js +131 -0
  76. package/dist/models/owlv2Detect.d.ts +25 -0
  77. package/dist/models/owlv2Detect.js +80 -0
  78. package/dist/models/owlv2Miniature.d.ts +27 -0
  79. package/dist/models/owlv2Miniature.js +87 -0
  80. package/dist/models/rope.d.ts +23 -0
  81. package/dist/models/rope.js +84 -0
  82. package/dist/models/sam21.d.ts +45 -0
  83. package/dist/models/sam21.js +126 -0
  84. package/dist/models/sam21Miniature.d.ts +37 -0
  85. package/dist/models/sam21Miniature.js +194 -0
  86. package/dist/models/sam21Video.d.ts +30 -0
  87. package/dist/models/sam21Video.js +101 -0
  88. package/dist/models/sam2Choice.d.ts +19 -0
  89. package/dist/models/sam2Choice.js +55 -0
  90. package/dist/models/sam2Decoder.d.ts +36 -0
  91. package/dist/models/sam2Decoder.js +108 -0
  92. package/dist/models/sam2Memories.d.ts +54 -0
  93. package/dist/models/sam2Memories.js +93 -0
  94. package/dist/models/sam2Memory.d.ts +30 -0
  95. package/dist/models/sam2Memory.js +136 -0
  96. package/dist/models/sam2Positions.d.ts +24 -0
  97. package/dist/models/sam2Positions.js +94 -0
  98. package/dist/models/sam2Tracker.d.ts +69 -0
  99. package/dist/models/sam2Tracker.js +172 -0
  100. package/dist/models/samDecoder.d.ts +45 -0
  101. package/dist/models/samDecoder.js +96 -0
  102. package/dist/models/samMiniature.d.ts +43 -0
  103. package/dist/models/samMiniature.js +161 -0
  104. package/dist/models/samPrompt.d.ts +57 -0
  105. package/dist/models/samPrompt.js +160 -0
  106. package/dist/models/seeded.d.ts +16 -0
  107. package/dist/models/seeded.js +48 -0
  108. package/dist/models/tinyVit.d.ts +38 -0
  109. package/dist/models/tinyVit.js +82 -0
  110. package/dist/models/twoWay.d.ts +46 -0
  111. package/dist/models/twoWay.js +67 -0
  112. package/dist/models/vit.d.ts +45 -0
  113. package/dist/models/vit.js +157 -0
  114. package/dist/models/windowAttention.d.ts +25 -0
  115. package/dist/models/windowAttention.js +104 -0
  116. package/dist/poses.d.ts +31 -0
  117. package/dist/poses.js +750 -0
  118. package/dist/prepare.d.ts +30 -0
  119. package/dist/prepare.js +238 -0
  120. package/dist/propose.d.ts +36 -0
  121. package/dist/propose.js +63 -0
  122. package/dist/resample.d.ts +39 -0
  123. package/dist/resample.js +165 -0
  124. package/dist/run.d.ts +3 -0
  125. package/dist/run.js +1 -0
  126. package/dist/scene.d.ts +104 -0
  127. package/dist/scene.js +109 -0
  128. package/dist/segment.d.ts +40 -0
  129. package/dist/segment.js +184 -0
  130. package/dist/testScene.d.ts +55 -0
  131. package/dist/testScene.js +214 -0
  132. package/dist/twoView.d.ts +22 -0
  133. package/dist/twoView.js +435 -0
  134. package/package.json +63 -0
  135. package/src/browserFrames.ts +145 -0
  136. package/src/collide.ts +128 -0
  137. package/src/decimate.ts +510 -0
  138. package/src/delight.ts +427 -0
  139. package/src/delightScene.ts +249 -0
  140. package/src/depth.ts +100 -0
  141. package/src/features.ts +302 -0
  142. package/src/frames.ts +111 -0
  143. package/src/fusion.ts +229 -0
  144. package/src/gaussians/adam.ts +131 -0
  145. package/src/gaussians/depth.ts +87 -0
  146. package/src/gaussians/gradients.ts +285 -0
  147. package/src/gaussians/harmonics.ts +122 -0
  148. package/src/gaussians/optimise.ts +191 -0
  149. package/src/gaussians/parameters.ts +186 -0
  150. package/src/gaussians/project.ts +207 -0
  151. package/src/gaussians/projectGradient.ts +234 -0
  152. package/src/gaussians/rasterise.ts +81 -0
  153. package/src/gaussians/refine.ts +176 -0
  154. package/src/gaussians/similarity.ts +87 -0
  155. package/src/index.ts +225 -0
  156. package/src/marching.ts +358 -0
  157. package/src/masks.ts +201 -0
  158. package/src/math/dense.ts +317 -0
  159. package/src/math/schur.ts +167 -0
  160. package/src/models/batchNorm.ts +57 -0
  161. package/src/models/clipTokenizer.ts +129 -0
  162. package/src/models/clipTower.ts +87 -0
  163. package/src/models/depthAnything2.ts +276 -0
  164. package/src/models/depthAnything3.ts +229 -0
  165. package/src/models/dualDpt.ts +233 -0
  166. package/src/models/hiera.ts +231 -0
  167. package/src/models/hieraAttention.ts +121 -0
  168. package/src/models/miniature.ts +241 -0
  169. package/src/models/mobileSam.ts +135 -0
  170. package/src/models/owlv2.ts +190 -0
  171. package/src/models/owlv2Detect.ts +116 -0
  172. package/src/models/owlv2Miniature.ts +108 -0
  173. package/src/models/rope.ts +111 -0
  174. package/src/models/sam21.ts +182 -0
  175. package/src/models/sam21Miniature.ts +225 -0
  176. package/src/models/sam21Video.ts +154 -0
  177. package/src/models/sam2Choice.ts +62 -0
  178. package/src/models/sam2Decoder.ts +218 -0
  179. package/src/models/sam2Memories.ts +165 -0
  180. package/src/models/sam2Memory.ts +247 -0
  181. package/src/models/sam2Positions.ts +106 -0
  182. package/src/models/sam2Tracker.ts +300 -0
  183. package/src/models/samDecoder.ts +225 -0
  184. package/src/models/samMiniature.ts +190 -0
  185. package/src/models/samPrompt.ts +227 -0
  186. package/src/models/seeded.ts +50 -0
  187. package/src/models/tinyVit.ts +178 -0
  188. package/src/models/twoWay.ts +122 -0
  189. package/src/models/vit.ts +265 -0
  190. package/src/models/windowAttention.ts +155 -0
  191. package/src/poses.ts +894 -0
  192. package/src/prepare.ts +306 -0
  193. package/src/propose.ts +96 -0
  194. package/src/resample.ts +197 -0
  195. package/src/run.ts +7 -0
  196. package/src/scene.ts +141 -0
  197. package/src/segment.ts +220 -0
  198. package/src/testScene.ts +283 -0
  199. package/src/twoView.ts +536 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Depth Anything 3's head, the DPT that turns four taps of tokens into depth and its confidence.
3
+ *
4
+ * **Each tap is normed, laid back out as an image, projected, and resized to its level of a
5
+ * pyramid** — four times the patch grid, twice, once, and half — then four fusion blocks walk the
6
+ * pyramid from coarse to fine, each adding the level below through a residual unit, and the result
7
+ * is brought up to the image by an aligned bilinear resize and read out by two convolutions: a
8
+ * depth logit and a confidence logit per pixel. Their activations, `exp` and `exp + 1`, are the
9
+ * caller's, applied to the two channels as they are read back, since the runtime has no `exp` and
10
+ * one pass over a map on the host costs less than a kernel for it.
11
+ *
12
+ * **A sinusoidal position is added three times**, after each projection and before the readout:
13
+ * a grid spanning the image's aspect normalised by its diagonal, as the upstream's `create_uv_grid`
14
+ * makes it, embedded at base 100 and scaled by a tenth. It depends only on shapes, so each is a
15
+ * constant of the graph.
16
+ *
17
+ * **The upstream's second head is not built.** It predicts rays for a pose estimate the camera
18
+ * decoder already makes, and the upstream deletes its output when that decoder is present, as it is
19
+ * in every accepted checkpoint; its weights are set aside by name.
20
+ */
21
+ import { exactCos, exactExp, exactSin } from '@driftengine/core';
22
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
23
+
24
+ import { LOG_BASE } from './rope.ts';
25
+
26
+ export interface HeadConfig {
27
+ readonly dimIn: number;
28
+ readonly features: number;
29
+ readonly outChannels: readonly [number, number, number, number];
30
+ readonly outputDim: number;
31
+ readonly patch: number;
32
+ }
33
+
34
+ /** The head's own norms keep PyTorch's default ε. */
35
+ const EPSILON = 1e-5;
36
+
37
+ const f = Math.fround;
38
+
39
+ /**
40
+ * `[channels, height, width]`: the upstream's `_add_pos_embed` for a map of this size in an image of
41
+ * `aspect` width over height — sine and cosine of the horizontal coordinate, then of the vertical,
42
+ * over `channels / 4` frequencies each, times a tenth.
43
+ */
44
+ export function uvEmbedding(
45
+ channels: number,
46
+ height: number,
47
+ width: number,
48
+ aspect: number,
49
+ ): Float32Array {
50
+ const diagonal = Math.sqrt(aspect * aspect + 1);
51
+ const spanX = aspect / diagonal;
52
+ const spanY = 1 / diagonal;
53
+ const coordinate = (span: number, count: number, i: number): number => {
54
+ const edge = (span * (count - 1)) / count;
55
+ return count === 1 ? -edge : f(-edge + ((2 * edge) / (count - 1)) * i);
56
+ };
57
+ const quarter = channels / 4;
58
+ const omega = Float32Array.from({ length: quarter }, (_, k) =>
59
+ f(1 / f(exactExp(f(k / quarter) * LOG_BASE))),
60
+ );
61
+ const out = new Float32Array(channels * height * width);
62
+ for (let y = 0; y < height; y += 1) {
63
+ const v = coordinate(spanY, height, y);
64
+ for (let x = 0; x < width; x += 1) {
65
+ const u = coordinate(spanX, width, x);
66
+ for (let k = 0; k < quarter; k += 1) {
67
+ const au = f(u * (omega[k] as number));
68
+ const av = f(v * (omega[k] as number));
69
+ const at = y * width + x;
70
+ const plane = height * width;
71
+ out[k * plane + at] = f(f(exactSin(au)) * 0.1);
72
+ out[(quarter + k) * plane + at] = f(f(exactCos(au)) * 0.1);
73
+ out[(2 * quarter + k) * plane + at] = f(f(exactSin(av)) * 0.1);
74
+ out[(3 * quarter + k) * plane + at] = f(f(exactCos(av)) * 0.1);
75
+ }
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+
81
+ /** The size a stride-2 3×3 convolution with padding 1 leaves. */
82
+ const halved = (size: number): number => Math.floor((size - 1) / 2) + 1;
83
+
84
+ /**
85
+ * The head over one view's four taps, each `[rows · cols, dimIn]`; the result is the logits,
86
+ * `[outputDim, rows · patch, cols · patch]`, named `output`. Several views share its constants.
87
+ */
88
+ export function dualDptHead(
89
+ weights: Weights,
90
+ graph: GraphBuilder,
91
+ config: HeadConfig,
92
+ prefix: string,
93
+ taps: readonly string[],
94
+ rows: number,
95
+ cols: number,
96
+ output: string,
97
+ ): string {
98
+ const node = graph.node.bind(graph);
99
+ const w = (name: string, shape: readonly number[]): string =>
100
+ weights.read(`${prefix}${name}`, shape);
101
+ const height = rows * config.patch;
102
+ const width = cols * config.patch;
103
+ const aspect = width / height;
104
+ const features = config.features;
105
+ const embed = (x: string, channels: number, h: number, wide: number): string =>
106
+ node('add', [
107
+ x,
108
+ weights.constant(
109
+ `uv.${channels}.${h}.${wide}`,
110
+ [channels, h, wide],
111
+ uvEmbedding(channels, h, wide, aspect),
112
+ ),
113
+ ]);
114
+ const conv = (
115
+ x: string,
116
+ name: string,
117
+ from: number,
118
+ to: number,
119
+ kernel: number,
120
+ attributes = {},
121
+ bias = true,
122
+ named?: string,
123
+ ): string =>
124
+ node(
125
+ 'conv2d',
126
+ bias
127
+ ? [x, w(`${name}.weight`, [to, from, kernel, kernel]), w(`${name}.bias`, [to])]
128
+ : [x, w(`${name}.weight`, [to, from, kernel, kernel])],
129
+ attributes,
130
+ named,
131
+ );
132
+
133
+ const sizes: [number, number][] = [
134
+ [4 * rows, 4 * cols],
135
+ [2 * rows, 2 * cols],
136
+ [rows, cols],
137
+ [halved(rows), halved(cols)],
138
+ ];
139
+ const levels = taps.map((tap, k) => {
140
+ const channels = config.outChannels[k] as number;
141
+ const normed = node(
142
+ 'layerNorm',
143
+ [tap, w('norm.weight', [config.dimIn]), w('norm.bias', [config.dimIn])],
144
+ { epsilon: EPSILON },
145
+ );
146
+ const image = node('reshape', [node('permute', [normed], { order: [1, 0] })], {
147
+ shape: [config.dimIn, rows, cols],
148
+ });
149
+ const projected = embed(
150
+ conv(image, `projects.${k}`, config.dimIn, channels, 1),
151
+ channels,
152
+ rows,
153
+ cols,
154
+ );
155
+ const resized =
156
+ k === 0
157
+ ? node(
158
+ 'convTranspose2d',
159
+ [
160
+ projected,
161
+ w('resize_layers.0.weight', [channels, channels, 4, 4]),
162
+ w('resize_layers.0.bias', [channels]),
163
+ ],
164
+ { stride: 4 },
165
+ )
166
+ : k === 1
167
+ ? node(
168
+ 'convTranspose2d',
169
+ [
170
+ projected,
171
+ w('resize_layers.1.weight', [channels, channels, 2, 2]),
172
+ w('resize_layers.1.bias', [channels]),
173
+ ],
174
+ { stride: 2 },
175
+ )
176
+ : k === 2
177
+ ? projected
178
+ : conv(projected, 'resize_layers.3', channels, channels, 3, { stride: 2, padding: 1 });
179
+ return conv(resized, `scratch.layer${k + 1}_rn`, channels, features, 3, { padding: 1 }, false);
180
+ });
181
+
182
+ const residual = (x: string, name: string): string => {
183
+ const first = conv(node('relu', [x]), `${name}.conv1`, features, features, 3, { padding: 1 });
184
+ return node('add', [
185
+ conv(node('relu', [first]), `${name}.conv2`, features, features, 3, { padding: 1 }),
186
+ x,
187
+ ]);
188
+ };
189
+ const fuse = (
190
+ level: number,
191
+ top: string,
192
+ lateral: string | null,
193
+ size: [number, number],
194
+ ): string => {
195
+ const name = `scratch.refinenet${level}`;
196
+ const merged =
197
+ lateral === null ? top : node('add', [top, residual(lateral, `${name}.resConfUnit1`)]);
198
+ const refined = residual(merged, `${name}.resConfUnit2`);
199
+ const up = node('resize', [refined], {
200
+ height: size[0],
201
+ width: size[1],
202
+ mode: 'bilinear',
203
+ alignCorners: true,
204
+ });
205
+ return conv(up, `${name}.out_conv`, features, features, 1);
206
+ };
207
+ const [l1, l2, l3, l4] = levels as [string, string, string, string];
208
+ let out = fuse(4, l4, null, sizes[2]);
209
+ out = fuse(3, out, l3, sizes[1]);
210
+ out = fuse(2, out, l2, sizes[0]);
211
+ out = fuse(1, out, l1, [2 * sizes[0][0], 2 * sizes[0][1]]);
212
+ out = conv(out, 'scratch.output_conv1', features, features / 2, 3, { padding: 1 });
213
+
214
+ const up = node('resize', [out], { height, width, mode: 'bilinear', alignCorners: true });
215
+ const read = conv(
216
+ embed(up, features / 2, height, width),
217
+ 'scratch.output_conv2.0',
218
+ features / 2,
219
+ 32,
220
+ 3,
221
+ { padding: 1 },
222
+ );
223
+ return conv(
224
+ node('relu', [read]),
225
+ 'scratch.output_conv2.2',
226
+ 32,
227
+ config.outputDim,
228
+ 1,
229
+ {},
230
+ true,
231
+ output,
232
+ );
233
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Hiera and its feature pyramid, as SAM 2 runs them: an image in, three levels of features out.
3
+ *
4
+ * **Four stages of transformer blocks over a shrinking grid.** A 7×7 convolution at stride 4 takes
5
+ * the image to a quarter; each later stage's first block halves the grid by pooling its queries 2×2
6
+ * — within each window for attention, and over the whole grid for the residual, which a projection
7
+ * widens — so attention reads every key and writes a quarter as many queries. Blocks attend within
8
+ * windows, padded with zeros after the block's norm where the window does not divide the grid, and
9
+ * the blocks the configuration names attend over the whole grid instead. A first block partitions by
10
+ * the previous stage's window and unpartitions by half of it.
11
+ *
12
+ * **The positional embedding is computed in the graph, from its two weights**: a table resized
13
+ * bicubically to the grid, plus a window's table tiled across it. Laid out at conversion it would be
14
+ * 12.6 MB of the file at half precision; computed, it costs two permutes a run.
15
+ *
16
+ * **The pyramid runs top-down only into the levels the configuration names**: each stage's output is
17
+ * projected to the pyramid's width, and a named level adds the level above it doubled by nearest
18
+ * resizing. The two finest levels are then narrowed by the mask decoder's own projections, as the
19
+ * upstream's `get_image_features` narrows them once rather than on every prompt. Norms take ε 1e-6.
20
+ */
21
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
22
+
23
+ import { hieraAttention } from './hieraAttention.ts';
24
+
25
+ type Four = readonly [number, number, number, number];
26
+
27
+ export interface HieraConfig {
28
+ readonly dims: Four;
29
+ readonly blocks: Four;
30
+ readonly heads: Four;
31
+ readonly windows: Four;
32
+ /** Block indices, counted across stages, that attend over the whole grid. */
33
+ readonly global: readonly number[];
34
+ /** The positional table's trained size, square. */
35
+ readonly background: number;
36
+ readonly mlpRatio: number;
37
+ /** The pyramid's width, and the levels (by stage) that take the level above them. */
38
+ readonly fpn: number;
39
+ readonly topDown: readonly number[];
40
+ }
41
+
42
+ export interface HieraOutput {
43
+ /** `[fpn, grid, grid]` at a sixteenth of the image: the features the decoder and memory read. */
44
+ readonly features: string;
45
+ /** `[fpn / 4, 2·grid, 2·grid]` and `[fpn / 8, 4·grid, 4·grid]`: the decoder's skips, narrowed. */
46
+ readonly high1: string;
47
+ readonly high0: string;
48
+ }
49
+
50
+ const EPSILON = 1e-6;
51
+
52
+ /**
53
+ * The encoder over `image`, `[3, size, size]` normalised as the upstream prepares it, writing its
54
+ * three outputs under the names given.
55
+ */
56
+ export function hiera(
57
+ weights: Weights,
58
+ graph: GraphBuilder,
59
+ config: HieraConfig,
60
+ image: string,
61
+ size: number,
62
+ names: HieraOutput,
63
+ ): void {
64
+ const node = graph.node.bind(graph);
65
+ const b = 'vision_encoder.backbone.';
66
+ const read = weights.read.bind(weights);
67
+ const linear = (x: string, p: string, out: number, inputs: number): string =>
68
+ node('linear', [x, read(`${p}weight`, [out, inputs]), read(`${p}bias`, [out])]);
69
+ const norm = (x: string, p: string, dim: number): string =>
70
+ node('layerNorm', [x, read(`${p}weight`, [dim]), read(`${p}bias`, [dim])], {
71
+ epsilon: EPSILON,
72
+ });
73
+
74
+ /* The patch embedding, then the positions, both channel-major. */
75
+ const [d0] = config.dims;
76
+ let grid = size / 4;
77
+ const patches = node(
78
+ 'conv2d',
79
+ [
80
+ image,
81
+ read(`${b}patch_embed.projection.weight`, [d0, 3, 7, 7]),
82
+ read(`${b}patch_embed.projection.bias`, [d0]),
83
+ ],
84
+ { stride: 4, padding: 3 },
85
+ );
86
+ const window0 = config.windows[0];
87
+ const tiles = grid / window0;
88
+ const background = node(
89
+ 'resize',
90
+ [
91
+ node('reshape', [read(`${b}pos_embed`, [1, d0, config.background, config.background])], {
92
+ shape: [d0, config.background, config.background],
93
+ }),
94
+ ],
95
+ { height: grid, width: grid, mode: 'bicubic', alignCorners: false },
96
+ );
97
+ /* The window's table added to every tile: tiles to the leading axes, the window to the last. */
98
+ const byTile = node(
99
+ 'reshape',
100
+ [
101
+ node(
102
+ 'permute',
103
+ [node('reshape', [background], { shape: [d0, tiles, window0, tiles, window0] })],
104
+ { order: [1, 3, 0, 2, 4] },
105
+ ),
106
+ ],
107
+ { shape: [tiles * tiles, d0 * window0 * window0] },
108
+ );
109
+ const window = node('reshape', [read(`${b}pos_embed_window`, [1, d0, window0, window0])], {
110
+ shape: [d0 * window0 * window0],
111
+ });
112
+ const positions = node(
113
+ 'reshape',
114
+ [
115
+ node(
116
+ 'permute',
117
+ [
118
+ node('reshape', [node('add', [byTile, window])], {
119
+ shape: [tiles, tiles, d0, window0, window0],
120
+ }),
121
+ ],
122
+ { order: [2, 0, 3, 1, 4] },
123
+ ),
124
+ ],
125
+ { shape: [d0, grid, grid] },
126
+ );
127
+ const embedded = node('add', [patches, positions]);
128
+ /* Tokens, row by row, `[grid · grid, dim]`, from here to the pyramid. */
129
+ let x = node('permute', [node('reshape', [embedded], { shape: [d0, grid * grid] })], {
130
+ order: [1, 0],
131
+ });
132
+
133
+ const pooled = (tokens: string, rows: number, dim: number): string => {
134
+ const map = node('reshape', [node('permute', [tokens], { order: [1, 0] })], {
135
+ shape: [dim, rows, rows],
136
+ });
137
+ const pool = node('maxPool2d', [map], { kernel: 2 });
138
+ return node('permute', [node('reshape', [pool], { shape: [dim, (rows / 2) * (rows / 2)] })], {
139
+ order: [1, 0],
140
+ });
141
+ };
142
+
143
+ const outputs: string[] = [];
144
+ let index = 0;
145
+ for (let stage = 0; stage < 4; stage += 1) {
146
+ for (let block = 0; block < (config.blocks[stage] as number); block += 1) {
147
+ const p = `${b}blocks.${index}.`;
148
+ const first = stage > 0 && block === 0;
149
+ const dim = first ? (config.dims[stage - 1] as number) : (config.dims[stage] as number);
150
+ const out = config.dims[stage] as number;
151
+ const heads = config.heads[stage] as number;
152
+ const side = config.global.includes(index)
153
+ ? 0
154
+ : first
155
+ ? (config.windows[stage - 1] as number)
156
+ : (config.windows[stage] as number);
157
+ const normed = norm(x, `${p}layer_norm1.`, dim);
158
+ const residual = first ? pooled(linear(normed, `${p}proj.`, out, dim), grid, out) : x;
159
+ const attended = hieraAttention(
160
+ weights,
161
+ graph,
162
+ p,
163
+ normed,
164
+ grid,
165
+ dim,
166
+ out,
167
+ heads,
168
+ side,
169
+ first,
170
+ );
171
+ if (first) grid /= 2;
172
+ x = node('add', [residual, attended]);
173
+ const hidden = node('gelu', [
174
+ linear(norm(x, `${p}layer_norm2.`, out), `${p}mlp.proj_in.`, out * config.mlpRatio, out),
175
+ ]);
176
+ x = node('add', [x, linear(hidden, `${p}mlp.proj_out.`, out, out * config.mlpRatio)]);
177
+ index += 1;
178
+ }
179
+ outputs.push(x);
180
+ }
181
+
182
+ /* The pyramid, coarsest first: each stage projected, and a named level adding the one above. */
183
+ const n = 3;
184
+ const sides = [size / 4, size / 8, size / 16, size / 32];
185
+ const levels: string[] = [];
186
+ let previous = '';
187
+ for (let i = n; i >= 0; i -= 1) {
188
+ const p = `vision_encoder.neck.convs.${n - i}.`;
189
+ const channels = config.dims[i] as number;
190
+ const side = sides[i] as number;
191
+ const map = node('reshape', [node('permute', [outputs[i] as string], { order: [1, 0] })], {
192
+ shape: [channels, side, side],
193
+ });
194
+ const down = config.topDown.includes(i) && i !== n;
195
+ const name = i === 2 ? names.features : undefined;
196
+ let lateral = node(
197
+ 'conv2d',
198
+ [map, read(`${p}weight`, [config.fpn, channels, 1, 1]), read(`${p}bias`, [config.fpn])],
199
+ {},
200
+ down ? undefined : name,
201
+ );
202
+ if (down) {
203
+ const above = node('resize', [previous], { height: side, width: side, mode: 'nearest' });
204
+ lateral = node('add', [lateral, above], {}, name);
205
+ }
206
+ previous = lateral;
207
+ levels[i] = lateral;
208
+ }
209
+ const d = 'mask_decoder.';
210
+ node(
211
+ 'conv2d',
212
+ [
213
+ levels[0] as string,
214
+ read(`${d}conv_s0.weight`, [config.fpn / 8, config.fpn, 1, 1]),
215
+ read(`${d}conv_s0.bias`, [config.fpn / 8]),
216
+ ],
217
+ {},
218
+ names.high0,
219
+ );
220
+ node(
221
+ 'conv2d',
222
+ [
223
+ levels[1] as string,
224
+ read(`${d}conv_s1.weight`, [config.fpn / 4, config.fpn, 1, 1]),
225
+ read(`${d}conv_s1.bias`, [config.fpn / 4]),
226
+ ],
227
+ {},
228
+ names.high1,
229
+ );
230
+ /* The coarsest level feeds the one below and is not itself an output, as the upstream keeps three. */
231
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * One Hiera block's attention: within windows of its grid, or over the whole grid, its queries
3
+ * pooled 2×2 within each window where the block changes stage.
4
+ *
5
+ * **Windows are partitioned after the block's norm**, padded with zeros where the window does not
6
+ * divide the grid — so a padded token is zero, where TinyViT's is its norm's β — and unpartitioned
7
+ * by the queries' window, the padding recomputed for the pooled grid, as the upstream's
8
+ * `window_unpartition` is given it. The projection lays each head's queries, keys and values out
9
+ * contiguously, as the attention operator takes them.
10
+ */
11
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
12
+
13
+ /**
14
+ * `[rows² or (rows/2)², out]` from `normed`, `[rows², dim]`: the attention under `p` within windows
15
+ * of `side` — the whole grid for 0 — pooling its queries when `pool`.
16
+ */
17
+ export function hieraAttention(
18
+ weights: Weights,
19
+ graph: GraphBuilder,
20
+ p: string,
21
+ normed: string,
22
+ rows: number,
23
+ dim: number,
24
+ out: number,
25
+ heads: number,
26
+ side: number,
27
+ pool: boolean,
28
+ ): string {
29
+ const node = graph.node.bind(graph);
30
+ const linear = (x: string, q: string, outputs: number, inputs: number): string =>
31
+ node('linear', [
32
+ x,
33
+ weights.read(`${q}weight`, [outputs, inputs]),
34
+ weights.read(`${q}bias`, [outputs]),
35
+ ]);
36
+
37
+ const span = side === 0 ? rows : side;
38
+ const pad = (span - (rows % span)) % span;
39
+ const across = (rows + pad) / span;
40
+ const count = across * across;
41
+ let tokens = normed;
42
+ if (side > 0) {
43
+ let map = node('reshape', [normed], { shape: [rows, rows, dim] });
44
+ if (pad > 0) map = node('pad', [map], { after: [pad, pad, 0] });
45
+ tokens = node(
46
+ 'reshape',
47
+ [
48
+ node('permute', [node('reshape', [map], { shape: [across, span, across, span, dim] })], {
49
+ order: [0, 2, 1, 3, 4],
50
+ }),
51
+ ],
52
+ { shape: [count * span * span, dim] },
53
+ );
54
+ }
55
+ const qkv = linear(tokens, `${p}attn.qkv.`, 3 * out, dim);
56
+ const part = (i: number): string =>
57
+ node('slice', [qkv], { axis: 1, start: i * out, end: (i + 1) * out });
58
+ let queries = node('reshape', [part(0)], { shape: [count, span * span, out] });
59
+ let qSpan = span;
60
+ if (pool) {
61
+ /* Each window's queries as a map, pooled 2×2: windows and channels together as the pool's planes. */
62
+ const maps = node(
63
+ 'reshape',
64
+ [
65
+ node('permute', [node('reshape', [part(0)], { shape: [count, span, span, out] })], {
66
+ order: [0, 3, 1, 2],
67
+ }),
68
+ ],
69
+ { shape: [count * out, span, span] },
70
+ );
71
+ qSpan = span / 2;
72
+ queries = node(
73
+ 'reshape',
74
+ [
75
+ node(
76
+ 'permute',
77
+ [
78
+ node('reshape', [node('maxPool2d', [maps], { kernel: 2 })], {
79
+ shape: [count, out, qSpan, qSpan],
80
+ }),
81
+ ],
82
+ { order: [0, 2, 3, 1] },
83
+ ),
84
+ ],
85
+ { shape: [count, qSpan * qSpan, out] },
86
+ );
87
+ }
88
+ const keys = node('reshape', [part(1)], { shape: [count, span * span, out] });
89
+ const values = node('reshape', [part(2)], { shape: [count, span * span, out] });
90
+ const attended = node('attention', [queries, keys, values], { heads });
91
+ const projected = linear(
92
+ node('reshape', [attended], { shape: [count * qSpan * qSpan, out] }),
93
+ `${p}attn.proj.`,
94
+ out,
95
+ out,
96
+ );
97
+ if (side === 0) return projected;
98
+ /* Unpartitioned by the queries' window, the padding recomputed for the pooled grid. */
99
+ const pooledRows = pool ? rows / 2 : rows;
100
+ const qPad = (qSpan - (pooledRows % qSpan)) % qSpan;
101
+ const qAcross = (pooledRows + qPad) / qSpan;
102
+ let map = node(
103
+ 'reshape',
104
+ [
105
+ node(
106
+ 'permute',
107
+ [node('reshape', [projected], { shape: [qAcross, qAcross, qSpan, qSpan, out] })],
108
+ { order: [0, 2, 1, 3, 4] },
109
+ ),
110
+ ],
111
+ { shape: [pooledRows + qPad, pooledRows + qPad, out] },
112
+ );
113
+ if (qPad > 0) {
114
+ map = node('slice', [node('slice', [map], { axis: 0, start: 0, end: pooledRows })], {
115
+ axis: 1,
116
+ start: 0,
117
+ end: pooledRows,
118
+ });
119
+ }
120
+ return node('reshape', [map], { shape: [pooledRows * pooledRows, out] });
121
+ }