Bot commited on
Commit
db89e8d
·
1 Parent(s): ca9c3e4

Fix ZeroGPU: lazy-load model inside decorated function, not at import time

Browse files
Files changed (1) hide show
  1. app.py +47 -43
app.py CHANGED
@@ -20,46 +20,48 @@ from transformers import AutoTokenizer, AutoModelForCausalLM
20
  from peft import PeftModel
21
  import torch
22
 
23
- # Model is loaded once at IMPORT TIME, not inside a FastAPI lifespan hook.
24
- # HF Spaces' Gradio SDK serves the `demo` Blocks object directly and never
25
- # runs `app`'s ASGI lifespan -- confirmed via server logs that this left
26
- # model/tokenizer permanently None, so every request hit the "Model not
27
- # loaded" fallback and crashed the gr.JSON output component with it.
28
- #
29
- # Also: under HF's free ZeroGPU tier, no GPU is visible at import time --
30
- # it's only allocated for the duration of an @spaces.GPU-decorated call.
31
- # So load on CPU here, then move to CUDA inside gradio_interface() below.
32
- #
33
- # IMPORTANT: load the base model in plain fp32 and apply the adapter via
34
- # plain PeftModel, NOT AutoPeftModelForCausalLM. The adapter was trained
35
- # with load_in_4bit=True, so its saved config carries a 4-bit quantization
36
- # config; AutoPeftModelForCausalLM auto-detects and applies that at load
37
- # time, which requires an actual CUDA device to instantiate -- confirmed
38
- # via a hard failure: "Could not load fine-tuned model (No CUDA GPUs are
39
- # available)" at import time, since ZeroGPU grants no GPU until a
40
- # @spaces.GPU-decorated call actually runs. Loading in plain fp32 needs no
41
- # quantization step at all, so it works with zero GPU present.
42
- print("Loading fine-tuned model...")
43
  ADAPTER_PATH = "vishnuadupa/qwen-sql-lora"
44
  BASE_MODEL_NAME = "Qwen/Qwen2.5-Coder-1.5B-Instruct"
45
 
46
- try:
47
- print(f"Loading adapter from HF Hub: {ADAPTER_PATH}")
48
- tokenizer = AutoTokenizer.from_pretrained(ADAPTER_PATH)
49
- base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL_NAME, torch_dtype=torch.float32)
50
- model = PeftModel.from_pretrained(base_model, ADAPTER_PATH)
51
- model = model.merge_and_unload()
52
- print("Fine-tuned model loaded.")
53
- except Exception as e:
54
- import traceback
55
- print(f"Could not load fine-tuned model ({e}); falling back to base model.")
56
- print("FULL TRACEBACK:")
57
- traceback.print_exc()
58
- tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_NAME)
59
- model = AutoModelForCausalLM.from_pretrained(BASE_MODEL_NAME, torch_dtype=torch.float32)
60
- print("Base model loaded.")
61
-
62
- model = model.to("cpu")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  app = FastAPI(title="SQL Copilot")
65
 
@@ -91,19 +93,20 @@ SQL:
91
 
92
 
93
  def generate_sql_text(question: str, schema: str, device: str = "cpu") -> str:
 
94
  prompt = build_prompt(question, schema)
95
- inputs = tokenizer(prompt, return_tensors="pt").to(device)
96
  # Greedy decoding, not sampling: a SQL generator should return its
97
  # single most-confident answer, not a random draw that can vary
98
  # between identical requests.
99
- outputs = model.generate(
100
  **inputs,
101
  max_new_tokens=256,
102
  do_sample=False,
103
- eos_token_id=tokenizer.eos_token_id,
104
- pad_token_id=tokenizer.eos_token_id,
105
  )
106
- response = tokenizer.decode(outputs[0], skip_special_tokens=True)
107
  sql = response.split("SQL:")[-1].strip()
108
 
109
  if "```" in sql:
@@ -206,9 +209,10 @@ def gradio_interface(question, schema):
206
  so the model is moved to "cuda" here rather than at import time.
207
  """
208
  device = "cuda" if torch.cuda.is_available() else "cpu"
209
- model.to(device)
210
 
211
  try:
 
 
212
  sql = generate_sql_text(question, schema, device=device)
213
  except Exception as e:
214
  # result_output is a gr.JSON component: it always requires a
 
20
  from peft import PeftModel
21
  import torch
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  ADAPTER_PATH = "vishnuadupa/qwen-sql-lora"
24
  BASE_MODEL_NAME = "Qwen/Qwen2.5-Coder-1.5B-Instruct"
25
 
26
+ # Model is loaded LAZILY, on first use, from inside the @spaces.GPU-decorated
27
+ # gradio_interface() -- not at import time, and not in a FastAPI lifespan
28
+ # hook (HF Spaces' Gradio SDK serves the `demo` Blocks object directly and
29
+ # never runs `app`'s ASGI lifespan, confirmed via logs: model/tokenizer
30
+ # stayed permanently None, so every request hit "Model not loaded").
31
+ #
32
+ # Loading at plain module level doesn't work either: `spaces` installs a
33
+ # global torch patch the moment it's imported that intercepts ALL tensor
34
+ # ops -- including a plain CPU safetensors.load_file() call -- and routes
35
+ # them through a GPU-context check. Confirmed via full traceback: loading
36
+ # the adapter at import time crashed inside that patch with "No CUDA GPUs
37
+ # are available", regardless of dtype/quantization choices, simply
38
+ # because no @spaces.GPU call was active yet. So loading must happen
39
+ # inside the decorated function itself, the first time it's actually
40
+ # invoked (which IS a valid GPU-allocated context under ZeroGPU).
41
+ _model = None
42
+ _tokenizer = None
43
+
44
+
45
+ def _ensure_model_loaded():
46
+ global _model, _tokenizer
47
+ if _model is not None:
48
+ return
49
+ print("Loading fine-tuned model...")
50
+ try:
51
+ print(f"Loading adapter from HF Hub: {ADAPTER_PATH}")
52
+ _tokenizer = AutoTokenizer.from_pretrained(ADAPTER_PATH)
53
+ base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL_NAME, torch_dtype=torch.float32)
54
+ _model = PeftModel.from_pretrained(base_model, ADAPTER_PATH)
55
+ _model = _model.merge_and_unload()
56
+ print("Fine-tuned model loaded.")
57
+ except Exception as e:
58
+ import traceback
59
+ print(f"Could not load fine-tuned model ({e}); falling back to base model.")
60
+ traceback.print_exc()
61
+ _tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL_NAME)
62
+ _model = AutoModelForCausalLM.from_pretrained(BASE_MODEL_NAME, torch_dtype=torch.float32)
63
+ print("Base model loaded.")
64
+
65
 
66
  app = FastAPI(title="SQL Copilot")
67
 
 
93
 
94
 
95
  def generate_sql_text(question: str, schema: str, device: str = "cpu") -> str:
96
+ _ensure_model_loaded()
97
  prompt = build_prompt(question, schema)
98
+ inputs = _tokenizer(prompt, return_tensors="pt").to(device)
99
  # Greedy decoding, not sampling: a SQL generator should return its
100
  # single most-confident answer, not a random draw that can vary
101
  # between identical requests.
102
+ outputs = _model.generate(
103
  **inputs,
104
  max_new_tokens=256,
105
  do_sample=False,
106
+ eos_token_id=_tokenizer.eos_token_id,
107
+ pad_token_id=_tokenizer.eos_token_id,
108
  )
109
+ response = _tokenizer.decode(outputs[0], skip_special_tokens=True)
110
  sql = response.split("SQL:")[-1].strip()
111
 
112
  if "```" in sql:
 
209
  so the model is moved to "cuda" here rather than at import time.
210
  """
211
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
212
 
213
  try:
214
+ _ensure_model_loaded() # first call here loads inside a valid GPU context
215
+ _model.to(device)
216
  sql = generate_sql_text(question, schema, device=device)
217
  except Exception as e:
218
  # result_output is a gr.JSON component: it always requires a