gpu-worker 1.0.0 → 1.0.1
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/README.md +20 -0
- package/bin/gpu-worker.js +295 -135
- package/cli.py +190 -42
- package/engines/__pycache__/__init__.cpython-312.pyc.2732413942320 +0 -0
- package/engines/__pycache__/base.cpython-312.pyc.2732445133408 +0 -0
- package/engines/__pycache__/image_gen.cpython-312.pyc.2733011596848 +0 -0
- package/engines/__pycache__/llm.cpython-312.pyc.2732444730592 +0 -0
- package/engines/__pycache__/llm_base.cpython-312.pyc.2733011595824 +0 -0
- package/engines/__pycache__/vision.cpython-312.pyc.2732444732032 +0 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,6 +33,23 @@ gpu-worker configure
|
|
|
33
33
|
gpu-worker start
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### Using system Python / skip install
|
|
37
|
+
|
|
38
|
+
If your server already has GPU dependencies installed globally, you can skip the virtual environment and auto-install:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx gpu-worker configure --use-system-python
|
|
42
|
+
npx gpu-worker start --use-system-python
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can also keep a venv but skip dependency installation:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx gpu-worker configure --skip-install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
On first run without a venv, the interactive CLI will prompt you to choose the mode.
|
|
52
|
+
|
|
36
53
|
## Requirements
|
|
37
54
|
|
|
38
55
|
- **Node.js**: >= 16.0.0
|
|
@@ -41,6 +58,9 @@ gpu-worker start
|
|
|
41
58
|
- **RAM**: 16GB+ recommended
|
|
42
59
|
- **Storage**: 50GB+ for model storage
|
|
43
60
|
|
|
61
|
+
CUDA note: the installer uses `nvidia-smi` to detect CUDA and selects `cu124` / `cu121` / `cu118` for PyTorch.
|
|
62
|
+
CUDA 12.2/12.3 use `cu121`. Other versions fall back to CPU builds.
|
|
63
|
+
|
|
44
64
|
## Configuration
|
|
45
65
|
|
|
46
66
|
The worker can be configured via:
|
package/bin/gpu-worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* GPU Worker CLI - Node.js 入口
|
|
4
4
|
* 包装 Python Worker,提供简单的 npm/npx 安装体验
|
|
@@ -38,8 +38,8 @@ function findPython() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// 检查虚拟环境
|
|
41
|
-
function getVenvPython() {
|
|
42
|
-
const venvPath = path.join(PACKAGE_DIR, '.venv');
|
|
41
|
+
function getVenvPython() {
|
|
42
|
+
const venvPath = path.join(PACKAGE_DIR, '.venv');
|
|
43
43
|
|
|
44
44
|
if (process.platform === 'win32') {
|
|
45
45
|
const pythonPath = path.join(venvPath, 'Scripts', 'python.exe');
|
|
@@ -48,12 +48,46 @@ function getVenvPython() {
|
|
|
48
48
|
const pythonPath = path.join(venvPath, 'bin', 'python');
|
|
49
49
|
if (fs.existsSync(pythonPath)) return pythonPath;
|
|
50
50
|
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function detectCudaVersion() {
|
|
55
|
+
try {
|
|
56
|
+
const output = execSync('nvidia-smi', { encoding: 'utf8' });
|
|
57
|
+
const match = output.match(/CUDA Version:\\s*(\\d+)\\.(\\d+)/);
|
|
58
|
+
if (!match) return null;
|
|
59
|
+
return { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) };
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function selectTorchIndexUrl(cudaVersion) {
|
|
66
|
+
if (!cudaVersion) return null;
|
|
67
|
+
const versionValue = cudaVersion.major * 100 + cudaVersion.minor;
|
|
68
|
+
if (versionValue >= 1204) return 'https://download.pytorch.org/whl/cu124';
|
|
69
|
+
if (versionValue >= 1201) return 'https://download.pytorch.org/whl/cu121';
|
|
70
|
+
if (versionValue >= 1108) return 'https://download.pytorch.org/whl/cu118';
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createRequirementsWithoutTorch(requirementsFile) {
|
|
75
|
+
const content = fs.readFileSync(requirementsFile, 'utf8');
|
|
76
|
+
const filtered = content
|
|
77
|
+
.split(/\r?\n/)
|
|
78
|
+
.filter((line) => {
|
|
79
|
+
const trimmed = line.trim();
|
|
80
|
+
if (!trimmed || trimmed.startsWith('#')) return true;
|
|
81
|
+
return !/^torch([<>=!~].*)?$/.test(trimmed);
|
|
82
|
+
});
|
|
83
|
+
const filteredPath = path.join(PACKAGE_DIR, '.requirements.no-torch.txt');
|
|
84
|
+
fs.writeFileSync(filteredPath, filtered.join('\n'));
|
|
85
|
+
return filteredPath;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 创建虚拟环境
|
|
89
|
+
async function createVenv(pythonCmd) {
|
|
90
|
+
const spinner = ora('Creating Python virtual environment...').start();
|
|
57
91
|
const venvPath = path.join(PACKAGE_DIR, '.venv');
|
|
58
92
|
|
|
59
93
|
try {
|
|
@@ -68,24 +102,49 @@ async function createVenv(pythonCmd) {
|
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
// 安装 Python 依赖
|
|
71
|
-
async function installDependencies() {
|
|
72
|
-
const venvPython = getVenvPython();
|
|
73
|
-
if (!venvPython) {
|
|
74
|
-
console.error(chalk.red('Virtual environment not found'));
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const spinner = ora('Installing Python dependencies...').start();
|
|
79
|
-
const requirementsFile = path.join(PACKAGE_DIR, 'requirements.txt');
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
async function installDependencies() {
|
|
106
|
+
const venvPython = getVenvPython();
|
|
107
|
+
if (!venvPython) {
|
|
108
|
+
console.error(chalk.red('Virtual environment not found'));
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const spinner = ora('Installing Python dependencies...').start();
|
|
113
|
+
const requirementsFile = path.join(PACKAGE_DIR, 'requirements.txt');
|
|
114
|
+
const cudaVersion = detectCudaVersion();
|
|
115
|
+
const torchIndexUrl = selectTorchIndexUrl(cudaVersion);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
if (cudaVersion && !torchIndexUrl) {
|
|
119
|
+
console.log(chalk.yellow(`检测到 CUDA ${cudaVersion.major}.${cudaVersion.minor},无匹配的 PyTorch 版本,将安装 CPU 版。`));
|
|
120
|
+
}
|
|
121
|
+
if (torchIndexUrl) {
|
|
122
|
+
execSync(`"${venvPython}" -m pip install torch --index-url "${torchIndexUrl}" --upgrade --force-reinstall -q`, {
|
|
123
|
+
stdio: 'pipe',
|
|
124
|
+
timeout: 600000 // 10分钟超时
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
execSync(`"${venvPython}" -m pip install "torch>=2.0.0" -q`, {
|
|
128
|
+
stdio: 'pipe',
|
|
129
|
+
timeout: 600000 // 10分钟超时
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const filteredRequirements = createRequirementsWithoutTorch(requirementsFile);
|
|
134
|
+
try {
|
|
135
|
+
execSync(`"${venvPython}" -m pip install -r "${filteredRequirements}" -q`, {
|
|
136
|
+
stdio: 'pipe',
|
|
137
|
+
timeout: 600000 // 10分钟超时
|
|
138
|
+
});
|
|
139
|
+
} finally {
|
|
140
|
+
if (fs.existsSync(filteredRequirements)) {
|
|
141
|
+
fs.unlinkSync(filteredRequirements);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
spinner.succeed('Dependencies installed');
|
|
146
|
+
return true;
|
|
147
|
+
} catch (e) {
|
|
89
148
|
spinner.fail('Failed to install dependencies');
|
|
90
149
|
console.error(chalk.red(e.message));
|
|
91
150
|
return false;
|
|
@@ -93,24 +152,24 @@ async function installDependencies() {
|
|
|
93
152
|
}
|
|
94
153
|
|
|
95
154
|
// 运行 Python CLI
|
|
96
|
-
function runPythonCLI(args) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (!
|
|
100
|
-
console.error(chalk.red('Python 3.9+ not found!'));
|
|
101
|
-
console.log(chalk.yellow('Please install Python 3.9 or higher:'));
|
|
102
|
-
console.log(' - Windows: https://www.python.org/downloads/');
|
|
103
|
-
console.log(' - macOS: brew install python@3.11');
|
|
104
|
-
console.log(' - Linux: sudo apt install python3.11');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const cliPath = path.join(PACKAGE_DIR, 'cli.py');
|
|
109
|
-
|
|
110
|
-
const proc = spawn(
|
|
111
|
-
stdio: 'inherit',
|
|
112
|
-
cwd: process.cwd()
|
|
113
|
-
});
|
|
155
|
+
function runPythonCLI(args, pythonCmd) {
|
|
156
|
+
const resolvedCmd = pythonCmd || getVenvPython() || findPython();
|
|
157
|
+
|
|
158
|
+
if (!resolvedCmd) {
|
|
159
|
+
console.error(chalk.red('Python 3.9+ not found!'));
|
|
160
|
+
console.log(chalk.yellow('Please install Python 3.9 or higher:'));
|
|
161
|
+
console.log(' - Windows: https://www.python.org/downloads/');
|
|
162
|
+
console.log(' - macOS: brew install python@3.11');
|
|
163
|
+
console.log(' - Linux: sudo apt install python3.11');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const cliPath = path.join(PACKAGE_DIR, 'cli.py');
|
|
168
|
+
|
|
169
|
+
const proc = spawn(resolvedCmd, [cliPath, ...args], {
|
|
170
|
+
stdio: 'inherit',
|
|
171
|
+
cwd: process.cwd()
|
|
172
|
+
});
|
|
114
173
|
|
|
115
174
|
proc.on('close', (code) => {
|
|
116
175
|
process.exit(code);
|
|
@@ -123,99 +182,195 @@ function runPythonCLI(args) {
|
|
|
123
182
|
}
|
|
124
183
|
|
|
125
184
|
// 初始化检查
|
|
126
|
-
async function ensureSetup() {
|
|
127
|
-
const venvPython = getVenvPython();
|
|
128
|
-
|
|
129
|
-
if (!venvPython) {
|
|
130
|
-
console.log(chalk.cyan('First time setup detected. Setting up environment...\n'));
|
|
131
|
-
|
|
132
|
-
const pythonCmd = findPython();
|
|
133
|
-
if (!pythonCmd) {
|
|
134
|
-
console.error(chalk.red('Python 3.9+ is required but not found!'));
|
|
135
|
-
console.log(chalk.yellow('\nPlease install Python:'));
|
|
136
|
-
console.log(' - Windows: https://www.python.org/downloads/');
|
|
137
|
-
console.log(' - macOS: brew install python@3.11');
|
|
138
|
-
console.log(' - Linux: sudo apt install python3.11');
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
console.log(chalk.green(`Found Python: ${pythonCmd}`));
|
|
143
|
-
|
|
144
|
-
if (!await createVenv(pythonCmd)) {
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!await installDependencies()) {
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(chalk.green('\n✓ Setup complete!\n'));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
185
|
|
|
156
186
|
// 主程序
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
async function ensureSetup(options = {}) {
|
|
188
|
+
const allowPrompt = Boolean(options.allowPrompt);
|
|
189
|
+
let useSystemPython = Boolean(options.useSystemPython);
|
|
190
|
+
let skipInstall = Boolean(options.skipInstall);
|
|
191
|
+
|
|
192
|
+
if (useSystemPython) {
|
|
193
|
+
const pythonCmd = findPython();
|
|
194
|
+
if (!pythonCmd) {
|
|
195
|
+
console.error(chalk.red('Python 3.9+ is required but not found!'));
|
|
196
|
+
console.log(chalk.yellow('\nPlease install Python:'));
|
|
197
|
+
console.log(' - Windows: https://www.python.org/downloads/');
|
|
198
|
+
console.log(' - macOS: brew install python@3.11');
|
|
199
|
+
console.log(' - Linux: sudo apt install python3.11');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(chalk.green(`Using system Python: ${pythonCmd}`));
|
|
204
|
+
return { pythonCmd, useSystemPython: true };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let venvPython = getVenvPython();
|
|
208
|
+
let createdVenv = false;
|
|
209
|
+
|
|
210
|
+
if (!venvPython) {
|
|
211
|
+
if (allowPrompt) {
|
|
212
|
+
const { mode } = await inquirer.prompt([{
|
|
213
|
+
type: 'list',
|
|
214
|
+
name: 'mode',
|
|
215
|
+
message: '未检测到虚拟环境,请选择运行方式:',
|
|
216
|
+
choices: [
|
|
217
|
+
{ name: '使用系统 Python(跳过虚拟环境与依赖安装)', value: 'system' },
|
|
218
|
+
{ name: '创建虚拟环境并安装依赖(推荐)', value: 'venv' },
|
|
219
|
+
{ name: '创建虚拟环境但跳过依赖安装', value: 'venv-skip-install' }
|
|
220
|
+
]
|
|
221
|
+
}]);
|
|
222
|
+
|
|
223
|
+
if (mode === 'system') {
|
|
224
|
+
useSystemPython = true;
|
|
225
|
+
const pythonCmd = findPython();
|
|
226
|
+
if (!pythonCmd) {
|
|
227
|
+
console.error(chalk.red('Python 3.9+ is required but not found!'));
|
|
228
|
+
console.log(chalk.yellow('\nPlease install Python:'));
|
|
229
|
+
console.log(' - Windows: https://www.python.org/downloads/');
|
|
230
|
+
console.log(' - macOS: brew install python@3.11');
|
|
231
|
+
console.log(' - Linux: sudo apt install python3.11');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log(chalk.green(`Using system Python: ${pythonCmd}`));
|
|
236
|
+
return { pythonCmd, useSystemPython: true };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (mode === 'venv-skip-install') {
|
|
240
|
+
skipInstall = true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log(chalk.cyan('First time setup detected. Setting up environment...\n'));
|
|
245
|
+
|
|
246
|
+
const pythonCmd = findPython();
|
|
247
|
+
if (!pythonCmd) {
|
|
248
|
+
console.error(chalk.red('Python 3.9+ is required but not found!'));
|
|
249
|
+
console.log(chalk.yellow('\nPlease install Python:'));
|
|
250
|
+
console.log(' - Windows: https://www.python.org/downloads/');
|
|
251
|
+
console.log(' - macOS: brew install python@3.11');
|
|
252
|
+
console.log(' - Linux: sudo apt install python3.11');
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log(chalk.green(`Found Python: ${pythonCmd}`));
|
|
257
|
+
|
|
258
|
+
if (!await createVenv(pythonCmd)) {
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
createdVenv = true;
|
|
263
|
+
venvPython = getVenvPython();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (createdVenv && !skipInstall) {
|
|
267
|
+
if (!await installDependencies()) {
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(chalk.green('\nSetup complete!\n'));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { pythonCmd: venvPython || findPython(), useSystemPython: false };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function addCommonOptions(command) {
|
|
278
|
+
return command
|
|
279
|
+
.option('--use-system-python', '使用系统 Python(跳过虚拟环境与依赖安装)')
|
|
280
|
+
.option('--skip-install', '跳过依赖安装,仅使用已有环境');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const program = new Command();
|
|
284
|
+
|
|
285
|
+
program
|
|
286
|
+
.name('gpu-worker')
|
|
287
|
+
.description('分布式GPU推理 Worker 节点')
|
|
288
|
+
.version('1.0.0')
|
|
289
|
+
.option('--use-system-python', '使用系统 Python(跳过虚拟环境与依赖安装)')
|
|
290
|
+
.option('--skip-install', '跳过依赖安装,仅使用已有环境');
|
|
291
|
+
|
|
292
|
+
addCommonOptions(
|
|
293
|
+
program
|
|
294
|
+
.command('install')
|
|
295
|
+
)
|
|
296
|
+
.description('安装/更新 Python 依赖')
|
|
297
|
+
.action(async function () {
|
|
298
|
+
const opts = { ...program.opts(), ...this.opts() };
|
|
299
|
+
if (opts.useSystemPython) {
|
|
300
|
+
console.log(chalk.yellow('install 仅针对虚拟环境,系统 Python 请自行安装依赖。'));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
await ensureSetup({ ...opts, useSystemPython: false, skipInstall: true, allowPrompt: false });
|
|
304
|
+
await installDependencies();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
addCommonOptions(
|
|
308
|
+
program
|
|
309
|
+
.command('configure')
|
|
310
|
+
)
|
|
311
|
+
.description('交互式配置向导')
|
|
312
|
+
.action(async function () {
|
|
313
|
+
const opts = { ...program.opts(), ...this.opts() };
|
|
314
|
+
const setup = await ensureSetup({ ...opts, allowPrompt: false });
|
|
315
|
+
runPythonCLI(['configure'], setup.pythonCmd);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
addCommonOptions(
|
|
319
|
+
program
|
|
320
|
+
.command('start')
|
|
321
|
+
)
|
|
322
|
+
.description('启动 Worker')
|
|
323
|
+
.option('-c, --config <path>', '配置文件路径', 'config.yaml')
|
|
324
|
+
.action(async function () {
|
|
325
|
+
const cmdOpts = this.opts();
|
|
326
|
+
const opts = { ...program.opts(), ...cmdOpts };
|
|
327
|
+
const setup = await ensureSetup({ ...opts, allowPrompt: false });
|
|
186
328
|
|
|
187
329
|
// 检查配置文件
|
|
188
|
-
const configPath = path.resolve(
|
|
330
|
+
const configPath = path.resolve(cmdOpts.config);
|
|
189
331
|
if (!fs.existsSync(configPath)) {
|
|
190
332
|
console.log(chalk.yellow('No config file found. Starting configuration wizard...\n'));
|
|
191
|
-
runPythonCLI(['configure']);
|
|
333
|
+
runPythonCLI(['configure'], setup.pythonCmd);
|
|
192
334
|
return;
|
|
193
335
|
}
|
|
194
336
|
|
|
195
|
-
runPythonCLI(['start', '-c', configPath]);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
program
|
|
199
|
-
.command('status')
|
|
200
|
-
.description('查看状态')
|
|
201
|
-
.action(async () => {
|
|
202
|
-
await ensureSetup();
|
|
203
|
-
runPythonCLI(['status']);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
program
|
|
207
|
-
.command('set <key> <value>')
|
|
208
|
-
.description('设置配置项')
|
|
209
|
-
.action(async (key, value) => {
|
|
210
|
-
await ensureSetup();
|
|
211
|
-
runPythonCLI(['set', key, value]);
|
|
337
|
+
runPythonCLI(['start', '-c', configPath], setup.pythonCmd);
|
|
212
338
|
});
|
|
213
339
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
340
|
+
addCommonOptions(
|
|
341
|
+
program
|
|
342
|
+
.command('status')
|
|
343
|
+
)
|
|
344
|
+
.description('查看状态')
|
|
345
|
+
.action(async function () {
|
|
346
|
+
const opts = { ...program.opts(), ...this.opts() };
|
|
347
|
+
const setup = await ensureSetup({ ...opts, allowPrompt: false });
|
|
348
|
+
runPythonCLI(['status'], setup.pythonCmd);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
addCommonOptions(
|
|
352
|
+
program
|
|
353
|
+
.command('set <key> <value>')
|
|
354
|
+
)
|
|
355
|
+
.description('设置配置项')
|
|
356
|
+
.action(async function (key, value) {
|
|
357
|
+
const opts = { ...program.opts(), ...this.opts() };
|
|
358
|
+
const setup = await ensureSetup({ ...opts, allowPrompt: false });
|
|
359
|
+
runPythonCLI(['set', key, value], setup.pythonCmd);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
addCommonOptions(
|
|
363
|
+
program
|
|
364
|
+
.command('setup')
|
|
365
|
+
)
|
|
366
|
+
.description('初始化环境(创建虚拟环境并安装依赖)')
|
|
367
|
+
.action(async function () {
|
|
368
|
+
const opts = { ...program.opts(), ...this.opts() };
|
|
369
|
+
if (opts.useSystemPython) {
|
|
370
|
+
console.log(chalk.yellow('setup 仅用于虚拟环境,系统 Python 请自行安装依赖。'));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const pythonCmd = findPython();
|
|
219
374
|
if (!pythonCmd) {
|
|
220
375
|
console.error(chalk.red('Python 3.9+ not found!'));
|
|
221
376
|
process.exit(1);
|
|
@@ -224,8 +379,10 @@ program
|
|
|
224
379
|
console.log(chalk.cyan('Setting up GPU Worker environment...\n'));
|
|
225
380
|
console.log(chalk.green(`Python: ${pythonCmd}`));
|
|
226
381
|
|
|
227
|
-
await createVenv(pythonCmd);
|
|
228
|
-
|
|
382
|
+
await createVenv(pythonCmd);
|
|
383
|
+
if (!opts.skipInstall) {
|
|
384
|
+
await installDependencies();
|
|
385
|
+
}
|
|
229
386
|
|
|
230
387
|
console.log(chalk.green('\n✓ Setup complete!'));
|
|
231
388
|
console.log(chalk.cyan('\nNext steps:'));
|
|
@@ -234,10 +391,13 @@ program
|
|
|
234
391
|
});
|
|
235
392
|
|
|
236
393
|
// 快速启动命令 (无参数时的默认行为)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
394
|
+
addCommonOptions(
|
|
395
|
+
program
|
|
396
|
+
.command('quick', { isDefault: true, hidden: true })
|
|
397
|
+
)
|
|
398
|
+
.action(async function () {
|
|
399
|
+
const opts = { ...program.opts(), ...this.opts() };
|
|
400
|
+
const setup = await ensureSetup({ ...opts, allowPrompt: true });
|
|
241
401
|
|
|
242
402
|
console.log(chalk.cyan.bold('\n GPU Worker - 分布式GPU推理节点\n'));
|
|
243
403
|
|
|
@@ -264,12 +424,12 @@ program
|
|
|
264
424
|
const configPath = path.join(process.cwd(), 'config.yaml');
|
|
265
425
|
if (!fs.existsSync(configPath)) {
|
|
266
426
|
console.log(chalk.yellow('\n未找到配置文件,先进行配置...\n'));
|
|
267
|
-
runPythonCLI(['configure']);
|
|
427
|
+
runPythonCLI(['configure'], setup.pythonCmd);
|
|
268
428
|
return;
|
|
269
429
|
}
|
|
270
430
|
}
|
|
271
431
|
|
|
272
|
-
runPythonCLI([action]);
|
|
432
|
+
runPythonCLI([action], setup.pythonCmd);
|
|
273
433
|
});
|
|
274
434
|
|
|
275
435
|
program.parse();
|
package/cli.py
CHANGED
|
@@ -5,11 +5,12 @@ GPU Worker CLI 安装器和配置向导
|
|
|
5
5
|
"""
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
|
-
import argparse
|
|
9
|
-
import subprocess
|
|
10
|
-
import platform
|
|
11
|
-
import shutil
|
|
12
|
-
|
|
8
|
+
import argparse
|
|
9
|
+
import subprocess
|
|
10
|
+
import platform
|
|
11
|
+
import shutil
|
|
12
|
+
import re
|
|
13
|
+
from pathlib import Path
|
|
13
14
|
from typing import Optional, Dict, Any, List
|
|
14
15
|
import json
|
|
15
16
|
import time
|
|
@@ -73,13 +74,91 @@ def clear_screen():
|
|
|
73
74
|
os.system('cls' if platform.system() == 'Windows' else 'clear')
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
def
|
|
77
|
-
"""
|
|
77
|
+
def _probe_nvidia_smi() -> Optional[Dict[str, Any]]:
|
|
78
|
+
"""通过 nvidia-smi 获取 GPU 信息(无 PyTorch 时的兜底)"""
|
|
79
|
+
try:
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits"],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True
|
|
84
|
+
)
|
|
85
|
+
if result.returncode != 0:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
89
|
+
if not lines:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
first = [part.strip() for part in lines[0].split(",")]
|
|
93
|
+
model = first[0] if first else "Unknown"
|
|
94
|
+
memory_gb = 0
|
|
95
|
+
if len(first) > 1:
|
|
96
|
+
try:
|
|
97
|
+
memory_gb = round(float(first[1]) / 1024, 1)
|
|
98
|
+
except ValueError:
|
|
99
|
+
memory_gb = 0
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"count": len(lines),
|
|
103
|
+
"model": model,
|
|
104
|
+
"memory_gb": memory_gb
|
|
105
|
+
}
|
|
106
|
+
except Exception:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _detect_cuda_version() -> Optional[Dict[str, int]]:
|
|
111
|
+
"""从 nvidia-smi 输出中解析 CUDA 版本"""
|
|
112
|
+
try:
|
|
113
|
+
result = subprocess.run(
|
|
114
|
+
["nvidia-smi"],
|
|
115
|
+
capture_output=True,
|
|
116
|
+
text=True
|
|
117
|
+
)
|
|
118
|
+
if result.returncode != 0:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
match = re.search(r"CUDA Version:\\s*(\\d+)\\.(\\d+)", result.stdout)
|
|
122
|
+
if not match:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"major": int(match.group(1)),
|
|
127
|
+
"minor": int(match.group(2))
|
|
128
|
+
}
|
|
129
|
+
except Exception:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _select_torch_index_url(cuda_version: Optional[Dict[str, int]]) -> Optional[str]:
|
|
134
|
+
"""根据 CUDA 版本选择 PyTorch 安装源"""
|
|
135
|
+
if not cuda_version:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
major = cuda_version["major"]
|
|
139
|
+
minor = cuda_version["minor"]
|
|
140
|
+
version_value = major * 100 + minor
|
|
141
|
+
|
|
142
|
+
if version_value >= 1204:
|
|
143
|
+
return "https://download.pytorch.org/whl/cu124"
|
|
144
|
+
if version_value >= 1201:
|
|
145
|
+
return "https://download.pytorch.org/whl/cu121"
|
|
146
|
+
if version_value >= 1108:
|
|
147
|
+
return "https://download.pytorch.org/whl/cu118"
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def check_gpu():
|
|
152
|
+
"""检测GPU信息"""
|
|
78
153
|
gpu_info = {
|
|
79
154
|
"available": False,
|
|
80
155
|
"count": 0,
|
|
81
156
|
"model": "Unknown",
|
|
82
|
-
"memory_gb": 0
|
|
157
|
+
"memory_gb": 0,
|
|
158
|
+
"nvidia_detected": False,
|
|
159
|
+
"nvidia_model": None,
|
|
160
|
+
"nvidia_memory_gb": None,
|
|
161
|
+
"nvidia_count": 0
|
|
83
162
|
}
|
|
84
163
|
|
|
85
164
|
try:
|
|
@@ -93,14 +172,22 @@ def check_gpu():
|
|
|
93
172
|
except ImportError:
|
|
94
173
|
pass
|
|
95
174
|
|
|
175
|
+
if not gpu_info["available"]:
|
|
176
|
+
nvidia_info = _probe_nvidia_smi()
|
|
177
|
+
if nvidia_info:
|
|
178
|
+
gpu_info["nvidia_detected"] = True
|
|
179
|
+
gpu_info["nvidia_count"] = nvidia_info["count"]
|
|
180
|
+
gpu_info["nvidia_model"] = nvidia_info["model"]
|
|
181
|
+
gpu_info["nvidia_memory_gb"] = nvidia_info["memory_gb"]
|
|
182
|
+
|
|
96
183
|
return gpu_info
|
|
97
184
|
|
|
98
185
|
|
|
99
|
-
def check_dependencies() -> Dict[str, bool]:
|
|
100
|
-
"""检查依赖"""
|
|
101
|
-
deps = {
|
|
102
|
-
"python": sys.version_info >= (3, 9),
|
|
103
|
-
"torch": False,
|
|
186
|
+
def check_dependencies() -> Dict[str, bool]:
|
|
187
|
+
"""检查依赖"""
|
|
188
|
+
deps = {
|
|
189
|
+
"python": sys.version_info >= (3, 9),
|
|
190
|
+
"torch": False,
|
|
104
191
|
"transformers": False,
|
|
105
192
|
"cuda": False
|
|
106
193
|
}
|
|
@@ -118,37 +205,75 @@ def check_dependencies() -> Dict[str, bool]:
|
|
|
118
205
|
except ImportError:
|
|
119
206
|
pass
|
|
120
207
|
|
|
121
|
-
return deps
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
208
|
+
return deps
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _pip_install(
|
|
212
|
+
requirement: str,
|
|
213
|
+
progress_callback=None,
|
|
214
|
+
index_url: Optional[str] = None,
|
|
215
|
+
extra_args: Optional[List[str]] = None
|
|
216
|
+
):
|
|
217
|
+
if progress_callback:
|
|
218
|
+
progress_callback(f"Installing {requirement}...")
|
|
219
|
+
|
|
220
|
+
command = [sys.executable, "-m", "pip", "install", requirement]
|
|
221
|
+
if index_url:
|
|
222
|
+
command.extend(["--index-url", index_url])
|
|
223
|
+
if extra_args:
|
|
224
|
+
command.extend(extra_args)
|
|
225
|
+
|
|
226
|
+
result = subprocess.run(
|
|
227
|
+
command,
|
|
228
|
+
capture_output=True,
|
|
229
|
+
text=True
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if result.returncode != 0:
|
|
233
|
+
raise Exception(f"Failed to install {requirement}: {result.stderr}")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def install_dependencies(progress_callback=None):
|
|
237
|
+
"""安装依赖"""
|
|
238
|
+
requirements = [
|
|
239
|
+
"torch>=2.0.0",
|
|
240
|
+
"transformers>=4.35.0",
|
|
129
241
|
"diffusers>=0.24.0",
|
|
130
242
|
"accelerate>=0.24.0",
|
|
131
243
|
"peft>=0.6.0",
|
|
132
244
|
"bitsandbytes>=0.41.0",
|
|
133
245
|
"httpx>=0.25.0",
|
|
134
246
|
"pyyaml>=6.0",
|
|
135
|
-
"pydantic>=2.0.0",
|
|
136
|
-
"fastapi>=0.100.0",
|
|
137
|
-
"uvicorn>=0.23.0"
|
|
138
|
-
]
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
247
|
+
"pydantic>=2.0.0",
|
|
248
|
+
"fastapi>=0.100.0",
|
|
249
|
+
"uvicorn>=0.23.0"
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
cuda_version = _detect_cuda_version()
|
|
253
|
+
torch_index_url = _select_torch_index_url(cuda_version)
|
|
254
|
+
|
|
255
|
+
if torch_index_url:
|
|
256
|
+
_pip_install(
|
|
257
|
+
"torch",
|
|
258
|
+
progress_callback,
|
|
259
|
+
index_url=torch_index_url,
|
|
260
|
+
extra_args=["--upgrade", "--force-reinstall"]
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
if cuda_version and progress_callback:
|
|
264
|
+
progress_callback(
|
|
265
|
+
f"检测到 CUDA {cuda_version['major']}.{cuda_version['minor']},"
|
|
266
|
+
"无匹配的 PyTorch 版本,将安装 CPU 版"
|
|
267
|
+
)
|
|
268
|
+
_pip_install("torch>=2.0.0", progress_callback)
|
|
269
|
+
|
|
270
|
+
non_torch_requirements = [
|
|
271
|
+
item for item in requirements
|
|
272
|
+
if not re.match(r"^torch([<>=!~].*)?$", item.strip())
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
for req in non_torch_requirements:
|
|
276
|
+
_pip_install(req, progress_callback)
|
|
152
277
|
|
|
153
278
|
|
|
154
279
|
def save_config(config: Dict[str, Any], path: str = CONFIG_FILE):
|
|
@@ -317,10 +442,25 @@ class ConfigWizard:
|
|
|
317
442
|
'enable_cpu_offload': gpu_info['memory_gb'] < 16
|
|
318
443
|
}
|
|
319
444
|
else:
|
|
320
|
-
|
|
321
|
-
|
|
445
|
+
nvidia_memory_gb = gpu_info.get("nvidia_memory_gb")
|
|
446
|
+
if gpu_info.get("nvidia_detected"):
|
|
447
|
+
if RICH_AVAILABLE:
|
|
448
|
+
console.print("[yellow]检测到 NVIDIA GPU,但 CUDA 版 PyTorch 不可用,将使用 CPU 模式(性能有限)[/yellow]")
|
|
449
|
+
console.print(f" 型号: {gpu_info.get('nvidia_model', 'Unknown')}")
|
|
450
|
+
if nvidia_memory_gb:
|
|
451
|
+
console.print(f" 显存: {nvidia_memory_gb} GB")
|
|
452
|
+
console.print("[yellow]请安装 CUDA 版 PyTorch(例如:python -m pip install torch --index-url https://download.pytorch.org/whl/cu121)[/yellow]")
|
|
453
|
+
else:
|
|
454
|
+
print("检测到 NVIDIA GPU,但 CUDA 版 PyTorch 不可用,将使用 CPU 模式(性能有限)")
|
|
455
|
+
print(f" 型号: {gpu_info.get('nvidia_model', 'Unknown')}")
|
|
456
|
+
if nvidia_memory_gb:
|
|
457
|
+
print(f" 显存: {nvidia_memory_gb} GB")
|
|
458
|
+
print("请安装 CUDA 版 PyTorch(例如:python -m pip install torch --index-url https://download.pytorch.org/whl/cu121)")
|
|
322
459
|
else:
|
|
323
|
-
|
|
460
|
+
if RICH_AVAILABLE:
|
|
461
|
+
console.print("[yellow]未检测到 GPU,将使用 CPU 模式(性能有限)[/yellow]")
|
|
462
|
+
else:
|
|
463
|
+
print("未检测到 GPU,将使用 CPU 模式(性能有限)")
|
|
324
464
|
|
|
325
465
|
self.config['gpu'] = {
|
|
326
466
|
'model': 'CPU',
|
|
@@ -628,9 +768,17 @@ def cmd_status(args):
|
|
|
628
768
|
|
|
629
769
|
# GPU信息
|
|
630
770
|
gpu_info = check_gpu()
|
|
631
|
-
print(f"\nGPU: {gpu_info['model'] if gpu_info['available'] else '未检测到'}")
|
|
632
771
|
if gpu_info['available']:
|
|
772
|
+
print(f"\nGPU: {gpu_info['model']}")
|
|
633
773
|
print(f"显存: {gpu_info['memory_gb']} GB")
|
|
774
|
+
elif gpu_info.get("nvidia_detected"):
|
|
775
|
+
nvidia_memory_gb = gpu_info.get("nvidia_memory_gb")
|
|
776
|
+
print(f"\nGPU: {gpu_info.get('nvidia_model', 'Unknown')} (驱动可用,PyTorch CUDA 不可用)")
|
|
777
|
+
if nvidia_memory_gb:
|
|
778
|
+
print(f"显存: {nvidia_memory_gb} GB")
|
|
779
|
+
print("建议安装 CUDA 版 PyTorch(例如:python -m pip install torch --index-url https://download.pytorch.org/whl/cu121)")
|
|
780
|
+
else:
|
|
781
|
+
print("\nGPU: 未检测到")
|
|
634
782
|
|
|
635
783
|
# 配置信息
|
|
636
784
|
print(f"\n配置:")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gpu-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Distributed GPU Inference Worker - Share idle GPU computing power for LLM and image generation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gpu",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"url": "https://github.com/Baozhi888/distributed-gpu-inference/issues"
|
|
28
28
|
},
|
|
29
29
|
"bin": {
|
|
30
|
-
"gpu-worker": "
|
|
30
|
+
"gpu-worker": "bin/gpu-worker.js"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"postinstall": "node scripts/postinstall.js",
|