Pretraining a 325M Language Model from Zero on an Inference-Only NPU

Community Article
Published October 26, 2026

A 325M language model pretrained from zero on an inference-only NPU

TL;DR

  • We pretrained VKORE-325M-NPU-Base, a 325.6M parameter decoder-only language model, from random initialization on a commercial inference-only NPU. Every weight was learned on the NPU with backpropagation run on the chip. No pretrained weights were used.
  • The run covered 5.0 billion byte tokens of FineWeb-Edu in 76,292 steps over about four days.
  • In parallel we trained the same model on a B200 GPU with the same initialization, data, batch and learning-rate schedule.
  • On 1,024 windows of FineWeb-Edu text that neither model ever saw, the NPU model reaches a loss of 0.7993 and the GPU model 0.7994. The paired difference is -0.0001 with a standard error of 0.0007. The NPU model is lower on 50.1% of windows.
  • Both sets of weights and the evaluation script are public, so anyone can check this result in a few minutes: FINAL-Bench/VKORE-325M-NPU-Base.

1. Why this matters

Accelerators sold for inference are now deployed in large numbers. They are cheaper and use less power than training GPUs, and their vendors ship them with a compiler and runtime built for one job: running a forward pass of a model that was trained somewhere else.

The accepted view is that such chips cannot train. Not a LoRA fine-tune, and certainly not a pretraining run from zero. Training needs a backward pass, an optimizer state, and numerically stable updates over tens of thousands of steps, none of which an inference stack is built to provide.

If that view is wrong, the consequences are practical. Organizations that already own inference hardware could adapt and even pretrain models on it, and new data centers could plan for one class of chip instead of two.

We tested the view directly, in two steps:

  1. VKORE-46M-NPU (earlier): a 46M model trained on TinyStories. It showed that an inference-only NPU can run backpropagation at all.
  2. VKORE-325M-NPU-Base (this post): a model seven times larger, trained on real web text for 76k steps, with a GPU twin to measure the result against.

From VKORE-46M-NPU to VKORE-325M-NPU-Base

2. Experimental design: one variable

A claim like "the NPU reached GPU quality" is only meaningful against a reference that differs in nothing but the hardware. We therefore trained the same model twice.

Experimental design: one model, two machines, everything else held fixed

Held fixed Value
Initialization identical random seed and initial weights
Training corpus the same 6.94 GB FineWeb-Edu byte file
Batch 65,536 byte tokens per step
Steps 76,293 (NPU run saved its final checkpoint at step 76,292)
Optimizer AdamW, betas (0.9, 0.95), weight decay 0.1, gradient clipping at 1.0
Learning rate 500-step warmup to 3e-4, step down to 1.5e-4 at step 18,000, linear decay over the last 20%

The only difference is the machine: a commercial inference-only NPU for run A, and a single B200 GPU for run B.

Identical learning-rate schedule on both runs

3. Model and data

Item Value
Parameters 325.6M
Architecture decoder-only Transformer, RoPE (theta 1e5), RMSNorm, SwiGLU, grouped-query attention
Layers 20
Hidden / FFN width 1024 / 3584
Attention 8 query heads, 2 key-value heads, head dimension 256
Vocabulary 256 (byte level: token id = byte value)
Context length 256
Format on the Hub standard LlamaForCausalLM, loads with plain transformers

Data. The training corpus is built from FineWeb-Edu sample/10BT. The parquet shards were read in repository order, each document was encoded as UTF-8 bytes, and documents were joined with a blank line. The corpus is the first two shards (000 and 001), 6.94 GB in total. Training drew random 257-byte windows from this file, so 5.0B training tokens correspond to about 0.72 passes over it.

A byte-level vocabulary keeps the comparison clean: there is no tokenizer to tune, and the reported loss is cross entropy per byte, which converts directly to bits per byte.

4. What made this hard

A backward pass is necessary but not sufficient. Our first long runs on the NPU trained correctly for thousands of steps and then diverged. Short tests did not reveal the problem; it only appeared in long runs checked against a GPU reference.

Making long runs stable on an inference-only stack was the main engineering work between the 46M model and this one. The technique was developed in house and is under patent application, so this post does not describe it. What we can show is the result: the 325M run trained for 76,292 steps with no divergence and no loss spikes. The run was interrupted twice by process failures and resumed from saved checkpoints both times, which is visible in the logs and has no visible effect on the curve.

5. Results

5.1 Training curves

Training loss of the NPU run and the B200 reference

The two curves overlap from the first thousand steps to the end. Both fall from about 5.8 to about 0.80. Small local wiggles differ because the two runs see their random windows in a different order; the trend is the same throughout. (The plotted NPU log runs to step 74,360; the final checkpoint is step 76,292.)

5.2 Loss on unseen text

The decisive test is text that was not in the training corpus at all. We used FineWeb-Edu sample/10BT shard 013, the last shard, which was never part of the training file, and evaluated both published models on 1,024 random 256-byte windows from it. We also report the 256 fixed windows from the end of the training corpus that were used during development.

Model Loss, unseen text (1,024 windows) Bits per byte, unseen Loss, training-corpus windows (256)
Inference-only NPU 0.7993 1.1531 0.8067
B200 GPU reference 0.7994 1.1533 0.8070

Evaluation with 95% confidence intervals

Because both models were evaluated on identical windows, we can compare them window by window. That removes the variation between windows and gives a much tighter test than comparing two averages.

Per-window loss difference on unseen text

The paired difference is -0.0001 (NPU minus GPU) with a standard error of 0.0007, and the NPU model has the lower loss on 50.1% of windows. In other words, at this resolution the two models cannot be told apart.

A note on evaluation hygiene: the first version of our model card called the 256 training-corpus windows a "held out" set. They are not strictly held out, because training sampled windows from the whole file. We corrected the card and added the unseen-shard evaluation above, which is the number we stand behind.

5.3 Samples

Both models are small base models trained on 5B tokens, so they write fluent text with shaky facts. The point is that they behave alike. Sampling with temperature 0.7, top-p 0.9, repetition penalty 1.15, seed 0:

Prompt NPU model B200 reference
Photosynthesis is the process ...by which plants can produce oxygen from the air. It is a process by which carbon dioxide (CO2) is transferred... ...by which plants use sunlight and carbon dioxide to convert carbohydrates (carbon dioxide) into food energy...
Climate change is caused by ...human activities and occurs in nature. The main reason for this problem is the continuing presence of carbon dioxide... ...human-caused greenhouse gas emissions. The U.S. Environmental Protection Agency (EPA) is committed to cutting emissions...
The heart is ...connected to the body. Blood pumps blood to the body and pumps blood to our body for oxygen... ...the most powerful organ in the body. It is also responsible for pumping oxygen to the blood...

6. Reproduce it

Both models live in one repository. The B200 reference is in the b200_reference subfolder.

from transformers import AutoModelForCausalLM

npu = AutoModelForCausalLM.from_pretrained("FINAL-Bench/VKORE-325M-NPU-Base")
gpu = AutoModelForCausalLM.from_pretrained("FINAL-Bench/VKORE-325M-NPU-Base", subfolder="b200_reference")

The unseen-text comparison in section 5.2 is one script, evaluate_unseen.py. It downloads shard 013, draws the same 1,024 windows (seed 2026), and prints both losses and the paired difference. It uses only transformers, numpy and pyarrow.

Generating text with the byte-level model:

import torch
prompt = "Photosynthesis is the process"
ids = torch.tensor([list(prompt.encode("utf-8"))])
out = npu.generate(ids, max_new_tokens=200, do_sample=True, temperature=0.7, top_p=0.9, repetition_penalty=1.15)
print(bytes(out[0].tolist()).decode("utf-8", errors="replace"))

7. What we do not claim

  • Not a capable assistant. This is a 325M base model trained on 5B tokens, released as a training-hardware result.
  • Not a speed record. This post is about whether an inference-only NPU can carry a full pretraining run to GPU quality, not about how fast. Throughput is a separate engineering track.
  • Not the method. The training loop and the stability technique are in house and under patent application. The weights, data recipe, hyperparameters and evaluation are fully open.

8. What comes next

  • Larger models on the same hardware class, and longer token budgets.
  • Fine-tuning and adaptation workloads on inference-only NPUs for customers who already own the hardware.
  • A paper with the full method once the patent filing is complete.

Links

VIDRAFT. Part of the VKORE project (NPU acceleration family).

Community

I'm very interested in the comparisons in time. Are there more details?

Sign up or log in to comment