File size: 5,206 Bytes
6adbbb1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/** Dense FP32 inference for the small Motion MLP. Feature construction is a separate layer. */
export class DenseNetwork {
  constructor(manifest, buffer) {
    if (!['superhq-dense-f32-v1', 'superhq-dense-f32-v2', 'superhq-dense-f32-v3'].includes(manifest.format) || manifest.byte_order !== 'little' ||
        !(buffer instanceof ArrayBuffer) || buffer.byteLength !== manifest.bytes || buffer.byteLength % 4) {
      throw new Error('Invalid dense network manifest or weight buffer.');
    }
    const bytes = new Uint8Array(new Uint32Array([1]).buffer);
    if (bytes[0] !== 1) throw new Error('This runtime requires a little-endian platform.');
    this.manifest = manifest;
    const weights = new Float32Array(buffer);
    if (!weights.every(Number.isFinite)) throw new Error('Nonfinite network weights.');
    let offset = 0;
    this.layers = manifest.layers.map((layer, i) => {
      const { inputs, outputs, weight_offset, bias_offset, activation } = layer;
      if (!Number.isInteger(inputs) || !Number.isInteger(outputs) || inputs < 1 || outputs < 1 ||
          weight_offset !== offset || bias_offset !== offset + inputs * outputs ||
          (i > 0 && inputs !== manifest.layers[i - 1].outputs) ||
          !['silu', 'identity'].includes(activation)) throw new Error('Invalid dense layer layout.');
      offset = bias_offset + outputs;
      if (offset > weights.length) throw new Error('Layer exceeds weight buffer.');
      return { inputs, outputs, activation, weight: weights.subarray(weight_offset, bias_offset),
        bias: weights.subarray(bias_offset, offset), output: new Float32Array(outputs) };
    });
    if (!this.layers.length || offset !== weights.length || offset !== manifest.parameters) {
      throw new Error('Weight count differs from manifest.');
    }
    this.outputSkip = manifest.output_skip ?? [];
    this.angularPrior = manifest.angular_prior;
    if ((manifest.format === 'superhq-dense-f32-v3') !== (this.angularPrior === 'articulated_constant_velocity_v1') ||
        (this.angularPrior && (this.layers[0].inputs !== 850 || this.layers.at(-1).outputs !== 144))) {
      throw new Error('Invalid angular prior contract.');
    }
    if (!Array.isArray(this.outputSkip) ||
        (manifest.format === 'superhq-dense-f32-v2' && !this.outputSkip.length) ||
        (manifest.format === 'superhq-dense-f32-v1' && this.outputSkip.length)) throw new Error('Invalid output skip layout.');
    for (const item of this.outputSkip) {
      if (!Number.isInteger(item.input) || item.input < 0 || item.input >= this.layers[0].inputs ||
          !Number.isInteger(item.output) || item.output < 0 || item.output >= this.layers.at(-1).outputs ||
          !Number.isFinite(item.scale)) throw new Error('Invalid output skip connection.');
    }
  }

  /** The returned scratch buffer is reused by the next call. Copy it when retaining a prediction. */
  forward(input) {
    if (!(input instanceof Float32Array) || input.length !== this.layers[0].inputs || !input.every(Number.isFinite)) {
      throw new Error('Expected a finite Float32Array of the network input width.');
    }
    let value = input;
    for (const layer of this.layers) {
      const { inputs, outputs, weight, bias, output, activation } = layer;
      for (let row = 0; row < outputs; row++) {
        let sum = bias[row];
        const start = row * inputs;
        for (let col = 0; col < inputs; col++) sum += weight[start + col] * value[col];
        output[row] = activation === 'silu' ? sum / (1 + Math.exp(-sum)) : sum;
      }
      if (!output.every(Number.isFinite)) throw new Error('Nonfinite network output.');
      value = output;
    }
    for (const item of this.outputSkip) value[item.output] += input[item.input] * item.scale;
    if (this.angularPrior) addAngularPrior(input, value);
    if (!value.every(Number.isFinite)) throw new Error('Nonfinite network output after skip connections.');
    return value;
  }
}

function rotation(input, offset) {
  const a = Array.from(input.subarray(offset, offset + 3));
  const b = Array.from(input.subarray(offset + 3, offset + 6));
  const na = Math.hypot(...a);
  if (na < 1e-6) throw new Error('Degenerate angular prior rotation.');
  const x = a.map(v => v / na), dot = x.reduce((s, v, i) => s + v * b[i], 0);
  const y = b.map((v, i) => v - dot * x[i]), nb = Math.hypot(...y);
  if (nb < 1e-6) throw new Error('Degenerate angular prior rotation.');
  for (let i = 0; i < 3; i++) y[i] /= nb;
  const z = [x[1]*y[2]-x[2]*y[1], x[2]*y[0]-x[0]*y[2], x[0]*y[1]-x[1]*y[0]];
  return [x, y, z]; // Columns, matching the Python six-dimensional representation.
}

function addAngularPrior(input, output) {
  for (let j = 0; j < 22; j++) {
    const previous = rotation(input, 276 + 6*j), current = rotation(input, 552 + 6*j);
    for (let col = 0; col < 2; col++) {
      // current * transpose(previous) * currentColumn
      const intermediate = previous.map(p => p.reduce((s, v, i) => s + v*current[col][i], 0));
      for (let row = 0; row < 3; row++) {
        const extrapolated = current.reduce((s, c, i) => s + c[row]*intermediate[i], 0);
        output[6*j + 3*col + row] += extrapolated-current[col][row];
      }
    }
  }
}