@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,161 @@
|
|
|
1
|
+
import { draw } from './seeded.js';
|
|
2
|
+
export const MINIATURE_MOBILE_SAM = {
|
|
3
|
+
size: 64,
|
|
4
|
+
encoder: {
|
|
5
|
+
embedDims: [8, 16, 24, 320],
|
|
6
|
+
depths: [1, 2, 1, 1],
|
|
7
|
+
heads: [1, 2, 3, 10],
|
|
8
|
+
windows: [7, 3, 4, 2],
|
|
9
|
+
mergeStrides: [2, 2, 1],
|
|
10
|
+
mlpRatio: 4,
|
|
11
|
+
expandRatio: 4,
|
|
12
|
+
neck: 256,
|
|
13
|
+
},
|
|
14
|
+
decoder: {
|
|
15
|
+
dim: 256,
|
|
16
|
+
heads: 8,
|
|
17
|
+
mlp: 32,
|
|
18
|
+
depth: 2,
|
|
19
|
+
downsample: 2,
|
|
20
|
+
masks: 4,
|
|
21
|
+
iouHidden: 16,
|
|
22
|
+
iouDepth: 3,
|
|
23
|
+
maskChannels: 16,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
/** The classes of the miniature's classification head, which nothing runs. */
|
|
27
|
+
export const MINIATURE_SAM_CLASSES = 10;
|
|
28
|
+
/**
|
|
29
|
+
* The original image the prompts are given in: 36 by 48, prepared at 48 by 64 in the encoder's 64
|
|
30
|
+
* square, so the masks are cropped before they come down to it and coordinates are scaled by 4/3.
|
|
31
|
+
*/
|
|
32
|
+
export const MINIATURE_SAM_IMAGE = { height: 36, width: 48 };
|
|
33
|
+
/** A point, which a padding point follows; a box; and two points with a box, which none follows. */
|
|
34
|
+
export const MINIATURE_SAM_PROMPTS = {
|
|
35
|
+
point: { points: [[20.5, 14.25, 1]] },
|
|
36
|
+
box: { box: [6, 5, 40.5, 30] },
|
|
37
|
+
both: {
|
|
38
|
+
points: [
|
|
39
|
+
[20.5, 14.25, 1],
|
|
40
|
+
[33, 25.75, 0],
|
|
41
|
+
],
|
|
42
|
+
box: [6, 5, 40.5, 30],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
/** Every tensor the upstream's encoder, prompt encoder and mask decoder hold. */
|
|
46
|
+
export function miniatureSamCheckpoint(config, seed) {
|
|
47
|
+
const out = new Map();
|
|
48
|
+
const add = (name, shape) => {
|
|
49
|
+
out.set(name, { shape, data: draw(name, shape, seed) });
|
|
50
|
+
};
|
|
51
|
+
const convBn = (p, cout, cin, kernel) => {
|
|
52
|
+
add(`${p}c.weight`, [cout, cin, kernel, kernel]);
|
|
53
|
+
for (const part of ['weight', 'bias', 'running_mean', 'running_var'])
|
|
54
|
+
add(`${p}bn.${part}`, [cout]);
|
|
55
|
+
out.set(`${p}bn.num_batches_tracked`, { shape: [], data: new Float32Array(1) });
|
|
56
|
+
};
|
|
57
|
+
const linear = (p, outputs, inputs) => {
|
|
58
|
+
add(`${p}weight`, [outputs, inputs]);
|
|
59
|
+
add(`${p}bias`, [outputs]);
|
|
60
|
+
};
|
|
61
|
+
const norm = (p, dim) => {
|
|
62
|
+
add(`${p}weight`, [dim]);
|
|
63
|
+
add(`${p}bias`, [dim]);
|
|
64
|
+
};
|
|
65
|
+
const { embedDims: dims, depths, windows, mlpRatio, expandRatio, neck } = config.encoder;
|
|
66
|
+
const e = 'image_encoder.';
|
|
67
|
+
convBn(`${e}patch_embed.seq.0.`, dims[0] / 2, 3, 3);
|
|
68
|
+
convBn(`${e}patch_embed.seq.2.`, dims[0], dims[0] / 2, 3);
|
|
69
|
+
for (let i = 0; i < depths[0]; i += 1) {
|
|
70
|
+
const p = `${e}layers.0.blocks.${i}.`;
|
|
71
|
+
const hidden = dims[0] * expandRatio;
|
|
72
|
+
convBn(`${p}conv1.`, hidden, dims[0], 1);
|
|
73
|
+
convBn(`${p}conv2.`, hidden, 1, 3);
|
|
74
|
+
convBn(`${p}conv3.`, dims[0], hidden, 1);
|
|
75
|
+
}
|
|
76
|
+
for (let stage = 0; stage < 4; stage += 1) {
|
|
77
|
+
const dim = dims[stage];
|
|
78
|
+
if (stage > 0) {
|
|
79
|
+
for (let i = 0; i < depths[stage]; i += 1) {
|
|
80
|
+
const p = `${e}layers.${stage}.blocks.${i}.`;
|
|
81
|
+
const window = windows[stage];
|
|
82
|
+
add(`${p}attn.attention_biases`, [config.encoder.heads[stage], window * window]);
|
|
83
|
+
norm(`${p}attn.norm.`, dim);
|
|
84
|
+
linear(`${p}attn.qkv.`, 3 * dim, dim);
|
|
85
|
+
linear(`${p}attn.proj.`, dim, dim);
|
|
86
|
+
norm(`${p}mlp.norm.`, dim);
|
|
87
|
+
linear(`${p}mlp.fc1.`, dim * mlpRatio, dim);
|
|
88
|
+
linear(`${p}mlp.fc2.`, dim, dim * mlpRatio);
|
|
89
|
+
convBn(`${p}local_conv.`, dim, 1, 3);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (stage < 3) {
|
|
93
|
+
const next = dims[stage + 1];
|
|
94
|
+
const p = `${e}layers.${stage}.downsample.`;
|
|
95
|
+
convBn(`${p}conv1.`, next, dim, 1);
|
|
96
|
+
convBn(`${p}conv2.`, next, 1, 3);
|
|
97
|
+
convBn(`${p}conv3.`, next, next, 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
norm(`${e}norm_head.`, dims[3]);
|
|
101
|
+
linear(`${e}head.`, MINIATURE_SAM_CLASSES, dims[3]);
|
|
102
|
+
add(`${e}neck.0.weight`, [neck, dims[3], 1, 1]);
|
|
103
|
+
norm(`${e}neck.1.`, neck);
|
|
104
|
+
add(`${e}neck.2.weight`, [neck, neck, 3, 3]);
|
|
105
|
+
norm(`${e}neck.3.`, neck);
|
|
106
|
+
const { dim, masks, mlp, maskChannels: c, iouHidden, iouDepth } = config.decoder;
|
|
107
|
+
const pe = 'prompt_encoder.';
|
|
108
|
+
add(`${pe}pe_layer.positional_encoding_gaussian_matrix`, [2, dim / 2]);
|
|
109
|
+
for (let i = 0; i < 4; i += 1)
|
|
110
|
+
add(`${pe}point_embeddings.${i}.weight`, [1, dim]);
|
|
111
|
+
add(`${pe}not_a_point_embed.weight`, [1, dim]);
|
|
112
|
+
add(`${pe}mask_downscaling.0.weight`, [c / 4, 1, 2, 2]);
|
|
113
|
+
add(`${pe}mask_downscaling.0.bias`, [c / 4]);
|
|
114
|
+
norm(`${pe}mask_downscaling.1.`, c / 4);
|
|
115
|
+
add(`${pe}mask_downscaling.3.weight`, [c, c / 4, 2, 2]);
|
|
116
|
+
add(`${pe}mask_downscaling.3.bias`, [c]);
|
|
117
|
+
norm(`${pe}mask_downscaling.4.`, c);
|
|
118
|
+
add(`${pe}mask_downscaling.6.weight`, [dim, c, 1, 1]);
|
|
119
|
+
add(`${pe}mask_downscaling.6.bias`, [dim]);
|
|
120
|
+
add(`${pe}no_mask_embed.weight`, [1, dim]);
|
|
121
|
+
const d = 'mask_decoder.';
|
|
122
|
+
const t = `${d}transformer.`;
|
|
123
|
+
const attention = (p, inner) => {
|
|
124
|
+
for (const projection of ['q_proj', 'k_proj', 'v_proj'])
|
|
125
|
+
linear(`${p}${projection}.`, inner, dim);
|
|
126
|
+
linear(`${p}out_proj.`, dim, inner);
|
|
127
|
+
};
|
|
128
|
+
const inner = dim / config.decoder.downsample;
|
|
129
|
+
for (let i = 0; i < config.decoder.depth; i += 1) {
|
|
130
|
+
const p = `${t}layers.${i}.`;
|
|
131
|
+
attention(`${p}self_attn.`, dim);
|
|
132
|
+
norm(`${p}norm1.`, dim);
|
|
133
|
+
attention(`${p}cross_attn_token_to_image.`, inner);
|
|
134
|
+
norm(`${p}norm2.`, dim);
|
|
135
|
+
linear(`${p}mlp.lin1.`, mlp, dim);
|
|
136
|
+
linear(`${p}mlp.lin2.`, dim, mlp);
|
|
137
|
+
norm(`${p}norm3.`, dim);
|
|
138
|
+
norm(`${p}norm4.`, dim);
|
|
139
|
+
attention(`${p}cross_attn_image_to_token.`, inner);
|
|
140
|
+
}
|
|
141
|
+
attention(`${t}final_attn_token_to_image.`, inner);
|
|
142
|
+
norm(`${t}norm_final_attn.`, dim);
|
|
143
|
+
add(`${d}iou_token.weight`, [1, dim]);
|
|
144
|
+
add(`${d}mask_tokens.weight`, [masks, dim]);
|
|
145
|
+
add(`${d}output_upscaling.0.weight`, [dim, dim / 4, 2, 2]);
|
|
146
|
+
add(`${d}output_upscaling.0.bias`, [dim / 4]);
|
|
147
|
+
norm(`${d}output_upscaling.1.`, dim / 4);
|
|
148
|
+
add(`${d}output_upscaling.3.weight`, [dim / 4, dim / 8, 2, 2]);
|
|
149
|
+
add(`${d}output_upscaling.3.bias`, [dim / 8]);
|
|
150
|
+
for (let i = 0; i < masks; i += 1) {
|
|
151
|
+
const p = `${d}output_hypernetworks_mlps.${i}.layers.`;
|
|
152
|
+
linear(`${p}0.`, dim, dim);
|
|
153
|
+
linear(`${p}1.`, dim, dim);
|
|
154
|
+
linear(`${p}2.`, dim / 8, dim);
|
|
155
|
+
}
|
|
156
|
+
const sizes = [dim, ...Array.from({ length: iouDepth - 1 }, () => iouHidden), masks];
|
|
157
|
+
for (let i = 0; i + 1 < sizes.length; i += 1) {
|
|
158
|
+
linear(`${d}iou_prediction_head.layers.${i}.`, sizes[i + 1], sizes[i]);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { WeightSource } from '@driftengine/texture';
|
|
2
|
+
export interface SamPrompt {
|
|
3
|
+
/** Points in the original image's pixels, each labelled 1 on the object and 0 off it. */
|
|
4
|
+
readonly points?: readonly (readonly [x: number, y: number, label: 0 | 1])[];
|
|
5
|
+
/** A box in the original image's pixels: left, top, right, bottom. */
|
|
6
|
+
readonly box?: readonly [number, number, number, number];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Where a checkpoint keeps its prompt encoder, and how its processor places a prompt: SAM's and
|
|
10
|
+
* MobileSAM's, or Transformers' SAM 2.
|
|
11
|
+
*/
|
|
12
|
+
export interface SamPromptLayout {
|
|
13
|
+
/** The Fourier matrix, `[2, dim / 2]`. */
|
|
14
|
+
readonly gaussian: string;
|
|
15
|
+
/** A learned point embedding by kind — 0 off, 1 on, 2 and 3 a box's corners — as name and row. */
|
|
16
|
+
readonly embedding: (kind: number) => readonly [name: string, row: number];
|
|
17
|
+
readonly notAPoint: string;
|
|
18
|
+
/** Whether a box is followed by a padding point, as SAM 2's is. */
|
|
19
|
+
readonly boxPadding: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether a box's corners come first as points and a padding point always ends the prompt, as a
|
|
22
|
+
* SAM 2 video session orders them — where a prompt of nothing is a point meaning none, padded.
|
|
23
|
+
*/
|
|
24
|
+
readonly boxAsPoints: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The prepared image's size for an original one: the longest side to `size` for SAM, the whole
|
|
27
|
+
* frame to a `size` square for SAM 2 — and whether a coordinate is scaled in double precision, as
|
|
28
|
+
* SAM's predictor does with numpy, or single, as SAM 2's processor does with a tensor.
|
|
29
|
+
*/
|
|
30
|
+
readonly prepared: (height: number, width: number, size: number) => readonly [number, number];
|
|
31
|
+
readonly singleScale: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare const SAM_PROMPT: SamPromptLayout;
|
|
34
|
+
export declare const SAM2_PROMPT: SamPromptLayout;
|
|
35
|
+
/** SAM 2 in a video session: the same encoder, a box as its two corner points first. */
|
|
36
|
+
export declare const SAM2_VIDEO_PROMPT: SamPromptLayout;
|
|
37
|
+
/** The upstream's `get_preprocess_shape`: the longest side to `longest`, each side rounded. */
|
|
38
|
+
export declare function samInputSize(height: number, width: number, longest: number): readonly [number, number];
|
|
39
|
+
/** How many tokens a prompt is, in a layout. */
|
|
40
|
+
export declare function samTokenCount(request: SamPrompt, layout?: SamPromptLayout): number;
|
|
41
|
+
/**
|
|
42
|
+
* `[grid · grid, dim]`: the position of each cell of the embedding's grid, at its centre, as the
|
|
43
|
+
* upstream's `get_dense_pe` lays it out and the decoder reads it as tokens.
|
|
44
|
+
*/
|
|
45
|
+
export declare function samGridPositions(gaussian: Float32Array, dim: number, grid: number): Float32Array;
|
|
46
|
+
/**
|
|
47
|
+
* The tokens of `request`, `[samTokenCount(request, layout), dim]`, into `out`, for an original
|
|
48
|
+
* image of `height` by `width` prepared at `size` as `layout`'s processor prepares it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function samPromptTokens(weights: WeightSource, dim: number, size: number, request: SamPrompt, height: number, width: number, out: Float32Array, layout?: SamPromptLayout): void;
|
|
51
|
+
/**
|
|
52
|
+
* The upstream's stability score: the share of the pixels above `threshold − offset` that are also
|
|
53
|
+
* above `threshold + offset` — one over the other of the mask thresholded high and thresholded low,
|
|
54
|
+
* since one always lies inside the other. A mask with no pixel above the lower threshold scores
|
|
55
|
+
* NaN, as the upstream's does.
|
|
56
|
+
*/
|
|
57
|
+
export declare function stabilityScore(logits: Float32Array, threshold: number, offset: number): number;
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
const f = Math.fround;
|
|
20
|
+
const TWO_PI = f(2 * Math.PI);
|
|
21
|
+
export const SAM_PROMPT = {
|
|
22
|
+
gaussian: 'prompt_encoder.pe_layer.positional_encoding_gaussian_matrix',
|
|
23
|
+
embedding: (kind) => [`prompt_encoder.point_embeddings.${kind}.weight`, 0],
|
|
24
|
+
notAPoint: 'prompt_encoder.not_a_point_embed.weight',
|
|
25
|
+
boxPadding: false,
|
|
26
|
+
boxAsPoints: false,
|
|
27
|
+
prepared: (height, width, size) => samInputSize(height, width, size),
|
|
28
|
+
singleScale: false,
|
|
29
|
+
};
|
|
30
|
+
export const SAM2_PROMPT = {
|
|
31
|
+
gaussian: 'prompt_encoder.shared_embedding.positional_embedding',
|
|
32
|
+
embedding: (kind) => ['prompt_encoder.point_embed.weight', kind],
|
|
33
|
+
notAPoint: 'prompt_encoder.not_a_point_embed.weight',
|
|
34
|
+
boxPadding: true,
|
|
35
|
+
boxAsPoints: false,
|
|
36
|
+
prepared: (_height, _width, size) => [size, size],
|
|
37
|
+
singleScale: true,
|
|
38
|
+
};
|
|
39
|
+
/** SAM 2 in a video session: the same encoder, a box as its two corner points first. */
|
|
40
|
+
export const SAM2_VIDEO_PROMPT = {
|
|
41
|
+
...SAM2_PROMPT,
|
|
42
|
+
boxPadding: false,
|
|
43
|
+
boxAsPoints: true,
|
|
44
|
+
};
|
|
45
|
+
/** The upstream's `get_preprocess_shape`: the longest side to `longest`, each side rounded. */
|
|
46
|
+
export function samInputSize(height, width, longest) {
|
|
47
|
+
const scale = (longest * 1.0) / Math.max(height, width);
|
|
48
|
+
return [Math.floor(height * scale + 0.5), Math.floor(width * scale + 0.5)];
|
|
49
|
+
}
|
|
50
|
+
/** How many tokens a prompt is, in a layout. */
|
|
51
|
+
export function samTokenCount(request, layout = SAM_PROMPT) {
|
|
52
|
+
const points = request.points?.length ?? 0;
|
|
53
|
+
if (layout.boxAsPoints) {
|
|
54
|
+
const given = points + (request.box === undefined ? 0 : 2);
|
|
55
|
+
return (given === 0 ? 1 : given) + 1;
|
|
56
|
+
}
|
|
57
|
+
const box = request.box === undefined ? 0 : layout.boxPadding ? 3 : 2;
|
|
58
|
+
return points > 0 ? points + (box === 0 ? 1 : box) : box;
|
|
59
|
+
}
|
|
60
|
+
/* Sines then cosines of a point in the unit square's angles, `2 · half` values from `at`. */
|
|
61
|
+
function fourier(gaussian, half, x, y, out, at) {
|
|
62
|
+
const u = f(2 * x - 1);
|
|
63
|
+
const v = f(2 * y - 1);
|
|
64
|
+
for (let k = 0; k < half; k += 1) {
|
|
65
|
+
const angle = f(TWO_PI * f(u * gaussian[k] + v * gaussian[half + k]));
|
|
66
|
+
out[at + k] = exactSin(angle);
|
|
67
|
+
out[at + half + k] = exactCos(angle);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function weight(weights, name, length, row = 0) {
|
|
71
|
+
const tensor = weights.get(name);
|
|
72
|
+
if (tensor === undefined || tensor.data.length < (row + 1) * length) {
|
|
73
|
+
throw new Error(`the prompt encoder has no "${name}" of ${length} values in row ${row}`);
|
|
74
|
+
}
|
|
75
|
+
return tensor.data.subarray(row * length, (row + 1) * length);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `[grid · grid, dim]`: the position of each cell of the embedding's grid, at its centre, as the
|
|
79
|
+
* upstream's `get_dense_pe` lays it out and the decoder reads it as tokens.
|
|
80
|
+
*/
|
|
81
|
+
export function samGridPositions(gaussian, dim, grid) {
|
|
82
|
+
const out = new Float32Array(grid * grid * dim);
|
|
83
|
+
for (let y = 0; y < grid; y += 1) {
|
|
84
|
+
for (let x = 0; x < grid; x += 1) {
|
|
85
|
+
fourier(gaussian, dim / 2, f((x + 0.5) / grid), f((y + 0.5) / grid), out, (y * grid + x) * dim);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The tokens of `request`, `[samTokenCount(request, layout), dim]`, into `out`, for an original
|
|
92
|
+
* image of `height` by `width` prepared at `size` as `layout`'s processor prepares it.
|
|
93
|
+
*/
|
|
94
|
+
export function samPromptTokens(weights, dim, size, request, height, width, out, layout = SAM_PROMPT) {
|
|
95
|
+
const half = dim / 2;
|
|
96
|
+
const gaussian = weight(weights, layout.gaussian, 2 * half);
|
|
97
|
+
const [inputHeight, inputWidth] = layout.prepared(height, width, size);
|
|
98
|
+
const scaleX = layout.singleScale ? f(inputWidth / width) : inputWidth / width;
|
|
99
|
+
const scaleY = layout.singleScale ? f(inputHeight / height) : inputHeight / height;
|
|
100
|
+
const place = (value, scale) => f(f(f(layout.singleScale ? f(value) * scale : value * scale) + 0.5) / size);
|
|
101
|
+
let at = 0;
|
|
102
|
+
/* A token: the position of a point at its pixel's centre, or none, and a learned embedding. */
|
|
103
|
+
const token = (x, y, positioned, name, row) => {
|
|
104
|
+
if (positioned) {
|
|
105
|
+
fourier(gaussian, half, place(x, scaleX), place(y, scaleY), out, at);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
out.fill(0, at, at + dim);
|
|
109
|
+
}
|
|
110
|
+
const learned = weight(weights, name, dim, row);
|
|
111
|
+
for (let c = 0; c < dim; c += 1) {
|
|
112
|
+
out[at + c] = f(out[at + c] + learned[c]);
|
|
113
|
+
}
|
|
114
|
+
at += dim;
|
|
115
|
+
};
|
|
116
|
+
if (layout.boxAsPoints) {
|
|
117
|
+
if (request.box !== undefined) {
|
|
118
|
+
const [left, top, right, bottom] = request.box;
|
|
119
|
+
token(left, top, true, ...layout.embedding(2));
|
|
120
|
+
token(right, bottom, true, ...layout.embedding(3));
|
|
121
|
+
}
|
|
122
|
+
for (const [x, y, label] of request.points ?? [])
|
|
123
|
+
token(x, y, true, ...layout.embedding(label));
|
|
124
|
+
if (request.box === undefined && (request.points?.length ?? 0) === 0) {
|
|
125
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
126
|
+
}
|
|
127
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const [x, y, label] of request.points ?? [])
|
|
131
|
+
token(x, y, true, ...layout.embedding(label));
|
|
132
|
+
if ((request.points?.length ?? 0) > 0 && request.box === undefined) {
|
|
133
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
134
|
+
}
|
|
135
|
+
if (request.box !== undefined) {
|
|
136
|
+
const [left, top, right, bottom] = request.box;
|
|
137
|
+
token(left, top, true, ...layout.embedding(2));
|
|
138
|
+
token(right, bottom, true, ...layout.embedding(3));
|
|
139
|
+
if (layout.boxPadding)
|
|
140
|
+
token(0, 0, false, layout.notAPoint, 0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The upstream's stability score: the share of the pixels above `threshold − offset` that are also
|
|
145
|
+
* above `threshold + offset` — one over the other of the mask thresholded high and thresholded low,
|
|
146
|
+
* since one always lies inside the other. A mask with no pixel above the lower threshold scores
|
|
147
|
+
* NaN, as the upstream's does.
|
|
148
|
+
*/
|
|
149
|
+
export function stabilityScore(logits, threshold, offset) {
|
|
150
|
+
let inside = 0;
|
|
151
|
+
let union = 0;
|
|
152
|
+
for (let i = 0; i < logits.length; i += 1) {
|
|
153
|
+
const value = logits[i];
|
|
154
|
+
if (value > threshold + offset)
|
|
155
|
+
inside += 1;
|
|
156
|
+
if (value > threshold - offset)
|
|
157
|
+
union += 1;
|
|
158
|
+
}
|
|
159
|
+
return inside / union;
|
|
160
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function nameSeed(name: string, seed: number): number;
|
|
2
|
+
/**
|
|
3
|
+
* The spread a tensor's values are drawn with, by what the name says it is: a norm's scale near
|
|
4
|
+
* one, a layer scale near a half, a bias small, and a weight at 1.7/√fan-in. At 1/√fan-in the
|
|
5
|
+
* head's twenty convolutions shrank the signal until the depth was its biases — 1.08 to 1.11
|
|
6
|
+
* everywhere, and a slip in a norm's ε moved nothing a test could see; at He's √(6/fan-in) the
|
|
7
|
+
* fusion's residual sums compounded it to depths of 10^33. At 1.7 the depth spans 0.96 to 11.7.
|
|
8
|
+
*
|
|
9
|
+
* **A one-dimensional weight is a norm's scale** in every checkpoint here, whatever its name — a
|
|
10
|
+
* channel norm is often a bare index in a sequence — and is drawn near one.
|
|
11
|
+
*
|
|
12
|
+
* **A batch norm's statistics are drawn as statistics**: a running variance between a half and one
|
|
13
|
+
* and a half, so the scale folded from it stays near one, and a running mean small. Drawn as any
|
|
14
|
+
* other vector, a variance near zero would fold into a scale of hundreds.
|
|
15
|
+
*/
|
|
16
|
+
export declare function draw(name: string, shape: readonly number[], seed: number): Float32Array;
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
/* FNV-1a over a name, so each tensor's values depend on its name and not on the order made. */
|
|
7
|
+
export function nameSeed(name, seed) {
|
|
8
|
+
let hash = 0x811c9dc5 ^ seed;
|
|
9
|
+
for (let i = 0; i < name.length; i += 1) {
|
|
10
|
+
hash = Math.imul(hash ^ name.charCodeAt(i), 0x01000193);
|
|
11
|
+
}
|
|
12
|
+
return hash >>> 0;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The spread a tensor's values are drawn with, by what the name says it is: a norm's scale near
|
|
16
|
+
* one, a layer scale near a half, a bias small, and a weight at 1.7/√fan-in. At 1/√fan-in the
|
|
17
|
+
* head's twenty convolutions shrank the signal until the depth was its biases — 1.08 to 1.11
|
|
18
|
+
* everywhere, and a slip in a norm's ε moved nothing a test could see; at He's √(6/fan-in) the
|
|
19
|
+
* fusion's residual sums compounded it to depths of 10^33. At 1.7 the depth spans 0.96 to 11.7.
|
|
20
|
+
*
|
|
21
|
+
* **A one-dimensional weight is a norm's scale** in every checkpoint here, whatever its name — a
|
|
22
|
+
* channel norm is often a bare index in a sequence — and is drawn near one.
|
|
23
|
+
*
|
|
24
|
+
* **A batch norm's statistics are drawn as statistics**: a running variance between a half and one
|
|
25
|
+
* and a half, so the scale folded from it stays near one, and a running mean small. Drawn as any
|
|
26
|
+
* other vector, a variance near zero would fold into a scale of hundreds.
|
|
27
|
+
*/
|
|
28
|
+
export function draw(name, shape, seed) {
|
|
29
|
+
const next = mulberry32(nameSeed(name, seed));
|
|
30
|
+
const count = shape.reduce((total, d) => total * d, 1);
|
|
31
|
+
const fanIn = shape.slice(1).reduce((total, d) => total * d, 1);
|
|
32
|
+
const norm = (/norm|\.bn\./.test(name) || shape.length === 1) && name.endsWith('.weight');
|
|
33
|
+
const gamma = name.endsWith('.gamma') || name.endsWith('.lambda1');
|
|
34
|
+
const variance = name.endsWith('.running_var');
|
|
35
|
+
const centre = norm || variance ? 1 : gamma ? 0.5 : 0;
|
|
36
|
+
const spread = norm
|
|
37
|
+
? 0.1
|
|
38
|
+
: gamma
|
|
39
|
+
? 0.2
|
|
40
|
+
: variance
|
|
41
|
+
? 0.5
|
|
42
|
+
: name.endsWith('.bias') || name.endsWith('.running_mean')
|
|
43
|
+
? 0.1
|
|
44
|
+
: shape.length >= 2 && name.endsWith('.weight')
|
|
45
|
+
? 1.7 / Math.sqrt(fanIn)
|
|
46
|
+
: 0.5;
|
|
47
|
+
return Float32Array.from({ length: count }, () => centre + (next() * 2 - 1) * spread);
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
type Four = readonly [number, number, number, number];
|
|
18
|
+
export interface TinyVitConfig {
|
|
19
|
+
readonly embedDims: Four;
|
|
20
|
+
readonly depths: Four;
|
|
21
|
+
/** Heads per stage; the first stage is convolutional and has none. */
|
|
22
|
+
readonly heads: Four;
|
|
23
|
+
readonly windows: Four;
|
|
24
|
+
/** The stride of each merge between stages. */
|
|
25
|
+
readonly mergeStrides: readonly [number, number, number];
|
|
26
|
+
readonly mlpRatio: number;
|
|
27
|
+
readonly expandRatio: number;
|
|
28
|
+
/** The embedding's channels. */
|
|
29
|
+
readonly neck: number;
|
|
30
|
+
}
|
|
31
|
+
/** The grid the encoder's embedding has for an image `size` across. */
|
|
32
|
+
export declare function tinyVitGrid(config: TinyVitConfig, size: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* `[neck, grid, grid]` from `image`, `[3, size, size]`, normalised and padded as the upstream
|
|
35
|
+
* prepares it; the embedding is written as `output`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function tinyVit(weights: Weights, graph: GraphBuilder, config: TinyVitConfig, prefix: string, image: string, size: number, output: string): void;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { convBatchNorm } from './batchNorm.js';
|
|
2
|
+
import { windowAttention } from './windowAttention.js';
|
|
3
|
+
const EPSILON = 1e-5;
|
|
4
|
+
const CHANNEL_EPSILON = 1e-6;
|
|
5
|
+
/** The grid the encoder's embedding has for an image `size` across. */
|
|
6
|
+
export function tinyVitGrid(config, size) {
|
|
7
|
+
return config.mergeStrides.reduce((grid, stride) => grid / stride, size / 4);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* `[neck, grid, grid]` from `image`, `[3, size, size]`, normalised and padded as the upstream
|
|
11
|
+
* prepares it; the embedding is written as `output`.
|
|
12
|
+
*/
|
|
13
|
+
export function tinyVit(weights, graph, config, prefix, image, size, output) {
|
|
14
|
+
const node = graph.node.bind(graph);
|
|
15
|
+
const dims = config.embedDims;
|
|
16
|
+
weights.ignore(`${prefix}norm_head.`);
|
|
17
|
+
weights.ignore(`${prefix}head.`);
|
|
18
|
+
const convBn = (p, x, cin, cout, kernel, stride, groups) => convBatchNorm(weights, graph, p, x, cin, cout, kernel, stride, groups);
|
|
19
|
+
const gelu = (x) => node('gelu', [x]);
|
|
20
|
+
const tokensOf = (map, channels, cells) => node('permute', [node('reshape', [map], { shape: [channels, cells] })], { order: [1, 0] });
|
|
21
|
+
const mapOf = (tokens, channels, rows, cols) => node('reshape', [node('permute', [tokens], { order: [1, 0] })], {
|
|
22
|
+
shape: [channels, rows, cols],
|
|
23
|
+
});
|
|
24
|
+
const norm = (x, p, dim, epsilon) => node('layerNorm', [x, weights.read(`${p}weight`, [dim]), weights.read(`${p}bias`, [dim])], {
|
|
25
|
+
epsilon,
|
|
26
|
+
});
|
|
27
|
+
const linear = (x, p, out, inputs) => node('linear', [x, weights.read(`${p}weight`, [out, inputs]), weights.read(`${p}bias`, [out])]);
|
|
28
|
+
const merge = (p, map, dim, out, stride) => {
|
|
29
|
+
const expanded = gelu(convBn(`${p}conv1.`, map, dim, out, 1, 1, 1));
|
|
30
|
+
const strided = gelu(convBn(`${p}conv2.`, expanded, out, out, 3, stride, out));
|
|
31
|
+
return convBn(`${p}conv3.`, strided, out, out, 1, 1, 1);
|
|
32
|
+
};
|
|
33
|
+
let map = gelu(convBn(`${prefix}patch_embed.seq.0.`, image, 3, dims[0] / 2, 3, 2, 1));
|
|
34
|
+
map = convBn(`${prefix}patch_embed.seq.2.`, map, dims[0] / 2, dims[0], 3, 2, 1);
|
|
35
|
+
let grid = size / 4;
|
|
36
|
+
const hidden = dims[0] * config.expandRatio;
|
|
37
|
+
for (let i = 0; i < config.depths[0]; i += 1) {
|
|
38
|
+
const p = `${prefix}layers.0.blocks.${i}.`;
|
|
39
|
+
const inner = gelu(convBn(`${p}conv1.`, map, dims[0], hidden, 1, 1, 1));
|
|
40
|
+
const spread = gelu(convBn(`${p}conv2.`, inner, hidden, hidden, 3, 1, hidden));
|
|
41
|
+
map = gelu(node('add', [convBn(`${p}conv3.`, spread, hidden, dims[0], 1, 1, 1), map]));
|
|
42
|
+
}
|
|
43
|
+
map = merge(`${prefix}layers.0.downsample.`, map, dims[0], dims[1], config.mergeStrides[0]);
|
|
44
|
+
grid /= config.mergeStrides[0];
|
|
45
|
+
let tokens = tokensOf(map, dims[1], grid * grid);
|
|
46
|
+
for (let stage = 1; stage < 4; stage += 1) {
|
|
47
|
+
const dim = dims[stage];
|
|
48
|
+
const heads = config.heads[stage];
|
|
49
|
+
const window = config.windows[stage];
|
|
50
|
+
for (let i = 0; i < config.depths[stage]; i += 1) {
|
|
51
|
+
const p = `${prefix}layers.${stage}.blocks.${i}.`;
|
|
52
|
+
const attended = node('add', [
|
|
53
|
+
tokens,
|
|
54
|
+
windowAttention(weights, graph, `${p}attn.`, tokens, dim, heads, window, grid, grid),
|
|
55
|
+
]);
|
|
56
|
+
const local = convBn(`${p}local_conv.`, mapOf(attended, dim, grid, grid), dim, dim, 3, 1, dim);
|
|
57
|
+
const mixed = tokensOf(local, dim, grid * grid);
|
|
58
|
+
const inner = gelu(linear(norm(mixed, `${p}mlp.norm.`, dim, EPSILON), `${p}mlp.fc1.`, dim * config.mlpRatio, dim));
|
|
59
|
+
tokens = node('add', [mixed, linear(inner, `${p}mlp.fc2.`, dim, dim * config.mlpRatio)]);
|
|
60
|
+
}
|
|
61
|
+
if (stage < 3) {
|
|
62
|
+
const stride = config.mergeStrides[stage];
|
|
63
|
+
const next = dims[stage + 1];
|
|
64
|
+
const merged = merge(`${prefix}layers.${stage}.downsample.`, mapOf(tokens, dim, grid, grid), dim, next, stride);
|
|
65
|
+
grid /= stride;
|
|
66
|
+
tokens = tokensOf(merged, next, grid * grid);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/* The neck: a pointwise and a 3×3 convolution, each followed by a norm over the channels. */
|
|
70
|
+
const channelNorm = (x, p, name) => node('reshape', [
|
|
71
|
+
node('permute', [norm(tokensOf(x, config.neck, grid * grid), p, config.neck, CHANNEL_EPSILON)], { order: [1, 0] }),
|
|
72
|
+
], { shape: [config.neck, grid, grid] }, name);
|
|
73
|
+
const pointwise = node('conv2d', [
|
|
74
|
+
mapOf(tokens, dims[3], grid, grid),
|
|
75
|
+
weights.read(`${prefix}neck.0.weight`, [config.neck, dims[3], 1, 1]),
|
|
76
|
+
]);
|
|
77
|
+
const spatial = node('conv2d', [
|
|
78
|
+
channelNorm(pointwise, `${prefix}neck.1.`),
|
|
79
|
+
weights.read(`${prefix}neck.2.weight`, [config.neck, config.neck, 3, 3]),
|
|
80
|
+
], { padding: 1 });
|
|
81
|
+
channelNorm(spatial, `${prefix}neck.3.`, output);
|
|
82
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
export interface TwoWayConfig {
|
|
19
|
+
readonly dim: number;
|
|
20
|
+
readonly heads: number;
|
|
21
|
+
readonly mlp: number;
|
|
22
|
+
readonly depth: number;
|
|
23
|
+
readonly downsample: number;
|
|
24
|
+
}
|
|
25
|
+
/** What the two checkpoint layouts call the transformer's parts. */
|
|
26
|
+
export interface TwoWayNames {
|
|
27
|
+
/** An attention's output projection. */
|
|
28
|
+
readonly out: string;
|
|
29
|
+
/** A layer's `i`-th norm, 1 to 4. */
|
|
30
|
+
readonly norm: (i: number) => string;
|
|
31
|
+
readonly finalNorm: string;
|
|
32
|
+
/** The MLP's two projections. */
|
|
33
|
+
readonly mlp: readonly [string, string];
|
|
34
|
+
}
|
|
35
|
+
/** The original SAM's names, which MobileSAM keeps. */
|
|
36
|
+
export declare const SAM_NAMES: TwoWayNames;
|
|
37
|
+
/** Transformers' names for SAM 2. */
|
|
38
|
+
export declare const SAM2_NAMES: TwoWayNames;
|
|
39
|
+
/**
|
|
40
|
+
* The transformer under `prefix` over `tokens`, `[count, dim]`, and `image`, `[cells, dim]` in token
|
|
41
|
+
* layout with `positions` of the same shape; answers both, attended.
|
|
42
|
+
*/
|
|
43
|
+
export declare function twoWay(weights: Weights, graph: GraphBuilder, prefix: string, config: TwoWayConfig, names: TwoWayNames, tokens: string, image: string, positions: string): {
|
|
44
|
+
readonly queries: string;
|
|
45
|
+
readonly keys: string;
|
|
46
|
+
};
|