@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,178 @@
1
+ /**
2
+ * TinyViT as MobileSAM runs it: an image in, a 256-channel embedding at a sixteenth of its size out.
3
+ *
4
+ * **Four stages, the first convolutional.** A patch embedding of two strided convolutions takes the
5
+ * image to a quarter; the first stage is inverted residual blocks, each a pointwise expansion, a
6
+ * depthwise convolution and a pointwise projection; the other three are transformer blocks that
7
+ * attend within windows, each followed by a depthwise convolution over the grid and an MLP. A merge
8
+ * between stages widens the channels and halves the grid — except where the upstream keeps its
9
+ * stride at one, which it decides by the output width and this definition takes as `mergeStrides`.
10
+ *
11
+ * **Every convolution carries a batch norm**, folded in at conversion by `convBatchNorm`, and the
12
+ * transformer blocks attend within windows through `windowAttention`, which pads, partitions and
13
+ * biases them as the upstream does. The blocks' MLP norms take PyTorch's default ε, 1e-5, and the
14
+ * neck's channel norms 1e-6.
15
+ */
16
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
17
+
18
+ import { convBatchNorm } from './batchNorm.ts';
19
+ import { windowAttention } from './windowAttention.ts';
20
+
21
+ type Four = readonly [number, number, number, number];
22
+
23
+ export interface TinyVitConfig {
24
+ readonly embedDims: Four;
25
+ readonly depths: Four;
26
+ /** Heads per stage; the first stage is convolutional and has none. */
27
+ readonly heads: Four;
28
+ readonly windows: Four;
29
+ /** The stride of each merge between stages. */
30
+ readonly mergeStrides: readonly [number, number, number];
31
+ readonly mlpRatio: number;
32
+ readonly expandRatio: number;
33
+ /** The embedding's channels. */
34
+ readonly neck: number;
35
+ }
36
+
37
+ const EPSILON = 1e-5;
38
+ const CHANNEL_EPSILON = 1e-6;
39
+
40
+ /** The grid the encoder's embedding has for an image `size` across. */
41
+ export function tinyVitGrid(config: TinyVitConfig, size: number): number {
42
+ return config.mergeStrides.reduce((grid, stride) => grid / stride, size / 4);
43
+ }
44
+
45
+ /**
46
+ * `[neck, grid, grid]` from `image`, `[3, size, size]`, normalised and padded as the upstream
47
+ * prepares it; the embedding is written as `output`.
48
+ */
49
+ export function tinyVit(
50
+ weights: Weights,
51
+ graph: GraphBuilder,
52
+ config: TinyVitConfig,
53
+ prefix: string,
54
+ image: string,
55
+ size: number,
56
+ output: string,
57
+ ): void {
58
+ const node = graph.node.bind(graph);
59
+ const dims = config.embedDims;
60
+ weights.ignore(`${prefix}norm_head.`);
61
+ weights.ignore(`${prefix}head.`);
62
+
63
+ const convBn = (
64
+ p: string,
65
+ x: string,
66
+ cin: number,
67
+ cout: number,
68
+ kernel: number,
69
+ stride: number,
70
+ groups: number,
71
+ ): string => convBatchNorm(weights, graph, p, x, cin, cout, kernel, stride, groups);
72
+ const gelu = (x: string): string => node('gelu', [x]);
73
+ const tokensOf = (map: string, channels: number, cells: number): string =>
74
+ node('permute', [node('reshape', [map], { shape: [channels, cells] })], { order: [1, 0] });
75
+ const mapOf = (tokens: string, channels: number, rows: number, cols: number): string =>
76
+ node('reshape', [node('permute', [tokens], { order: [1, 0] })], {
77
+ shape: [channels, rows, cols],
78
+ });
79
+ const norm = (x: string, p: string, dim: number, epsilon: number): string =>
80
+ node('layerNorm', [x, weights.read(`${p}weight`, [dim]), weights.read(`${p}bias`, [dim])], {
81
+ epsilon,
82
+ });
83
+ const linear = (x: string, p: string, out: number, inputs: number): string =>
84
+ node('linear', [x, weights.read(`${p}weight`, [out, inputs]), weights.read(`${p}bias`, [out])]);
85
+
86
+ const merge = (p: string, map: string, dim: number, out: number, stride: number): string => {
87
+ const expanded = gelu(convBn(`${p}conv1.`, map, dim, out, 1, 1, 1));
88
+ const strided = gelu(convBn(`${p}conv2.`, expanded, out, out, 3, stride, out));
89
+ return convBn(`${p}conv3.`, strided, out, out, 1, 1, 1);
90
+ };
91
+
92
+ let map = gelu(convBn(`${prefix}patch_embed.seq.0.`, image, 3, dims[0] / 2, 3, 2, 1));
93
+ map = convBn(`${prefix}patch_embed.seq.2.`, map, dims[0] / 2, dims[0], 3, 2, 1);
94
+ let grid = size / 4;
95
+ const hidden = dims[0] * config.expandRatio;
96
+ for (let i = 0; i < config.depths[0]; i += 1) {
97
+ const p = `${prefix}layers.0.blocks.${i}.`;
98
+ const inner = gelu(convBn(`${p}conv1.`, map, dims[0], hidden, 1, 1, 1));
99
+ const spread = gelu(convBn(`${p}conv2.`, inner, hidden, hidden, 3, 1, hidden));
100
+ map = gelu(node('add', [convBn(`${p}conv3.`, spread, hidden, dims[0], 1, 1, 1), map]));
101
+ }
102
+ map = merge(`${prefix}layers.0.downsample.`, map, dims[0], dims[1], config.mergeStrides[0]);
103
+ grid /= config.mergeStrides[0];
104
+
105
+ let tokens = tokensOf(map, dims[1], grid * grid);
106
+ for (let stage = 1; stage < 4; stage += 1) {
107
+ const dim = dims[stage] as number;
108
+ const heads = config.heads[stage] as number;
109
+ const window = config.windows[stage] as number;
110
+ for (let i = 0; i < (config.depths[stage] as number); i += 1) {
111
+ const p = `${prefix}layers.${stage}.blocks.${i}.`;
112
+ const attended = node('add', [
113
+ tokens,
114
+ windowAttention(weights, graph, `${p}attn.`, tokens, dim, heads, window, grid, grid),
115
+ ]);
116
+ const local = convBn(
117
+ `${p}local_conv.`,
118
+ mapOf(attended, dim, grid, grid),
119
+ dim,
120
+ dim,
121
+ 3,
122
+ 1,
123
+ dim,
124
+ );
125
+ const mixed = tokensOf(local, dim, grid * grid);
126
+ const inner = gelu(
127
+ linear(
128
+ norm(mixed, `${p}mlp.norm.`, dim, EPSILON),
129
+ `${p}mlp.fc1.`,
130
+ dim * config.mlpRatio,
131
+ dim,
132
+ ),
133
+ );
134
+ tokens = node('add', [mixed, linear(inner, `${p}mlp.fc2.`, dim, dim * config.mlpRatio)]);
135
+ }
136
+ if (stage < 3) {
137
+ const stride = config.mergeStrides[stage] as number;
138
+ const next = dims[stage + 1] as number;
139
+ const merged = merge(
140
+ `${prefix}layers.${stage}.downsample.`,
141
+ mapOf(tokens, dim, grid, grid),
142
+ dim,
143
+ next,
144
+ stride,
145
+ );
146
+ grid /= stride;
147
+ tokens = tokensOf(merged, next, grid * grid);
148
+ }
149
+ }
150
+
151
+ /* The neck: a pointwise and a 3×3 convolution, each followed by a norm over the channels. */
152
+ const channelNorm = (x: string, p: string, name?: string): string =>
153
+ node(
154
+ 'reshape',
155
+ [
156
+ node(
157
+ 'permute',
158
+ [norm(tokensOf(x, config.neck, grid * grid), p, config.neck, CHANNEL_EPSILON)],
159
+ { order: [1, 0] },
160
+ ),
161
+ ],
162
+ { shape: [config.neck, grid, grid] },
163
+ name,
164
+ );
165
+ const pointwise = node('conv2d', [
166
+ mapOf(tokens, dims[3], grid, grid),
167
+ weights.read(`${prefix}neck.0.weight`, [config.neck, dims[3], 1, 1]),
168
+ ]);
169
+ const spatial = node(
170
+ 'conv2d',
171
+ [
172
+ channelNorm(pointwise, `${prefix}neck.1.`),
173
+ weights.read(`${prefix}neck.2.weight`, [config.neck, config.neck, 3, 3]),
174
+ ],
175
+ { padding: 1 },
176
+ );
177
+ channelNorm(spatial, `${prefix}neck.3.`, output);
178
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * SAM's two-way transformer: a prompt's tokens and an image's, each attending to the other — the
3
+ * part SAM, MobileSAM and SAM 2 share, under whichever names their checkpoints give it.
4
+ *
5
+ * **The tokens attend to themselves, then to the image, then pass an MLP; then the image attends back
6
+ * to them.** Queries and keys are given their positions afresh at every attention — the tokens as
7
+ * they were first given, the image by its grid's positions — while values are not. **The first
8
+ * layer's self-attention replaces the tokens rather than adding to them**, and gives them no
9
+ * positions, as the upstream's `skip_first_layer_pe` does; every later one is a residual. Attention
10
+ * between tokens and image narrows to `dim / downsample`. Norms take PyTorch's default ε, 1e-5, and
11
+ * the MLP's activation is ReLU in every checkpoint here.
12
+ *
13
+ * **One definition under two sets of names**, not two definitions: the original's checkpoints say
14
+ * `out_proj`, `norm1` and `lin1` where Transformers' SAM 2 says `o_proj`, `layer_norm1` and
15
+ * `proj_in`, and a copy for each would be two implementations of one network, free to drift.
16
+ */
17
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
18
+
19
+ export interface TwoWayConfig {
20
+ readonly dim: number;
21
+ readonly heads: number;
22
+ readonly mlp: number;
23
+ readonly depth: number;
24
+ readonly downsample: number;
25
+ }
26
+
27
+ /** What the two checkpoint layouts call the transformer's parts. */
28
+ export interface TwoWayNames {
29
+ /** An attention's output projection. */
30
+ readonly out: string;
31
+ /** A layer's `i`-th norm, 1 to 4. */
32
+ readonly norm: (i: number) => string;
33
+ readonly finalNorm: string;
34
+ /** The MLP's two projections. */
35
+ readonly mlp: readonly [string, string];
36
+ }
37
+
38
+ /** The original SAM's names, which MobileSAM keeps. */
39
+ export const SAM_NAMES: TwoWayNames = {
40
+ out: 'out_proj',
41
+ norm: (i) => `norm${i}`,
42
+ finalNorm: 'norm_final_attn',
43
+ mlp: ['lin1', 'lin2'],
44
+ };
45
+
46
+ /** Transformers' names for SAM 2. */
47
+ export const SAM2_NAMES: TwoWayNames = {
48
+ out: 'o_proj',
49
+ norm: (i) => `layer_norm${i}`,
50
+ finalNorm: 'layer_norm_final_attn',
51
+ mlp: ['proj_in', 'proj_out'],
52
+ };
53
+
54
+ const EPSILON = 1e-5;
55
+
56
+ /**
57
+ * The transformer under `prefix` over `tokens`, `[count, dim]`, and `image`, `[cells, dim]` in token
58
+ * layout with `positions` of the same shape; answers both, attended.
59
+ */
60
+ export function twoWay(
61
+ weights: Weights,
62
+ graph: GraphBuilder,
63
+ prefix: string,
64
+ config: TwoWayConfig,
65
+ names: TwoWayNames,
66
+ tokens: string,
67
+ image: string,
68
+ positions: string,
69
+ ): { readonly queries: string; readonly keys: string } {
70
+ const node = graph.node.bind(graph);
71
+ const { dim, heads } = config;
72
+ const read = weights.read.bind(weights);
73
+ const linear = (x: string, p: string, out: number, inputs: number): string =>
74
+ node('linear', [x, read(`${p}weight`, [out, inputs]), read(`${p}bias`, [out])]);
75
+ const norm = (x: string, p: string): string =>
76
+ node('layerNorm', [x, read(`${p}weight`, [dim]), read(`${p}bias`, [dim])], {
77
+ epsilon: EPSILON,
78
+ });
79
+ const attend = (p: string, q: string, k: string, v: string, narrow: number): string => {
80
+ const inner = dim / narrow;
81
+ const attended = node(
82
+ 'attention',
83
+ [
84
+ linear(q, `${p}q_proj.`, inner, dim),
85
+ linear(k, `${p}k_proj.`, inner, dim),
86
+ linear(v, `${p}v_proj.`, inner, dim),
87
+ ],
88
+ { heads },
89
+ );
90
+ return linear(attended, `${p}${names.out}.`, dim, inner);
91
+ };
92
+ const [into, outOf] = names.mlp;
93
+
94
+ let queries = tokens;
95
+ let keys = image;
96
+ for (let i = 0; i < config.depth; i += 1) {
97
+ const p = `${prefix}layers.${i}.`;
98
+ if (i === 0) {
99
+ queries = attend(`${p}self_attn.`, queries, queries, queries, 1);
100
+ } else {
101
+ const q = node('add', [queries, tokens]);
102
+ queries = node('add', [queries, attend(`${p}self_attn.`, q, q, queries, 1)]);
103
+ }
104
+ queries = norm(queries, `${p}${names.norm(1)}.`);
105
+ let q = node('add', [queries, tokens]);
106
+ let k = node('add', [keys, positions]);
107
+ const toImage = attend(`${p}cross_attn_token_to_image.`, q, k, keys, config.downsample);
108
+ queries = norm(node('add', [queries, toImage]), `${p}${names.norm(2)}.`);
109
+ const hidden = node('relu', [linear(queries, `${p}mlp.${into}.`, config.mlp, dim)]);
110
+ const mixed = linear(hidden, `${p}mlp.${outOf}.`, dim, config.mlp);
111
+ queries = norm(node('add', [queries, mixed]), `${p}${names.norm(3)}.`);
112
+ q = node('add', [queries, tokens]);
113
+ k = node('add', [keys, positions]);
114
+ const toTokens = attend(`${p}cross_attn_image_to_token.`, k, q, queries, config.downsample);
115
+ keys = norm(node('add', [keys, toTokens]), `${p}${names.norm(4)}.`);
116
+ }
117
+ const q = node('add', [queries, tokens]);
118
+ const k = node('add', [keys, positions]);
119
+ const final = attend(`${prefix}final_attn_token_to_image.`, q, k, keys, config.downsample);
120
+ queries = norm(node('add', [queries, final]), `${prefix}${names.finalNorm}.`);
121
+ return { queries, keys };
122
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Depth Anything 3's backbone: DINOv2 as that model runs it, over one view or several at once.
3
+ *
4
+ * **Plain DINOv2 until `altStart`, and something else after it**, which is why this is not a
5
+ * generic vision transformer. From that block the class token of every view is replaced by a
6
+ * learned camera token — one for the first view, the reference, and another for the rest — and
7
+ * blocks alternate: an even one attends within each view, an odd one across all views at once.
8
+ * From `qkNormStart` each head's queries and keys are layer-normed, and from `ropeStart` they are
9
+ * turned by a two-dimensional rotary embedding: by the patch's row and column within a view, and
10
+ * across views by one position shared by every patch, so the global blocks see no geometry but
11
+ * which view a token belongs to.
12
+ *
13
+ * **Each tap is the last within-view output beside the current one**, `2·dim` wide, with only the
14
+ * second half normed — the upstream's `cat_token` — and its first token kept apart as that view's
15
+ * camera token, unnormed, which the camera decoder reads.
16
+ *
17
+ * **The positional embeddings are resized as the upstream resizes them**: bicubically, by the scale
18
+ * factor `(grid + 0.1)/trained`, whose inverse is the step a sample moves — not the ratio of the
19
+ * sizes. Only a square image at the trained grid skips it. The block norms take ε = 1e-6 and every
20
+ * other norm here PyTorch's default, 1e-5, which is a difference the checkpoint was trained with.
21
+ *
22
+ * What it gives up: the upstream reorders views around a chosen reference once there are three or
23
+ * more; here the first view given is the reference, and choosing it is the caller's.
24
+ */
25
+ import type { GraphBuilder, Weights } from '@driftengine/texture';
26
+
27
+ import { applyRope, ropeConstants } from './rope.ts';
28
+
29
+ export interface VitConfig {
30
+ readonly dim: number;
31
+ readonly heads: number;
32
+ readonly depth: number;
33
+ readonly hidden: number;
34
+ readonly patch: number;
35
+ /** The patch grid the positional embeddings were trained at: 518 / 14 = 37. */
36
+ readonly trainedGrid: number;
37
+ readonly taps: readonly number[];
38
+ readonly altStart: number;
39
+ readonly qkNormStart: number;
40
+ readonly ropeStart: number;
41
+ }
42
+
43
+ export interface VitOutput {
44
+ /** Per tap, per view: the patch tokens, `[rows · cols, 2 · dim]`. */
45
+ readonly features: readonly (readonly string[])[];
46
+ /** Per tap: every view's camera token, `[views, 2 · dim]`. */
47
+ readonly cameras: readonly string[];
48
+ }
49
+
50
+ /** Block norms are trained with ε = 1e-6; the rest keep PyTorch's default. */
51
+ const BLOCK_EPSILON = 1e-6;
52
+ const DEFAULT_EPSILON = 1e-5;
53
+
54
+ export function vitBackbone(
55
+ weights: Weights,
56
+ graph: GraphBuilder,
57
+ config: VitConfig,
58
+ prefix: string,
59
+ images: readonly string[],
60
+ rows: number,
61
+ cols: number,
62
+ ): VitOutput {
63
+ const { dim, heads } = config;
64
+ const headDim = dim / heads;
65
+ const patches = rows * cols;
66
+ const tokens = patches + 1;
67
+ const w = (name: string, shape: readonly number[]): string =>
68
+ weights.read(`${prefix}${name}`, shape);
69
+ const node = graph.node.bind(graph);
70
+
71
+ /* Positions, resized to this grid unless it is the trained one on a square image. */
72
+ const trained = config.trainedGrid;
73
+ const table = node('reshape', [w('pos_embed', [1, trained * trained + 1, dim])], {
74
+ shape: [trained * trained + 1, dim],
75
+ });
76
+ let positions = table;
77
+ if (rows !== trained || cols !== trained) {
78
+ const grid = node(
79
+ 'permute',
80
+ [
81
+ node(
82
+ 'reshape',
83
+ [node('slice', [table], { axis: 0, start: 1, end: trained * trained + 1 })],
84
+ {
85
+ shape: [trained, trained, dim],
86
+ },
87
+ ),
88
+ ],
89
+ { order: [2, 0, 1] },
90
+ );
91
+ const resized = node('resize', [grid], {
92
+ height: rows,
93
+ width: cols,
94
+ mode: 'bicubic',
95
+ alignCorners: false,
96
+ stepHeight: trained / (rows + 0.1),
97
+ stepWidth: trained / (cols + 0.1),
98
+ });
99
+ const flat = node('reshape', [node('permute', [resized], { order: [1, 2, 0] })], {
100
+ shape: [patches, dim],
101
+ });
102
+ positions = node('concat', [node('slice', [table], { axis: 0, start: 0, end: 1 }), flat], {
103
+ axis: 0,
104
+ });
105
+ }
106
+
107
+ const cls = node('reshape', [w('cls_token', [1, 1, dim])], { shape: [1, dim] });
108
+ let views = images.map((image) => {
109
+ const embedded = node(
110
+ 'patchEmbed',
111
+ [
112
+ image,
113
+ w('patch_embed.proj.weight', [dim, 3, config.patch, config.patch]),
114
+ w('patch_embed.proj.bias', [dim]),
115
+ ],
116
+ { patch: config.patch },
117
+ );
118
+ return node('add', [node('concat', [cls, embedded], { axis: 0 }), positions]);
119
+ });
120
+
121
+ /* Rotary tables: within a view by row and column from 1, across views all patches at 1, 1. */
122
+ const local = new Int32Array(2 * tokens);
123
+ const global = new Int32Array(2 * tokens * images.length);
124
+ for (let p = 0; p < patches; p += 1) {
125
+ local[2 * (p + 1)] = Math.floor(p / cols) + 1;
126
+ local[2 * (p + 1) + 1] = (p % cols) + 1;
127
+ }
128
+ for (let v = 0; v < images.length; v += 1) {
129
+ for (let p = 1; p < tokens; p += 1)
130
+ global.fill(1, 2 * (v * tokens + p), 2 * (v * tokens + p) + 2);
131
+ }
132
+ const rope =
133
+ config.ropeStart >= 0
134
+ ? {
135
+ local: ropeConstants(weights, 'rope.local', local, heads, headDim),
136
+ global: ropeConstants(weights, 'rope.global', global, heads, headDim),
137
+ }
138
+ : null;
139
+
140
+ const block = (i: number, x: string, count: number, across: boolean): string => {
141
+ const p = `blocks.${i}.`;
142
+ const h = node('layerNorm', [x, w(`${p}norm1.weight`, [dim]), w(`${p}norm1.bias`, [dim])], {
143
+ epsilon: BLOCK_EPSILON,
144
+ });
145
+ const qkv = node('linear', [
146
+ h,
147
+ w(`${p}attn.qkv.weight`, [3 * dim, dim]),
148
+ w(`${p}attn.qkv.bias`, [3 * dim]),
149
+ ]);
150
+ let q = node('slice', [qkv], { axis: 1, start: 0, end: dim });
151
+ let k = node('slice', [qkv], { axis: 1, start: dim, end: 2 * dim });
152
+ const v = node('slice', [qkv], { axis: 1, start: 2 * dim, end: 3 * dim });
153
+ if (config.qkNormStart >= 0 && i >= config.qkNormStart) {
154
+ const perHead = (value: string, norm: string): string =>
155
+ node(
156
+ 'reshape',
157
+ [
158
+ node(
159
+ 'layerNorm',
160
+ [
161
+ node('reshape', [value], { shape: [count * heads, headDim] }),
162
+ w(`${p}attn.${norm}.weight`, [headDim]),
163
+ w(`${p}attn.${norm}.bias`, [headDim]),
164
+ ],
165
+ { epsilon: DEFAULT_EPSILON },
166
+ ),
167
+ ],
168
+ { shape: [count, dim] },
169
+ );
170
+ q = perHead(q, 'q_norm');
171
+ k = perHead(k, 'k_norm');
172
+ }
173
+ if (rope !== null && i >= config.ropeStart) {
174
+ const tables = across ? rope.global : rope.local;
175
+ q = applyRope(graph, q, count, heads, headDim, tables.cos, tables.sin);
176
+ k = applyRope(graph, k, count, heads, headDim, tables.cos, tables.sin);
177
+ }
178
+ const attended = node('attention', [q, k, v], { heads });
179
+ const projected = node('linear', [
180
+ attended,
181
+ w(`${p}attn.proj.weight`, [dim, dim]),
182
+ w(`${p}attn.proj.bias`, [dim]),
183
+ ]);
184
+ const x1 = node('add', [x, node('mul', [projected, w(`${p}ls1.gamma`, [dim])])]);
185
+ const h2 = node('layerNorm', [x1, w(`${p}norm2.weight`, [dim]), w(`${p}norm2.bias`, [dim])], {
186
+ epsilon: BLOCK_EPSILON,
187
+ });
188
+ const inner = node('gelu', [
189
+ node('linear', [
190
+ h2,
191
+ w(`${p}mlp.fc1.weight`, [config.hidden, dim]),
192
+ w(`${p}mlp.fc1.bias`, [config.hidden]),
193
+ ]),
194
+ ]);
195
+ const out = node('linear', [
196
+ inner,
197
+ w(`${p}mlp.fc2.weight`, [dim, config.hidden]),
198
+ w(`${p}mlp.fc2.bias`, [dim]),
199
+ ]);
200
+ return node('add', [x1, node('mul', [out, w(`${p}ls2.gamma`, [dim])])]);
201
+ };
202
+
203
+ const features: string[][] = [];
204
+ const cameras: string[] = [];
205
+ let latest = views;
206
+ for (let i = 0; i < config.depth; i += 1) {
207
+ if (config.altStart >= 0 && i === config.altStart) {
208
+ const camera = node('reshape', [w('camera_token', [1, 2, dim])], { shape: [2, dim] });
209
+ const reference = node('slice', [camera], { axis: 0, start: 0, end: 1 });
210
+ const source = node('slice', [camera], { axis: 0, start: 1, end: 2 });
211
+ views = views.map((x, v) =>
212
+ node(
213
+ 'concat',
214
+ [v === 0 ? reference : source, node('slice', [x], { axis: 0, start: 1, end: tokens })],
215
+ { axis: 0 },
216
+ ),
217
+ );
218
+ }
219
+ const across = config.altStart >= 0 && i >= config.altStart && i % 2 === 1;
220
+ if (across) {
221
+ const all = views.length === 1 ? (views[0] as string) : node('concat', views, { axis: 0 });
222
+ const mixed = block(i, all, tokens * views.length, true);
223
+ views = views.map((_, v) =>
224
+ views.length === 1
225
+ ? mixed
226
+ : node('slice', [mixed], { axis: 0, start: v * tokens, end: (v + 1) * tokens }),
227
+ );
228
+ } else {
229
+ views = views.map((x) => block(i, x, tokens, false));
230
+ latest = views;
231
+ }
232
+ if (config.taps.includes(i)) {
233
+ const tapped = views.map((x, v) => node('concat', [latest[v] as string, x], { axis: 1 }));
234
+ cameras.push(
235
+ tapped.length === 1
236
+ ? node('slice', [tapped[0] as string], { axis: 0, start: 0, end: 1 })
237
+ : node(
238
+ 'concat',
239
+ tapped.map((t) => node('slice', [t], { axis: 0, start: 0, end: 1 })),
240
+ { axis: 0 },
241
+ ),
242
+ );
243
+ features.push(
244
+ tapped.map((t) => {
245
+ const normed = node(
246
+ 'layerNorm',
247
+ [
248
+ node('slice', [t], { axis: 1, start: dim, end: 2 * dim }),
249
+ w('norm.weight', [dim]),
250
+ w('norm.bias', [dim]),
251
+ ],
252
+ { epsilon: DEFAULT_EPSILON },
253
+ );
254
+ const joined = node(
255
+ 'concat',
256
+ [node('slice', [t], { axis: 1, start: 0, end: dim }), normed],
257
+ { axis: 1 },
258
+ );
259
+ return node('slice', [joined], { axis: 0, start: 1, end: tokens });
260
+ }),
261
+ );
262
+ }
263
+ }
264
+ return { features, cameras };
265
+ }