Andhs commited on
Commit
c999755
·
verified ·
1 Parent(s): 6282d7b

Upload 2 files

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