Andhs commited on
Commit
df717ea
·
verified ·
1 Parent(s): da77057

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +332 -6
app.py CHANGED
@@ -9,7 +9,337 @@ import torch.nn.functional as F
9
  from flask import Flask, request, jsonify, Response
10
  from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModelForCausalLM, TextIteratorStreamer
11
  from threading import Thread
12
- from flask import redirect, send_from_directory
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # 1. Environment Parsing & Architecture Strategy Mapping
15
  MODEL_NAME = os.getenv("MODEL_NAME", "dllm-hub/Qwen3-0.6B-diffusion-bd3lm-v0.1")
@@ -534,11 +864,7 @@ def generate_text_sse():
534
 
535
  @app.route('/')
536
  def index():
537
- return redirect('/ui')
538
-
539
- @app.route('/ui')
540
- def ui():
541
- return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'index.html')
542
 
543
  if __name__ == '__main__':
544
  load_model()
 
9
  from flask import Flask, request, jsonify, Response
10
  from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModelForCausalLM, TextIteratorStreamer
11
  from threading import Thread
12
+
13
+ HTML_UI = """
14
+ <!DOCTYPE html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ <title>LLM API Tester</title>
20
+ <style>
21
+ * { box-sizing: border-box; }
22
+ body {
23
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
24
+ max-width: 900px;
25
+ margin: 0 auto;
26
+ padding: 20px;
27
+ background: #f5f5f5;
28
+ color: #333;
29
+ }
30
+ h1 { margin-top: 0; font-size: 1.5rem; }
31
+ .card {
32
+ background: #fff;
33
+ border-radius: 8px;
34
+ padding: 20px;
35
+ margin-bottom: 16px;
36
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
37
+ }
38
+ label {
39
+ display: block;
40
+ font-weight: 600;
41
+ margin-bottom: 6px;
42
+ font-size: 0.9rem;
43
+ }
44
+ textarea, input, select {
45
+ width: 100%;
46
+ padding: 10px;
47
+ border: 1px solid #ddd;
48
+ border-radius: 6px;
49
+ font-size: 0.95rem;
50
+ font-family: inherit;
51
+ }
52
+ textarea { resize: vertical; min-height: 80px; }
53
+ .row {
54
+ display: grid;
55
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
56
+ gap: 12px;
57
+ margin-bottom: 12px;
58
+ }
59
+ .field { margin-bottom: 12px; }
60
+ .field.inline {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ }
65
+ .field.inline label { margin: 0; }
66
+ .field.inline input, .field.inline select {
67
+ width: auto;
68
+ flex: 1;
69
+ }
70
+ button {
71
+ background: #2563eb;
72
+ color: #fff;
73
+ border: none;
74
+ padding: 10px 20px;
75
+ border-radius: 6px;
76
+ font-size: 1rem;
77
+ cursor: pointer;
78
+ font-weight: 600;
79
+ }
80
+ button:hover { background: #1d4ed8; }
81
+ button:disabled { background: #93c5fd; cursor: not-allowed; }
82
+ .output {
83
+ background: #1e1e1e;
84
+ color: #e4e4e4;
85
+ padding: 16px;
86
+ border-radius: 6px;
87
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
88
+ font-size: 0.9rem;
89
+ white-space: pre-wrap;
90
+ word-break: break-word;
91
+ min-height: 120px;
92
+ max-height: 500px;
93
+ overflow-y: auto;
94
+ }
95
+ .output:empty::before {
96
+ content: "Response will appear here...";
97
+ color: #666;
98
+ }
99
+ .status {
100
+ font-size: 0.85rem;
101
+ color: #666;
102
+ margin-top: 8px;
103
+ }
104
+ .error { color: #dc2626; }
105
+ .success { color: #16a34a; }
106
+ .route-badge {
107
+ display: inline-block;
108
+ background: #e5e7eb;
109
+ padding: 2px 8px;
110
+ border-radius: 4px;
111
+ font-size: 0.8rem;
112
+ font-weight: 600;
113
+ margin-bottom: 8px;
114
+ }
115
+ </style>
116
+ </head>
117
+ <body>
118
+ <h1>LLM API Tester</h1>
119
+
120
+ <div class="card">
121
+ <div class="route-badge" id="routeBadge">/generate</div>
122
+ <div class="field">
123
+ <label for="route">Route</label>
124
+ <select id="route">
125
+ <option value="/generate">/generate (sync JSON)</option>
126
+ <option value="/generate_stream">/generate_stream (sync JSON + intermediates)</option>
127
+ <option value="/generate_sse">/generate_sse (streaming SSE)</option>
128
+ </select>
129
+ </div>
130
+
131
+ <div class="field">
132
+ <label for="prompt">Prompt</label>
133
+ <textarea id="prompt" placeholder="Enter your prompt here...">Write a short poem about stars</textarea>
134
+ </div>
135
+
136
+ <div class="row">
137
+ <div class="field">
138
+ <label for="max_new_tokens">max_new_tokens</label>
139
+ <input type="number" id="max_new_tokens" value="150" min="1" max="2048">
140
+ </div>
141
+ <div class="field">
142
+ <label for="temperature">temperature</label>
143
+ <input type="number" id="temperature" value="0.0" min="0" max="2" step="0.1">
144
+ </div>
145
+ <div class="field">
146
+ <label for="steps">steps (diffusion)</label>
147
+ <input type="number" id="steps" value="256" min="1">
148
+ </div>
149
+ <div class="field">
150
+ <label for="block_size">block_size</label>
151
+ <input type="number" id="block_size" value="32" min="1">
152
+ </div>
153
+ </div>
154
+
155
+ <div class="row">
156
+ <div class="field">
157
+ <label for="cfg_scale">cfg_scale</label>
158
+ <input type="number" id="cfg_scale" value="0.0" min="0" step="0.1">
159
+ </div>
160
+ <div class="field">
161
+ <label for="remasking">remasking</label>
162
+ <select id="remasking">
163
+ <option value="low_confidence">low_confidence</option>
164
+ <option value="random">random</option>
165
+ </select>
166
+ </div>
167
+ <div class="field">
168
+ <label for="capture_interval">capture_interval</label>
169
+ <input type="number" id="capture_interval" value="10" min="1">
170
+ </div>
171
+ </div>
172
+
173
+ <button id="sendBtn">Send Request</button>
174
+ <div class="status" id="status"></div>
175
+ </div>
176
+
177
+ <div class="card">
178
+ <label>Response</label>
179
+ <div class="output" id="output"></div>
180
+ </div>
181
+
182
+ <script>
183
+ const $ = id => document.getElementById(id);
184
+ const routeSelect = $('route');
185
+ const routeBadge = $('routeBadge');
186
+ const sendBtn = $('sendBtn');
187
+ const output = $('output');
188
+ const status = $('status');
189
+
190
+ routeSelect.addEventListener('change', () => {
191
+ routeBadge.textContent = routeSelect.value;
192
+ });
193
+
194
+ function setStatus(msg, isError = false) {
195
+ status.textContent = msg;
196
+ status.className = 'status ' + (isError ? 'error' : 'success');
197
+ }
198
+
199
+ function appendOutput(text, clear = false) {
200
+ if (clear) output.textContent = '';
201
+ output.textContent += text;
202
+ output.scrollTop = output.scrollHeight;
203
+ }
204
+
205
+ function getPayload() {
206
+ return {
207
+ prompt: $('prompt').value,
208
+ max_new_tokens: parseInt($('max_new_tokens').value),
209
+ temperature: parseFloat($('temperature').value),
210
+ steps: parseInt($('steps').value),
211
+ block_size: parseInt($('block_size').value),
212
+ cfg_scale: parseFloat($('cfg_scale').value),
213
+ remasking: $('remasking').value,
214
+ capture_interval: parseInt($('capture_interval').value)
215
+ };
216
+ }
217
+
218
+ async function handleGenerate() {
219
+ const payload = getPayload();
220
+ // Remove diffusion-only fields for non-diffusion if needed, but server ignores extras
221
+ const t0 = performance.now();
222
+ const res = await fetch('/generate', {
223
+ method: 'POST',
224
+ headers: { 'Content-Type': 'application/json' },
225
+ body: JSON.stringify(payload)
226
+ });
227
+ const data = await res.json();
228
+ const ms = Math.round(performance.now() - t0);
229
+ if (res.ok) {
230
+ appendOutput(`[${ms}ms]\n${data.generated_text || JSON.stringify(data, null, 2)}\n\n`, true);
231
+ setStatus(`OK — ${ms}ms`);
232
+ } else {
233
+ appendOutput(`Error ${res.status}:\n${JSON.stringify(data, null, 2)}\n\n`, true);
234
+ setStatus(`HTTP ${res.status}`, true);
235
+ }
236
+ }
237
+
238
+ async function handleGenerateStream() {
239
+ const payload = getPayload();
240
+ const t0 = performance.now();
241
+ const res = await fetch('/generate_stream', {
242
+ method: 'POST',
243
+ headers: { 'Content-Type': 'application/json' },
244
+ body: JSON.stringify(payload)
245
+ });
246
+ const data = await res.json();
247
+ const ms = Math.round(performance.now() - t0);
248
+ if (res.ok) {
249
+ let text = `[${ms}ms]\nGenerated text:\n${data.generated_text}\n\n`;
250
+ if (data.intermediate_states && data.intermediate_states.length) {
251
+ text += `Intermediate states (${data.intermediate_states.length}):\n`;
252
+ data.intermediate_states.forEach((s, i) => {
253
+ text += ` Step ${s.step}: ${s.text.substring(0, 120).replace(/\n/g, ' ')}...\n`;
254
+ });
255
+ }
256
+ appendOutput(text + '\n', true);
257
+ setStatus(`OK — ${ms}ms, ${data.intermediate_states?.length || 0} intermediates`);
258
+ } else {
259
+ appendOutput(`Error ${res.status}:\n${JSON.stringify(data, null, 2)}\n\n`, true);
260
+ setStatus(`HTTP ${res.status}`, true);
261
+ }
262
+ }
263
+
264
+ async function handleGenerateSSE() {
265
+ const payload = getPayload();
266
+ const t0 = performance.now();
267
+ appendOutput('', true);
268
+ setStatus('Connecting SSE...');
269
+
270
+ const res = await fetch('/generate_sse', {
271
+ method: 'POST',
272
+ headers: { 'Content-Type': 'application/json' },
273
+ body: JSON.stringify(payload)
274
+ });
275
+
276
+ if (!res.ok) {
277
+ const data = await res.json().catch(() => ({}));
278
+ appendOutput(`Error ${res.status}:\n${JSON.stringify(data, null, 2)}`, true);
279
+ setStatus(`HTTP ${res.status}`, true);
280
+ return;
281
+ }
282
+
283
+ const reader = res.body.getReader();
284
+ const decoder = new TextDecoder();
285
+ let buffer = '';
286
+ let finalText = '';
287
+ let eventCount = 0;
288
+
289
+ while (true) {
290
+ const { done, value } = await reader.read();
291
+ if (done) break;
292
+ buffer += decoder.decode(value, { stream: true });
293
+ const lines = buffer.split('\n');
294
+ buffer = lines.pop(); // keep incomplete line in buffer
295
+
296
+ for (const line of lines) {
297
+ if (!line.startsWith('data: ')) continue;
298
+ const jsonStr = line.slice(6).trim();
299
+ if (!jsonStr) continue;
300
+ try {
301
+ const event = JSON.parse(jsonStr);
302
+ eventCount++;
303
+ if (event.type === 'final') {
304
+ finalText = event.text;
305
+ const ms = Math.round(performance.now() - t0);
306
+ appendOutput(`[${ms}ms | ${eventCount} events]\n${finalText}\n`, true);
307
+ setStatus(`Done — ${ms}ms, ${eventCount} events, ${event.total_steps || '?'} steps`);
308
+ } else if (event.type === 'intermediate' || event.type === 'token') {
309
+ // Live update: overwrite with latest accumulated text
310
+ appendOutput(`${event.text}`, true);
311
+ setStatus(`Streaming... (${eventCount} events)`);
312
+ }
313
+ } catch (e) {
314
+ // ignore malformed lines
315
+ }
316
+ }
317
+ }
318
+
319
+ if (!finalText && eventCount === 0) {
320
+ setStatus('Stream ended with no events', true);
321
+ }
322
+ }
323
+
324
+ sendBtn.addEventListener('click', async () => {
325
+ sendBtn.disabled = true;
326
+ setStatus('Sending...');
327
+ try {
328
+ const route = routeSelect.value;
329
+ if (route === '/generate') await handleGenerate();
330
+ else if (route === '/generate_stream') await handleGenerateStream();
331
+ else if (route === '/generate_sse') await handleGenerateSSE();
332
+ } catch (err) {
333
+ appendOutput(`Network/JS Error:\n${err.message}\n\n`, true);
334
+ setStatus(err.message, true);
335
+ } finally {
336
+ sendBtn.disabled = false;
337
+ }
338
+ });
339
+ </script>
340
+ </body>
341
+ </html>
342
+ """
343
 
344
  # 1. Environment Parsing & Architecture Strategy Mapping
345
  MODEL_NAME = os.getenv("MODEL_NAME", "dllm-hub/Qwen3-0.6B-diffusion-bd3lm-v0.1")
 
864
 
865
  @app.route('/')
866
  def index():
867
+ return Response(HTML_UI, mimetype='text/html')
 
 
 
 
868
 
869
  if __name__ == '__main__':
870
  load_model()