amv-train 0.0.0-stage → 2.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 +57 -0
- package/NOTICE +17 -0
- package/README.md +388 -2
- package/README.pypi.md +90 -0
- package/bin/cli.js +224 -0
- package/index.d.ts +182 -0
- package/index.js +39 -0
- package/lib/benchmark.js +118 -0
- package/lib/doctor.js +149 -0
- package/lib/errors.js +60 -0
- package/lib/multimodal.js +95 -0
- package/lib/trainer.js +350 -0
- package/package.json +55 -4
package/bin/cli.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0 OR Commercial-License
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* termux-train: Node.js Global CLI Binary.
|
|
9
|
+
* High-performance on-device Deep Learning & LoRA Training Framework.
|
|
10
|
+
* Open-Source under Apache License 2.0.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { spawnSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
const { version } = require('../package.json');
|
|
20
|
+
const { runDoctor, resolvePythonCmd } = require('../lib/doctor');
|
|
21
|
+
const { runBenchmark } = require('../lib/benchmark');
|
|
22
|
+
const { TermuxTrainer } = require('../lib/trainer');
|
|
23
|
+
const {
|
|
24
|
+
PythonRuntimeNotFoundError,
|
|
25
|
+
TermuxTrainModuleNotFoundError
|
|
26
|
+
} = require('../lib/errors');
|
|
27
|
+
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const command = args[0] || '--help';
|
|
30
|
+
|
|
31
|
+
function getArg(flag, alias = null) {
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const item = args[i];
|
|
34
|
+
if (item.startsWith(`${flag}=`)) {
|
|
35
|
+
return item.slice(flag.length + 1);
|
|
36
|
+
}
|
|
37
|
+
if (alias && item.startsWith(`${alias}=`)) {
|
|
38
|
+
return item.slice(alias.length + 1);
|
|
39
|
+
}
|
|
40
|
+
if (item === flag || (alias && item === alias)) {
|
|
41
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
42
|
+
return args[i + 1];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasFlag(flag, alias = null) {
|
|
50
|
+
return args.includes(flag) || (alias && args.includes(alias));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printHelp() {
|
|
54
|
+
console.log(`termux-train CLI v${version} (Unified Node.js & Python Dual Engine)`);
|
|
55
|
+
console.log('Usage: termux-train <command> [options]\n');
|
|
56
|
+
console.log('Core Commands:');
|
|
57
|
+
console.log(' doctor Inspect device hardware, RAM tier, and Vulkan GPU');
|
|
58
|
+
console.log(' check Run self-diagnostic mathematical checks across backends');
|
|
59
|
+
console.log(' score Run 0-point baseline granular audit scoring system');
|
|
60
|
+
console.log(' benchmark [options] Run on-device GEMM & Autograd latency benchmark');
|
|
61
|
+
console.log(' train [options] Run on-device training / LoRA loop');
|
|
62
|
+
console.log(' peft [options] Run on-device PEFT (LoRA / DoRA) training & export');
|
|
63
|
+
console.log(' diffusion-train [options] Run on-device Image Folder Diffusion LoRA training');
|
|
64
|
+
console.log(' vision-train [options] Run on-device Vision VLM Multimodal LoRA training');
|
|
65
|
+
console.log(' stt-train [options] Run on-device Whisper STT Cross-Attention LoRA training');
|
|
66
|
+
console.log(' tts-train [options] Run on-device Audio TTS Speaker Adaptation LoRA training');
|
|
67
|
+
console.log(' rl [options] Run on-device Reinforcement Learning (GRPO / DPO / PPO)');
|
|
68
|
+
console.log(' cluster-worker [options] Start symmetric RPC worker server for Virtual RAM pooling');
|
|
69
|
+
console.log(' cluster-probe [options] Probe cluster RPC nodes and inspect pooled virtual RAM');
|
|
70
|
+
console.log(' export [options] Export trained adapter to target runtime format');
|
|
71
|
+
console.log(' demo <1..8> Execute one of 8 canonical example demos\n');
|
|
72
|
+
console.log('Options:');
|
|
73
|
+
console.log(' --json Output results in standard JSON format');
|
|
74
|
+
console.log(' --dim <num> Matrix dimension for benchmark / model dim (default: 256 / 32)');
|
|
75
|
+
console.log(' --data <path> Path to dataset file (.safetensors, .jsonl, .txt)');
|
|
76
|
+
console.log(' --image-dir <path> Directory containing training images for diffusion');
|
|
77
|
+
console.log(' --output <path> Output path for exported SafeTensors LoRA adapter');
|
|
78
|
+
console.log(' --epochs <num> Number of training epochs (default: 5)');
|
|
79
|
+
console.log(' --lr <val> Learning rate (default: 0.001)');
|
|
80
|
+
console.log(' --batch-size <num> Mini-batch size (default: 16)');
|
|
81
|
+
console.log(' --rank <num> LoRA rank (default: 4 / 8)');
|
|
82
|
+
console.log(' --backend <name> Compute backend (auto, vulkan, amuda, numpy, python)');
|
|
83
|
+
console.log(' --checkpoint <path> Path to save SafeTensors checkpoint');
|
|
84
|
+
console.log(' -v, --version Display version information');
|
|
85
|
+
console.log(' -h, --help Show this help message');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main() {
|
|
89
|
+
if (hasFlag('-v') || hasFlag('--version')) {
|
|
90
|
+
console.log(`termux-train ${version}`);
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (command === '--help' || command === '-h' || command === 'help') {
|
|
95
|
+
printHelp();
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (command === 'doctor') {
|
|
100
|
+
const isJson = hasFlag('--json');
|
|
101
|
+
const rep = runDoctor();
|
|
102
|
+
|
|
103
|
+
if (isJson) {
|
|
104
|
+
console.log(JSON.stringify(rep, null, 2));
|
|
105
|
+
} else {
|
|
106
|
+
console.log('=== termux-train Diagnostic Doctor (Node.js Engine) ===');
|
|
107
|
+
console.log(` Platform : ${rep.platform.system} (${rep.platform.arch}) | Android/Termux: ${rep.platform.isAndroid || rep.platform.isTermux}`);
|
|
108
|
+
console.log(` Hardware Tier : ${rep.hardware.tier} (RAM: ${rep.hardware.totalRamMb}MB | Cores: ${rep.hardware.cpuCores})`);
|
|
109
|
+
console.log(` Python Runtime : ${rep.python.available ? rep.python.version : 'NOT FOUND'} (${rep.python.moduleInstalled ? 'termux_train installed' : 'module missing'})`);
|
|
110
|
+
console.log(` Vulkan GPU : ${rep.vulkan.status}`);
|
|
111
|
+
console.log(` Preset Config : Max LoRA Rank r=${rep.recommendedTrainingConfig.loraRank} | Batch Size=${rep.recommendedTrainingConfig.batchSize}`);
|
|
112
|
+
console.log(` Overall Status : ${rep.status}`);
|
|
113
|
+
}
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (command === 'benchmark') {
|
|
118
|
+
const isJson = hasFlag('--json');
|
|
119
|
+
const dim = parseInt(getArg('--dim') || '256', 10);
|
|
120
|
+
try {
|
|
121
|
+
const res = runBenchmark({ dim });
|
|
122
|
+
if (isJson) {
|
|
123
|
+
console.log(JSON.stringify(res, null, 2));
|
|
124
|
+
} else {
|
|
125
|
+
console.log(`=== termux-train Benchmark (Dimension: ${res.dimension}) ===`);
|
|
126
|
+
console.log(` Backend : ${res.backend}`);
|
|
127
|
+
console.log(` Forward GEMM Latency : ${res.gemmLatencyMs} ms`);
|
|
128
|
+
console.log(` Full Autograd Step (F+B) : ${res.autogradStepLatencyMs} ms`);
|
|
129
|
+
console.log(` Throughput : ${res.throughputGflops} GFLOPS`);
|
|
130
|
+
}
|
|
131
|
+
process.exit(0);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(`[ERROR] Benchmark failed: ${err.message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (command === 'train') {
|
|
139
|
+
const epochs = parseInt(getArg('--epochs') || '5', 10);
|
|
140
|
+
const lr = parseFloat(getArg('--lr') || '0.001');
|
|
141
|
+
const modelType = getArg('--model') || 'mlp';
|
|
142
|
+
const loraRank = parseInt(getArg('--rank') || '8', 10);
|
|
143
|
+
const dim = parseInt(getArg('--dim') || '32', 10);
|
|
144
|
+
const checkpoint = getArg('--checkpoint') || null;
|
|
145
|
+
const resume = getArg('--resume') || null;
|
|
146
|
+
const data = getArg('--data') || getArg('--dataPath') || null;
|
|
147
|
+
const backend = getArg('--backend') || 'auto';
|
|
148
|
+
const batchSize = parseInt(getArg('--batch-size') || getArg('--batchSize') || '16', 10);
|
|
149
|
+
const seqLen = parseInt(getArg('--seq-len') || getArg('--seqLen') || '32', 10);
|
|
150
|
+
|
|
151
|
+
if (data && !fs.existsSync(path.resolve(data))) {
|
|
152
|
+
console.error(`[ERROR] Specified dataset path does not exist: "${data}"`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (resume && !fs.existsSync(path.resolve(resume))) {
|
|
157
|
+
console.error(`[ERROR] Specified resume checkpoint path does not exist: "${resume}"`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`[*] Initializing TermuxTrainer (model=${modelType}, dim=${dim}, rank=${loraRank}, epochs=${epochs}, lr=${lr}, batchSize=${batchSize}, backend=${backend}, data=${data || 'synthetic'}, resume=${resume || 'none'})...`);
|
|
162
|
+
const trainer = new TermuxTrainer({
|
|
163
|
+
modelType,
|
|
164
|
+
dim,
|
|
165
|
+
loraRank,
|
|
166
|
+
lr,
|
|
167
|
+
batchSize,
|
|
168
|
+
seqLen,
|
|
169
|
+
backend
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
trainer.on('step', (m) => {
|
|
173
|
+
console.log(` [Epoch ${m.epoch}/${m.totalEpochs}] Loss: ${m.loss.toFixed(6)} (${m.latencyMs} ms)`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
trainer.on('checkpoint', (ckpt) => {
|
|
177
|
+
console.log(` [Checkpoint] Saved SafeTensors to: ${ckpt.path} (${ckpt.sizeBytes} bytes)`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const res = await trainer.fit({
|
|
182
|
+
epochs,
|
|
183
|
+
checkpointPath: checkpoint,
|
|
184
|
+
resumePath: resume ? path.resolve(resume) : null,
|
|
185
|
+
dataPath: data ? path.resolve(data) : null
|
|
186
|
+
});
|
|
187
|
+
console.log(`[+] Training completed successfully in ${res.epochs} epochs.`);
|
|
188
|
+
trainer.dispose();
|
|
189
|
+
process.exit(0);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error(`[ERROR] Training failed: ${err.message}`);
|
|
192
|
+
trainer.dispose();
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const forwardedCommands = new Set([
|
|
198
|
+
'check', 'score', 'demo', 'info',
|
|
199
|
+
'diffusion-train', 'vision-train', 'stt-train', 'tts-train',
|
|
200
|
+
'peft', 'lora', 'rl', 'export',
|
|
201
|
+
'cluster-worker', 'cluster-probe',
|
|
202
|
+
'component', 'model', 'instance'
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
if (forwardedCommands.has(command)) {
|
|
206
|
+
const pyCmd = resolvePythonCmd();
|
|
207
|
+
if (!pyCmd) {
|
|
208
|
+
console.error('[ERROR] Python runtime is required to execute native training pipelines.');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
const pyArgs = ['-m', 'termux_train.cli', ...args];
|
|
212
|
+
const res = spawnSync(pyCmd, pyArgs, { stdio: 'inherit' });
|
|
213
|
+
process.exit(res.status || 0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.error(`[ERROR] Unknown command: '${command}'`);
|
|
217
|
+
printHelp();
|
|
218
|
+
process.exit(2);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
main().catch((err) => {
|
|
222
|
+
console.error(`[FATAL] ${err.message}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
});
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0 OR Commercial-License
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Type declarations for termux-train Node.js SDK
|
|
7
|
+
* Open-Source under Apache License 2.0.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
|
|
12
|
+
export const version: string;
|
|
13
|
+
export const __version__: string;
|
|
14
|
+
|
|
15
|
+
export interface DoctorPlatformInfo {
|
|
16
|
+
system: string;
|
|
17
|
+
arch: string;
|
|
18
|
+
isTermux: boolean;
|
|
19
|
+
isAndroid: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DoctorHardwareInfo {
|
|
23
|
+
cpuCores: number;
|
|
24
|
+
totalRamMb: number;
|
|
25
|
+
freeRamMb: number;
|
|
26
|
+
tier: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DoctorPythonInfo {
|
|
30
|
+
available: boolean;
|
|
31
|
+
version: string;
|
|
32
|
+
binary: string | null;
|
|
33
|
+
moduleInstalled: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DoctorVulkanInfo {
|
|
37
|
+
gpuAcceleration: boolean;
|
|
38
|
+
status: 'AVAILABLE' | 'CPU_FALLBACK';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RecommendedTrainingConfig {
|
|
42
|
+
loraRank: number;
|
|
43
|
+
batchSize: number;
|
|
44
|
+
seqLen: number;
|
|
45
|
+
safeTensorsMmap: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DoctorReport {
|
|
49
|
+
framework: 'termux-train';
|
|
50
|
+
version: string;
|
|
51
|
+
platform: DoctorPlatformInfo;
|
|
52
|
+
hardware: DoctorHardwareInfo;
|
|
53
|
+
python: DoctorPythonInfo;
|
|
54
|
+
vulkan: DoctorVulkanInfo;
|
|
55
|
+
recommendedTrainingConfig: RecommendedTrainingConfig;
|
|
56
|
+
status: 'READY' | 'SETUP_REQUIRED';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface BenchmarkOptions {
|
|
60
|
+
dim?: number;
|
|
61
|
+
iters?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface BenchmarkResult {
|
|
65
|
+
dimension: string;
|
|
66
|
+
iterations: number;
|
|
67
|
+
backend: string;
|
|
68
|
+
gemmLatencyMs: number;
|
|
69
|
+
autogradStepLatencyMs: number;
|
|
70
|
+
throughputGflops: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface TrainerConfig {
|
|
74
|
+
modelType?: 'mlp' | 'transformer' | 'transformer-lm' | 'lora' | 'linear-lora' | 'rope';
|
|
75
|
+
dim?: number;
|
|
76
|
+
hiddenDim?: number;
|
|
77
|
+
outDim?: number;
|
|
78
|
+
loraRank?: number;
|
|
79
|
+
loraAlpha?: number;
|
|
80
|
+
vocabSize?: number;
|
|
81
|
+
layers?: number;
|
|
82
|
+
heads?: number;
|
|
83
|
+
lr?: number;
|
|
84
|
+
batchSize?: number;
|
|
85
|
+
seqLen?: number;
|
|
86
|
+
backend?: 'auto' | 'cpu' | 'numpy' | 'vulkan';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface FitOptions {
|
|
90
|
+
epochs?: number;
|
|
91
|
+
lr?: number;
|
|
92
|
+
checkpointPath?: string;
|
|
93
|
+
dataPath?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TrainingMetrics {
|
|
97
|
+
event: 'step' | 'epoch';
|
|
98
|
+
epoch: number;
|
|
99
|
+
totalEpochs: number;
|
|
100
|
+
loss: number;
|
|
101
|
+
latencyMs: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface CheckpointInfo {
|
|
105
|
+
event: 'checkpoint';
|
|
106
|
+
path: string;
|
|
107
|
+
tensorsSaved: number;
|
|
108
|
+
sizeBytes: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface FitResult {
|
|
112
|
+
status: 'SUCCESS' | 'FAILED';
|
|
113
|
+
epochs: number;
|
|
114
|
+
modelType: string;
|
|
115
|
+
checkpoint: CheckpointInfo | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class TermuxTrainer extends EventEmitter {
|
|
119
|
+
constructor(config?: TrainerConfig);
|
|
120
|
+
fit(options?: FitOptions): Promise<FitResult>;
|
|
121
|
+
stop(): Promise<void>;
|
|
122
|
+
|
|
123
|
+
on(event: 'step', listener: (metrics: TrainingMetrics) => void): this;
|
|
124
|
+
on(event: 'epoch', listener: (metrics: TrainingMetrics) => void): this;
|
|
125
|
+
on(event: 'checkpoint', listener: (info: CheckpointInfo) => void): this;
|
|
126
|
+
on(event: 'complete', listener: (res: FitResult) => void): this;
|
|
127
|
+
on(event: 'warning', listener: (msg: string) => void): this;
|
|
128
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
129
|
+
on(event: 'stopped', listener: () => void): this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const Trainer: typeof TermuxTrainer;
|
|
133
|
+
|
|
134
|
+
export function doctor(options?: Record<string, unknown>): DoctorReport;
|
|
135
|
+
export function runDoctor(options?: Record<string, unknown>): DoctorReport;
|
|
136
|
+
export function benchmark(options?: BenchmarkOptions): Promise<BenchmarkResult>;
|
|
137
|
+
export function runBenchmark(options?: BenchmarkOptions): BenchmarkResult;
|
|
138
|
+
export interface MultimodalTrainingOptions {
|
|
139
|
+
data?: string;
|
|
140
|
+
imageDir?: string;
|
|
141
|
+
output?: string;
|
|
142
|
+
prompt?: string;
|
|
143
|
+
resolution?: 256 | 512 | 768;
|
|
144
|
+
epochs?: number;
|
|
145
|
+
lr?: number;
|
|
146
|
+
batchSize?: number;
|
|
147
|
+
rank?: number;
|
|
148
|
+
alpha?: number;
|
|
149
|
+
backend?: 'auto' | 'vulkan' | 'amuda' | 'numpy' | 'python';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface MultimodalTrainingResult {
|
|
153
|
+
status: 'SUCCESS' | 'FAILED';
|
|
154
|
+
subcommand: string;
|
|
155
|
+
stdout: string;
|
|
156
|
+
stderr: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ClusterProbeOptions {
|
|
160
|
+
rpc: string;
|
|
161
|
+
guardBand?: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function trainDiffusion(options?: MultimodalTrainingOptions): Promise<MultimodalTrainingResult>;
|
|
165
|
+
export function trainVision(options?: MultimodalTrainingOptions): Promise<MultimodalTrainingResult>;
|
|
166
|
+
export function trainSTT(options?: MultimodalTrainingOptions): Promise<MultimodalTrainingResult>;
|
|
167
|
+
export function trainTTS(options?: MultimodalTrainingOptions): Promise<MultimodalTrainingResult>;
|
|
168
|
+
export function runClusterProbe(options: ClusterProbeOptions): Promise<MultimodalTrainingResult>;
|
|
169
|
+
|
|
170
|
+
export namespace errors {
|
|
171
|
+
export class TermuxTrainError extends Error {}
|
|
172
|
+
export class PythonRuntimeNotFoundError extends TermuxTrainError {}
|
|
173
|
+
export class TermuxTrainModuleNotFoundError extends TermuxTrainError {}
|
|
174
|
+
export class TrainingExecutionError extends TermuxTrainError {
|
|
175
|
+
exitCode: number;
|
|
176
|
+
stderr: string;
|
|
177
|
+
}
|
|
178
|
+
export class VulkanNotAvailableError extends TermuxTrainError {}
|
|
179
|
+
export class InvalidCheckpointError extends TermuxTrainError {
|
|
180
|
+
path: string;
|
|
181
|
+
}
|
|
182
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0 OR Commercial-License
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* termux-train: Native On-Device Deep Learning & LoRA Training Framework
|
|
7
|
+
* Dual-Engine (Python & Node.js/TypeScript) Native Module for Android Termux & ARM64.
|
|
8
|
+
* Open-Source under Apache License 2.0.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const errors = require('./lib/errors');
|
|
14
|
+
const doctor = require('./lib/doctor');
|
|
15
|
+
const benchmark = require('./lib/benchmark');
|
|
16
|
+
const trainer = require('./lib/trainer');
|
|
17
|
+
const multimodal = require('./lib/multimodal');
|
|
18
|
+
const packageJson = require('./package.json');
|
|
19
|
+
|
|
20
|
+
const version = packageJson.version;
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
version,
|
|
24
|
+
__version__: version,
|
|
25
|
+
errors,
|
|
26
|
+
doctor: doctor.runDoctor,
|
|
27
|
+
runDoctor: doctor.runDoctor,
|
|
28
|
+
invalidatePythonCache: doctor.invalidatePythonCache,
|
|
29
|
+
benchmark: benchmark.runBenchmarkAsync,
|
|
30
|
+
runBenchmark: benchmark.runBenchmark,
|
|
31
|
+
runBenchmarkAsync: benchmark.runBenchmarkAsync,
|
|
32
|
+
TermuxTrainer: trainer.TermuxTrainer,
|
|
33
|
+
Trainer: trainer.TermuxTrainer,
|
|
34
|
+
trainDiffusion: multimodal.trainDiffusion,
|
|
35
|
+
trainVision: multimodal.trainVision,
|
|
36
|
+
trainSTT: multimodal.trainSTT,
|
|
37
|
+
trainTTS: multimodal.trainTTS,
|
|
38
|
+
runClusterProbe: multimodal.runClusterProbe,
|
|
39
|
+
};
|
package/lib/benchmark.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0 OR Commercial-License
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* termux-train: Benchmark Suite
|
|
7
|
+
* High-performance GEMM & DAG Autograd Latency & Throughput Benchmark.
|
|
8
|
+
* All benchmark execution uses stdin JSON IPC via termux_train.runtime.benchmarker
|
|
9
|
+
* — zero python -c inline script injection.
|
|
10
|
+
* Open-Source under Apache License 2.0.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const { spawn, spawnSync } = require('child_process');
|
|
16
|
+
const { resolvePythonCmd } = require('./doctor');
|
|
17
|
+
const { PythonRuntimeNotFoundError, TermuxTrainModuleNotFoundError } = require('./errors');
|
|
18
|
+
|
|
19
|
+
function _validateBenchmarkOptions(options = {}) {
|
|
20
|
+
const rawDim = options.dim !== undefined ? options.dim : 256;
|
|
21
|
+
const rawIters = options.iters !== undefined ? options.iters : 10;
|
|
22
|
+
|
|
23
|
+
if (!Number.isInteger(rawDim) || rawDim < 2 || rawDim > 4096) {
|
|
24
|
+
throw new TypeError(`Invalid dimension: ${rawDim}. Must be an integer between 2 and 4096.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!Number.isInteger(rawIters) || rawIters < 1 || rawIters > 1000) {
|
|
28
|
+
throw new TypeError(`Invalid iterations: ${rawIters}. Must be an integer between 1 and 1000.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
dim: Math.floor(rawDim),
|
|
33
|
+
iters: Math.floor(rawIters)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function runBenchmarkAsync(options = {}) {
|
|
38
|
+
const { dim, iters } = _validateBenchmarkOptions(options);
|
|
39
|
+
const pyCmd = resolvePythonCmd();
|
|
40
|
+
|
|
41
|
+
if (!pyCmd) {
|
|
42
|
+
throw new PythonRuntimeNotFoundError();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const cfg = JSON.stringify({ dim, iters });
|
|
46
|
+
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const proc = spawn(pyCmd, ['-m', 'termux_train.runtime.benchmarker', '--stdin-json'], {
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
proc.stdin.write(cfg);
|
|
53
|
+
proc.stdin.end();
|
|
54
|
+
|
|
55
|
+
let stdoutAcc = '';
|
|
56
|
+
let stderrAcc = '';
|
|
57
|
+
|
|
58
|
+
proc.stdout.on('data', (c) => { stdoutAcc += c.toString('utf-8'); });
|
|
59
|
+
proc.stderr.on('data', (c) => { stderrAcc += c.toString('utf-8'); });
|
|
60
|
+
|
|
61
|
+
proc.on('close', (code) => {
|
|
62
|
+
if (code !== 0) {
|
|
63
|
+
if (stderrAcc.includes('No module named termux_train')) {
|
|
64
|
+
return reject(new TermuxTrainModuleNotFoundError());
|
|
65
|
+
}
|
|
66
|
+
return reject(new Error(`Benchmark process failed (exit code ${code}): ${stderrAcc}`));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(stdoutAcc.trim());
|
|
71
|
+
resolve(parsed);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
reject(new Error(`Failed to parse benchmark JSON output: ${stdoutAcc}`));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
proc.on('error', (err) => {
|
|
78
|
+
reject(err);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runBenchmark(options = {}) {
|
|
84
|
+
const { dim, iters } = _validateBenchmarkOptions(options);
|
|
85
|
+
const pyCmd = resolvePythonCmd();
|
|
86
|
+
|
|
87
|
+
if (!pyCmd) {
|
|
88
|
+
throw new PythonRuntimeNotFoundError();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const cfg = JSON.stringify({ dim, iters });
|
|
92
|
+
|
|
93
|
+
const res = spawnSync(pyCmd, ['-m', 'termux_train.runtime.benchmarker', '--stdin-json'], {
|
|
94
|
+
input: cfg,
|
|
95
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
96
|
+
encoding: 'utf-8'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (res.status !== 0) {
|
|
100
|
+
const err = res.stderr || res.stdout || '';
|
|
101
|
+
if (err.includes('No module named termux_train')) {
|
|
102
|
+
throw new TermuxTrainModuleNotFoundError();
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Benchmark failed: ${err}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(res.stdout.trim());
|
|
109
|
+
} catch (e) {
|
|
110
|
+
throw new Error(`Failed to parse benchmark output: ${res.stdout}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
runBenchmark,
|
|
116
|
+
runBenchmarkAsync,
|
|
117
|
+
benchmark: runBenchmarkAsync
|
|
118
|
+
};
|