Post training Inkling 2 Small
to run a railway

Inkling 2 Small dispatches trains on a real Dutch single-track line, before and after reinforcement learning on Tinker

Author

Affiliation

  1. RLSimEnv

Published

 

Resources

The job

Between Leeuwarden and Groningen, in the north of the Netherlands, runs 55 km of mostly single track. Trains going opposite ways can only pass each other at stations with two tracks. Eight trains an hour share it. When something goes wrong (a broken-down train, a collision at a level crossing, a signal failure) a dispatcher has a few minutes to re-plan the next three hours: hold this train at that station, turn that one back early, cancel another.

RailSimEnv (bpHigh, 2026) turns real days from 2025 into that job. It is an OpenEnv (Meta PyTorch, 2025) environment rebuilt from the public record of every train on the line (Rijden de Treinen, 2025), and it grades every plan against the best plan possible, proven by a solver. We took Thinking Machines’ newest small model, Inkling 2 Small, measured it, and fine-tuned it on the environment with reinforcement learning through the Tinker API (Thinking Machines Lab, 2026).

13 March 2025, from 07:22. A broken-down train has closed Leeuwarden - Hurdegaryp. Under the fine-tuned model’s plan this train set stops at Hurdegaryp instead of going on into the closed section, and its next run, 37424 to Groningen, starts from there at 07:29. The replay is the environment’s own simulation of the submitted plan, drawn on OpenStreetMap (OpenStreetMap contributors, 2026).

In one paragraph

Inkling 2 Small started at 0.119 on the 45 hard evaluation tasks, below a simple rule-based dispatcher (0.180). Reinforcement learning on other days of the same line taught it to stop running out of words: 0.186. Switching to the long context model increased the score to 0.259 before any training, and 0.316 after a second round of reinforcement learning. The last gain is promising but not yet settled.

  1. bpHigh. (2026). RailSimEnv: dispatching trains on the Leeuwarden - Groningen line. https://huggingface.co/spaces/bpHigh/RailSimEnv
  2. Meta PyTorch. (2025). OpenEnv: an interface for agentic execution environments. https://github.com/meta-pytorch/OpenEnv
  3. OpenStreetMap contributors. (2026). OpenStreetMap. https://www.openstreetmap.org
  4. Rijden de Treinen. (2025). Open data: train services, disruptions and the Dutch rail network. https://www.rijdendetreinen.nl/en/open-data
  5. Thinking Machines Lab. (2026). Tinker: a training API for researchers and developers. https://tinker-docs.thinkingmachines.ai/

The environment

What the model sees and does

Each task is one real disruption window: the line as it stood at that minute, every train’s timetable and position, the closure or the lost passing track, and the train sets that work chains of runs. The model plans every train with three levers: hold it at a station until a minute, short-turn it at Hurdegaryp, Buitenpost or Zuidhorn (and start its set’s next run from there), or cancel it. Trains it leaves alone run as early as the physics allow, never ahead of the timetable.

It has three tools. get_situation() describes the line, the closures and every train. check_plan(plan) simulates a draft and lists every train’s times, every rule it breaks and the cost; it can be called ten times. submit_plan(plan) ends the episode with one grade.

How a plan is scored

The cost is counted in stop-minutes , the delay passengers feel. A solver (CP-SAT from OR-Tools (Perron & Furnon, 2024)) has proven the cheapest possible plan for every task, so the reward is honest:

The 45 evaluation tasks were chosen for hardness from days the training tasks never use1. Larger models do fine but not great on them, and a simple rule-based dispatcher stays near 0.2:

Mean reward on the 45 evaluation tasks
One rollout per task, 95% bootstrap intervals over tasks. Inkling 2 Small ran with 64K or 256K of context and 8K or 32K output tokens per turn (next sections). Hover a row for its numbers.

Footnotes

  1. Training tasks come from 84 days of January to June 2025, evaluation tasks from held-out weeks of the same months. Each task is a 150 to 240 minute window with 15 to 38 trains.

  1. Perron, L., & Furnon, V. (2024). OR-Tools. Google. https://developers.google.com/optimization/

The model and the training loop

Inkling 2 Small (Thinking Machines Lab, 2026) is a mixture-of-experts model from Thinking Machines Lab: 594 billion parameters, 17 billion active per token. Tinker trains a LoRA adapter on it (Hu et al., 2021), a small set of extra weights, while Thinking Machines runs the hardware. We used rank 8 throughout.

The loop is plain policy-gradient reinforcement learning with group-relative advantages, in the spirit of GRPO (Shao et al., 2024):

  1. pick a handful of training tasks;
  2. let the model play each one 4 times with the current weights;
  3. compare the four scores: attempts that beat their siblings are pushed up, worse ones pushed down;
  4. one optimizer step, then repeat with the new weights.

Every token the model wrote in an episode (its reasoning, its tool calls, its final plan) is one training sequence, and the reward is the environment’s own score.

The details: rules of an episode and of a training step
  • An episode: 12 turns at most, check_plan up to 10 times, submit_plan once. Two turns in a row without a tool call end it; no plan scores 0. After every turn the model is told how many turns and how much context it has left, and when another check would no longer fit beside the final answer the next turn is announced as the last.
  • A step: 16 tasks × 4 episodes (64K run) or 8 tasks × 4 (256K run); rewards centred within each task’s group; groups whose four rewards are equal carry no signal and are skipped; one importance_sampling step at learning rate 1e-4.
  • Tracking progress honestly: 16 training tasks are held out and played once every 6 steps; the 45 evaluation tasks are only played before and after.
  • A spending cap stops the run before a step that would pass it.
  1. Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2021). LoRA: Low-Rank Adaptation of Large Language Models. arXiv Preprint arXiv:2106.09685.
  2. Shao, Z., Wang, P., Zhu, Q., Xu, R., Song, J., Bi, X., Zhang, H., Zhang, M., Li, Y. K., Wu, Y., & Guo, D. (2024). DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models. arXiv Preprint arXiv:2402.03300.
  3. Thinking Machines Lab. (2026). Using Inkling. https://tinker-docs.thinkingmachines.ai/cookbook/inkling/

Round 1: a 64K context and 8K words a turn

Tinker’s standard Inkling 2 Small has a 65,536-token context. A single check_plan report can be 10,000 tokens, so we gave the model 8,192 tokens of output per turn, at its default reasoning effort (“high”).

Before training: 0.119

That is below the rule-based dispatcher. Reading the transcripts showed why: the model thought too long. A third of its turns hit the 8,192-token limit in the middle of reasoning, before it called any tool, and 11 of 45 episodes ended without a plan at all.

The obvious fix, maximum effort instead of high, made it worse: 0.111, with 21 episodes ending without a plan. More thinking in the same small box only meant running out of room sooner.

After 23 steps of RL: 0.186

On the same 45 tasks the trained model is better by +0.067 per task (95% interval +0.024 to +0.115, comparing each task with itself), better on 30 tasks and worse on 12. What it learned is clearest in how it behaves:

before RLafter RL
episodes that submitted a plan34 of 4545 of 45
turns cut off at the token limit32 %4 %
output tokens per episode22,90011,700
check_plan calls per episode2.44.1
legal plans913

It learned to budget: shorter reasoning, more drafts checked, always a plan at the end. The plans themselves improved much less. That is typical of what reinforcement learning finds first, the cheapest way to stop losing rewards.

The learning rate mattered

The first six steps ran at a learning rate of 4e-5 and moved the held-out tasks by +0.022 (standard error 0.020): nothing. At 1e-4 the behaviour changed within four steps. The run resumed from its saved state with the new rate.

Round 2: give it room to think

If the problem was room, the fix was room. Tinker also serves Inkling 2 Small with a 262,144-token context (256K, as Tinker lists it: 256 × 1,024). We ran it at maximum effort with 32,768 output tokens per turn.

Before any training: 0.259

More than double the 64K baseline, and better than the model we had just trained. 25 of 45 plans were legal (9 before) and two matched the proven optimum. The lesson of round 1 held: the bottleneck was the budget, not the model.

After 12 steps of RL: 0.316

Episodes here are about 143,000 tokens long, so each step played 8 tasks × 4 instead of 16 × 4. After 12 steps the fine-tuned model scores 0.316: +0.056 per task, better on 22 tasks, worse on 13, the same on 10. This time it was not about budget (the model already submitted almost every time) but about plan quality. Legal plans now cost 23 % more than the optimum on the median, against 37 % before, and three reached the optimum.

Every evaluation task, before and after RL
Each row is one task: grey is the base model, coloured the fine-tuned one. Rows are sorted by how much the task changed. Switch between the two rounds above the chart; hover a row for its numbers. Plans left of 0.2 broke a rule.
The held-out tasks during training
Both runs, played once every six steps (and at the end). The 64K run climbs once the learning rate was raised at step 6; the 256K run starts higher and stays flat on these 16 tasks.

Watching it on the line

Averages hide what a dispatcher actually does. Here are three tasks, before and after the second round of training, on the environment’s train graph (time across, the line from Leeuwarden down to Groningen) and in its 3D replay. Every plan below is the one the model submitted, replayed exactly as it was graded; all of them can be replayed on the RailSimEnv Space, first tab.

13 March 2025, 07:01: a broken-down train

A real breakdown closed Leeuwarden - Hurdegaryp from 07:01 to 08:00, with 26 trains to plan.

Train graph of the base model's plan for 13 March: trains held at Hurdegaryp, Feanwalden and Buitenpost, and red dashed lines after 10:00 where trains meet head-on
Before RL: 0.131. The base model holds trains at Hurdegaryp, Feanwalden and Buitenpost and waits the closure out. The waiting ripples on: after 10:00 trains meet head-on on single track (red dashes), 16 rule breaks in all.
Train graph of the fine-tuned model's plan for 13 March: eight trains turned back at Hurdegaryp, a clean timetable afterwards
After RL: 0.938. The fine-tuned model turns eight trains back at Hurdegaryp (the circles) so each train set's next run starts on time from there. Cost 382 stop-minutes against a proven optimum of 378.

The difference is a decision a human dispatcher would recognise: when a section is closed for an hour, do not queue trains in front of it, turn them back where they can be turned and keep the rest of the timetable intact. The base plan’s queue does not hurt during the closure; it hurts two and a half hours later, when the delayed trains collide with the timetable coming the other way.

10:44 to 11:02, following the train set that runs 37430. Left, the base model’s plan: 37430, now 118 minutes late, runs into the single-track section Leeuwarden Camminghaburen - Hurdegaryp just as two trains come the other way (rule breaks at 10:55). Right, the fine-tuned model’s plan at the same minutes: the same set ran 37430 on time and is already back in Leeuwarden for its next run, 37427.

The driver’s view. Train 37424 under the fine-tuned plan, from 07:30: turned at Hurdegaryp, it starts its run to Groningen there instead of in Leeuwarden, behind the closure and on time.

16 February 2025, 17:00: litter on the track

Litter on the track closed Buitenpost - Groningen from 17:10 to 18:42: half the line, in the evening rush.

Train graph of the base model's plan for 16 February: five trains cancelled
Before RL: 0.200. Legal, but the base model cancels five trains: 1,140 of its 1,468 stop-minutes are stops nobody served.
Train graph of the fine-tuned model's plan for 16 February: one cancellation and six short-turns, the proven optimum
After RL: 1.000. The fine-tuned model cancels one train and short-turns six instead, serving what can be served: 796 stop-minutes, exactly the proven optimum.

17:50 to 18:14 at Buitenpost, following the train set that arrived as 37462. Left, the base model’s plan: the set stands at Buitenpost, waiting for the line to Groningen to reopen at 18:42; its next two runs, 37357 and 37368, are cancelled. Right, the fine-tuned model’s plan: the same set turned at Buitenpost and leaves on time at 17:56 as 37357, back to Leeuwarden.

The driver’s view. Train 37357 under the fine-tuned plan, from 17:54: it starts at Buitenpost instead of Groningen, on time, and runs west to Leeuwarden while the line east of Buitenpost is still closed.

31 May 2025, 09:00: where training made it worse

Not every task improved. A defective track closed Leeuwarden - Hurdegaryp from 09:30 to 09:56, a short closure.

Train graph of the base model's plan for 31 May: four short-turns
Before RL: 0.756. The base model short-turns four trains: 246 stop-minutes against an optimum of 226.
Train graph of the fine-tuned model's plan for 31 May: every train held, none turned
After RL: 0.200. The fine-tuned model holds every train through the closure and turns none back: legal, but 708 stop-minutes of delay.

09:48 to 10:16, following the train set that ran 37423. Left, the base model’s plan: the set turned at Hurdegaryp and leaves at 09:52 as 37334 to Groningen, on time. Right, the fine-tuned model’s plan: the same set waits at Hurdegaryp until the closure ends at 09:56, runs on into Leeuwarden and starts 37334 from there at 10:14, 28 minutes late.

What this shows

On a 26-minute closure, waiting is a reasonable instinct; the optimum mixes a few short-turns with short holds. The fine-tuned model does not yet weigh the closure’s length well: it learned that turning back is often right on long closures, and here it swung the other way. One rollout per task makes single tasks noisy, which is why the paired average over all 45 is the number to trust.

What we noticed

Reinforcement learning learns the easy lesson first

On the 64K model nearly all of the gain was “stop running out of tokens, always submit a plan”: useful. On the long-context model, where that lesson was already learned, the gains came more slowly and were about the plans themselves: fewer cancellations, more short-turns, cheaper legal plans.

Small samples mislead in both directions

Training batches draw different tasks every step, so a batch swung from 0.18 to 0.38 by luck. The 16 held-out tasks and the 45 evaluation tasks sometimes disagreed. Comparing the same tasks before and after1, rather than two averages, was the only reliable way to see an effect, and even that leaves the second round’s gain on the edge.

The model found a bug in the grader

During training the model tried a plan that starts a train at its own final station, leaving nothing to run. The simulator crashed instead of calling it a broken rule. Reinforcement learning is very good at finding the corners of an environment that nobody tested. The fix and a test for it went in the same night.

Some habits never changed

Every episode, before and after training, begins by calling get_situation, though the situation is already in the first message. Each call costs about 4,000 tokens of context, and neither round of training removed it.

Footnotes

  1. A paired bootstrap: resample tasks, and for each resample average the per-task difference (after minus before). It removes the spread between easy and hard tasks, which is most of the spread in a single run’s mean.

What it took

All of the training ran on Tinker; the loop, the environment and the grader ran on one small cloud machine. In tokens and time:

64K run (rl-a)256K run (rl-b)
RL steps2312
episodes per step16 tasks × 48 tasks × 4
training episodes1,472384
tokens sampled (with validation)28.4M46.5M
tokens trained on62.9M57.5M
average training sequence44k tokens143k tokens
hours of sampling / of training3.0 / 0.56.0 / 0.5

Each evaluation of 45 tasks took 7 minutes at 64K and 30 minutes at 256K, with all 45 episodes in flight at once. At 256K most of the training cost is training itself: the sequences are three times longer and long-context training is priced higher per token.

What’s next

  1. Train longer at 256K: round one needed about a dozen steps before anything moved; round two stopped at twelve.
  2. Teach the length of a closure: the 31 May regression suggests the model has not learned when waiting beats turning back.

Try it

Everything is open:

Data and credits

Train services, delays and disruptions: Rijden de Treinen open data (CC BY 4.0) (Rijden de Treinen, 2025). Map: © OpenStreetMap contributors (ODbL) (OpenStreetMap contributors, 2026). Inkling 2 Small and Tinker: Thinking Machines Lab (Thinking Machines Lab, 2026b, 2026a).

  1. OpenStreetMap contributors. (2026). OpenStreetMap. https://www.openstreetmap.org
  2. Rijden de Treinen. (2025). Open data: train services, disruptions and the Dutch rail network. https://www.rijdendetreinen.nl/en/open-data
  3. Thinking Machines Lab. (2026a). Tinker: a training API for researchers and developers. https://tinker-docs.thinkingmachines.ai/
  4. Thinking Machines Lab. (2026b). Using Inkling. https://tinker-docs.thinkingmachines.ai/cookbook/inkling/