This is the model card of a transformers model that has been pushed on the Hub. Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. Use the code below to get started with the model. Carbon emissions can be estimated using the Machine Learning Impact calculator presented in Lacoste et al. (2019).
Open-weight model · Text generation
gaggle-nanochat-pretrained-climbmix-20260924
by Castorini castorini/gaggle-nanochat-pretrained-climbmix-20260924
gaggle-nanochat-pretrained-climbmix-20260924 is an open-weight model for text generation from Castorini, released under MIT License. It has 3.3B parameters. At 16-bit it needs about 7.9 GB of GPU memory, which fits on 1x MI300X from $1.85 an hour, at the lowest prices in the SAVRN Index. It draws 252 downloads a month.
A 3.3B-parameter decoder-only base language model, pre-trained from scratch with nanochat on one pass over ClimbMix (287B tokens). This is the causal model described in the pre-training section of (PDF).
Runs On
What it takes to serve gaggle-nanochat-pretrained-climbmix-20260924 (3.3B parameters): the memory its weights need at each precision, and the cheapest way to rent enough data-center GPUs to hold them.
| Precision | Weights | Memory needed | Cheapest setup | Per hour | Also fits |
|---|---|---|---|---|---|
| 16-bit | 6.6 GB | 7.9 GB | 1x MI300X (192 GB) Vultr |
$1.85 | 1x H100 $1.99 · 1x MI325X $2.00 |
| 8-bit | 3.3 GB | 3.9 GB | 1x MI300X (192 GB) Vultr |
$1.85 | 1x H100 $1.99 · 1x MI325X $2.00 |
| 4-bit | 1.6 GB | 2.0 GB | 1x MI300X (192 GB) Vultr |
$1.85 | 1x H100 $1.99 · 1x MI325X $2.00 |
Memory is the weights at that precision plus 20% for the runtime and a short context; a long context needs more. Prices are the lowest on-demand hourly rates in the SAVRN Index, read Oct 18, 2026.
Model Card
By Castorini, published under mit, revision 87d51f06df2b.
A 3.3B-parameter decoder-only base language model, pre-trained from scratch with nanochat on one pass over ClimbMix (287B tokens). This is the causal model described in the pre-training section of (PDF). It has had no post-training (no instruction tuning or RLHF), so it continues text rather than following instructions. The same weights load three ways: with transformers (trustremotecode=True), with vLLM (through a small plugin), and with nanochat's own code from the original checkpoint files, which are unchanged. one pass of 287B tokens. The last of its 6,543 shards is held out for validation. Documents are packed into 2,048-token sequences with nanochat's BOS-aligned best-fit packing.…
Read Castorini's full model card
A 3.3B-parameter decoder-only base language model, pre-trained from scratch with nanochat on one pass over ClimbMix (287B tokens). This is the causal model described in the pre-training section of Project Greenhouse: Progress Toward Fully Open and Sovereign Agentic Search (PDF). It has had no post-training (no instruction tuning or RLHF), so it continues text rather than following instructions.
The same weights load three ways: with transformers (trust_remote_code=True), with vLLM (through a small
plugin), and with nanochat's own code from the original checkpoint files, which are unchanged.
Model
| Architecture | nanochat depth-34 decoder-only transformer, unmodified: 34 layers, width 2,176, 17 attention heads of dimension 128, 2,048-token context |
| Components | rotary position embeddings, QK normalization, parameter-free RMSNorm, squared-ReLU MLP, untied input/output embeddings, logit soft-capping; a value embedding on every other layer; 3 of every 4 layers use a 512-token sliding window |
| Parameters | 3.29B: 1.93B in the transformer blocks, 1.21B in the value-embedding tables, 0.14B in the input and output embeddings. Value embeddings are lookups, so per-token compute is that of a dense model of about 2B parameters. |
| Tokenizer | byte-level BPE, 32,768 entries, trained on the first 2B characters of the training split |
| Code | karpathy/nanochat at commit 92d63d4 |
Training
- Data: ClimbMix (
karpathy/climbmix-400b-shuffle), one pass of 287B tokens. The last of its 6,543 shards is held out for validation. Documents are packed into 2,048-token sequences with nanochat's BOS-aligned best-fit packing. - Optimizer and schedule: nanochat's, unchanged. Muon updates the weight matrices of the transformer blocks and AdamW the embeddings and scalar parameters; the learning rate warms up briefly, stays constant, and then decays linearly.
- Batch size: our only change. The batch grows from 262,144 to 2,097,152 tokens over training. Unlike nanochat, which scales its learning rates with the square root of the batch size, we keep their peak values unchanged as the batch grows.
- Compute: one node of eight NVIDIA H100 GPUs with FP8 matrix multiplications, about 1,600 H100-hours.
- This checkpoint: step 227,158, after 283B tokens. It is the checkpoint with the highest CORE measured during training; the final checkpoint (287B tokens) scored 0.004 lower.
Evaluation
Base-model benchmarks that need no instruction following: CORE (nanochat's average centered accuracy over 22 in-context learning tasks, 500 examples each), and 5-shot MMLU and MMLU-Pro scored by the log-likelihood of the answer letter with the LM Evaluation Harness.
| Model | Tokens | CORE | MMLU | MMLU-Pro |
|---|---|---|---|---|
| This model | 283B | 0.458 | 0.493 | 0.178 |
| nanochat d34 | 89B | 0.338 | 0.257 | 0.115 |
All numbers are from our evaluation except nanochat d34's CORE, which is the published number.
Validation loss on the held-out ClimbMix shard is 0.596 bits per byte.
Usage
Sequences must start with the BOS token <|bos|>, as in training. The transformers tokenizer adds it for you
(add_special_tokens=True, the default), and so does vLLM.
transformers
pip install "transformers>=4.57" "torch>=2.5"
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
repo = "castorini/gaggle-nanochat-pretrained-climbmix-20260924"
tokenizer = AutoTokenizer.from_pretrained(repo)
model = AutoModelForCausalLM.from_pretrained(repo, trust_remote_code=True, dtype=torch.bfloat16).to("cuda").eval()
inputs = tokenizer("The capital of France is", return_tensors="pt").to("cuda") # starts with <|bos|>
out = model.generate(**inputs, max_new_tokens=20, do_sample=False)
print(tokenizer.decode(out[0], skip_special_tokens=True))
Output on one H200 GPU:
The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France
Log-likelihood of a continuation, as in the nanochat example below:
@torch.inference_mode()
def continuation_logprob(context, continuation):
ctx = tokenizer(context)["input_ids"] # starts with <|bos|>
cont = tokenizer(continuation, add_special_tokens=False)["input_ids"]
ids = torch.tensor([ctx + cont], device="cuda")
logp = torch.log_softmax(model(ids).logits[0, len(ctx) - 1:-1], -1)
return logp[torch.arange(len(cont)), torch.tensor(cont, device="cuda")].sum().item()
print(continuation_logprob("The capital of France is", " Paris")) # -0.9538
print(continuation_logprob("The capital of France is", " Berlin")) # -7.7336
These can differ from the nanochat example's printout in the last digits: nanochat runs attention with Flash
Attention 3 on Hopper GPUs when that kernel is available, while this port always uses PyTorch's
scaled_dot_product_attention, which is nanochat's own fallback and the kernel all our evaluations ran with. With the
same kernel the two produce bit-identical logits (see below); across kernels, bf16 rounding moves log-probabilities
slightly.
trust_remote_code=True is needed because the architecture is nanochat's rather than one transformers ships;
configuration_gaggle.py and modeling_gaggle.py in this repo define it. They are plain PyTorch, import nothing
beyond torch and transformers, and are short enough to read first. No nanochat checkout and no Flash Attention
build are involved. model(input_ids).logits are fp32 and already soft-capped, as nanochat returns them; passing
labels=input_ids gives the usual next-token loss. Batches may be padded on either side.
With the LM Evaluation Harness, pass add_bos_token=True (it does not add special tokens by default):
lm_eval --model hf --model_args pretrained=castorini/gaggle-nanochat-pretrained-climbmix-20260924,trust_remote_code=True,dtype=bfloat16,add_bos_token=True --tasks mmlu --num_fewshot 5
vLLM
vLLM has no built-in class for this architecture, so install the small plugin that provides one; vLLM then finds it automatically:
pip install vllm https://huggingface.co/castorini/gaggle-nanochat-pretrained-climbmix-20260924/resolve/main/vllm_gaggle-0.1.0-py3-none-any.whl
vllm serve castorini/gaggle-nanochat-pretrained-climbmix-20260924 --trust-remote-code
from vllm import LLM, SamplingParams
llm = LLM("castorini/gaggle-nanochat-pretrained-climbmix-20260924", trust_remote_code=True)
out = llm.generate(["The capital of France is"], SamplingParams(max_tokens=20, temperature=0))
print(out[0].outputs[0].text)
Output on one H200 GPU:
Paris. The capital of France is Paris. The capital of France is Paris. The capital of France
The plugin (vllm_plugin/ in this repo; one model file of about 230 lines) registers the architecture through
vLLM's vllm.general_plugins entry point, so every vLLM process finds it; nothing in vLLM itself is changed. It
reads the same config.json and safetensors as transformers. Every nanochat component maps onto ordinary tensor
operations except the embedding smear, which mixes the previous token's embedding into each position: while
decoding, vLLM gives the model only the newest token, so the port recovers the previous embedding from the cache
with one extra attention layer whose two-token window is set up to select the previous position exactly. It runs
on one GPU (no tensor parallelism) with the model's 2,048-token context, with or without CUDA graphs and
torch.compile. On one H200, 256 prompts x 256 new tokens run at about 13,000 tokens/s, three times
transformers generate() on the same batch.
nanochat
The original training checkpoint is still here, unchanged (model_227158.pt, meta_227158.json, tokenizer/),
so nanochat's own code loads it exactly as before. Set up nanochat at the commit the model was trained with:
git clone https://github.com/karpathy/nanochat.git
cd nanochat && git checkout 92d63d4
uv sync --extra gpu # use --extra cpu on a machine without a CUDA GPU
Save the following as example.py in the nanochat repository root (nanochat is imported from there, not
installed as a package) and run uv run --extra gpu --with huggingface_hub python example.py.
import os, torch
from huggingface_hub import snapshot_download
# only the nanochat files; the safetensors are for transformers and vLLM
path = snapshot_download("castorini/gaggle-nanochat-pretrained-climbmix-20260924",
allow_patterns=["model_227158.pt", "meta_227158.json", "tokenizer/*"])
os.environ["NANOCHAT_BASE_DIR"] = path # nanochat loads the tokenizer from $NANOCHAT_BASE_DIR/tokenizer
from nanochat.checkpoint_manager import build_model
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model, tokenizer, meta = build_model(path, step=227158, device=device, phase="eval")
# Greedy continuation
prompt = "The capital of France is"
ids = tokenizer.encode(prompt, prepend=tokenizer.get_bos_token_id())
new = list(model.generate(ids, max_tokens=20, temperature=0.0))
print(prompt + tokenizer.decode(new))
# Log-likelihood of a continuation given a context
@torch.inference_mode()
def continuation_logprob(context, continuation):
ctx = tokenizer.encode(context, prepend=tokenizer.get_bos_token_id())
cont = tokenizer.encode(continuation)
x = torch.tensor([ctx + cont[:-1]], device=device)
y = torch.tensor([[-1] * (len(ctx) - 1) + cont], device=device) # -1 = ignore context positions
return -model(x, targets=y, loss_reduction="sum").item()
print(continuation_logprob("The capital of France is", " Paris"))
print(continuation_logprob("The capital of France is", " Berlin"))
Output on one H200 GPU:
The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France
-0.9807320833206177
-7.733334541320801
Notes:
- On GPUs with bfloat16 support the model computes in bfloat16; on CPU and MPS it uses float32. Override with
NANOCHAT_DTYPE=bfloat16|float16|float32. - For further pre-training or fine-tuning, use nanochat's training scripts (
scripts/base_train.py,scripts/chat_sft.py) with this checkpoint as the starting point.
The three implementations agree
All three load the same tensors. We checked them against nanochat's own forward pass on the original checkpoint, which is what every number in the Evaluation section was computed with.
| Check | Result |
|---|---|
transformers vs nanochat, bf16 on an H200: logits of real text at 2 to 2,048 tokens |
bitwise identical |
| same, a right-padded batch vs nanochat on the same batch | bitwise identical |
| same, fp32 on CPU | bitwise identical |
| loss on a 2,048-token sequence | identical (2.2183) |
| greedy generation, 5 prompts x 48 tokens, with and without the KV cache | identical token for token |
| tokenizer vs nanochat's, 400 documents | identical ids, identical decoding |
transformers 4.57 vs 5.17 |
bitwise identical logits |
CORE / MMLU / MMLU-Pro run through transformers |
0.458 / 0.493 / 0.178, as in the table above |
| MMLU with the stock LM Evaluation Harness (no project code) | 0.493 |
vLLM vs transformers, fp32: per-token log-probabilities |
mean difference 0.0009 nats; all 5 greedy generations identical |
vLLM vs transformers, bf16 |
mean difference 0.016 nats, the same size as transformers' own bf16 rounding between a single sequence and a padded batch (0.013-0.026) |
In bf16, any change of kernel shape — batching, padding, a KV cache, a different attention kernel — moves logits by
rounding, which can flip near-tied greedy choices after a few dozen tokens; nanochat itself behaves the same way.
convert_nanochat_to_hf.py is the converter that produced the safetensors from
model_227158.pt.
Files
model-0000{1,2,3}-of-00003.safetensors,model.safetensors.index.json: the weights fortransformersand vLLM. These are the tensors ofmodel_227158.pt, unchanged in value and dtype (fp32 matrices, bf16 embedding tables), renamed tomodel.<nanochat name>(lm_head.weightkeeps its name).config.json,generation_config.json,configuration_gaggle.py,modeling_gaggle.py: the architecture fortransformers(and the configuration vLLM reads).tokenizer.json,tokenizer_config.json: the tokenizer, converted fromtokenizer/tokenizer.pkl; it encodes every text exactly as nanochat does and prepends<|bos|>.model_227158.pt: model weights (nanochat state dict), for nanochat.meta_227158.json: model configuration, training configuration, and validation loss.tokenizer/: the 32,768-entry tokenizer in nanochat's format (tokenizer.pkl,token_bytes.pt).SHA256SUMS: checksums of the weight and metadata files.
Optimizer state is not included.
Related
castorini/gaggle-nanochat-pretrained-climbmix-bidirectional-20260929: the bidirectional variant, converted from this run's causal checkpoint at 245B tokens and trained on the remaining 42B tokens with bidirectional attention and masked next-token prediction, following LLM2Vec.castorini/gaggle-reranker-20261005: the pointwise reranker fine-tuned from this checkpoint.
Citation
If you use this checkpoint, please cite our paper:
@misc{lin2026projectgreenhouseprogressfully,
title={{Project Greenhouse}: Progress Toward Fully Open and Sovereign Agentic Search},
author={Jimmy Lin and Sahel Sharifymoghaddam and Lingwei Gu and Nour Jedidi},
year={2026},
eprint={2610.11922},
archivePrefix={arXiv},
primaryClass={cs.IR},
url={https://arxiv.org/abs/2610.11922}
}
Configuration
- Architecture
- GaggleForCausalLM
- Vocabulary size
- 32,768
- RoPE base
- 100000
- Model type
- gaggle
Identity and Version
- Repository
- castorini/gaggle-nanochat-pretrained-climbmix-20260924
- Publisher
- Castorini
- Task
- Text generation
- Modality
- Text
- Library
- transformers
- Parameters
- 3.3B parameters
- Languages
- en
- Revision
- 87d51f06df2bbe0b631beb5d94e3fa9063e6cb70
- First published
- 2026-09-25
- Last updated
- 2026-10-16
Files and Weights
22 files, 21.2 GB in total. The weights are 5 files totalling 21.2 GB in pt, safetensors.
Every file
| File | Type | Size | SHA-256 |
|---|---|---|---|
| model-00001-of-00003.safetensors | Weights | 5.0 GB | 168812c1affb |
| model-00002-of-00003.safetensors | Weights | 4.9 GB | 9ba8aa83f47d |
| model-00003-of-00003.safetensors | Weights | 713.0 MB | bcf63426fff7 |
| model_227158.pt | Weights | 10.6 GB | 0d314c2a3441 |
| tokenizer/token_bytes.pt | Weights | 132.6 KB | 0bae8cf2ec0d |
| config.json | Configuration | 854 B | — |
| configuration_gaggle.py | Configuration | 3.9 KB | — |
| convert_nanochat_to_hf.py | Configuration | 6.3 KB | — |
| generation_config.json | Configuration | 95 B | — |
| meta_227158.json | Configuration | 3.7 KB | — |
| model.safetensors.index.json | Configuration | 20.0 KB | — |
| modeling_gaggle.py | Configuration | 19.0 KB | — |
| vllm_plugin/vllm_gaggle/__init__.py | Configuration | 622 B | — |
| vllm_plugin/vllm_gaggle/model.py | Configuration | 12.4 KB | — |
| README.md | Documentation | 14.5 KB | — |
| SHA256SUMS | Other | 1.6 KB | — |
| vllm_gaggle-0.1.0-py3-none-any.whl | Other | 5.8 KB | — |
| vllm_plugin/pyproject.toml | Other | 461 B | — |
| .gitattributes | Repository | 1.5 KB | — |
| tokenizer.json | Tokenizer | 2.9 MB | — |
| tokenizer/tokenizer.pkl | Tokenizer | 412.1 KB | ae73c5f7a960 |
| tokenizer_config.json | Tokenizer | 312 B | — |
License and Download
- License
- mit
- Access
- Open weights, no gate
- Download size
- 21.2 GB
Released by Castorini through its official repository on Hugging Face. Read the license.
Built From
- Described by arXiv:2610.11922
- Trained on (disclosed) karpathy/climbmix-400b-shuffle
Memory Requirements
| Precision | Weights in memory |
|---|---|
| As published | 21.2 GB |
| 16-bit | 6.6 GB |
| 8-bit | 3.3 GB |
| 4-bit | 1.6 GB |
Weights only, from the published parameter count; the key-value cache and runtime add to this.
Questions About gaggle-nanochat-pretrained-climbmix-20260924
How much GPU memory does gaggle-nanochat-pretrained-climbmix-20260924 need?
About 7.9 GB at 16-bit and 2 GB at 4-bit: the weights (3.3B parameters) plus a working margin. A long context needs more.
What is the cheapest GPU to run gaggle-nanochat-pretrained-climbmix-20260924 on?
At 16-bit, 1x MI300X from $1.85 an hour; at 4-bit, 1x MI300X from $1.85 an hour, at the lowest on-demand prices the SAVRN Index lists.
Can I use gaggle-nanochat-pretrained-climbmix-20260924 commercially?
Yes. gaggle-nanochat-pretrained-climbmix-20260924 is released under MIT License. The MIT License is a short permissive license. It permits commercial use, modification and redistribution, provided the copyright notice and permission notice are included.
Similar Models
The Prism Roleplay 1.5 Small recipe on PrismML's 1-bit Bonsai-8B: a standalone model, faster and smaller than the 4-bit original, with a small quality gap A standalone model, not an adapter: load it directly with mlx-lm. It is Prism Roleplay 1.5 Small's training recipe (same data, hyperparameters and system prompt) applied to prism-ml/Bonsai-8B-mlx-1bit, a 1-bit (g128) build of Qwen3-8B. How the LoRA was merged into a 1-bit model. A plain merge re-quantizes every layer to 1 bit, and the small LoRA update disappears in the rounding (we measured it: merged held-out loss 3.415 = untuned base 3.405). So the 16 layers the LoRA touched (all their attention and MLP projections) are merged at fp16…
This is a MarinSkyRL-native Open-MOPD student after 32 optimizer steps. It starts from the authors' mixed-domain SFT checkpoint. Student responses were scored by the authors' math, code, and instruction-following RL teachers, routed by domain. The objective uses the student's selected top-16 token IDs and a clipped policy surrogate. This is an early checkpoint, not the authors' step-200 final model. The checkpoint is an unquantized, six-file Hugging Face export of the durable MarinSkyRL globalstep32 FSDP2 checkpoint. The policy export was used for the independent step-32 evaluation. The export's model.safetensors SHA-256 is bb7326640142069bc2e1fba5f54f15e0cccb1ff861f34f318b372eaab7abaf4b.…
The Llama 3.2 collection of multilingual large language models (LLMs) is a collection of pretrained and instruction-tuned generative models in 1B and 3B sizes (text in/text out). The Llama 3.2 instruction-tuned text only models are optimized for multilingual dialogue use cases, including agentic retrieval and summarization tasks. They outperform many of the available open source and closed chat models on common industry benchmarks. Model Architecture: Llama 3.2 is an auto-regressive language model that uses an optimized transformer architecture. The tuned versions use supervised fine-tuning (SFT) and reinforcement learning with human feedback (RLHF) to align with human preferences for…
This llama model was trained 2x faster with Unsloth and Huggingface's TRL library.
This llama model was trained 2x faster with Unsloth and Huggingface's TRL library.