aledraa commited on
Commit
2fb221d
·
verified ·
1 Parent(s): e74ef5f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +134 -45
main.py CHANGED
@@ -2,19 +2,33 @@ from fastapi import FastAPI
2
  from pydantic import BaseModel
3
  import torch
4
  from transformers import AutoModelForCausalLM, AutoTokenizer
 
 
5
 
6
  # --- App and Model Loading ---
7
  app = FastAPI()
8
-
9
  model_name = "Qwen/Qwen2-0.5B-Instruct"
10
  print("Loading model...")
11
- # To leverage a GPU on Hugging Face Spaces, device_map="auto" is key
 
12
  model = AutoModelForCausalLM.from_pretrained(
13
  model_name,
14
- torch_dtype="auto",
15
- device_map="auto"
 
 
 
 
 
 
 
 
16
  )
17
- tokenizer = AutoTokenizer.from_pretrained(model_name)
 
 
 
 
18
  print("Model loaded successfully.")
19
 
20
  # --- API Request and Response Models ---
@@ -25,53 +39,128 @@ class GenerationRequest(BaseModel):
25
  class GenerationResponse(BaseModel):
26
  data: list
27
 
28
- # --- API Endpoint ---
29
- @app.post("/generate", response_model=GenerationResponse)
30
- async def generate_data(request: GenerationRequest):
31
- prompt = f"""
32
- You are a data generator. Your task is to generate {request.batch_size} random, non-similar rows of data based on the following commands.
33
- Each command corresponds to a column.
34
- Commands: {request.llm_commands}
35
- Return the data as a valid JSON array of arrays, where each inner array represents a row.
36
- For example, for the commands ["an age between 20 and 30", "a random city in California"], the output should look like:
37
- [[25, "Los Angeles"], [22, "San Francisco"]]
38
- Do not include any extra text, explanations, or markdown formatting in your response. Only output the raw JSON array.
39
- """
40
-
41
- messages = [
42
- {"role": "system", "content": "You are a helpful assistant that generates structured data."},
43
- {"role": "user", "content": prompt}
44
- ]
 
 
 
 
 
 
 
 
45
 
46
- text = tokenizer.apply_chat_template(
47
- messages,
48
- tokenize=False,
49
- add_generation_prompt=True
50
- )
51
 
52
- model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
 
53
 
54
- generated_ids = model.generate(
55
- **model_inputs,
56
- max_new_tokens=2048 # Increased to handle larger batches
57
- )
58
-
59
- generated_ids = [
60
- output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
61
- ]
62
-
63
- response_text = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
64
 
 
 
 
65
  try:
66
- # The model might still add extra text, so we clean it
67
- json_response = torch.tensor(eval(response_text.strip()))
68
- return {"data": json_response.tolist()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  except Exception as e:
70
- print(f"Error parsing model output: {e}")
71
- print(f"Raw output was: {response_text}")
72
- # Return empty on failure to prevent crashing the Inngest job
73
  return {"data": []}
74
 
75
  @app.get("/")
76
  def read_root():
77
- return {"status": "ok"}
 
 
 
 
 
 
 
 
 
2
  from pydantic import BaseModel
3
  import torch
4
  from transformers import AutoModelForCausalLM, AutoTokenizer
5
+ import json
6
+ import re
7
 
8
  # --- App and Model Loading ---
9
  app = FastAPI()
 
10
  model_name = "Qwen/Qwen2-0.5B-Instruct"
11
  print("Loading model...")
12
+
13
+ # Optimized model loading
14
  model = AutoModelForCausalLM.from_pretrained(
15
  model_name,
16
+ torch_dtype=torch.float16, # Use float16 for speed
17
+ device_map="auto",
18
+ trust_remote_code=True,
19
+ use_cache=True, # Enable KV cache for faster generation
20
+ low_cpu_mem_usage=True
21
+ )
22
+ tokenizer = AutoTokenizer.from_pretrained(
23
+ model_name,
24
+ trust_remote_code=True,
25
+ padding_side="left" # Better for batch generation
26
  )
27
+
28
+ # Set pad token if not exists
29
+ if tokenizer.pad_token is None:
30
+ tokenizer.pad_token = tokenizer.eos_token
31
+
32
  print("Model loaded successfully.")
33
 
34
  # --- API Request and Response Models ---
 
39
  class GenerationResponse(BaseModel):
40
  data: list
41
 
42
+ # --- Helper Functions ---
43
+ def extract_json_from_text(text: str):
44
+ """Extract JSON array from model output, handling extra text."""
45
+ # Look for JSON array pattern
46
+ json_pattern = r'\[\s*\[.*?\]\s*\]'
47
+ matches = re.findall(json_pattern, text, re.DOTALL)
48
+
49
+ if matches:
50
+ try:
51
+ return json.loads(matches[0])
52
+ except:
53
+ pass
54
+
55
+ # Fallback: try to find anything that looks like nested arrays
56
+ try:
57
+ # Find content between first [ and last ]
58
+ start = text.find('[')
59
+ end = text.rfind(']') + 1
60
+ if start != -1 and end != 0:
61
+ json_candidate = text[start:end]
62
+ return json.loads(json_candidate)
63
+ except:
64
+ pass
65
+
66
+ return None
67
 
68
+ def create_optimized_prompt(commands: list[str], batch_size: int) -> str:
69
+ """Create a more structured prompt to reduce hallucination."""
70
+ return f"""Generate exactly {batch_size} rows of data. Each row has {len(commands)} columns:
71
+ {chr(10).join([f'Column {i+1}: {cmd}' for i, cmd in enumerate(commands)])}
 
72
 
73
+ Output format: JSON array only, no explanations.
74
+ Example: [[value1, value2], [value3, value4]]
75
 
76
+ Generate {batch_size} rows:"""
 
 
 
 
 
 
 
 
 
77
 
78
+ # --- API Endpoint ---
79
+ @app.post("/generate", response_model=GenerationResponse)
80
+ async def generate_data(request: GenerationRequest):
81
  try:
82
+ # Create optimized prompt
83
+ prompt = create_optimized_prompt(request.llm_commands, request.batch_size)
84
+
85
+ messages = [
86
+ {"role": "system", "content": "You are a precise data generator. Output only valid JSON arrays with no extra text."},
87
+ {"role": "user", "content": prompt}
88
+ ]
89
+
90
+ # Apply chat template
91
+ text = tokenizer.apply_chat_template(
92
+ messages,
93
+ tokenize=False,
94
+ add_generation_prompt=True
95
+ )
96
+
97
+ # Tokenize with optimized settings
98
+ model_inputs = tokenizer(
99
+ text,
100
+ return_tensors="pt",
101
+ truncation=True,
102
+ max_length=2048, # Limit input length
103
+ padding=False
104
+ ).to(model.device)
105
+
106
+ # Generate with optimized parameters
107
+ with torch.no_grad(): # Disable gradients for inference
108
+ generated_ids = model.generate(
109
+ **model_inputs,
110
+ max_new_tokens=min(1024, request.batch_size * 20), # Dynamic max tokens
111
+ min_new_tokens=10,
112
+ do_sample=True,
113
+ temperature=0.7, # Balanced creativity/consistency
114
+ top_p=0.9,
115
+ top_k=50,
116
+ repetition_penalty=1.1,
117
+ pad_token_id=tokenizer.pad_token_id,
118
+ eos_token_id=tokenizer.eos_token_id,
119
+ use_cache=True,
120
+ num_beams=1, # Faster than beam search
121
+ early_stopping=True
122
+ )
123
+
124
+ # Extract generated text
125
+ generated_ids = [
126
+ output_ids[len(input_ids):]
127
+ for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
128
+ ]
129
+
130
+ response_text = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
131
+ print(f"Raw model output: {response_text[:200]}...") # Debug print
132
+
133
+ # Extract JSON data
134
+ json_data = extract_json_from_text(response_text)
135
+
136
+ if json_data and isinstance(json_data, list):
137
+ # Validate data structure
138
+ if len(json_data) > 0 and isinstance(json_data[0], list):
139
+ # Ensure we have the right number of columns
140
+ expected_cols = len(request.llm_commands)
141
+ filtered_data = [
142
+ row for row in json_data
143
+ if isinstance(row, list) and len(row) == expected_cols
144
+ ]
145
+
146
+ if filtered_data:
147
+ return {"data": filtered_data[:request.batch_size]}
148
+
149
+ print(f"Failed to parse JSON. Raw output: {response_text}")
150
+ return {"data": []}
151
+
152
  except Exception as e:
153
+ print(f"Error in generation: {e}")
 
 
154
  return {"data": []}
155
 
156
  @app.get("/")
157
  def read_root():
158
+ return {"status": "ok", "model": model_name}
159
+
160
+ @app.get("/health")
161
+ def health_check():
162
+ return {
163
+ "status": "healthy",
164
+ "model_loaded": model is not None,
165
+ "device": str(model.device) if model else "unknown"
166
+ }