akhaliq HF Staff commited on
Commit
bc1db28
Β·
1 Parent(s): 1c7c9b7

Capture and stream model stdout output in real-time

Browse files
Files changed (1) hide show
  1. app.py +69 -36
app.py CHANGED
@@ -1,6 +1,9 @@
1
  import subprocess, sys, os, tempfile
2
  from threading import Thread
3
  from typing import Iterator
 
 
 
4
 
5
  # ──────────────────────────────────────────────────────────────────────────────
6
  # Runtime install of exact model-required versions.
@@ -92,6 +95,30 @@ def _collect_output(out_dir: str) -> str:
92
  #
93
  # ZeroGPU: duration=60 β†’ highest queue priority; one page per call.
94
  #
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  @app.api(stream_every=0.1)
96
  @spaces.GPU(duration=60)
97
  def run_ocr(
@@ -129,50 +156,56 @@ def run_ocr(
129
  save_results=True,
130
  )
131
 
132
- # ── Attempt real token streaming via TextIteratorStreamer ─────────────────
133
- streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=False)
134
- errors = []
135
 
136
- def _infer_with_streamer():
137
  try:
138
- model.infer(tokenizer, **_infer_kwargs, streamer=streamer)
139
- except TypeError:
140
- # model.infer() doesn't accept `streamer` kwarg.
141
- # Re-run WITHOUT it so output files are actually written,
142
- # then signal the streamer loop to end.
143
- try:
144
- model.infer(tokenizer, **_infer_kwargs)
145
- except Exception as e:
146
- errors.append(str(e))
147
- finally:
148
- streamer.end()
149
  except Exception as e:
150
  errors.append(str(e))
151
- streamer.end()
152
 
153
- thread = Thread(target=_infer_with_streamer, daemon=True)
154
- thread.start()
 
 
 
155
 
156
  accumulated = ""
157
- for token in streamer: # blocks until next token or end()
158
- accumulated += token
159
- yield {"text": accumulated, "done": False}
160
-
161
- thread.join()
162
-
163
- # ── Fallback: streamer not used β†’ read file, stream word-by-word ──────────
164
-
165
- if not accumulated:
166
- full_text = _collect_output(out_dir)
167
- words = full_text.split()
168
- acc = ""
169
- for i, word in enumerate(words):
170
- acc += ("" if i == 0 else " ") + word
171
- if i % 5 == 0: # emit every 5 words
172
- yield {"text": acc, "done": False}
173
- yield {"text": full_text, "done": True}
 
 
 
 
174
  else:
175
- yield {"text": accumulated, "done": True}
 
 
 
 
 
 
 
 
 
 
 
176
 
177
 
178
  # ── PDF explode β€” CPU only, no GPU ───────────────────────────────────────────
 
1
  import subprocess, sys, os, tempfile
2
  from threading import Thread
3
  from typing import Iterator
4
+ import queue
5
+ import threading
6
+
7
 
8
  # ──────────────────────────────────────────────────────────────────────────────
9
  # Runtime install of exact model-required versions.
 
95
  #
96
  # ZeroGPU: duration=60 β†’ highest queue priority; one page per call.
97
  #
98
+ class ThreadTargetedStdout:
99
+ def __init__(self, target_thread, q, original_stdout):
100
+ self.target_thread = target_thread
101
+ self.q = q
102
+ self.original_stdout = original_stdout
103
+
104
+ def write(self, data):
105
+ self.original_stdout.write(data)
106
+ self.original_stdout.flush()
107
+ if threading.current_thread() == self.target_thread:
108
+ if data:
109
+ lower_data = data.lower()
110
+ if "tps:" in lower_data or "tokens/s" in lower_data:
111
+ return len(data)
112
+ self.q.put(data)
113
+ return len(data)
114
+
115
+ def flush(self):
116
+ self.original_stdout.flush()
117
+
118
+ def __getattr__(self, name):
119
+ return getattr(self.original_stdout, name)
120
+
121
+
122
  @app.api(stream_every=0.1)
123
  @spaces.GPU(duration=60)
124
  def run_ocr(
 
156
  save_results=True,
157
  )
158
 
159
+ q = queue.Queue()
160
+ errors = []
 
161
 
162
+ def _infer_thread():
163
  try:
164
+ model.infer(tokenizer, **_infer_kwargs)
 
 
 
 
 
 
 
 
 
 
165
  except Exception as e:
166
  errors.append(str(e))
 
167
 
168
+ thread = Thread(target=_infer_thread, daemon=True)
169
+
170
+ original_stdout = sys.stdout
171
+ targeted_stdout = ThreadTargetedStdout(thread, q, original_stdout)
172
+ sys.stdout = targeted_stdout
173
 
174
  accumulated = ""
175
+ try:
176
+ thread.start()
177
+ while thread.is_alive() or not q.empty():
178
+ try:
179
+ chunk = q.get(timeout=0.02)
180
+ accumulated += chunk
181
+ yield {"text": accumulated, "done": False}
182
+ except queue.Empty:
183
+ continue
184
+ finally:
185
+ sys.stdout = original_stdout
186
+ thread.join()
187
+
188
+ # ── Fallback/Final: read file to get clean text ───────────────────────────
189
+ full_text = _collect_output(out_dir)
190
+
191
+ if accumulated:
192
+ if full_text:
193
+ yield {"text": full_text, "done": True}
194
+ else:
195
+ yield {"text": accumulated, "done": True}
196
  else:
197
+ if full_text:
198
+ words = full_text.split()
199
+ acc = ""
200
+ for i, word in enumerate(words):
201
+ acc += ("" if i == 0 else " ") + word
202
+ if i % 5 == 0:
203
+ yield {"text": acc, "done": False}
204
+ yield {"text": full_text, "done": True}
205
+ else:
206
+ if errors:
207
+ raise RuntimeError(f"Inference failed: {', '.join(errors)}")
208
+ yield {"text": "", "done": True}
209
 
210
 
211
  # ── PDF explode β€” CPU only, no GPU ───────────────────────────────────────────