Andhs commited on
Commit
2424bab
·
verified ·
1 Parent(s): b8d11d8

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +338 -40
app.py CHANGED
@@ -10,6 +10,337 @@ 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")
15
  IS_DIFFUSION = "diffusion" in MODEL_NAME.lower()
@@ -79,11 +410,13 @@ def clone_past_key_values(pkv):
79
  """Clone KV-cache. Fast path for tuples and Cache objects; falls back to deepcopy."""
80
  if pkv is None:
81
  return None
 
82
  if isinstance(pkv, tuple):
83
  return tuple(
84
  (k.clone() if k is not None else None, v.clone() if v is not None else None)
85
  for k, v in pkv
86
  )
 
87
  if hasattr(pkv, 'key_cache') and hasattr(pkv, 'value_cache'):
88
  try:
89
  new_cache = pkv.__class__()
@@ -95,6 +428,7 @@ def clone_past_key_values(pkv):
95
  return new_cache
96
  except Exception:
97
  pass
 
98
  return copy.deepcopy(pkv)
99
 
100
 
@@ -332,6 +666,7 @@ def load_model():
332
  torch_dtype=torch.bfloat16,
333
  trust_remote_code=False
334
  ).to(device).eval()
 
335
  if not IS_DIFFUSION:
336
  try:
337
  model = torch.compile(model, mode="reduce-overhead", fullgraph=False)
@@ -344,7 +679,7 @@ def load_model():
344
  MODEL_NAME,
345
  trust_remote_code=IS_DIFFUSION
346
  )
347
- print("Model loaded into memory workspace.")
348
 
349
 
350
  @app.route('/health', methods=['GET'])
@@ -367,6 +702,7 @@ def generate_text():
367
  {"role": "system", "content": system_prompt},
368
  {"role": "user", "content": prompt}
369
  ]
 
370
  encoded = tokenizer.apply_chat_template(
371
  messages,
372
  add_generation_prompt=True,
@@ -511,7 +847,7 @@ def generate_text_sse():
511
 
512
  accumulated = []
513
  for text in streamer:
514
- if not text:
515
  continue
516
  accumulated.append(text)
517
  current = "".join(accumulated)
@@ -526,48 +862,10 @@ def generate_text_sse():
526
  )
527
 
528
 
529
- HTML_UI = """<!DOCTYPE html>
530
- <html lang="en">
531
- <head>
532
- <meta charset="UTF-8">
533
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
534
- <title>LLM API Tester</title>
535
- <style>
536
- *{box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:900px;margin:0 auto;padding:20px;background:#f5f5f5;color:#333}h1{margin-top:0;font-size:1.5rem}.card{background:#fff;border-radius:8px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.1)}label{display:block;font-weight:600;margin-bottom:6px;font-size:.9rem}textarea,input,select{width:100%;padding:10px;border:1px solid #ddd;border-radius:6px;font-size:.95rem;font-family:inherit}textarea{resize:vertical;min-height:80px}.row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:12px}.field{margin-bottom:12px}button{background:#2563eb;color:#fff;border:none;padding:10px 20px;border-radius:6px;font-size:1rem;cursor:pointer;font-weight:600}button:hover{background:#1d4ed8}button:disabled{background:#93c5fd;cursor:not-allowed}.output{background:#1e1e1e;color:#e4e4e4;padding:16px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:.9rem;white-space:pre-wrap;word-break:break-word;min-height:120px;max-height:500px;overflow-y:auto}.output:empty::before{content:"Response will appear here...";color:#666}.status{font-size:.85rem;color:#666;margin-top:8px}.error{color:#dc2626}.success{color:#16a34a}.route-badge{display:inline-block;background:#e5e7eb;padding:2px 8px;border-radius:4px;font-size:.8rem;font-weight:600;margin-bottom:8px}
537
- </style>
538
- </head>
539
- <body>
540
- <h1>LLM API Tester</h1>
541
- <div class="card">
542
- <div class="route-badge" id="routeBadge">/generate</div>
543
- <div class="field"><label for="route">Route</label><select id="route"><option value="/generate">/generate (sync JSON)</option><option value="/generate_stream">/generate_stream (sync JSON + intermediates)</option><option value="/generate_sse">/generate_sse (streaming SSE)</option></select></div>
544
- <div class="field"><label for="prompt">Prompt</label><textarea id="prompt" placeholder="Enter your prompt here...">Write a short poem about stars</textarea></div>
545
- <div class="row"><div class="field"><label for="max_new_tokens">max_new_tokens</label><input type="number" id="max_new_tokens" value="150" min="1" max="2048"></div><div class="field"><label for="temperature">temperature</label><input type="number" id="temperature" value="0.0" min="0" max="2" step="0.1"></div><div class="field"><label for="steps">steps (diffusion)</label><input type="number" id="steps" value="256" min="1"></div><div class="field"><label for="block_size">block_size</label><input type="number" id="block_size" value="32" min="1"></div></div>
546
- <div class="row"><div class="field"><label for="cfg_scale">cfg_scale</label><input type="number" id="cfg_scale" value="0.0" min="0" step="0.1"></div><div class="field"><label for="remasking">remasking</label><select id="remasking"><option value="low_confidence">low_confidence</option><option value="random">random</option></select></div><div class="field"><label for="capture_interval">capture_interval</label><input type="number" id="capture_interval" value="10" min="1"></div></div>
547
- <button id="sendBtn">Send Request</button>
548
- <div class="status" id="status"></div>
549
- </div>
550
- <div class="card"><label>Response</label><div class="output" id="output"></div></div>
551
- <script>
552
- const $=id=>document.getElementById(id);
553
- const routeSelect=$('route');const routeBadge=$('routeBadge');const sendBtn=$('sendBtn');const output=$('output');const status=$('status');
554
- routeSelect.addEventListener('change',()=>{routeBadge.textContent=routeSelect.value;});
555
- function setStatus(msg,isError=false){status.textContent=msg;status.className='status '+(isError?'error':'success');}
556
- function appendOutput(text,clear=false){if(clear)output.textContent='';output.textContent+=text;output.scrollTop=output.scrollHeight;}
557
- function getPayload(){return{prompt:$('prompt').value,max_new_tokens:parseInt($('max_new_tokens').value),temperature:parseFloat($('temperature').value),steps:parseInt($('steps').value),block_size:parseInt($('block_size').value),cfg_scale:parseFloat($('cfg_scale').value),remasking:$('remasking').value,capture_interval:parseInt($('capture_interval').value)};}
558
- async function handleGenerate(){const payload=getPayload();const t0=performance.now();const res=await fetch('/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});const data=await res.json();const ms=Math.round(performance.now()-t0);if(res.ok){appendOutput(`[${ms}ms]\n${data.generated_text||JSON.stringify(data,null,2)}\n\n`,true);setStatus(`OK — ${ms}ms`);}else{appendOutput(`Error ${res.status}:\n${JSON.stringify(data,null,2)}\n\n`,true);setStatus(`HTTP ${res.status}`,true);}}
559
- async function handleGenerateStream(){const payload=getPayload();const t0=performance.now();const res=await fetch('/generate_stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});const data=await res.json();const ms=Math.round(performance.now()-t0);if(res.ok){let text=`[${ms}ms]\nGenerated text:\n${data.generated_text}\n\n`;if(data.intermediate_states&&data.intermediate_states.length){text+=`Intermediate states (${data.intermediate_states.length}):\n`;data.intermediate_states.forEach((s,i)=>{text+=` Step ${s.step}: ${s.text.substring(0,120).replace(/\n/g,' ')}...\n`;});}appendOutput(text+'\n',true);setStatus(`OK — ${ms}ms, ${data.intermediate_states?.length||0} intermediates`);}else{appendOutput(`Error ${res.status}:\n${JSON.stringify(data,null,2)}\n\n`,true);setStatus(`HTTP ${res.status}`,true);}}
560
- async function handleGenerateSSE(){const payload=getPayload();const t0=performance.now();appendOutput('',true);setStatus('Connecting SSE...');const res=await fetch('/generate_sse',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});if(!res.ok){const data=await res.json().catch(()=>({}));appendOutput(`Error ${res.status}:\n${JSON.stringify(data,null,2)}`,true);setStatus(`HTTP ${res.status}`,true);return;}const reader=res.body.getReader();const decoder=new TextDecoder();let buffer='';let finalText='';let eventCount=0;while(true){const {done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:true});const lines=buffer.split('\n');buffer=lines.pop();for(const line of lines){if(!line.startsWith('data: '))continue;const jsonStr=line.slice(6).trim();if(!jsonStr)continue;try{const event=JSON.parse(jsonStr);eventCount++;if(event.type==='final'){finalText=event.text;const ms=Math.round(performance.now()-t0);appendOutput(`[${ms}ms | ${eventCount} events]\n${finalText}\n`,true);setStatus(`Done — ${ms}ms, ${eventCount} events, ${event.total_steps||'?'} steps`);}else if(event.type==='intermediate'||event.type==='token'){appendOutput(`${event.text}`,true);setStatus(`Streaming... (${eventCount} events)`);}}catch(e){}}}if(!finalText&&eventCount===0){setStatus('Stream ended with no events',true);}}
561
- sendBtn.addEventListener('click',async()=>{sendBtn.disabled=true;setStatus('Sending...');try{const route=routeSelect.value;if(route==='/generate')await handleGenerate();else if(route==='/generate_stream')await handleGenerateStream();else if(route==='/generate_sse')await handleGenerateSSE();}catch(err){appendOutput(`Network/JS Error:\n${err.message}\n\n`,true);setStatus(err.message,true);}finally{sendBtn.disabled=false;}});
562
- </script>
563
- </body>
564
- </html>"""
565
-
566
  @app.route('/')
567
  def index():
568
  return Response(HTML_UI, mimetype='text/html')
569
 
570
-
571
  if __name__ == '__main__':
572
  load_model()
573
  app.run(host='0.0.0.0', port=int(os.getenv('PORT', 7860)))
 
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")
346
  IS_DIFFUSION = "diffusion" in MODEL_NAME.lower()
 
410
  """Clone KV-cache. Fast path for tuples and Cache objects; falls back to deepcopy."""
411
  if pkv is None:
412
  return None
413
+ # Fast path: legacy tuple format
414
  if isinstance(pkv, tuple):
415
  return tuple(
416
  (k.clone() if k is not None else None, v.clone() if v is not None else None)
417
  for k, v in pkv
418
  )
419
+ # Fast path: transformers Cache objects (DynamicCache, etc.)
420
  if hasattr(pkv, 'key_cache') and hasattr(pkv, 'value_cache'):
421
  try:
422
  new_cache = pkv.__class__()
 
428
  return new_cache
429
  except Exception:
430
  pass
431
+ # Fallback
432
  return copy.deepcopy(pkv)
433
 
434
 
 
666
  torch_dtype=torch.bfloat16,
667
  trust_remote_code=False
668
  ).to(device).eval()
669
+ # Compile model for faster inference — ONLY for standard causal models
670
  if not IS_DIFFUSION:
671
  try:
672
  model = torch.compile(model, mode="reduce-overhead", fullgraph=False)
 
679
  MODEL_NAME,
680
  trust_remote_code=IS_DIFFUSION
681
  )
682
+ print("Model compilation completed and loaded into memory workspace.")
683
 
684
 
685
  @app.route('/health', methods=['GET'])
 
702
  {"role": "system", "content": system_prompt},
703
  {"role": "user", "content": prompt}
704
  ]
705
+ # enable_thinking=False for ALL routes to prevent Qwen3 from leaking internal monologue
706
  encoded = tokenizer.apply_chat_template(
707
  messages,
708
  add_generation_prompt=True,
 
847
 
848
  accumulated = []
849
  for text in streamer:
850
+ if not text: # skip empty chunks
851
  continue
852
  accumulated.append(text)
853
  current = "".join(accumulated)
 
862
  )
863
 
864
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
865
  @app.route('/')
866
  def index():
867
  return Response(HTML_UI, mimetype='text/html')
868
 
 
869
  if __name__ == '__main__':
870
  load_model()
871
  app.run(host='0.0.0.0', port=int(os.getenv('PORT', 7860)))