pi-llamacpp-infra 1.2.1 → 1.2.2
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 +8 -8
- package/package.json +1 -1
- package/src/index.ts +3 -5
- package/src/speed.ts +26 -21
package/README.md
CHANGED
|
@@ -247,20 +247,20 @@ llama.cpp-family models are registered as reasoning models, exactly like a nativ
|
|
|
247
247
|
|
|
248
248
|
## Live Speed & Metrics (footer)
|
|
249
249
|
|
|
250
|
-
When enabled, the speed reading appears in the footer's status line (no extra terminal row) whenever the active model is from llamacpp-infra, updating constantly while tokens flow:
|
|
250
|
+
When enabled, the speed reading appears in the footer's status line (no extra terminal row) whenever the active model is from llamacpp-infra, updating constantly while tokens flow. Both entries are kept ultra-compact so they coexist with other extensions on pi's single status line (which truncates from the end):
|
|
251
251
|
|
|
252
252
|
```
|
|
253
|
-
🦙
|
|
254
|
-
🦙
|
|
255
|
-
🦙
|
|
256
|
-
🦙
|
|
257
|
-
🦙
|
|
253
|
+
🦙(12) ⚡… (before the first token)
|
|
254
|
+
🦙(12) ⚡ 420 t/s 🔥 38.1 t/s (while streaming)
|
|
255
|
+
🦙(12) ⚡ 420 t/s 🔥 38.1 t/s (just after the answer ends)
|
|
256
|
+
🦙(12) ⏸ (between turns)
|
|
257
|
+
🦙(12) ▶2 ⚡ 150 t/s 🔥 18.0 t/s (pi idle, server busy for other clients)
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
-
(
|
|
260
|
+
(`🦙(n)` is the extension's model-count status; both live on the same footer line, so no extra row is consumed.)
|
|
261
261
|
|
|
262
262
|
- **Client measurement (always, no `--metrics` needed)** — prefill speed = `prompt tokens ÷ (request → first token)` (pi's `usage.input`, OpenAI-style `prompt_tokens` as fallback); generation speed = a moving 1.5 s window over per-token arrival samples. Updated ~every 100 ms while a stream is live (throttled, and unchanged text is skipped, so the footer never churns).
|
|
263
|
-
- **Server supplement (only when pi is idle)** — the poller fetches the server's Prometheus `/metrics` endpoint (or JSON `/stats`) every `metricsPollMs` (default 5 s). If the server reports other clients processing, their ⚡/🔥 rates are shown; when the server is idle, the plain
|
|
263
|
+
- **Server supplement (only when pi is idle)** — the poller fetches the server's Prometheus `/metrics` endpoint (or JSON `/stats`) every `metricsPollMs` (default 5 s). If the server reports other clients processing, their ⚡/🔥 rates are shown (`▶n`); when the server is idle, the plain `⏸` reading returns.
|
|
264
264
|
|
|
265
265
|
## Architecture
|
|
266
266
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-llamacpp-infra",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Discovery, metrics and control of llama.cpp-family servers for pi (llama.cpp, ZINC, DwarfStar/ds4, lucebox, LM Studio): scan machines (localhost, LAN, Tailscale), register models, live metrics, per-model thinking budgets, vision detection, native config UI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/index.ts
CHANGED
|
@@ -225,7 +225,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
225
225
|
|
|
226
226
|
async function rescan(ctx?: ExtensionContext) {
|
|
227
227
|
if (!extensionActive) return;
|
|
228
|
-
if (ctxHasUI(ctx)) ctx.ui.setStatus(STATUS_KEY, "🔎
|
|
228
|
+
if (ctxHasUI(ctx)) ctx.ui.setStatus(STATUS_KEY, "🔎");
|
|
229
229
|
stopPolling();
|
|
230
230
|
registerEmptyProvider();
|
|
231
231
|
const r = await discoverAndRegister();
|
|
@@ -237,11 +237,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
237
237
|
function updateStatusFooter(ctx?: ExtensionContext) {
|
|
238
238
|
if (!ctxHasUI(ctx)) return;
|
|
239
239
|
if (shared.registeredCount > 0) {
|
|
240
|
-
|
|
241
|
-
const total = shared.lastScan?.serversTotal ?? 0;
|
|
242
|
-
ctx.ui.setStatus(STATUS_KEY, `🦙 ${shared.registeredCount} models · ${up}/${total} ✓`);
|
|
240
|
+
ctx.ui.setStatus(STATUS_KEY, `🦙(${shared.registeredCount})`);
|
|
243
241
|
} else if (shared.lastScan?.endpoints.some((e) => e.loading)) {
|
|
244
|
-
ctx.ui.setStatus(STATUS_KEY, "⏳
|
|
242
|
+
ctx.ui.setStatus(STATUS_KEY, "⏳");
|
|
245
243
|
} else if (shared.lastError) {
|
|
246
244
|
ctx.ui.setStatus(STATUS_KEY, "⚠️");
|
|
247
245
|
} else {
|
package/src/speed.ts
CHANGED
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
// prefill t/s = prompt tokens / (before_provider_request → first token)
|
|
7
7
|
// gen t/s = moving window over per-token arrival samples
|
|
8
8
|
//
|
|
9
|
+
// Footer format (kept ultra-compact so it coexists with other extensions on
|
|
10
|
+
// pi's single status line, which is truncated at the end):
|
|
11
|
+
//
|
|
12
|
+
// ⚡ {prefill} t/s 🔥 {gen} t/s
|
|
13
|
+
//
|
|
9
14
|
// The server-side /metrics polling (metrics.ts) only supplements this:
|
|
10
15
|
// when the client is idle but the server reports other clients processing,
|
|
11
|
-
// their rate is shown
|
|
12
|
-
// no --metrics endpoint.
|
|
16
|
+
// their rate is shown as `▶{n} ⚡ … t/s 🔥 … t/s`. The client measurement
|
|
17
|
+
// works even when the server has no --metrics endpoint.
|
|
13
18
|
|
|
14
19
|
import { debugLog, METRICS_STATUS_KEY } from "./core.ts";
|
|
15
20
|
import type { AssistantMessageEvent, ExtensionContext, ServerMetricsState, ThemeFg } from "./types.ts";
|
|
@@ -126,58 +131,58 @@ export function createSpeedTracker(deps: SpeedDeps) {
|
|
|
126
131
|
return tokens / (span / 1000);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
function buildLine(ctx: ExtensionContext | undefined, now: number): string
|
|
134
|
+
function buildLine(ctx: ExtensionContext | undefined, now: number): string {
|
|
130
135
|
const f = fg(ctx);
|
|
131
|
-
const parts: string[] = [
|
|
136
|
+
const parts: string[] = [];
|
|
132
137
|
|
|
133
138
|
switch (state) {
|
|
134
139
|
case "prefill":
|
|
135
|
-
parts.push(f("warning", "
|
|
140
|
+
parts.push(f("warning", "⚡…"));
|
|
136
141
|
break;
|
|
137
142
|
|
|
138
143
|
case "streaming": {
|
|
144
|
+
// Prefill rate of the previous call (only known once it ended).
|
|
145
|
+
if (lastPrefillTps !== undefined) {
|
|
146
|
+
parts.push(f("muted", `⚡ ${Math.round(lastPrefillTps)} t/s`));
|
|
147
|
+
}
|
|
139
148
|
const gen = genRateAt(now);
|
|
140
149
|
if (gen !== undefined) {
|
|
141
150
|
lastGenTps = gen;
|
|
142
151
|
parts.push(f(rateColor(gen, "gen"), `🔥 ${formatRate(gen)} t/s`));
|
|
143
152
|
}
|
|
144
|
-
if (lastPrefillTps !== undefined) {
|
|
145
|
-
parts.push(f("muted", `⚡ ${Math.round(lastPrefillTps)}`));
|
|
146
|
-
}
|
|
147
|
-
parts.push(f("muted", `${tokenCount} tok`));
|
|
148
153
|
break;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
case "done": {
|
|
152
|
-
if (lastGenTps !== undefined) {
|
|
153
|
-
parts.push(f("muted", `🔥 ${formatRate(lastGenTps)} t/s`));
|
|
154
|
-
}
|
|
155
157
|
if (lastPrefillTps !== undefined) {
|
|
156
|
-
parts.push(f("
|
|
158
|
+
parts.push(f(rateColor(lastPrefillTps, "prefill"), `⚡ ${Math.round(lastPrefillTps)} t/s`));
|
|
159
|
+
}
|
|
160
|
+
if (lastGenTps !== undefined) {
|
|
161
|
+
parts.push(f(rateColor(lastGenTps, "gen"), `🔥 ${formatRate(lastGenTps)} t/s`));
|
|
157
162
|
}
|
|
158
|
-
parts.push(f("muted", `${tokenCount} tok`));
|
|
159
163
|
break;
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
case "idle": {
|
|
163
167
|
// Server supplement: this endpoint is busy for *other* clients.
|
|
164
168
|
if (server && server.processing > 0) {
|
|
165
|
-
parts.push(f("success",
|
|
169
|
+
parts.push(f("success", `▶${server.processing}`));
|
|
166
170
|
if (server.promptTps !== undefined && server.promptTps > 0) {
|
|
167
|
-
parts.push(f(rateColor(server.promptTps, "prefill"), `⚡ ${Math.round(server.promptTps)}`));
|
|
171
|
+
parts.push(f(rateColor(server.promptTps, "prefill"), `⚡ ${Math.round(server.promptTps)} t/s`));
|
|
168
172
|
}
|
|
169
173
|
if (server.genTps !== undefined && server.genTps > 0) {
|
|
170
|
-
parts.push(f(rateColor(server.genTps, "gen"), `🔥 ${formatRate(server.genTps)}`));
|
|
174
|
+
parts.push(f(rateColor(server.genTps, "gen"), `🔥 ${formatRate(server.genTps)} t/s`));
|
|
171
175
|
}
|
|
172
|
-
parts.push(f("muted", "server"));
|
|
173
176
|
} else {
|
|
174
|
-
parts.push(f("muted", "⏸
|
|
177
|
+
parts.push(f("muted", "⏸"));
|
|
175
178
|
}
|
|
176
179
|
break;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
// A stream is live but no rate is computable yet (first ~300 ms of a call).
|
|
184
|
+
if (parts.length === 0) parts.push(f("muted", "🔥…"));
|
|
185
|
+
return parts.join(" ");
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
function render(ctx: ExtensionContext | undefined, now: number, force = false): void {
|
|
@@ -186,7 +191,7 @@ export function createSpeedTracker(deps: SpeedDeps) {
|
|
|
186
191
|
return;
|
|
187
192
|
}
|
|
188
193
|
if (!deps.hasUI(ctx) || !deps.isOurs(ctx)) return;
|
|
189
|
-
const line = buildLine(ctx, now)
|
|
194
|
+
const line = buildLine(ctx, now);
|
|
190
195
|
if (!force && now - lastRenderAt < RENDER_THROTTLE_MS) return;
|
|
191
196
|
lastRenderAt = now;
|
|
192
197
|
// No visual change → skip (avoids a full UI re-render on the footer).
|