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,333 @@
|
|
|
1
|
+
# 6장 — 네이티브 엔진 (CPU + 다중 백엔드 GPU)
|
|
2
|
+
|
|
3
|
+
*목표: VolvoxAI의 **네이티브** 쪽 — 브라우저와 **같은 설계도** 를 데스크톱과 폰에서, CPU와 여러
|
|
4
|
+
GPU/NPU 백엔드에 걸쳐 실행하는 독립형(freestanding) C 프로그램 — 과 이를 가능하게 하는 설계
|
|
5
|
+
아이디어를 이해합니다.*
|
|
6
|
+
|
|
7
|
+
1–5장은 주로 JavaScript 계층을 읽었습니다. 가장 명료한 교재이기 때문이죠. 하지만 그것은 VolvoxAI의
|
|
8
|
+
절반일 뿐입니다. 나머지 절반은 `native/`: *동일한* `config.json` + `.safetensors` 를 받아, 브라우저도
|
|
9
|
+
Node도 없이 — 그리고 이것이 놀라운 부분인데 — **정적으로 링크된 GPU SDK 없이** 실행하는
|
|
10
|
+
**베어메탈(bare-metal) C 엔진** 입니다. 이 장은 그 아키텍처와 "왜"에 대한 것입니다.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 6.1 핵심 아이디어: 하나의 설계도, 두 개의 세계
|
|
15
|
+
|
|
16
|
+
프로젝트 전체가 하나의 원칙을 중심으로 구성됩니다:
|
|
17
|
+
|
|
18
|
+
> **모델을 설계도로 한 번 작성하고, 어디서든 실행한다.**
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart TD
|
|
22
|
+
BP["설계도<br/>config.json + model.safetensors"]:::bp
|
|
23
|
+
BP --> WEB["브라우저 세계 · JS/WASM/WGSL"]
|
|
24
|
+
BP --> NAT["네이티브 세계 · 독립형 C"]
|
|
25
|
+
WEB --> W1["WebNN"]
|
|
26
|
+
WEB --> W2["WebGPU"]
|
|
27
|
+
WEB --> W3["WASM SIMD"]
|
|
28
|
+
WEB --> W4["순수 JS"]
|
|
29
|
+
NAT --> N1["CPU · AVX2 / NEON"]
|
|
30
|
+
NAT --> N2["Vulkan"]
|
|
31
|
+
NAT --> N3["OpenGL / GLES"]
|
|
32
|
+
NAT --> N4["Metal"]
|
|
33
|
+
NAT --> N5["Android NNAPI"]
|
|
34
|
+
classDef bp fill:#eef,stroke:#66a;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
브라우저 세계는 5장이었습니다. 네이티브 세계는 터미널에서 실행하는 독립 실행 파일
|
|
38
|
+
(`native/volvoxai`)입니다:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
./native/volvoxai generate models/tinystories_1m --prompt "Once upon a time, Lily" --max-new 50
|
|
42
|
+
./native/volvoxai detect models/efficientdet_lite0_int8 --image input0=photo.png \
|
|
43
|
+
--image-normalize raw-255 --boxes boxes --scores scores
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
브라우저가 로드하는 것과 같은 파일들입니다. 이 대칭성이 곧 설계입니다.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 6.2 독립형 철학 (왜 특이한가)
|
|
51
|
+
|
|
52
|
+
두 개의 의도적 제약이 네이티브 엔진을 규정합니다:
|
|
53
|
+
|
|
54
|
+
1. **Emscripten 없음 / 무거운 런타임 없음.** WASM 모듈은 순수
|
|
55
|
+
`clang --target=wasm32 -msimd128` 로 빌드됩니다 — `--no-entry`, libc 런타임 없는 *독립형*
|
|
56
|
+
빌드지요. 네이티브 바이너리는 평범한 `clang -O3 -mavx2 -mfma -pthread` 입니다. 아래에 프레임워크가
|
|
57
|
+
없습니다. 엔진이 *곧* `native/` 의 코드입니다.
|
|
58
|
+
2. **정적 GPU 의존성 없음.** 바이너리는 빌드 시점에 `libvulkan` 이나 OpenGL SDK를 링크하지
|
|
59
|
+
**않습니다**. 대신 GPU 드라이버를 *실행 시점에* **`dlopen`** 하고 진입점을 이름으로 찾습니다.
|
|
60
|
+
드라이버가 있으면 GPU 가속을 얻고, 없으면 정확히 같은 바이너리가 CPU에서 돕니다. 하나의 산출물이,
|
|
61
|
+
GPU 스택이 크게 다른 기계들에 걸쳐 이식됩니다.
|
|
62
|
+
|
|
63
|
+
다음이 그 실행 시점 로딩입니다, 원문 그대로(`native/vulkan_engine.c`, `native/opengl_engine.c`):
|
|
64
|
+
|
|
65
|
+
```c
|
|
66
|
+
// Vulkan: 플랫폼의 로더 이름을 순서대로, 실행 시점에 시도.
|
|
67
|
+
const char* names[] = { "libvulkan.so.1", "libvulkan.so", "vulkan-1.dll" };
|
|
68
|
+
for (i = 0; i < 3; i++) vulkan_lib = dlopen(names[i], RTLD_NOW | RTLD_LOCAL);
|
|
69
|
+
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(vulkan_lib, "vkGetInstanceProcAddr");
|
|
70
|
+
|
|
71
|
+
// OpenGL/GLES: 마찬가지로 libEGL + libGLESv2/libGL(윈도우에서는 *.dll)을 dlopen.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
이것이 "GPU SDK 링크 없이 Vulkan/OpenGL/Metal/NNAPI에서 실행"의 비결 전부입니다. 빌드 명령의
|
|
75
|
+
`-ldl` 이 유일한 대가입니다.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 6.3 하나의 커널 소스, 두 개의 기계 (공유 ABI)
|
|
80
|
+
|
|
81
|
+
VolvoxAI는 모든 커널을 두 벌로 유지하는 것을 피합니다. `native/kernels/*.c`(`native/kernels.c` 로
|
|
82
|
+
묶임)의 이식성 있는 C가 WebAssembly(브라우저의 Tier 3)로 **그리고** 네이티브 바이너리로 **둘 다**
|
|
83
|
+
컴파일됩니다. 비결은 **`uintptr_t` 힙-포인터 ABI** 입니다: 커널이 하나의 평평한 힙에 대한 정수
|
|
84
|
+
오프셋으로 메모리를 주소 지정하므로, 그 힙이 WASM 선형 메모리든 네이티브 `malloc` 아레나든 같은
|
|
85
|
+
소스가 동작합니다. 그러면 컴파일러가 **x86에서는 AVX2**, **arm64에서는 NEON** 으로 자동
|
|
86
|
+
벡터화합니다.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
┌─────────────────────────────┐
|
|
90
|
+
native/kernels/*.c │ 이식성 C, uintptr_t 힙 │
|
|
91
|
+
└───────────┬─────────────────┘
|
|
92
|
+
clang --target=wasm32 │ clang -O3 -mavx2 (x86) / -march=…(arm64)
|
|
93
|
+
┌──────────────┴───────────────┐
|
|
94
|
+
volvoxai.wasm native/volvoxai
|
|
95
|
+
(브라우저 Tier 3) (데스크톱/안드로이드 CPU)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
순수 JS 연산(`js/ops/*.js`)은 이들을 검증하는 기준으로 남습니다 — 그래서 각 연산에는 실제로 **세**
|
|
99
|
+
가지 표현(JS 참조, 이식성 C, 그리고 핫 연산의 경우 *최적화된* C 커널)이 있고, 모두 일치해야 합니다.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 6.4 엔진 수명 주기 (`native/engine.c`)
|
|
104
|
+
|
|
105
|
+
네이티브 엔진은 작고 명시적인 상태 기계입니다. 그 공개 API(`native/engine.h`)는 JS의
|
|
106
|
+
`compile()` / `execute()` 에 대응하는 C 버전입니다:
|
|
107
|
+
|
|
108
|
+
```c
|
|
109
|
+
int engine_init(const char* config_path, const char* weights_path); // 로드 + 빌드 한 번
|
|
110
|
+
float* engine_input_ptr(const char* name, long* numel); // 입력 텐서에 값 넣기
|
|
111
|
+
int engine_forward(void); // 그래프 전체 실행
|
|
112
|
+
const float* engine_last_logits(int* count); // 출력 행 읽기
|
|
113
|
+
void engine_free_ctx(void); // 정리
|
|
114
|
+
// 자기회귀 보조:
|
|
115
|
+
int engine_prefill(int n_tokens); // 프롬프트 처리, K/V 캐시 채우기
|
|
116
|
+
int engine_decode(int pos); // 캐시로 새 토큰 하나 처리
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`engine_init` 이 일회성 무거운 작업을 합니다(`native/engine.c`):
|
|
120
|
+
|
|
121
|
+
```c
|
|
122
|
+
int engine_init(const char* config_path, const char* weights_path) {
|
|
123
|
+
load_weights(weights_path); // .safetensors 블롭을 mmap/파싱
|
|
124
|
+
build_graph(config_path); // config.json 파싱 → g_t[] 텐서, g_n[] 노드
|
|
125
|
+
prepack_qconv_weights(); // 빠른 커널용으로 int8 conv 가중치 재배치 (§5.3)
|
|
126
|
+
prepack_conv_weights(); // fp32 conv 가중치 재배치 (im2col/GEMM 순서)
|
|
127
|
+
g_loaded = 1;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
그다음 `engine_forward` 는 익숙한 루프입니다 — 노드 목록을 훑으며 각각을 디스패치:
|
|
132
|
+
|
|
133
|
+
```c
|
|
134
|
+
for (int i = 0; i < g_nn; i++)
|
|
135
|
+
run_node(&g_n[i], i, /*is_last=*/ i == g_nn - 1);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
그래프는 **한 번** 빌드되고, 순전파는 저렴하고 반복 가능합니다. 이것이 `generate` 가 가중치를 다시
|
|
139
|
+
로드하지 않고 수백 번의 순전파를 돌리게 해줍니다.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 6.5 노드별 백엔드 선택 (`native/engine_runtime.c`)
|
|
144
|
+
|
|
145
|
+
`run_node` 가 "다중 백엔드"가 실제로 일어나는 곳입니다. 각 노드마다 *누가 그것을 계산할지* 를
|
|
146
|
+
결정하며, 결정은 모델 단위가 아니라 노드 단위입니다:
|
|
147
|
+
|
|
148
|
+
```mermaid
|
|
149
|
+
flowchart TD
|
|
150
|
+
N["노드 i"] --> Q{"GPU 켜짐?<br/>--vulkan / --opengl"}
|
|
151
|
+
Q -- 아니오 --> CPU
|
|
152
|
+
Q -- 예 --> AR{"자기회귀<br/>디코드 단계?"}
|
|
153
|
+
AR -- 예 --> CPU["CPU 커널<br/>conv_f32_opt / quant_cpu_opt / kernels.c"]
|
|
154
|
+
AR -- 아니오 --> SUP{"GPU 그래프 & FP32에서<br/>지원되는 연산?"}
|
|
155
|
+
SUP -- 예 --> GPU["GPU 그래프 노드<br/>vk_graph_* / opengl_*"]
|
|
156
|
+
SUP -- 아니오 --> CPU
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
디스패처에 새겨진 핵심 규칙:
|
|
160
|
+
|
|
161
|
+
- **GPU는 옵트인** 입니다, CLI 플래그(`--vulkan`, `--opengl`, `--nnapi`)로. 기본은 CPU입니다.
|
|
162
|
+
드라이버를 로드할 수 없으면 예컨대 `Backend: CPU (Vulkan unavailable)` 을 출력하고 계속합니다.
|
|
163
|
+
- **GPU 백엔드는 FP32 전용** 입니다. `QConv2D`(int8) 노드는 `--vulkan` 이 있어도 항상 CPU의 양자화
|
|
164
|
+
섬(`quant_cpu_opt.c`)에 머뭅니다 — 그래서 int8 탐지기는 conv를 CPU에서 돌리고 FP32 연산만
|
|
165
|
+
오프로드합니다.
|
|
166
|
+
- **생성은 대부분 CPU에 머뭅니다.** `engine_decode`/`engine_prefill` 동안에는 Vulkan/OpenGL 그래프
|
|
167
|
+
경로를 건너뜁니다. 토큰별 디코드는 지연(latency)에 민감하기 때문입니다. 다만 작업량이 충분히 큰
|
|
168
|
+
MatMul/Gemm/Linear 노드는 일회성 Vulkan/OpenGL 오프로드를 쓸 수 있고, 작은 디코드 MatMul은
|
|
169
|
+
디스패치 오버헤드를 피하려고 CPU에 남습니다.
|
|
170
|
+
- 모든 노드는 어떤 백엔드가 실행했는지 기록합니다(`"vulkan-graph"`, `"cpu-qconv"`, …), `--debug`
|
|
171
|
+
프로파일 보고를 위해.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6.6 GPU 경로는 지연 실행되는 명령 그래프다
|
|
176
|
+
|
|
177
|
+
네이티브 GPU 백엔드는 매번 CPU 왕복을 하며 연산별로 실행하지 않습니다. WebGPU 계층처럼, **명령
|
|
178
|
+
그래프를 만들어 재생** 하며 데이터를 기기에 상주시킵니다. `vk_graph_*` 인터페이스
|
|
179
|
+
(`native/vulkan_engine.h`)가 그 형태를 보여줍니다:
|
|
180
|
+
|
|
181
|
+
```c
|
|
182
|
+
vk_graph_begin_forward(); // 기록 시작
|
|
183
|
+
vk_graph_conv2d_f32(in, out, w, b, …); // conv 기록
|
|
184
|
+
vk_graph_add_relu_f32(a, b, out, n, relu); // 융합된 add+relu 기록
|
|
185
|
+
vk_graph_maxpool2d_f32(…); vk_graph_resize_nearest_f32(…);
|
|
186
|
+
vk_graph_layernorm_f32(…); vk_graph_gelu_f32(…); vk_graph_softmax_f32(…);
|
|
187
|
+
vk_graph_end_forward(); // 제출 + 한 번 대기
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
이것을 올바르게 만드는 두 가지 보조 아이디어:
|
|
191
|
+
|
|
192
|
+
- **호스트/기기 동기화 추적.** `vk_graph_mark_host()` / `vk_graph_sync_host()` 가 CPU가 건드린
|
|
193
|
+
버퍼를 추적해, 데이터가 매 노드가 아니라 실제로 필요할 때만 업로드/다운로드되게 합니다. 가중치는
|
|
194
|
+
한 번 업로드되고, 활성값은 노드 사이에 GPU에 머뭅니다.
|
|
195
|
+
- **융합이 이어집니다.** 디스패처는 융합된 노드(`add+relu`, `concat+sigmoid`)를 단일 GPU 연산으로
|
|
196
|
+
기록하므로, 그래프 수준 융합 패스(§6.8)가 GPU에서도 이득을 냅니다.
|
|
197
|
+
|
|
198
|
+
OpenGL/GLES 백엔드는 이 API(`opengl_graph_*`)를 그대로 반영합니다. 안드로이드 **NNAPI**
|
|
199
|
+
(`nnapi_engine.c`)는 큰 dense 계층을 위한 별도 선택 분기를 가집니다. Metal(`metal_engine.m`)은
|
|
200
|
+
attention, Conv1D, Mul/Sub/Div, Split, DequantizeLinear, NMS, custom profile ops 같은 선택된
|
|
201
|
+
F32 연산에 대해 Apple 전용 그래프 디스패치를 제공합니다. 정확한 네이티브 GPU 연산 지원표는
|
|
202
|
+
[`docs/operation_list.md`](../../operation_list.md)에 있습니다.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 6.7 셰이더 파이프라인 (WGSL이 유일한 소스)
|
|
207
|
+
|
|
208
|
+
네이티브 GPU 백엔드가 손으로 쓴 Vulkan/Metal/GLSL 셰이더를 필요로 할 것이라 예상할 수 있습니다.
|
|
209
|
+
그렇지 않습니다 — VolvoxAI는 **WGSL을 유일한 셰이더 언어** 로 유지하고 그것을 *교차 컴파일* 합니다.
|
|
210
|
+
`make compile_shaders` 가 `tools/compile_shaders.sh` 를 실행하는데, 이는 Mozilla의 **`naga`** 를 써서
|
|
211
|
+
모든 `shaders/*.wgsl` 을 각 네이티브 백엔드가 원하는 형식으로 번역합니다:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
shaders/*.wgsl ──naga──▶ native/shaders/spv/ (SPIR-V → Vulkan)
|
|
215
|
+
native/shaders/glsl/ (GLSL → 데스크톱 OpenGL)
|
|
216
|
+
native/shaders/gles/ (GLSL ES → 안드로이드/임베디드)
|
|
217
|
+
native/shaders/metal/ (MSL → 애플 Metal)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
커널의 셰이더를 WGSL로 한 번 쓰고, 그것을 네이티브 셰이더 형식으로 번역합니다. 하지만 런타임
|
|
221
|
+
지원에는 백엔드 래퍼와 디스패처 호출이 따로 필요합니다. 현재 Vulkan/OpenGL은 선택된 생성 셰이더를
|
|
222
|
+
연결하고, Metal은 더 작은 Apple 전용 부분집합을 `metal_graph_*` 로 연결합니다. C 커널(§6.3)과 같은
|
|
223
|
+
"하나의 소스, 여러 타깃" 원칙을 셰이더에 적용하되, 생성과 연결 상태를 별도로 추적하는 구조입니다.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 6.8 컴파일 시점 연산 융합 (`native/graph_opt_fusion.c`)
|
|
228
|
+
|
|
229
|
+
첫 순전파 전에, 네이티브 엔진은 파싱된 그래프에 융합 패스를 실행합니다(5장의 설계를, 여기서는 C
|
|
230
|
+
수준으로). 노드 목록을 제자리에서 다시 씁니다 — `fuse_relu6` 를 표시하고, 제거된 노드에 `skip` 을
|
|
231
|
+
표시하고, `concat_sigmoid_fuse` 로 태그를 답니다:
|
|
232
|
+
|
|
233
|
+
- **Conv + ReLU6** → conv의 쓰기 안에서 클램프(`fuse_relu6`).
|
|
234
|
+
- **연쇄 `Add`** → 순차 잔차 덧셈을 하나로 접기.
|
|
235
|
+
- **뎁스와이즈 → 포인트와이즈** → 중간 텐서를 흘리지 않고 MBConv 쌍을 실행.
|
|
236
|
+
- **Concat + Sigmoid** → 탐지기의 클래스-헤드 꼬리를 융합.
|
|
237
|
+
- **별칭 제거(alias elision)** → 무의미한 `Reshape`/복사 노드를 제거(`skip`).
|
|
238
|
+
|
|
239
|
+
노드가 줄고, 큰 피처 맵을 훑는 전체 패스가 줄어듭니다 — SIMD 다음으로 가장 큰 지렛대입니다.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 6.9 태스크 런타임: 텐서에서 쓸모로
|
|
244
|
+
|
|
245
|
+
`native/main.c` 는 텐서 엔진을 실제 작업으로 감싸는 CLI입니다. 디스패처는 `argv[1]` 에 대한 단순한
|
|
246
|
+
`switch` 입니다:
|
|
247
|
+
|
|
248
|
+
| 명령 | 하는 일 | 추가 장치 |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `run` | 원시 그래프 러너: 입력 텐서/이미지를 넣고 출력 텐서를 덤프 | `image_io.c` (stb_image PNG/JPEG → NHWC) |
|
|
251
|
+
| `generate` | 자기회귀 텍스트 (TinyStories) | `tokenizer.c` (BPE) + prefill/decode + KV-캐시 |
|
|
252
|
+
| `classify` | Top-K 이미지 분류 | argmax + `labels.txt` |
|
|
253
|
+
| `detect` | 객체 탐지 → 순위 박스 | 앵커 채점 + 라벨 |
|
|
254
|
+
| `ctc` | CTC 시퀀스 디코딩 (예: OCR) | CTC 병합 |
|
|
255
|
+
| `seq2seq` / `chat` | 인코더–디코더 / 챗 루프 | 크로스 어텐션 런타임 |
|
|
256
|
+
|
|
257
|
+
언급할 가치가 있는 두 보조 런타임: **`tokenizer.c`**(`js/Tokenizer.js` 와 같은 `vocab.bin` +
|
|
258
|
+
`merges.txt` 를 읽는, 처음부터 만든 바이트 수준 BPE 토크나이저)와 **`kie_runtime.c`**(영수증 핵심
|
|
259
|
+
정보 추출 작업). 모든 것이 하나의 `engine_forward` 코어 위에 놓입니다.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 6.10 KV-캐시 오케스트레이션 (네이티브 생성)
|
|
264
|
+
|
|
265
|
+
2장에서 KV-캐시를 개념적으로 소개했습니다. 네이티브 엔진이 그것이 구현된 곳입니다. 각 어텐션 노드는
|
|
266
|
+
Key와 Value 캐시(`native/engine_internal.h` 의 `g_kcache[i]`, `g_vcache[i]`)를 가집니다. 생성은 두
|
|
267
|
+
단계로 나뉩니다:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
engine_prefill(n_tokens): 프롬프트 전체에 대해 그래프를 한 번 실행, 모든 K/V 캐시를 채움
|
|
271
|
+
반복:
|
|
272
|
+
engine_last_logits() ─▶ argmax ─▶ 다음 토큰
|
|
273
|
+
engine_decode(pos): 새 위치 하나에 대해 그래프 실행, 캐시된 K/V를 읽고,
|
|
274
|
+
이 토큰의 K/V를 추가 (O(seq) 작업, O(seq²) 아님)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
이것이 정확히 실전 LLM 서버가 쓰는 prefill/decode 분할입니다 — 여기서는 몇백 줄의 C로 구현되어,
|
|
278
|
+
유난히 읽기 쉽습니다.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 6.11 네이티브 엔진 빌드하기
|
|
283
|
+
|
|
284
|
+
하나의 `clang` 줄이 전체를 빌드합니다(`Makefile` 에서):
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
clang -O3 -mavx2 -mfma -pthread -Inative \
|
|
288
|
+
native/cJSON.c native/safetensors.c native/kernels.c \
|
|
289
|
+
native/quant_cpu_opt.c native/conv_f32_opt.c native/tensor_f32_opt.c \
|
|
290
|
+
native/engine_runtime.c native/engine.c native/image_io.c native/kie_runtime.c \
|
|
291
|
+
native/vulkan_engine.c native/opengl_engine.c native/tokenizer.c native/nnapi_engine.c \
|
|
292
|
+
native/main.c -o native/volvoxai -lm -ldl
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- `make build_native` — 데스크톱 빌드(CPU + 실행 시점 `dlopen` 을 통한 Vulkan/OpenGL).
|
|
296
|
+
- `make build_android` — `-DUSE_NNAPI` 를 추가하고 안드로이드 arm64용 `nnapi_engine.c` 를 링크.
|
|
297
|
+
- `make compile_shaders` — WGSL에서 SPIR-V/GLSL/GLES/Metal을 재생성.
|
|
298
|
+
|
|
299
|
+
`-lvulkan` / `-lGL` 이 **없다** 는 점에 주목하세요: GPU 관련 플래그는 `-ldl` 하나뿐입니다. 그 하나의
|
|
300
|
+
부재가 "정적 GPU 의존성 없음" 약속 전체를, 구체적으로 실현한 것입니다.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## 6.12 설계, 그림 하나로
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
┌───────────────────────── native/volvoxai ─────────────────────────┐
|
|
308
|
+
config.json ──▶ build_graph ──▶ 융합 패스 ──▶ 가중치 사전 패킹 ──▶ engine_forward 루프 │
|
|
309
|
+
.safetensors ─▶ load_weights │ │
|
|
310
|
+
노드마다: run_node() │
|
|
311
|
+
├─ CPU: conv_f32_opt / │
|
|
312
|
+
│ quant_cpu_opt / │
|
|
313
|
+
│ kernels.c (AVX2/NEON) │
|
|
314
|
+
└─ GPU/NPU (dlopen됨): │
|
|
315
|
+
vulkan / opengl / nnapi │
|
|
316
|
+
Apple 플랫폼의 metal │
|
|
317
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
318
|
+
태스크 래퍼: run · generate · classify · detect · ctc · seq2seq · chat
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**핵심 요점:**
|
|
322
|
+
|
|
323
|
+
- VolvoxAI는 **설계상 이중 타깃(dual-target)** 입니다: 하나의 설계도가 브라우저 계층과 독립형
|
|
324
|
+
네이티브 바이너리 둘 다에 공급됩니다.
|
|
325
|
+
- 네이티브 엔진은 **범용화하지 않으면서 이식성 있습니다**: Emscripten 없음, 정적 GPU SDK 없음 — GPU
|
|
326
|
+
드라이버는 실행 시점에 `dlopen` 되므로, 하나의 바이너리가 매우 다른 기계들을 아우릅니다.
|
|
327
|
+
- **재사용이 세 축에 걸쳐 강제됩니다**: 하나의 C 커널 소스(WASM + 네이티브), 하나의 셰이더 언어
|
|
328
|
+
(WGSL → 생성된 네이티브 셰이더 형식, 백엔드별 연결), 하나의 설계도(모든 백엔드) — 순수 JS 참조를
|
|
329
|
+
정확성의 기준(oracle)으로 두고서.
|
|
330
|
+
- 모든 것은 여전히 1장의 루프로 환원됩니다: **그래프를 훑고, 각 노드를 사용 가능한 최선의 백엔드로
|
|
331
|
+
디스패치한다.** 네이티브는 단지 백엔드를 더하고 커널을 더 날카롭게 할 뿐입니다.
|
|
332
|
+
|
|
333
|
+
**다음:** [7장 — 용어집과 다음 단계 →](07-glossary-and-next-steps.md)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# 7장 — 용어집과 다음 단계
|
|
2
|
+
|
|
3
|
+
*목표: 모든 용어를 한곳에, 이 저장소를 지나는 구체적 경로, 그리고 읽기를 실력으로 바꾸는 실습.*
|
|
4
|
+
|
|
5
|
+
> 용어집 표제어는 영어 용어를 그대로 둡니다(논문·코드에서 이 영어 표현을 만나게 되므로 익혀두는 것이
|
|
6
|
+
> 중요합니다). 설명은 한국어입니다. 순서는 영어 알파벳순.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 7.1 용어집
|
|
11
|
+
|
|
12
|
+
**Activation (활성값)** — 연산 사이를 흐르는 중간 값의 텐서(*가중치* 와 대비). VolvoxAI는 fp32로
|
|
13
|
+
유지합니다(네이티브 양자화 경로에서는 int8).
|
|
14
|
+
|
|
15
|
+
**Anchor (앵커)** — 탐지기가 박스를 처음부터 예측하는 대신 조정하는, 고정된 기준 박스(사전값).
|
|
16
|
+
EfficientDet-Lite0는 격자 셀당 9개 → 총 19,206개를 씁니다.
|
|
17
|
+
|
|
18
|
+
**Attention (어텐션, SDPA)** — 각 토큰이 자신의 **Query** 를 모든 토큰의 **Key** 와 비교하고 유사도로
|
|
19
|
+
그들의 **Value** 를 혼합하는 트랜스포머 메커니즘. "앞의 어떤 단어가 나에게 중요한가?"
|
|
20
|
+
|
|
21
|
+
**Autoregressive (자기회귀)** — 시퀀스를 한 번에 한 토큰씩 생성하며, 각 출력을 다시 입력으로 넣는 것.
|
|
22
|
+
|
|
23
|
+
**Backbone (백본)** — 비전 모델의 피처 추출 단계(여기서는 EfficientNet-Lite0).
|
|
24
|
+
|
|
25
|
+
**Backend (백엔드)** — 그래프 연산의 구체적 실행기. 브라우저 백엔드는 네 개의 *계층(tier)* 이고,
|
|
26
|
+
네이티브 백엔드는 CPU, Vulkan, OpenGL/GLES, Metal, NNAPI입니다. VolvoxAI는 노드마다 하나를 고릅니다.
|
|
27
|
+
|
|
28
|
+
**BiFPN** — Bi-directional Feature Pyramid Network. resize/pool/add로 해상도 간 피처를 융합해 모든
|
|
29
|
+
스케일이 디테일과 의미를 모두 갖게 합니다.
|
|
30
|
+
|
|
31
|
+
**BPE (바이트 페어 인코딩)** — 토크나이저 알고리즘: 바이트에서 시작해, 학습된 병합 목록에 따라 가장
|
|
32
|
+
빈번한 인접 쌍을 반복적으로 병합합니다.
|
|
33
|
+
|
|
34
|
+
**Broadcast (브로드캐스트)** — 원소별 연산에서 작은 텐서를 큰 텐서에 맞게 늘리는 것(예: 채널별 편향
|
|
35
|
+
`[C]` 를 `[N,H,W,C]` 에 더하기).
|
|
36
|
+
|
|
37
|
+
**Causal mask (인과적 마스크)** — 위치 *q* 가 `≤ q` 인 위치만 보도록 어텐션을 제한하는 것. 왼쪽→오른쪽
|
|
38
|
+
생성기를 만듭니다.
|
|
39
|
+
|
|
40
|
+
**Channel (채널)** — 텐서의 한 "피처 평면"(NHWC의 `C`). 입력 이미지는 3개(RGB), 은닉층은 여러 개를
|
|
41
|
+
가집니다.
|
|
42
|
+
|
|
43
|
+
**Convolution (합성곱, Conv2D)** — 작은 학습 필터를 이미지 위로 슬라이드하며 각 지점에서 내적해 패턴을
|
|
44
|
+
어디서나 감지. **뎁스와이즈** = 채널별 공간 필터, **포인트와이즈** = 1×1 채널 혼합기. 둘을 합친 것
|
|
45
|
+
(**뎁스와이즈 분리형**)이 저렴하며 백본을 구동합니다.
|
|
46
|
+
|
|
47
|
+
**Dequantize (역양자화)** — int8을 float로 되돌리기: `r = (q − zero_point) × scale`.
|
|
48
|
+
|
|
49
|
+
**dlopen / dlsym** — 공유 라이브러리를 로드하고 그 함수를 *실행 시점에* (링크 시점이 아니라) 찾는 것.
|
|
50
|
+
VolvoxAI의 네이티브 바이너리가 GPU SDK를 링크하지 않고 GPU 드라이버(`libvulkan`, `libGL`)를 쓰는
|
|
51
|
+
방식 — "정적 GPU 의존성 없음" 설계입니다.
|
|
52
|
+
|
|
53
|
+
**Embedding (임베딩)** — 이산 토큰을 나타내는 학습된 벡터. `Embedding` 연산은 표 조회입니다.
|
|
54
|
+
|
|
55
|
+
**Forward pass / Inference (순전파 / 추론)** — 그래프를 한 번 실행, 입력 → 출력. VolvoxAI는 이것만
|
|
56
|
+
합니다(학습 없음).
|
|
57
|
+
|
|
58
|
+
**Fusion (융합)** — 인접 연산을 병합해(예: Conv+ReLU) 중간 데이터를 한 번만 쓰는 것.
|
|
59
|
+
|
|
60
|
+
**GELU / ReLU / ReLU6 / Sigmoid** — 비선형성. 선형 층들 사이의 "결정 곡선". 이것들이 없으면 쌓인
|
|
61
|
+
MatMul이 하나로 붕괴합니다.
|
|
62
|
+
|
|
63
|
+
**GEMM** — GEneral Matrix Multiply(일반 행렬 곱). 대부분의 빠른 conv/matmul 경로가 귀결되는, 고도로
|
|
64
|
+
최적화된 루틴.
|
|
65
|
+
|
|
66
|
+
**Graph (그래프)** — 모델의 연산 목록: 이름 붙은 텐서로 연결된 노드(연산)들. `config.json` 으로 저장.
|
|
67
|
+
|
|
68
|
+
**Head (헤드)** — 최종 작업별 층: LM 헤드(→ 어휘 로짓) 또는 탐지기의 클래스/박스 헤드.
|
|
69
|
+
|
|
70
|
+
**im2col** — "image to columns". conv 입력 패치를 행렬로 펼쳐 conv를 GEMM으로 만드는 것.
|
|
71
|
+
|
|
72
|
+
**int8 / fp16 / fp32** — 8비트 정수 / 16비트 float / 32비트 float 숫자 형식(1 / 2 / 4 바이트).
|
|
73
|
+
4장 참고.
|
|
74
|
+
|
|
75
|
+
**KV-cache (KV-캐시)** — 지난 토큰들의 Key와 Value를 캐시해 각 생성 단계가 새 토큰의 어텐션만
|
|
76
|
+
계산하게 하는 것. 이 저장소에서는 `engine_prefill` + `engine_decode`.
|
|
77
|
+
|
|
78
|
+
**LayerNorm / RMSNorm** — 벡터를 정규화(평균 0, 분산 1, 그다음 학습된 스케일/이동)해 깊은 신경망의
|
|
79
|
+
숫자를 안정적으로 유지.
|
|
80
|
+
|
|
81
|
+
**Logits (로짓)** — 원시의, 정규화되지 않은 점수(소프트맥스/시그모이드 이전). LM 헤드와 클래스 헤드가
|
|
82
|
+
내놓습니다.
|
|
83
|
+
|
|
84
|
+
**MatMul (행렬 곱)** — 트랜스포머의 핵심 특징 혼합 연산.
|
|
85
|
+
|
|
86
|
+
**MBConv** — Mobile inverted BOTTLENECK conv 블록: 확장 → 뎁스와이즈 → 투영, 잔차 포함. 백본의 반복
|
|
87
|
+
단위입니다.
|
|
88
|
+
|
|
89
|
+
**NHWC / NCHW** — 텐서 차원 순서(배치, 높이, 너비, 채널) vs (배치, 채널, 높이, 너비). VolvoxAI 비전
|
|
90
|
+
모델은 NHWC를 씁니다.
|
|
91
|
+
|
|
92
|
+
**naga** — VolvoxAI가 하나의 WGSL 셰이더를 SPIR-V(Vulkan), GLSL(OpenGL), GLSL ES, MSL(Metal)로 교차
|
|
93
|
+
컴파일할 때 쓰는 Rust 도구. 생성된 셰이더 출력은 런타임 지원과 같지 않습니다. 해당 op를 그 백엔드에서
|
|
94
|
+
실행하려면 네이티브 디스패처가 백엔드 래퍼를 연결해야 합니다.
|
|
95
|
+
|
|
96
|
+
**NMS (비최대 억제, Non-Max Suppression)** — 겹치는 중복 탐지를 제거하고 객체당 점수가 가장 높은
|
|
97
|
+
박스를 남기는 후처리.
|
|
98
|
+
|
|
99
|
+
**NNAPI** — 안드로이드의 Neural Networks API. VolvoxAI의 네이티브 엔진이 안드로이드에서 이것으로
|
|
100
|
+
디스패치할 수 있습니다(`native/nnapi_engine.c`, `make build_android`).
|
|
101
|
+
|
|
102
|
+
**Node (노드)** — 그래프의 한 항목: 연산 + 그 입력/출력 텐서 이름과 파라미터.
|
|
103
|
+
|
|
104
|
+
**Op / Operation / Kernel (연산 / 커널)** — 하나의 수학 루틴(Add, Conv2D, SDPA…). "Op"은 그래프 수준의
|
|
105
|
+
이름, "kernel"은 그것의 구체적 구현입니다.
|
|
106
|
+
|
|
107
|
+
**Quantization (양자화)** — `scale` + `zero_point` 로 가중치/활성값을 더 적은 비트로 표현하는 것.
|
|
108
|
+
|
|
109
|
+
**Residual (잔차, skip connection)** — 블록의 입력을 출력에 더하는 것(`out = x + f(x)`). 정보와 기울기가
|
|
110
|
+
깊은 층을 지나 살아남게 합니다. 두 모델 모두에 있습니다.
|
|
111
|
+
|
|
112
|
+
**Safetensors** — 가중치의 표준 바이너리 파일 형식.
|
|
113
|
+
|
|
114
|
+
**Scale / Zero-point (스케일 / 제로포인트)** — 양자화 레시피의 두 숫자: 눈금 크기, 그리고 어떤 정수가
|
|
115
|
+
실제 0을 의미하는지.
|
|
116
|
+
|
|
117
|
+
**Softmax (소프트맥스)** — 점수 벡터를 확률 분포(양수, 합이 1)로 바꾸는 것.
|
|
118
|
+
|
|
119
|
+
**SPIR-V** — Vulkan이 소비하는 바이너리 셰이더 형식. `naga` 가 VolvoxAI의 WGSL을 여기로 컴파일합니다.
|
|
120
|
+
|
|
121
|
+
**Prefill / Decode (프리필 / 디코드)** — 네이티브 텍스트 생성의 두 단계: *prefill* 은 프롬프트를 한 번
|
|
122
|
+
실행해 KV-캐시를 채우고, *decode* 는 캐시를 써서 한 번에 새 토큰 하나를 실행합니다. `engine_prefill` /
|
|
123
|
+
`engine_decode` 참고.
|
|
124
|
+
|
|
125
|
+
**Tensor (텐서)** — 형태를 가진 다차원 숫자 배열. 엔진의 유일한 자료형입니다.
|
|
126
|
+
|
|
127
|
+
**Tier (계층)** — VolvoxAI의 네 브라우저 백엔드(WebNN / WebGPU / WASM / 순수 JS) 중 하나. 성능에 따라
|
|
128
|
+
선택됩니다.
|
|
129
|
+
|
|
130
|
+
**Token (토큰)** — 정수 id로 매핑된 텍스트 조각(단어/하위 단어/바이트).
|
|
131
|
+
|
|
132
|
+
**Weight (가중치)** — 학습 중에 얻어진 숫자. 추론 시 읽기 전용.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 7.2 이 저장소를 지나는 경로
|
|
137
|
+
|
|
138
|
+
"개념은 이해했다"에서 "엔진을 수정할 수 있다"로 가려면 이 순서로 읽으세요:
|
|
139
|
+
|
|
140
|
+
1. **자료 모델** — `js/Tensor.js`(25줄), `js/Graph.js`(48줄). 작으니 전부 읽으세요.
|
|
141
|
+
2. **실행기** — `js/CPUEngine.js`. `for (node of graph.nodes)` 루프와 `switch` 디스패치를 보세요.
|
|
142
|
+
이것이 런타임 전부입니다.
|
|
143
|
+
3. **네 개의 소박한 커널** — `js/ops/add.js`, `embedding.js`, `layerNorm.js`, `matMul.js`. 각각 몇십
|
|
144
|
+
줄로 읽기 쉽습니다.
|
|
145
|
+
4. **두 모델의 설계도** — `models/tinystories_1m/config.json` 와
|
|
146
|
+
`models/efficientdet_lite0_fp32/config.json` 을 훑고, 노드를 2–3장과 맞춰 보세요.
|
|
147
|
+
5. **어텐션 + conv 커널** — `js/ops/sDPA.js`, `js/ops/conv2D.js`. 두 개의 "심장".
|
|
148
|
+
6. **양자화** — `js/ops/dequantizeLinear.js`, 그다음 실제 int8 conv는 `native/quant_cpu_opt.c`.
|
|
149
|
+
7. **최적화** — `js/ops/conv2D.js` 를 `native/conv_f32_opt.c` 와 비교하며
|
|
150
|
+
`docs/microkernel_optimization_guide.md` 와 `docs/xnnpack_optimization_guide.md` 를 읽으세요.
|
|
151
|
+
8. **GPU 계층** — `shaders/*.wgsl`(예: `matmul`)과 `js/GraphExecutor.js`.
|
|
152
|
+
9. **네이티브 엔진**(6장) — `native/engine.h` + `native/engine.c`(수명 주기),
|
|
153
|
+
`native/engine_runtime.c`(`run_node` 백엔드 선택), 그다음 `native/vulkan_engine.c` 같은 기기 백엔드
|
|
154
|
+
(맨 위의 `dlopen` 을 보세요). `native/main.c` 에 태스크 CLI가 있습니다.
|
|
155
|
+
|
|
156
|
+
`docs/operation_list.md` 는 연산×백엔드 지원 행렬입니다 — 당신의 참조 지도지요.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 7.3 모델을 직접 실행해 보기
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# 언어 모델 — 텍스트 생성(탐욕적). 먼저 네이티브 바이너리 빌드: `make build_native`.
|
|
164
|
+
./native/volvoxai generate models/tinystories_1m \
|
|
165
|
+
--prompt "Once upon a time, Lily" --max-new 50 [--debug]
|
|
166
|
+
|
|
167
|
+
# 원시 그래프 러너 — 고정 토큰 집합에 대한 로짓 텐서 덤프.
|
|
168
|
+
./native/volvoxai run models/tinystories_1m \
|
|
169
|
+
--input tokens=models/tinystories_1m/tokens.i32 \
|
|
170
|
+
--input positions=models/tinystories_1m/positions.i32 \
|
|
171
|
+
--output logits=out.f32 --last-token 4
|
|
172
|
+
|
|
173
|
+
# 객체 탐지기 — 이미지를 순위 박스로 디코드.
|
|
174
|
+
./native/volvoxai detect models/efficientdet_lite0_int8 \
|
|
175
|
+
--image input0=photo.png --image-normalize raw-255 \
|
|
176
|
+
--boxes boxes --scores scores --max-det 20
|
|
177
|
+
|
|
178
|
+
# Node에서(WASM / 순수 JS 계층), 아무 설계도나 스모크 테스트:
|
|
179
|
+
node bin/volvox.js run --model models/tinystories_1m/model.safetensors --backend wasm
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`generate` 에 `--debug` 를 붙이면 노드별 시간과 초당 토큰(tokens/sec)을 볼 수 있습니다 — 시간이
|
|
183
|
+
어디로 가는지 *체감* 하기(그리고 5장의 최적화가 효과를 내는 것을 지켜보기)에 좋은 방법입니다.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 7.4 실습 (읽기 → 실력)
|
|
188
|
+
|
|
189
|
+
1. **손으로 따라가기.** 시퀀스 `[5, 5]`(동일한 토큰 두 개)와 가상의 2차원 임베딩을 가정하세요.
|
|
190
|
+
`Embedding → Add(위치) → LayerNorm` 을 종이와 펜으로 따라가고, 형태가 `config.json` 과 맞는지
|
|
191
|
+
확인하세요.
|
|
192
|
+
2. **인과성 깨기.** `js/ops/sDPA.js` 에서 `k <= q` 를 `k < seq_len` 으로 바꾸세요. 생성 텍스트에 무슨
|
|
193
|
+
일이 왜 일어날지 예측하세요. (그다음 되돌리세요.)
|
|
194
|
+
3. **가중치 양자화하기.** `scale = 0.02`, `zero_point = -5` 를 고르세요. `r = 0.31` 을 양자화한 뒤
|
|
195
|
+
다시 역양자화하세요. 왕복 오차를 보고하세요. 이제 `scale = 0.002` 를 시도하세요. 정밀도가 범위에서
|
|
196
|
+
무엇을 대가로 치렀나요?
|
|
197
|
+
4. **FLOP 세기.** 첫 `Conv2D`(stem: 320×320×3 → 160×160×32, 3×3 필터)의 곱-덧셈 수를 추정하세요. 같은
|
|
198
|
+
출력 크기의 1×1 포인트와이즈 conv와 비교하세요. 왜 뎁스와이즈 분리형이 더 저렴한가요?
|
|
199
|
+
5. **연산 추가하기.** `js/ops/` 에 원소별 `Abs` 커널을 구현하고, `CPUEngine.js` 의 `switch` 에 연결한
|
|
200
|
+
뒤, 디스패치되는지 확인하세요. (`js/ops/reLU.js` 를 템플릿으로 따라 하세요.)
|
|
201
|
+
6. **융합 찾기.** `models/efficientdet_lite0_fp32/config.json` 에서 `relu` 파라미터가 설정된 `Conv2D`
|
|
202
|
+
를 찾으세요 — 그것이 이미 구워진 Conv+ReLU 융합입니다. 그것이 어떤 두 연산을 나타내는지 설명하세요.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 7.5 이 코드베이스의 현재 빈틈
|
|
207
|
+
|
|
208
|
+
VolvoxAI는 추론 엔진이고, 이 교과서도 그 경계를 따릅니다. 이 코드베이스는 학습된 가중치를 로드하고
|
|
209
|
+
순전파를 실행할 수 있지만, 모델을 만들고 튜닝하고 과학적으로 검증하는 데 필요한 시스템은 아직 없습니다.
|
|
210
|
+
|
|
211
|
+
| 빠진 영역 | 추가되어야 할 것 | 왜 중요한가 |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| **학습** | reverse-mode autodiff, backward 커널, 손실 함수, Adam/SGD 같은 최적화기, 학습률 스케줄, 초기화, 체크포인팅, 정규화. | 가중치를 소비하는 대신 발견하는 방법입니다. |
|
|
214
|
+
| **수학 기초** | 선형대수 유도, 연쇄 법칙과 기울기를 위한 미적분, 확률, entropy/cross-entropy, KL divergence, likelihood. | 학습과 평가가 왜 그렇게 움직이는지 설명하는 도구입니다. |
|
|
215
|
+
| **데이터** | 데이터셋 manifest, 스트리밍/입력 파이프라인, 증강, 정제, 토크나이저 학습, train/validation/test 분할, 누수 점검. | 모델 품질은 보통 데이터 품질과 실험 위생에 의해 제한됩니다. |
|
|
216
|
+
| **평가와 실험** | 작업별 지표, 검증 루프, baseline, ablation, 하이퍼파라미터 sweep, 과적합 점검, bias-variance 분석. | 모델이 실제로 더 좋아졌는지, 단지 달라졌는지 구분하는 방법입니다. |
|
|
217
|
+
| **아키텍처 폭** | diffusion, graph neural network, RNN/LSTM, 강화학습, VAE/GAN, retrieval과 embedding 시스템, multimodal 모델, mixture-of-experts, state-space model. | 현재 설명은 트랜스포머 LM 하나와 CNN 탐지기 하나입니다. 많은 도메인은 다른 inductive bias를 씁니다. |
|
|
218
|
+
| **최신 LLM 학습 스택** | 사전학습 루프, 지도 미세조정, LoRA/adapter, RLHF/DPO 선호 학습, 분산 데이터/모델 병렬화, FlashAttention 내부. | 대규모 LLM 작업의 대부분은 학습 레시피, 메모리 효율적 어텐션, 대규모 시스템 주변에서 일어납니다. |
|
|
219
|
+
| **연구 실무** | 논문 재현, 결과 유도, 통제 실험, scaling-law 분석, 오류 분석, 가정 문서화. | 모델을 실행하는 것과 신뢰할 수 있는 새 지식을 만드는 것의 차이입니다. |
|
|
220
|
+
|
|
221
|
+
이것들은 추론 장들의 선행 조건이 아니라, 앞으로 추가될 수 있는 교과서/코드 모듈입니다. 범위는 명확합니다:
|
|
222
|
+
이 저장소는 강한 추론/런타임 기초이지만, 완전한 학습·연구 커리큘럼은 아닙니다.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 7.6 여기서 어디로 갈까
|
|
227
|
+
|
|
228
|
+
자연스러운 다음 단계는 두 갈래로 나뉩니다:
|
|
229
|
+
|
|
230
|
+
- **이 저장소의 추론 경로를 더 깊게 파기.** `native/conv_f32_opt.c` 를 Google의 **XNNPACK** 과
|
|
231
|
+
비교하세요. 이 저장소의 `docs/xnnpack_optimization_guide.md` 가 안내된 투어입니다. 그다음
|
|
232
|
+
`shaders/*.wgsl` 과 네이티브 GPU 백엔드를 살펴보세요.
|
|
233
|
+
- **트랜스포머 키우기.** GPT-2/3, LLaMA, Mistral, Qwen은 2장의 그래프를 더 넓고 깊게 한 것에 약간의
|
|
234
|
+
변형을 더한 것입니다: LayerNorm 대신 **RMSNorm**, 학습된 `wpe` 대신 **RoPE** 회전 위치,
|
|
235
|
+
**그룹 쿼리 어텐션(grouped-query attention)**, **SwiGLU** MLP. 각각은 당신이 아는 연산의 작은
|
|
236
|
+
변주입니다.
|
|
237
|
+
- **아키텍처 넓히기.** 분류, 분할, 자세, diffusion, retrieval, multimodal, MoE, SSM 시스템은 모두
|
|
238
|
+
텐서/그래프 멘탈 모델을 재사용하지만, 서로 다른 블록과 학습 목표를 더합니다.
|
|
239
|
+
- **학습 빈틈을 의도적으로 메우기.** 작은 autograd 엔진, cross-entropy 손실, Adam, 작은 데이터셋 로더,
|
|
240
|
+
검증 루프가 첫 번째 구체적 추가가 될 수 있습니다.
|
|
241
|
+
- **원 논문 읽기**, 메커니즘이 구체화된 뒤: *Attention Is All You Need*(트랜스포머),
|
|
242
|
+
*EfficientDet*(이 탐지기), *EfficientNet*(백본), 그리고 양자화 입문(예: "gemmlowp"/TFLite 정수
|
|
243
|
+
양자화 글).
|
|
244
|
+
- **이웃 런타임 탐색하기**: **wonnx**(WebGPU/ONNX), **ncnn**(무의존성 네이티브),
|
|
245
|
+
**ggml/llama.cpp**(이식성 C LLM 추론).
|
|
246
|
+
|
|
247
|
+
여기서 세운 멘탈 모델 — *모델은 학습된 가중치를 가진 작은 텐서 연산의 그래프다. 추론은 그래프를
|
|
248
|
+
훑는다. 성능은 메모리 배치다. 정밀도는 크기/정확도 다이얼이다* — 은 앞으로의 모듈들에도 전이되지만,
|
|
249
|
+
그것은 전체 스택의 한 부분입니다.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
*VolvoxAI 교과서의 끝. [목차](README.md)로 돌아가기.*
|