Spaces:
Running on Zero
Running on Zero
Add CPU Qwen3.8 inference job
Browse files
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", ""))
|