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.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/bin/volvox.js +72 -0
  4. package/dist/v0.1.0/volvoxai.js +4664 -0
  5. package/dist/v0.1.0/volvoxai.min.js +1848 -0
  6. package/dist/v0.1.0/volvoxai.wasm +0 -0
  7. package/dist/volvoxai.js +4664 -0
  8. package/dist/volvoxai.min.js +1848 -0
  9. package/dist/volvoxai.wasm +0 -0
  10. package/docs/README.md +22 -0
  11. package/docs/browser-runtime.md +87 -0
  12. package/docs/efficientdet_tflite_vs_volvoxai.md +445 -0
  13. package/docs/microkernel_optimization_guide.md +153 -0
  14. package/docs/model-format.md +108 -0
  15. package/docs/models.md +103 -0
  16. package/docs/native-runtime.md +189 -0
  17. package/docs/operation_list.md +232 -0
  18. package/docs/operator_fusion_patterns.md +58 -0
  19. package/docs/quickstart.md +115 -0
  20. package/docs/roadmap.md +19 -0
  21. package/docs/testing.md +97 -0
  22. package/docs/textbook/01-foundations.md +233 -0
  23. package/docs/textbook/02-tinystories-language-model.md +300 -0
  24. package/docs/textbook/03-efficientdet-vision-model.md +281 -0
  25. package/docs/textbook/04-precision-and-quantization.md +208 -0
  26. package/docs/textbook/05-inside-the-engine.md +155 -0
  27. package/docs/textbook/06-native-engine-architecture.md +338 -0
  28. package/docs/textbook/07-glossary-and-next-steps.md +258 -0
  29. package/docs/textbook/README.md +85 -0
  30. package/docs/textbook/ko/01-foundations.md +231 -0
  31. package/docs/textbook/ko/02-tinystories-language-model.md +300 -0
  32. package/docs/textbook/ko/03-efficientdet-vision-model.md +277 -0
  33. package/docs/textbook/ko/04-precision-and-quantization.md +206 -0
  34. package/docs/textbook/ko/05-inside-the-engine.md +154 -0
  35. package/docs/textbook/ko/06-native-engine-architecture.md +333 -0
  36. package/docs/textbook/ko/07-glossary-and-next-steps.md +253 -0
  37. package/docs/textbook/ko/README.md +83 -0
  38. package/docs/xnnpack_optimization_guide.md +197 -0
  39. package/js/CPUEngine.js +241 -0
  40. package/js/Graph.js +49 -0
  41. package/js/GraphExecutor.js +1020 -0
  42. package/js/GraphLoader.js +282 -0
  43. package/js/ShaderLibrary.js +236 -0
  44. package/js/Tensor.js +25 -0
  45. package/js/Tokenizer.js +266 -0
  46. package/js/VolvoxAI.js +130 -0
  47. package/js/WasmEngine.js +378 -0
  48. package/js/WebNNEngine.js +169 -0
  49. package/js/index.js +11 -0
  50. package/js/ops/add.js +31 -0
  51. package/js/ops/argMax.js +33 -0
  52. package/js/ops/averagePool2D.js +38 -0
  53. package/js/ops/batchNorm2D.js +28 -0
  54. package/js/ops/cast.js +19 -0
  55. package/js/ops/clip.js +15 -0
  56. package/js/ops/concat2.js +18 -0
  57. package/js/ops/conv1D.js +35 -0
  58. package/js/ops/conv2D.js +70 -0
  59. package/js/ops/convTranspose2D.js +45 -0
  60. package/js/ops/crossAttention.js +69 -0
  61. package/js/ops/crossSDPA.js +41 -0
  62. package/js/ops/dequantizeLinear.js +9 -0
  63. package/js/ops/div.js +15 -0
  64. package/js/ops/embedding.js +14 -0
  65. package/js/ops/expand.js +24 -0
  66. package/js/ops/gELU.js +9 -0
  67. package/js/ops/gather.js +51 -0
  68. package/js/ops/gatherElements.js +33 -0
  69. package/js/ops/globalAveragePool.js +21 -0
  70. package/js/ops/hardSigmoid.js +12 -0
  71. package/js/ops/hardSwish.js +12 -0
  72. package/js/ops/interp1D.js +25 -0
  73. package/js/ops/layerNorm.js +25 -0
  74. package/js/ops/leakyReLU.js +10 -0
  75. package/js/ops/logSoftmax.js +15 -0
  76. package/js/ops/matMul.js +35 -0
  77. package/js/ops/maxPool2D.js +36 -0
  78. package/js/ops/meanHeight.js +17 -0
  79. package/js/ops/mul.js +31 -0
  80. package/js/ops/nonMaxSuppression.js +72 -0
  81. package/js/ops/pReLU.js +11 -0
  82. package/js/ops/pad.js +35 -0
  83. package/js/ops/profileX.js +22 -0
  84. package/js/ops/profileY.js +22 -0
  85. package/js/ops/rMSNorm.js +14 -0
  86. package/js/ops/reLU.js +8 -0
  87. package/js/ops/reduceMean.js +17 -0
  88. package/js/ops/reduceSum.js +19 -0
  89. package/js/ops/reshape.js +6 -0
  90. package/js/ops/resize.js +44 -0
  91. package/js/ops/sDPA.js +44 -0
  92. package/js/ops/siLU.js +8 -0
  93. package/js/ops/sigmoid.js +6 -0
  94. package/js/ops/slice.js +36 -0
  95. package/js/ops/softmax.js +18 -0
  96. package/js/ops/spatialSoftargmaxY.js +28 -0
  97. package/js/ops/split.js +24 -0
  98. package/js/ops/sub.js +11 -0
  99. package/js/ops/tanh.js +7 -0
  100. package/js/ops/transpose.js +34 -0
  101. package/js/ops/upsample2x.js +23 -0
  102. package/js/ops/where.js +15 -0
  103. package/package.json +33 -0
  104. package/shaders/add.wgsl +13 -0
  105. package/shaders/add3Relu.wgsl +23 -0
  106. package/shaders/addRelu.wgsl +22 -0
  107. package/shaders/averagePool2D.wgsl +24 -0
  108. package/shaders/batchNorm2D.wgsl +21 -0
  109. package/shaders/binaryBroadcast.wgsl +34 -0
  110. package/shaders/broadcastBinary.wgsl +26 -0
  111. package/shaders/clip.wgsl +10 -0
  112. package/shaders/concat2.wgsl +16 -0
  113. package/shaders/concatCopy.wgsl +10 -0
  114. package/shaders/concatSigmoidCopy.wgsl +16 -0
  115. package/shaders/conv1D.wgsl +37 -0
  116. package/shaders/conv2D.wgsl +80 -0
  117. package/shaders/conv2DDepthwise4.wgsl +74 -0
  118. package/shaders/conv2DDepthwise8.wgsl +66 -0
  119. package/shaders/conv2DPointwise16.wgsl +67 -0
  120. package/shaders/conv2DPointwise16Tile.wgsl +86 -0
  121. package/shaders/conv2DPointwise8.wgsl +85 -0
  122. package/shaders/conv2DPointwise8Vec2.wgsl +70 -0
  123. package/shaders/conv2DPointwise8Vec4.wgsl +65 -0
  124. package/shaders/conv2DRegularC3Out16.wgsl +75 -0
  125. package/shaders/convTranspose2D.wgsl +33 -0
  126. package/shaders/copy.wgsl +13 -0
  127. package/shaders/crossAttention.wgsl +140 -0
  128. package/shaders/crossAttentionF32.wgsl +98 -0
  129. package/shaders/crossSDPA.wgsl +74 -0
  130. package/shaders/dequantizeLinear.wgsl +14 -0
  131. package/shaders/div.wgsl +34 -0
  132. package/shaders/elementwise.wgsl +13 -0
  133. package/shaders/embedding.wgsl +22 -0
  134. package/shaders/expand.wgsl +18 -0
  135. package/shaders/gELU.wgsl +13 -0
  136. package/shaders/gather.wgsl +17 -0
  137. package/shaders/generalTranspose.wgsl +19 -0
  138. package/shaders/globalAveragePool.wgsl +19 -0
  139. package/shaders/hardSigmoid.wgsl +13 -0
  140. package/shaders/hardSwish.wgsl +13 -0
  141. package/shaders/interp1D.wgsl +28 -0
  142. package/shaders/layerNorm.wgsl +33 -0
  143. package/shaders/leakyReLU.wgsl +11 -0
  144. package/shaders/linearF32.wgsl +33 -0
  145. package/shaders/linearF32RowMajor.wgsl +24 -0
  146. package/shaders/linearInt8.wgsl +42 -0
  147. package/shaders/logSoftmax.wgsl +22 -0
  148. package/shaders/maxPool2D.wgsl +37 -0
  149. package/shaders/meanHeight.wgsl +18 -0
  150. package/shaders/mul.wgsl +32 -0
  151. package/shaders/nonMaxSuppression.wgsl +92 -0
  152. package/shaders/pReLU.wgsl +14 -0
  153. package/shaders/pad.wgsl +19 -0
  154. package/shaders/profileX.wgsl +28 -0
  155. package/shaders/profileY.wgsl +28 -0
  156. package/shaders/quantizeLinear.wgsl +69 -0
  157. package/shaders/rMSNorm.wgsl +21 -0
  158. package/shaders/reLU.wgsl +13 -0
  159. package/shaders/reduce.wgsl +17 -0
  160. package/shaders/resize.wgsl +52 -0
  161. package/shaders/sDPA.wgsl +71 -0
  162. package/shaders/siLU.wgsl +13 -0
  163. package/shaders/sigmoid.wgsl +13 -0
  164. package/shaders/slice.wgsl +26 -0
  165. package/shaders/softmax.wgsl +23 -0
  166. package/shaders/spatialSoftargmaxY.wgsl +32 -0
  167. package/shaders/split.wgsl +15 -0
  168. package/shaders/sub.wgsl +34 -0
  169. package/shaders/tanh.wgsl +13 -0
  170. package/shaders/upsample2x.wgsl +24 -0
  171. package/shaders/where.wgsl +12 -0
  172. package/volvoxai.wasm +0 -0
@@ -0,0 +1,83 @@
1
+ # AI는 실제로 어떻게 동작하는가 — VolvoxAI 교과서
2
+
3
+ *🌐 언어: [English](../README.md) · **한국어***
4
+
5
+ > 이 저장소의 실제 코드를 바탕으로 신경망 **추론(inference)** 을 직접 따라가 보는 안내서입니다.
6
+ > 동작하는 두 개의 모델 — **언어 모델**(TinyStories)과 **객체 탐지기**(EfficientDet-Lite0,
7
+ > fp32 / fp16 / int8) — 을 골라, 하나의 입력이 답이 되기까지를 **연산 하나하나** 따라갑니다.
8
+
9
+ 이 책을 다 읽고 나면 어떤 최신 모델이든 **작은 수학 연산들의 그래프(graph)** 로 읽어낼 수 있고,
10
+ 각 연산이 실제로 무엇을 계산하는지 알게 되며, 그것을 실제 하드웨어에서 빠르게 돌리는 엔지니어링
11
+ 선택(메모리 배치, 양자화, GPU vs CPU)을 이해하게 됩니다. 이 책이 만드는 지식은 추론 메커니즘과
12
+ 런타임 엔지니어링입니다.
13
+
14
+ ---
15
+
16
+ ## 범위와 현재 빠진 부분
17
+
18
+ 이 교과서는 **추론(inference)** 에 집중합니다: 학습된 가중치를 로드하고, 모델 그래프를 실행하고,
19
+ 출력을 디코드하고, 가중치를 양자화하고, 연산을 브라우저/네이티브 백엔드에 매핑하는 것까지입니다.
20
+ 더 넓은 학습·연구 스택은 아직 다루지 않습니다:
21
+
22
+ | 빠진 영역 | 현재 없는 것 |
23
+ |---|---|
24
+ | 학습 | 역전파, autograd, 손실 함수, Adam 같은 최적화기, 학습률 스케줄, 초기화, 정규화. |
25
+ | 수학 기초 | 선형대수 유도, 연쇄 법칙/기울기를 위한 미적분, 확률, 정보 이론. |
26
+ | 데이터 | 데이터셋 구성, 입력 파이프라인, 증강, 토크나이저 학습, train/validation/test 분할, 누수 점검. |
27
+ | 평가와 실험 | 지표, 검증 방법론, ablation, 과적합, bias-variance 분석. |
28
+ | 아키텍처 폭 | diffusion, GNN, RNN/LSTM, 강화학습, VAE/GAN, retrieval과 embedding, multimodal, MoE, SSM. |
29
+ | 최신 LLM 학습 스택 | 사전학습, 지도 미세조정, LoRA/adapter, RLHF/DPO, 분산 학습, FlashAttention 내부. |
30
+ | 연구 실무 | 논문 재현, 결과 유도, inductive bias 추론, scaling law. |
31
+
32
+ 7장에서 이 목록을 다음에 무엇을 추가해야 하는지에 대한 구체적 지도로 바꿉니다.
33
+
34
+ ---
35
+
36
+ ## 이 책을 읽는 방법
37
+
38
+ 각 장은 앞 장을 기반으로 합니다. 처음 읽을 때는 순서대로 읽으세요.
39
+
40
+ 1. **[기초](01-foundations.md)** — 추론이란 무엇인가. 텐서(tensor), 그래프, 연산(operation).
41
+ VolvoxAI의 멘탈 모델과 네 개의 "계층(tier)". 모델이 *설계도(blueprint)* 로 저장되는 방식.
42
+ 2. **[언어 모델 한 연산씩 (TinyStories)](02-tinystories-language-model.md)** —
43
+ 문장 *"Once upon a time, Lily"* 가 GPT 스타일 트랜스포머(transformer)를 통과해 다음 단어를
44
+ 예측하기까지. 토큰화 → 임베딩 → 어텐션 → 피드포워드 → 로짓 → 샘플링 → 반복.
45
+ 3. **[비전 모델 한 연산씩 (EfficientDet-Lite0)](03-efficientdet-vision-model.md)** —
46
+ 320×320 사진이 합성곱 탐지기를 통과해 박스와 라벨을 내놓기까지.
47
+ 백본 → 피처 피라미드 → 탐지 헤드 → 디코드.
48
+ 4. **[정밀도와 양자화 (fp32 / fp16 / int8)](04-precision-and-quantization.md)** —
49
+ 숫자가 비트로 저장되는 방식, 같은 탐지기가 왜 세 가지 크기로 배포되는지, 그리고 int8 버전을
50
+ 4배 작게 만드는 정확한 정수 연산.
51
+ 5. **[엔진 내부](05-inside-the-engine.md)** — VolvoxAI가 *브라우저*에서 그래프를 실행하는 방법:
52
+ 네 개의 하드웨어 계층, *소박한(naive)* 커널에서 *빠른* 커널로의 도약, 그리고 연산 융합.
53
+ 6. **[네이티브 엔진](06-native-engine-architecture.md)** — *나머지 절반*: 같은 설계도를 CPU +
54
+ Vulkan/OpenGL/Metal/NNAPI 위에서 실행하는 독립형(freestanding) C 바이너리로, GPU 드라이버를
55
+ 실행 시점에 로드합니다. 전체 이중 타깃(dual-target) 아키텍처와 그 설계 선택들.
56
+ 7. **[용어집과 다음 단계](07-glossary-and-next-steps.md)** — 모든 용어를 한곳에, 추천 학습 경로,
57
+ 이 저장소를 활용한 실습 문제, 그리고 추론 너머의 현재 빈틈.
58
+
59
+ > **다이어그램에 대하여.** 플로차트는 [Mermaid](https://mermaid.js.org/)로 작성되어 GitHub,
60
+ > VS Code(Markdown Preview Mermaid 확장), 대부분의 마크다운 뷰어에서 이미지로 렌더링됩니다.
61
+ > 데이터 배치를 보여주는 그림은 어디서든 보이도록 순수 ASCII로 그렸습니다.
62
+
63
+ ---
64
+
65
+ ## 한 문단 요약
66
+
67
+ 신경망은 마법도 아니고 두뇌도 아닙니다. 그것은 **고정된 산술 연산의 목록** — 대부분 곱하고
68
+ 더하기 — 을, 큰 숫자 격자(당신의 입력)에 또 다른 큰 숫자 격자(**가중치(weights)**, 학습 중에
69
+ 얻어진 값)를 사용해 적용하는 것입니다. 이 목록을 입력에서 출력까지 한 번 실행하는 것을
70
+ **순전파(forward pass)** 또는 **추론(inference)** 이라고 합니다. VolvoxAI는 바로 이 일을 하는
71
+ 엔진입니다. 연산 목록(*그래프*)을 읽고, 가중치를 읽어, 출력을 계산합니다. 좋은 가중치를
72
+ *발견하는* 별도의 더 어려운 과정인 학습(training)은 이 저장소에 없습니다. 우리는 **학습이 끝난
73
+ 모델을 답으로 바꾸는** 부분을 공부합니다.
74
+
75
+ ```mermaid
76
+ flowchart LR
77
+ A["입력<br/>텍스트 또는 이미지"] --> B["숫자로 인코딩<br/>토큰 / 픽셀"]
78
+ B --> C["순전파<br/>수학 연산 그래프 + 가중치"]
79
+ C --> D["원시 출력<br/>로짓 / 박스"]
80
+ D --> E["의미로 디코딩<br/>다음 단어 / 라벨링된 박스"]
81
+ ```
82
+
83
+ 이제 **[1장: 기초](01-foundations.md)** 로 시작하세요.
@@ -0,0 +1,197 @@
1
+ # XNNPACK-Level Microkernel Optimization Guide
2
+
3
+ This document outlines the architectural roadmap and concrete code-level strategies required to elevate VolvoxAI's native CPU inference engine to XNNPACK-level performance across INT4, INT8, FP16, and FP32 data types.
4
+
5
+ ## 1. Architectural Paradigm Shift
6
+
7
+ Currently, VolvoxAI relies on generic `Conv2D` and `QConv2D` implementations where a C compiler auto-vectorizes nested loops. To achieve XNNPACK-level speeds, we must transition to a **Microkernel Architecture**:
8
+
9
+ 1. **NHWC Layout**: Keep all activations in NHWC format to ensure channels are contiguous in memory.
10
+ 2. **Indirection Buffers**: Remove padding and bounds-checking from the inner loops.
11
+ 3. **Weight Packing**: Pre-arrange weights in memory to perfectly match the target SIMD register width.
12
+ 4. **Register Tiling (Microkernels)**: Hardcode loop unrolling to maximize register usage (e.g., computing a 4x8 output tile in 32 SIMD registers simultaneously) using specific intrinsics.
13
+
14
+ ---
15
+
16
+ ## 2. Indirection Buffers (Depthwise & Im2Col)
17
+
18
+ **Problem:** Branching inside the MAC loop for padding (`if (x < 0 || x >= width)`) stalls the CPU pipeline.
19
+
20
+ **Solution:** Precompute an array of pointers (the indirection buffer). Padded areas point to a pre-allocated zero-buffer. The microkernel strictly does pointer dereferencing and math.
21
+
22
+ ### Example: Setting up an Indirection Buffer
23
+ ```c
24
+ // Pre-allocate a zero buffer for padding
25
+ static const int8_t zero_buffer[MAX_CHANNELS] = {0};
26
+
27
+ // Setup before the convolution
28
+ const int8_t** indirection_buffer = malloc(output_height * output_width * kernel_size * sizeof(int8_t*));
29
+ int idx = 0;
30
+ for (int oy = 0; oy < output_height; oy++) {
31
+ for (int ox = 0; ox < output_width; ox++) {
32
+ for (int ky = 0; ky < kh; ky++) {
33
+ for (int kx = 0; kx < kw; kx++) {
34
+ int iy = oy * stride - pad_top + ky;
35
+ int ix = ox * stride - pad_left + kx;
36
+ if (iy >= 0 && iy < input_height && ix >= 0 && ix < input_width) {
37
+ // Valid pixel pointer
38
+ indirection_buffer[idx++] = input_image + (iy * input_width + ix) * channels;
39
+ } else {
40
+ // Padding: Point to zero buffer. No branching in inner loop!
41
+ indirection_buffer[idx++] = zero_buffer;
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 3. Data Type Specific Optimizations
52
+
53
+ ### 3.1 INT8: The Workhorse (VNNI / ARM sdot)
54
+
55
+ For INT8, we must use dedicated dot-product instructions. ARM provides `sdot` (NEON) and Intel provides VNNI (AVX512/AVX2-VNNI). These instructions perform 4 MACs in a single cycle.
56
+
57
+ **Weight Packing for INT8 (e.g., 8 output channels at a time, chunks of 4 input channels for dot product):**
58
+ Weights are packed into blocks of `[OC/8, IC/4, 8, 4]`.
59
+
60
+ **Microkernel Example: ARM NEON INT8 (Processing 4 Output pixels, 4 Output Channels)**
61
+ ```c
62
+ #include <arm_neon.h>
63
+
64
+ void microkernel_qconv2d_int8_neon_sdot(
65
+ const int8_t** input_pointers,
66
+ const int8_t* packed_weights,
67
+ int32_t* output_accumulators,
68
+ int kernel_elements,
69
+ int channels)
70
+ {
71
+ // Initialize accumulators to bias + zero-point offsets
72
+ int32x4_t acc0 = vld1q_s32(output_accumulators + 0);
73
+ int32x4_t acc1 = vld1q_s32(output_accumulators + 4);
74
+ // ... setup for multiple output pixels
75
+
76
+ for (int k = 0; k < kernel_elements; k++) {
77
+ const int8_t* in_ptr = input_pointers[k];
78
+ for (int c = 0; c < channels; c += 4) {
79
+ // Load 4 input channels (broadcast to all output channels)
80
+ int8x8_t in_val = vld1_s8(in_ptr + c);
81
+ int8x16_t in_dup = vcombine_s8(in_val, in_val);
82
+
83
+ // Load packed weights: 4 channels x 8 output channels
84
+ int8x16_t w_val = vld1q_s8(packed_weights);
85
+ packed_weights += 16;
86
+
87
+ // Perform 4-way dot product: acc += sum(in[i] * w[i])
88
+ // Requires ARMv8.2-A Dot Product extension
89
+ acc0 = vdotq_s32(acc0, in_dup, w_val);
90
+ // ... repeat for other registers
91
+ }
92
+ }
93
+ // Fused requantization (int32 -> int8) happens here before writing to memory
94
+ }
95
+ ```
96
+
97
+ ### 3.2 INT4: Extreme Memory Bandwidth Saving
98
+
99
+ INT4 cuts memory bandwidth in half again compared to INT8, making it highly optimal for memory-bound LLM weights or heavily quantized vision models.
100
+
101
+ **Challenge:** Most CPUs lack native INT4 MAC instructions.
102
+ **Solution:** Decompress INT4 to INT8 *in registers* immediately upon loading, then feed into the INT8 microkernel.
103
+
104
+ **Microkernel Strategy for INT4:**
105
+ ```c
106
+ // Weights are packed 2-per-byte.
107
+ // Load 128-bits of INT4 data (which represents 32 weights)
108
+ uint8x16_t w_int4 = vld1q_u8(packed_int4_weights);
109
+
110
+ // Decompress to INT8 using masking and shifting
111
+ uint8x16_t mask = vdupq_n_u8(0x0F);
112
+ uint8x16_t w_lo = vandq_u8(w_int4, mask); // Extract lower nibbles
113
+ uint8x16_t w_hi = vshrq_n_u8(w_int4, 4); // Extract upper nibbles
114
+
115
+ // Convert to signed int8 if using symmetric zero-point, then proceed with sdot
116
+ // ... (feed w_lo and w_hi into vdotq_s32 as shown in the INT8 example)
117
+ ```
118
+
119
+ ### 3.3 FP16: Native Half-Precision (NEON / AVX512-FP16)
120
+
121
+ Currently, VolvoxAI casts FP16 source models to FP32, doubling the memory bandwidth and cache footprint. Native FP16 execution solves this.
122
+
123
+ **Hardware support:** ARMv8.2-A provides native `__fp16` arithmetic (`vmlaq_f16`).
124
+
125
+ **Microkernel Example: ARM NEON FP16**
126
+ ```c
127
+ #include <arm_neon.h>
128
+
129
+ void microkernel_conv2d_fp16_neon(
130
+ const float16_t* input,
131
+ const float16_t* packed_weights,
132
+ float16_t* output)
133
+ {
134
+ float16x8_t acc = vdupq_n_f16(0.0f); // 8 output channels
135
+
136
+ // Inner loop MAC
137
+ float16x8_t in_val = vdupq_n_f16(input[0]); // Broadcast 1 input to 8 weights
138
+ float16x8_t w_val = vld1q_f16(packed_weights);
139
+
140
+ // Native FP16 Fused-Multiply-Add
141
+ acc = vfmaq_f16(acc, in_val, w_val);
142
+
143
+ vst1q_f16(output, acc);
144
+ }
145
+ ```
146
+
147
+ ### 3.4 FP32: Maximum Register Tiling (AVX2 / FMA)
148
+
149
+ FP32 is inherently memory-heavy. To hide latency, we must use **Register Tiling**. An AVX2 CPU has 16 YMM registers. We use them strictly:
150
+ - 1 for input broadcast
151
+ - 3 for weights
152
+ - 12 for accumulators (computing a `3x8` output tile simultaneously)
153
+
154
+ **Microkernel Example: AVX2 FP32 (Processing 3 spatial pixels x 8 output channels)**
155
+ ```c
156
+ #include <immintrin.h>
157
+
158
+ void microkernel_conv2d_fp32_avx2(
159
+ const float* in_p0, const float* in_p1, const float* in_p2, // 3 input pixels
160
+ const float* packed_weights,
161
+ float* out_p0, float* out_p1, float* out_p2)
162
+ {
163
+ // 3 pixels * 8 channels = 3 AVX2 registers for accumulation
164
+ __m256 acc0 = _mm256_setzero_ps();
165
+ __m256 acc1 = _mm256_setzero_ps();
166
+ __m256 acc2 = _mm256_setzero_ps();
167
+
168
+ for (int ic = 0; ic < input_channels; ic++) {
169
+ // Load weights for 8 output channels
170
+ __m256 w0 = _mm256_loadu_ps(packed_weights + ic * 8);
171
+
172
+ // Broadcast input channel 'ic' for each of the 3 pixels
173
+ __m256 in0 = _mm256_set1_ps(in_p0[ic]);
174
+ __m256 in1 = _mm256_set1_ps(in_p1[ic]);
175
+ __m256 in2 = _mm256_set1_ps(in_p2[ic]);
176
+
177
+ // Fused Multiply-Add
178
+ acc0 = _mm256_fmadd_ps(in0, w0, acc0);
179
+ acc1 = _mm256_fmadd_ps(in1, w0, acc1);
180
+ acc2 = _mm256_fmadd_ps(in2, w0, acc2);
181
+ }
182
+
183
+ // Apply ReLU and Store
184
+ _mm256_storeu_ps(out_p0, acc0);
185
+ _mm256_storeu_ps(out_p1, acc1);
186
+ _mm256_storeu_ps(out_p2, acc2);
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Conclusion & Action Plan
193
+
194
+ To implement this in VolvoxAI:
195
+ 1. **Create an `indirection.c` module**: To pre-calculate pointer buffers during the graph build phase (not runtime).
196
+ 2. **Create a `pack_weights.c` module**: To permute weights offline during the `export_safetensors.py` step or at graph initialization.
197
+ 3. **Rewrite `quant_cpu_opt.c`**: Strip out the generic C loops. Replace them with discrete microkernels `vx_ukernel_qconv2d_int8_sdot`, `vx_ukernel_conv2d_fp32_avx2`, etc., routed dynamically based on CPU feature detection (`CPUID` / `getauxval`).
@@ -0,0 +1,241 @@
1
+ import { _cpuPReLU } from './ops/pReLU.js';
2
+ import { _cpuExpand } from './ops/expand.js';
3
+ import { _cpuDequantizeLinear } from './ops/dequantizeLinear.js';
4
+ import { _cpuTanh } from './ops/tanh.js';
5
+ import { _cpuRMSNorm } from './ops/rMSNorm.js';
6
+ import { _cpuSiLU } from './ops/siLU.js';
7
+ import { _cpuSub } from './ops/sub.js';
8
+ import { _cpuLogSoftmax } from './ops/logSoftmax.js';
9
+ import { _cpuSoftmax } from './ops/softmax.js';
10
+ import { _cpuWhere } from './ops/where.js';
11
+ import { _cpuPad } from './ops/pad.js';
12
+ import { _cpuAveragePool2D } from './ops/averagePool2D.js';
13
+ import { _cpuSlice } from './ops/slice.js';
14
+ import { _cpuConvTranspose2D } from './ops/convTranspose2D.js';
15
+ import { _cpuReduceSum } from './ops/reduceSum.js';
16
+ import { _cpuReduceMean } from './ops/reduceMean.js';
17
+ import { _cpuBatchNorm2D } from './ops/batchNorm2D.js';
18
+ import { _cpuDiv } from './ops/div.js';
19
+ import { _cpuNonMaxSuppression } from './ops/nonMaxSuppression.js';
20
+ import { _cpuGather } from './ops/gather.js';
21
+ import { _cpuGatherElements } from './ops/gatherElements.js';
22
+ import { _cpuCrossAttention } from './ops/crossAttention.js';
23
+ import { _cpuSpatialSoftargmaxY } from './ops/spatialSoftargmaxY.js';
24
+ import { _cpuTranspose } from './ops/transpose.js';
25
+ import { _cpuCrossSDPA } from './ops/crossSDPA.js';
26
+ import { _cpuMeanHeight } from './ops/meanHeight.js';
27
+ import { _cpuMaxPool2D } from './ops/maxPool2D.js';
28
+ import { _cpuInterp1D } from './ops/interp1D.js';
29
+ import { _cpuProfileX } from './ops/profileX.js';
30
+ import { _cpuProfileY } from './ops/profileY.js';
31
+ import { _cpuConcat2 } from './ops/concat2.js';
32
+ import { _cpuUpsample2x } from './ops/upsample2x.js';
33
+ import { _cpuSDPA } from './ops/sDPA.js';
34
+ import { _cpuEmbedding } from './ops/embedding.js';
35
+ import { _cpuMul } from './ops/mul.js';
36
+ import { _cpuAdd } from './ops/add.js';
37
+ import { _cpuGlobalAveragePool } from './ops/globalAveragePool.js';
38
+ import { _cpuClip } from './ops/clip.js';
39
+ import { _cpuReshape } from './ops/reshape.js';
40
+ import { _cpuSplit } from './ops/split.js';
41
+ import { _cpuResize } from './ops/resize.js';
42
+ import { _cpuSigmoid } from './ops/sigmoid.js';
43
+ import { _cpuHardSigmoid } from './ops/hardSigmoid.js';
44
+ import { _cpuHardSwish } from './ops/hardSwish.js';
45
+ import { _cpuReLU } from './ops/reLU.js';
46
+ import { _cpuLeakyReLU } from './ops/leakyReLU.js';
47
+ import { _cpuGELU } from './ops/gELU.js';
48
+ import { _cpuLayerNorm } from './ops/layerNorm.js';
49
+ import { _cpuConv2D } from './ops/conv2D.js';
50
+ import { _cpuConv1D } from './ops/conv1D.js';
51
+ import { _cpuMatMul } from './ops/matMul.js';
52
+ import { _cpuCast } from './ops/cast.js';
53
+ import { _cpuArgMax } from './ops/argMax.js';
54
+
55
+
56
+ export class CPUEngine {
57
+ constructor() {
58
+ this.tensors = /* @__PURE__ */ new Map();
59
+ console.log("[VolvoxAI] CPU Fallback Engine ready.");
60
+ }
61
+ /**
62
+ * Allocates CPU memory (ArrayBuffers) for the graph's tensors.
63
+ */
64
+ allocateGraph(graph) {
65
+ this.graph = graph;
66
+ for (const [name, tensor] of graph.tensors.entries()) {
67
+ if (!tensor.buffer) {
68
+ if (tensor.dtype === "int8") {
69
+ tensor.buffer = new Int8Array(tensor.sizeBytes);
70
+ } else {
71
+ tensor.buffer = new Float32Array(tensor.sizeBytes / 4);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Executes the graph linearly on the CPU.
78
+ */
79
+ async execute(inputsOrGraph, maybeInputs) {
80
+ const graph = maybeInputs ? inputsOrGraph : this.graph;
81
+ const inputs = maybeInputs || inputsOrGraph;
82
+ for (const [name, data] of Object.entries(inputs)) {
83
+ const tensor = graph.tensors.get(name);
84
+ if (tensor && tensor.buffer) {
85
+ tensor.buffer.set(data);
86
+ }
87
+ }
88
+ for (const node of graph.nodes) {
89
+ this._runNode(node);
90
+ }
91
+ const result = {};
92
+ if (graph.outputNames && graph.outputNames.length > 0) {
93
+ for (const name of graph.outputNames) {
94
+ result[name] = graph.tensors.get(name).buffer;
95
+ }
96
+ } else {
97
+ const lastNode = graph.nodes[graph.nodes.length - 1];
98
+ for (const [key, t] of Object.entries(lastNode.outputs)) {
99
+ result[t.name] = graph.tensors.get(t.name).buffer;
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+ _runNode(node) {
105
+ switch (node.opType) {
106
+ // --- Linear / attention ---
107
+ case "MatMul": return this._cpuMatMul(node);
108
+ case "LayerNorm": return this._cpuLayerNorm(node);
109
+ case "RMSNorm": return this._cpuRMSNorm(node);
110
+ case "Embedding": return this._cpuEmbedding(node);
111
+ case "SDPA": return this._cpuSDPA(node);
112
+ case "CrossSDPA": return this._cpuCrossSDPA(node);
113
+ case "CrossAttention": return this._cpuCrossAttention(node);
114
+
115
+ // --- Convolution / pooling ---
116
+ case "Conv2D": return this._cpuConv2D(node);
117
+ case "Conv1D": return this._cpuConv1D(node);
118
+ case "ConvTranspose2D": return this._cpuConvTranspose2D(node);
119
+ case "MaxPool2D": return this._cpuMaxPool2D(node);
120
+ case "AveragePool":
121
+ case "AveragePool2D": return this._cpuAveragePool2D(node);
122
+ case "GlobalAveragePool": return this._cpuGlobalAveragePool(node);
123
+ case "BatchNorm2D": return this._cpuBatchNorm2D(node);
124
+ case "ResizeNearest2D":
125
+ case "Resize": return this._cpuResize(node);
126
+ case "Upsample2x":
127
+ case "UpsampleNearest2D": return this._cpuUpsample2x(node);
128
+ case "Interp1D":
129
+ case "InterpLinear1D": return this._cpuInterp1D(node);
130
+
131
+ // --- Activations ---
132
+ case "ReLU": return this._cpuReLU(node);
133
+ case "LeakyReLU": return this._cpuLeakyReLU(node);
134
+ case "PReLU": return this._cpuPReLU(node);
135
+ case "GELU": return this._cpuGELU(node);
136
+ case "SiLU":
137
+ case "Swish": return this._cpuSiLU(node);
138
+ case "Sigmoid": return this._cpuSigmoid(node);
139
+ case "HardSwish": return this._cpuHardSwish(node);
140
+ case "HardSigmoid": return this._cpuHardSigmoid(node);
141
+ case "Tanh": return this._cpuTanh(node);
142
+ case "Clip": return this._cpuClip(node);
143
+
144
+ // --- Elementwise / reduction ---
145
+ case "Add": return this._cpuAdd(node);
146
+ case "Mul": return this._cpuMul(node);
147
+ case "Sub": return this._cpuSub(node);
148
+ case "Div": return this._cpuDiv(node);
149
+ case "Softmax": return this._cpuSoftmax(node);
150
+ case "LogSoftmax": return this._cpuLogSoftmax(node);
151
+ case "ReduceSum": return this._cpuReduceSum(node);
152
+ case "ReduceMean": return this._cpuReduceMean(node);
153
+ case "ArgMax": return this._cpuArgMax(node);
154
+
155
+ // --- Shape / gather / misc ---
156
+ case "Transpose": return this._cpuTranspose(node);
157
+ case "Concat":
158
+ case "Concat2": return this._cpuConcat2(node);
159
+ case "Split": return this._cpuSplit(node);
160
+ case "Slice": return this._cpuSlice(node);
161
+ case "Pad": return this._cpuPad(node);
162
+ case "Expand":
163
+ case "Broadcast": return this._cpuExpand(node);
164
+ case "Gather": return this._cpuGather(node);
165
+ case "GatherElements": return this._cpuGatherElements(node);
166
+ case "Where":
167
+ case "Mask": return this._cpuWhere(node);
168
+ case "Cast": return this._cpuCast(node);
169
+ case "DequantizeLinear": return this._cpuDequantizeLinear(node);
170
+ case "NonMaxSuppression": return this._cpuNonMaxSuppression(node);
171
+ case "SpatialSoftargmaxY": return this._cpuSpatialSoftargmaxY(node);
172
+ case "ProfileX": return this._cpuProfileX(node);
173
+ case "ProfileY": return this._cpuProfileY(node);
174
+ case "MeanHeight": return this._cpuMeanHeight(node);
175
+
176
+ // Shape-only ops just copy their data through to the output buffer.
177
+ case "Reshape":
178
+ case "Flatten":
179
+ case "Squeeze":
180
+ case "Unsqueeze":
181
+ case "Dropout":
182
+ case "Identity": return this._cpuReshape(node);
183
+
184
+ default:
185
+ console.warn(`[VolvoxAI CPU] Executing ${node.opType} is not implemented; node ${node.id} skipped.`);
186
+ }
187
+ }
188
+ };
189
+ CPUEngine.prototype._cpuWhere = _cpuWhere;
190
+ CPUEngine.prototype._cpuPad = _cpuPad;
191
+ CPUEngine.prototype._cpuAveragePool2D = _cpuAveragePool2D;
192
+ CPUEngine.prototype._cpuSlice = _cpuSlice;
193
+ CPUEngine.prototype._cpuConvTranspose2D = _cpuConvTranspose2D;
194
+ CPUEngine.prototype._cpuReduceSum = _cpuReduceSum;
195
+ CPUEngine.prototype._cpuReduceMean = _cpuReduceMean;
196
+ CPUEngine.prototype._cpuBatchNorm2D = _cpuBatchNorm2D;
197
+ CPUEngine.prototype._cpuSoftmax = _cpuSoftmax;
198
+ CPUEngine.prototype._cpuLogSoftmax = _cpuLogSoftmax;
199
+ CPUEngine.prototype._cpuSub = _cpuSub;
200
+ CPUEngine.prototype._cpuSiLU = _cpuSiLU;
201
+ CPUEngine.prototype._cpuRMSNorm = _cpuRMSNorm;
202
+ CPUEngine.prototype._cpuTanh = _cpuTanh;
203
+ CPUEngine.prototype._cpuDequantizeLinear = _cpuDequantizeLinear;
204
+ CPUEngine.prototype._cpuExpand = _cpuExpand;
205
+ CPUEngine.prototype._cpuPReLU = _cpuPReLU;
206
+ CPUEngine.prototype._cpuDiv = _cpuDiv;
207
+ CPUEngine.prototype._cpuNonMaxSuppression = _cpuNonMaxSuppression;
208
+ CPUEngine.prototype._cpuGather = _cpuGather;
209
+ CPUEngine.prototype._cpuGatherElements = _cpuGatherElements;
210
+ CPUEngine.prototype._cpuCrossAttention = _cpuCrossAttention;
211
+ CPUEngine.prototype._cpuSpatialSoftargmaxY = _cpuSpatialSoftargmaxY;
212
+ CPUEngine.prototype._cpuTranspose = _cpuTranspose;
213
+ CPUEngine.prototype._cpuCrossSDPA = _cpuCrossSDPA;
214
+ CPUEngine.prototype._cpuMeanHeight = _cpuMeanHeight;
215
+ CPUEngine.prototype._cpuMaxPool2D = _cpuMaxPool2D;
216
+ CPUEngine.prototype._cpuInterp1D = _cpuInterp1D;
217
+ CPUEngine.prototype._cpuProfileX = _cpuProfileX;
218
+ CPUEngine.prototype._cpuProfileY = _cpuProfileY;
219
+ CPUEngine.prototype._cpuConcat2 = _cpuConcat2;
220
+ CPUEngine.prototype._cpuUpsample2x = _cpuUpsample2x;
221
+ CPUEngine.prototype._cpuSDPA = _cpuSDPA;
222
+ CPUEngine.prototype._cpuEmbedding = _cpuEmbedding;
223
+ CPUEngine.prototype._cpuMul = _cpuMul;
224
+ CPUEngine.prototype._cpuAdd = _cpuAdd;
225
+ CPUEngine.prototype._cpuGlobalAveragePool = _cpuGlobalAveragePool;
226
+ CPUEngine.prototype._cpuClip = _cpuClip;
227
+ CPUEngine.prototype._cpuReshape = _cpuReshape;
228
+ CPUEngine.prototype._cpuSplit = _cpuSplit;
229
+ CPUEngine.prototype._cpuResize = _cpuResize;
230
+ CPUEngine.prototype._cpuSigmoid = _cpuSigmoid;
231
+ CPUEngine.prototype._cpuHardSigmoid = _cpuHardSigmoid;
232
+ CPUEngine.prototype._cpuHardSwish = _cpuHardSwish;
233
+ CPUEngine.prototype._cpuReLU = _cpuReLU;
234
+ CPUEngine.prototype._cpuLeakyReLU = _cpuLeakyReLU;
235
+ CPUEngine.prototype._cpuGELU = _cpuGELU;
236
+ CPUEngine.prototype._cpuLayerNorm = _cpuLayerNorm;
237
+ CPUEngine.prototype._cpuConv2D = _cpuConv2D;
238
+ CPUEngine.prototype._cpuConv1D = _cpuConv1D;
239
+ CPUEngine.prototype._cpuMatMul = _cpuMatMul;
240
+ CPUEngine.prototype._cpuCast = _cpuCast;
241
+ CPUEngine.prototype._cpuArgMax = _cpuArgMax;
package/js/Graph.js ADDED
@@ -0,0 +1,49 @@
1
+ import { Tensor } from './Tensor.js';
2
+
3
+ export class Graph {
4
+ constructor() {
5
+ this.nodes = [];
6
+ this.tensors = /* @__PURE__ */ new Map();
7
+ }
8
+ /**
9
+ * Define an input tensor
10
+ */
11
+ addInput(name, shape, dtype = "float32") {
12
+ const tensor = new Tensor(name, shape, dtype, false);
13
+ this.tensors.set(name, tensor);
14
+ return tensor;
15
+ }
16
+ /**
17
+ * Define a weight tensor (learned parameter)
18
+ */
19
+ addWeight(name, shape, dtype = "float32") {
20
+ const tensor = new Tensor(name, shape, dtype, true);
21
+ this.tensors.set(name, tensor);
22
+ return tensor;
23
+ }
24
+ /**
25
+ * Add a computation operation to the graph
26
+ * @param {string} opType - e.g., 'MatMul', 'Conv2D', 'LayerNorm'
27
+ * @param {Object} inputs - Key-value pair of input names to Tensor objects
28
+ * @param {Object} outputs - Key-value pair of output names to Tensor shapes
29
+ * @param {Object} params - Uniform parameters for the shader (e.g., stride, kernel size)
30
+ */
31
+ addOp(opType, inputs, outputs, params = {}) {
32
+ const outTensors = {};
33
+ for (const [key, shape] of Object.entries(outputs)) {
34
+ const outName = `${opType}_${this.nodes.length}_out_${key}`;
35
+ const t = new Tensor(outName, shape, "float32", false);
36
+ this.tensors.set(outName, t);
37
+ outTensors[key] = t;
38
+ }
39
+ const node = {
40
+ id: this.nodes.length,
41
+ opType,
42
+ inputs,
43
+ outputs: outTensors,
44
+ params
45
+ };
46
+ this.nodes.push(node);
47
+ return outTensors;
48
+ }
49
+ };