@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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM 2's memory: the encoder that makes a frame and its mask into a memory, and the attention that
|
|
3
|
+
* conditions a new frame's features on the memories kept.
|
|
4
|
+
*
|
|
5
|
+
* **A memory is the frame's features where the mask says the object is**: the mask, taken through a
|
|
6
|
+
* sigmoid unless it is already binary, scaled to ±10 and downsampled sixteen times by strided 3×3
|
|
7
|
+
* convolutions, is added to the features projected once, and the sum is fused by ConvNeXt blocks —
|
|
8
|
+
* a depthwise 7×7, a channel norm, and a pointwise MLP scaled per channel — and narrowed to the
|
|
9
|
+
* memory's 64 channels. Norms of channels take ε 1e-6.
|
|
10
|
+
*
|
|
11
|
+
* **The attention reads every memory at once**: the current features, with a tenth of their sine
|
|
12
|
+
* positions added, attend to themselves and then to the memories' tokens — every frame's cells and
|
|
13
|
+
* the object pointers after them — then pass an MLP, four times. Queries and the frames' keys are
|
|
14
|
+
* turned by an axial rotary embedding of the cell's column and row, the same table for every frame;
|
|
15
|
+
* the pointers' keys are not turned, as the upstream's `num_k_exclude_rope` leaves them. Its norms
|
|
16
|
+
* take PyTorch's default ε, 1e-5.
|
|
17
|
+
*/
|
|
18
|
+
import type { GraphBuilder, Weights } from '@driftengine/texture';
|
|
19
|
+
|
|
20
|
+
import type { Sam21MemoryConfig } from './sam21.ts';
|
|
21
|
+
import { sam2Rotary, sam2SinePositions } from './sam2Positions.ts';
|
|
22
|
+
|
|
23
|
+
const CHANNEL_EPSILON = 1e-6;
|
|
24
|
+
const EPSILON = 1e-5;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `[memory.dim, grid, grid]` into `output`, from `features`, `[dim, grid, grid]`, and `mask`,
|
|
28
|
+
* `[1, 16·grid, 16·grid]` — logits, or a binary mask when `binary`.
|
|
29
|
+
*/
|
|
30
|
+
export function sam2MemoryEncoder(
|
|
31
|
+
weights: Weights,
|
|
32
|
+
graph: GraphBuilder,
|
|
33
|
+
dim: number,
|
|
34
|
+
memory: Sam21MemoryConfig,
|
|
35
|
+
grid: number,
|
|
36
|
+
features: string,
|
|
37
|
+
mask: string,
|
|
38
|
+
binary: boolean,
|
|
39
|
+
output: string,
|
|
40
|
+
): void {
|
|
41
|
+
const node = graph.node.bind(graph);
|
|
42
|
+
const read = weights.read.bind(weights);
|
|
43
|
+
const e = 'memory_encoder.';
|
|
44
|
+
const side = 16 * grid;
|
|
45
|
+
const conv = (
|
|
46
|
+
x: string,
|
|
47
|
+
p: string,
|
|
48
|
+
out: number,
|
|
49
|
+
into: number,
|
|
50
|
+
k: number,
|
|
51
|
+
attributes = {},
|
|
52
|
+
): string =>
|
|
53
|
+
node('conv2d', [x, read(`${p}weight`, [out, into, k, k]), read(`${p}bias`, [out])], attributes);
|
|
54
|
+
const tokensOf = (map: string, channels: number, count: number): string =>
|
|
55
|
+
node('permute', [node('reshape', [map], { shape: [channels, count] })], { order: [1, 0] });
|
|
56
|
+
const mapOf = (tokens: string, channels: number, rows: number): string =>
|
|
57
|
+
node('reshape', [node('permute', [tokens], { order: [1, 0] })], {
|
|
58
|
+
shape: [channels, rows, rows],
|
|
59
|
+
});
|
|
60
|
+
const channelNorm = (map: string, p: string, channels: number, rows: number): string =>
|
|
61
|
+
mapOf(
|
|
62
|
+
node(
|
|
63
|
+
'layerNorm',
|
|
64
|
+
[
|
|
65
|
+
tokensOf(map, channels, rows * rows),
|
|
66
|
+
read(`${p}weight`, [channels]),
|
|
67
|
+
read(`${p}bias`, [channels]),
|
|
68
|
+
],
|
|
69
|
+
{ epsilon: CHANNEL_EPSILON },
|
|
70
|
+
),
|
|
71
|
+
channels,
|
|
72
|
+
rows,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
/* The mask to ±10: a sigmoid's 0 to 1, or a binary mask's, times 20 less 10. */
|
|
76
|
+
const unit = binary ? mask : node('sigmoid', [mask]);
|
|
77
|
+
const scale = weights.constant(
|
|
78
|
+
`sam2.mask_scale.${side}`,
|
|
79
|
+
[side],
|
|
80
|
+
new Float32Array(side).fill(20),
|
|
81
|
+
);
|
|
82
|
+
const shift = weights.constant(
|
|
83
|
+
`sam2.mask_shift.${side}`,
|
|
84
|
+
[side],
|
|
85
|
+
new Float32Array(side).fill(-10),
|
|
86
|
+
);
|
|
87
|
+
let down = node('add', [node('mul', [unit, scale]), shift]);
|
|
88
|
+
let channels = 1;
|
|
89
|
+
let rows = side;
|
|
90
|
+
for (let i = 0; i < 4; i += 1) {
|
|
91
|
+
const p = `${e}mask_downsampler.layers.${i}.`;
|
|
92
|
+
down = conv(down, `${p}conv.`, channels * 4, channels, 3, { stride: 2, padding: 1 });
|
|
93
|
+
channels *= 4;
|
|
94
|
+
rows /= 2;
|
|
95
|
+
down = node('gelu', [channelNorm(down, `${p}layer_norm.`, channels, rows)]);
|
|
96
|
+
}
|
|
97
|
+
down = conv(down, `${e}mask_downsampler.final_conv.`, dim, channels, 1);
|
|
98
|
+
let fused = node('add', [conv(features, `${e}feature_projection.`, dim, dim, 1), down]);
|
|
99
|
+
for (let i = 0; i < memory.fuserLayers; i += 1) {
|
|
100
|
+
const p = `${e}memory_fuser.layers.${i}.`;
|
|
101
|
+
const spread = conv(fused, `${p}depthwise_conv.`, dim, 1, 7, { padding: 3, groups: dim });
|
|
102
|
+
const normed = tokensOf(channelNorm(spread, `${p}layer_norm.`, dim, grid), dim, grid * grid);
|
|
103
|
+
const hidden = node('gelu', [
|
|
104
|
+
node('linear', [
|
|
105
|
+
normed,
|
|
106
|
+
read(`${p}pointwise_conv1.weight`, [memory.fuserHidden, dim]),
|
|
107
|
+
read(`${p}pointwise_conv1.bias`, [memory.fuserHidden]),
|
|
108
|
+
]),
|
|
109
|
+
]);
|
|
110
|
+
const back = node('linear', [
|
|
111
|
+
hidden,
|
|
112
|
+
read(`${p}pointwise_conv2.weight`, [dim, memory.fuserHidden]),
|
|
113
|
+
read(`${p}pointwise_conv2.bias`, [dim]),
|
|
114
|
+
]);
|
|
115
|
+
fused = node('add', [fused, mapOf(node('mul', [back, read(`${p}scale`, [dim])]), dim, grid)]);
|
|
116
|
+
}
|
|
117
|
+
node(
|
|
118
|
+
'conv2d',
|
|
119
|
+
[
|
|
120
|
+
fused,
|
|
121
|
+
read(`${e}projection.weight`, [memory.dim, dim, 1, 1]),
|
|
122
|
+
read(`${e}projection.bias`, [memory.dim]),
|
|
123
|
+
],
|
|
124
|
+
{},
|
|
125
|
+
output,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* `[dim, grid, grid]` into `output`: `features`, `[dim, grid, grid]`, conditioned on `memory`,
|
|
131
|
+
* `[frames·grid² + pointers, memory.dim]`, whose positions are `positions` of that shape — the
|
|
132
|
+
* frames' cells first and `pointers` object-pointer tokens after them.
|
|
133
|
+
*/
|
|
134
|
+
export function sam2MemoryAttention(
|
|
135
|
+
weights: Weights,
|
|
136
|
+
graph: GraphBuilder,
|
|
137
|
+
dim: number,
|
|
138
|
+
config: Sam21MemoryConfig,
|
|
139
|
+
grid: number,
|
|
140
|
+
frames: number,
|
|
141
|
+
pointers: number,
|
|
142
|
+
features: string,
|
|
143
|
+
memory: string,
|
|
144
|
+
positions: string,
|
|
145
|
+
output: string,
|
|
146
|
+
): void {
|
|
147
|
+
const node = graph.node.bind(graph);
|
|
148
|
+
const read = weights.read.bind(weights);
|
|
149
|
+
const a = 'memory_attention.';
|
|
150
|
+
const cells = grid * grid;
|
|
151
|
+
const linear = (x: string, p: string, out: number, into: number): string =>
|
|
152
|
+
node('linear', [x, read(`${p}weight`, [out, into]), read(`${p}bias`, [out])]);
|
|
153
|
+
const norm = (x: string, p: string): string =>
|
|
154
|
+
node('layerNorm', [x, read(`${p}weight`, [dim]), read(`${p}bias`, [dim])], {
|
|
155
|
+
epsilon: EPSILON,
|
|
156
|
+
});
|
|
157
|
+
const tokensOf = (map: string, channels: number): string =>
|
|
158
|
+
node('permute', [node('reshape', [map], { shape: [channels, cells] })], { order: [1, 0] });
|
|
159
|
+
|
|
160
|
+
const sine = sam2SinePositions(dim / 2, grid, grid);
|
|
161
|
+
const tenth = Float32Array.from(sine, (value) => Math.fround(Math.fround(0.1) * value));
|
|
162
|
+
const place = weights.constant(`sam2.feature_positions.${grid}`, [dim, grid, grid], tenth);
|
|
163
|
+
let x = tokensOf(node('add', [features, place]), dim);
|
|
164
|
+
const rotary = sam2Rotary(dim / config.attentionHeads, grid, grid);
|
|
165
|
+
if (config.attentionHeads !== 1)
|
|
166
|
+
throw new Error('the memory attention is written for one head, as SAM 2.1 has');
|
|
167
|
+
const cos = weights.constant(`sam2.rotary_cos.${grid}`, [cells * dim], rotary.cos);
|
|
168
|
+
const sin = weights.constant(`sam2.rotary_sin.${grid}`, [cells * dim], rotary.sin);
|
|
169
|
+
/* `x·cos + swap(x)·sin` over `count` blocks of the grid's cells, `swap` exchanging each pair. */
|
|
170
|
+
const turn = (value: string, count: number): string => {
|
|
171
|
+
const pairs = node('reshape', [value], { shape: [count * cells * (dim / 2), 2] });
|
|
172
|
+
const swapped = node(
|
|
173
|
+
'concat',
|
|
174
|
+
[
|
|
175
|
+
node('slice', [pairs], { axis: 1, start: 1, end: 2 }),
|
|
176
|
+
node('slice', [pairs], { axis: 1, start: 0, end: 1 }),
|
|
177
|
+
],
|
|
178
|
+
{ axis: 1 },
|
|
179
|
+
);
|
|
180
|
+
const flat = (v: string): string => node('reshape', [v], { shape: [count, cells * dim] });
|
|
181
|
+
const turned = node('add', [
|
|
182
|
+
node('mul', [flat(value), cos]),
|
|
183
|
+
node('mul', [flat(swapped), sin]),
|
|
184
|
+
]);
|
|
185
|
+
return node('reshape', [turned], { shape: [count * cells, dim] });
|
|
186
|
+
};
|
|
187
|
+
const keyed = node('add', [memory, positions]);
|
|
188
|
+
const rotated = frames * cells;
|
|
189
|
+
const attend = (
|
|
190
|
+
p: string,
|
|
191
|
+
q: string,
|
|
192
|
+
k: string,
|
|
193
|
+
v: string,
|
|
194
|
+
into: number,
|
|
195
|
+
turnKeys: boolean,
|
|
196
|
+
): string => {
|
|
197
|
+
const queries = turn(linear(q, `${p}q_proj.`, dim, dim), 1);
|
|
198
|
+
let keys = linear(k, `${p}k_proj.`, dim, into);
|
|
199
|
+
if (turnKeys) {
|
|
200
|
+
const cellsPart = turn(node('slice', [keys], { axis: 0, start: 0, end: rotated }), frames);
|
|
201
|
+
keys =
|
|
202
|
+
pointers === 0
|
|
203
|
+
? cellsPart
|
|
204
|
+
: node(
|
|
205
|
+
'concat',
|
|
206
|
+
[
|
|
207
|
+
cellsPart,
|
|
208
|
+
node('slice', [keys], { axis: 0, start: rotated, end: rotated + pointers }),
|
|
209
|
+
],
|
|
210
|
+
{ axis: 0 },
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
keys = turn(keys, 1);
|
|
214
|
+
}
|
|
215
|
+
const values = linear(v, `${p}v_proj.`, dim, into);
|
|
216
|
+
return linear(
|
|
217
|
+
node('attention', [queries, keys, values], { heads: config.attentionHeads }),
|
|
218
|
+
`${p}o_proj.`,
|
|
219
|
+
dim,
|
|
220
|
+
dim,
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
for (let i = 0; i < config.attentionLayers; i += 1) {
|
|
224
|
+
const p = `${a}layers.${i}.`;
|
|
225
|
+
const q1 = norm(x, `${p}layer_norm1.`);
|
|
226
|
+
x = node('add', [x, attend(`${p}self_attn.`, q1, q1, q1, dim, false)]);
|
|
227
|
+
const q2 = norm(x, `${p}layer_norm2.`);
|
|
228
|
+
x = node('add', [x, attend(`${p}cross_attn_image.`, q2, keyed, memory, config.dim, true)]);
|
|
229
|
+
const q3 = norm(x, `${p}layer_norm3.`);
|
|
230
|
+
x = node('add', [
|
|
231
|
+
x,
|
|
232
|
+
linear(
|
|
233
|
+
node('relu', [linear(q3, `${p}linear1.`, config.feedForward, dim)]),
|
|
234
|
+
`${p}linear2.`,
|
|
235
|
+
dim,
|
|
236
|
+
config.feedForward,
|
|
237
|
+
),
|
|
238
|
+
]);
|
|
239
|
+
}
|
|
240
|
+
const normed = norm(x, `${a}layer_norm.`);
|
|
241
|
+
node(
|
|
242
|
+
'reshape',
|
|
243
|
+
[node('permute', [normed], { order: [1, 0] })],
|
|
244
|
+
{ shape: [dim, grid, grid] },
|
|
245
|
+
output,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM 2's position tables, which depend only on shapes: the feature pyramid's and the memory's
|
|
3
|
+
* two-dimensional sines, the object pointers' one-dimensional sines, and the memory attention's
|
|
4
|
+
* rotary tables. Each is laid out once per size and used as a constant of a graph or by the host.
|
|
5
|
+
*
|
|
6
|
+
* **Every rounding is the upstream's, in single precision**: a coordinate normalised to the grid's
|
|
7
|
+
* extent, divided by its frequency, and only then taken through `sin` or `cos` — the engine's
|
|
8
|
+
* reproducible ones, since a capture reproduces. A frequency is `10000^t`, taken as `exp(t·ln 10000)`
|
|
9
|
+
* rather than `**`, which is not the same bits on every engine.
|
|
10
|
+
*/
|
|
11
|
+
import { exactCos, exactExp, exactSin } from '@driftengine/core';
|
|
12
|
+
|
|
13
|
+
const f = Math.fround;
|
|
14
|
+
const TWO_PI = f(2 * Math.PI);
|
|
15
|
+
/** `ln 10000`, the temperature's logarithm. */
|
|
16
|
+
const LOG_TEMPERATURE = 9.210340371976184;
|
|
17
|
+
|
|
18
|
+
/* `10000^(2·⌊i/2⌋/count)` in single precision, as the upstream's `temperature ** (...)` makes it. */
|
|
19
|
+
function frequency(i: number, count: number): number {
|
|
20
|
+
return f(exactExp(f((2 * Math.floor(i / 2)) / count) * LOG_TEMPERATURE));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `[2·features, height, width]`: the upstream's normalised sine embedding — a row's and a column's
|
|
25
|
+
* index from one, over the last and times 2π, each divided by `features` frequencies taken in pairs,
|
|
26
|
+
* sine then cosine alternating — with the rows' `features` channels before the columns'.
|
|
27
|
+
*/
|
|
28
|
+
export function sam2SinePositions(features: number, height: number, width: number): Float32Array {
|
|
29
|
+
const out = new Float32Array(2 * features * height * width);
|
|
30
|
+
const plane = height * width;
|
|
31
|
+
const embed = (index: number, count: number): number =>
|
|
32
|
+
f(f((index + 1) / f(count + 1e-6)) * TWO_PI);
|
|
33
|
+
for (let i = 0; i < features; i += 1) {
|
|
34
|
+
const divisor = frequency(i, features);
|
|
35
|
+
const wave = i % 2 === 0 ? exactSin : exactCos;
|
|
36
|
+
for (let y = 0; y < height; y += 1) {
|
|
37
|
+
const row = wave(f(embed(y, height) / divisor));
|
|
38
|
+
for (let x = 0; x < width; x += 1) {
|
|
39
|
+
out[i * plane + y * width + x] = row;
|
|
40
|
+
out[(features + i) * plane + y * width + x] = wave(f(embed(x, width) / divisor));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `[positions.length, dim]`: the upstream's `get_1d_sine_pe` — each position divided by `dim / 2`
|
|
49
|
+
* frequencies taken in pairs, its sines then its cosines.
|
|
50
|
+
*/
|
|
51
|
+
export function sam2PointerPositions(positions: readonly number[], dim: number): Float32Array {
|
|
52
|
+
const half = dim / 2;
|
|
53
|
+
const out = new Float32Array(positions.length * dim);
|
|
54
|
+
for (let p = 0; p < positions.length; p += 1) {
|
|
55
|
+
for (let k = 0; k < half; k += 1) {
|
|
56
|
+
const angle = f(f(positions[p] as number) / frequency(k, half));
|
|
57
|
+
out[p * dim + k] = exactSin(angle);
|
|
58
|
+
out[p * dim + half + k] = exactCos(angle);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The memory attention's axial rotary tables over a `width` by `height` grid, row by row, for a
|
|
66
|
+
* head of `dim`, at the upstream's base of 10,000 — its configuration's `rope_theta`, the one value
|
|
67
|
+
* any checkpoint here uses: `[cells, dim]` cosines and sines, the first half of the channels turning by the
|
|
68
|
+
* column and the second by the row, each frequency on a pair of channels. **The sine carries the
|
|
69
|
+
* rotation's sign** — negative on a pair's first channel, positive on its second — so a rotation is
|
|
70
|
+
* `x·cos + swap(x)·sin`, `swap` exchanging each pair, which a graph composes from its shape
|
|
71
|
+
* operators.
|
|
72
|
+
*/
|
|
73
|
+
export function sam2Rotary(
|
|
74
|
+
dim: number,
|
|
75
|
+
width: number,
|
|
76
|
+
height: number,
|
|
77
|
+
): { readonly cos: Float32Array; readonly sin: Float32Array } {
|
|
78
|
+
const spatial = dim / 2;
|
|
79
|
+
const count = spatial / 2;
|
|
80
|
+
const inverse = Float32Array.from({ length: count }, (_, k) =>
|
|
81
|
+
f(1 / f(exactExp(f((2 * k) / spatial) * LOG_TEMPERATURE))),
|
|
82
|
+
);
|
|
83
|
+
const cells = width * height;
|
|
84
|
+
const cos = new Float32Array(cells * dim);
|
|
85
|
+
const sin = new Float32Array(cells * dim);
|
|
86
|
+
for (let y = 0; y < height; y += 1) {
|
|
87
|
+
for (let x = 0; x < width; x += 1) {
|
|
88
|
+
const at = (y * width + x) * dim;
|
|
89
|
+
for (let k = 0; k < count; k += 1) {
|
|
90
|
+
for (const [offset, position] of [
|
|
91
|
+
[0, x],
|
|
92
|
+
[spatial, y],
|
|
93
|
+
] as const) {
|
|
94
|
+
const angle = f(position * (inverse[k] as number));
|
|
95
|
+
const c = exactCos(angle);
|
|
96
|
+
const s = exactSin(angle);
|
|
97
|
+
cos[at + offset + 2 * k] = c;
|
|
98
|
+
cos[at + offset + 2 * k + 1] = c;
|
|
99
|
+
sin[at + offset + 2 * k] = -s;
|
|
100
|
+
sin[at + offset + 2 * k + 1] = s;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { cos, sin };
|
|
106
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAM 2.1 tracking objects through a video: the upstream's `Sam2VideoModel` loop on the host, its
|
|
3
|
+
* networks as the engine's graphs, run by whatever the caller gives — the device's runner, or the
|
|
4
|
+
* CPU's evaluator.
|
|
5
|
+
*
|
|
6
|
+
* **A frame is decoded against what the tracker remembers.** A prompted frame, the first time, reads
|
|
7
|
+
* nothing and is given `no_memory_embedding`; every other frame's features are first conditioned by
|
|
8
|
+
* the memory attention on the frames and pointers `sam2Memories.ts` chooses, and `sam2Choice.ts`
|
|
9
|
+
* keeps one of the four masks decoded.
|
|
10
|
+
*
|
|
11
|
+
* **Then each object's frame becomes a memory**, all of a frame's objects together: the mask
|
|
12
|
+
* brought to the frame's size, binarised when any of them was just prompted and taken through a
|
|
13
|
+
* sigmoid otherwise — the upstream decides it once for the batch — encoded with the frame's
|
|
14
|
+
* features, the occlusion embedding added where the object is absent, and rounded to bfloat16.
|
|
15
|
+
*
|
|
16
|
+
* What it gives up: a mask as a prompt, which the upstream downsamples with an antialiased resize
|
|
17
|
+
* the runtime does not yet have, and more prompted frames than all of them — the upstream's default
|
|
18
|
+
* — are the two options not taken.
|
|
19
|
+
*/
|
|
20
|
+
import type { NetworkGraph, WeightSource } from '@driftengine/texture';
|
|
21
|
+
import { graphFromWeights, type Architecture } from '@driftengine/texture';
|
|
22
|
+
|
|
23
|
+
import type { GraphRun } from '../run.ts';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
memoriesFor,
|
|
27
|
+
memoryTokens,
|
|
28
|
+
pointersFor,
|
|
29
|
+
toBfloat16,
|
|
30
|
+
type Sam2History,
|
|
31
|
+
type Sam2Output,
|
|
32
|
+
} from './sam2Memories.ts';
|
|
33
|
+
import { chooseMask } from './sam2Choice.ts';
|
|
34
|
+
import { sam2SinePositions } from './sam2Positions.ts';
|
|
35
|
+
import { sam21Decoder, sam21Encoder, type Sam21Config } from './sam21.ts';
|
|
36
|
+
import { sam21MemoryAttention, sam21MemoryEncoder, sam21Upscale } from './sam21Video.ts';
|
|
37
|
+
import { SAM2_VIDEO_PROMPT, samPromptTokens, samTokenCount, type SamPrompt } from './samPrompt.ts';
|
|
38
|
+
|
|
39
|
+
export type { GraphRun } from '../run.ts';
|
|
40
|
+
|
|
41
|
+
/** The weights each graph reads: a converted file's graphs, or a checkpoint for all four. */
|
|
42
|
+
export interface Sam21Weights {
|
|
43
|
+
readonly encoder: WeightSource;
|
|
44
|
+
readonly decoder: WeightSource;
|
|
45
|
+
readonly memoryEncoder: WeightSource;
|
|
46
|
+
readonly memoryAttention: WeightSource;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Sam21Video {
|
|
50
|
+
readonly frames: number;
|
|
51
|
+
/** The original frame's size, which prompts are given in. */
|
|
52
|
+
readonly height: number;
|
|
53
|
+
readonly width: number;
|
|
54
|
+
/** A frame prepared as the encoder takes it, `[3, size, size]`. */
|
|
55
|
+
frame(index: number): Float32Array;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A frame's result: every object's mask logits at a quarter of the encoder's size, and its score. */
|
|
59
|
+
export interface Sam21Frame {
|
|
60
|
+
readonly frame: number;
|
|
61
|
+
readonly objects: readonly number[];
|
|
62
|
+
/** `[objects, (size / 4)²]`. */
|
|
63
|
+
readonly masks: Float32Array;
|
|
64
|
+
readonly scores: Float32Array;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const NO_WEIGHTS: WeightSource = { get: () => undefined, names: () => [] };
|
|
68
|
+
|
|
69
|
+
interface Tracked extends Sam2History {
|
|
70
|
+
readonly prompts: Map<number, SamPrompt>;
|
|
71
|
+
readonly seen: Set<number>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class Sam21Tracker {
|
|
75
|
+
private readonly objects = new Map<number, Tracked>();
|
|
76
|
+
private fresh: number[] = [];
|
|
77
|
+
private readonly graphs = new Map<string, NetworkGraph>();
|
|
78
|
+
private encoded: {
|
|
79
|
+
readonly frame: number;
|
|
80
|
+
readonly values: ReadonlyMap<string, Float32Array>;
|
|
81
|
+
} | null = null;
|
|
82
|
+
private readonly cellPositions: Float32Array;
|
|
83
|
+
|
|
84
|
+
constructor(
|
|
85
|
+
private readonly config: Sam21Config,
|
|
86
|
+
private readonly weights: Sam21Weights,
|
|
87
|
+
private readonly run: GraphRun,
|
|
88
|
+
private readonly video: Sam21Video,
|
|
89
|
+
) {
|
|
90
|
+
const grid = config.size / 16;
|
|
91
|
+
const width = config.memory.dim;
|
|
92
|
+
const sine = sam2SinePositions(width / 2, grid, grid);
|
|
93
|
+
this.cellPositions = new Float32Array(grid * grid * width);
|
|
94
|
+
for (let c = 0; c < width; c += 1) {
|
|
95
|
+
for (let i = 0; i < grid * grid; i += 1)
|
|
96
|
+
this.cellPositions[i * width + c] = sine[c * grid * grid + i] as number;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Prompts on `frame`, replacing each object's earlier ones there; the next step decodes them. */
|
|
101
|
+
prompt(frame: number, prompts: ReadonlyMap<number, SamPrompt>): void {
|
|
102
|
+
for (const [object, prompt] of prompts) {
|
|
103
|
+
let state = this.objects.get(object);
|
|
104
|
+
if (state === undefined) {
|
|
105
|
+
state = { prompts: new Map(), prompted: new Map(), tracked: new Map(), seen: new Set() };
|
|
106
|
+
this.objects.set(object, state);
|
|
107
|
+
}
|
|
108
|
+
state.prompts.set(frame, prompt);
|
|
109
|
+
}
|
|
110
|
+
this.fresh = [...prompts.keys()];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Every object on `frame`, as the upstream's `forward` over its session answers it. */
|
|
114
|
+
async step(frame: number, reverse = false): Promise<Sam21Frame> {
|
|
115
|
+
const objects = [...this.objects.keys()];
|
|
116
|
+
const side = this.config.size / 4;
|
|
117
|
+
const cells = side * side;
|
|
118
|
+
const masks = new Float32Array(objects.length * cells);
|
|
119
|
+
const scores = new Float32Array(objects.length);
|
|
120
|
+
const pending: { readonly output: Sam2Output; readonly fromPrompt: boolean }[] = [];
|
|
121
|
+
let backwards = reverse;
|
|
122
|
+
for (const [i, object] of objects.entries()) {
|
|
123
|
+
const state = this.objects.get(object) as Tracked;
|
|
124
|
+
const isNew = this.fresh.includes(object);
|
|
125
|
+
let output = state.prompted.get(frame);
|
|
126
|
+
let first = true;
|
|
127
|
+
if (isNew || output === undefined) {
|
|
128
|
+
first = false;
|
|
129
|
+
let prompt: SamPrompt | null = null;
|
|
130
|
+
if (isNew) {
|
|
131
|
+
first = !state.seen.has(frame);
|
|
132
|
+
if (first) backwards = false;
|
|
133
|
+
prompt = state.prompts.get(frame) ?? null;
|
|
134
|
+
if (prompt !== null) this.fresh = this.fresh.filter((id) => id !== object);
|
|
135
|
+
}
|
|
136
|
+
output = await this.decode(state, frame, first, prompt, backwards);
|
|
137
|
+
(first ? state.prompted : state.tracked).set(frame, output);
|
|
138
|
+
pending.push({ output, fromPrompt: prompt !== null });
|
|
139
|
+
}
|
|
140
|
+
masks.set(output.lowRes, i * cells);
|
|
141
|
+
scores[i] = output.score;
|
|
142
|
+
if (!first) state.seen.add(frame);
|
|
143
|
+
}
|
|
144
|
+
await this.remember(frame, pending);
|
|
145
|
+
return { frame, objects, masks, scores };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private graph(key: string, source: WeightSource, build: () => Architecture): NetworkGraph {
|
|
149
|
+
let graph = this.graphs.get(key);
|
|
150
|
+
if (graph === undefined) {
|
|
151
|
+
graph = graphFromWeights(source, build());
|
|
152
|
+
this.graphs.set(key, graph);
|
|
153
|
+
}
|
|
154
|
+
return graph;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async features(frame: number): Promise<ReadonlyMap<string, Float32Array>> {
|
|
158
|
+
if (this.encoded?.frame !== frame) {
|
|
159
|
+
const graph = this.graph('encoder', this.weights.encoder, () => sam21Encoder(this.config));
|
|
160
|
+
const out = await this.run(graph, new Map([['image', this.video.frame(frame)]]));
|
|
161
|
+
this.encoded = {
|
|
162
|
+
frame,
|
|
163
|
+
values: new Map([...out].map(([name, values]) => [name, Float32Array.from(values)])),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return this.encoded.values;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private async decode(
|
|
170
|
+
state: Tracked,
|
|
171
|
+
frame: number,
|
|
172
|
+
first: boolean,
|
|
173
|
+
prompt: SamPrompt | null,
|
|
174
|
+
reverse: boolean,
|
|
175
|
+
): Promise<Sam2Output> {
|
|
176
|
+
const { config } = this;
|
|
177
|
+
const { dim } = config.decoder;
|
|
178
|
+
const features = await this.features(frame);
|
|
179
|
+
let embedding = features.get('features') as Float32Array;
|
|
180
|
+
if (!first) embedding = await this.condition(state, frame, embedding, reverse);
|
|
181
|
+
const request = prompt ?? {};
|
|
182
|
+
const tokens = new Float32Array(samTokenCount(request, SAM2_VIDEO_PROMPT) * dim);
|
|
183
|
+
samPromptTokens(
|
|
184
|
+
this.weights.decoder,
|
|
185
|
+
dim,
|
|
186
|
+
config.size,
|
|
187
|
+
request,
|
|
188
|
+
this.video.height,
|
|
189
|
+
this.video.width,
|
|
190
|
+
tokens,
|
|
191
|
+
SAM2_VIDEO_PROMPT,
|
|
192
|
+
);
|
|
193
|
+
const n = tokens.length / dim;
|
|
194
|
+
const decoder = this.graph(`decoder.${n}.${first}`, this.weights.decoder, () =>
|
|
195
|
+
sam21Decoder(config, n, { noMemory: first }),
|
|
196
|
+
);
|
|
197
|
+
const out = await this.run(
|
|
198
|
+
decoder,
|
|
199
|
+
new Map([
|
|
200
|
+
['embedding', embedding],
|
|
201
|
+
['high1', features.get('high1') as Float32Array],
|
|
202
|
+
['high0', features.get('high0') as Float32Array],
|
|
203
|
+
['prompt', tokens],
|
|
204
|
+
]),
|
|
205
|
+
);
|
|
206
|
+
const score = (out.get('object') as Float32Array)[0] as number;
|
|
207
|
+
const { lowRes, pointer } = chooseMask(
|
|
208
|
+
out.get('masks') as Float32Array,
|
|
209
|
+
out.get('quality') as Float32Array,
|
|
210
|
+
out.get('pointers') as Float32Array,
|
|
211
|
+
score,
|
|
212
|
+
(prompt?.points?.length ?? 0) + (prompt?.box === undefined ? 0 : 2),
|
|
213
|
+
this.tensor(this.weights.decoder, 'no_object_pointer'),
|
|
214
|
+
);
|
|
215
|
+
return { lowRes, pointer, score, memory: null };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async condition(
|
|
219
|
+
state: Tracked,
|
|
220
|
+
frame: number,
|
|
221
|
+
features: Float32Array,
|
|
222
|
+
reverse: boolean,
|
|
223
|
+
): Promise<Float32Array> {
|
|
224
|
+
const { config } = this;
|
|
225
|
+
const memories = memoriesFor(state, frame, config.memory.frames, reverse);
|
|
226
|
+
const pointers = pointersFor(state, frame, this.video.frames, config.memory.pointers, reverse);
|
|
227
|
+
const { memory, positions, pointerTokens } = memoryTokens(
|
|
228
|
+
this.weights.memoryAttention,
|
|
229
|
+
memories,
|
|
230
|
+
pointers,
|
|
231
|
+
this.cellPositions,
|
|
232
|
+
config.memory.dim,
|
|
233
|
+
config.decoder.dim,
|
|
234
|
+
);
|
|
235
|
+
const graph = this.graph(
|
|
236
|
+
`attention.${memories.length}.${pointerTokens}`,
|
|
237
|
+
this.weights.memoryAttention,
|
|
238
|
+
() => sam21MemoryAttention(config, memories.length, pointerTokens),
|
|
239
|
+
);
|
|
240
|
+
const out = await this.run(
|
|
241
|
+
graph,
|
|
242
|
+
new Map([
|
|
243
|
+
['features', features],
|
|
244
|
+
['memory', memory],
|
|
245
|
+
['positions', positions],
|
|
246
|
+
]),
|
|
247
|
+
);
|
|
248
|
+
return Float32Array.from(out.get('conditioned') as Float32Array);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async remember(
|
|
252
|
+
frame: number,
|
|
253
|
+
pending: readonly { readonly output: Sam2Output; readonly fromPrompt: boolean }[],
|
|
254
|
+
): Promise<void> {
|
|
255
|
+
if (pending.length === 0) return;
|
|
256
|
+
const { config } = this;
|
|
257
|
+
const binary = pending.some(({ fromPrompt }) => fromPrompt);
|
|
258
|
+
const features = (await this.features(frame)).get('features') as Float32Array;
|
|
259
|
+
const upscale = this.graph('upscale', NO_WEIGHTS, () => sam21Upscale(config, 1));
|
|
260
|
+
const encoder = this.graph(`memory.${binary}`, this.weights.memoryEncoder, () =>
|
|
261
|
+
sam21MemoryEncoder(config, binary),
|
|
262
|
+
);
|
|
263
|
+
const occlusion = this.tensor(
|
|
264
|
+
this.weights.memoryEncoder,
|
|
265
|
+
'occlusion_spatial_embedding_parameter',
|
|
266
|
+
);
|
|
267
|
+
const width = config.memory.dim;
|
|
268
|
+
for (const { output } of pending) {
|
|
269
|
+
const high = Float32Array.from(
|
|
270
|
+
(await this.run(upscale, new Map([['masks', output.lowRes]]))).get('high') as Float32Array,
|
|
271
|
+
);
|
|
272
|
+
if (binary)
|
|
273
|
+
for (let i = 0; i < high.length; i += 1) high[i] = (high[i] as number) > 0 ? 1 : 0;
|
|
274
|
+
const encoded = (
|
|
275
|
+
await this.run(
|
|
276
|
+
encoder,
|
|
277
|
+
new Map([
|
|
278
|
+
['features', features],
|
|
279
|
+
['mask', high],
|
|
280
|
+
]),
|
|
281
|
+
)
|
|
282
|
+
).get('memory') as Float32Array;
|
|
283
|
+
const cells = encoded.length / width;
|
|
284
|
+
const memory = new Float32Array(encoded.length);
|
|
285
|
+
for (let c = 0; c < width; c += 1) {
|
|
286
|
+
const add = output.score > 0 ? 0 : (occlusion[c] as number);
|
|
287
|
+
for (let i = 0; i < cells; i += 1)
|
|
288
|
+
memory[i * width + c] = Math.fround((encoded[c * cells + i] as number) + add);
|
|
289
|
+
}
|
|
290
|
+
toBfloat16(memory);
|
|
291
|
+
output.memory = memory;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private tensor(source: WeightSource, name: string): Float32Array {
|
|
296
|
+
const held = source.get(name);
|
|
297
|
+
if (held === undefined) throw new Error(`the tracker's weights have no "${name}"`);
|
|
298
|
+
return held.data;
|
|
299
|
+
}
|
|
300
|
+
}
|