AfkaraLP commited on
Commit
ef83975
·
0 Parent(s):

RustLean FIM Space: cursor-aware Rust completion via llama-cpp-python + ZeroGPU

Browse files
Files changed (6) hide show
  1. .env.example +5 -0
  2. .gitignore +7 -0
  3. README.md +65 -0
  4. app.py +209 -0
  5. flake.nix +58 -0
  6. requirements.txt +4 -0
.env.example ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ HF_TOKEN=hf_xxx
2
+ # MODEL_REPO=AfkaraLP/rustlean-gguf
3
+ # MODEL_FILE=rustlean-final.Q8_0.gguf
4
+ # RUSTLEAN_CTX=8192
5
+ # N_GPU_LAYERS=-1
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ .env
3
+ __pycache__/
4
+ *.pyc
5
+ result
6
+ models/
7
+ *.gguf
README.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: RustLean FIM Playground
3
+ emoji: 🦀
4
+ colorFrom: indigo
5
+ colorTo: red
6
+ sdk: gradio
7
+ sdk_version: "4.44.1"
8
+ app_file: app.py
9
+ pinned: false
10
+ license: apache-2.0
11
+ models:
12
+ - AfkaraLP/rustlean-gguf
13
+ short_description: Cursor-aware Rust FIM code completion
14
+ ---
15
+
16
+ # RustLean FIM Playground
17
+
18
+ Cursor-aware Rust code completion using
19
+ [`AfkaraLP/rustlean-gguf`](https://huggingface.co/AfkaraLP/rustlean-gguf) — a
20
+ Qwen2.5-Coder-1.5B model fine-tuned for **fill-in-the-middle (FIM)** on Rust,
21
+ served via `llama-cpp-python`.
22
+
23
+ ## How it works
24
+
25
+ Place a **`<|cursor|>`** marker anywhere in the editor. The app splits the file
26
+ into a prefix and a suffix at that marker and builds the Qwen PSM prompt:
27
+
28
+ ```
29
+ <|fim_prefix|>{prefix}<|fim_suffix|>{suffix}<|fim_middle|>
30
+ ```
31
+
32
+ The generated middle is spliced back in. If there is no suffix, the model runs
33
+ in pure prefix-completion mode.
34
+
35
+ ## Hardware (ZeroGPU)
36
+
37
+ Set the Space hardware to **ZeroGPU** in *Settings → Hardware*.
38
+
39
+ ## Secrets
40
+
41
+ Add a Space **Secret** named `HF_TOKEN` with a read token
42
+ (<https://huggingface.co/settings/tokens>). The Space only needs it if the model
43
+ repo is private or to avoid rate limits while downloading the GGUF.
44
+
45
+ Optional environment overrides (Space Variables or local `.env`):
46
+
47
+ | Variable | Default | Meaning |
48
+ | ---------------- | -------------------------- | -------------------------------------- |
49
+ | `MODEL_REPO` | `AfkaraLP/rustlean-gguf` | HF repo id |
50
+ | `MODEL_FILE` | `rustlean-final.Q8_0.gguf` | GGUF filename |
51
+ | `RUSTLEAN_CTX` | `8192` | Context window |
52
+ | `N_GPU_LAYERS` | `-1` | `-1` offloads all layers to the GPU |
53
+
54
+ ## Local development (NixOS / CUDA)
55
+
56
+ This repo ships a `flake.nix` for a CUDA dev shell (built for the RTX 2070 Super,
57
+ compute capability `sm_75` / Turing):
58
+
59
+ ```bash
60
+ nix develop # sets up CUDA + builds llama-cpp-python with GGML_CUDA
61
+ cp .env.example .env && edit .env # put your HF_TOKEN here
62
+ python app.py
63
+ ```
64
+
65
+ On a 2070 Super expect ~40 tok/s decode (per the model card).
app.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from pathlib import Path
4
+
5
+
6
+ def _load_env(path: str = ".env"):
7
+ p = Path(path)
8
+ if not p.exists():
9
+ return
10
+ for line in p.read_text().splitlines():
11
+ line = line.strip()
12
+ if not line or line.startswith("#") or "=" not in line:
13
+ continue
14
+ k, v = line.split("=", 1)
15
+ os.environ.setdefault(k.strip(), v.strip())
16
+
17
+
18
+ _load_env()
19
+
20
+ import gradio as gr
21
+ from huggingface_hub import hf_hub_download
22
+ from llama_cpp import Llama
23
+
24
+ try:
25
+ import spaces
26
+ gpu = spaces.GPU
27
+ except Exception:
28
+ def gpu(*args, **kwargs):
29
+ def deco(fn):
30
+ return fn
31
+ return deco
32
+
33
+ MODEL_REPO = os.environ.get("MODEL_REPO", "AfkaraLP/rustlean-gguf")
34
+ MODEL_FILE = os.environ.get("MODEL_FILE", "rustlean-final.Q8_0.gguf")
35
+ CTX = int(os.environ.get("RUSTLEAN_CTX", "8192"))
36
+ N_GPU_LAYERS = int(os.environ.get("N_GPU_LAYERS", "-1"))
37
+
38
+ FIM_PREFIX = "<|fim_prefix|>"
39
+ FIM_SUFFIX = "<|fim_suffix|>"
40
+ FIM_MIDDLE = "<|fim_middle|>"
41
+ CURSOR = "<|cursor|>"
42
+ STOPS = ["<|endoftext|>", FIM_PREFIX, FIM_SUFFIX, FIM_MIDDLE, "<|fim_pad|>"]
43
+
44
+ DEFAULT_CODE = """use std::collections::HashMap;
45
+
46
+ fn word_count(text: &str) -> HashMap<&str, u32> {
47
+ let mut counts = HashMap::new();
48
+ <|cursor|>
49
+ counts
50
+ }
51
+ """
52
+
53
+ EXAMPLES = [
54
+ ("""fn fibonacci(n: u32) -> u32 {
55
+ match n {
56
+ 0 => 0,
57
+ 1 => 1,
58
+ <|cursor|>
59
+ }
60
+ }
61
+ """, 0.2, 96, 0.9),
62
+ ("""struct Point { x: f64, y: f64 }
63
+
64
+ impl Point {
65
+ fn distance(&self, other: &Point) -> f64 {
66
+ <|cursor|>
67
+ }
68
+ }
69
+ """, 0.2, 96, 0.9),
70
+ ("""fn is_prime(n: u64) -> bool {
71
+ if n < 2 {
72
+ return false;
73
+ }
74
+ <|cursor|>
75
+ }
76
+ """, 0.2, 128, 0.9),
77
+ ]
78
+
79
+ _llm = None
80
+ _model_path = None
81
+
82
+
83
+ def model_path():
84
+ global _model_path
85
+ if _model_path is None:
86
+ _model_path = hf_hub_download(
87
+ repo_id=MODEL_REPO,
88
+ filename=MODEL_FILE,
89
+ token=os.environ.get("HF_TOKEN"),
90
+ )
91
+ return _model_path
92
+
93
+
94
+ def get_llm():
95
+ global _llm
96
+ if _llm is None:
97
+ _llm = Llama(
98
+ model_path=model_path(),
99
+ n_gpu_layers=N_GPU_LAYERS,
100
+ n_ctx=CTX,
101
+ n_batch=512,
102
+ verbose=False,
103
+ logits_all=False,
104
+ )
105
+ return _llm
106
+
107
+
108
+ def build_prompt(prefix: str, suffix: str) -> str:
109
+ return f"{FIM_PREFIX}{prefix}{FIM_SUFFIX}{suffix}{FIM_MIDDLE}"
110
+
111
+
112
+ def clean(text: str) -> str:
113
+ for s in [FIM_MIDDLE, FIM_SUFFIX, FIM_PREFIX, "<|endoftext|>", "<|fim_pad|>"]:
114
+ text = text.replace(s, "")
115
+ return text.rstrip()
116
+
117
+
118
+ @gpu(duration=60)
119
+ def complete(code, temperature, max_tokens, top_p):
120
+ if code.strip() == "":
121
+ return "", code, "*Empty input.*"
122
+ if CURSOR in code:
123
+ prefix, suffix = code.split(CURSOR, 1)
124
+ else:
125
+ prefix, suffix = code, ""
126
+ prompt = build_prompt(prefix, suffix)
127
+
128
+ def run():
129
+ llm = get_llm()
130
+ return llm.create_completion(
131
+ prompt=prompt,
132
+ max_tokens=int(max_tokens),
133
+ temperature=float(temperature),
134
+ top_p=float(top_p),
135
+ stop=STOPS,
136
+ stream=False,
137
+ )
138
+
139
+ t0 = time.time()
140
+ try:
141
+ out = run()
142
+ except Exception:
143
+ global _llm
144
+ _llm = None
145
+ out = run()
146
+ dt = time.time() - t0
147
+
148
+ text = clean(out["choices"][0]["text"])
149
+ assembled = f"{prefix}{text}{suffix}"
150
+ n_tok = out.get("usage", {}).get("completion_tokens", 0)
151
+ tps = (n_tok / dt) if dt > 0 else 0.0
152
+ mode = "prefix" if suffix == "" else "FIM (prefix+suffix)"
153
+ stats = (
154
+ f"**{n_tok}** tokens · **{dt:.2f}s** · **{tps:.1f} tok/s**"
155
+ f" · mode: {mode}"
156
+ f" · gpu_layers: {N_GPU_LAYERS}"
157
+ )
158
+ return text, assembled, stats
159
+
160
+
161
+ with gr.Blocks(title="RustLean FIM Playground", theme=gr.themes.Soft()) as demo:
162
+ gr.Markdown(
163
+ "# 🦀 RustLean — Rust FIM Completion\n"
164
+ "Fill-in-the-middle completion from "
165
+ "[`AfkaraLP/rustlean-gguf`](https://huggingface.co/AfkaraLP/rustlean-gguf) "
166
+ "(Qwen2.5-Coder-1.5B, Q8_0). Put **`<|cursor|>`** where you want the model to "
167
+ "write code. Everything before it is the prefix, everything after is the suffix."
168
+ )
169
+
170
+ with gr.Row():
171
+ with gr.Column(scale=1):
172
+ code_in = gr.Code(
173
+ value=DEFAULT_CODE,
174
+ language="rust",
175
+ label="Rust source (mark the hole with <|cursor|>)",
176
+ interactive=True,
177
+ )
178
+ with gr.Accordion("Sampling", open=False):
179
+ temperature = gr.Slider(0.0, 1.5, value=0.2, step=0.05, label="temperature")
180
+ top_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="top_p")
181
+ max_tokens = gr.Slider(16, 256, value=96, step=8, label="max tokens")
182
+ with gr.Row():
183
+ btn = gr.Button("Complete at cursor", variant="primary")
184
+ clear = gr.Button("Reset")
185
+ with gr.Column(scale=1):
186
+ stats = gr.Markdown("_Not run yet._")
187
+ completion = gr.Code(
188
+ label="Generated middle",
189
+ language="rust",
190
+ interactive=False,
191
+ )
192
+ result = gr.Code(
193
+ label="Assembled result (prefix + middle + suffix)",
194
+ language="rust",
195
+ interactive=True,
196
+ )
197
+
198
+ gr.Examples(examples=EXAMPLES, inputs=[code_in, temperature, max_tokens, top_p])
199
+
200
+ btn.click(
201
+ complete,
202
+ inputs=[code_in, temperature, max_tokens, top_p],
203
+ outputs=[completion, result, stats],
204
+ )
205
+ clear.click(lambda: (DEFAULT_CODE, "", "", "_Reset._"), None,
206
+ outputs=[code_in, completion, result, stats])
207
+
208
+ if __name__ == "__main__":
209
+ demo.queue().launch()
flake.nix ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ description = "RustLean FIM Space — local dev shell (CUDA + llama-cpp-python) for RTX 2070 Super";
3
+
4
+ inputs = {
5
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
+ flake-utils.url = "github:numtide/flake-utils";
7
+ };
8
+
9
+ outputs = { self, nixpkgs, flake-utils }:
10
+ flake-utils.lib.eachDefaultSystem (system:
11
+ let
12
+ pkgs = import nixpkgs {
13
+ inherit system;
14
+ config.allowUnfree = true;
15
+ };
16
+ cuda = pkgs.cudaPackages;
17
+ py = pkgs.python312;
18
+ in {
19
+ devShells.default = pkgs.mkShell {
20
+ packages = [
21
+ py
22
+ py.pkgs.pip
23
+ py.pkgs.virtualenv
24
+ pkgs.cmake
25
+ pkgs.ninja
26
+ pkgs.gcc
27
+ pkgs.gnumake
28
+ pkgs.git
29
+ cuda.cudatoolkit
30
+ cuda.cudnn
31
+ ];
32
+
33
+ env = {
34
+ CUDA_HOME = "${cuda.cudatoolkit}";
35
+ CMAKE_ARGS = "-DGGML_CUDA=on -DCMAKE_CUDA_ARCHITECTURES=75";
36
+ };
37
+
38
+ shellHook = ''
39
+ export PATH="$CUDA_HOME/bin:$PATH"
40
+ export LD_LIBRARY_PATH="${cuda.cudatoolkit}/lib:${cuda.cudnn}/lib:$LD_LIBRARY_PATH"
41
+ export LIBRARY_PATH="${cuda.cudatoolkit}/lib:${cuda.cudnn}/lib:$LIBRARY_PATH"
42
+
43
+ if [ ! -d .venv ]; then
44
+ ${py}/bin/python -m venv .venv
45
+ ./.venv/bin/pip install --upgrade pip wheel setuptools
46
+ fi
47
+ source .venv/bin/activate
48
+
49
+ pip install -r requirements.txt
50
+ pip install --force-reinstall --no-binary llama-cpp-python llama-cpp-python
51
+
52
+ echo ""
53
+ echo "RustLean dev shell ready. RTX 2070 Super = sm_75 (Turing)."
54
+ echo "Put your token in .env (HF_TOKEN), then run: python app.py"
55
+ '';
56
+ };
57
+ });
58
+ }
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu124
2
+ gradio>=4.44.0
3
+ huggingface_hub>=0.25.0
4
+ llama-cpp-python>=0.3.1