@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.
- package/LICENSE +202 -0
- package/NOTICE +29 -0
- package/README.md +68 -0
- package/dist/browserFrames.d.ts +31 -0
- package/dist/browserFrames.js +105 -0
- package/dist/collide.d.ts +59 -0
- package/dist/collide.js +92 -0
- package/dist/decimate.d.ts +42 -0
- package/dist/decimate.js +439 -0
- package/dist/delight.d.ts +42 -0
- package/dist/delight.js +351 -0
- package/dist/delightScene.d.ts +37 -0
- package/dist/delightScene.js +183 -0
- package/dist/depth.d.ts +44 -0
- package/dist/depth.js +53 -0
- package/dist/features.d.ts +33 -0
- package/dist/features.js +260 -0
- package/dist/frames.d.ts +47 -0
- package/dist/frames.js +87 -0
- package/dist/fusion.d.ts +77 -0
- package/dist/fusion.js +152 -0
- package/dist/gaussians/adam.d.ts +57 -0
- package/dist/gaussians/adam.js +88 -0
- package/dist/gaussians/depth.d.ts +8 -0
- package/dist/gaussians/depth.js +73 -0
- package/dist/gaussians/gradients.d.ts +36 -0
- package/dist/gaussians/gradients.js +228 -0
- package/dist/gaussians/harmonics.d.ts +46 -0
- package/dist/gaussians/harmonics.js +99 -0
- package/dist/gaussians/optimise.d.ts +50 -0
- package/dist/gaussians/optimise.js +125 -0
- package/dist/gaussians/parameters.d.ts +35 -0
- package/dist/gaussians/parameters.js +165 -0
- package/dist/gaussians/project.d.ts +72 -0
- package/dist/gaussians/project.js +137 -0
- package/dist/gaussians/projectGradient.d.ts +15 -0
- package/dist/gaussians/projectGradient.js +213 -0
- package/dist/gaussians/rasterise.d.ts +13 -0
- package/dist/gaussians/rasterise.js +65 -0
- package/dist/gaussians/refine.d.ts +36 -0
- package/dist/gaussians/refine.js +136 -0
- package/dist/gaussians/similarity.d.ts +25 -0
- package/dist/gaussians/similarity.js +80 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +96 -0
- package/dist/marching.d.ts +38 -0
- package/dist/marching.js +258 -0
- package/dist/masks.d.ts +59 -0
- package/dist/masks.js +165 -0
- package/dist/math/dense.d.ts +68 -0
- package/dist/math/dense.js +294 -0
- package/dist/math/schur.d.ts +23 -0
- package/dist/math/schur.js +140 -0
- package/dist/models/batchNorm.d.ts +17 -0
- package/dist/models/batchNorm.js +24 -0
- package/dist/models/clipTokenizer.d.ts +40 -0
- package/dist/models/clipTokenizer.js +122 -0
- package/dist/models/clipTower.d.ts +27 -0
- package/dist/models/clipTower.js +45 -0
- package/dist/models/depthAnything2.d.ts +38 -0
- package/dist/models/depthAnything2.js +163 -0
- package/dist/models/depthAnything3.d.ts +26 -0
- package/dist/models/depthAnything3.js +171 -0
- package/dist/models/dualDpt.d.ts +19 -0
- package/dist/models/dualDpt.js +138 -0
- package/dist/models/hiera.d.ts +49 -0
- package/dist/models/hiera.js +120 -0
- package/dist/models/hieraAttention.d.ts +16 -0
- package/dist/models/hieraAttention.js +68 -0
- package/dist/models/miniature.d.ts +36 -0
- package/dist/models/miniature.js +216 -0
- package/dist/models/mobileSam.d.ts +41 -0
- package/dist/models/mobileSam.js +88 -0
- package/dist/models/owlv2.d.ts +49 -0
- package/dist/models/owlv2.js +131 -0
- package/dist/models/owlv2Detect.d.ts +25 -0
- package/dist/models/owlv2Detect.js +80 -0
- package/dist/models/owlv2Miniature.d.ts +27 -0
- package/dist/models/owlv2Miniature.js +87 -0
- package/dist/models/rope.d.ts +23 -0
- package/dist/models/rope.js +84 -0
- package/dist/models/sam21.d.ts +45 -0
- package/dist/models/sam21.js +126 -0
- package/dist/models/sam21Miniature.d.ts +37 -0
- package/dist/models/sam21Miniature.js +194 -0
- package/dist/models/sam21Video.d.ts +30 -0
- package/dist/models/sam21Video.js +101 -0
- package/dist/models/sam2Choice.d.ts +19 -0
- package/dist/models/sam2Choice.js +55 -0
- package/dist/models/sam2Decoder.d.ts +36 -0
- package/dist/models/sam2Decoder.js +108 -0
- package/dist/models/sam2Memories.d.ts +54 -0
- package/dist/models/sam2Memories.js +93 -0
- package/dist/models/sam2Memory.d.ts +30 -0
- package/dist/models/sam2Memory.js +136 -0
- package/dist/models/sam2Positions.d.ts +24 -0
- package/dist/models/sam2Positions.js +94 -0
- package/dist/models/sam2Tracker.d.ts +69 -0
- package/dist/models/sam2Tracker.js +172 -0
- package/dist/models/samDecoder.d.ts +45 -0
- package/dist/models/samDecoder.js +96 -0
- package/dist/models/samMiniature.d.ts +43 -0
- package/dist/models/samMiniature.js +161 -0
- package/dist/models/samPrompt.d.ts +57 -0
- package/dist/models/samPrompt.js +160 -0
- package/dist/models/seeded.d.ts +16 -0
- package/dist/models/seeded.js +48 -0
- package/dist/models/tinyVit.d.ts +38 -0
- package/dist/models/tinyVit.js +82 -0
- package/dist/models/twoWay.d.ts +46 -0
- package/dist/models/twoWay.js +67 -0
- package/dist/models/vit.d.ts +45 -0
- package/dist/models/vit.js +157 -0
- package/dist/models/windowAttention.d.ts +25 -0
- package/dist/models/windowAttention.js +104 -0
- package/dist/poses.d.ts +31 -0
- package/dist/poses.js +750 -0
- package/dist/prepare.d.ts +30 -0
- package/dist/prepare.js +238 -0
- package/dist/propose.d.ts +36 -0
- package/dist/propose.js +63 -0
- package/dist/resample.d.ts +39 -0
- package/dist/resample.js +165 -0
- package/dist/run.d.ts +3 -0
- package/dist/run.js +1 -0
- package/dist/scene.d.ts +104 -0
- package/dist/scene.js +109 -0
- package/dist/segment.d.ts +40 -0
- package/dist/segment.js +184 -0
- package/dist/testScene.d.ts +55 -0
- package/dist/testScene.js +214 -0
- package/dist/twoView.d.ts +22 -0
- package/dist/twoView.js +435 -0
- package/package.json +63 -0
- package/src/browserFrames.ts +145 -0
- package/src/collide.ts +128 -0
- package/src/decimate.ts +510 -0
- package/src/delight.ts +427 -0
- package/src/delightScene.ts +249 -0
- package/src/depth.ts +100 -0
- package/src/features.ts +302 -0
- package/src/frames.ts +111 -0
- package/src/fusion.ts +229 -0
- package/src/gaussians/adam.ts +131 -0
- package/src/gaussians/depth.ts +87 -0
- package/src/gaussians/gradients.ts +285 -0
- package/src/gaussians/harmonics.ts +122 -0
- package/src/gaussians/optimise.ts +191 -0
- package/src/gaussians/parameters.ts +186 -0
- package/src/gaussians/project.ts +207 -0
- package/src/gaussians/projectGradient.ts +234 -0
- package/src/gaussians/rasterise.ts +81 -0
- package/src/gaussians/refine.ts +176 -0
- package/src/gaussians/similarity.ts +87 -0
- package/src/index.ts +225 -0
- package/src/marching.ts +358 -0
- package/src/masks.ts +201 -0
- package/src/math/dense.ts +317 -0
- package/src/math/schur.ts +167 -0
- package/src/models/batchNorm.ts +57 -0
- package/src/models/clipTokenizer.ts +129 -0
- package/src/models/clipTower.ts +87 -0
- package/src/models/depthAnything2.ts +276 -0
- package/src/models/depthAnything3.ts +229 -0
- package/src/models/dualDpt.ts +233 -0
- package/src/models/hiera.ts +231 -0
- package/src/models/hieraAttention.ts +121 -0
- package/src/models/miniature.ts +241 -0
- package/src/models/mobileSam.ts +135 -0
- package/src/models/owlv2.ts +190 -0
- package/src/models/owlv2Detect.ts +116 -0
- package/src/models/owlv2Miniature.ts +108 -0
- package/src/models/rope.ts +111 -0
- package/src/models/sam21.ts +182 -0
- package/src/models/sam21Miniature.ts +225 -0
- package/src/models/sam21Video.ts +154 -0
- package/src/models/sam2Choice.ts +62 -0
- package/src/models/sam2Decoder.ts +218 -0
- package/src/models/sam2Memories.ts +165 -0
- package/src/models/sam2Memory.ts +247 -0
- package/src/models/sam2Positions.ts +106 -0
- package/src/models/sam2Tracker.ts +300 -0
- package/src/models/samDecoder.ts +225 -0
- package/src/models/samMiniature.ts +190 -0
- package/src/models/samPrompt.ts +227 -0
- package/src/models/seeded.ts +50 -0
- package/src/models/tinyVit.ts +178 -0
- package/src/models/twoWay.ts +122 -0
- package/src/models/vit.ts +265 -0
- package/src/models/windowAttention.ts +155 -0
- package/src/poses.ts +894 -0
- package/src/prepare.ts +306 -0
- package/src/propose.ts +96 -0
- package/src/resample.ts +197 -0
- package/src/run.ts +7 -0
- package/src/scene.ts +141 -0
- package/src/segment.ts +220 -0
- package/src/testScene.ts +283 -0
- package/src/twoView.ts +536 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM's mask decoder: an image embedding and a prompt's tokens in, masks and their predicted
|
|
3
|
+
* quality out — MobileSAM's, and the original SAM's, which share it.
|
|
4
|
+
*
|
|
5
|
+
* **A two-way transformer** (`twoWay.ts`, under the original's names) over the prompt's tokens,
|
|
6
|
+
* behind a learned token for the quality estimate and one per mask, and the image with its dense
|
|
7
|
+
* prompt added.
|
|
8
|
+
*
|
|
9
|
+
* **Masks come from a product, not a convolution**: the image is upsampled four times by two
|
|
10
|
+
* transposed convolutions, and each mask token, through an MLP of its own, becomes a vector that
|
|
11
|
+
* is dotted with every upsampled pixel. The first mask is the one to take for an unambiguous
|
|
12
|
+
* prompt, and the other three for an ambiguous one, as the upstream's `multimask_output` chooses.
|
|
13
|
+
*
|
|
14
|
+
* **Every prompt weight is read, whether this graph's nodes use it or not**: the prompt's tokens
|
|
15
|
+
* are made on the host from the Fourier weights, and the mask input's downscaling is only in the
|
|
16
|
+
* graph that takes one — but the converted file is built from one graph, and each has to find
|
|
17
|
+
* every weight the others need in it.
|
|
18
|
+
*/
|
|
19
|
+
import type { GraphBuilder, Weights } from '@driftengine/texture';
|
|
20
|
+
|
|
21
|
+
import { samGridPositions } from './samPrompt.ts';
|
|
22
|
+
import { SAM_NAMES, twoWay } from './twoWay.ts';
|
|
23
|
+
|
|
24
|
+
export interface SamDecoderConfig {
|
|
25
|
+
readonly dim: number;
|
|
26
|
+
readonly heads: number;
|
|
27
|
+
readonly mlp: number;
|
|
28
|
+
readonly depth: number;
|
|
29
|
+
/** The width attention between tokens and image narrows by. */
|
|
30
|
+
readonly downsample: number;
|
|
31
|
+
/** The mask tokens: one for an unambiguous prompt and three for an ambiguous one. */
|
|
32
|
+
readonly masks: number;
|
|
33
|
+
readonly iouHidden: number;
|
|
34
|
+
readonly iouDepth: number;
|
|
35
|
+
/** The channels a mask input is downscaled through. */
|
|
36
|
+
readonly maskChannels: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CHANNEL_EPSILON = 1e-6;
|
|
40
|
+
|
|
41
|
+
/** What the decoder's two outputs are named. */
|
|
42
|
+
export interface SamDecoderOutput {
|
|
43
|
+
/** `[masks, 4·grid, 4·grid]`: each mask's logits at four times the embedding's grid. */
|
|
44
|
+
readonly masks: string;
|
|
45
|
+
/** `[1, masks]`: each mask's predicted intersection over union. */
|
|
46
|
+
readonly quality: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The decoder over `embedding`, `[dim, grid, grid]`, and `promptTokens`, `[tokens, dim]` or null for a
|
|
51
|
+
* prompt of none, with `mask`, `[1, 4·grid, 4·grid]`, the previous logits to refine, or null.
|
|
52
|
+
*/
|
|
53
|
+
export function samDecoder(
|
|
54
|
+
weights: Weights,
|
|
55
|
+
graph: GraphBuilder,
|
|
56
|
+
config: SamDecoderConfig,
|
|
57
|
+
embedding: string,
|
|
58
|
+
promptTokens: string | null,
|
|
59
|
+
mask: string | null,
|
|
60
|
+
grid: number,
|
|
61
|
+
names: SamDecoderOutput,
|
|
62
|
+
): void {
|
|
63
|
+
const node = graph.node.bind(graph);
|
|
64
|
+
const { dim, masks } = config;
|
|
65
|
+
const cells = grid * grid;
|
|
66
|
+
const d = 'mask_decoder.';
|
|
67
|
+
const t = `${d}transformer.`;
|
|
68
|
+
const pe = 'prompt_encoder.';
|
|
69
|
+
const read = weights.read.bind(weights);
|
|
70
|
+
|
|
71
|
+
const linear = (x: string, p: string, out: number, inputs: number, name?: string): string =>
|
|
72
|
+
node('linear', [x, read(`${p}weight`, [out, inputs]), read(`${p}bias`, [out])], {}, name);
|
|
73
|
+
const tokensOf = (map: string, channels: number, count: number): string =>
|
|
74
|
+
node('permute', [node('reshape', [map], { shape: [channels, count] })], { order: [1, 0] });
|
|
75
|
+
const channelNorm = (map: string, p: string, channels: number, rows: number): string =>
|
|
76
|
+
node(
|
|
77
|
+
'reshape',
|
|
78
|
+
[
|
|
79
|
+
node(
|
|
80
|
+
'permute',
|
|
81
|
+
[
|
|
82
|
+
node(
|
|
83
|
+
'layerNorm',
|
|
84
|
+
[
|
|
85
|
+
tokensOf(map, channels, rows * rows),
|
|
86
|
+
read(`${p}weight`, [channels]),
|
|
87
|
+
read(`${p}bias`, [channels]),
|
|
88
|
+
],
|
|
89
|
+
{ epsilon: CHANNEL_EPSILON },
|
|
90
|
+
),
|
|
91
|
+
],
|
|
92
|
+
{ order: [1, 0] },
|
|
93
|
+
),
|
|
94
|
+
],
|
|
95
|
+
{ shape: [channels, rows, rows] },
|
|
96
|
+
);
|
|
97
|
+
const perceptron = (x: string, p: string, sizes: readonly number[], name?: string): string => {
|
|
98
|
+
let y = x;
|
|
99
|
+
for (let i = 0; i + 1 < sizes.length; i += 1) {
|
|
100
|
+
const last = i + 2 === sizes.length;
|
|
101
|
+
y = linear(
|
|
102
|
+
y,
|
|
103
|
+
`${p}layers.${i}.`,
|
|
104
|
+
sizes[i + 1] as number,
|
|
105
|
+
sizes[i] as number,
|
|
106
|
+
last ? name : undefined,
|
|
107
|
+
);
|
|
108
|
+
if (i + 2 < sizes.length) y = node('relu', [y]);
|
|
109
|
+
}
|
|
110
|
+
return y;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/* The prompt encoder's weights, all of them: see the header. */
|
|
114
|
+
read(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]);
|
|
115
|
+
for (let i = 0; i < 4; i += 1) read(`${pe}point_embeddings.${i}.weight`, [1, dim]);
|
|
116
|
+
read(`${pe}not_a_point_embed.weight`, [1, dim]);
|
|
117
|
+
const c = config.maskChannels;
|
|
118
|
+
const downscaled = (input: string): string => {
|
|
119
|
+
const p = `${pe}mask_downscaling.`;
|
|
120
|
+
let x = node(
|
|
121
|
+
'conv2d',
|
|
122
|
+
[input, read(`${p}0.weight`, [c / 4, 1, 2, 2]), read(`${p}0.bias`, [c / 4])],
|
|
123
|
+
{ stride: 2 },
|
|
124
|
+
);
|
|
125
|
+
x = node('gelu', [channelNorm(x, `${p}1.`, c / 4, 2 * grid)]);
|
|
126
|
+
x = node('conv2d', [x, read(`${p}3.weight`, [c, c / 4, 2, 2]), read(`${p}3.bias`, [c])], {
|
|
127
|
+
stride: 2,
|
|
128
|
+
});
|
|
129
|
+
x = node('gelu', [channelNorm(x, `${p}4.`, c, grid)]);
|
|
130
|
+
x = node('conv2d', [x, read(`${p}6.weight`, [dim, c, 1, 1]), read(`${p}6.bias`, [dim])]);
|
|
131
|
+
return tokensOf(x, dim, cells);
|
|
132
|
+
};
|
|
133
|
+
const noMask = read(`${pe}no_mask_embed.weight`, [1, dim]);
|
|
134
|
+
if (mask === null) downscaledWeights(read, `${pe}mask_downscaling.`, dim, c);
|
|
135
|
+
const dense = mask === null ? node('reshape', [noMask], { shape: [dim] }) : downscaled(mask);
|
|
136
|
+
|
|
137
|
+
const positions = weights.derive(`${pe}grid_positions.${grid}x${grid}`, [cells, dim], (values) =>
|
|
138
|
+
samGridPositions(
|
|
139
|
+
values(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]),
|
|
140
|
+
dim,
|
|
141
|
+
grid,
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
const tokens = node(
|
|
145
|
+
'concat',
|
|
146
|
+
[
|
|
147
|
+
read(`${d}iou_token.weight`, [1, dim]),
|
|
148
|
+
read(`${d}mask_tokens.weight`, [masks, dim]),
|
|
149
|
+
...(promptTokens === null ? [] : [promptTokens]),
|
|
150
|
+
],
|
|
151
|
+
{ axis: 0 },
|
|
152
|
+
);
|
|
153
|
+
const { queries, keys } = twoWay(
|
|
154
|
+
weights,
|
|
155
|
+
graph,
|
|
156
|
+
t,
|
|
157
|
+
config,
|
|
158
|
+
SAM_NAMES,
|
|
159
|
+
tokens,
|
|
160
|
+
node('add', [tokensOf(embedding, dim, cells), dense]),
|
|
161
|
+
positions,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
/* Upsampled four times, and each mask token's vector dotted with every pixel. */
|
|
165
|
+
const u = `${d}output_upscaling.`;
|
|
166
|
+
const image = node('reshape', [node('permute', [keys], { order: [1, 0] })], {
|
|
167
|
+
shape: [dim, grid, grid],
|
|
168
|
+
});
|
|
169
|
+
let up = node(
|
|
170
|
+
'convTranspose2d',
|
|
171
|
+
[image, read(`${u}0.weight`, [dim, dim / 4, 2, 2]), read(`${u}0.bias`, [dim / 4])],
|
|
172
|
+
{ stride: 2 },
|
|
173
|
+
);
|
|
174
|
+
up = node('gelu', [channelNorm(up, `${u}1.`, dim / 4, 2 * grid)]);
|
|
175
|
+
up = node('gelu', [
|
|
176
|
+
node(
|
|
177
|
+
'convTranspose2d',
|
|
178
|
+
[up, read(`${u}3.weight`, [dim / 4, dim / 8, 2, 2]), read(`${u}3.bias`, [dim / 8])],
|
|
179
|
+
{ stride: 2 },
|
|
180
|
+
),
|
|
181
|
+
]);
|
|
182
|
+
const vectors = Array.from({ length: masks }, (_, i) =>
|
|
183
|
+
perceptron(
|
|
184
|
+
node('slice', [queries], { axis: 0, start: 1 + i, end: 2 + i }),
|
|
185
|
+
`${d}output_hypernetworks_mlps.${i}.`,
|
|
186
|
+
[dim, dim, dim, dim / 8],
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
const pixels = 16 * cells;
|
|
190
|
+
const products = node('linear', [
|
|
191
|
+
tokensOf(up, dim / 8, pixels),
|
|
192
|
+
node('concat', vectors, { axis: 0 }),
|
|
193
|
+
]);
|
|
194
|
+
node(
|
|
195
|
+
'reshape',
|
|
196
|
+
[node('permute', [products], { order: [1, 0] })],
|
|
197
|
+
{ shape: [masks, 4 * grid, 4 * grid] },
|
|
198
|
+
names.masks,
|
|
199
|
+
);
|
|
200
|
+
perceptron(
|
|
201
|
+
node('slice', [queries], { axis: 0, start: 0, end: 1 }),
|
|
202
|
+
`${d}iou_prediction_head.`,
|
|
203
|
+
[dim, ...Array.from({ length: config.iouDepth - 1 }, () => config.iouHidden), masks],
|
|
204
|
+
names.quality,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* The mask input's downscaling weights, read into a graph that takes no mask input. */
|
|
209
|
+
function downscaledWeights(
|
|
210
|
+
read: (name: string, shape?: readonly number[]) => string,
|
|
211
|
+
p: string,
|
|
212
|
+
dim: number,
|
|
213
|
+
c: number,
|
|
214
|
+
): void {
|
|
215
|
+
read(`${p}0.weight`, [c / 4, 1, 2, 2]);
|
|
216
|
+
read(`${p}0.bias`, [c / 4]);
|
|
217
|
+
read(`${p}1.weight`, [c / 4]);
|
|
218
|
+
read(`${p}1.bias`, [c / 4]);
|
|
219
|
+
read(`${p}3.weight`, [c, c / 4, 2, 2]);
|
|
220
|
+
read(`${p}3.bias`, [c]);
|
|
221
|
+
read(`${p}4.weight`, [c]);
|
|
222
|
+
read(`${p}4.bias`, [c]);
|
|
223
|
+
read(`${p}6.weight`, [dim, c, 1, 1]);
|
|
224
|
+
read(`${p}6.bias`, [dim]);
|
|
225
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A seeded miniature of MobileSAM: the upstream's layout, every tensor name and shape of it, at a
|
|
3
|
+
* size the CPU evaluates in a moment — what the model's tests and its hand-run oracle share, as the
|
|
4
|
+
* depth models' miniature is.
|
|
5
|
+
*
|
|
6
|
+
* **Small everywhere the upstream lets it be.** A 64-pixel image, four stages of 8, 16, 24 and 320
|
|
7
|
+
* channels — the last must be 320, since the upstream keeps its last merge's stride at one by that
|
|
8
|
+
* width alone — windows of 3, 4 and 2 over grids of 8, 4 and 4, so one stage pads its windows, one
|
|
9
|
+
* is attended whole and one divides evenly, and one or two blocks a stage. The neck's 256 channels
|
|
10
|
+
* are the upstream's own constant, so the decoder is 256 wide too; its MLP and quality head are
|
|
11
|
+
* narrow, and its grid is 4.
|
|
12
|
+
*
|
|
13
|
+
* **Every tensor the upstream holds is here**, the classification head the definition sets aside
|
|
14
|
+
* and the batch norms' counters included, so the oracle loads it strictly.
|
|
15
|
+
*/
|
|
16
|
+
import type { GraphTensor } from '@driftengine/texture';
|
|
17
|
+
|
|
18
|
+
import type { MobileSamConfig } from './mobileSam.ts';
|
|
19
|
+
import type { SamPrompt } from './samPrompt.ts';
|
|
20
|
+
import { draw } from './seeded.ts';
|
|
21
|
+
|
|
22
|
+
export const MINIATURE_MOBILE_SAM: MobileSamConfig = {
|
|
23
|
+
size: 64,
|
|
24
|
+
encoder: {
|
|
25
|
+
embedDims: [8, 16, 24, 320],
|
|
26
|
+
depths: [1, 2, 1, 1],
|
|
27
|
+
heads: [1, 2, 3, 10],
|
|
28
|
+
windows: [7, 3, 4, 2],
|
|
29
|
+
mergeStrides: [2, 2, 1],
|
|
30
|
+
mlpRatio: 4,
|
|
31
|
+
expandRatio: 4,
|
|
32
|
+
neck: 256,
|
|
33
|
+
},
|
|
34
|
+
decoder: {
|
|
35
|
+
dim: 256,
|
|
36
|
+
heads: 8,
|
|
37
|
+
mlp: 32,
|
|
38
|
+
depth: 2,
|
|
39
|
+
downsample: 2,
|
|
40
|
+
masks: 4,
|
|
41
|
+
iouHidden: 16,
|
|
42
|
+
iouDepth: 3,
|
|
43
|
+
maskChannels: 16,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** The classes of the miniature's classification head, which nothing runs. */
|
|
48
|
+
export const MINIATURE_SAM_CLASSES = 10;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The original image the prompts are given in: 36 by 48, prepared at 48 by 64 in the encoder's 64
|
|
52
|
+
* square, so the masks are cropped before they come down to it and coordinates are scaled by 4/3.
|
|
53
|
+
*/
|
|
54
|
+
export const MINIATURE_SAM_IMAGE = { height: 36, width: 48 } as const;
|
|
55
|
+
|
|
56
|
+
/** A point, which a padding point follows; a box; and two points with a box, which none follows. */
|
|
57
|
+
export const MINIATURE_SAM_PROMPTS = {
|
|
58
|
+
point: { points: [[20.5, 14.25, 1]] },
|
|
59
|
+
box: { box: [6, 5, 40.5, 30] },
|
|
60
|
+
both: {
|
|
61
|
+
points: [
|
|
62
|
+
[20.5, 14.25, 1],
|
|
63
|
+
[33, 25.75, 0],
|
|
64
|
+
],
|
|
65
|
+
box: [6, 5, 40.5, 30],
|
|
66
|
+
},
|
|
67
|
+
} as const satisfies Record<string, SamPrompt>;
|
|
68
|
+
|
|
69
|
+
/** Every tensor the upstream's encoder, prompt encoder and mask decoder hold. */
|
|
70
|
+
export function miniatureSamCheckpoint(
|
|
71
|
+
config: MobileSamConfig,
|
|
72
|
+
seed: number,
|
|
73
|
+
): Map<string, GraphTensor> {
|
|
74
|
+
const out = new Map<string, GraphTensor>();
|
|
75
|
+
const add = (name: string, shape: readonly number[]): void => {
|
|
76
|
+
out.set(name, { shape, data: draw(name, shape, seed) });
|
|
77
|
+
};
|
|
78
|
+
const convBn = (p: string, cout: number, cin: number, kernel: number): void => {
|
|
79
|
+
add(`${p}c.weight`, [cout, cin, kernel, kernel]);
|
|
80
|
+
for (const part of ['weight', 'bias', 'running_mean', 'running_var'])
|
|
81
|
+
add(`${p}bn.${part}`, [cout]);
|
|
82
|
+
out.set(`${p}bn.num_batches_tracked`, { shape: [], data: new Float32Array(1) });
|
|
83
|
+
};
|
|
84
|
+
const linear = (p: string, outputs: number, inputs: number): void => {
|
|
85
|
+
add(`${p}weight`, [outputs, inputs]);
|
|
86
|
+
add(`${p}bias`, [outputs]);
|
|
87
|
+
};
|
|
88
|
+
const norm = (p: string, dim: number): void => {
|
|
89
|
+
add(`${p}weight`, [dim]);
|
|
90
|
+
add(`${p}bias`, [dim]);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const { embedDims: dims, depths, windows, mlpRatio, expandRatio, neck } = config.encoder;
|
|
94
|
+
const e = 'image_encoder.';
|
|
95
|
+
convBn(`${e}patch_embed.seq.0.`, dims[0] / 2, 3, 3);
|
|
96
|
+
convBn(`${e}patch_embed.seq.2.`, dims[0], dims[0] / 2, 3);
|
|
97
|
+
for (let i = 0; i < depths[0]; i += 1) {
|
|
98
|
+
const p = `${e}layers.0.blocks.${i}.`;
|
|
99
|
+
const hidden = dims[0] * expandRatio;
|
|
100
|
+
convBn(`${p}conv1.`, hidden, dims[0], 1);
|
|
101
|
+
convBn(`${p}conv2.`, hidden, 1, 3);
|
|
102
|
+
convBn(`${p}conv3.`, dims[0], hidden, 1);
|
|
103
|
+
}
|
|
104
|
+
for (let stage = 0; stage < 4; stage += 1) {
|
|
105
|
+
const dim = dims[stage] as number;
|
|
106
|
+
if (stage > 0) {
|
|
107
|
+
for (let i = 0; i < (depths[stage] as number); i += 1) {
|
|
108
|
+
const p = `${e}layers.${stage}.blocks.${i}.`;
|
|
109
|
+
const window = windows[stage] as number;
|
|
110
|
+
add(`${p}attn.attention_biases`, [config.encoder.heads[stage] as number, window * window]);
|
|
111
|
+
norm(`${p}attn.norm.`, dim);
|
|
112
|
+
linear(`${p}attn.qkv.`, 3 * dim, dim);
|
|
113
|
+
linear(`${p}attn.proj.`, dim, dim);
|
|
114
|
+
norm(`${p}mlp.norm.`, dim);
|
|
115
|
+
linear(`${p}mlp.fc1.`, dim * mlpRatio, dim);
|
|
116
|
+
linear(`${p}mlp.fc2.`, dim, dim * mlpRatio);
|
|
117
|
+
convBn(`${p}local_conv.`, dim, 1, 3);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (stage < 3) {
|
|
121
|
+
const next = dims[stage + 1] as number;
|
|
122
|
+
const p = `${e}layers.${stage}.downsample.`;
|
|
123
|
+
convBn(`${p}conv1.`, next, dim, 1);
|
|
124
|
+
convBn(`${p}conv2.`, next, 1, 3);
|
|
125
|
+
convBn(`${p}conv3.`, next, next, 1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
norm(`${e}norm_head.`, dims[3]);
|
|
129
|
+
linear(`${e}head.`, MINIATURE_SAM_CLASSES, dims[3]);
|
|
130
|
+
add(`${e}neck.0.weight`, [neck, dims[3], 1, 1]);
|
|
131
|
+
norm(`${e}neck.1.`, neck);
|
|
132
|
+
add(`${e}neck.2.weight`, [neck, neck, 3, 3]);
|
|
133
|
+
norm(`${e}neck.3.`, neck);
|
|
134
|
+
|
|
135
|
+
const { dim, masks, mlp, maskChannels: c, iouHidden, iouDepth } = config.decoder;
|
|
136
|
+
const pe = 'prompt_encoder.';
|
|
137
|
+
add(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]);
|
|
138
|
+
for (let i = 0; i < 4; i += 1) add(`${pe}point_embeddings.${i}.weight`, [1, dim]);
|
|
139
|
+
add(`${pe}not_a_point_embed.weight`, [1, dim]);
|
|
140
|
+
add(`${pe}mask_downscaling.0.weight`, [c / 4, 1, 2, 2]);
|
|
141
|
+
add(`${pe}mask_downscaling.0.bias`, [c / 4]);
|
|
142
|
+
norm(`${pe}mask_downscaling.1.`, c / 4);
|
|
143
|
+
add(`${pe}mask_downscaling.3.weight`, [c, c / 4, 2, 2]);
|
|
144
|
+
add(`${pe}mask_downscaling.3.bias`, [c]);
|
|
145
|
+
norm(`${pe}mask_downscaling.4.`, c);
|
|
146
|
+
add(`${pe}mask_downscaling.6.weight`, [dim, c, 1, 1]);
|
|
147
|
+
add(`${pe}mask_downscaling.6.bias`, [dim]);
|
|
148
|
+
add(`${pe}no_mask_embed.weight`, [1, dim]);
|
|
149
|
+
|
|
150
|
+
const d = 'mask_decoder.';
|
|
151
|
+
const t = `${d}transformer.`;
|
|
152
|
+
const attention = (p: string, inner: number): void => {
|
|
153
|
+
for (const projection of ['q_proj', 'k_proj', 'v_proj'])
|
|
154
|
+
linear(`${p}${projection}.`, inner, dim);
|
|
155
|
+
linear(`${p}out_proj.`, dim, inner);
|
|
156
|
+
};
|
|
157
|
+
const inner = dim / config.decoder.downsample;
|
|
158
|
+
for (let i = 0; i < config.decoder.depth; i += 1) {
|
|
159
|
+
const p = `${t}layers.${i}.`;
|
|
160
|
+
attention(`${p}self_attn.`, dim);
|
|
161
|
+
norm(`${p}norm1.`, dim);
|
|
162
|
+
attention(`${p}cross_attn_token_to_image.`, inner);
|
|
163
|
+
norm(`${p}norm2.`, dim);
|
|
164
|
+
linear(`${p}mlp.lin1.`, mlp, dim);
|
|
165
|
+
linear(`${p}mlp.lin2.`, dim, mlp);
|
|
166
|
+
norm(`${p}norm3.`, dim);
|
|
167
|
+
norm(`${p}norm4.`, dim);
|
|
168
|
+
attention(`${p}cross_attn_image_to_token.`, inner);
|
|
169
|
+
}
|
|
170
|
+
attention(`${t}final_attn_token_to_image.`, inner);
|
|
171
|
+
norm(`${t}norm_final_attn.`, dim);
|
|
172
|
+
add(`${d}iou_token.weight`, [1, dim]);
|
|
173
|
+
add(`${d}mask_tokens.weight`, [masks, dim]);
|
|
174
|
+
add(`${d}output_upscaling.0.weight`, [dim, dim / 4, 2, 2]);
|
|
175
|
+
add(`${d}output_upscaling.0.bias`, [dim / 4]);
|
|
176
|
+
norm(`${d}output_upscaling.1.`, dim / 4);
|
|
177
|
+
add(`${d}output_upscaling.3.weight`, [dim / 4, dim / 8, 2, 2]);
|
|
178
|
+
add(`${d}output_upscaling.3.bias`, [dim / 8]);
|
|
179
|
+
for (let i = 0; i < masks; i += 1) {
|
|
180
|
+
const p = `${d}output_hypernetworks_mlps.${i}.layers.`;
|
|
181
|
+
linear(`${p}0.`, dim, dim);
|
|
182
|
+
linear(`${p}1.`, dim, dim);
|
|
183
|
+
linear(`${p}2.`, dim / 8, dim);
|
|
184
|
+
}
|
|
185
|
+
const sizes = [dim, ...Array.from({ length: iouDepth - 1 }, () => iouHidden), masks];
|
|
186
|
+
for (let i = 0; i + 1 < sizes.length; i += 1) {
|
|
187
|
+
linear(`${d}iou_prediction_head.layers.${i}.`, sizes[i + 1] as number, sizes[i] as number);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What MobileSAM computes on the host rather than the device: a prompt's tokens, the positions of
|
|
3
|
+
* the embedding's grid, the size an image is prepared at, and a mask's stability.
|
|
4
|
+
*
|
|
5
|
+
* **A position is random Fourier features**: a point in the unit square, taken to [−1, 1], times a
|
|
6
|
+
* fixed Gaussian matrix of the checkpoint's, times 2π, and the sine then the cosine of each of those
|
|
7
|
+
* angles. The angles reach tens of radians, which the device's `sin` is only specified to 2^−11
|
|
8
|
+
* over [−π, π] for, so they are taken here, to single precision, with the engine's reproducible
|
|
9
|
+
* functions and in the upstream's order of roundings. That is also why a prompt is tokens given to
|
|
10
|
+
* the decoder rather than coordinates: a few tokens a prompt, against a grid of positions laid out
|
|
11
|
+
* once per size.
|
|
12
|
+
*
|
|
13
|
+
* **A prompt is the upstream's**: each point shifted to its pixel's centre and labelled on the
|
|
14
|
+
* object or off it, a padding point appended when there is no box, and a box as its two corners.
|
|
15
|
+
* Points and boxes are given in the original image's pixels and scaled to the prepared image as the
|
|
16
|
+
* upstream's predictor scales them.
|
|
17
|
+
*/
|
|
18
|
+
import { exactCos, exactSin } from '@driftengine/core';
|
|
19
|
+
import type { WeightSource } from '@driftengine/texture';
|
|
20
|
+
|
|
21
|
+
export interface SamPrompt {
|
|
22
|
+
/** Points in the original image's pixels, each labelled 1 on the object and 0 off it. */
|
|
23
|
+
readonly points?: readonly (readonly [x: number, y: number, label: 0 | 1])[];
|
|
24
|
+
/** A box in the original image's pixels: left, top, right, bottom. */
|
|
25
|
+
readonly box?: readonly [number, number, number, number];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const f = Math.fround;
|
|
29
|
+
const TWO_PI = f(2 * Math.PI);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Where a checkpoint keeps its prompt encoder, and how its processor places a prompt: SAM's and
|
|
33
|
+
* MobileSAM's, or Transformers' SAM 2.
|
|
34
|
+
*/
|
|
35
|
+
export interface SamPromptLayout {
|
|
36
|
+
/** The Fourier matrix, `[2, dim / 2]`. */
|
|
37
|
+
readonly gaussian: string;
|
|
38
|
+
/** A learned point embedding by kind — 0 off, 1 on, 2 and 3 a box's corners — as name and row. */
|
|
39
|
+
readonly embedding: (kind: number) => readonly [name: string, row: number];
|
|
40
|
+
readonly notAPoint: string;
|
|
41
|
+
/** Whether a box is followed by a padding point, as SAM 2's is. */
|
|
42
|
+
readonly boxPadding: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether a box's corners come first as points and a padding point always ends the prompt, as a
|
|
45
|
+
* SAM 2 video session orders them — where a prompt of nothing is a point meaning none, padded.
|
|
46
|
+
*/
|
|
47
|
+
readonly boxAsPoints: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The prepared image's size for an original one: the longest side to `size` for SAM, the whole
|
|
50
|
+
* frame to a `size` square for SAM 2 — and whether a coordinate is scaled in double precision, as
|
|
51
|
+
* SAM's predictor does with numpy, or single, as SAM 2's processor does with a tensor.
|
|
52
|
+
*/
|
|
53
|
+
readonly prepared: (height: number, width: number, size: number) => readonly [number, number];
|
|
54
|
+
readonly singleScale: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const SAM_PROMPT: SamPromptLayout = {
|
|
58
|
+
gaussian: 'prompt_encoder.pe_layer.positional_encoding_gaussian_matrix',
|
|
59
|
+
embedding: (kind) => [`prompt_encoder.point_embeddings.${kind}.weight`, 0],
|
|
60
|
+
notAPoint: 'prompt_encoder.not_a_point_embed.weight',
|
|
61
|
+
boxPadding: false,
|
|
62
|
+
boxAsPoints: false,
|
|
63
|
+
prepared: (height, width, size) => samInputSize(height, width, size),
|
|
64
|
+
singleScale: false,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const SAM2_PROMPT: SamPromptLayout = {
|
|
68
|
+
gaussian: 'prompt_encoder.shared_embedding.positional_embedding',
|
|
69
|
+
embedding: (kind) => ['prompt_encoder.point_embed.weight', kind],
|
|
70
|
+
notAPoint: 'prompt_encoder.not_a_point_embed.weight',
|
|
71
|
+
boxPadding: true,
|
|
72
|
+
boxAsPoints: false,
|
|
73
|
+
prepared: (_height, _width, size) => [size, size],
|
|
74
|
+
singleScale: true,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** SAM 2 in a video session: the same encoder, a box as its two corner points first. */
|
|
78
|
+
export const SAM2_VIDEO_PROMPT: SamPromptLayout = {
|
|
79
|
+
...SAM2_PROMPT,
|
|
80
|
+
boxPadding: false,
|
|
81
|
+
boxAsPoints: true,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** The upstream's `get_preprocess_shape`: the longest side to `longest`, each side rounded. */
|
|
85
|
+
export function samInputSize(
|
|
86
|
+
height: number,
|
|
87
|
+
width: number,
|
|
88
|
+
longest: number,
|
|
89
|
+
): readonly [number, number] {
|
|
90
|
+
const scale = (longest * 1.0) / Math.max(height, width);
|
|
91
|
+
return [Math.floor(height * scale + 0.5), Math.floor(width * scale + 0.5)];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** How many tokens a prompt is, in a layout. */
|
|
95
|
+
export function samTokenCount(request: SamPrompt, layout: SamPromptLayout = SAM_PROMPT): number {
|
|
96
|
+
const points = request.points?.length ?? 0;
|
|
97
|
+
if (layout.boxAsPoints) {
|
|
98
|
+
const given = points + (request.box === undefined ? 0 : 2);
|
|
99
|
+
return (given === 0 ? 1 : given) + 1;
|
|
100
|
+
}
|
|
101
|
+
const box = request.box === undefined ? 0 : layout.boxPadding ? 3 : 2;
|
|
102
|
+
return points > 0 ? points + (box === 0 ? 1 : box) : box;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Sines then cosines of a point in the unit square's angles, `2 · half` values from `at`. */
|
|
106
|
+
function fourier(
|
|
107
|
+
gaussian: Float32Array,
|
|
108
|
+
half: number,
|
|
109
|
+
x: number,
|
|
110
|
+
y: number,
|
|
111
|
+
out: Float32Array,
|
|
112
|
+
at: number,
|
|
113
|
+
): void {
|
|
114
|
+
const u = f(2 * x - 1);
|
|
115
|
+
const v = f(2 * y - 1);
|
|
116
|
+
for (let k = 0; k < half; k += 1) {
|
|
117
|
+
const angle = f(TWO_PI * f(u * (gaussian[k] as number) + v * (gaussian[half + k] as number)));
|
|
118
|
+
out[at + k] = exactSin(angle);
|
|
119
|
+
out[at + half + k] = exactCos(angle);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function weight(weights: WeightSource, name: string, length: number, row = 0): Float32Array {
|
|
124
|
+
const tensor = weights.get(name);
|
|
125
|
+
if (tensor === undefined || tensor.data.length < (row + 1) * length) {
|
|
126
|
+
throw new Error(`the prompt encoder has no "${name}" of ${length} values in row ${row}`);
|
|
127
|
+
}
|
|
128
|
+
return tensor.data.subarray(row * length, (row + 1) * length);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* `[grid · grid, dim]`: the position of each cell of the embedding's grid, at its centre, as the
|
|
133
|
+
* upstream's `get_dense_pe` lays it out and the decoder reads it as tokens.
|
|
134
|
+
*/
|
|
135
|
+
export function samGridPositions(gaussian: Float32Array, dim: number, grid: number): Float32Array {
|
|
136
|
+
const out = new Float32Array(grid * grid * dim);
|
|
137
|
+
for (let y = 0; y < grid; y += 1) {
|
|
138
|
+
for (let x = 0; x < grid; x += 1) {
|
|
139
|
+
fourier(
|
|
140
|
+
gaussian,
|
|
141
|
+
dim / 2,
|
|
142
|
+
f((x + 0.5) / grid),
|
|
143
|
+
f((y + 0.5) / grid),
|
|
144
|
+
out,
|
|
145
|
+
(y * grid + x) * dim,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The tokens of `request`, `[samTokenCount(request, layout), dim]`, into `out`, for an original
|
|
154
|
+
* image of `height` by `width` prepared at `size` as `layout`'s processor prepares it.
|
|
155
|
+
*/
|
|
156
|
+
export function samPromptTokens(
|
|
157
|
+
weights: WeightSource,
|
|
158
|
+
dim: number,
|
|
159
|
+
size: number,
|
|
160
|
+
request: SamPrompt,
|
|
161
|
+
height: number,
|
|
162
|
+
width: number,
|
|
163
|
+
out: Float32Array,
|
|
164
|
+
layout: SamPromptLayout = SAM_PROMPT,
|
|
165
|
+
): void {
|
|
166
|
+
const half = dim / 2;
|
|
167
|
+
const gaussian = weight(weights, layout.gaussian, 2 * half);
|
|
168
|
+
const [inputHeight, inputWidth] = layout.prepared(height, width, size);
|
|
169
|
+
const scaleX = layout.singleScale ? f(inputWidth / width) : inputWidth / width;
|
|
170
|
+
const scaleY = layout.singleScale ? f(inputHeight / height) : inputHeight / height;
|
|
171
|
+
const place = (value: number, scale: number): number =>
|
|
172
|
+
f(f(f(layout.singleScale ? f(value) * scale : value * scale) + 0.5) / size);
|
|
173
|
+
let at = 0;
|
|
174
|
+
/* A token: the position of a point at its pixel's centre, or none, and a learned embedding. */
|
|
175
|
+
const token = (x: number, y: number, positioned: boolean, name: string, row: number): void => {
|
|
176
|
+
if (positioned) {
|
|
177
|
+
fourier(gaussian, half, place(x, scaleX), place(y, scaleY), out, at);
|
|
178
|
+
} else {
|
|
179
|
+
out.fill(0, at, at + dim);
|
|
180
|
+
}
|
|
181
|
+
const learned = weight(weights, name, dim, row);
|
|
182
|
+
for (let c = 0; c < dim; c += 1) {
|
|
183
|
+
out[at + c] = f((out[at + c] as number) + (learned[c] as number));
|
|
184
|
+
}
|
|
185
|
+
at += dim;
|
|
186
|
+
};
|
|
187
|
+
if (layout.boxAsPoints) {
|
|
188
|
+
if (request.box !== undefined) {
|
|
189
|
+
const [left, top, right, bottom] = request.box;
|
|
190
|
+
token(left, top, true, ...layout.embedding(2));
|
|
191
|
+
token(right, bottom, true, ...layout.embedding(3));
|
|
192
|
+
}
|
|
193
|
+
for (const [x, y, label] of request.points ?? []) token(x, y, true, ...layout.embedding(label));
|
|
194
|
+
if (request.box === undefined && (request.points?.length ?? 0) === 0) {
|
|
195
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
196
|
+
}
|
|
197
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
for (const [x, y, label] of request.points ?? []) token(x, y, true, ...layout.embedding(label));
|
|
201
|
+
if ((request.points?.length ?? 0) > 0 && request.box === undefined) {
|
|
202
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
203
|
+
}
|
|
204
|
+
if (request.box !== undefined) {
|
|
205
|
+
const [left, top, right, bottom] = request.box;
|
|
206
|
+
token(left, top, true, ...layout.embedding(2));
|
|
207
|
+
token(right, bottom, true, ...layout.embedding(3));
|
|
208
|
+
if (layout.boxPadding) token(0, 0, false, layout.notAPoint, 0);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The upstream's stability score: the share of the pixels above `threshold − offset` that are also
|
|
214
|
+
* above `threshold + offset` — one over the other of the mask thresholded high and thresholded low,
|
|
215
|
+
* since one always lies inside the other. A mask with no pixel above the lower threshold scores
|
|
216
|
+
* NaN, as the upstream's does.
|
|
217
|
+
*/
|
|
218
|
+
export function stabilityScore(logits: Float32Array, threshold: number, offset: number): number {
|
|
219
|
+
let inside = 0;
|
|
220
|
+
let union = 0;
|
|
221
|
+
for (let i = 0; i < logits.length; i += 1) {
|
|
222
|
+
const value = logits[i] as number;
|
|
223
|
+
if (value > threshold + offset) inside += 1;
|
|
224
|
+
if (value > threshold - offset) union += 1;
|
|
225
|
+
}
|
|
226
|
+
return inside / union;
|
|
227
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A miniature checkpoint's values, drawn from a seed and a tensor's name, so each tensor depends on
|
|
3
|
+
* its name and never on the order the tensors were made in.
|
|
4
|
+
*/
|
|
5
|
+
import { mulberry32 } from '@driftengine/core';
|
|
6
|
+
|
|
7
|
+
/* FNV-1a over a name, so each tensor's values depend on its name and not on the order made. */
|
|
8
|
+
export function nameSeed(name: string, seed: number): number {
|
|
9
|
+
let hash = 0x811c9dc5 ^ seed;
|
|
10
|
+
for (let i = 0; i < name.length; i += 1) {
|
|
11
|
+
hash = Math.imul(hash ^ name.charCodeAt(i), 0x01000193);
|
|
12
|
+
}
|
|
13
|
+
return hash >>> 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The spread a tensor's values are drawn with, by what the name says it is: a norm's scale near
|
|
18
|
+
* one, a layer scale near a half, a bias small, and a weight at 1.7/√fan-in. At 1/√fan-in the
|
|
19
|
+
* head's twenty convolutions shrank the signal until the depth was its biases — 1.08 to 1.11
|
|
20
|
+
* everywhere, and a slip in a norm's ε moved nothing a test could see; at He's √(6/fan-in) the
|
|
21
|
+
* fusion's residual sums compounded it to depths of 10^33. At 1.7 the depth spans 0.96 to 11.7.
|
|
22
|
+
*
|
|
23
|
+
* **A one-dimensional weight is a norm's scale** in every checkpoint here, whatever its name — a
|
|
24
|
+
* channel norm is often a bare index in a sequence — and is drawn near one.
|
|
25
|
+
*
|
|
26
|
+
* **A batch norm's statistics are drawn as statistics**: a running variance between a half and one
|
|
27
|
+
* and a half, so the scale folded from it stays near one, and a running mean small. Drawn as any
|
|
28
|
+
* other vector, a variance near zero would fold into a scale of hundreds.
|
|
29
|
+
*/
|
|
30
|
+
export function draw(name: string, shape: readonly number[], seed: number): Float32Array {
|
|
31
|
+
const next = mulberry32(nameSeed(name, seed));
|
|
32
|
+
const count = shape.reduce((total, d) => total * d, 1);
|
|
33
|
+
const fanIn = shape.slice(1).reduce((total, d) => total * d, 1);
|
|
34
|
+
const norm = (/norm|\.bn\./.test(name) || shape.length === 1) && name.endsWith('.weight');
|
|
35
|
+
const gamma = name.endsWith('.gamma') || name.endsWith('.lambda1');
|
|
36
|
+
const variance = name.endsWith('.running_var');
|
|
37
|
+
const centre = norm || variance ? 1 : gamma ? 0.5 : 0;
|
|
38
|
+
const spread = norm
|
|
39
|
+
? 0.1
|
|
40
|
+
: gamma
|
|
41
|
+
? 0.2
|
|
42
|
+
: variance
|
|
43
|
+
? 0.5
|
|
44
|
+
: name.endsWith('.bias') || name.endsWith('.running_mean')
|
|
45
|
+
? 0.1
|
|
46
|
+
: shape.length >= 2 && name.endsWith('.weight')
|
|
47
|
+
? 1.7 / Math.sqrt(fanIn)
|
|
48
|
+
: 0.5;
|
|
49
|
+
return Float32Array.from({ length: count }, () => centre + (next() * 2 - 1) * spread);
|
|
50
|
+
}
|