@minimalcorp/tsunagi 0.1.26 → 0.1.28
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/.next/standalone/apps/web/.next/BUILD_ID +1 -1
- package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
- package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
- package/.next/standalone/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/server/lib/llm-process.js +29 -8
- package/dist/server/lib/whisper-process.js +27 -6
- package/npm-shrinkwrap.json +505 -119
- package/package.json +1 -1
- package/whisper-server/server.py +2 -1
- /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → ryH59GWZw_ooyW3FOb1yO}/_buildManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → ryH59GWZw_ooyW3FOb1yO}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{7jfNq7gWy6LmFypqM6QZf → ryH59GWZw_ooyW3FOb1yO}/_ssgManifest.js +0 -0
|
@@ -19,6 +19,14 @@ const HF_CACHE_DIR = path.join(TSUNAGI_LLM_DIR, 'cache');
|
|
|
19
19
|
const MODEL_CACHE_DIR = path.join(HF_CACHE_DIR, 'hub', 'models--mlx-community--Qwen3-30B-A3B-Instruct-2507-4bit');
|
|
20
20
|
// 実測値(2026-07時点、4bit量子化された重み一式の合計)。多少の変動はあるがETA計算の目安として使う。
|
|
21
21
|
const EXPECTED_MODEL_BYTES = 17_200_000_000;
|
|
22
|
+
// starting_serverフェーズ(spawn後にhealthyになるまで待つ時間)の上限。
|
|
23
|
+
// mlx_lm.serverは/healthをモデル本体のロード完了を待たずに返す(ロードはバックグラウンド
|
|
24
|
+
// スレッドで進む)ため、この待ち時間の大半はPython起動/import初期化のばらつきに対する
|
|
25
|
+
// マージン。whisperより依存関係が重いぶん長めの90秒を維持する。
|
|
26
|
+
// プロセスが起動途中でクラッシュした場合はこのタイムアウトを待たず即座に検知する。
|
|
27
|
+
const STARTUP_TIMEOUT_MS = 90_000;
|
|
28
|
+
// クラッシュ時にエラーメッセージへ含めるstderrの上限文字数。
|
|
29
|
+
const STDERR_TAIL_MAX_CHARS = 4000;
|
|
22
30
|
// このファイルは apps/server/src/lib (dev) または apps/cli/dist/server/lib
|
|
23
31
|
// (npm配布物) のいずれかにいる。どちらの場合も3階層上に llm-server が
|
|
24
32
|
// 兄弟ディレクトリとして存在するようレイアウトを揃えている
|
|
@@ -111,7 +119,11 @@ async function trackModelDownload(child) {
|
|
|
111
119
|
const elapsedSec = (now - lastTime) / 1000;
|
|
112
120
|
const bytesPerSec = elapsedSec > 0 ? (downloaded - lastBytes) / elapsedSec : 0;
|
|
113
121
|
const remaining = EXPECTED_MODEL_BYTES - downloaded;
|
|
114
|
-
|
|
122
|
+
// 転送が一時的に止まって見える(ポーリング間隔とディスク書き込みのタイミングがずれる)だけで
|
|
123
|
+
// 速度0になることがあるため、その場合は直前のETAを維持し「計算中...」への逆戻りを防ぐ。
|
|
124
|
+
const etaSeconds = bytesPerSec > 0
|
|
125
|
+
? Math.max(0, Math.round(remaining / bytesPerSec))
|
|
126
|
+
: (downloadProgress?.etaSeconds ?? null);
|
|
115
127
|
downloadProgress = {
|
|
116
128
|
downloadedBytes: downloaded,
|
|
117
129
|
totalBytes: EXPECTED_MODEL_BYTES,
|
|
@@ -150,25 +162,34 @@ async function runSetupAndStart(dir) {
|
|
|
150
162
|
downloadProgress = undefined;
|
|
151
163
|
}
|
|
152
164
|
currentStep = 'starting_server';
|
|
153
|
-
const child = spawn(venvPython(), ['-m', 'mlx_lm.server', '--model', MODEL, '--host', '127.0.0.1', '--port', String(LLM_PORT)], { cwd: dir, stdio: 'ignore', env: hfEnv });
|
|
154
|
-
|
|
165
|
+
const child = spawn(venvPython(), ['-m', 'mlx_lm.server', '--model', MODEL, '--host', '127.0.0.1', '--port', String(LLM_PORT)], { cwd: dir, stdio: ['ignore', 'ignore', 'pipe'], env: hfEnv });
|
|
166
|
+
let stderrTail = '';
|
|
167
|
+
child.stderr?.on('data', (chunk) => {
|
|
168
|
+
stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);
|
|
169
|
+
});
|
|
170
|
+
// healthポーリングとは別に、プロセスが起動途中で終了したことをタイムアウトを待たず検知する。
|
|
171
|
+
let exitInfo = null;
|
|
172
|
+
child.on('exit', (code, signal) => {
|
|
173
|
+
exitInfo = `exit code=${code} signal=${signal}`;
|
|
155
174
|
if (managedProcess === child)
|
|
156
175
|
managedProcess = null;
|
|
157
176
|
});
|
|
158
|
-
child.on('error', () => {
|
|
177
|
+
child.on('error', (err) => {
|
|
178
|
+
exitInfo = `spawn error: ${err.message}`;
|
|
159
179
|
if (managedProcess === child)
|
|
160
180
|
managedProcess = null;
|
|
161
181
|
});
|
|
162
182
|
managedProcess = child;
|
|
163
|
-
// MoEで実計算はアクティブパラメータ(~3B)分のみだが、4bit量子化でも~17GBの
|
|
164
|
-
// 重みファイル自体はメモリへ展開する必要があるため、whisperより長めに待つ。
|
|
165
183
|
const start = Date.now();
|
|
166
|
-
while (Date.now() - start <
|
|
184
|
+
while (Date.now() - start < STARTUP_TIMEOUT_MS) {
|
|
185
|
+
if (exitInfo) {
|
|
186
|
+
throw new Error(`llm-server crashed while starting (${exitInfo})${stderrTail ? `\n${stderrTail}` : ''}`);
|
|
187
|
+
}
|
|
167
188
|
if (await checkHealth())
|
|
168
189
|
return;
|
|
169
190
|
await sleep(1000);
|
|
170
191
|
}
|
|
171
|
-
throw new Error(
|
|
192
|
+
throw new Error(`Server did not become healthy within ${STARTUP_TIMEOUT_MS / 1000}s of starting`);
|
|
172
193
|
}
|
|
173
194
|
export function startLlmServer() {
|
|
174
195
|
if (setupPromise || managedProcess) {
|
|
@@ -15,6 +15,12 @@ const HF_CACHE_DIR = path.join(TSUNAGI_WHISPER_DIR, 'cache');
|
|
|
15
15
|
const MODEL_CACHE_DIR = path.join(HF_CACHE_DIR, 'hub', 'models--mlx-community--whisper-large-v3-turbo');
|
|
16
16
|
// 実測値(2026-07時点、encoder+decoder等の合計)。多少の変動はあるがETA計算の目安として使う。
|
|
17
17
|
const EXPECTED_MODEL_BYTES = 1_614_000_000;
|
|
18
|
+
// starting_serverフェーズ(spawn後にhealthyになるまで待つ時間)の上限。
|
|
19
|
+
// 遅いマシンでのPython起動/import/Metal初期化のばらつきを吸収するため60秒に設定。
|
|
20
|
+
// プロセスが起動途中でクラッシュした場合はこのタイムアウトを待たず即座に検知する。
|
|
21
|
+
const STARTUP_TIMEOUT_MS = 60_000;
|
|
22
|
+
// クラッシュ時にエラーメッセージへ含めるstderrの上限文字数。
|
|
23
|
+
const STDERR_TAIL_MAX_CHARS = 4000;
|
|
18
24
|
// このファイルは apps/server/src/lib (dev) または apps/cli/dist/server/lib
|
|
19
25
|
// (npm配布物) のいずれかにいる。どちらの場合も3階層上に whisper-server が
|
|
20
26
|
// 兄弟ディレクトリとして存在するようレイアウトを揃えている
|
|
@@ -107,7 +113,11 @@ async function trackModelDownload(child) {
|
|
|
107
113
|
const elapsedSec = (now - lastTime) / 1000;
|
|
108
114
|
const bytesPerSec = elapsedSec > 0 ? (downloaded - lastBytes) / elapsedSec : 0;
|
|
109
115
|
const remaining = EXPECTED_MODEL_BYTES - downloaded;
|
|
110
|
-
|
|
116
|
+
// 転送が一時的に止まって見える(ポーリング間隔とディスク書き込みのタイミングがずれる)だけで
|
|
117
|
+
// 速度0になることがあるため、その場合は直前のETAを維持し「計算中...」への逆戻りを防ぐ。
|
|
118
|
+
const etaSeconds = bytesPerSec > 0
|
|
119
|
+
? Math.max(0, Math.round(remaining / bytesPerSec))
|
|
120
|
+
: (downloadProgress?.etaSeconds ?? null);
|
|
111
121
|
downloadProgress = {
|
|
112
122
|
downloadedBytes: downloaded,
|
|
113
123
|
totalBytes: EXPECTED_MODEL_BYTES,
|
|
@@ -146,23 +156,34 @@ async function runSetupAndStart(dir) {
|
|
|
146
156
|
downloadProgress = undefined;
|
|
147
157
|
}
|
|
148
158
|
currentStep = 'starting_server';
|
|
149
|
-
const child = spawn(venvPython(), ['-m', 'uvicorn', 'server:app', '--host', '127.0.0.1', '--port', '8765'], { cwd: dir, stdio: 'ignore', env: hfEnv });
|
|
150
|
-
|
|
159
|
+
const child = spawn(venvPython(), ['-m', 'uvicorn', 'server:app', '--host', '127.0.0.1', '--port', '8765'], { cwd: dir, stdio: ['ignore', 'ignore', 'pipe'], env: hfEnv });
|
|
160
|
+
let stderrTail = '';
|
|
161
|
+
child.stderr?.on('data', (chunk) => {
|
|
162
|
+
stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);
|
|
163
|
+
});
|
|
164
|
+
// healthポーリングとは別に、プロセスが起動途中で終了したことをタイムアウトを待たず検知する。
|
|
165
|
+
let exitInfo = null;
|
|
166
|
+
child.on('exit', (code, signal) => {
|
|
167
|
+
exitInfo = `exit code=${code} signal=${signal}`;
|
|
151
168
|
if (managedProcess === child)
|
|
152
169
|
managedProcess = null;
|
|
153
170
|
});
|
|
154
|
-
child.on('error', () => {
|
|
171
|
+
child.on('error', (err) => {
|
|
172
|
+
exitInfo = `spawn error: ${err.message}`;
|
|
155
173
|
if (managedProcess === child)
|
|
156
174
|
managedProcess = null;
|
|
157
175
|
});
|
|
158
176
|
managedProcess = child;
|
|
159
177
|
const start = Date.now();
|
|
160
|
-
while (Date.now() - start <
|
|
178
|
+
while (Date.now() - start < STARTUP_TIMEOUT_MS) {
|
|
179
|
+
if (exitInfo) {
|
|
180
|
+
throw new Error(`whisper-server crashed while starting (${exitInfo})${stderrTail ? `\n${stderrTail}` : ''}`);
|
|
181
|
+
}
|
|
161
182
|
if (await checkHealth())
|
|
162
183
|
return;
|
|
163
184
|
await sleep(1000);
|
|
164
185
|
}
|
|
165
|
-
throw new Error(
|
|
186
|
+
throw new Error(`Server did not become healthy within ${STARTUP_TIMEOUT_MS / 1000}s of starting`);
|
|
166
187
|
}
|
|
167
188
|
export function startWhisperServer() {
|
|
168
189
|
if (setupPromise || managedProcess) {
|