@modular-prompt/driver 0.14.0 → 0.15.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/README.md +58 -5
- package/dist/driver-registry/ai-service.d.ts +23 -1
- package/dist/driver-registry/ai-service.d.ts.map +1 -1
- package/dist/driver-registry/ai-service.js +44 -10
- package/dist/driver-registry/ai-service.js.map +1 -1
- package/dist/driver-registry/config-based-factory.d.ts.map +1 -1
- package/dist/driver-registry/config-based-factory.js +16 -0
- package/dist/driver-registry/config-based-factory.js.map +1 -1
- package/dist/driver-registry/factory-helper.d.ts +2 -0
- package/dist/driver-registry/factory-helper.d.ts.map +1 -1
- package/dist/driver-registry/factory-helper.js +21 -3
- package/dist/driver-registry/factory-helper.js.map +1 -1
- package/dist/driver-registry/index.d.ts +2 -2
- package/dist/driver-registry/index.d.ts.map +1 -1
- package/dist/driver-registry/index.js +1 -1
- package/dist/driver-registry/index.js.map +1 -1
- package/dist/driver-registry/registry.d.ts.map +1 -1
- package/dist/driver-registry/registry.js +3 -1
- package/dist/driver-registry/registry.js.map +1 -1
- package/dist/driver-registry/types.d.ts +8 -2
- package/dist/driver-registry/types.d.ts.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/local-inference/adapters.d.ts +66 -0
- package/dist/local-inference/adapters.d.ts.map +1 -0
- package/dist/local-inference/adapters.js +2 -0
- package/dist/local-inference/adapters.js.map +1 -0
- package/dist/local-inference/driver.d.ts +51 -0
- package/dist/local-inference/driver.d.ts.map +1 -0
- package/dist/local-inference/driver.js +309 -0
- package/dist/local-inference/driver.js.map +1 -0
- package/dist/local-inference/index.d.ts +22 -0
- package/dist/local-inference/index.d.ts.map +1 -0
- package/dist/local-inference/index.js +17 -0
- package/dist/local-inference/index.js.map +1 -0
- package/dist/local-inference/process-client.d.ts +50 -0
- package/dist/local-inference/process-client.d.ts.map +1 -0
- package/dist/local-inference/process-client.js +92 -0
- package/dist/local-inference/process-client.js.map +1 -0
- package/dist/local-inference/process-communication.d.ts +41 -0
- package/dist/local-inference/process-communication.d.ts.map +1 -0
- package/dist/{mlx-ml/process → local-inference}/process-communication.js +25 -61
- package/dist/local-inference/process-communication.js.map +1 -0
- package/dist/local-inference/process-port.d.ts +12 -0
- package/dist/local-inference/process-port.d.ts.map +1 -0
- package/dist/local-inference/process-port.js +2 -0
- package/dist/local-inference/process-port.js.map +1 -0
- package/dist/local-inference/prompt-utils.d.ts +6 -0
- package/dist/local-inference/prompt-utils.d.ts.map +1 -0
- package/dist/local-inference/prompt-utils.js +17 -0
- package/dist/local-inference/prompt-utils.js.map +1 -0
- package/dist/local-inference/protocol.d.ts +192 -0
- package/dist/local-inference/protocol.d.ts.map +1 -0
- package/dist/local-inference/protocol.js +2 -0
- package/dist/local-inference/protocol.js.map +1 -0
- package/dist/local-inference/queue-types.d.ts +54 -0
- package/dist/local-inference/queue-types.d.ts.map +1 -0
- package/dist/local-inference/queue-types.js +2 -0
- package/dist/local-inference/queue-types.js.map +1 -0
- package/dist/local-inference/request-queue.d.ts +36 -0
- package/dist/local-inference/request-queue.d.ts.map +1 -0
- package/dist/{mlx-ml/process/queue.js → local-inference/request-queue.js} +83 -56
- package/dist/local-inference/request-queue.js.map +1 -0
- package/dist/local-inference/stream-utils.d.ts +19 -0
- package/dist/local-inference/stream-utils.d.ts.map +1 -0
- package/dist/local-inference/stream-utils.js +76 -0
- package/dist/local-inference/stream-utils.js.map +1 -0
- package/dist/mlx-ml/mlx-cache-support.d.ts +23 -0
- package/dist/mlx-ml/mlx-cache-support.d.ts.map +1 -0
- package/dist/mlx-ml/mlx-cache-support.js +45 -0
- package/dist/mlx-ml/mlx-cache-support.js.map +1 -0
- package/dist/mlx-ml/mlx-driver.d.ts +20 -59
- package/dist/mlx-ml/mlx-driver.d.ts.map +1 -1
- package/dist/mlx-ml/mlx-driver.js +86 -460
- package/dist/mlx-ml/mlx-driver.js.map +1 -1
- package/dist/mlx-ml/mlx-local-inference-adapters.d.ts +3 -0
- package/dist/mlx-ml/mlx-local-inference-adapters.d.ts.map +1 -0
- package/dist/mlx-ml/mlx-local-inference-adapters.js +19 -0
- package/dist/mlx-ml/mlx-local-inference-adapters.js.map +1 -0
- package/dist/mlx-ml/mlx-options.d.ts +19 -0
- package/dist/mlx-ml/mlx-options.d.ts.map +1 -0
- package/dist/mlx-ml/mlx-options.js +30 -0
- package/dist/mlx-ml/mlx-options.js.map +1 -0
- package/dist/mlx-ml/process/index.d.ts +10 -8
- package/dist/mlx-ml/process/index.d.ts.map +1 -1
- package/dist/mlx-ml/process/index.js +73 -54
- package/dist/mlx-ml/process/index.js.map +1 -1
- package/dist/mlx-ml/process/model-specific.d.ts +2 -1
- package/dist/mlx-ml/process/model-specific.d.ts.map +1 -1
- package/dist/mlx-ml/process/model-specific.js.map +1 -1
- package/dist/mlx-ml/process/prompt-builder.d.ts +11 -0
- package/dist/mlx-ml/process/prompt-builder.d.ts.map +1 -0
- package/dist/mlx-ml/process/prompt-builder.js +51 -0
- package/dist/mlx-ml/process/prompt-builder.js.map +1 -0
- package/dist/mlx-ml/process/types.d.ts +15 -183
- package/dist/mlx-ml/process/types.d.ts.map +1 -1
- package/dist/mlx-ml/types.d.ts +2 -45
- package/dist/mlx-ml/types.d.ts.map +1 -1
- package/dist/models-config/index.d.ts +8 -0
- package/dist/models-config/index.d.ts.map +1 -0
- package/dist/models-config/index.js +7 -0
- package/dist/models-config/index.js.map +1 -0
- package/dist/models-config/loader.d.ts +20 -0
- package/dist/models-config/loader.d.ts.map +1 -0
- package/dist/models-config/loader.js +85 -0
- package/dist/models-config/loader.js.map +1 -0
- package/dist/models-config/paths.d.ts +7 -0
- package/dist/models-config/paths.d.ts.map +1 -0
- package/dist/models-config/paths.js +11 -0
- package/dist/models-config/paths.js.map +1 -0
- package/dist/models-config/resolve.d.ts +57 -0
- package/dist/models-config/resolve.d.ts.map +1 -0
- package/dist/models-config/resolve.js +187 -0
- package/dist/models-config/resolve.js.map +1 -0
- package/dist/models-config/types.d.ts +60 -0
- package/dist/models-config/types.d.ts.map +1 -0
- package/dist/models-config/types.js +5 -0
- package/dist/models-config/types.js.map +1 -0
- package/dist/pytorch/process/index.d.ts +35 -0
- package/dist/pytorch/process/index.d.ts.map +1 -0
- package/dist/pytorch/process/index.js +69 -0
- package/dist/pytorch/process/index.js.map +1 -0
- package/dist/pytorch/pytorch-driver.d.ts +35 -0
- package/dist/pytorch/pytorch-driver.d.ts.map +1 -0
- package/dist/pytorch/pytorch-driver.js +48 -0
- package/dist/pytorch/pytorch-driver.js.map +1 -0
- package/dist/pytorch/pytorch-local-inference-adapters.d.ts +3 -0
- package/dist/pytorch/pytorch-local-inference-adapters.d.ts.map +1 -0
- package/dist/pytorch/pytorch-local-inference-adapters.js +19 -0
- package/dist/pytorch/pytorch-local-inference-adapters.js.map +1 -0
- package/dist/pytorch/pytorch-options.d.ts +8 -0
- package/dist/pytorch/pytorch-options.d.ts.map +1 -0
- package/dist/pytorch/pytorch-options.js +21 -0
- package/dist/pytorch/pytorch-options.js.map +1 -0
- package/dist/query-logger.js +1 -1
- package/dist/query-logger.js.map +1 -1
- package/dist/runtime/check.d.ts +10 -0
- package/dist/runtime/check.d.ts.map +1 -0
- package/dist/runtime/check.js +24 -0
- package/dist/runtime/check.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/manifest-core.d.mts +27 -0
- package/dist/runtime/manifest-core.d.mts.map +1 -0
- package/dist/runtime/manifest-core.mjs +68 -0
- package/dist/runtime/manifest-core.mjs.map +1 -0
- package/dist/runtime/manifest.d.ts +18 -0
- package/dist/runtime/manifest.d.ts.map +1 -0
- package/dist/runtime/manifest.js +9 -0
- package/dist/runtime/manifest.js.map +1 -0
- package/dist/runtime/paths-core.d.mts +17 -0
- package/dist/runtime/paths-core.d.mts.map +1 -0
- package/dist/runtime/paths-core.mjs +67 -0
- package/dist/runtime/paths-core.mjs.map +1 -0
- package/dist/runtime/paths.d.ts +12 -0
- package/dist/runtime/paths.d.ts.map +1 -0
- package/dist/runtime/paths.js +17 -0
- package/dist/runtime/paths.js.map +1 -0
- package/dist/types.d.ts +11 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -7
- package/scripts/download-model.js +25 -9
- package/scripts/runtime-cli.js +315 -0
- package/src/mlx-ml/python/__main__.py +43 -4
- package/src/mlx-ml/python/handlers/__init__.py +2 -1
- package/src/mlx-ml/python/handlers/completion.py +3 -27
- package/src/mlx-ml/python/handlers/{chat.py → generate.py} +33 -106
- package/src/mlx-ml/python/handlers/render.py +40 -0
- package/src/mlx-ml/python/pyproject.toml +3 -2
- package/src/mlx-ml/python/server.py +28 -8
- package/src/mlx-ml/python/utils/template_render.py +80 -0
- package/src/mlx-ml/python/utils/token_utils.py +2 -2
- package/src/mlx-ml/python/uv.lock +549 -454
- package/src/pytorch/python/__main__.py +19 -0
- package/src/pytorch/python/backends/__init__.py +3 -0
- package/src/pytorch/python/backends/base.py +84 -0
- package/src/pytorch/python/backends/transformers_lm.py +127 -0
- package/src/pytorch/python/handlers/__init__.py +6 -0
- package/src/pytorch/python/handlers/cancel.py +53 -0
- package/src/pytorch/python/handlers/capabilities.py +6 -0
- package/src/pytorch/python/handlers/completion.py +15 -0
- package/src/pytorch/python/handlers/format_test.py +70 -0
- package/src/pytorch/python/handlers/generate.py +68 -0
- package/src/pytorch/python/handlers/render.py +40 -0
- package/src/pytorch/python/handlers/tokenize.py +63 -0
- package/src/pytorch/python/pyproject.toml +36 -0
- package/src/pytorch/python/server.py +140 -0
- package/src/pytorch/python/utils/__init__.py +0 -0
- package/src/pytorch/python/utils/chat_template_constraints.py +164 -0
- package/src/pytorch/python/utils/prompt_builder.py +54 -0
- package/src/pytorch/python/utils/template_render.py +80 -0
- package/src/pytorch/python/utils/token_utils.py +376 -0
- package/src/pytorch/python/uv.lock +694 -0
- package/dist/mlx-ml/process/process-communication.d.ts +0 -45
- package/dist/mlx-ml/process/process-communication.d.ts.map +0 -1
- package/dist/mlx-ml/process/process-communication.js.map +0 -1
- package/dist/mlx-ml/process/queue.d.ts +0 -35
- package/dist/mlx-ml/process/queue.d.ts.map +0 -1
- package/dist/mlx-ml/process/queue.js.map +0 -1
- package/scripts/setup-mlx.js +0 -53
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Python runtime 管理 CLI
|
|
5
|
+
*
|
|
6
|
+
* setup mlx — ~/.modular-prompt/runtimes/mlx に venv を作成
|
|
7
|
+
* setup pytorch — ~/.modular-prompt/runtimes/pytorch に cpu-minimal venv を作成
|
|
8
|
+
* setup --status
|
|
9
|
+
* cleanup mlx [--yes]
|
|
10
|
+
* cleanup --all [--yes]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
import { existsSync, readFileSync, rmSync, mkdirSync } from 'fs';
|
|
15
|
+
import { createInterface } from 'readline';
|
|
16
|
+
import { dirname, join } from 'path';
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const packageRoot = join(__dirname, '..');
|
|
21
|
+
const driverVersion = readPackageVersion();
|
|
22
|
+
|
|
23
|
+
function runtimeModuleUrl(name) {
|
|
24
|
+
const distPath = join(packageRoot, 'dist', 'runtime', name);
|
|
25
|
+
const srcPath = join(packageRoot, 'src', 'runtime', name);
|
|
26
|
+
return pathToFileURL(existsSync(distPath) ? distPath : srcPath).href;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
RUNTIME_PROFILES,
|
|
31
|
+
getModularPromptHome,
|
|
32
|
+
getRuntimeDir,
|
|
33
|
+
getVenvPath,
|
|
34
|
+
getMlxPythonDir,
|
|
35
|
+
getPytorchPythonDir,
|
|
36
|
+
isRuntimeReady,
|
|
37
|
+
} = await import(runtimeModuleUrl('paths-core.mjs'));
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
collectInstalledPackages,
|
|
41
|
+
readManifest,
|
|
42
|
+
writeManifest,
|
|
43
|
+
} = await import(runtimeModuleUrl('manifest-core.mjs'));
|
|
44
|
+
|
|
45
|
+
function readPackageVersion() {
|
|
46
|
+
try {
|
|
47
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
48
|
+
return pkg.version ?? 'unknown';
|
|
49
|
+
} catch {
|
|
50
|
+
return 'unknown';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ensureUv() {
|
|
55
|
+
try {
|
|
56
|
+
execSync('uv --version', { stdio: 'ignore' });
|
|
57
|
+
return;
|
|
58
|
+
} catch {
|
|
59
|
+
console.log('⚠️ uv is not installed. Installing uv...');
|
|
60
|
+
execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', { stdio: 'inherit' });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function setupMlx() {
|
|
65
|
+
if (process.platform !== 'darwin') {
|
|
66
|
+
console.error('❌ MLX runtime is only available on macOS (Apple Silicon).');
|
|
67
|
+
console.error(' For local inference on this platform, use setup-pytorch or vLLM.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const pythonDir = getMlxPythonDir(packageRoot);
|
|
72
|
+
if (!existsSync(pythonDir)) {
|
|
73
|
+
console.error(`❌ MLX Python project not found: ${pythonDir}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const venvPath = getVenvPath('mlx');
|
|
78
|
+
const runtimeDir = getRuntimeDir('mlx');
|
|
79
|
+
|
|
80
|
+
console.log('🚀 Setting up MLX runtime...\n');
|
|
81
|
+
console.log(`📁 Python project: ${pythonDir}`);
|
|
82
|
+
console.log(`📁 Runtime venv: ${venvPath}\n`);
|
|
83
|
+
|
|
84
|
+
ensureUv();
|
|
85
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
const env = {
|
|
88
|
+
...process.env,
|
|
89
|
+
UV_PROJECT_ENVIRONMENT: venvPath,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
execSync('uv venv --python 3.13', { cwd: pythonDir, stdio: 'inherit', env });
|
|
94
|
+
execSync('uv pip install -e .', { cwd: pythonDir, stdio: 'inherit', env });
|
|
95
|
+
|
|
96
|
+
writeManifest('mlx', {
|
|
97
|
+
profile: 'mlx',
|
|
98
|
+
driverVersion,
|
|
99
|
+
platform: process.platform,
|
|
100
|
+
pythonVersion: '3.13',
|
|
101
|
+
createdAt: new Date().toISOString(),
|
|
102
|
+
packages: collectInstalledPackages(pythonDir, venvPath),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log('\n✅ MLX runtime setup completed.');
|
|
106
|
+
console.log(` Home: ${getModularPromptHome()}`);
|
|
107
|
+
console.log(' You can now use MlxDriver from @modular-prompt/driver');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
console.error('❌ Failed to setup MLX runtime:', message);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const PYTORCH_CPU_INDEX = 'https://download.pytorch.org/whl/cpu';
|
|
116
|
+
const PYTORCH_PYTHON_VERSION = '3.12';
|
|
117
|
+
|
|
118
|
+
function setupPytorch() {
|
|
119
|
+
const pythonDir = getPytorchPythonDir(packageRoot);
|
|
120
|
+
if (!existsSync(pythonDir)) {
|
|
121
|
+
console.error(`❌ PyTorch Python project not found: ${pythonDir}`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const venvPath = getVenvPath('pytorch');
|
|
126
|
+
const runtimeDir = getRuntimeDir('pytorch');
|
|
127
|
+
|
|
128
|
+
console.log('🚀 Setting up PyTorch runtime (cpu-minimal)...\n');
|
|
129
|
+
console.log(`📁 Python project: ${pythonDir}`);
|
|
130
|
+
console.log(`📁 Runtime venv: ${venvPath}`);
|
|
131
|
+
console.log(`📦 torch index: ${PYTORCH_CPU_INDEX}\n`);
|
|
132
|
+
|
|
133
|
+
ensureUv();
|
|
134
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
135
|
+
|
|
136
|
+
const env = {
|
|
137
|
+
...process.env,
|
|
138
|
+
UV_PROJECT_ENVIRONMENT: venvPath,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
execSync(`uv venv --python ${PYTORCH_PYTHON_VERSION}`, { cwd: pythonDir, stdio: 'inherit', env });
|
|
143
|
+
const venvPython =
|
|
144
|
+
process.platform === 'win32'
|
|
145
|
+
? join(venvPath, 'Scripts', 'python.exe')
|
|
146
|
+
: join(venvPath, 'bin', 'python');
|
|
147
|
+
execSync(`uv pip install --python "${venvPython}" "torch==2.9.1" --index-url ${PYTORCH_CPU_INDEX}`, {
|
|
148
|
+
cwd: pythonDir,
|
|
149
|
+
stdio: 'inherit',
|
|
150
|
+
env,
|
|
151
|
+
});
|
|
152
|
+
execSync(`uv pip install --python "${venvPython}" -e .`, { cwd: pythonDir, stdio: 'inherit', env });
|
|
153
|
+
|
|
154
|
+
const packages = collectInstalledPackages(pythonDir, venvPath);
|
|
155
|
+
writeManifest('pytorch', {
|
|
156
|
+
profile: 'pytorch',
|
|
157
|
+
variant: 'cpu-minimal',
|
|
158
|
+
driverVersion,
|
|
159
|
+
platform: process.platform,
|
|
160
|
+
pythonVersion: PYTORCH_PYTHON_VERSION,
|
|
161
|
+
torchVersion: packages?.torch,
|
|
162
|
+
createdAt: new Date().toISOString(),
|
|
163
|
+
packages,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
console.log('\n✅ PyTorch runtime setup completed (cpu-minimal).');
|
|
167
|
+
console.log(` Home: ${getModularPromptHome()}`);
|
|
168
|
+
console.log(' You can now use PyTorchDriver from @modular-prompt/driver');
|
|
169
|
+
console.log(' For CUDA / custom environments, see docs/LOCAL_MODEL_SETUP.md');
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
172
|
+
console.error('❌ Failed to setup PyTorch runtime:', message);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatManifestDetail(manifest) {
|
|
178
|
+
const parts = [`driver ${manifest.driverVersion}`];
|
|
179
|
+
if (manifest.variant) {
|
|
180
|
+
parts.push(`variant ${manifest.variant}`);
|
|
181
|
+
}
|
|
182
|
+
const torchVersion = manifest.torchVersion ?? manifest.packages?.torch;
|
|
183
|
+
if (torchVersion) {
|
|
184
|
+
parts.push(`torch ${torchVersion}`);
|
|
185
|
+
}
|
|
186
|
+
parts.push(manifest.createdAt);
|
|
187
|
+
return ` (${parts.join(', ')})`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function printStatus() {
|
|
191
|
+
console.log(`modular-prompt home: ${getModularPromptHome()}\n`);
|
|
192
|
+
for (const profile of RUNTIME_PROFILES) {
|
|
193
|
+
const ready = isRuntimeReady(profile);
|
|
194
|
+
const manifest = ready ? readManifest(profile) : null;
|
|
195
|
+
const detail = manifest ? formatManifestDetail(manifest) : '';
|
|
196
|
+
const icon = ready ? '✅' : '❌';
|
|
197
|
+
const runtimePath = getRuntimeDir(profile);
|
|
198
|
+
console.log(`${icon} ${profile}: ${ready ? 'ready' : 'not installed'}${detail}`);
|
|
199
|
+
console.log(` ${runtimePath}`);
|
|
200
|
+
}
|
|
201
|
+
const setupHints = [];
|
|
202
|
+
if (!isRuntimeReady('mlx') && process.platform === 'darwin') {
|
|
203
|
+
setupHints.push('pnpm run setup-mlx -w @modular-prompt/driver');
|
|
204
|
+
}
|
|
205
|
+
if (!isRuntimeReady('pytorch')) {
|
|
206
|
+
setupHints.push('pnpm run setup-pytorch -w @modular-prompt/driver');
|
|
207
|
+
}
|
|
208
|
+
if (setupHints.length > 0) {
|
|
209
|
+
console.log(`\nRun: ${setupHints.join(' or ')}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function confirm(message) {
|
|
214
|
+
if (process.argv.includes('--yes') || process.argv.includes('-y')) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
218
|
+
const answer = await new Promise((resolve) => {
|
|
219
|
+
rl.question(`${message} [y/N] `, resolve);
|
|
220
|
+
});
|
|
221
|
+
rl.close();
|
|
222
|
+
return String(answer).toLowerCase() === 'y' || String(answer).toLowerCase() === 'yes';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function cleanupProfile(profile) {
|
|
226
|
+
if (!existsSync(getRuntimeDir(profile))) {
|
|
227
|
+
console.log(`ℹ️ ${profile}: nothing to clean (${getRuntimeDir(profile)} does not exist)`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const ok = await confirm(`Delete ${profile} runtime at ${getRuntimeDir(profile)}?`);
|
|
231
|
+
if (!ok) {
|
|
232
|
+
console.log('Cancelled.');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
rmSync(getRuntimeDir(profile), { recursive: true, force: true });
|
|
236
|
+
console.log(`✅ Removed ${profile} runtime.`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function cleanupAll() {
|
|
240
|
+
const home = getModularPromptHome();
|
|
241
|
+
if (!existsSync(home)) {
|
|
242
|
+
console.log(`ℹ️ Nothing to clean (${home} does not exist)`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const ok = await confirm(`Delete entire modular-prompt home at ${home}?`);
|
|
246
|
+
if (!ok) {
|
|
247
|
+
console.log('Cancelled.');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
rmSync(home, { recursive: true, force: true });
|
|
251
|
+
console.log(`✅ Removed ${home}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function printUsage() {
|
|
255
|
+
console.log(`Usage:
|
|
256
|
+
node scripts/runtime-cli.js setup mlx Set up MLX Python runtime (macOS only)
|
|
257
|
+
node scripts/runtime-cli.js setup pytorch Set up PyTorch runtime (cpu-minimal)
|
|
258
|
+
node scripts/runtime-cli.js setup --status Show runtime status
|
|
259
|
+
node scripts/runtime-cli.js cleanup mlx Remove MLX runtime
|
|
260
|
+
node scripts/runtime-cli.js cleanup pytorch Remove PyTorch runtime
|
|
261
|
+
node scripts/runtime-cli.js cleanup --all Remove entire ~/.modular-prompt
|
|
262
|
+
node scripts/runtime-cli.js cleanup ... --yes Skip confirmation
|
|
263
|
+
|
|
264
|
+
npm scripts: setup-mlx, setup-pytorch, runtime:status, runtime:cleanup`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function main() {
|
|
268
|
+
const [command, target] = process.argv.slice(2);
|
|
269
|
+
|
|
270
|
+
if (!command || command === '--help' || command === '-h') {
|
|
271
|
+
printUsage();
|
|
272
|
+
process.exit(0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (command === 'setup') {
|
|
276
|
+
if (target === '--status' || target === 'status') {
|
|
277
|
+
printStatus();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (target === 'mlx') {
|
|
281
|
+
setupMlx();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (target === 'pytorch') {
|
|
285
|
+
setupPytorch();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.error(`Unknown setup target: ${target ?? '(none)'}`);
|
|
289
|
+
printUsage();
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (command === 'cleanup') {
|
|
294
|
+
if (target === '--all' || target === 'all') {
|
|
295
|
+
await cleanupAll();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (target === 'mlx' || target === 'pytorch') {
|
|
299
|
+
await cleanupProfile(target);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
console.error(`Unknown cleanup target: ${target ?? '(none)'}`);
|
|
303
|
+
printUsage();
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.error(`Unknown command: ${command}`);
|
|
308
|
+
printUsage();
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
main().catch((error) => {
|
|
313
|
+
console.error(error);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
});
|
|
@@ -6,7 +6,28 @@ from utils.vlm_utils import detect_model_kind
|
|
|
6
6
|
from server import Server
|
|
7
7
|
|
|
8
8
|
model_name = sys.argv[1] if len(sys.argv) > 1 else "mlx-community/gemma-3-270m-it-qat-4bit"
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
VALID_BACKENDS = {"auto", "lm", "vlm", "optiq"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_backend_mode() -> str:
|
|
14
|
+
if "--backend" in sys.argv:
|
|
15
|
+
idx = sys.argv.index("--backend")
|
|
16
|
+
if idx + 1 < len(sys.argv):
|
|
17
|
+
mode = sys.argv[idx + 1]
|
|
18
|
+
if mode not in VALID_BACKENDS:
|
|
19
|
+
sys.stderr.write(
|
|
20
|
+
f"Invalid --backend value: {mode}. "
|
|
21
|
+
f"Use one of: {', '.join(sorted(VALID_BACKENDS))}\n"
|
|
22
|
+
)
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
return mode
|
|
25
|
+
if "--text-only" in sys.argv:
|
|
26
|
+
return "lm"
|
|
27
|
+
return "auto"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
backend_mode = parse_backend_mode()
|
|
10
31
|
|
|
11
32
|
drafter_model = None
|
|
12
33
|
if "--drafter" in sys.argv:
|
|
@@ -25,8 +46,26 @@ if "--draft-block-size" in sys.argv:
|
|
|
25
46
|
sys.exit(1)
|
|
26
47
|
|
|
27
48
|
|
|
28
|
-
def
|
|
29
|
-
|
|
49
|
+
def ensure_optiq_registered() -> None:
|
|
50
|
+
import optiq # noqa: F401 - registers vendored architectures into mlx-lm
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def create_backend(model_name: str, mode: str = "auto"):
|
|
54
|
+
if mode == "optiq":
|
|
55
|
+
ensure_optiq_registered()
|
|
56
|
+
mode = "lm"
|
|
57
|
+
|
|
58
|
+
if mode == "lm":
|
|
59
|
+
backend = MlxLmBackend()
|
|
60
|
+
backend.load(model_name)
|
|
61
|
+
return backend, "lm"
|
|
62
|
+
|
|
63
|
+
if mode == "vlm":
|
|
64
|
+
backend = MlxVlmBackend()
|
|
65
|
+
backend.load(model_name)
|
|
66
|
+
return backend, "vlm"
|
|
67
|
+
|
|
68
|
+
model_kind = detect_model_kind(model_name)
|
|
30
69
|
|
|
31
70
|
if model_kind == "vlm":
|
|
32
71
|
backend = MlxVlmBackend()
|
|
@@ -42,7 +81,7 @@ def create_backend(model_name: str, text_only: bool = False):
|
|
|
42
81
|
|
|
43
82
|
|
|
44
83
|
if __name__ == "__main__":
|
|
45
|
-
backend, model_kind = create_backend(model_name,
|
|
84
|
+
backend, model_kind = create_backend(model_name, backend_mode)
|
|
46
85
|
|
|
47
86
|
if drafter_model:
|
|
48
87
|
backend.load_drafter(drafter_model)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from handlers.cache import handle_cache_prefill
|
|
2
2
|
from handlers.capabilities import handle_capabilities
|
|
3
|
-
from handlers.chat import handle_chat
|
|
4
3
|
from handlers.completion import handle_completion
|
|
5
4
|
from handlers.format_test import handle_format_test
|
|
5
|
+
from handlers.generate import handle_generate
|
|
6
|
+
from handlers.render import handle_render
|
|
6
7
|
from handlers.tokenize import handle_tokenize
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
|
-
import re
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
3
|
from backends.base import ModelBackend
|
|
8
|
-
from handlers.
|
|
4
|
+
from handlers.generate import handle_generate
|
|
9
5
|
|
|
10
6
|
|
|
11
7
|
def handle_completion(
|
|
@@ -15,25 +11,5 @@ def handle_completion(
|
|
|
15
11
|
images: list | None = None,
|
|
16
12
|
max_image_size: int = 768,
|
|
17
13
|
) -> None:
|
|
18
|
-
"""completion API
|
|
19
|
-
|
|
20
|
-
options = {}
|
|
21
|
-
|
|
22
|
-
final_options = dict(options)
|
|
23
|
-
if images:
|
|
24
|
-
final_options["max_image_size"] = max_image_size
|
|
25
|
-
if os.getenv('MLX_DEBUG'):
|
|
26
|
-
display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
|
|
27
|
-
sys.stderr.write(f"--- vlm completion (images: {len(images)}, max_size: {max_image_size})\n{display_prompt}\n")
|
|
28
|
-
elif os.getenv('MLX_DEBUG'):
|
|
29
|
-
if isinstance(prompt, list):
|
|
30
|
-
sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
|
|
31
|
-
else:
|
|
32
|
-
sys.stderr.write(f"--- prompt\n{prompt}\n")
|
|
33
|
-
|
|
34
|
-
for response in backend.stream_generate(prompt, final_options, images):
|
|
35
|
-
if poll_cancel():
|
|
36
|
-
break
|
|
37
|
-
print(response.text.replace("\0", ""), end="", flush=True)
|
|
38
|
-
|
|
39
|
-
print("\n", end="\0", flush=True)
|
|
14
|
+
"""completion API(後方互換)— generate に委譲"""
|
|
15
|
+
handle_generate(backend, prompt, options, images, max_image_size)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import re
|
|
5
6
|
import sys
|
|
6
7
|
|
|
7
8
|
from backends.base import ModelBackend
|
|
8
9
|
from mlx_lm.models.cache import trim_prompt_cache
|
|
9
|
-
from utils.prompt_builder import generate_merged_prompt, supports_chat_template
|
|
10
10
|
from handlers.cancel import poll_cancel
|
|
11
11
|
|
|
12
12
|
|
|
@@ -53,65 +53,42 @@ def _stream_to_stdout(
|
|
|
53
53
|
print("", end="\0", flush=True)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def
|
|
56
|
+
def handle_generate(
|
|
57
57
|
backend: ModelBackend,
|
|
58
|
-
|
|
59
|
-
messages: list,
|
|
60
|
-
primer: str | None = None,
|
|
58
|
+
prompt: str | list[int],
|
|
61
59
|
options: dict | None = None,
|
|
62
|
-
tools: list | None = None,
|
|
63
60
|
images: list | None = None,
|
|
64
61
|
max_image_size: int = 768,
|
|
65
|
-
|
|
62
|
+
primer: str | None = None,
|
|
66
63
|
cache_path: str | None = None,
|
|
67
64
|
cache_trim_tokens: int | None = None,
|
|
68
65
|
) -> None:
|
|
69
|
-
"""
|
|
66
|
+
"""LIP generate: 整形済み prompt のストリーム推論"""
|
|
70
67
|
if options is None:
|
|
71
68
|
options = {}
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if backend.supports_vision():
|
|
76
|
-
add_generation_prompt = True
|
|
77
|
-
fmt_messages = list(messages)
|
|
78
|
-
if primer is not None:
|
|
79
|
-
fmt_messages.append({"role": "assistant", "content": primer})
|
|
80
|
-
add_generation_prompt = False
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
prompt = tokenizer.apply_chat_template(
|
|
84
|
-
fmt_messages,
|
|
85
|
-
tools=tools,
|
|
86
|
-
add_generation_prompt=add_generation_prompt,
|
|
87
|
-
tokenize=False,
|
|
88
|
-
)
|
|
89
|
-
except TypeError:
|
|
90
|
-
prompt = tokenizer.apply_chat_template(
|
|
91
|
-
fmt_messages,
|
|
92
|
-
add_generation_prompt=add_generation_prompt,
|
|
93
|
-
tokenize=False,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
if primer is not None:
|
|
97
|
-
prompt = primer.join(prompt.split(primer)[0:-1]) + primer
|
|
98
|
-
|
|
99
|
-
display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
|
|
100
|
-
sys.stderr.write(f"--- vlm prompt (images: {len(images) if images else 0}, max_size: {max_image_size})\n{display_prompt}\n")
|
|
101
|
-
|
|
102
|
-
final_options = dict(options)
|
|
70
|
+
final_options = dict(options)
|
|
71
|
+
if images:
|
|
103
72
|
final_options["max_image_size"] = max_image_size
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
73
|
+
if os.getenv('MLX_DEBUG'):
|
|
74
|
+
if isinstance(prompt, str):
|
|
75
|
+
display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
|
|
76
|
+
else:
|
|
77
|
+
display_prompt = f"<token ids: len={len(prompt)}>"
|
|
78
|
+
sys.stderr.write(
|
|
79
|
+
f"--- vlm generate (images: {len(images)}, max_size: {max_image_size})\n{display_prompt}\n"
|
|
80
|
+
)
|
|
81
|
+
elif os.getenv('MLX_DEBUG'):
|
|
82
|
+
if isinstance(prompt, list):
|
|
83
|
+
sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
|
|
84
|
+
else:
|
|
85
|
+
sys.stderr.write(f"--- prompt\n{prompt}\n")
|
|
112
86
|
|
|
113
|
-
|
|
87
|
+
# VLM 経路では KV キャッシュを使わない(旧 chat ハンドラと同様)
|
|
88
|
+
prompt_cache = None
|
|
114
89
|
cache_tokens = 0
|
|
90
|
+
if cache_path and not images:
|
|
91
|
+
prompt_cache = backend.load_cache_from_file(cache_path)
|
|
115
92
|
if prompt_cache is not None:
|
|
116
93
|
if cache_trim_tokens is not None:
|
|
117
94
|
current_offset = backend.get_cache_offset(prompt_cache)
|
|
@@ -128,7 +105,6 @@ def handle_chat(
|
|
|
128
105
|
if meta_count is not None:
|
|
129
106
|
cache_tokens = meta_count
|
|
130
107
|
else:
|
|
131
|
-
# Legacy cache without meta file - skip it for safety
|
|
132
108
|
sys.stderr.write(
|
|
133
109
|
f"WARNING: Cache file exists but no .meta.json found at {cache_path}. "
|
|
134
110
|
"Ignoring cache for safety (may be from old implementation).\n"
|
|
@@ -142,67 +118,11 @@ def handle_chat(
|
|
|
142
118
|
elif cache_path:
|
|
143
119
|
sys.stderr.write(f"KV cache load FAILED: {cache_path}\n")
|
|
144
120
|
|
|
145
|
-
if not supports_chat_template(tokenizer):
|
|
146
|
-
prompt = generate_merged_prompt(messages, capabilities)
|
|
147
|
-
if prompt_cache is not None:
|
|
148
|
-
sys.stderr.write("KV cache ignored: model does not support chat template\n")
|
|
149
|
-
_stream_to_stdout(backend, prompt, options, primer=primer)
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
add_generation_prompt = True
|
|
153
|
-
fmt_messages = list(messages)
|
|
154
|
-
if primer is not None:
|
|
155
|
-
fmt_messages.append({"role": "assistant", "content": primer})
|
|
156
|
-
add_generation_prompt = False
|
|
157
|
-
|
|
158
|
-
extra_kwargs = {}
|
|
159
|
-
if tools is not None:
|
|
160
|
-
extra_kwargs["tools"] = tools
|
|
161
|
-
if reasoning_effort is not None:
|
|
162
|
-
extra_kwargs["reasoning_effort"] = reasoning_effort
|
|
163
|
-
|
|
164
|
-
trust_remote_code = options.get("trust_remote_code")
|
|
165
|
-
if trust_remote_code is not None:
|
|
166
|
-
extra_kwargs["trust_remote_code"] = trust_remote_code
|
|
167
|
-
|
|
168
|
-
try:
|
|
169
|
-
prompt = tokenizer.apply_chat_template(
|
|
170
|
-
fmt_messages,
|
|
171
|
-
add_generation_prompt=add_generation_prompt,
|
|
172
|
-
tokenize=False,
|
|
173
|
-
**extra_kwargs,
|
|
174
|
-
)
|
|
175
|
-
except TypeError:
|
|
176
|
-
try:
|
|
177
|
-
fallback_kwargs = {}
|
|
178
|
-
if tools is not None:
|
|
179
|
-
fallback_kwargs["tools"] = tools
|
|
180
|
-
prompt = tokenizer.apply_chat_template(
|
|
181
|
-
fmt_messages,
|
|
182
|
-
add_generation_prompt=add_generation_prompt,
|
|
183
|
-
tokenize=False,
|
|
184
|
-
**fallback_kwargs,
|
|
185
|
-
)
|
|
186
|
-
except TypeError:
|
|
187
|
-
prompt = tokenizer.apply_chat_template(
|
|
188
|
-
fmt_messages,
|
|
189
|
-
add_generation_prompt=add_generation_prompt,
|
|
190
|
-
tokenize=False,
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
if primer is not None:
|
|
194
|
-
prompt = primer.join(prompt.split(primer)[0:-1]) + primer
|
|
195
|
-
|
|
196
|
-
if isinstance(prompt, list):
|
|
197
|
-
sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
|
|
198
|
-
else:
|
|
199
|
-
sys.stderr.write(f"--- prompt\n{prompt}\n")
|
|
200
|
-
|
|
201
|
-
final_options = dict(options)
|
|
202
121
|
final_options.pop("trust_remote_code", None)
|
|
203
122
|
|
|
204
123
|
effective_prompt = prompt
|
|
205
124
|
if prompt_cache is not None and cache_tokens > 0 and isinstance(prompt, str):
|
|
125
|
+
tokenizer = backend.get_tokenizer()
|
|
206
126
|
add_special = tokenizer.bos_token is None or not prompt.startswith(
|
|
207
127
|
tokenizer.bos_token
|
|
208
128
|
)
|
|
@@ -221,4 +141,11 @@ def handle_chat(
|
|
|
221
141
|
)
|
|
222
142
|
prompt_cache = None
|
|
223
143
|
|
|
224
|
-
_stream_to_stdout(
|
|
144
|
+
_stream_to_stdout(
|
|
145
|
+
backend,
|
|
146
|
+
effective_prompt,
|
|
147
|
+
final_options,
|
|
148
|
+
images=images,
|
|
149
|
+
primer=primer,
|
|
150
|
+
prompt_cache=prompt_cache,
|
|
151
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from backends.base import ModelBackend
|
|
6
|
+
from utils.template_render import apply_chat_template_prompt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle_render(
|
|
10
|
+
backend: ModelBackend,
|
|
11
|
+
messages: list,
|
|
12
|
+
options: dict | None = None,
|
|
13
|
+
tools: list | None = None,
|
|
14
|
+
reasoning_effort: str | None = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
"""LIP render: apply_chat_template のみ(推論しない)"""
|
|
17
|
+
if options is None:
|
|
18
|
+
options = {}
|
|
19
|
+
|
|
20
|
+
result: dict = {
|
|
21
|
+
"formatted_prompt": None,
|
|
22
|
+
"error": None,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
trust_remote_code = options.get("trust_remote_code")
|
|
27
|
+
primer = options.get("primer")
|
|
28
|
+
prompt = apply_chat_template_prompt(
|
|
29
|
+
backend,
|
|
30
|
+
messages,
|
|
31
|
+
primer=primer,
|
|
32
|
+
tools=tools,
|
|
33
|
+
reasoning_effort=reasoning_effort,
|
|
34
|
+
trust_remote_code=trust_remote_code,
|
|
35
|
+
)
|
|
36
|
+
result["formatted_prompt"] = prompt
|
|
37
|
+
except Exception as e:
|
|
38
|
+
result["error"] = str(e)
|
|
39
|
+
|
|
40
|
+
print(json.dumps(result), end="\0", flush=True)
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
name = "mlx_driver"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
description = "MLX driver for moduler-prompt"
|
|
5
|
-
requires-python = ">=3.
|
|
5
|
+
requires-python = ">=3.11,<3.14"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"flex==6.14.1",
|
|
8
8
|
"hf-xet==1.2.0",
|
|
9
9
|
"jinja2==3.1.6",
|
|
10
10
|
"mlx>=0.31.2; sys_platform == 'darwin'",
|
|
11
11
|
"mlx-lm==0.31.3; sys_platform == 'darwin'",
|
|
12
|
-
"mlx-
|
|
12
|
+
"mlx-optiq>=0.4.4; sys_platform == 'darwin'",
|
|
13
|
+
"mlx-vlm==0.6.4; sys_platform == 'darwin'",
|
|
13
14
|
"tokenizers==0.22.2",
|
|
14
15
|
"torch==2.9.1",
|
|
15
16
|
"torchvision==0.24.1",
|