GGUF
English
llama.cpp
decision-model
system-one
typed-decisions
calibrated-probabilities
ainode
conversational
Instructions to use frontier-infra/jebadiah-4b-v2-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use frontier-infra/jebadiah-4b-v2-GGUF with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Use Docker
docker model run hf.co/frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
- LM Studio
- Jan
- Ollama
How to use frontier-infra/jebadiah-4b-v2-GGUF with Ollama:
ollama run hf.co/frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
- Unsloth Desktop
- Pi
How to use frontier-infra/jebadiah-4b-v2-GGUF with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use frontier-infra/jebadiah-4b-v2-GGUF with Docker Model Runner:
docker model run hf.co/frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
- Lemonade
How to use frontier-infra/jebadiah-4b-v2-GGUF with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Run and chat with the model
lemonade run user.jebadiah-4b-v2-GGUF-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use frontier-infra/jebadiah-4b-v2-GGUF with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use frontier-infra/jebadiah-4b-v2-GGUF with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
File size: 10,576 Bytes
0fda8e6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | """The Jebadiah prompt: AINode's own /v1/systemone -> /v1/decide rendering, then the base model's
chat template, then ONE label token. Train, eval and the local server all go through here, and
the served route (AINode) renders the same bytes because the renderer is AINode's, copied
verbatim (ainode_prompt_verbatim.py, hashed into the training config).
A question is the Jev wire shape {type, instructions, criteria}. AINode translates it to a list of
option lines with letter labels (A..Z, AA..) and the wire names each label answers to:
choice -> the criteria keys, in criteria order (or the order the caller passes)
noul -> ["true", "false"] (always this order, AINode's rule)
score -> the level texts in rubric order; we report them as level indices "0".."k-1"
The answer position is the token right after the generation prompt, and the candidate tokens are
the label strings themselves ("A", "B", ...), each one ordinary token at that boundary (checked
against the tokenizer at load time).
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from ainode_prompt_verbatim import (
MAX_CRITERIA,
PROMPT_SOURCE_COMMIT,
PROMPT_SOURCE_SHA256,
Translated,
build_messages,
criteria_pairs,
option_label,
option_text,
serialize_state,
translate_one,
)
# Keys that must never appear inside a question object sent to the model. The linter rejects
# them; the server refuses them.
LABEL_KEYS = {"label", "labels", "expected", "passingAnswer", "passing_answer", "answer", "answers",
"target", "targets", "gold", "reference", "truth", "correct"}
CHAT_TEMPLATE_KWARGS = {"add_generation_prompt": True, "enable_thinking": False, "thinking": False}
def flatten_text(v) -> str:
"""Instructions and descriptions must be strings on AINode's wire. Sources that ship an object
or a list (Kev's {question, focus}) are flattened once, at conversion time, by joining the
string values with a space. Never called on a state."""
if v is None:
return ""
if isinstance(v, str):
return v.strip()
if isinstance(v, dict):
return " ".join(s for s in (flatten_text(x) for x in v.values()) if s)
if isinstance(v, list):
return " ".join(s for s in (flatten_text(x) for x in v) if s)
return json.dumps(v, ensure_ascii=False)
def wire_keys(q: dict, order: list | None = None) -> list[str]:
"""The answer keys of a question in the order the options are shown."""
t = q["type"]
crit = q.get("criteria")
if t == "choice":
keys = list(crit) if isinstance(crit, dict) else [str(c) for c in crit]
if order is not None:
assert sorted(order) == sorted(keys), "order must be a permutation of the option keys"
keys = list(order)
return keys
if t == "noul":
return ["true", "false"]
if t == "score":
return [str(i) for i in range(len(crit))]
raise ValueError(f"unknown question type {t!r}")
def reorder_choice(q: dict, order: list) -> dict:
"""The same choice question with its criteria in `order` (AINode shows criteria order)."""
crit = q["criteria"]
if not isinstance(crit, dict):
crit = {str(c): None for c in crit}
return {**q, "criteria": {k: crit[k] for k in order}}
@dataclass
class Rendered:
prompt: str # the chat-templated text ending in the generation prompt
keys: list[str] # wire keys in display order
letters: list[str] # the label shown for each key, same order
cand_ids: list[int] # token id of each label at the answer boundary, same order
truncated: bool # the state was cut to fit the token budget
label_scheme: str = "ainode" # "ainode" (option_label, what /v1/decide emits) or "extended"
class Renderer:
"""Renders (state, question) exactly as AINode's /v1/systemone would, with this tokenizer's
chat template, and knows the label token ids."""
def __init__(self, tokenizer, max_tokens: int = 2048):
self.tok = tokenizer
self.max_tokens = max_tokens
self._label_ids: dict[str, int] = {}
# every label AINode can emit up to 255 options must be one ordinary token at the boundary
probe = self.render_messages("x", "q?", ["o1", "o2"])
base = tokenizer.encode(probe, add_special_tokens=False)
specials = set(tokenizer.all_special_ids)
for i in range(255):
lab = option_label(i)
comb = tokenizer.encode(probe + lab, add_special_tokens=False)
suffix = comb[len(base):]
if comb[:len(base)] != base or len(suffix) != 1 or suffix[0] in specials:
break
self._label_ids[lab] = suffix[0]
self.max_options = len(self._label_ids)
if self.max_options < 26:
raise ValueError(f"only {self.max_options} labels are single tokens in this tokenizer")
# Past AINode's single-token range (68 labels on the Qwen3.5 tokenizer: A..Z, AA..AZ,
# BA..BP) the served route cannot answer anyway (its cap is 20). For the LOCAL read of a
# wider question (Jevals Banking77, 77 options) we fall back to an extended alphabet: the
# same A..Z, then every two-letter uppercase string that is one ordinary token at the
# boundary, in alphabetical order. Rendered.label_scheme says which alphabet was used.
import string
self._extended: list[tuple[str, int]] = [(L, i) for L, i in self._label_ids.items() if len(L) == 1]
for a in string.ascii_uppercase:
for b in string.ascii_uppercase:
lab = a + b
comb = tokenizer.encode(probe + lab, add_special_tokens=False)
suffix = comb[len(base):]
if comb[:len(base)] == base and len(suffix) == 1 and suffix[0] not in specials:
self._extended.append((lab, suffix[0]))
self.max_options_extended = len(self._extended)
def render_messages(self, state_text: str, question: str, options: list[str],
letters: list[str] | None = None) -> str:
messages = build_messages(state_text, None, question, options)
if letters is not None:
# the extended alphabet: swap AINode's letters for ours in the option lines, nothing else
user = messages[1]["content"]
head, _, rest = user.partition("\nOPTIONS:\n")
lines = rest.split("\n")
for i in range(len(options)):
assert lines[i].startswith(f"{option_label(i)}. ")
lines[i] = f"{letters[i]}. " + lines[i][len(option_label(i)) + 2:]
messages[1]["content"] = head + "\nOPTIONS:\n" + "\n".join(lines)
return self.tok.apply_chat_template(messages, tokenize=False, **CHAT_TEMPLATE_KWARGS)
def render(self, state, q: dict, order: list | None = None) -> Rendered:
if q["type"] == "choice" and order is not None:
q = reorder_choice(q, order)
crit = q.get("criteria")
if q["type"] == "choice" and isinstance(crit, dict) and len(crit) > MAX_CRITERIA:
# AINode's route refuses this many options (its top-20 logprob read); the local read
# renders them the same way and scores every label directly off the logits
pairs = criteria_pairs("q", crit, "choice")
t = Translated("choice", str(q.get("instructions", "")).strip(),
[option_text(n, d) for n, d in pairs], [n for n, _ in pairs])
else:
t = translate_one("q", q)
keys = wire_keys(q, order)
n_opt = len(t.names)
scheme = "ainode"
if n_opt <= self.max_options:
letters = [option_label(i) for i in range(n_opt)]
cand_ids = [self._label_ids[L] for L in letters]
shown = None
elif n_opt <= self.max_options_extended:
letters = [L for L, _ in self._extended[:n_opt]]
cand_ids = [i for _, i in self._extended[:n_opt]]
shown = letters
scheme = "extended"
else:
raise ValueError(f"{n_opt} options exceed the {self.max_options_extended} single-token labels")
state_text = serialize_state(state)
prompt = self.render_messages(state_text, t.question, t.options, shown)
truncated = False
n = len(self.tok.encode(prompt, add_special_tokens=False))
if n > self.max_tokens:
# cut the state from its end so the question, options and template survive intact
overhead = n - len(self.tok.encode(state_text, add_special_tokens=False))
keep = max(self.max_tokens - overhead - 4, 16)
ids = self.tok.encode(state_text, add_special_tokens=False)[:keep]
state_text = self.tok.decode(ids) + " [truncated]"
prompt = self.render_messages(state_text, t.question, t.options, shown)
truncated = True
return Rendered(prompt, keys, letters, cand_ids, truncated, scheme)
def answer_from_probs(q: dict, keys: list[str], probs: list[float]) -> dict:
"""The Jev wire answer for one question from its probabilities over `keys` (display order).
choice: {type, choice, confidence, probabilities}; noul: {type, noul}; score: {type, score,
confidence, legend, probabilities}. Ties: choice -> the option listed first; score -> the
lower level (the Jevals rule). Confidence is (p_max - 1/n) / (1 - 1/n), the chance-corrected
top probability AINode's route also reports."""
t = q["type"]
if t == "noul":
return {"type": "noul", "noul": round(float(probs[keys.index("true")]), 6)}
best = 0
for i, p in enumerate(probs):
if p > probs[best]:
best = i
n = len(keys)
p_max = float(probs[best])
confidence = 1.0 if n == 1 else (p_max - 1.0 / n) / (1.0 - 1.0 / n)
if t == "choice":
return {"type": "choice", "choice": keys[best], "confidence": round(confidence, 6),
"probabilities": {k: round(float(p), 6) for k, p in zip(keys, probs)}}
expected = sum(int(k) * float(p) for k, p in zip(keys, probs))
crit = q.get("criteria") or []
legend = {str(i): (c if isinstance(c, str) else str(c)) for i, c in enumerate(crit)} if isinstance(crit, list) \
else {str(i): k for i, k in enumerate(crit)}
return {"type": "score", "score": round(expected, 6), "confidence": round(confidence, 6),
"legend": legend, "probabilities": {k: round(float(p), 6) for k, p in zip(keys, probs)}}
|