Mica Technical Report 2 - first edition, October 2026, revised with each side’s footprint. States the compiler at release 7.7.0, commit ff523c023, measured by the protocol of 2026-10-01 and its eight amendments, in the series run from 2026-10-04 to 2026-10-05.

Abstract

A Mica program trains transformers on an NVIDIA card as native code: it needs no Python, no framework and no runtime compiler where it runs. This report measures it beside PyTorch 2.14.1, training the same models through nanoGPT: nanoGPT’s character-level Shakespeare model and GPT-2’s small and medium shapes, each in float32, bfloat16 and float16, on an RTX 4090 and an RTX 3090 Ti. A protocol written before the first measurement decides what is measured, how often, with which statistics and in which words, and it publishes a step time only where two correctness checks have shown that both sides train the same model.

Against the fastest of PyTorch’s four modes on each card, model and element type, the Mica program is faster in 6, level in 11 and slower in 1 of the 18 comparisons, level meaning a median less than 5% above PyTorch’s. GPT-2 medium in float32 ran PyTorch out of the card’s memory in its max-autotune mode on both cards, where the Mica program trained it with less memory than any of PyTorch’s modes. It opens cuBLAS, cuBLASLt and the driver from NVIDIA and nothing else. Every number links the raw data it comes from, and the public container repeats each one.

1. Introduction

A Mica program can train a transformer on an NVIDIA card. It holds its tensors on the card, records its forward pass on a tape and walks the tape backwards for its gradients, and its optimizer updates the weights where they live. Nothing of it is interpreted: the program is native code, built ahead of time by the Mica compiler, and it needs no Python, no framework and no runtime compiler where it runs.

This report measures such a program beside PyTorch, the framework nanoGPT is written in, on the trainings people run: nanoGPT’s character-level Shakespeare model, and GPT-2’s small and medium shapes, each in float32, bfloat16 and float16. Both sides train the same model on the same data for the same steps, on the same card, timed by one clock.

Three questions are asked, in this order:

  1. Do both sides train the same model? A step time means nothing unless both sides reach the same loss by the same path. Two methods answer it (section 7).
  2. How long does a training step take? Per card, model and element type, against the fastest of PyTorch’s four modes (section 6).
  3. What does each side need at run time? The NVIDIA libraries each program opens while it trains (section 9).

Every number in this report comes from a protocol written and merged before the first measurement it governs. The protocol fixes the configurations, the number of runs, the statistics and the words a result may be called by, so no result could choose how it is reported. Deficits stand in the same tables, in the same type, as leads.

2. The words, one at a time

A step is one update of the model: a forward pass over a batch, the backward pass for its gradients, and the optimizer’s update of every weight.

The window. Each timed run trains 400 steps. The first 100 are warm-up and never measured: compilations, the tuning of kernels, the capture of graphs, memory pools growing, clocks rising. A run’s step time is the time from its report line of step 100 to its line of step 400, divided by 300.

The outside clock. One reader starts each program and stamps every line of its output as it arrives. Both programs print a line every ten steps, right after a read of the loss that waits for the card, so the window holds whole steps, with nothing in flight at either end. The same reader times both sides.

A configuration is one side’s program in one element type and, for PyTorch, one mode. Each configuration runs once, discarded, and then five times, once in each of five rounds; the order rotates from round to round, so heat and drift touch every configuration alike.

PyTorch’s modes. Eager runs every operation as its own launch. The default mode of torch.compile, as nanoGPT ships it, compiles the model into fused kernels. reduce-overhead adds CUDA graphs, which replay a recorded step in one launch, and max-autotune adds the timing of candidate kernels to choose the fastest. The comparison is against the fastest of the four, as measured on the card, model and element type.

The element types. float32 multiplies on the tensor cores in TF32 on both sides, nanoGPT’s own setting; the Mica program permits it with fp_tensor_float32. In bfloat16 and float16 the weights and the residual stream stay float32 and every matrix product reads the half type, as PyTorch’s autocast does; float16 scales the loss as PyTorch’s GradScaler does.

Half roundings. PyTorch lets cuBLAS add a product’s partial sums in the half type, and its FlashAttention rounds the attention’s weights into the half type. The Mica build compared in a half type permits exactly those roundings with the opt-in flag fp_half_intermediates; the build without it, every digit kept, stands beside it in every table.

The products record. PyTorch’s fastest mode chooses its kernels by timing them. Each Mica build does the same for its matrix products: its first run, discarded, times the cuBLASLt routines for every product shape on the card and keeps the fastest in a file, which every timed run of the series reads, so they all run the same routines.

The verdicts. Per card, model and element type: faster when Mica’s slowest run is faster than PyTorch’s fastest run; level when not faster and Mica’s median stands less than 5% above PyTorch’s; slower when Mica’s median stands 5% or more above PyTorch’s, published with the ratio of the medians. A configuration whose correctness check has not passed has no verdict.

3. The two programs

3.1 nanoGPT

nanoGPT is Andrej Karpathy’s compact implementation of GPT training in PyTorch. Its train.py runs unchanged in its eager and default modes. The two modes beyond the default take one published change, which hands the mode to torch.compile; the correctness checks take two more, one making the seed a setting and one reading a recorded batch order (section 7). GPT-2’s shapes read their settings from a configuration file beside the runner, so train.py stays as nanoGPT ships it.

3.2 The Mica program

The Mica side is one program, GptRef.mica, which a script writes from the same numbers nanoGPT’s configuration states, layer by layer in nanoGPT’s order: embeddings, the blocks with their layer normalizations, causal self-attention over one product holding the queries, keys and values side by side, the GELU MLP, the residual stream, the loss, AdamW with its warm-up and cosine decay, and the clipping of the gradients’ norm.

How its kernels are made: Mica writes every kernel in its intermediate language; Dragon’s NVIDIA provider renders it as CUDA C, and NVIDIA’s NVRTC compiles it to the card’s machine code; matrix products run in cuBLAS. The compiler’s own kernels and the attention ship precompiled for five card generations (sm_80, sm_86, sm_89, sm_90, sm_120), and a program’s own kernels are precompiled when it is linked, so on those generations the program loads every kernel ready and never opens NVRTC; it opens cuBLAS, cuBLASLt and the driver alone, and each series records that it did.

The published builds are --optimize O2,fp_tensor_float32, with fp_half_intermediates for the half types, and every check of O2 stays on; each build’s manifest is recorded beside its runs.

4. Method

The method is the measurement protocol, published with this report. Its rules, in short:

  1. Fixed before measuring. Models, configurations, runs, statistics and success criteria were written down and merged before the first run they govern; a later change is an amendment, dated, with its reason, and never changes which configuration is the comparison.
  2. PyTorch at its best: the fastest of its four modes, as measured.
  3. One instrument for both sides: the outside clock. Each program’s own reading is recorded beside it; the Mica program’s must agree with the clock within 1%, or the run is void.
  4. Every number carries its conditions: runs, median and range, card, versions, date, commit and raw logs.
  5. A deficit is published as measured.
  6. Speed is published only for a configuration whose correctness check passed.
  7. The raw data is published: every log, kernel table, loss curve and script.
  8. A measurement is not a projection.
  9. Published numbers come from the released compiler.

4.1 The amendments

AmendmentDateWhat it settled
12026-10-01the verdicts as section 2 states them, so a lead whose ranges overlap reads level and never slower
22026-10-01which NVIDIA libraries each program opened, recorded in each configuration’s first run
32026-10-01the released compiler carries the precompiled kernels
42026-10-02float16; the half roundings; the products record; GPT-2 medium
52026-10-03a PyTorch mode that runs out of the card’s memory is recorded so, and the verdict compares the modes that ran
62026-10-03method B for GPT-2 medium judges the steps float32 holds, with the evidence of PyTorch against itself
72026-10-04the third card’s own correctness check, and method B on every card
82026-10-04a run out of the card’s memory, on either side, ends no series

4.2 Correctness

Method A, families of runs. Each side trains the model’s whole schedule with five seeds, 1337 to 1341, in every element type, evaluated every 250 steps on 200 batches of each split. The two sides draw different dropout masks and batches, so no single pair of runs can match; the families must. The check passes when, at every evaluation from step 1,000 on, the two sides’ median validation losses differ by no more than the larger of the two sides’ ranges at that step. The RTX 4090 runs the five seeds; the RTX 3090 Ti runs one seed a side as a check of its own.

Method B, one shared start. Both sides start from one set of initial weights made by nanoGPT’s own initialization, read one recorded batch order, train without dropout and multiply in plain float32, so only the order of their sums differs. The check passes when the losses at step 0 agree within 1e-5 of the loss and those of each later step within 1%; for GPT-2 medium, through step 100, where float32 alone carries two runs of PyTorch itself a percent apart by step 200 (amendment 6). Each card runs it.

5. Setup

Two cards ran the series this report publishes, the RTX 4090 and the RTX 3090 Ti, in two workstations, each with the released compiler as its packages ship it. The protocol names a third card; it is not part of this report, and its series and checks stand in the container as they were measured. The table is read from each series’ machine record:

RTX 4090RTX 3090 Ti
cardNVIDIA GeForce RTX 4090, 24564 MiBNVIDIA GeForce RTX 3090 Ti, 24564 MiB
card generationsm_89sm_86
driver610.57.04610.57.04
processorAMD Ryzen Threadripper 3990X 64-Core ProcessorAMD Ryzen 9 3900X 12-Core Processor
systemUbuntu 26.04.1 LTS, kernel 7.0.0-38-genericUbuntu 26.04.1 LTS, kernel 7.0.0-38-generic
kernels loaded precompiledyes, NVRTC never openedyes, NVRTC never opened

Every series ran Mica Optimizing Compiler Version 7.7.0 ff523c023, PyTorch 2.14.1+cu130 cuda 13.0 cudnn 92400, Triton 3.8.0, Python 3.14.4 and nanoGPT at 3adf61e, measured from 2026-10-04 to 2026-10-05.

The clocks are not locked: the cards run as their owners’ do, and every run samples the card’s clocks, temperature, power and memory once a second.

6. Step time

Every comparison at a glance: the verdict, the ratio of the medians and, under it, the mode of PyTorch 2.14.1 it was measured against, the fastest of its four on that card, model and type. Each model links its series, and the tables below name both sides’ times and PyTorch’s attention kernels.

cardmodelfloat32bfloat16float16
RTX 4090nanoGPTfaster 0.981
max-autotune
slower 1.052
max-autotune
level 1.030
max-autotune
RTX 4090GPT-2 smallfaster 0.965
default
faster 0.973
max-autotune
faster 0.989
max-autotune
RTX 4090GPT-2 mediumfaster 0.971
default
level 1.007
max-autotune
level 1.012
max-autotune
RTX 3090 TinanoGPTlevel 1.031
max-autotune
level 1.006
max-autotune
level 0.998
max-autotune
RTX 3090 TiGPT-2 smalllevel 1.032
max-autotune
level 1.001
max-autotune
level 1.009
max-autotune
RTX 3090 TiGPT-2 mediumfaster 0.996
default
level 1.009
max-autotune
level 1.013
max-autotune

nanoGPT’s Shakespeare model is a 10.7-million-parameter model with a 256-character context, where launches and small products weigh more than in larger models. GPT-2 small and medium stand beside it, with 123.59M and 353.55M parameters as nanoGPT counts them, over GPT-2’s context and vocabulary.

Each table below is one card. A row is one model in one element type: the Mica program’s median step time over its five timed runs, with the fastest and the slowest of them in brackets; PyTorch’s fastest mode on that card, model and type, with its median and range and the attention kernels it ran; the ratio of the two medians, below 1.000 where the Mica program is faster; and the verdict of section 2. Times are milliseconds per step.

RTX 4090

modeltypeMica, msPyTorch's fastest mode, msratioverdictresults
nanoGPTfloat3227.05 (26.96–27.07)max-autotune 27.58 (27.53–27.60)
attention: fmha_cutlassB_f32_aligned_64x64_k64_dropout_sm80, fmha_cutlassF_f32_aligned_64x64_rf_sm80
0.981faster20261004-211558-NVIDIA-GeForce-RTX-4090
nanoGPTbfloat1613.23 (13.20–13.23)max-autotune 12.57 (12.55–12.58)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.052slower20261004-211558-NVIDIA-GeForce-RTX-4090
nanoGPTfloat1613.31 (13.29–13.32)max-autotune 12.92 (12.90–12.92)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.030level20261004-211558-NVIDIA-GeForce-RTX-4090
GPT-2 smallfloat32136.05 (135.06–136.15)default 140.94 (139.85–141.15)
attention: fmha_cutlassB_f32_aligned_64x64_k64_sm80, fmha_cutlassF_f32_aligned_64x64_rf_sm80
0.965faster20261004-151530-NVIDIA-GeForce-RTX-4090
GPT-2 smallbfloat1661.77 (61.69–61.80)max-autotune 63.47 (63.44–63.51)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
0.973faster20261004-151530-NVIDIA-GeForce-RTX-4090
GPT-2 smallfloat1663.45 (63.37–63.52)max-autotune 64.18 (64.16–64.18)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
0.989faster20261004-151530-NVIDIA-GeForce-RTX-4090
GPT-2 mediumfloat32357.80 (356.07–358.23)default 368.63 (368.55–369.54)
attention: fmha_cutlassB_f32_aligned_64x64_k64_sm80, fmha_cutlassF_f32_aligned_64x64_rf_sm80
out of the card's memory: pytorch-float32-max-autotune
0.971faster20261004-171016-NVIDIA-GeForce-RTX-4090
GPT-2 mediumbfloat16164.42 (164.38–164.55)max-autotune 163.27 (163.21–163.45)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.007level20261004-171016-NVIDIA-GeForce-RTX-4090
GPT-2 mediumfloat16168.63 (168.55–168.70)max-autotune 166.60 (166.48–166.75)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.012level20261004-171016-NVIDIA-GeForce-RTX-4090

RTX 3090 Ti

modeltypeMica, msPyTorch's fastest mode, msratioverdictresults
nanoGPTfloat3254.25 (53.87–54.33)max-autotune 52.60 (52.60–52.61)
attention: fmha_cutlassB_f32_aligned_64x64_k64_dropout_sm80, fmha_cutlassF_f32_aligned_64x64_rf_sm80
1.031level20261004-151204-NVIDIA-GeForce-RTX-3090-Ti
nanoGPTbfloat1622.85 (22.83–22.87)max-autotune 22.71 (22.70–22.74)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.006level20261004-151204-NVIDIA-GeForce-RTX-3090-Ti
nanoGPTfloat1622.79 (22.76–22.82)max-autotune 22.83 (22.81–22.87)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
0.998level20261004-151204-NVIDIA-GeForce-RTX-3090-Ti
GPT-2 smallfloat32269.81 (269.50–270.04)max-autotune 261.55 (261.44–261.70)
attention: fmha_cutlassB_f32_aligned_64x64_k64_sm80, fmha_cutlassF_f32_aligned_64x64_rf_sm80
1.032level20261004-160629-NVIDIA-GeForce-RTX-3090-Ti
GPT-2 smallbfloat16111.61 (111.55–111.65)max-autotune 111.48 (111.43–111.60)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.001level20261004-160629-NVIDIA-GeForce-RTX-3090-Ti
GPT-2 smallfloat16113.47 (113.42–113.54)max-autotune 112.50 (112.33–112.52)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.009level20261004-160629-NVIDIA-GeForce-RTX-3090-Ti
GPT-2 mediumfloat32737.60 (737.43–737.88)default 740.77 (740.73–740.80)
attention: fmha_cutlassB_f32_aligned_64x64_k64_sm80, fmha_cutlassF_f32_aligned_64x64_rf_sm80
out of the card's memory: pytorch-float32-max-autotune
0.996faster20261004-190019-NVIDIA-GeForce-RTX-3090-Ti
GPT-2 mediumbfloat16305.95 (305.86–305.98)max-autotune 303.13 (303.03–303.65)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.009level20261004-190019-NVIDIA-GeForce-RTX-3090-Ti
GPT-2 mediumfloat16312.02 (311.82–312.35)max-autotune 307.92 (307.65–308.19)
attention: pytorch_flash::flash_bwd_dq_dk_dv_loop_seqk_parallel_kernel, pytorch_flash::flash_fwd_kernel, pytorch_flash::flash_bwd_dot_do_o_kernel, pytorch_flash::flash_bwd_convert_dq_kernel
1.013level20261004-190019-NVIDIA-GeForce-RTX-3090-Ti

The half types without the half roundings

Each half type's build without fp_half_intermediates, every digit kept, against the same PyTorch mode as above.

cardmodeltypeMica, msratio
RTX 4090nanoGPTbfloat1617.25 (17.22–17.26)1.372
RTX 4090nanoGPTfloat1618.02 (18.00–18.03)1.395
RTX 4090GPT-2 smallbfloat1689.21 (89.21–89.24)1.406
RTX 4090GPT-2 smallfloat1692.04 (92.04–92.08)1.434
RTX 4090GPT-2 mediumbfloat16230.45 (230.38–230.54)1.411
RTX 4090GPT-2 mediumfloat16237.41 (237.16–237.47)1.425
RTX 3090 TinanoGPTbfloat1630.57 (30.55–30.58)1.346
RTX 3090 TinanoGPTfloat1634.50 (34.47–34.53)1.512
RTX 3090 TiGPT-2 smallbfloat16161.69 (161.61–161.76)1.450
RTX 3090 TiGPT-2 smallfloat16170.65 (170.52–170.78)1.517
RTX 3090 TiGPT-2 mediumbfloat16435.75 (435.65–435.82)1.438
RTX 3090 TiGPT-2 mediumfloat16461.05 (460.48–461.61)1.497

Slower, as measured and in the same tables: nanoGPT's 10.7-million-parameter model in bfloat16 on the RTX 4090, 1.052 against PyTorch 2.14.1's max-autotune with FlashAttention. Section 8 shows where the time goes.

Where PyTorch ran out of the card’s memory

A mode that runs out of the card’s memory has no time, and the comparison is against the fastest of the modes that ran (amendment 5). It happened on PyTorch’s side alone, and only to GPT-2 medium in float32. The table gives, for those runs, each configuration’s peak memory over its timed runs, or that it ran out of the card’s memory.

cardmodeltypeMicaPyTorch eagerPyTorch defaultPyTorch reduce-overheadPyTorch max-autotuneresults
RTX 4090 (24564 MiB)GPT-2 mediumfloat3221,160 MiB23,939 MiB22,426 MiB23,598 MiBout of memoryseries
RTX 3090 Ti (24564 MiB)GPT-2 mediumfloat3220,828 MiB23,588 MiB22,074 MiB23,246 MiBout of memoryseries

The Mica program trained every model in every element type on both cards, and in these rows with less memory than any of PyTorch’s modes.

7. Correctness

Each check ran after its series, with the series’ record of measured products and with nanoGPT in the mode the series compares for each element type (amendment 4). Method A’s families ran on the RTX 4090 and give the verdict for every card; method B and the single seed a side belong to the card they ran on (amendment 7). Each check links its results: every run’s output, both sides’ curves plotted together, and the record the verdict was read from.

Method A, families of runs

modeltypecard, seedsjudgedMica, final validation lossPyTorch, final validation losscheck
nanoGPTfloat32RTX 4090, 5171.6921 (1.6839–1.7044)1.6998 (1.6866–1.7039)passed, results
nanoGPTbfloat16RTX 4090, 5171.6954 (1.6800–1.7200)1.7042 (1.6966–1.7136)passed, results
nanoGPTfloat16RTX 4090, 5171.6948 (1.6878–1.7069)1.7010 (1.6923–1.7037)passed, results
GPT-2 smallfloat32RTX 4090, 556.9967 (6.9663–7.0687)7.0519 (7.0297–7.0845)passed, results
GPT-2 smallbfloat16RTX 4090, 557.0352 (6.9057–7.0626)7.0114 (6.9508–7.0940)passed, results
GPT-2 smallfloat16RTX 4090, 556.9916 (6.9247–7.0017)7.0646 (7.0323–7.1595)passed, results
GPT-2 mediumfloat32RTX 4090, 556.9395 (6.8489–6.9756)6.9225 (6.9126–6.9997)passed, results
GPT-2 mediumbfloat16RTX 4090, 556.9115 (6.8607–7.0423)6.9469 (6.9337–6.9797)passed, results
GPT-2 mediumfloat16RTX 4090, 556.9390 (6.8842–6.9841)6.9264 (6.8895–7.0075)passed, results

Method B, one shared start

cardmodelstep 0, apartfurthest apart after itcheck
RTX 4090nanoGPT00.43% at step 149 of 200passed, results
RTX 4090GPT-2 small00.32% at step 196 of 200passed, results
RTX 4090GPT-2 medium9.1e-080.73% at step 85 of 100
over all 200 steps: 1.10% at step 172
passed, results
RTX 3090 TinanoGPT00.42% at step 149 of 200passed, results
RTX 3090 TiGPT-2 small00.29% at step 196 of 200passed, results
RTX 3090 TiGPT-2 medium9.1e-080.70% at step 57 of 100
over all 200 steps: 1.24% at step 183
passed, results

Each card's own seed a side

One seed a side on the cards that did not run the families, read beside them and judged by none.

cardmodeltypeMica, final validation lossPyTorch, final validation losscheck
RTX 3090 TinanoGPTfloat321.70841.7016results
RTX 3090 TinanoGPTbfloat161.70841.7132results
RTX 3090 TinanoGPTfloat161.69891.6963results
RTX 3090 TiGPT-2 smallfloat327.02277.0180results
RTX 3090 TiGPT-2 smallbfloat167.03977.0204results
RTX 3090 TiGPT-2 smallfloat166.98417.0146results
RTX 3090 TiGPT-2 mediumfloat326.99897.0048results
RTX 3090 TiGPT-2 mediumbfloat166.92786.8881results
RTX 3090 TiGPT-2 mediumfloat166.96886.9797results

Why GPT-2’s final losses stand high. GPT-2’s two shapes learn the small Shakespeare corpus by heart within their schedule: their validation loss falls at first and then rises while their training loss falls toward zero, on both sides alike at every evaluation, so their final validation loss stands above its lowest. Method A compares the two sides’ curves at every evaluation, which overfitting leaves as comparable as learning does.

GPT-2 medium’s window. Method B judges GPT-2 medium through step 100 and states the 200 steps beside it. From one start, float32 carries the last bit of a model of 353.55M parameters to a percent within 200 steps, for any two stacks: nanoGPT trained from weights moved by one unit in their last place, once at the start or after every step, the size of the difference two stacks summing in different orders leave, parted from its own run about as far as the Mica program parts from it. Those two runs of PyTorch against itself, the change to train.py that made them and their curves are published with the checks (amendment 6).

8. Where the time goes

Each series profiles one run of every configuration with NVIDIA’s Nsight Systems and sorts the kernels of its steady window into families by one rules file, the same for both sides (families.tsv in the container). A family’s time is its kernels’ time per step; launches, gaps and the host are not in it. A profiled run’s kernel time explains a step time and never stands in for one.

nanoGPT’s 10.7-million-parameter model with a 256-character context, in bfloat16 on the RTX 4090, the comparison PyTorch wins by the widest margin:

familyMica, msPyTorch max-autotune, ms
attention1.531.28
conversions0.200.18
gelu1.441.16
layer normalization, dropout and residual2.361.94
layout0.000.36
loss and embedding0.100.15
optimizer0.380.44
other0.030.09
products6.876.85
together12.9212.45

RTX 4090, nanoGPT, bfloat16: the profiled runs' window, the Mica program against PyTorch 2.14.1 in max-autotune with FlashAttention, 20261004-211558-NVIDIA-GeForce-RTX-4090.

The products stand level. The difference sits in the kernels around them - the GELU, the layer normalizations with the dropout and the residual, and the attention - which PyTorch’s max-autotune partly fuses into its products, and in the time between kernels, which its CUDA graphs shorten; PyTorch spends time on layout kernels the Mica program never runs.

The largest model in the same type and on the same card, GPT-2 medium:

familyMica, msPyTorch max-autotune, ms
attention17.0614.69
conversions3.853.61
gelu7.987.77
layer normalization, dropout and residual12.449.43
layout0.002.24
loss and embedding4.043.97
optimizer14.4215.66
other0.071.09
products103.82101.78
together163.67160.25

RTX 4090, GPT-2 medium, bfloat16: the profiled runs' window, the Mica program against PyTorch 2.14.1 in max-autotune with FlashAttention, 20261004-171016-NVIDIA-GeForce-RTX-4090.

Every series holds the tables of all its configurations, and the kernels by name.

9. What each side needs at run time

What each program opened from NVIDIA while it trained, read from the dynamic loader in the first run of every configuration, counted over the comparisons of section 6:

sideNVIDIA libraries openedcomparisons
MicalibcublasLt.so.13, libcublas.so.13, libcuda.so.118 of 18
PyTorchlibcublasLt.so.13, libcublas.so.13, libcuda.so.1, libcudnn.so.9, libcufft.so.12, libcupti.so.13, libcurand.so.10, libcusolver.so.12, libcusparse.so.12, libnccl.so.2, libnvJitLink.so.13, libnvrtc-builtins.so.13.0, libnvrtc.so.1318 of 18

The Mica program loads every kernel precompiled for the card’s generation and opens NVIDIA’s runtime compiler on neither card: what it needs where it runs is the program, cuBLAS and the driver.

The protocol’s section 8 also measures each side’s smallest container image that trains, built from the Dockerfiles in the container’s benchmarks/NanoGpt/images/: Mica’s holds the program, the NVIDIA libraries it opens and the system libraries those need; PyTorch’s holds Python and what pip install torch numpy brings, with nanoGPT’s own files. Each image proves itself by training on a card, on a machine with NVIDIA’s container toolkit:

imageMBfilesits training's last evaluation
Mica637.210step 5000: train loss 0.609480, val loss 1.703618
PyTorch6016.430,107step 5000: train loss 0.6171, val loss 1.7083

nanoGPT in bfloat16, PyTorch in its mode default, 5000 steps on each side; the driver's libcuda comes from the host in both: 20261005-113556-NVIDIA-L4-footprint.

Each image trained the model on the card to the loss its row shows. PyTorch’s also needs a C compiler beside Python, which Triton asks for when it compiles the kernels of a compiled mode.

10. Limits, stated

  • Model size. nanoGPT’s model is small; GPT-2’s shapes are measured beside it.
  • The corpus. GPT-2’s shapes train on nanoGPT’s Shakespeare text, far smaller than what they were made for, and overfit it within their schedule: this report measures how fast both sides train and whether they train alike, not a model worth using.
  • The processor. Training on the processor against PyTorch’s CPU path is not measured here.
  • The image network. Convolutional networks are not measured here.
  • The clocks are not locked, as stated in section 5.

nanoGPT [1] is the compact GPT training this report measures against, unchanged in its model and in its training loop. GPT-2 [2] gives the two larger shapes. PyTorch 2 [3] compiles a model’s Python with TorchDynamo and generates its kernels with TorchInductor, largely in Triton [4]; its reduce-overhead mode replays CUDA graphs and its max-autotune mode times candidate kernels, which is why the comparison is against the fastest mode as measured rather than any one. Its attention runs FlashAttention [5, 6] in the half types and the memory-efficient attention of xFormers [7] in float32. Mixed-precision training with a scaled loss [8] is what PyTorch’s autocast and GradScaler do, and what the Mica program does in the half types. The Mica program’s matrix products run in NVIDIA’s cuBLAS and cuBLASLt [9]; PyTorch’s run in cuBLAS too in its first three modes, and under max-autotune largely in the Triton kernels its timing chose, many with the next operation fused in.

12. Conclusion

A Mica program, compiled ahead of time to native code, trains nanoGPT’s Shakespeare model and GPT-2’s small and medium shapes on two NVIDIA cards in float32, bfloat16 and float16, and trains the same models as PyTorch does: both correctness checks say so for every model on every card. Against the fastest of PyTorch’s four modes, measured on each card, model and type, it is faster in 6, level in 11 and slower in 1 of the 18 comparisons; what is slower is published as measured, with where its time goes.

It does so without what PyTorch brings to the machine it trains on: no interpreter, no framework, no runtime compiler, and a fraction of the NVIDIA libraries. Every number in this report was fixed in its method before it was measured, links the raw data it comes from, and can be measured again by anyone with an NVIDIA card or a rented one, from the public container.

How to repeat every number

Every number of this report is measured again from the public container, mica-dev/mica-container, on a machine with an NVIDIA card and the released compiler, with the versions each series records. One card’s series and checks of one model, as the published ones ran:

mica-provision nvidia gpu-profiling gpu-containers     # cuBLAS and NVRTC, the profiler, Docker for the footprint
cd benchmarks/NanoGpt
./run.sh both --model nanogpt --dtypes float32,bfloat16,float16 --modes eager,default,reduce-overhead,max-autotune \
    --rounds 5 --publication --optimize O2,fp_tensor_float32,fp_half_intermediates --products-record \
    --torch 2.14.1 --nanogpt 3adf61e                      # the series: results/<series>
./correctness.sh --publication --method b --model nanogpt --modes-of results/<series> \
    --torch 2.14.1 --nanogpt 3adf61e                      # method B: results/<check-b>
./correctness.sh --publication --method a --model nanogpt --dtypes float32,bfloat16,float16 \
    --seeds 1337,1338,1339,1340,1341 --products results/<series> --modes-of results/<series> \
    --optimize O2,fp_tensor_float32,fp_half_intermediates \
    --torch 2.14.1 --nanogpt 3adf61e                      # method A: results/<check-a>
./series.py take results/<series> results/<check-a> results/<check-b>

GPT-2’s shapes take --model gpt2-small or --model gpt2-medium. A card that does not run the families runs method A with --seeds 1337, and its series takes the families’ check beside its own. The footprint is ./footprint.sh --model nanogpt --dtype bfloat16 --mode <the series' bfloat16 mode> with the same versions. The container’s benchmarks/NanoGpt/README.md states every option, and its results/ holds every series and check of this report, each linked from its table, and the scripts that started each card’s series and checks, with their logs and what happened on the way.

References

  1. A. Karpathy. nanoGPT. https://github.com/karpathy/nanoGPT, 2022.
  2. A. Radford, J. Wu, R. Child, D. Luan, D. Amodei, I. Sutskever. Language Models are Unsupervised Multitask Learners. OpenAI, 2019.
  3. J. Ansel et al. PyTorch 2: Faster Machine Learning Through Dynamic Python Bytecode Transformation and Graph Compilation. ASPLOS 2024.
  4. P. Tillet, H. T. Kung, D. Cox. Triton: An Intermediate Language and Compiler for Tiled Neural Network Computations. MAPL 2019.
  5. T. Dao, D. Y. Fu, S. Ermon, A. Rudra, C. Ré. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. NeurIPS 2022.
  6. T. Dao. FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning. ICLR 2024.
  7. B. Lefaudeux et al. xFormers: A modular and hackable Transformer modelling library. https://github.com/facebookresearch/xformers, 2022.
  8. P. Micikevicius et al. Mixed Precision Training. ICLR 2018.
  9. NVIDIA. cuBLAS. https://docs.nvidia.com/cuda/cublas/.