File size: 6,331 Bytes
8b821fa cb7092d 8b821fa cb7092d 8b821fa 66bef3c 8b821fa cb7092d 8b821fa cb7092d 8b821fa cb7092d 8b821fa 66bef3c 8b821fa d0e252f 66bef3c 8b821fa cb7092d 66bef3c cb7092d 8b821fa cb7092d d0e252f 8b821fa cb7092d 8b821fa cb7092d 8b821fa cb7092d 8b821fa d7530db cb7092d 66bef3c d0e252f 66bef3c d0e252f cb7092d d7530db d0e252f 8b821fa cb7092d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | 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"
# --- 1. SETUP QUEUE & LOCKING ---
# We use a Queue to serialize requests so the GPU is only accessed by one thread at a time.
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 = []
# Safety check for layer bounds
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
# Initialize model globally
model = RePo(CKPT_NAME)
# --- 2. BACKGROUND WORKER ---
def worker():
"""
Consumer thread that processes requests sequentially.
"""
print("Background worker started.")
while True:
# Get a job from the queue
# job structure: (future_object, args_dict)
future, args = execution_queue.get()
try:
# Run the heavy model inference
result = model.forward(
prompt=args['sentence'],
layer=args['layer'],
head=args['head'],
max_tokens=args['max_tokens']
)
# Pass result back to the waiting HTTP thread
future.set_result(result)
except Exception as e:
future.set_exception(e)
finally:
execution_queue.task_done()
# Start the worker thread
threading.Thread(target=worker, daemon=True).start()
@app.route('/')
def index():
return render_template('index.html')
# --- 3. NEW STATUS ENDPOINT ---
@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()
# --- OUTLIER DETECTION LOGIC ---
suggested_range = None
y_vals = [d['y'] for d in results]
# Only apply logic if we have enough data points
if len(y_vals) > 5:
# Calculate Quartiles
q75, q25 = np.percentile(y_vals, [75 ,25])
iqr = q75 - q25
# Define bounds (1.5 * IQR is standard for outliers)
lower_bound = q25 - (1.5 * iqr)
upper_bound = q75 + (1.5 * iqr)
# Find the actual data range within these bounds
inliers = [y for y in y_vals if lower_bound <= y <= upper_bound]
if inliers:
# Add 5% padding for visual comfort
min_in = min(inliers)
max_in = max(inliers)
padding = (max_in - min_in) * 0.05
if padding == 0: padding = 1.0 # Handle flat lines
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
# Note: The if __name__ == '__main__' block is handled by uvicorn in your docker command |