suhaas-teja commited on
Commit
e4c9005
·
verified ·
1 Parent(s): 32c2707

Engine: streaming auto and chat

Browse files
Files changed (1) hide show
  1. dmm/engine.py +67 -1
dmm/engine.py CHANGED
@@ -154,9 +154,13 @@ class Engine:
154
  # Generation --------------------------------------------------------------------------
155
 
156
  @torch.no_grad()
157
- def _generate(self, ids: list[int], max_new_tokens: int, cache: StateCache | None = None):
 
 
158
  x = torch.tensor([ids], device=self.model.device)
159
  kw = {"past_key_values": cache.kv} if cache is not None else {}
 
 
160
  with lora.disabled(self.model):
161
  out = self.model.generate(
162
  x,
@@ -168,6 +172,68 @@ class Engine:
168
  )
169
  return out[0, len(ids) :].tolist()
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def chat(self, messages: list[dict], max_new_tokens: int = 512) -> list[int]:
172
  ids = self.tok.apply_chat_template(
173
  messages, add_generation_prompt=True, tokenize=True, return_dict=True
 
154
  # Generation --------------------------------------------------------------------------
155
 
156
  @torch.no_grad()
157
+ def _generate(
158
+ self, ids: list[int], max_new_tokens: int, cache: StateCache | None = None, streamer=None
159
+ ):
160
  x = torch.tensor([ids], device=self.model.device)
161
  kw = {"past_key_values": cache.kv} if cache is not None else {}
162
+ if streamer is not None:
163
+ kw["streamer"] = streamer
164
  with lora.disabled(self.model):
165
  out = self.model.generate(
166
  x,
 
172
  )
173
  return out[0, len(ids) :].tolist()
174
 
175
+ def _stream(self, ids: list[int], max_new_tokens: int, cache: StateCache | None = None):
176
+ """Generate as _generate does, yielding {"event": "text"} pieces as they decode.
177
+
178
+ Returns the generated token ids (the generator's return value). Generation runs in a
179
+ thread feeding a streamer; the thread is always joined, even if the caller stops early,
180
+ so the model and the state cache are never touched by two requests at once.
181
+ """
182
+ from transformers import TextIteratorStreamer
183
+
184
+ streamer = TextIteratorStreamer(self.tok, skip_prompt=True, skip_special_tokens=True)
185
+ result: dict = {}
186
+
187
+ def run():
188
+ try:
189
+ result["tokens"] = self._generate(ids, max_new_tokens, cache, streamer)
190
+ except BaseException as e: # surface in the caller's thread
191
+ result["error"] = e
192
+ streamer.end()
193
+
194
+ t = threading.Thread(target=run, daemon=True)
195
+ t.start()
196
+ try:
197
+ for piece in streamer:
198
+ if piece:
199
+ yield {"event": "text", "text": piece}
200
+ finally:
201
+ t.join()
202
+ if "error" in result:
203
+ raise result["error"]
204
+ return result["tokens"]
205
+
206
+ def chat_stream(self, messages: list[dict], max_new_tokens: int = 512):
207
+ """chat(), streamed: {"event": "text"} pieces, then {"event": "done", "tokens": [...]}."""
208
+ ids = self.tok.apply_chat_template(
209
+ messages, add_generation_prompt=True, tokenize=True, return_dict=True
210
+ )["input_ids"]
211
+ with self.lock:
212
+ tokens = yield from self._stream(ids, max_new_tokens)
213
+ yield {"event": "done", "tokens": tokens}
214
+
215
+ def auto_stream(self, state: str, name: str, question: dict, max_new_tokens: int = 512):
216
+ """auto(), streamed. Yields {"event": "decision", ...} as soon as the quick decision and
217
+ the router's call are known, then {"event": "text"} reasoning pieces when escalated, then
218
+ {"event": "done", ...} carrying exactly what auto() returns."""
219
+ item = to_item(state, name, question)
220
+ with self.lock:
221
+ cache = StateCache(self.model, self.tpl.state_ids(item))
222
+ r = self._decide_on(cache, item)
223
+ out = {
224
+ "decision": self._answer(item, r),
225
+ "escalated": r["escalate"],
226
+ "state_prefills": 1,
227
+ }
228
+ yield {"event": "decision", **out}
229
+ if r["escalate"]:
230
+ ids = cache.ids + self.tpl.encode(chat_text(item))
231
+ tokens = yield from self._stream(ids, max_new_tokens, cache)
232
+ out["generation_tokens"] = tokens
233
+ out["generation"] = self.tok.decode(tokens, skip_special_tokens=True)
234
+ cache.kv.crop(len(cache))
235
+ yield {"event": "done", **out}
236
+
237
  def chat(self, messages: list[dict], max_new_tokens: int = 512) -> list[int]:
238
  ids = self.tok.apply_chat_template(
239
  messages, add_generation_prompt=True, tokenize=True, return_dict=True