Download code/train/hid_server.py from AIGeeksGroup/CuriousGame: direct link, hf CLI and curl.
- Browser
- Download file 6.21 kB
-
https://huggingface.co/AIGeeksGroup/CuriousGame/resolve/main/code/train/hid_server.py
- Command line
-
hf download hf://AIGeeksGroup/CuriousGame/code/train/hid_server.py
-
curl -L -o hid_server.py https://huggingface.co/AIGeeksGroup/CuriousGame/resolve/main/code/train/hid_server.py
6.21 kB
| """Hidden-state feature of the hidden-state arms (EXPERIMENT_LOG.md 2026-10-19, tasks 3 and 4a) and its HTTP server. | |
| Runs in .train-venv. | |
| Feature: Qwen3.5-9B's last-layer hidden state (after the final norm, i.e. the lm_head input; 4096 dims) at the last | |
| prompt token of A's grounding question (ctrl_policy.ground(): the frame, GROUND.format(name), thinking off, the answer | |
| started as START, rendered by the chat template with continue_final_message as vLLM renders it), prefill only. Batches | |
| are right-padded and each row's last real token is gathered. Values travel as base64 of the bf16 bits (encode/decode; | |
| hid_policy.decode is the numpy twin), so a logged feature is exactly the one the controller used. | |
| Server: one model replica per GPU (threads), a shared queue, each replica takes up to MAX_BATCH waiting requests per | |
| forward. POST /feat {"frame": base64 PNG, "name": target name} -> {"feat": ...} and, with --base, also "base": the | |
| feature with the adapter disabled (the 4a KL reference input). The port opens once every replica is loaded. | |
| Usage: CUDA_VISIBLE_DEVICES=2 .train-venv/bin/python train/hid_server.py --port 9210 [--gpus 0] [--adapter DIR --base] | |
| """ | |
| import argparse | |
| import base64 | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| import io | |
| import json | |
| from pathlib import Path | |
| import queue | |
| import sys | |
| import threading | |
| import time | |
| import torch | |
| from PIL import Image | |
| from transformers import AutoProcessor | |
| from bt_train import MODULES, load_model # noqa: F401 (MODULES: the LoRA targets, re-exported for hid_grpo) | |
| from ctrl_policy import START | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.append(str(ROOT / "bench")) | |
| from diag_perception_direction import GROUND # noqa: E402 | |
| MAX_BATCH = 32 | |
| def processor(): | |
| return AutoProcessor.from_pretrained(ROOT / "models/Qwen3.5-9B", local_files_only=True) | |
| def prompt(proc, name): | |
| return proc.apply_chat_template( | |
| [{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": GROUND.format(name)}]}, | |
| {"role": "assistant", "content": START}], | |
| tokenize=False, add_generation_prompt=False, continue_final_message=True, enable_thinking=False) | |
| def batch_inputs(proc, images, names): | |
| return dict(proc(text=[prompt(proc, n) for n in names], images=images, padding=True, padding_side="right", | |
| return_tensors="pt")) | |
| def features(model, inputs, device): | |
| """(B, 4096) bf16 last-layer hidden states at each row's last prompt token; model: the Qwen3.5 model or its | |
| PeftModel (grad follows the caller's mode).""" | |
| body = (model.get_base_model() if hasattr(model, "get_base_model") else model).model | |
| x = {k: v.to(device) for k, v in inputs.items()} | |
| with torch.autocast("cuda", dtype=torch.bfloat16): | |
| h = body(**x, use_cache=False).last_hidden_state | |
| return h[torch.arange(len(h), device=device), x["attention_mask"].sum(1) - 1] | |
| def encode(f): | |
| """bf16 vector -> base64 of its bits.""" | |
| return base64.b64encode(f.to(torch.bfloat16).view(torch.int16).cpu().numpy().tobytes()).decode() | |
| def decode(s): | |
| return torch.frombuffer(bytearray(base64.b64decode(s)), dtype=torch.int16).view(torch.bfloat16) | |
| def png(b64): | |
| return Image.open(io.BytesIO(base64.b64decode(b64))).convert("RGB") | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--port", type=int, required=True) | |
| ap.add_argument("--gpus", default="0", help="local cuda indices, one replica each") | |
| ap.add_argument("--adapter", type=Path) | |
| ap.add_argument("--base", action="store_true", help="also return the feature with the adapter disabled") | |
| args = ap.parse_args() | |
| proc = processor() | |
| jobs = queue.Queue() | |
| def replica(model, device): | |
| while True: | |
| items = [jobs.get()] | |
| while len(items) < MAX_BATCH: | |
| try: | |
| items.append(jobs.get_nowait()) | |
| except queue.Empty: | |
| break | |
| try: | |
| inputs = batch_inputs(proc, [png(i["frame"]) for i in items], [i["name"] for i in items]) | |
| with torch.no_grad(): | |
| out = {"feat": features(model, inputs, device)} | |
| if args.base: | |
| with model.disable_adapter(): | |
| out["base"] = features(model, inputs, device) | |
| for k, item in enumerate(items): | |
| item["out"] = {key: encode(f[k]) for key, f in out.items()} | |
| except Exception as e: # answer every waiting request rather than leave it hanging | |
| for item in items: | |
| item["out"] = {"error": f"{type(e).__name__}: {e}"} | |
| for item in items: | |
| item["done"].set() | |
| gpus = [int(g) for g in args.gpus.split(",")] | |
| for g in gpus: | |
| model = load_model(g) | |
| if args.adapter: | |
| from peft import PeftModel | |
| model = PeftModel.from_pretrained(model, args.adapter) | |
| model.eval() | |
| threading.Thread(target=replica, args=(model, torch.device("cuda", g)), daemon=True).start() | |
| class Handler(BaseHTTPRequestHandler): | |
| def log_message(self, *a): | |
| pass | |
| def reply(self, code, body): | |
| data = json.dumps(body).encode() | |
| self.send_response(code) | |
| self.send_header("Content-Type", "application/json") | |
| self.send_header("Content-Length", str(len(data))) | |
| self.end_headers() | |
| self.wfile.write(data) | |
| def do_GET(self): | |
| self.reply(200, {"gpus": gpus}) | |
| def do_POST(self): | |
| item = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) | |
| item["done"] = threading.Event() | |
| jobs.put(item) | |
| item["done"].wait() | |
| self.reply(500 if "error" in item["out"] else 200, item["out"]) | |
| server = ThreadingHTTPServer(("0.0.0.0", args.port), Handler) | |
| server.daemon_threads = True | |
| print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} feature server on port {args.port}, gpus {gpus}, adapter " | |
| f"{args.adapter}, base {args.base}", flush=True) | |
| server.serve_forever() | |
| if __name__ == "__main__": | |
| main() | |