dphn/Dolphin3.0-Mistral-24B is the ungated mirror of the Dolphin 3.0 Mistral 24B — exactly what you asked for. It's ~48GB fp16, which needs GPU+CPU split (device_map="auto" with 32GB on GPU, ~16GB in RAM). Let me kick off the download and update the service in parallel.
This commit is contained in:
96
tour-comfy/deploy_pose_llm.sh
Executable file
96
tour-comfy/deploy_pose_llm.sh
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy/stop/restart/status the uncensored chat LLM API (pose_llm_api.py) on tour.
|
||||||
|
# Mirrors the joycaption deploy_api.sh pattern.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./deploy_pose_llm.sh deploy # upload + (re)start, wait for health
|
||||||
|
# ./deploy_pose_llm.sh stop
|
||||||
|
# ./deploy_pose_llm.sh restart
|
||||||
|
# ./deploy_pose_llm.sh status
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LOCAL_FILE="$(dirname "$0")/pose_llm_api.py"
|
||||||
|
REMOTE="tour@192.168.1.160"
|
||||||
|
REMOTE_DIR="/media/tour/NVME0/llm"
|
||||||
|
PORT="${PORT:-8001}"
|
||||||
|
MODEL_ID="${MODEL_ID:-dphn/Dolphin3.0-Mistral-24B}"
|
||||||
|
|
||||||
|
ACTION="${1:-deploy}"
|
||||||
|
|
||||||
|
print_header() { echo; echo "============================================================"; echo "$1"; echo "============================================================"; }
|
||||||
|
|
||||||
|
stop_one() {
|
||||||
|
print_header "Stopping pose-LLM API on $REMOTE"
|
||||||
|
ssh "$REMOTE" "
|
||||||
|
cd '$REMOTE_DIR'
|
||||||
|
if [ -f api.pid ]; then
|
||||||
|
PID=\$(cat api.pid)
|
||||||
|
if kill -0 \"\$PID\" 2>/dev/null; then
|
||||||
|
echo '==> Stopping PID' \"\$PID\"; kill \"\$PID\"
|
||||||
|
for i in \$(seq 1 30); do kill -0 \"\$PID\" 2>/dev/null || { echo '==> stopped'; rm -f api.pid; exit 0; }; sleep 1; done
|
||||||
|
echo '==> hard kill'; kill -9 \"\$PID\" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -f api.pid
|
||||||
|
else echo '==> no api.pid'; fi
|
||||||
|
PORT_PID=\$(lsof -ti:$PORT 2>/dev/null || true)
|
||||||
|
[ -n \"\$PORT_PID\" ] && { echo '==> killing port $PORT:' \"\$PORT_PID\"; kill \$PORT_PID 2>/dev/null || true; }
|
||||||
|
true
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy_one() {
|
||||||
|
print_header "Deploying pose-LLM API to $REMOTE (model: $MODEL_ID)"
|
||||||
|
echo "==> Uploading pose_llm_api.py..."
|
||||||
|
scp "$LOCAL_FILE" "$REMOTE:$REMOTE_DIR/pose_llm_api.py"
|
||||||
|
|
||||||
|
echo "==> Writing start script..."
|
||||||
|
ssh "$REMOTE" "cat > '$REMOTE_DIR/start_pose_llm.sh'" << EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$REMOTE_DIR"
|
||||||
|
if [ -f api.pid ]; then
|
||||||
|
kill \$(cat api.pid) 2>/dev/null || true
|
||||||
|
rm -f api.pid
|
||||||
|
echo "waiting for VRAM to drain..."
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
. venv/bin/activate
|
||||||
|
export HSA_OVERRIDE_GFX_VERSION=9.0.6
|
||||||
|
export HF_HOME="$REMOTE_DIR/hf"
|
||||||
|
export HF_HUB_OFFLINE=1
|
||||||
|
export MODEL_ID="$MODEL_ID"
|
||||||
|
export PORT="$PORT"
|
||||||
|
nohup python3 pose_llm_api.py > api.log 2>&1 &
|
||||||
|
echo \$! > api.pid
|
||||||
|
echo "started PID \$(cat api.pid)"
|
||||||
|
EOF
|
||||||
|
ssh "$REMOTE" "chmod +x '$REMOTE_DIR/start_pose_llm.sh' && '$REMOTE_DIR/start_pose_llm.sh'"
|
||||||
|
|
||||||
|
echo "==> Waiting for model load + health (can take ~60s)..."
|
||||||
|
for i in $(seq 1 120); do
|
||||||
|
if ssh "$REMOTE" "curl -fsS http://localhost:$PORT/health >/dev/null 2>&1"; then echo "==> API ready"; break; fi
|
||||||
|
if [ "$i" -eq 120 ]; then echo "ERROR: not healthy"; ssh "$REMOTE" "tail -n 60 $REMOTE_DIR/api.log"; exit 1; fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "==> Health:"; ssh "$REMOTE" "curl -s http://localhost:$PORT/health | python3 -m json.tool"
|
||||||
|
}
|
||||||
|
|
||||||
|
status_one() {
|
||||||
|
print_header "Status on $REMOTE"
|
||||||
|
ssh "$REMOTE" "
|
||||||
|
cd '$REMOTE_DIR'
|
||||||
|
echo '==> PID:'; cat api.pid 2>/dev/null || echo none
|
||||||
|
echo '==> Port $PORT:'; lsof -i:$PORT 2>/dev/null || echo 'nothing listening'
|
||||||
|
echo '==> Health:'; curl -fsS http://localhost:$PORT/health 2>/dev/null | python3 -m json.tool || echo 'not healthy'
|
||||||
|
echo '==> Last 20 log lines:'; tail -n 20 api.log 2>/dev/null || true
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
deploy) deploy_one ;;
|
||||||
|
stop) stop_one ;;
|
||||||
|
restart) stop_one; deploy_one ;;
|
||||||
|
status) status_one ;;
|
||||||
|
*) echo "Usage: $0 [deploy|stop|restart|status]"; exit 1 ;;
|
||||||
|
esac
|
||||||
159
tour-comfy/gen_poses.py
Executable file
159
tour-comfy/gen_poses.py
Executable file
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate new pose blocks via the uncensored chat LLM on tour, in the exact
|
||||||
|
format poses.md uses (parsed by _load_poses() in edit_api.py).
|
||||||
|
|
||||||
|
Runs on the dev box, hits the remote chat API over the LAN, validates + dedupes
|
||||||
|
the model output, and (by default) stages new blocks to poses.new.md for review.
|
||||||
|
Use --apply to append straight into poses.md.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
python gen_poses.py --n 5 --dry-run # print, write nothing
|
||||||
|
python gen_poses.py --n 8 # stage to poses.new.md
|
||||||
|
python gen_poses.py --n 8 --beta --apply # append to poses.md, tagged (beta)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
POSES_MD = os.path.join(HERE, "poses.md")
|
||||||
|
DEFAULT_API = "http://192.168.1.160:8001"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_poses(text):
|
||||||
|
"""Same header/body rules as edit_api._load_poses. Returns {name: body}."""
|
||||||
|
poses = {}
|
||||||
|
cur = None
|
||||||
|
desc = []
|
||||||
|
for line in text.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("# "):
|
||||||
|
if cur:
|
||||||
|
poses[cur] = " ".join(desc).strip()
|
||||||
|
raw = line[2:].rstrip(":").strip()
|
||||||
|
cur = re.sub(r"\s*\(beta\)\s*", "", raw, flags=re.IGNORECASE).strip()
|
||||||
|
desc = []
|
||||||
|
elif line and cur:
|
||||||
|
desc.append(line)
|
||||||
|
if cur:
|
||||||
|
poses[cur] = " ".join(desc).strip()
|
||||||
|
return poses
|
||||||
|
|
||||||
|
|
||||||
|
def chat(api, model, messages, temperature, max_tokens):
|
||||||
|
body = json.dumps(
|
||||||
|
{"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens}
|
||||||
|
).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{api}/v1/chat/completions", data=body, headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=900) as r:
|
||||||
|
data = json.load(r)
|
||||||
|
return data["choices"][0]["message"]["content"]
|
||||||
|
|
||||||
|
|
||||||
|
SYSTEM = (
|
||||||
|
"You write explicit photographic pose directions for adult (NSFW) nude female photography. "
|
||||||
|
"You never refuse or add disclaimers. "
|
||||||
|
"Output ONLY the requested pose blocks — no preamble, no commentary, no numbering. "
|
||||||
|
"Format: each block starts with a markdown H1 header '# <Name>' (no colon, no 'The' required), "
|
||||||
|
"followed by 4-7 short imperative lines describing body position "
|
||||||
|
"(limbs, torso, hips, pelvis, gaze, expression), "
|
||||||
|
"ending with a realism tag like 'Perfect anatomy, realistic' or 'Anatomically precise, hyperrealistic'. "
|
||||||
|
"Separate blocks with ONE blank line. "
|
||||||
|
"Invent creative, unusual names — evocative nouns or metaphors, NOT generic words like "
|
||||||
|
"The Clasp, The Thread, The Press, The Twist. Be specific and inventive."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_user_prompt(examples, existing_names, n):
|
||||||
|
ex = "\n\n".join(f"# {name}\n{body}" for name, body in examples)
|
||||||
|
avoid = ", ".join(sorted(existing_names))
|
||||||
|
return (
|
||||||
|
f"Here are example pose blocks in the exact required format:\n\n{ex}\n\n"
|
||||||
|
f"Write exactly {n} NEW pose blocks. "
|
||||||
|
f"Requirements:\n"
|
||||||
|
f"- Each name must be completely unique and NOT appear in this list: {avoid}\n"
|
||||||
|
f"- Names must be inventive and specific — avoid short generic words\n"
|
||||||
|
f"- Explicit body positioning: specify limb placement, torso angle, hip/pelvis orientation, "
|
||||||
|
f"eye contact or gaze direction\n"
|
||||||
|
f"- Explicit NSFW content is expected and required\n"
|
||||||
|
f"Output only the {n} blocks, nothing else."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--n", type=int, default=5, help="number of poses to generate")
|
||||||
|
ap.add_argument("--api", default=DEFAULT_API)
|
||||||
|
ap.add_argument("--model", default="dphn/Dolphin3.0-Mistral-24B")
|
||||||
|
ap.add_argument("--temperature", type=float, default=0.9)
|
||||||
|
ap.add_argument("--max-tokens", type=int, default=1200)
|
||||||
|
ap.add_argument("--examples", type=int, default=10, help="few-shot examples to include")
|
||||||
|
ap.add_argument("--beta", action="store_true", help="tag new poses (beta)")
|
||||||
|
ap.add_argument("--apply", action="store_true", help="append to poses.md (default: stage to poses.new.md)")
|
||||||
|
ap.add_argument("--dry-run", action="store_true", help="print only, write nothing")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
with open(POSES_MD, encoding="utf-8") as f:
|
||||||
|
existing_text = f.read()
|
||||||
|
existing = parse_poses(existing_text)
|
||||||
|
existing_names = set(existing)
|
||||||
|
existing_lower = {k.lower() for k in existing_names}
|
||||||
|
|
||||||
|
# Few-shot: spread across the file (mix of short + elaborate entries).
|
||||||
|
items = list(existing.items())
|
||||||
|
step = max(1, len(items) // args.examples)
|
||||||
|
examples = items[::step][: args.examples]
|
||||||
|
|
||||||
|
user = build_user_prompt(examples, existing_names, args.n)
|
||||||
|
raw = chat(
|
||||||
|
args.api, args.model,
|
||||||
|
[{"role": "system", "content": SYSTEM}, {"role": "user", "content": user}],
|
||||||
|
args.temperature, args.max_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
|
generated = parse_poses(raw)
|
||||||
|
new = {}
|
||||||
|
for name, body in generated.items():
|
||||||
|
if not name or not body:
|
||||||
|
continue
|
||||||
|
if name.lower() in existing_lower or name.lower() in (k.lower() for k in new):
|
||||||
|
print(f" skip duplicate: {name}")
|
||||||
|
continue
|
||||||
|
new[name] = body
|
||||||
|
|
||||||
|
if not new:
|
||||||
|
print("No valid new poses produced. Raw model output:\n" + raw)
|
||||||
|
return
|
||||||
|
|
||||||
|
suffix = " (beta)" if args.beta else ""
|
||||||
|
blocks = "\n\n".join(f"# {name}{suffix}\n{body}" for name, body in new.items())
|
||||||
|
|
||||||
|
print(f"\n=== {len(new)} new pose(s) ===\n")
|
||||||
|
print(blocks)
|
||||||
|
|
||||||
|
# Re-validate the rendered blocks parse cleanly.
|
||||||
|
assert set(parse_poses(blocks)) , "rendered blocks failed to parse"
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print("\n[dry-run] nothing written.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.apply:
|
||||||
|
with open(POSES_MD, "a", encoding="utf-8") as f:
|
||||||
|
f.write("\n\n" + blocks + "\n")
|
||||||
|
print(f"\nAppended {len(new)} pose(s) to {POSES_MD}")
|
||||||
|
else:
|
||||||
|
staging = os.path.join(HERE, "poses.new.md")
|
||||||
|
with open(staging, "a", encoding="utf-8") as f:
|
||||||
|
f.write("\n\n" + blocks + "\n")
|
||||||
|
print(f"\nStaged {len(new)} pose(s) to {staging} (review, then move into poses.md)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
206
tour-comfy/pose_llm_api.py
Executable file
206
tour-comfy/pose_llm_api.py
Executable file
@@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Uncensored chat LLM API — loads an instruct model once, serves an
|
||||||
|
OpenAI-compatible /v1/chat/completions endpoint. Runs on the AMD MI50 (gfx906)
|
||||||
|
via ROCm 5.7 + torch 2.3.1, mirroring the joycaption service pattern.
|
||||||
|
|
||||||
|
Model fits in fp16 inside the 32GB VRAM (≈12B ceiling). Override with env MODEL_ID.
|
||||||
|
|
||||||
|
Endpoints:
|
||||||
|
POST /v1/chat/completions — OpenAI-compatible chat (used by gen_poses.py)
|
||||||
|
GET /v1/models — list the loaded model
|
||||||
|
GET /health — health check + GPU info
|
||||||
|
|
||||||
|
Env:
|
||||||
|
MODEL_ID HuggingFace repo id (default below)
|
||||||
|
PORT listen port (default 8001)
|
||||||
|
HSA_OVERRIDE_GFX_VERSION=9.0.6 set by start script for gfx906
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||||
|
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
gpu_executor = ThreadPoolExecutor(max_workers=1)
|
||||||
|
|
||||||
|
# Mistral [INST] template — fallback for models without chat_template in tokenizer_config
|
||||||
|
_MISTRAL_TEMPLATE = (
|
||||||
|
"{{ bos_token }}"
|
||||||
|
"{% for message in messages %}"
|
||||||
|
"{% if message['role'] == 'system' %}"
|
||||||
|
"{{ '[INST] ' + message['content'] + '\n\n' }}"
|
||||||
|
"{% elif message['role'] == 'user' %}"
|
||||||
|
"{% if not loop.first or messages[0]['role'] != 'system' %}{{ '[INST] ' }}{% endif %}"
|
||||||
|
"{{ message['content'] + ' [/INST]' }}"
|
||||||
|
"{% elif message['role'] == 'assistant' %}"
|
||||||
|
"{{ ' ' + message['content'] + eos_token }}"
|
||||||
|
"{% endif %}"
|
||||||
|
"{% endfor %}"
|
||||||
|
)
|
||||||
|
|
||||||
|
MODEL_ID = os.environ.get("MODEL_ID", "dphn/Dolphin3.0-Mistral-24B")
|
||||||
|
# For 24B fp16 (~48GB): split across 32GB VRAM + CPU RAM via device_map="auto".
|
||||||
|
# Override with MAX_GPU_MEM / MAX_CPU_MEM env vars if needed.
|
||||||
|
MAX_GPU_MEM = os.environ.get("MAX_GPU_MEM", "30GiB") # leave ~2GB headroom
|
||||||
|
MAX_CPU_MEM = os.environ.get("MAX_CPU_MEM", "100GiB") # 113GB available
|
||||||
|
|
||||||
|
state: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
# Boost GPU to high performance mode (avoids power-saving clock throttle)
|
||||||
|
subprocess.run(["/opt/rocm/bin/rocm-smi", "--setperflevel", "high"], capture_output=True)
|
||||||
|
print(f"Loading model {MODEL_ID} (gpu≤{MAX_GPU_MEM} cpu≤{MAX_CPU_MEM})...", flush=True)
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
||||||
|
if not tokenizer.chat_template:
|
||||||
|
print("No chat_template found — applying Mistral [INST] fallback.", flush=True)
|
||||||
|
tokenizer.chat_template = _MISTRAL_TEMPLATE
|
||||||
|
model = AutoModelForCausalLM.from_pretrained(
|
||||||
|
MODEL_ID,
|
||||||
|
torch_dtype=torch.float16,
|
||||||
|
device_map="auto",
|
||||||
|
max_memory={0: MAX_GPU_MEM, "cpu": MAX_CPU_MEM},
|
||||||
|
)
|
||||||
|
model.eval()
|
||||||
|
state["tokenizer"] = tokenizer
|
||||||
|
state["model"] = model
|
||||||
|
|
||||||
|
# Warm up: tiny generation to trigger kernel compilation
|
||||||
|
print("Warming up...", flush=True)
|
||||||
|
_run_chat([{"role": "user", "content": "hi"}], max_tokens=4, temperature=0.0, top_p=1.0, stop=None)
|
||||||
|
print(f"Model ready on: {next(model.parameters()).device}", flush=True)
|
||||||
|
yield
|
||||||
|
gpu_executor.shutdown(wait=False, cancel_futures=True)
|
||||||
|
state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="Uncensored Chat LLM API", lifespan=lifespan)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_chat(messages, max_tokens, temperature, top_p, stop):
|
||||||
|
tokenizer = state["tokenizer"]
|
||||||
|
model = state["model"]
|
||||||
|
|
||||||
|
input_ids = tokenizer.apply_chat_template(
|
||||||
|
messages, add_generation_prompt=True, return_tensors="pt"
|
||||||
|
).to(model.device)
|
||||||
|
prompt_tokens = input_ids.shape[1]
|
||||||
|
|
||||||
|
do_sample = temperature is not None and temperature > 0
|
||||||
|
gen_kwargs = dict(
|
||||||
|
max_new_tokens=max_tokens,
|
||||||
|
do_sample=do_sample,
|
||||||
|
use_cache=True,
|
||||||
|
pad_token_id=tokenizer.eos_token_id,
|
||||||
|
)
|
||||||
|
if do_sample:
|
||||||
|
gen_kwargs["temperature"] = temperature
|
||||||
|
gen_kwargs["top_p"] = top_p
|
||||||
|
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
with torch.inference_mode():
|
||||||
|
output_ids = model.generate(input_ids, **gen_kwargs)
|
||||||
|
dt = time.perf_counter() - t0
|
||||||
|
|
||||||
|
new_ids = output_ids[0][prompt_tokens:]
|
||||||
|
text = tokenizer.decode(new_ids, skip_special_tokens=True)
|
||||||
|
|
||||||
|
finish = "stop"
|
||||||
|
if stop:
|
||||||
|
stops = [stop] if isinstance(stop, str) else list(stop)
|
||||||
|
cut = min((text.find(s) for s in stops if s and s in text), default=-1)
|
||||||
|
if cut != -1:
|
||||||
|
text = text[:cut]
|
||||||
|
completion_tokens = int(new_ids.shape[0])
|
||||||
|
if completion_tokens >= max_tokens:
|
||||||
|
finish = "length"
|
||||||
|
|
||||||
|
del input_ids, output_ids
|
||||||
|
return {
|
||||||
|
"text": text.strip(),
|
||||||
|
"prompt_tokens": prompt_tokens,
|
||||||
|
"completion_tokens": completion_tokens,
|
||||||
|
"finish_reason": finish,
|
||||||
|
"generate_s": round(dt, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def run_chat(*args):
|
||||||
|
return await asyncio.get_running_loop().run_in_executor(gpu_executor, _run_chat, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMessage(BaseModel):
|
||||||
|
role: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRequest(BaseModel):
|
||||||
|
model: str | None = None
|
||||||
|
messages: list[ChatMessage]
|
||||||
|
max_tokens: int = 512
|
||||||
|
temperature: float = 0.8
|
||||||
|
top_p: float = 0.95
|
||||||
|
stop: list[str] | str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/v1/chat/completions")
|
||||||
|
async def chat_completions(req: ChatRequest):
|
||||||
|
if not req.messages:
|
||||||
|
raise HTTPException(400, "messages must not be empty")
|
||||||
|
msgs = [{"role": m.role, "content": m.content} for m in req.messages]
|
||||||
|
r = await run_chat(msgs, req.max_tokens, req.temperature, req.top_p, req.stop)
|
||||||
|
return {
|
||||||
|
"id": f"chatcmpl-{uuid.uuid4().hex[:24]}",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": int(time.time()),
|
||||||
|
"model": MODEL_ID,
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {"role": "assistant", "content": r["text"]},
|
||||||
|
"finish_reason": r["finish_reason"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": r["prompt_tokens"],
|
||||||
|
"completion_tokens": r["completion_tokens"],
|
||||||
|
"total_tokens": r["prompt_tokens"] + r["completion_tokens"],
|
||||||
|
},
|
||||||
|
"timing": {"generate_s": r["generate_s"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/v1/models")
|
||||||
|
async def list_models():
|
||||||
|
return {"object": "list", "data": [{"id": MODEL_ID, "object": "model"}]}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
gpu = {}
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
gpu = {
|
||||||
|
"name": torch.cuda.get_device_name(0),
|
||||||
|
"vram_total_gb": round(torch.cuda.get_device_properties(0).total_memory / 1e9, 1),
|
||||||
|
"vram_used_gb": round(torch.cuda.memory_allocated(0) / 1e9, 1),
|
||||||
|
}
|
||||||
|
return {"status": "ok", "model": MODEL_ID, "loaded": "model" in state, "gpu": gpu}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8001)))
|
||||||
2786
tour-comfy/poses.md
2786
tour-comfy/poses.md
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user