Gladios logo

Gladios 0.3B ENG β€” Prototype

A from-scratch GPT-style chat model (~300M parameters), trained on English conversational data.


Model Description

Gladios is a small conversational language model, built entirely from scratch β€” no pretrained base, no fine-tuning of an existing LLM. Both the byte-level BPE tokenizer and the transformer weights were trained from random initialization on the datasets listed below.

This is a genuine hobbyist prototype/test project, built by a single passionate individual with the help of AI tools during development β€” not a team, not a company, not a production-ready assistant. At ~300M parameters trained on a modest dataset, expect coherent but simple answers β€” not GPT-4-level reasoning.

Full training code and documentation: GitHub β€” Plimb-ai/gladios-0.3b-eng-prototype

Architecture

A decoder-only transformer, implemented from scratch in PyTorch β€” no transformers.GPT2Model or similar off-the-shelf model class, only nn.Linear, nn.LayerNorm, nn.Embedding and PyTorch's scaled_dot_product_attention.

Component Detail
Parameters ~300M
Layers 24
Attention heads 16
Embedding dim 1024
Context length 256 tokens
Vocabulary 8,000 tokens (custom-trained byte-level BPE)
Positional encoding Learned absolute position embeddings

Files in this repository

File Description
gladios-0.3b-eng-prototype.pt Model weights (lightweight export β€” no optimizer state), ~1.25 GB
vocab.json BPE tokenizer vocabulary
merges.txt BPE tokenizer merge rules

Training Data

Trained on a combination of public English conversational/instruction datasets:

Each example is formatted as <|user|> question <|bot|> answer <|endoftext|>.

How to Use

1. Install dependencies

pip install torch tokenizers huggingface_hub

2. Download the weights and tokenizer

from huggingface_hub import snapshot_download

local_dir = snapshot_download(repo_id="plimb/gladios-0.3b-eng-prototype")

3. Rebuild the model class

The GPT model class isn't packaged as a pip-installable library β€” copy it from the snippet below (same code used to train this model):

import math
import torch
import torch.nn as nn
import torch.nn.functional as F

class CausalSelfAttention(nn.Module):
    def __init__(self, n_embd, n_head, block_size, dropout=0.1):
        super().__init__()
        assert n_embd % n_head == 0
        self.n_head = n_head
        self.n_embd = n_embd
        self.head_dim = n_embd // n_head
        self.qkv = nn.Linear(n_embd, 3 * n_embd, bias=False)
        self.proj = nn.Linear(n_embd, n_embd, bias=False)
        self.attn_dropout = nn.Dropout(dropout)
        self.resid_dropout = nn.Dropout(dropout)
        self.dropout = dropout
        self.register_buffer(
            "mask",
            torch.tril(torch.ones(block_size, block_size)).view(1, 1, block_size, block_size),
        )
        self.flash = hasattr(F, "scaled_dot_product_attention")

    def forward(self, x):
        B, T, C = x.shape
        qkv = self.qkv(x)
        q, k, v = qkv.split(self.n_embd, dim=2)
        q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        if self.flash:
            y = F.scaled_dot_product_attention(
                q, k, v, attn_mask=None,
                dropout_p=self.dropout if self.training else 0.0,
                is_causal=True,
            )
        else:
            att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(self.head_dim))
            att = att.masked_fill(self.mask[:, :, :T, :T] == 0, float("-inf"))
            att = F.softmax(att, dim=-1)
            att = self.attn_dropout(att)
            y = att @ v
        y = y.transpose(1, 2).contiguous().view(B, T, C)
        y = self.resid_dropout(self.proj(y))
        return y


class MLP(nn.Module):
    def __init__(self, n_embd, dropout=0.1):
        super().__init__()
        self.fc1 = nn.Linear(n_embd, 4 * n_embd, bias=False)
        self.gelu = nn.GELU()
        self.fc2 = nn.Linear(4 * n_embd, n_embd, bias=False)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        return self.dropout(self.fc2(self.gelu(self.fc1(x))))


class Block(nn.Module):
    def __init__(self, n_embd, n_head, block_size, dropout=0.1):
        super().__init__()
        self.ln1 = nn.LayerNorm(n_embd)
        self.attn = CausalSelfAttention(n_embd, n_head, block_size, dropout)
        self.ln2 = nn.LayerNorm(n_embd)
        self.mlp = MLP(n_embd, dropout)

    def forward(self, x):
        x = x + self.attn(self.ln1(x))
        x = x + self.mlp(self.ln2(x))
        return x


class GPT(nn.Module):
    def __init__(self, vocab_size, block_size, n_layer=24, n_head=16, n_embd=1024, dropout=0.0):
        super().__init__()
        self.block_size = block_size
        self.tok_emb = nn.Embedding(vocab_size, n_embd)
        self.pos_emb = nn.Embedding(block_size, n_embd)
        self.drop = nn.Dropout(dropout)
        self.blocks = nn.ModuleList([
            Block(n_embd, n_head, block_size, dropout) for _ in range(n_layer)
        ])
        self.ln_f = nn.LayerNorm(n_embd)
        self.head = nn.Linear(n_embd, vocab_size, bias=False)
        self.tok_emb.weight = self.head.weight

    def forward(self, idx, targets=None):
        B, T = idx.shape
        pos = torch.arange(0, T, dtype=torch.long, device=idx.device)
        x = self.tok_emb(idx) + self.pos_emb(pos)
        x = self.drop(x)
        for block in self.blocks:
            x = block(x)
        x = self.ln_f(x)
        logits = self.head(x)
        loss = None
        if targets is not None:
            loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
        return logits, loss

    @torch.no_grad()
    def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None, stop_token=None):
        self.eval()
        for _ in range(max_new_tokens):
            idx_cond = idx if idx.size(1) <= self.block_size else idx[:, -self.block_size:]
            logits, _ = self(idx_cond)
            logits = logits[:, -1, :] / max(temperature, 1e-6)
            if top_k is not None:
                v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
                logits[logits < v[:, [-1]]] = -float("inf")
            probs = F.softmax(logits, dim=-1)
            next_id = torch.multinomial(probs, num_samples=1)
            idx = torch.cat((idx, next_id), dim=1)
            if stop_token is not None and next_id.item() == stop_token:
                break
        self.train()
        return idx

4. Load the weights and tokenizer

import os
import torch
from tokenizers import ByteLevelBPETokenizer

device = "cuda" if torch.cuda.is_available() else "cpu"

ckpt = torch.load(os.path.join(local_dir, "gladios-0.3b-eng-prototype.pt"), map_location=device)
cfg = ckpt["config"]

model = GPT(
    vocab_size=cfg["vocab_size"],
    block_size=cfg["block_size"],
    n_layer=cfg["n_layer"],
    n_head=cfg["n_head"],
    n_embd=cfg["n_embd"],
    dropout=0.0,
).to(device)
model.load_state_dict(ckpt["model"])
model.eval()

tokenizer = ByteLevelBPETokenizer(
    os.path.join(local_dir, "vocab.json"),
    os.path.join(local_dir, "merges.txt"),
)
eot_id = tokenizer.token_to_id("<|endoftext|>")
user_id = tokenizer.token_to_id("<|user|>")
bot_id = tokenizer.token_to_id("<|bot|>")

5. Chat

def chat(message, max_new_tokens=100, temperature=0.8, top_k=40):
    ids = [user_id] + tokenizer.encode(message).ids + [bot_id]
    x = torch.tensor([ids], dtype=torch.long, device=device)
    out = model.generate(
        x, max_new_tokens=max_new_tokens, temperature=temperature, top_k=top_k,
        stop_token=eot_id,
    )
    response_ids = out[0, len(ids):].tolist()
    if response_ids and response_ids[-1] == eot_id:
        response_ids = response_ids[:-1]
    return tokenizer.decode(response_ids).strip()

print(chat("What is the capital of France?"))

Training Details

  • Optimizer: AdamW, cosine learning rate schedule with warmup
  • Precision: bfloat16 autocast + gradient scaling on GPU
  • Regularization: dropout 0.2, weight decay 0.15
  • Hardware: single Colab GPU (T4 / A100 depending on availability)
  • Checkpointing: evaluated every 200 iterations, keeping the best validation loss

Limitations

  • English only β€” no meaningful multilingual capability; prompting in other languages produces incoherent output.
  • Overfitting risk β€” with ~300M parameters trained on a comparatively small combined corpus, validation loss plateaus after a certain point even with regularization (dropout, weight decay) applied. Treat outputs as a learning-project result, not a benchmark-grade model.
  • No safety alignment β€” no RLHF, no content filtering, no moderation layer. This is a from-scratch research/learning prototype.
  • Short context window (256 tokens) β€” long conversations lose earlier context quickly.

License

MIT. Note that the training datasets each carry their own license β€” check each dataset's card before redistribution.

Citation

If you reference this project, please link back to the GitHub repository.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support