Downloads · 30 days
23
14% of all-time downloads
Sakatepon/Brujula-150M-32K-chat
Brujula-150M-32K-chat is a text generation model from Sakatepon. Use it when you need the model to write or continue text. It is set up for transformers. The card lists the license as apache-2.0.
A ~150M-parameter (157M trainable, tied embeddings) DeepSeek-style decoder that runs a 32,768-token context window on a single consumer GPU, fine-tuned for chat and long-context retrieval. I'm not aware of a smaller m…
Downloads · 30 days
23
14% of all-time downloads
All-time downloads
159
Public
Parameters
157M
630 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors630 MB · 99%
From the Hugging Face model README
A ~150M-parameter (157M trainable, tied embeddings) DeepSeek-style decoder that runs a 32,768-token context window on a single consumer GPU, fine-tuned for chat and long-context retrieval. I'm not aware of a smaller model with a working 32K context — comparably-tiny models like SmolLM2-135M cap out at 8K, and the smallest mainstream 32K model (Qwen2.5-0.5B) is ~3× larger. (I haven't audited every model on the Hub, so this is not a claim to an absolute record — just that no smaller one is known to me.) It was built and trained end-to-end by one hobbyist on a single Intel Arc B580 (12 GB).
This is v1.0. It is good enough to be useful and, more importantly, small and cheap enough to fine-tune further yourself — that is the point of shipping it.
Honesty first: this is a 150M hobby model, not a Phi/SmolLM competitor. It chats, follows simple instructions, and retrieves facts from very long inputs, but it has the factual ceiling you'd expect at this size (see Limitations). Compare it to other consumer-GPU hobby projects, not to lab models.
This model uses custom modeling code, so pass trust_remote_code=True:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
repo = "Sakatepon/Brujula-150M-32K-chat"
tok = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
repo, trust_remote_code=True, dtype=torch.bfloat16
).to("cuda").eval() # "xpu" for Intel Arc; "cpu" works too
prompt = "User: Explain why the sky is blue in one paragraph.\nAssistant:"
ids = tok(prompt, return_tensors="pt").input_ids.to(model.device)
out = model.generate(
ids, max_new_tokens=256,
do_sample=True, temperature=0.8, top_p=0.95,
repetition_penalty=1.3, # important — see Limitations
use_cache=False, # this reference impl has no KV cache
pad_token_id=50256, eos_token_id=50256,
)
print(tok.decode(out[0], skip_special_tokens=True))
Chat format (plain, no special chat template):
User: <your message>
Assistant:
The model emits <|endoftext|> (id 50256) to end a turn; generation stops there.
Put the long document first, then ask at the end:
context = open("long_document.txt").read()
prompt = f"{context}\n\nUser: According to the document, who founded the company?\nAssistant:"
It accepts up to 32,768 tokens. Generation has no KV cache (the AttnRes aggregators mix across layers, which makes a per-step cache intricate), so it recomputes the context each step — correct but slow at long context. Short chats are fast.
This is the model's headline skill, but it is the one most people test wrong and walk away
disappointed. Don't probe retrieval with the User:/Assistant: chat format and sampling.
Two reasons it fails that way:
... IMPORTANT: the X is Y. Please remember it.)
and the query is a stem the model completes (Question: what is the X? Answer: the X is ).
Wrapping the whole haystack in User: ... Assistant: and asking conversationally is
out-of-distribution for the retrieval head — it reads as a chat and free-associates instead of
looking the fact up.do_sample=False). With the
chat defaults (temperature=0.8, top_p=0.95) the correct token is usually in the
distribution but you won't reliably draw it — sampling turns a hit into a coin-flip. Also drop
repetition_penalty here (it can push the model off the correct token) and keep
max_new_tokens small (~16) — you only need the fact echoed back.This is exactly the harness the model was evaluated with. Copy-paste it:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
repo = "Sakatepon/Brujula-150M-32K-chat"
tok = AutoTokenizer.from_pretrained(repo, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
repo, trust_remote_code=True, dtype=torch.bfloat16
).to("cuda").eval() # "xpu" for Intel Arc; "cpu" works too
NOISE = " The weather report mentioned clear skies and a gentle breeze across the quiet valley that morning."
def needle_test(label, secret, context_len=16384, depth=0.5):
"""Plant `secret` ~`depth` of the way into a `context_len`-token haystack, then ask for it."""
nz = tok(NOISE, add_special_tokens=False).input_ids
fact = tok(f" IMPORTANT: the {label} is {secret}. Please remember it.", add_special_tokens=False).input_ids
q = tok(f" Question: what is the {label}? Answer: the {label} is", add_special_tokens=False).input_ids
budget = max(0, context_len - len(fact) - len(q) - 2)
fill = lambda n: (nz * (n // len(nz) + 1))[:n]
nb = int(budget * depth)
ids = torch.tensor([fill(nb) + fact + fill(budget - nb) + q], device=model.device)
out = model.generate(
ids, max_new_tokens=16,
do_sample=False, # GREEDY — retrieval is a lookup, not a creative task
use_cache=False, # no KV cache in this reference impl
pad_token_id=50256, eos_token_id=50256,
)
answer = tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True)
print(f"[{label}] depth {depth:.0%} of {context_len:>6}: "
f"{'✓ FOUND' if secret in answer else '✗ miss'} -> {answer!r}")
needle_test("vault code", "MELON-7714", context_len=16384, depth=0.10)
needle_test("password", "river-galaxy-92", context_len=16384, depth=0.50)
needle_test("room number", "B-4417", context_len=32768, depth=0.70)
What to expect (own harness — single-needle, greedy):
... Answer: and let it continue) — just decode greedily. The chat persona is for short turns;
the retrieval head wants a continuation.repetition_penalty ≈ 1.3. Without it, it loops. This single knob was the
biggest lever on chat quality in testing.yarn_aggr (NTK-by-parts ramp β=16 + attention temperature
mscale = 0.1·ln(32768/1024)+1), applied training-free, then answer-masked SFT at 16K
(loss only on completion tokens) on a passkey/needle retrieval set to restore retrieval at the
extended length.smol-smoltalk, mixed with retrieval windows so the 32K skill
survives the chat tuning.Reported numbers (own harness; not directly comparable to public leaderboards): the pretrained backbone scores ≈ 19.5 val perplexity (FineWeb-Edu) / 32.7 on WikiText-103 — roughly GPT-2-small territory at this harness (GPT-2 small ≈ 29.3 WikiText here). Chat+retrieval SFT validation perplexity is 4.91 on the SFT mixture (a different distribution, not an LM benchmark — don't compare it to the backbone numbers).
That's the idea. It's small enough to fine-tune on a single GPU. Continue SFT on your own chat or
domain data, or do a document-QA round to push past the confabulation ceiling. The full training /
eval / context-extension code (the faro/Brújula stack) is a separate project — the modeling code
in this repo (modeling_brujula_v2.py, configuration_brujula_v2.py) is self-contained and
matches that trainer bit-for-bit (logits parity-checked < 1e-5 at build).
Apache-2.0.