Mike0021 commited on
Commit
8b954a7
·
1 Parent(s): 88068aa

Keep Ava's replies as text in history for longer memory

Browse files
Files changed (1) hide show
  1. workers/model-worker.js +32 -5
workers/model-worker.js CHANGED
@@ -26,10 +26,11 @@ import {
26
 
27
  const LOAD_ATTEMPTS = 3; // Chromium occasionally returns no adapter on a cold GPU process
28
  const LOAD_RETRY_MS = 1500;
29
- // Speech costs ~16 tokens/s each way and re-reading history after a trim takes
30
- // ~2.3 ms/token, so history stays well under the session's 8192 tokens.
 
31
  const SEARCH = new URL(location.href).searchParams;
32
- const HISTORY_BUDGET_TOKENS = Number(SEARCH.get("historyBudget")) || 3000;
33
 
34
  const PRE_SPEECH_SAMPLES = 1200; // keep 50 ms before her first sound
35
  /** @type {any} */ let model = null;
@@ -37,6 +38,7 @@ const PRE_SPEECH_SAMPLES = 1200; // keep 50 ms before her first sound
37
  let system = "";
38
  /** @type {any[]} */ let history = [];
39
  let idle = Promise.resolve();
 
40
  /** @type {Map<number, AbortController>} */
41
  const running = new Map();
42
  const simulateRtf = Number(SEARCH.get("simulateRtf")) || 0;
@@ -62,6 +64,23 @@ function compactHistory() {
62
  for (let i = 0; i < Math.floor(exchanges / 2); i++) dropOldestExchange();
63
  }
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  async function load({ model: modelId, system: sys }) {
66
  system = sys;
67
  let lastErr;
@@ -105,6 +124,7 @@ async function respond({ id, audio }) {
105
  const controller = new AbortController();
106
  running.set(id, controller);
107
  const { signal } = controller;
 
108
  const prior = idle;
109
  /** @type {() => void} */ let release = () => {};
110
  idle = new Promise((r) => (release = r));
@@ -150,7 +170,7 @@ async function respond({ id, audio }) {
150
  const result = await stream.result;
151
  usage = result.usage;
152
  reason = result.finish_reason;
153
- history.push(result.message);
154
  break;
155
  }
156
  } catch (err) {
@@ -164,7 +184,14 @@ async function respond({ id, audio }) {
164
  } finally {
165
  running.delete(id);
166
  post({ kind: "finished", id, reason, usage, generated, error });
167
- if (usage && usage.prompt_tokens + usage.completion_tokens > HISTORY_BUDGET_TOKENS) compactHistory();
 
 
 
 
 
 
 
168
  release();
169
  }
170
  }
 
26
 
27
  const LOAD_ATTEMPTS = 3; // Chromium occasionally returns no adapter on a cold GPU process
28
  const LOAD_RETRY_MS = 1500;
29
+ // Her replies stay in history as text only: her speech costs ~16 tokens/s and left
30
+ // no room for what was said, so a 44-turn chat now fits and she still recalls it.
31
+ // Past the budget half of it goes, well under the session's 8192 tokens.
32
  const SEARCH = new URL(location.href).searchParams;
33
+ const HISTORY_BUDGET_TOKENS = Number(SEARCH.get("historyBudget")) || 6000;
34
 
35
  const PRE_SPEECH_SAMPLES = 1200; // keep 50 ms before her first sound
36
  /** @type {any} */ let model = null;
 
38
  let system = "";
39
  /** @type {any[]} */ let history = [];
40
  let idle = Promise.resolve();
41
+ /** @type {AbortController | null} */ let rereading = null;
42
  /** @type {Map<number, AbortController>} */
43
  const running = new Map();
44
  const simulateRtf = Number(SEARCH.get("simulateRtf")) || 0;
 
64
  for (let i = 0; i < Math.floor(exchanges / 2); i++) dropOldestExchange();
65
  }
66
 
67
+ /** The reply as history keeps it: what she said, not the sound of it. */
68
+ function textOnly(message) {
69
+ if (!Array.isArray(message.content)) return message;
70
+ return { ...message, content: message.content.filter((c) => c.type === "text").map((c) => c.text).join("") };
71
+ }
72
+
73
+ /**
74
+ * Settle the cache while she's idle, re-reading a trimmed history and rewinding
75
+ * past her audio, so nobody waits for it on their next turn. Stops short of the last reply: the cache can only rewind to its end,
76
+ * and this prompt's end must still be a prefix of the next one.
77
+ */
78
+ async function reread(signal) {
79
+ const stream = session.generate(messages().slice(0, -1), { generation_mode: "text", max_new_tokens: 1, signal });
80
+ for await (const _ of stream);
81
+ await stream.result;
82
+ }
83
+
84
  async function load({ model: modelId, system: sys }) {
85
  system = sys;
86
  let lastErr;
 
124
  const controller = new AbortController();
125
  running.set(id, controller);
126
  const { signal } = controller;
127
+ rereading?.abort();
128
  const prior = idle;
129
  /** @type {() => void} */ let release = () => {};
130
  idle = new Promise((r) => (release = r));
 
170
  const result = await stream.result;
171
  usage = result.usage;
172
  reason = result.finish_reason;
173
+ history.push(textOnly(result.message));
174
  break;
175
  }
176
  } catch (err) {
 
184
  } finally {
185
  running.delete(id);
186
  post({ kind: "finished", id, reason, usage, generated, error });
187
+ if (usage && !signal.aborted) {
188
+ if (session.count_tokens(messages()).prompt_tokens > HISTORY_BUDGET_TOKENS) compactHistory();
189
+ rereading = new AbortController();
190
+ await reread(rereading.signal).catch((err) => {
191
+ if (err?.name !== "AbortError" && !rereading?.signal.aborted) console.warn("re-read failed:", err);
192
+ });
193
+ rereading = null;
194
+ }
195
  release();
196
  }
197
  }