Use Transformers runtime for Space inference

#5
Files changed (5) hide show
  1. README.md +2 -3
  2. app.py +12 -0
  3. btl/model.py +43 -61
  4. pyproject.toml +1 -1
  5. requirements.txt +1 -2
README.md CHANGED
@@ -12,7 +12,6 @@ tags:
12
  - backyard-ai
13
  - tiny-titan
14
  - well-tuned
15
- - llama-cpp
16
  - code
17
  ---
18
 
@@ -22,7 +21,7 @@ A small-model code-reading assistant for Python files. It parses a single file d
22
 
23
  The app includes two modes:
24
 
25
- - **Base Mellum2:** richer comments from the base instruct model through llama.cpp.
26
  - **Fine-tuned LoRA:** a concise comment style trained on CodeSearchNet-derived Python examples with Modal.
27
 
28
  The model never edits code directly. It only proposes comments, and the app rejects any annotated file whose semantic AST changes.
@@ -48,7 +47,7 @@ npx between-the-lines-cli path/to/file.py --model base --output annotated.py
48
  npx between-the-lines-cli path/to/file.py --model tuned --in-place
49
  ```
50
 
51
- `--model base` uses the richer Mellum2 GGUF path. `--model tuned` uses the LoRA adapter for shorter comments. Both modes run the AST validation before writing output.
52
 
53
  ## Hackathon Fit
54
 
 
12
  - backyard-ai
13
  - tiny-titan
14
  - well-tuned
 
15
  - code
16
  ---
17
 
 
21
 
22
  The app includes two modes:
23
 
24
+ - **Base Mellum2:** richer comments from the base instruct model through Transformers.
25
  - **Fine-tuned LoRA:** a concise comment style trained on CodeSearchNet-derived Python examples with Modal.
26
 
27
  The model never edits code directly. It only proposes comments, and the app rejects any annotated file whose semantic AST changes.
 
47
  npx between-the-lines-cli path/to/file.py --model tuned --in-place
48
  ```
49
 
50
+ `--model base` uses the richer Mellum2 instruct path. `--model tuned` uses the LoRA adapter for shorter comments. Both modes run the AST validation before writing output.
51
 
52
  ## Hackathon Fit
53
 
app.py CHANGED
@@ -7,6 +7,17 @@ import gradio as gr
7
  from btl.annotate import annotate_python, collect_blocks, parse_python
8
  from btl.model import ModelUnavailableError
9
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  CSS = """
12
  :root {
@@ -161,6 +172,7 @@ MODEL_LABELS = {
161
  }
162
 
163
 
 
164
  def annotate_code(source: str, model_label: str) -> tuple[str, str, str]:
165
  try:
166
  result = annotate_python(source, MODEL_LABELS.get(model_label, "base"))
 
7
  from btl.annotate import annotate_python, collect_blocks, parse_python
8
  from btl.model import ModelUnavailableError
9
 
10
+ try:
11
+ import spaces
12
+ except ImportError:
13
+ spaces = None
14
+
15
+
16
+ def zero_gpu_task(fn):
17
+ if spaces is None:
18
+ return fn
19
+ return spaces.GPU(duration=180)(fn)
20
+
21
 
22
  CSS = """
23
  :root {
 
172
  }
173
 
174
 
175
+ @zero_gpu_task
176
  def annotate_code(source: str, model_label: str) -> tuple[str, str, str]:
177
  try:
178
  result = annotate_python(source, MODEL_LABELS.get(model_label, "base"))
btl/model.py CHANGED
@@ -5,8 +5,6 @@ from typing import Literal
5
  from .prompts import build_comment_messages
6
 
7
 
8
- DEFAULT_MODEL_REPO = "JetBrains/Mellum2-12B-A2.5B-Instruct-GGUF-Q8_0"
9
- DEFAULT_MODEL_FILE = "Mellum2-12B-A2.5B-Instruct-Q8_0.gguf"
10
  DEFAULT_BASE_TRANSFORMERS_MODEL = "JetBrains/Mellum2-12B-A2.5B-Instruct"
11
  DEFAULT_TUNED_ADAPTER_REPO = "coolbeanz79/between-the-lines-mellum2-lora"
12
 
@@ -18,31 +16,38 @@ class ModelUnavailableError(RuntimeError):
18
 
19
 
20
  @lru_cache(maxsize=1)
21
- def _load_base_llm():
22
  try:
23
- from llama_cpp import Llama
 
24
  except ImportError as exc:
25
  raise ModelUnavailableError(
26
- "llama-cpp-python is not installed. Install requirements before using model annotations."
27
  ) from exc
28
 
29
- repo_id = os.getenv("BTL_MODEL_REPO", DEFAULT_MODEL_REPO)
30
- filename = os.getenv("BTL_MODEL_FILE", DEFAULT_MODEL_FILE)
31
- n_ctx = int(os.getenv("BTL_MODEL_CTX", "4096"))
32
- n_gpu_layers = int(os.getenv("BTL_MODEL_GPU_LAYERS", "-1"))
33
 
34
  try:
35
- return Llama.from_pretrained(
36
- repo_id=repo_id,
37
- filename=filename,
38
- n_ctx=n_ctx,
39
- n_gpu_layers=n_gpu_layers,
40
- verbose=False,
 
 
 
41
  )
 
 
 
 
 
 
 
 
42
  except Exception as exc:
43
- raise ModelUnavailableError(
44
- f"Could not load `{repo_id}` / `{filename}` with llama-cpp-python: {exc}"
45
- ) from exc
46
 
47
 
48
  def _clean_comment(text: str) -> str:
@@ -74,32 +79,14 @@ def _load_tuned_model():
74
  )
75
 
76
  try:
77
- import torch
78
  from peft import PeftModel
79
- from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
80
  except ImportError as exc:
81
  raise ModelUnavailableError(
82
- "Tuned LoRA inference requires torch, transformers, peft, and bitsandbytes."
83
  ) from exc
84
 
85
- model_name = os.getenv("BTL_TUNED_BASE_MODEL", DEFAULT_BASE_TRANSFORMERS_MODEL)
86
  try:
87
- tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
88
- if tokenizer.pad_token is None:
89
- tokenizer.pad_token = tokenizer.eos_token
90
-
91
- quantization_config = BitsAndBytesConfig(
92
- load_in_4bit=True,
93
- bnb_4bit_compute_dtype=torch.bfloat16,
94
- bnb_4bit_quant_type="nf4",
95
- bnb_4bit_use_double_quant=True,
96
- )
97
- base_model = AutoModelForCausalLM.from_pretrained(
98
- model_name,
99
- trust_remote_code=True,
100
- device_map="auto",
101
- quantization_config=quantization_config,
102
- )
103
  model = PeftModel.from_pretrained(base_model, adapter_path_or_repo)
104
  model.eval()
105
  return tokenizer, model
@@ -110,39 +97,24 @@ def _load_tuned_model():
110
  def generate_comment(kind: str, name: str, source: str, variant: ModelVariant = "base") -> str:
111
  if variant == "tuned":
112
  return generate_comment_with_tuned_model(kind, name, source)
113
- llm = load_llm()
114
- return generate_comment_with_llm(llm, kind, name, source)
115
-
116
 
117
- def load_llm():
118
- return _load_base_llm()
119
 
120
-
121
- def generate_comment_with_llm(llm, kind: str, name: str, source: str) -> str:
122
- messages = build_comment_messages(kind, name, source)
123
- response = llm.create_chat_completion(
124
- messages=messages,
125
- temperature=0.15,
126
- top_p=0.9,
127
- max_tokens=80,
128
- stop=["\n\n", "```"],
129
- )
130
- text = response["choices"][0]["message"]["content"]
131
- return _clean_comment(text)
132
-
133
-
134
- def generate_comment_with_tuned_model(kind: str, name: str, source: str) -> str:
135
  import torch
136
 
137
- tokenizer, model = _load_tuned_model()
138
  messages = build_comment_messages(kind, name, source)
139
  prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
140
  encoded = tokenizer(
141
  prompt,
142
  return_tensors="pt",
143
  truncation=True,
144
- max_length=int(os.getenv("BTL_TUNED_MODEL_CTX", "4096")),
145
- ).to(model.device)
 
 
 
 
146
 
147
  with torch.inference_mode():
148
  output_ids = model.generate(
@@ -156,3 +128,13 @@ def generate_comment_with_tuned_model(kind: str, name: str, source: str) -> str:
156
  generated_ids = output_ids[encoded["input_ids"].shape[-1] :]
157
  text = tokenizer.decode(generated_ids, skip_special_tokens=True)
158
  return _clean_comment(text)
 
 
 
 
 
 
 
 
 
 
 
5
  from .prompts import build_comment_messages
6
 
7
 
 
 
8
  DEFAULT_BASE_TRANSFORMERS_MODEL = "JetBrains/Mellum2-12B-A2.5B-Instruct"
9
  DEFAULT_TUNED_ADAPTER_REPO = "coolbeanz79/between-the-lines-mellum2-lora"
10
 
 
16
 
17
 
18
  @lru_cache(maxsize=1)
19
+ def _load_base_model():
20
  try:
21
+ import torch
22
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
23
  except ImportError as exc:
24
  raise ModelUnavailableError(
25
+ "Base Mellum2 inference requires torch, transformers, accelerate, and bitsandbytes."
26
  ) from exc
27
 
28
+ model_name = os.getenv("BTL_BASE_MODEL", DEFAULT_BASE_TRANSFORMERS_MODEL)
 
 
 
29
 
30
  try:
31
+ tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
32
+ if tokenizer.pad_token is None:
33
+ tokenizer.pad_token = tokenizer.eos_token
34
+
35
+ quantization_config = BitsAndBytesConfig(
36
+ load_in_4bit=True,
37
+ bnb_4bit_compute_dtype=torch.bfloat16,
38
+ bnb_4bit_quant_type="nf4",
39
+ bnb_4bit_use_double_quant=True,
40
  )
41
+ model = AutoModelForCausalLM.from_pretrained(
42
+ model_name,
43
+ trust_remote_code=True,
44
+ device_map="auto",
45
+ quantization_config=quantization_config,
46
+ )
47
+ model.eval()
48
+ return tokenizer, model
49
  except Exception as exc:
50
+ raise ModelUnavailableError(f"Could not load base Mellum2 model `{model_name}`: {exc}") from exc
 
 
51
 
52
 
53
  def _clean_comment(text: str) -> str:
 
79
  )
80
 
81
  try:
 
82
  from peft import PeftModel
 
83
  except ImportError as exc:
84
  raise ModelUnavailableError(
85
+ "Tuned LoRA inference requires peft in addition to the base Transformers runtime."
86
  ) from exc
87
 
 
88
  try:
89
+ tokenizer, base_model = _load_base_model()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  model = PeftModel.from_pretrained(base_model, adapter_path_or_repo)
91
  model.eval()
92
  return tokenizer, model
 
97
  def generate_comment(kind: str, name: str, source: str, variant: ModelVariant = "base") -> str:
98
  if variant == "tuned":
99
  return generate_comment_with_tuned_model(kind, name, source)
100
+ return generate_comment_with_base_model(kind, name, source)
 
 
101
 
 
 
102
 
103
+ def _generate_with_transformers(tokenizer, model, kind: str, name: str, source: str, max_length_env: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  import torch
105
 
 
106
  messages = build_comment_messages(kind, name, source)
107
  prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
108
  encoded = tokenizer(
109
  prompt,
110
  return_tensors="pt",
111
  truncation=True,
112
+ max_length=int(os.getenv(max_length_env, "4096")),
113
+ )
114
+
115
+ device = getattr(model, "device", None)
116
+ if device is not None:
117
+ encoded = encoded.to(device)
118
 
119
  with torch.inference_mode():
120
  output_ids = model.generate(
 
128
  generated_ids = output_ids[encoded["input_ids"].shape[-1] :]
129
  text = tokenizer.decode(generated_ids, skip_special_tokens=True)
130
  return _clean_comment(text)
131
+
132
+
133
+ def generate_comment_with_base_model(kind: str, name: str, source: str) -> str:
134
+ tokenizer, model = _load_base_model()
135
+ return _generate_with_transformers(tokenizer, model, kind, name, source, "BTL_MODEL_CTX")
136
+
137
+
138
+ def generate_comment_with_tuned_model(kind: str, name: str, source: str) -> str:
139
+ tokenizer, model = _load_tuned_model()
140
+ return _generate_with_transformers(tokenizer, model, kind, name, source, "BTL_TUNED_MODEL_CTX")
pyproject.toml CHANGED
@@ -13,9 +13,9 @@ dependencies = [
13
  "bitsandbytes",
14
  "gradio==5.50.0",
15
  "huggingface_hub",
16
- "llama-cpp-python",
17
  "peft",
18
  "safetensors",
 
19
  "torch",
20
  "transformers @ git+https://github.com/huggingface/transformers.git",
21
  "truststore",
 
13
  "bitsandbytes",
14
  "gradio==5.50.0",
15
  "huggingface_hub",
 
16
  "peft",
17
  "safetensors",
18
+ "spaces",
19
  "torch",
20
  "transformers @ git+https://github.com/huggingface/transformers.git",
21
  "truststore",
requirements.txt CHANGED
@@ -1,11 +1,10 @@
1
  gradio==5.50.0
2
  huggingface_hub
3
- llama-cpp-python
4
- datasets==2.21.0
5
  truststore
6
  accelerate
7
  bitsandbytes
8
  peft
9
  safetensors
 
10
  torch
11
  git+https://github.com/huggingface/transformers.git
 
1
  gradio==5.50.0
2
  huggingface_hub
 
 
3
  truststore
4
  accelerate
5
  bitsandbytes
6
  peft
7
  safetensors
8
+ spaces
9
  torch
10
  git+https://github.com/huggingface/transformers.git