| from flask import Flask, render_template, request, jsonify |
| import numpy as np |
| import traceback |
| import torch |
| import sys |
| import queue |
| import threading |
| from concurrent.futures import Future |
| from transformers import AutoModelForCausalLM, AutoTokenizer, AutoConfig |
| from collections import OrderedDict |
|
|
| app = Flask(__name__) |
| CKPT_NAME = "SakanaAI/RePo-OLMo2-1B-stage2-L5" |
|
|
| |
| |
| execution_queue = queue.Queue() |
|
|
| class RePo: |
| def __init__(self, model_name="SakanaAI/RePo-OLMo2-1B-stage2-L5", start_layer=5): |
| print(f"Loading model: {model_name}...") |
| self.tokenizer = AutoTokenizer.from_pretrained(model_name) |
| self.config = AutoConfig.from_pretrained(model_name) |
| model = AutoModelForCausalLM.from_pretrained(model_name) |
| |
| if torch.cuda.is_available(): |
| model = model.to("cuda") |
| self.device = torch.device("cuda") |
| print("Model loaded on CUDA.") |
| else: |
| print("[Warning] No GPU available, the service may be super slow") |
| self.device = torch.device("cpu") |
| self.model = model |
| self.start_layer = start_layer |
| self.cache = OrderedDict() |
| self.cache_size = 8 |
| |
| @torch.no_grad() |
| def forward(self, prompt, layer, head, max_tokens=512): |
| truncated = False |
| inputs = self.tokenizer(prompt, return_tensors="pt") |
| seq_len = inputs['input_ids'].shape[1] |
|
|
| if seq_len > max_tokens: |
| truncated = True |
| inputs['input_ids'] = inputs['input_ids'][:, :max_tokens] |
| if 'attention_mask' in inputs: |
| inputs['attention_mask'] = inputs['attention_mask'][:, :max_tokens] |
| prompt = self.tokenizer.decode(inputs['input_ids'][0], skip_special_tokens=False) |
|
|
| if prompt in self.cache: |
| pred_indices, toks = self.cache[prompt] |
| self.cache.move_to_end(prompt) |
| else: |
| inputs = self.tokenizer(prompt, return_tensors="pt") |
| tok_ids = inputs['input_ids'] |
| toks = self.tokenizer.convert_ids_to_tokens(tok_ids.squeeze(0), skip_special_tokens=False) |
| toks = [t.replace("Ġ", " ").replace("Ċ", "\n") for t in toks] |
| n_toks = len(toks) |
| inputs = {k: v.to(self.device) for k, v in inputs.items()} |
| outputs = self.model(**inputs, return_dict=True, output_pred_indices=True) |
| pred_indices = outputs.pred_indices |
| pred_indices = [it.data.squeeze(0).reshape(-1, n_toks).tolist() for it in pred_indices] |
| self.cache[prompt] = (pred_indices, toks) |
| if len(self.cache) > self.cache_size: |
| self.cache.popitem(last=False) |
| |
| data = [] |
| |
| if layer < len(pred_indices): |
| for x, (y, t) in enumerate(zip(pred_indices[layer][head], toks)): |
| data.append({ |
| "x": int(x), |
| "y": float(y), |
| "t": str(t) |
| }) |
| return data, truncated |
|
|
| |
| model = RePo(CKPT_NAME) |
|
|
| |
| def worker(): |
| """ |
| Consumer thread that processes requests sequentially. |
| """ |
| print("Background worker started.") |
| while True: |
| |
| |
| future, args = execution_queue.get() |
| try: |
| |
| result = model.forward( |
| prompt=args['sentence'], |
| layer=args['layer'], |
| head=args['head'], |
| max_tokens=args['max_tokens'] |
| ) |
| |
| future.set_result(result) |
| except Exception as e: |
| future.set_exception(e) |
| finally: |
| execution_queue.task_done() |
|
|
| |
| threading.Thread(target=worker, daemon=True).start() |
|
|
|
|
| @app.route('/') |
| def index(): |
| return render_template('index.html') |
|
|
| |
| @app.route('/queue_status', methods=['GET']) |
| def queue_status(): |
| """Returns the current number of requests waiting in queue.""" |
| return jsonify({"count": execution_queue.qsize()}) |
|
|
| @app.route('/process_sentence', methods=['POST']) |
| def process_sentence(): |
| try: |
| req_data = request.json |
| sentence = req_data.get('sentence', '') |
| layer = int(req_data.get('layer', 5)) |
| head = int(req_data.get('head', 0)) |
| |
| future = Future() |
| execution_queue.put((future, { |
| 'sentence': sentence, |
| 'layer': layer, |
| 'head': head, |
| 'max_tokens': 512 |
| })) |
| |
| results, was_truncated = future.result() |
|
|
| |
| suggested_range = None |
| y_vals = [d['y'] for d in results] |
| |
| |
| if len(y_vals) > 5: |
| |
| q75, q25 = np.percentile(y_vals, [75 ,25]) |
| iqr = q75 - q25 |
| |
| |
| lower_bound = q25 - (1.5 * iqr) |
| upper_bound = q75 + (1.5 * iqr) |
| |
| |
| inliers = [y for y in y_vals if lower_bound <= y <= upper_bound] |
| |
| if inliers: |
| |
| min_in = min(inliers) |
| max_in = max(inliers) |
| padding = (max_in - min_in) * 0.05 |
| if padding == 0: padding = 1.0 |
| |
| suggested_range = [min_in - padding, max_in + padding] |
|
|
| return jsonify({ |
| "status": "success", |
| "data": results, |
| "truncated": was_truncated, |
| "suggested_range": suggested_range |
| }) |
|
|
| except Exception as e: |
| print(traceback.format_exc()) |
| return jsonify({"status": "error", "message": str(e)}), 500 |
|
|
| |