cazyundee commited on
Commit
e8b6e3f
·
verified ·
1 Parent(s): 9a9fa33

Add CPU Qwen3.8 inference job

Browse files
Files changed (1) hide show
  1. app.py +102 -3
app.py CHANGED
@@ -107,6 +107,19 @@ API_RESOURCES = {
107
  },
108
  "output": "WAV audio file",
109
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  "tinyllama_inference": {
111
  "name": "TinyLlama text generation",
112
  "description": "Generate text with TinyLlama 1.1B using the Space CPU; no GPU allocation.",
@@ -488,7 +501,7 @@ def _run_cpu_diagnose(model_filter):
488
  return result
489
 
490
 
491
- def _run_benchmark_job(job_id, kind, model_filter=None, force=False):
492
  try:
493
  if kind == "smoke":
494
  with _BENCH_LOCK:
@@ -502,6 +515,16 @@ def _run_benchmark_job(job_id, kind, model_filter=None, force=False):
502
  with _BENCH_LOCK:
503
  result = _run_cpu_process_probe(model_filter)
504
  payload = {"status": "complete", "result": result}
 
 
 
 
 
 
 
 
 
 
505
  else:
506
  if force:
507
  global _BENCH_RESULTS
@@ -522,7 +545,7 @@ def _run_benchmark_job(job_id, kind, model_filter=None, force=False):
522
  _BENCH_JOBS[job_id] = {"status": "error", "error": str(exc)}
523
 
524
 
525
- def _start_benchmark_job(kind, model_filter=None, force=False):
526
  job_id = f"bench-{int(time.time() * 1000)}-{threading.get_ident()}"
527
  with _BENCH_JOBS_LOCK:
528
  _BENCH_JOBS[job_id] = {
@@ -530,10 +553,11 @@ def _start_benchmark_job(kind, model_filter=None, force=False):
530
  "kind": kind,
531
  "model": model_filter,
532
  "started_at": time.time(),
 
533
  }
534
  threading.Thread(
535
  target=_run_benchmark_job,
536
- args=(job_id, kind, model_filter, force),
537
  daemon=True,
538
  ).start()
539
  return job_id
@@ -544,6 +568,57 @@ _TINYLLAMA = None
544
  _TINYLLAMA_TOKENIZER = None
545
  _TINYLLAMA_LOCK = threading.Lock()
546
  _TINYLLAMA_REPO = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
 
549
  def _load_tinyllama():
@@ -664,6 +739,30 @@ def _patched_create_app(blocks, **kwargs):
664
  try: return JSONResponse(_search_web(q, backend=backend, max_results=max_results, extract_top=extract_top, region=region))
665
  except Exception as e: return JSONResponse({"error": str(e)}, status_code=502)
666
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  @fa_app.post("/respite/text/generate")
668
  def _tinyllama(payload: dict = Body(...)):
669
  prompt = str(payload.get("prompt", ""))
 
107
  },
108
  "output": "WAV audio file",
109
  },
110
+ "qwen38_inference": {
111
+ "name": "Qwen3.8-27B text generation",
112
+ "description": "Generate text with Qwen3.8-27B using the Space CPU; no GPU allocation.",
113
+ "endpoint": "/respite/text/qwen38",
114
+ "method": "POST",
115
+ "input": {
116
+ "prompt": "string",
117
+ "max_new_tokens": "integer (1-256, default 64)",
118
+ "temperature": "number (0-2, default 0.7)",
119
+ "thinking": "boolean (default false)",
120
+ },
121
+ "output": "JSON containing generated text and CPU timing",
122
+ },
123
  "tinyllama_inference": {
124
  "name": "TinyLlama text generation",
125
  "description": "Generate text with TinyLlama 1.1B using the Space CPU; no GPU allocation.",
 
501
  return result
502
 
503
 
504
+ def _run_benchmark_job(job_id, kind, model_filter=None, force=False, request=None):
505
  try:
506
  if kind == "smoke":
507
  with _BENCH_LOCK:
 
515
  with _BENCH_LOCK:
516
  result = _run_cpu_process_probe(model_filter)
517
  payload = {"status": "complete", "result": result}
518
+ elif kind == "qwen38":
519
+ request = request or _BENCH_JOBS.get(job_id, {}).get("request", {})
520
+ with _BENCH_LOCK:
521
+ result = _generate_qwen38(
522
+ model_filter,
523
+ request.get("max_new_tokens", 64),
524
+ request.get("temperature", 0.7),
525
+ request.get("thinking", False),
526
+ )
527
+ payload = {"status": "complete", "result": result}
528
  else:
529
  if force:
530
  global _BENCH_RESULTS
 
545
  _BENCH_JOBS[job_id] = {"status": "error", "error": str(exc)}
546
 
547
 
548
+ def _start_benchmark_job(kind, model_filter=None, force=False, request=None):
549
  job_id = f"bench-{int(time.time() * 1000)}-{threading.get_ident()}"
550
  with _BENCH_JOBS_LOCK:
551
  _BENCH_JOBS[job_id] = {
 
553
  "kind": kind,
554
  "model": model_filter,
555
  "started_at": time.time(),
556
+ "request": request or {},
557
  }
558
  threading.Thread(
559
  target=_run_benchmark_job,
560
+ args=(job_id, kind, model_filter, force, request or {}),
561
  daemon=True,
562
  ).start()
563
  return job_id
 
568
  _TINYLLAMA_TOKENIZER = None
569
  _TINYLLAMA_LOCK = threading.Lock()
570
  _TINYLLAMA_REPO = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
571
+ _QWEN38 = None
572
+ _QWEN38_PROCESSOR = None
573
+ _QWEN38_LOCK = threading.Lock()
574
+ _QWEN38_REPO = "Qwen/Qwen3.8-27B"
575
+
576
+
577
+ def _load_qwen38():
578
+ global _QWEN38, _QWEN38_PROCESSOR
579
+ with _QWEN38_LOCK:
580
+ if _QWEN38 is not None:
581
+ return _QWEN38_PROCESSOR, _QWEN38
582
+ from transformers import AutoModelForImageTextToText, AutoProcessor
583
+ try:
584
+ torch.set_num_threads(min(_parse_cpu_count() or 1, 16))
585
+ except RuntimeError:
586
+ pass
587
+ processor = AutoProcessor.from_pretrained(_QWEN38_REPO)
588
+ model = AutoModelForImageTextToText.from_pretrained(
589
+ _QWEN38_REPO,
590
+ torch_dtype=torch.bfloat16,
591
+ low_cpu_mem_usage=True,
592
+ device_map="cpu",
593
+ )
594
+ model.eval()
595
+ _QWEN38_PROCESSOR = processor
596
+ _QWEN38 = model
597
+ return processor, model
598
+
599
+
600
+ def _generate_qwen38(prompt, max_new_tokens=64, temperature=0.7, thinking=False):
601
+ processor, model = _load_qwen38()
602
+ max_new_tokens = max(1, min(256, int(max_new_tokens)))
603
+ temperature = max(0.0, min(2.0, float(temperature)))
604
+ content = prompt if thinking else prompt + "/no_think"
605
+ messages = [{"role": "user", "content": content}]
606
+ text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
607
+ inputs = processor(text=[text], return_tensors="pt")
608
+ inputs = {key: value.to("cpu") if hasattr(value, "to") else value for key, value in inputs.items()}
609
+ t0 = time.time()
610
+ with torch.inference_mode():
611
+ output = model.generate(
612
+ **inputs,
613
+ max_new_tokens=max_new_tokens,
614
+ do_sample=temperature > 0,
615
+ temperature=max(temperature, 0.01),
616
+ )
617
+ elapsed = max(time.time() - t0, 0.001)
618
+ input_tokens = int(inputs["input_ids"].shape[-1])
619
+ generated = output[0, input_tokens:]
620
+ answer = processor.tokenizer.decode(generated, skip_special_tokens=True)
621
+ return {"text": answer, "prompt_tokens": input_tokens, "completion_tokens": int(generated.shape[-1]), "generation_tokens_per_sec": round(float(generated.shape[-1]) / elapsed, 2), "inference_time_sec": round(elapsed, 3), "device": "cpu", "dtype": "bfloat16", "thinking": bool(thinking)}
622
 
623
 
624
  def _load_tinyllama():
 
739
  try: return JSONResponse(_search_web(q, backend=backend, max_results=max_results, extract_top=extract_top, region=region))
740
  except Exception as e: return JSONResponse({"error": str(e)}, status_code=502)
741
 
742
+ @fa_app.post("/respite/text/qwen38")
743
+ def _qwen38(payload: dict = Body(...)):
744
+ prompt = str(payload.get("prompt", ""))
745
+ if not prompt.strip():
746
+ return JSONResponse({"error": "Missing 'prompt'"}, status_code=400)
747
+ job = _start_benchmark_job(
748
+ "qwen38",
749
+ prompt,
750
+ False,
751
+ request={
752
+ "max_new_tokens": payload.get("max_new_tokens", 64),
753
+ "temperature": payload.get("temperature", 0.7),
754
+ "thinking": payload.get("thinking", False),
755
+ },
756
+ )
757
+ return JSONResponse({"status": "accepted", "job_id": job, "poll": "/respite/text/qwen38?job_id=" + job}, status_code=202)
758
+
759
+ @fa_app.get("/respite/text/qwen38")
760
+ def _qwen38_status(job_id: str = ""):
761
+ if not job_id:
762
+ return JSONResponse({"error": "Missing 'job_id'"}, status_code=400)
763
+ with _BENCH_JOBS_LOCK:
764
+ return JSONResponse(_BENCH_JOBS.get(job_id, {"status": "not_found"}))
765
+
766
  @fa_app.post("/respite/text/generate")
767
  def _tinyllama(payload: dict = Body(...)):
768
  prompt = str(payload.get("prompt", ""))