volvoxai 0.1.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 +21 -0
- package/README.md +145 -0
- package/bin/volvox.js +72 -0
- package/dist/v0.1.0/volvoxai.js +4664 -0
- package/dist/v0.1.0/volvoxai.min.js +1848 -0
- package/dist/v0.1.0/volvoxai.wasm +0 -0
- package/dist/volvoxai.js +4664 -0
- package/dist/volvoxai.min.js +1848 -0
- package/dist/volvoxai.wasm +0 -0
- package/docs/README.md +22 -0
- package/docs/browser-runtime.md +87 -0
- package/docs/efficientdet_tflite_vs_volvoxai.md +445 -0
- package/docs/microkernel_optimization_guide.md +153 -0
- package/docs/model-format.md +108 -0
- package/docs/models.md +103 -0
- package/docs/native-runtime.md +189 -0
- package/docs/operation_list.md +232 -0
- package/docs/operator_fusion_patterns.md +58 -0
- package/docs/quickstart.md +115 -0
- package/docs/roadmap.md +19 -0
- package/docs/testing.md +97 -0
- package/docs/textbook/01-foundations.md +233 -0
- package/docs/textbook/02-tinystories-language-model.md +300 -0
- package/docs/textbook/03-efficientdet-vision-model.md +281 -0
- package/docs/textbook/04-precision-and-quantization.md +208 -0
- package/docs/textbook/05-inside-the-engine.md +155 -0
- package/docs/textbook/06-native-engine-architecture.md +338 -0
- package/docs/textbook/07-glossary-and-next-steps.md +258 -0
- package/docs/textbook/README.md +85 -0
- package/docs/textbook/ko/01-foundations.md +231 -0
- package/docs/textbook/ko/02-tinystories-language-model.md +300 -0
- package/docs/textbook/ko/03-efficientdet-vision-model.md +277 -0
- package/docs/textbook/ko/04-precision-and-quantization.md +206 -0
- package/docs/textbook/ko/05-inside-the-engine.md +154 -0
- package/docs/textbook/ko/06-native-engine-architecture.md +333 -0
- package/docs/textbook/ko/07-glossary-and-next-steps.md +253 -0
- package/docs/textbook/ko/README.md +83 -0
- package/docs/xnnpack_optimization_guide.md +197 -0
- package/js/CPUEngine.js +241 -0
- package/js/Graph.js +49 -0
- package/js/GraphExecutor.js +1020 -0
- package/js/GraphLoader.js +282 -0
- package/js/ShaderLibrary.js +236 -0
- package/js/Tensor.js +25 -0
- package/js/Tokenizer.js +266 -0
- package/js/VolvoxAI.js +130 -0
- package/js/WasmEngine.js +378 -0
- package/js/WebNNEngine.js +169 -0
- package/js/index.js +11 -0
- package/js/ops/add.js +31 -0
- package/js/ops/argMax.js +33 -0
- package/js/ops/averagePool2D.js +38 -0
- package/js/ops/batchNorm2D.js +28 -0
- package/js/ops/cast.js +19 -0
- package/js/ops/clip.js +15 -0
- package/js/ops/concat2.js +18 -0
- package/js/ops/conv1D.js +35 -0
- package/js/ops/conv2D.js +70 -0
- package/js/ops/convTranspose2D.js +45 -0
- package/js/ops/crossAttention.js +69 -0
- package/js/ops/crossSDPA.js +41 -0
- package/js/ops/dequantizeLinear.js +9 -0
- package/js/ops/div.js +15 -0
- package/js/ops/embedding.js +14 -0
- package/js/ops/expand.js +24 -0
- package/js/ops/gELU.js +9 -0
- package/js/ops/gather.js +51 -0
- package/js/ops/gatherElements.js +33 -0
- package/js/ops/globalAveragePool.js +21 -0
- package/js/ops/hardSigmoid.js +12 -0
- package/js/ops/hardSwish.js +12 -0
- package/js/ops/interp1D.js +25 -0
- package/js/ops/layerNorm.js +25 -0
- package/js/ops/leakyReLU.js +10 -0
- package/js/ops/logSoftmax.js +15 -0
- package/js/ops/matMul.js +35 -0
- package/js/ops/maxPool2D.js +36 -0
- package/js/ops/meanHeight.js +17 -0
- package/js/ops/mul.js +31 -0
- package/js/ops/nonMaxSuppression.js +72 -0
- package/js/ops/pReLU.js +11 -0
- package/js/ops/pad.js +35 -0
- package/js/ops/profileX.js +22 -0
- package/js/ops/profileY.js +22 -0
- package/js/ops/rMSNorm.js +14 -0
- package/js/ops/reLU.js +8 -0
- package/js/ops/reduceMean.js +17 -0
- package/js/ops/reduceSum.js +19 -0
- package/js/ops/reshape.js +6 -0
- package/js/ops/resize.js +44 -0
- package/js/ops/sDPA.js +44 -0
- package/js/ops/siLU.js +8 -0
- package/js/ops/sigmoid.js +6 -0
- package/js/ops/slice.js +36 -0
- package/js/ops/softmax.js +18 -0
- package/js/ops/spatialSoftargmaxY.js +28 -0
- package/js/ops/split.js +24 -0
- package/js/ops/sub.js +11 -0
- package/js/ops/tanh.js +7 -0
- package/js/ops/transpose.js +34 -0
- package/js/ops/upsample2x.js +23 -0
- package/js/ops/where.js +15 -0
- package/package.json +33 -0
- package/shaders/add.wgsl +13 -0
- package/shaders/add3Relu.wgsl +23 -0
- package/shaders/addRelu.wgsl +22 -0
- package/shaders/averagePool2D.wgsl +24 -0
- package/shaders/batchNorm2D.wgsl +21 -0
- package/shaders/binaryBroadcast.wgsl +34 -0
- package/shaders/broadcastBinary.wgsl +26 -0
- package/shaders/clip.wgsl +10 -0
- package/shaders/concat2.wgsl +16 -0
- package/shaders/concatCopy.wgsl +10 -0
- package/shaders/concatSigmoidCopy.wgsl +16 -0
- package/shaders/conv1D.wgsl +37 -0
- package/shaders/conv2D.wgsl +80 -0
- package/shaders/conv2DDepthwise4.wgsl +74 -0
- package/shaders/conv2DDepthwise8.wgsl +66 -0
- package/shaders/conv2DPointwise16.wgsl +67 -0
- package/shaders/conv2DPointwise16Tile.wgsl +86 -0
- package/shaders/conv2DPointwise8.wgsl +85 -0
- package/shaders/conv2DPointwise8Vec2.wgsl +70 -0
- package/shaders/conv2DPointwise8Vec4.wgsl +65 -0
- package/shaders/conv2DRegularC3Out16.wgsl +75 -0
- package/shaders/convTranspose2D.wgsl +33 -0
- package/shaders/copy.wgsl +13 -0
- package/shaders/crossAttention.wgsl +140 -0
- package/shaders/crossAttentionF32.wgsl +98 -0
- package/shaders/crossSDPA.wgsl +74 -0
- package/shaders/dequantizeLinear.wgsl +14 -0
- package/shaders/div.wgsl +34 -0
- package/shaders/elementwise.wgsl +13 -0
- package/shaders/embedding.wgsl +22 -0
- package/shaders/expand.wgsl +18 -0
- package/shaders/gELU.wgsl +13 -0
- package/shaders/gather.wgsl +17 -0
- package/shaders/generalTranspose.wgsl +19 -0
- package/shaders/globalAveragePool.wgsl +19 -0
- package/shaders/hardSigmoid.wgsl +13 -0
- package/shaders/hardSwish.wgsl +13 -0
- package/shaders/interp1D.wgsl +28 -0
- package/shaders/layerNorm.wgsl +33 -0
- package/shaders/leakyReLU.wgsl +11 -0
- package/shaders/linearF32.wgsl +33 -0
- package/shaders/linearF32RowMajor.wgsl +24 -0
- package/shaders/linearInt8.wgsl +42 -0
- package/shaders/logSoftmax.wgsl +22 -0
- package/shaders/maxPool2D.wgsl +37 -0
- package/shaders/meanHeight.wgsl +18 -0
- package/shaders/mul.wgsl +32 -0
- package/shaders/nonMaxSuppression.wgsl +92 -0
- package/shaders/pReLU.wgsl +14 -0
- package/shaders/pad.wgsl +19 -0
- package/shaders/profileX.wgsl +28 -0
- package/shaders/profileY.wgsl +28 -0
- package/shaders/quantizeLinear.wgsl +69 -0
- package/shaders/rMSNorm.wgsl +21 -0
- package/shaders/reLU.wgsl +13 -0
- package/shaders/reduce.wgsl +17 -0
- package/shaders/resize.wgsl +52 -0
- package/shaders/sDPA.wgsl +71 -0
- package/shaders/siLU.wgsl +13 -0
- package/shaders/sigmoid.wgsl +13 -0
- package/shaders/slice.wgsl +26 -0
- package/shaders/softmax.wgsl +23 -0
- package/shaders/spatialSoftargmaxY.wgsl +32 -0
- package/shaders/split.wgsl +15 -0
- package/shaders/sub.wgsl +34 -0
- package/shaders/tanh.wgsl +13 -0
- package/shaders/upsample2x.wgsl +24 -0
- package/shaders/where.wgsl +12 -0
- package/volvoxai.wasm +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Microkernel Optimization Guide
|
|
2
|
+
|
|
3
|
+
This document outlines the architectural roadmap and concrete code-level strategies for
|
|
4
|
+
raising VolvoxAI's native CPU inference performance across INT4, INT8, FP16, and FP32 data
|
|
5
|
+
types.
|
|
6
|
+
|
|
7
|
+
## 1. Architectural Shift
|
|
8
|
+
|
|
9
|
+
VolvoxAI should continue moving from generic `Conv2D` and `QConv2D` loops toward a
|
|
10
|
+
microkernel architecture:
|
|
11
|
+
|
|
12
|
+
1. **NHWC layout**: keep activations in NHWC format so channels are contiguous in memory.
|
|
13
|
+
2. **Indirection buffers**: remove padding and bounds checks from inner loops.
|
|
14
|
+
3. **Weight packing**: arrange weights to match the target SIMD register width and kernel
|
|
15
|
+
tile shape.
|
|
16
|
+
4. **Register tiling**: hardcode loop unrolling around the target register file, such as
|
|
17
|
+
computing multiple pixels and output channels per kernel call.
|
|
18
|
+
|
|
19
|
+
## 2. Indirection Buffers
|
|
20
|
+
|
|
21
|
+
Branching inside the MAC loop for padding stalls the CPU pipeline. Precompute an array of
|
|
22
|
+
input pointers instead. Padded areas point to a zero buffer, so the microkernel can do
|
|
23
|
+
pointer dereferences and math without bounds checks.
|
|
24
|
+
|
|
25
|
+
```c
|
|
26
|
+
static const int8_t zero_buffer[MAX_CHANNELS] = {0};
|
|
27
|
+
|
|
28
|
+
const int8_t** indirection_buffer =
|
|
29
|
+
malloc(output_height * output_width * kernel_size * sizeof(int8_t*));
|
|
30
|
+
int idx = 0;
|
|
31
|
+
for (int oy = 0; oy < output_height; oy++) {
|
|
32
|
+
for (int ox = 0; ox < output_width; ox++) {
|
|
33
|
+
for (int ky = 0; ky < kh; ky++) {
|
|
34
|
+
for (int kx = 0; kx < kw; kx++) {
|
|
35
|
+
int iy = oy * stride - pad_top + ky;
|
|
36
|
+
int ix = ox * stride - pad_left + kx;
|
|
37
|
+
if (iy >= 0 && iy < input_height && ix >= 0 && ix < input_width) {
|
|
38
|
+
indirection_buffer[idx++] =
|
|
39
|
+
input_image + (iy * input_width + ix) * channels;
|
|
40
|
+
} else {
|
|
41
|
+
indirection_buffer[idx++] = zero_buffer;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 3. Data Type Strategies
|
|
50
|
+
|
|
51
|
+
### INT8
|
|
52
|
+
|
|
53
|
+
Use dot-product instructions where available. ARM provides `sdot`, while newer Intel and
|
|
54
|
+
AMD CPUs provide VNNI-family instructions. Pack weights into blocks that align with the
|
|
55
|
+
dot-product width, such as `[OC/8, IC/4, 8, 4]`.
|
|
56
|
+
|
|
57
|
+
```c
|
|
58
|
+
#include <arm_neon.h>
|
|
59
|
+
|
|
60
|
+
void microkernel_qconv2d_int8_neon_sdot(
|
|
61
|
+
const int8_t** input_pointers,
|
|
62
|
+
const int8_t* packed_weights,
|
|
63
|
+
int32_t* output_accumulators,
|
|
64
|
+
int kernel_elements,
|
|
65
|
+
int channels)
|
|
66
|
+
{
|
|
67
|
+
int32x4_t acc0 = vld1q_s32(output_accumulators + 0);
|
|
68
|
+
int32x4_t acc1 = vld1q_s32(output_accumulators + 4);
|
|
69
|
+
|
|
70
|
+
for (int k = 0; k < kernel_elements; k++) {
|
|
71
|
+
const int8_t* in_ptr = input_pointers[k];
|
|
72
|
+
for (int c = 0; c < channels; c += 4) {
|
|
73
|
+
int8x8_t in_val = vld1_s8(in_ptr + c);
|
|
74
|
+
int8x16_t in_dup = vcombine_s8(in_val, in_val);
|
|
75
|
+
int8x16_t w_val = vld1q_s8(packed_weights);
|
|
76
|
+
packed_weights += 16;
|
|
77
|
+
acc0 = vdotq_s32(acc0, in_dup, w_val);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### INT4
|
|
84
|
+
|
|
85
|
+
INT4 cuts memory bandwidth in half compared to INT8, which is useful for bandwidth-bound
|
|
86
|
+
LLM weights or heavily quantized vision models. Most CPUs lack native INT4 MAC
|
|
87
|
+
instructions, so the practical path is to unpack INT4 to INT8 in registers and feed the
|
|
88
|
+
INT8 kernel.
|
|
89
|
+
|
|
90
|
+
```c
|
|
91
|
+
uint8x16_t w_int4 = vld1q_u8(packed_int4_weights);
|
|
92
|
+
uint8x16_t mask = vdupq_n_u8(0x0F);
|
|
93
|
+
uint8x16_t w_lo = vandq_u8(w_int4, mask);
|
|
94
|
+
uint8x16_t w_hi = vshrq_n_u8(w_int4, 4);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### FP16
|
|
98
|
+
|
|
99
|
+
Native FP16 execution reduces memory bandwidth and cache footprint on hardware with fast
|
|
100
|
+
half-precision arithmetic, such as ARMv8.2-A FP16 or AVX512-FP16.
|
|
101
|
+
|
|
102
|
+
```c
|
|
103
|
+
#include <arm_neon.h>
|
|
104
|
+
|
|
105
|
+
void microkernel_conv2d_fp16_neon(
|
|
106
|
+
const float16_t* input,
|
|
107
|
+
const float16_t* packed_weights,
|
|
108
|
+
float16_t* output)
|
|
109
|
+
{
|
|
110
|
+
float16x8_t acc = vdupq_n_f16(0.0f);
|
|
111
|
+
float16x8_t in_val = vdupq_n_f16(input[0]);
|
|
112
|
+
float16x8_t w_val = vld1q_f16(packed_weights);
|
|
113
|
+
acc = vfmaq_f16(acc, in_val, w_val);
|
|
114
|
+
vst1q_f16(output, acc);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### FP32
|
|
119
|
+
|
|
120
|
+
FP32 should use register tiling to hide latency. On AVX2, one useful pointwise Conv shape is
|
|
121
|
+
six spatial pixels by 16 output channels, using broadcast input values and packed weights.
|
|
122
|
+
|
|
123
|
+
```c
|
|
124
|
+
#include <immintrin.h>
|
|
125
|
+
|
|
126
|
+
void microkernel_conv2d_fp32_avx2(
|
|
127
|
+
const float* in_p0,
|
|
128
|
+
const float* in_p1,
|
|
129
|
+
const float* packed_weights,
|
|
130
|
+
float* out_p0,
|
|
131
|
+
float* out_p1)
|
|
132
|
+
{
|
|
133
|
+
__m256 acc0 = _mm256_setzero_ps();
|
|
134
|
+
__m256 acc1 = _mm256_setzero_ps();
|
|
135
|
+
|
|
136
|
+
for (int ic = 0; ic < input_channels; ic++) {
|
|
137
|
+
__m256 w0 = _mm256_loadu_ps(packed_weights + ic * 8);
|
|
138
|
+
acc0 = _mm256_fmadd_ps(_mm256_set1_ps(in_p0[ic]), w0, acc0);
|
|
139
|
+
acc1 = _mm256_fmadd_ps(_mm256_set1_ps(in_p1[ic]), w0, acc1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_mm256_storeu_ps(out_p0, acc0);
|
|
143
|
+
_mm256_storeu_ps(out_p1, acc1);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Action Plan
|
|
148
|
+
|
|
149
|
+
1. Add reusable indirection-buffer builders during graph initialization.
|
|
150
|
+
2. Extend weight-packing caches so each hot op has a layout matched to its microkernel.
|
|
151
|
+
3. Replace generic inner loops in `quant_cpu_opt.c` with CPU-feature-routed microkernels.
|
|
152
|
+
4. Keep the activation arena planner enabled for transient tensors with non-overlapping
|
|
153
|
+
lifetimes.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Model Format
|
|
2
|
+
|
|
3
|
+
VolvoxAI loads inference packages made of:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
config.json
|
|
7
|
+
model.safetensors
|
|
8
|
+
optional tokenizer/assets, such as vocab.bin, merges.txt, labels.txt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Large `.safetensors` files and generated model directories should stay out of git.
|
|
12
|
+
|
|
13
|
+
## Graph Model
|
|
14
|
+
|
|
15
|
+
A model graph contains:
|
|
16
|
+
|
|
17
|
+
- `Tensor`: name, shape, dtype, and `isWeight`.
|
|
18
|
+
- `Node`: `opType`, named `inputs`, named `outputs`, and `params`.
|
|
19
|
+
- `graph.outputNames`: output tensor names returned by WASM/CPU.
|
|
20
|
+
|
|
21
|
+
`GraphLoader.js` builds this graph from `config.json` and `model.safetensors`.
|
|
22
|
+
Applications can also construct graphs programmatically with `addInput`,
|
|
23
|
+
`addWeight`, and `addOp`.
|
|
24
|
+
|
|
25
|
+
## Volvox Blueprint
|
|
26
|
+
|
|
27
|
+
The primary format is a precomputed Volvox blueprint:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"inputs": {
|
|
32
|
+
"image": { "shape": [1, 320, 320, 3], "dtype": "float32" }
|
|
33
|
+
},
|
|
34
|
+
"nodes": [
|
|
35
|
+
{
|
|
36
|
+
"opType": "Conv2D",
|
|
37
|
+
"inputs": { "input": "image", "weight": "stem.weight", "bias": "stem.bias" },
|
|
38
|
+
"outputs": { "out": "stem.out" },
|
|
39
|
+
"outputs_shape": { "out": [1, 160, 160, 32] },
|
|
40
|
+
"params": { "stride": [2, 2], "padding": [1, 1], "weight_layout": "OHWI" }
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Exporters map source-model ops to Volvox `opType`s, precompute tensor shapes, and
|
|
47
|
+
write the weights into safetensors. The browser then only maps memory and executes.
|
|
48
|
+
|
|
49
|
+
## Hugging Face Config Path
|
|
50
|
+
|
|
51
|
+
If `config.json` has `model_type` but no topological `nodes`, `GraphLoader` checks
|
|
52
|
+
`GraphLoader.ModelBuilders`. A model family can register a builder:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
GraphLoader.ModelBuilders.llama = (graph, config, tensors) => {
|
|
56
|
+
// call graph.addOp(...) to assemble the model
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Safetensors Loading
|
|
61
|
+
|
|
62
|
+
VolvoxAI reads standard safetensors weights. Tensor metadata provides dtype, shape,
|
|
63
|
+
and byte offsets. Graph topology can also be embedded in safetensors metadata, but
|
|
64
|
+
the normal package layout keeps topology in `config.json`.
|
|
65
|
+
|
|
66
|
+
## Layout Policy
|
|
67
|
+
|
|
68
|
+
Runtime image tensors use NHWC:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
[batch, height, width, channels]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Browser Conv2D kernels use HWIO/HWCM internally. Native TFLite exports may keep
|
|
75
|
+
TFLite-native layouts such as `OHWI` for regular conv and `1HWO` for depthwise conv;
|
|
76
|
+
the native engine prepares HWIO/HWCM compute caches at load time.
|
|
77
|
+
|
|
78
|
+
## Precision Policy
|
|
79
|
+
|
|
80
|
+
VolvoxAI focuses on FP32 activations/intermediates and INT8-packed or quantized
|
|
81
|
+
weights.
|
|
82
|
+
|
|
83
|
+
- FP32 is the portable activation format across WebGPU, WASM, CPU, and native paths.
|
|
84
|
+
- INT8 reduces weight bandwidth and storage while avoiding the bit-unpacking cost of
|
|
85
|
+
INT4 on low-end GPUs.
|
|
86
|
+
- FP16 weights can be kept on disk for exported vision models; runtime paths widen or
|
|
87
|
+
handle them according to backend support.
|
|
88
|
+
|
|
89
|
+
INT8 handling:
|
|
90
|
+
|
|
91
|
+
- MatMul keeps an INT8 plus per-channel-scale fast path in browser shaders and native
|
|
92
|
+
kernels.
|
|
93
|
+
- Browser Conv2D/Conv1D dequantizes quantized weights to FP32 at load time.
|
|
94
|
+
- Native CPU can keep quantized Conv/Add islands in int8 via `native/quant_cpu_opt.c`.
|
|
95
|
+
- Native Vulkan/OpenGL graph paths are FP32-only, so `QConv2D` stays on CPU.
|
|
96
|
+
|
|
97
|
+
## LLM Scope
|
|
98
|
+
|
|
99
|
+
VolvoxAI includes the core ops used by small GPT-style models: `Embedding`,
|
|
100
|
+
`LayerNorm`, `MatMul`, `SDPA`, and `GELU`. The native CLI includes TinyStories
|
|
101
|
+
generation with prefill/decode KV-cache orchestration.
|
|
102
|
+
|
|
103
|
+
It is not a frontier LLM runtime today:
|
|
104
|
+
|
|
105
|
+
- No INT4 weight format.
|
|
106
|
+
- Browser text generation currently needs application-side autoregressive loops.
|
|
107
|
+
- Native generation is ahead of browser generation helpers.
|
|
108
|
+
|
package/docs/models.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Models
|
|
2
|
+
|
|
3
|
+
The `models/` directory is ignored because generated model packages and source
|
|
4
|
+
weights are large. Regenerate examples from public sources when needed.
|
|
5
|
+
|
|
6
|
+
## Dependencies
|
|
7
|
+
|
|
8
|
+
Model export is development-only and runs on the host, not in the Docker build image.
|
|
9
|
+
Install exporter dependencies once:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
make models_deps
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This installs `tools/requirements-export.txt`, including numpy, flatbuffers,
|
|
16
|
+
safetensors, torch, and transformers.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | What it does |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `make models` | Download and export EfficientDet Lite0 fp32/fp16/int8 plus TinyStories-1M. |
|
|
23
|
+
| `make models_efficientdet` | Export EfficientDet Lite0 packages from MediaPipe TFLite models. |
|
|
24
|
+
| `make models_tinystories` | Export `roneneldan/TinyStories-1M` plus tokenizer assets. |
|
|
25
|
+
| `make models_clean` | Remove regenerated example model directories. |
|
|
26
|
+
|
|
27
|
+
`tools/fetch_models.sh` accepts `efficientdet`, `tinystories`, or `all`. For
|
|
28
|
+
EfficientDet, set `ONLY=int8`, `ONLY=float16`, or `ONLY=float32` to fetch one
|
|
29
|
+
precision.
|
|
30
|
+
|
|
31
|
+
## EfficientDet Lite0
|
|
32
|
+
|
|
33
|
+
EfficientDet packages contain:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
config.json
|
|
37
|
+
model.safetensors
|
|
38
|
+
labels.txt
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The source TFLite models come from Google's MediaPipe model store, for example:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/int8/latest/efficientdet_lite0.tflite
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The exported package has an NHWC image input and named raw outputs:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
input0: [1, 320, 320, 3] raw RGB 0..255
|
|
51
|
+
scores: [1, 19206, 90]
|
|
52
|
+
boxes: [1, 19206, 4]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The COCO label file keeps the 90-slot class-id alignment. `???` entries are unused
|
|
56
|
+
placeholders and should not be deleted.
|
|
57
|
+
|
|
58
|
+
## TinyStories
|
|
59
|
+
|
|
60
|
+
TinyStories exports:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
config.json
|
|
64
|
+
model.safetensors
|
|
65
|
+
vocab.bin
|
|
66
|
+
merges.txt
|
|
67
|
+
tokens.i32
|
|
68
|
+
positions.i32
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The native runtime can use the package directly:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
./native/volvoxai generate models/tinystories_1m \
|
|
75
|
+
--prompt "Once upon a time, Lily" \
|
|
76
|
+
--max-new 50
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## TinyReceiptKIE
|
|
80
|
+
|
|
81
|
+
Custom TinyReceiptKIE checkpoints can be exported into the same package layout:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python3 tools/export_kie_safetensors.py \
|
|
85
|
+
--checkpoint checkpoint.pt \
|
|
86
|
+
--out models/tiny_receipt_kie
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then run it through the native chat alias:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
./native/volvoxai chat models/tiny_receipt_kie \
|
|
93
|
+
--image image=receipt.jpg \
|
|
94
|
+
--prompt "What is the first number of the store's phone number?" \
|
|
95
|
+
--family auto \
|
|
96
|
+
--max-new 128
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The package declares the `tiny_receipt_kie` chat interface in `config.json` and keeps
|
|
100
|
+
weights in `model.safetensors`. LoRA is folded into dense linear weights during
|
|
101
|
+
export. If the checkpoint is a base model plus components, add `--lora lora.pt` and
|
|
102
|
+
repeat `--adapter family=adapter_family.pt` for each adapter family.
|
|
103
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Native Runtime
|
|
2
|
+
|
|
3
|
+
`native/` is a freestanding C runtime that runs the same Volvox blueprint package as
|
|
4
|
+
the browser runtime: `config.json` plus `.safetensors`. It has no static GPU SDK
|
|
5
|
+
dependency. Vulkan, OpenGL, Metal, and NNAPI are loaded dynamically when requested.
|
|
6
|
+
|
|
7
|
+
## Build
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
make build_native
|
|
11
|
+
./native/volvoxai --version
|
|
12
|
+
./native/volvoxai --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The Docker build compiles the native binary with clang, AVX2/FMA on x86, pthreads,
|
|
16
|
+
and the CPU/GPU backend sources. The binary embeds package version, git commit,
|
|
17
|
+
and UTC build date from the Makefile build.
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
Key files:
|
|
22
|
+
|
|
23
|
+
- `engine.c`: parses the blueprint, builds tensors/nodes, owns the global engine context.
|
|
24
|
+
- `engine_runtime.c`: dispatches each node to CPU, Vulkan, OpenGL, Metal, or NNAPI.
|
|
25
|
+
- `graph_opt_fusion.c`: compile-time fusion pass for patterns such as Conv+ReLU6,
|
|
26
|
+
chained Add, depthwise-to-pointwise, concat+sigmoid, and alias elision.
|
|
27
|
+
- `conv_f32_opt.c`: optimized FP32 Conv2D with weight prepacking and IGEMM.
|
|
28
|
+
- `quant_cpu_opt.c`: INT8 quantized CPU path for `QConv2D`, quantized Add, and
|
|
29
|
+
quantization helpers.
|
|
30
|
+
- `kernels.c`: shared portable kernels used by both native and wasm32.
|
|
31
|
+
- `image_io.c`: PNG/JPEG decode through `stb_image.h` into NHWC tensors.
|
|
32
|
+
- `tokenizer.c` and `kie_runtime.c`: native BPE tokenizer and receipt KIE task runtime.
|
|
33
|
+
|
|
34
|
+
## Raw Tensor Runner
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
./native/volvoxai run models/tinystories_1m \
|
|
38
|
+
--input tokens=models/tinystories_1m/tokens.i32 \
|
|
39
|
+
--input positions=models/tinystories_1m/positions.i32 \
|
|
40
|
+
--output logits=out.f32 \
|
|
41
|
+
--last-token 4 \
|
|
42
|
+
--debug
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Image-shaped inputs can be decoded directly:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
./native/volvoxai run models/vision_model \
|
|
49
|
+
--image image=receipt.png \
|
|
50
|
+
--image-normalize zero-one \
|
|
51
|
+
--output logits=out.f32
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`--image-normalize` supports `zero-one`, `minus-one-one`, and `raw-255`.
|
|
55
|
+
Video, audio, camera, and streaming input should be handled by a frontend that feeds
|
|
56
|
+
decoded tensors to `run`.
|
|
57
|
+
|
|
58
|
+
## Task Wrappers
|
|
59
|
+
|
|
60
|
+
Task wrappers sit on top of the tensor runner:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
./native/volvoxai classify models/classifier \
|
|
64
|
+
--image image=photo.jpg \
|
|
65
|
+
--logits logits \
|
|
66
|
+
--labels labels.txt \
|
|
67
|
+
--top-k 5
|
|
68
|
+
|
|
69
|
+
./native/volvoxai detect models/detector \
|
|
70
|
+
--image image=receipt.jpg \
|
|
71
|
+
--boxes boxes \
|
|
72
|
+
--scores scores \
|
|
73
|
+
--classes classes \
|
|
74
|
+
--max-det 20
|
|
75
|
+
|
|
76
|
+
./native/volvoxai ctc models/ocr_line \
|
|
77
|
+
--image image=line.png \
|
|
78
|
+
--logits logits \
|
|
79
|
+
--labels labels.txt \
|
|
80
|
+
--blank 0
|
|
81
|
+
|
|
82
|
+
./native/volvoxai seq2seq models/encoder_decoder \
|
|
83
|
+
--image image=receipt.jpg \
|
|
84
|
+
--prompt "What is the first number of the store phone?" \
|
|
85
|
+
--prompt-input q_tokens \
|
|
86
|
+
--decoder-input y_tokens \
|
|
87
|
+
--logits logits \
|
|
88
|
+
--max-new 192
|
|
89
|
+
|
|
90
|
+
./native/volvoxai chat models/multimodal_chat \
|
|
91
|
+
--prompt "Summarize this receipt" \
|
|
92
|
+
--image image=receipt.jpg
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`classify` ranks logits, `ctc` performs greedy CTC collapse, and `detect` prints or
|
|
96
|
+
writes named raw tensors because detector output layouts vary by exporter. `seq2seq`
|
|
97
|
+
runs a greedy encoder-decoder loop. `chat` is a user-facing alias for multimodal
|
|
98
|
+
seq2seq packages.
|
|
99
|
+
|
|
100
|
+
## Generation
|
|
101
|
+
|
|
102
|
+
TinyStories generation uses the same loaded graph repeatedly:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
./native/volvoxai generate models/tinystories_1m \
|
|
106
|
+
--prompt "Once upon a time, Lily" \
|
|
107
|
+
--max-new 50 \
|
|
108
|
+
--debug
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Weights and graph load once. Generation updates token inputs and reruns the node loop.
|
|
112
|
+
For language models, `--vulkan` uses the MatMul-focused path: large MatMul/Gemm/Linear
|
|
113
|
+
nodes run through the `linearF32` shader, while small decode-time MatMuls stay on CPU
|
|
114
|
+
to avoid dispatch overhead.
|
|
115
|
+
|
|
116
|
+
## Native GPU and NPU Backends
|
|
117
|
+
|
|
118
|
+
Driver loading is resolved at runtime:
|
|
119
|
+
|
|
120
|
+
- Vulkan (`--vulkan`): `libvulkan.so.1`, `libvulkan.so`, or `vulkan-1.dll`.
|
|
121
|
+
- OpenGL (`--opengl`): `libGL.so.1`, `opengl32.dll`, or macOS `OpenGL.framework`.
|
|
122
|
+
- Metal (`--metal`, macOS): default `MTLDevice` through the Objective-C runtime.
|
|
123
|
+
- NNAPI (`--nnapi`, Android build with `-DUSE_NNAPI`): Android Neural Networks API
|
|
124
|
+
for large MatMul/FullyConnected offload.
|
|
125
|
+
|
|
126
|
+
If multiple accelerator flags are passed, priority is `--nnapi`, then `--vulkan`,
|
|
127
|
+
then `--metal`, then `--opengl`.
|
|
128
|
+
|
|
129
|
+
EfficientDet-style vision graphs have graph-resident Vulkan and OpenGL paths for
|
|
130
|
+
fused Conv2D/ReLU6, MaxPool2D, same-size Add, Clip, Sigmoid, 2x nearest upsample,
|
|
131
|
+
Concat, and reshape/identity aliasing. The native GPU graph path is FP32-only;
|
|
132
|
+
quantized `QConv2D` runs on CPU.
|
|
133
|
+
|
|
134
|
+
On software Vulkan/OpenGL stacks, dispatch synchronization can be slower than the
|
|
135
|
+
AVX2 CPU path. The GPU win is on real mobile or desktop GPUs.
|
|
136
|
+
|
|
137
|
+
## Benchmark Flags
|
|
138
|
+
|
|
139
|
+
For TFLite-style timing:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
./native/volvoxai detect models/efficientdet_lite0_int8 \
|
|
143
|
+
--image input0=photo.jpg \
|
|
144
|
+
--image-normalize raw-255 \
|
|
145
|
+
--boxes boxes \
|
|
146
|
+
--scores scores \
|
|
147
|
+
--max-det 5 \
|
|
148
|
+
--num_threads 4 \
|
|
149
|
+
--warmup_runs 5 \
|
|
150
|
+
--num_runs 20
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`--num_threads` maps to `VOLVOX_NUM_THREADS`. `--debug` prints graph load, build,
|
|
154
|
+
node backend, and per-op timing logs.
|
|
155
|
+
|
|
156
|
+
See [efficientdet_tflite_vs_volvoxai.md](efficientdet_tflite_vs_volvoxai.md) for
|
|
157
|
+
the current EfficientDet benchmark methodology and results.
|
|
158
|
+
|
|
159
|
+
## Android Cross-Compile
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
NDK=$ANDROID_SDK/ndk/<version>
|
|
163
|
+
CC=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang
|
|
164
|
+
cd native && "$CC" -O3 -DUSE_NNAPI -pthread -Wno-unknown-attributes -I. \
|
|
165
|
+
cJSON.c safetensors.c kernels.c quant_cpu_opt.c conv_f32_opt.c tensor_f32_opt.c \
|
|
166
|
+
engine_runtime.c engine.c image_io.c kie_runtime.c \
|
|
167
|
+
vulkan_engine.c opengl_engine.c tokenizer.c nnapi_engine.c main.c \
|
|
168
|
+
-o volvoxai_android -lm -ldl -lneuralnetworks
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Then push the binary, model directory, and native shader outputs to the device:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
adb push volvoxai_android /data/local/tmp/volvoxai
|
|
175
|
+
adb shell "cd /data/local/tmp && VOLVOX_NUM_THREADS=2 ./volvoxai run models/tinystories_1m \
|
|
176
|
+
--input tokens=models/tinystories_1m/tokens.i32 \
|
|
177
|
+
--input positions=models/tinystories_1m/positions.i32 \
|
|
178
|
+
--output logits=out.f32 --vulkan --debug"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
ARM/NEON builds default to two CPU threads. Use one thread for stable profiling and
|
|
182
|
+
test two threads for throughput; using all cores is often slower because of big/little
|
|
183
|
+
scheduling and thermal limits.
|
|
184
|
+
|
|
185
|
+
## Service Runtime
|
|
186
|
+
|
|
187
|
+
The Rust crate under `runtime/` builds a `libvolvoxai.so` Synurang service wrapper
|
|
188
|
+
around the C engine. See [../runtime/README.md](../runtime/README.md) for the FFI
|
|
189
|
+
service ABI, build commands, and current RPC coverage.
|