---
language:
- en
license: apache-2.0
library_name: transformers
tags:
- text-generation
- llama
- small-language-model
- efficient
- edge-deployment
- speculative-decoding
- 150m-parameters
- tpu-trained
- research
- low-resource
- portimbria
- gqa
- fineweb
pipeline_tag: text-generation
datasets:
- epfml/FineWeb-HQ
- HuggingFaceTB/finemath
- bigcode/starcoderdata
thumbnail: https://huggingface.co/StentorLabs/Portimbria-150M/resolve/main/thumbnail.png
widget:
- text: The history of artificial intelligence began
example_title: History Continuation
- text: "def quicksort(arr):"
example_title: Code Continuation
- text: Once upon a time in a distant kingdom
example_title: Story Generation
- text: The laws of thermodynamics describe
example_title: Science Continuation
- text: Neural networks are computational models that
example_title: Technical Explanation
model_card_authors:
- StentorLabs
model-index:
- name: Portimbria-150M
results:
- task:
type: text-generation
dataset:
name: FineWeb-HQ (validation split)
type: epfml/FineWeb-HQ
metrics:
- name: Best Validation Loss
type: loss
value: 2.8906
- name: Best Perplexity
type: perplexity
value: 18.00
---
# Portimbria-150M







[](https://huggingface.co/StentorLabs)
> ๐ฌ **Research Artifact & Base Language Model.** Portimbria-150M is a next-token predictor โ not a chat assistant. It has no safety tuning and should not be deployed in user-facing applications without fine-tuning first. It is, however, a high-quality open foundation: **fine-tune it, quantize it, convert it, distill from it, run LoRA on it, adapt it to your domain, or build anything else you can imagine** โ and please publish your results! See [Intended Uses](#use-cases--intended-uses) for details.
> ๐ก **Built by a solo 14-year-old developer, on a laptop, for $0.** Every model StentorLabs has released โ including this one โ was conceived, designed, coded, and trained without a budget, a team, a GPU cluster, or institutional support. The total cost of producing Portimbria-150M was zero dollars, using free Kaggle TPU quota and publicly available datasets. This is what democratized AI research looks like.
---
## What Is This?
**Portimbria-150M** is the first 150M-parameter model from StentorLabs and the inaugural entry in the **Portimbria** model family โ a new scaling tier above the Stentor2 line. The name is a deliberate rearrangement of *Portia fimbriata*, a jumping spider famous for being extraordinarily intelligent relative to its tiny body size. That tension โ compact but capable โ is the design philosophy of this model family.
At ~151M parameters, Portimbria-150M is a base causal language model trained entirely from scratch on free-tier Kaggle compute using a Google Cloud TPU v5e-8 (eight chips). It was trained on approximately **6 billion tokens** drawn from a web/code/math curriculum, with a 4096-token context window โ the longest in the StentorLabs model lineup to date.
Like all StentorLabs models, this is a **base next-token predictor**, not a chat assistant. It will not reliably follow instructions, has no safety tuning, and is best suited for research, prototyping, speculative decoding, and infrastructure experiments.
The key architectural differentiators from Stentor2-12M are: a **~12ร parameter scale-up** (12.3M โ 151M), a **4ร longer context window** (4096 vs 1024 tokens), **Grouped Query Attention** (6 query heads, 2 KV heads), and a standard **Mistral BPE vocabulary** (32,768 tokens) rather than a compact custom tokenizer. This enables full compatibility with the standard `AutoTokenizer` ecosystem.
GQA training stability is worth noting: Stentor2-12M experienced minor training instability when GQA was first introduced, largely because at 12M parameters the model simply wasn't large enough to absorb the optimization pressure smoothly. At 151M parameters โ more than 12 times larger โ Portimbria-150M handled GQA training without issue. The benefits (smaller KV cache, faster inference, no quality loss) clearly outweigh the minor challenge that existed only at the 12M scale.
---
## The Portimbria Name
*Portia fimbriata* is a species of jumping spider native to Queensland, Australia. It is considered one of the most cognitively sophisticated spiders ever studied โ capable of problem-solving, planning, and learned behavior โ yet it fits comfortably on a fingertip. The word "Portimbria" is a scrambled encoding of the species name, chosen to reflect the same principle: a model small enough to train for free, yet ambitious enough to compete meaningfully with models trained at far greater cost.
---
๐ Table of Contents
1. [What Is This?](#what-is-this)
2. [The Portimbria Name](#the-portimbria-name)
3. [Model Architecture](#model-architecture)
4. [Head-to-Head: StentorLabs Model Family](#head-to-head-stentorLabs-model-family)
5. [Quick Start](#-quick-start)
6. [Important Limitations](#๏ธ-important-limitations)
7. [Honest Notices](#-honest-notices)
8. [Training Infrastructure](#training-infrastructure)
9. [Training Hyperparameters โ Complete Reference](#training-hyperparameters--complete-reference)
10. [Precision Stability Recipe](#precision-stability-recipe)
11. [Data Pipeline](#data-pipeline)
12. [Weight Initialization](#weight-initialization)
13. [Evaluation & Results](#evaluation--results)
14. [Benchmark Results](#benchmark-results)
15. [Training Dynamics](#training-dynamics)
16. [Use Cases & Intended Uses](#use-cases--intended-uses)
17. [Out-of-Scope Uses](#out-of-scope-uses)
18. [Ethical Considerations & Societal Impact](#ethical-considerations--societal-impact)
19. [Inference Guide](#inference-guide)
20. [Free Inference โ Try It Now](#-free-inference--try-it-now)
21. [Quantization](#quantization)
22. [Community Contributions](#-community-contributions--build-on-this-model)
23. [Format Conversion](#format-conversion)
24. [Speculative Decoding](#speculative-decoding)
25. [Bias, Risks & Limitations](#bias-risks--limitations)
26. [Related Work](#related-work)
27. [Environmental Impact](#environmental-impact)
28. [Citation](#citation)
โถ Full architecture spec, GQA explanation & parameter count breakdown
### Full Core Configuration
| Component | Value | Notes |
|---|---|---|
| **Architecture** | `LlamaForCausalLM` | Standard transformer decoder |
| **Hidden Size** | 768 | |
| **Intermediate Size (FFN)** | 2,048 | Hidden ร 2.67 (SwiGLU with 3 matrices) |
| **Num Hidden Layers** | 20 | |
| **Num Attention Heads** | 6 | |
| **Num Key/Value Heads** | 2 | GQA โ 3:1 query-to-KV ratio |
| **Head Dimension** | 128 | 768 รท 6 โ TPU v5e optimal |
| **KV Dimension** | 256 | 768 ร (2/6) |
| **Vocab Size** | 32,768 | Mistral BPE, padded to multiple of 128 |
| **Max Position Embeddings** | 4,096 | `block_size` in training script |
| **Hidden Activation** | SiLU | LlamaForCausalLM default |
| **Positional Encoding** | RoPE | `rope_theta = 50,000.0` |
| **RMS Norm Epsilon** | 1e-5 | |
| **Tie Word Embeddings** | True | Shared embedding / LM head |
| **Attention Bias** | False | |
| **MLP Bias** | False | |
| **Attention Implementation** | SDPA | PyTorch Scaled Dot Product Attention |
### Why GQA?
Grouped Query Attention (6Q, 2KV) reduces the KV cache memory footprint by 67% at inference time compared to standard Multi-Head Attention at the same hidden size. At a 4096-token context window this matters substantially: the KV cache for a single sequence is proportional to `2 ร num_kv_heads ร head_dim ร num_layers ร seq_len`. With 2 KV heads instead of 6, the cache shrinks to one-third of its full-MHA equivalent, enabling longer generation on memory-constrained hardware.
### Parameter Count Breakdown
```python
def estimate_llama_params_gqa(vocab_size, hidden_size, intermediate_size,
num_hidden_layers, num_attention_heads, num_key_value_heads):
kv_dim = int(hidden_size * num_key_value_heads / num_attention_heads)
q_proj = hidden_size * hidden_size
k_proj = hidden_size * kv_dim
v_proj = hidden_size * kv_dim
o_proj = hidden_size * hidden_size
attn = q_proj + k_proj + v_proj + o_proj
mlp = 3 * hidden_size * intermediate_size # gate, up, down
norm = 2 * hidden_size # input + post-attention RMSNorm
total = vocab_size * hidden_size + num_hidden_layers * (attn + mlp + norm) + hidden_size
return total
```
Plugging in Portimbria-150M values:
```
kv_dim = 768 ร (2/6) = 256
q_proj = 768 ร 768 = 589,824
k_proj = 768 ร 256 = 196,608
v_proj = 768 ร 256 = 196,608
o_proj = 768 ร 768 = 589,824
attn/layer = 1,572,864
mlp/layer = 3 ร 768 ร 2,048 = 4,718,592
norm/layer = 2 ร 768 = 1,536
per_layer = 6,292,992
embedding = 32,768 ร 768 = 25,165,824
layers = 20 ร 6,292,992 = 125,859,840
final_norm = 768
total = 25,165,824 + 125,859,840 + 768 = 151,026,432 โ
```
| Component | Parameters | % of Total |
|---|---|---|
| Embedding Table (tied with LM Head) | 25,165,824 | 16.7% |
| Transformer Layers ร 20 | 125,859,840 | 83.3% |
| โ Attention (per layer ร 20) | 31,457,280 | 20.8% |
| โ FFN/MLP (per layer ร 20) | 94,371,840 | 62.5% |
| โ Layer Norms (per layer ร 20) | 30,720 | 0.02% |
| Final RMS Norm | 768 | 0.001% |
| **Total** | **151,026,432** | **100%** |
With a standard 32K vocabulary, embedding takes only 16.7% of the parameter budget โ leaving 83.3% for the transformer stack that actually learns language patterns. This represents a healthy allocation at this scale, especially with GQA dramatically cutting the attention head count without sacrificing hidden dimension depth.
โถ Pipeline usage & recommended generation settings
### 4. Using the Pipeline
```python
from transformers import pipeline
pipe = pipeline(
"text-generation",
model="StentorLabs/Portimbria-150M",
torch_dtype=torch.float16,
device_map="auto",
)
result = pipe(
"Neural networks are computational models",
max_new_tokens=100,
temperature=0.8,
top_p=0.9,
repetition_penalty=1.1,
do_sample=True,
)
print(result[0]["generated_text"])
```
### 5. Recommended Generation Settings
| Parameter | Recommended Range | Notes |
|---|---|---|
| `temperature` | 0.5 โ 0.8 | Lower values (0.5โ0.6) give more coherent, on-topic output; higher values (0.7โ0.8) give more variety. Stay below 1.0. |
| `top_p` | 0.85 โ 0.90 | This range prevents gibberish and completely random tokens without over-restricting word choice. |
| `repetition_penalty` | 1.05 โ 1.2 | Stops looping and over-repetition while keeping outputs high quality. The sweet spot is 1.1. |
| `max_new_tokens` | 40 โ 4096 | Depends entirely on your goal. For a quick definition or fact, 40โ60 is enough. For a story or long document, use 2000โ4096. |
> **Temperature guidance:** Lower temperature keeps the model closer to its learned distribution and more likely to stay on topic. Higher temperature increases creativity and diversity at the cost of some coherence.
> **max_new_tokens guidance:** Don't set this too low for creative tasks โ the model often generates an EOS token and stops on its own before hitting the ceiling anyway. Setting a generous ceiling (e.g. 2000) for open-ended generation costs nothing if the model stops early.
โถ Software stack & throughput details
### Software Stack
| Package | Role |
|---|---|
| PyTorch 2.6 | Core tensor operations |
| torch_xla 2.6 | XLA/TPU backend |
| HuggingFace Transformers | Model architecture (LlamaForCausalLM) |
| HuggingFace Accelerate | Distributed training orchestration |
| HuggingFace Datasets | Data loading and streaming |
| safetensors | Model serialization |
### Throughput
| Metric | Value |
|---|---|
| Average global tokens/sec | ~253,000 |
| Per-chip tokens/sec | ~31,600 |
| Total training tokens | ~6,000,000,000 |
| Total wall-clock time (epoch) | 28,871s (~8.02h) |
โถ Full hyperparameter tables (optimizer, batch, schedule, checkpointing)
### Core Training Parameters
| Hyperparameter | Value | Notes |
|---|---|---|
| `learning_rate` | 8e-4 | Peak AdamW LR |
| `weight_decay` | 0.01 | Applied to Linear weights only |
| `max_grad_norm` | 1.0 | Gradient clipping |
| `optimizer` | AdamW | `betas=(0.9, 0.95)`, `eps=1e-8` |
| `scheduler` | Cosine | With linear warmup |
| `warmup_steps` | 1,144 | 5% of max_train_steps |
| `stable_steps` | 18,311 | 80% of max_train_steps |
| `max_train_steps` | 22,889 | Token budget reached first |
| `token_budget` | 6,000,000,000 | Total training tokens |
| `source_token_budget` | 6,000,000,000 | Source data token cap |
| `seed` | 42 | |
| `mixed_precision` | bf16 | Native TPU BF16 |
### Batch & Sequence Parameters
| Hyperparameter | Value | Notes |
|---|---|---|
| `per_device_train_batch_size` | 8 | Per TPU chip |
| `num_processes` | 8 | One per chip |
| `total_batch_size` | 64 | 8 ร 8 |
| `block_size` | 4,096 | Sequence / context length |
| `tokens_per_optimizer_step` | 262,144 | `total_batch_size ร block_size` |
| `gradient_accumulation_steps` | 1 | No accumulation |
| `num_train_epochs` | 1 | Token budget exhausted within epoch 0 |
| `pack` | True | Required for TPU static shapes |
### Evaluation & Checkpointing
| Hyperparameter | Value |
|---|---|
| `eval_steps` | 1,000 |
| `best_eval_steps` | 1,000 |
| `best_eval_start_step` | 1,000 |
| `max_eval_samples` | 5,000 |
### AdamW Optimizer โ Detailed
- **Decay group:** All `nn.Linear` weight matrices โ `weight_decay = 0.01`
- **No-decay group:** Bias terms, normalization parameters, embedding parameters โ `weight_decay = 0.0`
- **Betas:** `(0.9, 0.95)`
- **Epsilon:** `1e-8`
- **Fused kernel:** Enabled when CUDA available (not applicable on TPU)
### Learning Rate Schedule
```
Phase 1 โ Warmup (steps 0โ1,144):
LR ramps linearly from 0 โ 8e-4
Phase 2 โ Cosine Decay (steps 1,144โ22,889):
LR decays from 8e-4 โ 0 following a cosine curve
```
โถ FP32 norm patching, critical layer wrapping & recipe summary
Training on TPU v5e in BF16 requires deliberate precision management to avoid gradient instabilities at 150M scale.
### 1. FP32 Normalization Layers (41 modules)
All RMSNorm modules are monkey-patched to compute in FP32:
```python
def _fp32_norm_forward(hidden_states, *args, _orig=original_forward, **kwargs):
input_dtype = hidden_states.dtype
output = _orig(hidden_states.float().contiguous(), *args, **kwargs)
if torch.is_floating_point(output):
output = output.to(input_dtype)
return output
```
**Count:** 20 layers ร 2 norms each + 1 final norm = **41 modules total**.
### 2. FP32 Critical Layers (2 layers)
The **first and last transformer layers** run their entire forward pass in FP32:
- Weights remain in their training dtype; inputs are cast to `.float()` on entry
- `torch.amp.autocast("cuda", enabled=False)` prevents re-downcasting
**Rationale:** Boundary layers โ where embeddings project in and logits project out โ are most sensitive to numerical precision. Wrapping them in FP32 provides a stable floor at minimal compute cost.
### 3. FP32 Attention Softmax โ Skipped
Not applied. PyTorch SDPA handles softmax numerical stability internally and requires FP16/BF16 inputs for its optimized code paths on both CUDA and XLA.
### Recipe Summary
| Technique | Count | Scope |
|---|---|---|
| FP32 norm modules | **41** | All RMSNorm layers |
| FP32 critical layers | **2** | First + last transformer layers |
| FP32 softmax modules | **0** | Skipped โ SDPA incompatible |
โถ Curriculum design, text preprocessing code & sequence packing details
### Curriculum Design
Training used a **curriculum anneal** over the final 15% of the token budget, upweighting code and math relative to web text. This front-loads web generalization while ensuring the model sees a higher concentration of structured/formal content near the end of training.
### Text Preprocessing
```python
def clean_text(text: str, preserve_linebreaks: bool = False) -> str:
text = unicodedata.normalize("NFKC", text)
text = text.replace("\r\n", "\n").replace("\r", "\n")
if preserve_linebreaks:
lines = [line.rstrip() for line in text.splitlines()]
text = "\n".join(lines).strip()
else:
lines = [line.strip() for line in text.splitlines() if line.strip()]
text = " ".join(lines)
text = " ".join(text.split())
return text
```
- **NFKC normalization** maps visually-equivalent Unicode to canonical form
- **Linebreak preservation** for code samples (not applicable to web/math)
- **Whitespace collapse** for web/math text
### Sequence Packing
Samples are packed into fixed 4,096-token blocks. Labels are identical to `input_ids` (causal LM objective). No cross-document attention masking is applied between packed samples โ this is standard practice for web-text pretraining.
โถ Initialization scheme & residual scaling code
```python
def initialize_weights(model, std=0.02, num_hidden_layers=20):
layer_count = 20
residual_std = std / math.sqrt(2.0 * layer_count) # โ 0.00316
for name, module in model.named_modules():
if isinstance(module, nn.Embedding):
module.weight.data.normal_(mean=0.0, std=std)
elif isinstance(module, nn.Linear):
# Scaled-down std for output projections (residual path)
proj_std = residual_std if name.endswith(("o_proj", "down_proj")) else std
module.weight.data.normal_(mean=0.0, std=proj_std)
if module.bias is not None:
module.bias.data.zero_()
elif "rmsnorm" in type(module).__name__.lower():
if module.weight is not None:
module.weight.data.fill_(1.0)
```
- Residual projections (`o_proj`, `down_proj`) use scaled-down std (`0.02 / sqrt(2 ร 20) โ 0.00316`) to prevent residual stream explosion at initialization, following the GPT-2 convention.
- All other Linear layers use `std=0.02`.
- RMSNorm scales start at 1.0 (identity).
โถ Full checkpoint history (14-step loss & perplexity table)
| Step | Eval Loss | Perplexity | Notes |
|---|---|---|---|
| 1,000 | 5.3438 | ~209 | First best checkpoint |
| 2,000 | 4.1250 | ~62 | |
| 3,000 | 3.5625 | ~35 | |
| 8,000 | 3.4531 | ~31.6 | |
| 9,000 | 3.3125 | ~27.4 | |
| 10,000 | 3.1875 | ~24.3 | |
| 11,000 | 3.1406 | ~23.1 | |
| 12,000 | 3.0625 | ~21.4 | |
| 13,000 | 3.0312 | ~20.7 | |
| 14,000 | 2.9844 | ~19.8 | |
| 15,000 | 2.9375 | ~18.9 | |
| 17,000 | 2.9062 | ~18.3 | |
| **18,000** | **2.8906** | **18.03** | **Best checkpoint saved** |
| Final (epoch end) | 2.8906 | **18.00** | Final model |
โถ Step-by-step training phase breakdown & throughput details
The training run processed approximately **6 billion tokens** across a single epoch (epoch 0), running for **22,889 optimizer steps** before the token budget was exhausted.
**Early training (steps 0โ1,144, warmup phase):** LR ramped linearly from 0 to peak. Loss dropped quickly from above 5.0. First best checkpoint recorded at step 1,000 (eval loss 5.3438).
**Mid training (steps 1,144โ18,311, stable cosine phase):** Smooth and consistent loss reduction. Gradient norms were well-behaved in the 0.3โ0.6 range for most steps, with occasional spikes (notably 3.7 at step 1,800 and 8.5 at step 13,200 โ both recovered cleanly). New best checkpoints recorded at steps 1,000 / 2,000 / 3,000 / 8,000 / 9,000 / 10,000 / 11,000 / 12,000 / 13,000 / 14,000 / 15,000 / 17,000 / 18,000.
**Late training (steps 18,311โ22,889, cosine decay tail):** LR decaying toward zero. Eval loss stopped improving after step 18,000, confirming the best model was saved at that checkpoint.
**Throughput:** ~253,000 global tokens/sec average (~31,600 per chip), with a brief XLA warmup window reset at step 300.
**Total wall-clock time:** ~8.02 hours (epoch training) + ~8 minutes (final eval and save).
โถ Data biases, safety considerations & societal impact
### Inherited Data Biases
Trained on FineWeb-HQ, StarCoderData, and FineMath-4+ โ all derived from web-scraped data. The model inherits:
- **Western-centric perspective** โ English-language web text skews toward Western viewpoints and cultural contexts.
- **English monolingualism** โ Mistral BPE is optimized for English. Other languages will produce high fertility and poor quality.
- **Demographic underrepresentation** โ Groups underrepresented in English web text will be underrepresented in outputs.
- **Code ecosystem bias** โ StarCoderData covers many programming languages, but this model was deliberately trained only on the Python, JavaScript, and TypeScript subsets. These three were chosen because they are among the most widely used languages in 2026 and are generally more accessible to the majority of developers.
### No Safety Tuning
No RLHF, DPO, constitutional AI, or content filtering of any kind has been applied.
### Positive Aspects
- **Democratizing AI research** โ Trained entirely on free Kaggle TPU compute.
- **Full transparency** โ Complete training hyperparameters, architecture, and logs published.
- **Minimal environmental footprint** โ ~8 hours of TPU compute is negligible versus large-scale pretraining runs.
โถ CPU inference (INT8) & GPU inference (FP16) code
### CPU Inference (INT8 Dynamic Quantization)
```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("StentorLabs/Portimbria-150M")
tokenizer = AutoTokenizer.from_pretrained("StentorLabs/Portimbria-150M")
# Dynamically quantize for CPU
model_int8 = torch.quantization.quantize_dynamic(
model.cpu(),
{torch.nn.Linear},
dtype=torch.qint8,
)
inputs = tokenizer("The laws of physics state that", return_tensors="pt")
with torch.inference_mode():
output = model_int8.generate(**inputs, max_new_tokens=80, do_sample=True)
print(tokenizer.decode(output[0], skip_special_tokens=True))
```
### GPU Inference (FP16)
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model = AutoModelForCausalLM.from_pretrained(
"StentorLabs/Portimbria-150M",
torch_dtype=torch.float16,
device_map="cuda",
).eval()
tokenizer = AutoTokenizer.from_pretrained("StentorLabs/Portimbria-150M")
def generate(prompt, max_new_tokens=100, temperature=0.8, top_p=0.9):
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(model.device)
with torch.inference_mode():
output = model.generate(
input_ids,
attention_mask=torch.ones_like(input_ids),
max_new_tokens=max_new_tokens,
do_sample=True,
temperature=temperature,
top_p=top_p,
repetition_penalty=1.1,
pad_token_id=tokenizer.eos_token_id,
)
return tokenizer.decode(output[0][input_ids.shape[1]:], skip_special_tokens=True)
print(generate("Once upon a time in a distant kingdom"))
```
โถ FP16, BF16 & 4-bit (bitsandbytes) quantization code
### FP16 (GPU)
```python
model = AutoModelForCausalLM.from_pretrained(
"StentorLabs/Portimbria-150M",
torch_dtype=torch.float16,
)
```
### BF16
```python
model = AutoModelForCausalLM.from_pretrained(
"StentorLabs/Portimbria-150M",
torch_dtype=torch.bfloat16,
)
```
### 4-bit (bitsandbytes)
```bash
pip install bitsandbytes accelerate
```
```python
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16)
model = AutoModelForCausalLM.from_pretrained(
"StentorLabs/Portimbria-150M",
quantization_config=bnb_config,
device_map="auto",
)
```
โถ Convert to GGUF (llama.cpp) & ONNX
### Convert to GGUF (llama.cpp)
```bash
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp && pip install -r requirements.txt
huggingface-cli download StentorLabs/Portimbria-150M --local-dir portimbria-150m
python convert_hf_to_gguf.py portimbria-150m/ \
--outfile portimbria-150m.gguf \
--outtype f16
./llama-quantize portimbria-150m.gguf portimbria-150m-q4_k_m.gguf q4_k_m
./llama-cli -m portimbria-150m-q4_k_m.gguf -p "The history of computing" -n 100
```
### Convert to ONNX
```bash
pip install optimum[exporters]
optimum-cli export onnx \
--model StentorLabs/Portimbria-150M \
--task text-generation-with-past \
portimbria-150m-onnx/
```
โถ Speculative decoding code & vocabulary compatibility notes
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
draft_model = AutoModelForCausalLM.from_pretrained(
"StentorLabs/Portimbria-150M",
torch_dtype=torch.float16,
).to("cuda")
target_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.2-1B",
torch_dtype=torch.float16,
device_map="auto",
)
target_tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B")
inputs = target_tokenizer("Explain the concept of recursion:", return_tensors="pt").to("cuda")
outputs = target_model.generate(
**inputs,
assistant_model=draft_model,
do_sample=True,
max_new_tokens=200,
)
print(target_tokenizer.decode(outputs[0], skip_special_tokens=True))
```
> **Vocabulary compatibility:** Portimbria-150M uses the **Mistral-7B tokenizer** (32K BPE), which is *not* identical to the LLaMA-3 tokenizer (also 32K but with different token merges). It is compatible with models that use the same Mistral BPE vocabulary (e.g. `mistralai/Mistral-7B-v0.1` and derivatives). Vocabulary-compatible speculative decoding will yield higher acceptance rates; vocabulary-mismatched pairs will still work via HuggingFace's assisted generation but with lower acceptance rates.
โถ Related research papers
| Paper | Relevance |
|---|---|
| [Scaling Laws](https://arxiv.org/abs/2001.08361) โ Kaplan et al., 2020 | Informs token budget decisions |
| [Chinchilla](https://arxiv.org/abs/2203.15556) โ Hoffmann et al., 2022 | 6B tokens for 150M params is ~40ร (above Chinchilla optimal) |
| [GQA](https://arxiv.org/abs/2305.13245) โ Ainslie et al., 2023 | Grouped Query Attention used in this model |
| [RoPE](https://arxiv.org/abs/2104.09864) โ Su et al., 2021 | Positional encoding |
| [LLaMA](https://arxiv.org/abs/2302.13971) โ Touvron et al., 2023 | Architecture basis |
| [Pythia](https://arxiv.org/abs/2304.01373) โ Biderman et al., 2023 | Comparable small-model scaling study |
| [Speculative Decoding](https://arxiv.org/abs/2211.17192) โ Leviathan et al., 2023 | Primary deployment use case |
Made with โค๏ธ by StentorLabs
Democratizing AI through accessible, efficient models โ trained on free compute, shared with everyone.