Use Transformers runtime for Space inference
#5
by coolbeanz79 - opened
- README.md +2 -3
- app.py +12 -0
- btl/model.py +43 -61
- pyproject.toml +1 -1
- 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
|
| 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
|
| 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
|
| 22 |
try:
|
| 23 |
-
|
|
|
|
| 24 |
except ImportError as exc:
|
| 25 |
raise ModelUnavailableError(
|
| 26 |
-
"
|
| 27 |
) from exc
|
| 28 |
|
| 29 |
-
|
| 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 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 83 |
) from exc
|
| 84 |
|
| 85 |
-
model_name = os.getenv("BTL_TUNED_BASE_MODEL", DEFAULT_BASE_TRANSFORMERS_MODEL)
|
| 86 |
try:
|
| 87 |
-
tokenizer =
|
| 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 |
-
|
| 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(
|
| 145 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|