Fine-tune your own AI — the friendly way.
Thinker is a studio for fine-tuning open language models with the Tinker training API. Bring your data, pick a model, press start. Every setting is explained in plain English, and a built-in Demo mode lets you try the whole flow with no API key and no cost.
-
Add data three ways — upload a file (
.jsonl/.json/.csv), type examples by hand, or import a dataset straight from Hugging Face. Thinker validates it against the training type and tells you exactly what to fix. -
See inside a file before importing it — uploading runs inspect → map → preview → commit. Thinker reads the file into a staging area and reports what it found: the columns, which training type it can feed, and the actual converted examples a field mapping would produce. Nothing is written to your dataset list until you've seen it, and backing out discards the staged copy.
-
Credential scanning, before anything leaves your machine — training data is uploaded to Tinker, and whatever a model memorises can resurface in its output later. So every ingest is scanned for API keys, tokens, private keys and passwords. Findings are shown redacted, with a choice of redacting them, dropping those rows, or importing as-is. It's tuned for precision over recall — a scanner that cries wolf gets ignored — so it passes over things like
api_key = your-api-key-here. -
Find a dataset without guessing — the Hugging Face importer opens on a goal-first shortlist ("make it follow instructions", "match my tone"), and every suggestion and search result is checked live against the Hub before you commit: trains as-is, partly usable, or needs field mapping, with the detected columns shown. It catches real traps — e.g.
Anthropic/hh-rlhfbakes the prompt into the chosen/rejected text, so it can't feed DPO unmapped. -
Drop the rows you don't want — importers can filter as well as map. Schema validation can't tell a real answer from the literal string
[removed], so filters handle content: non-empty (which knows the placeholder vocabulary), numeric thresholds, equality, contains, and booleans for flags likesubreddit.nsfw. Suggestions come from Thinker having actually read your sample, so they only appear when the data warrants them and each says why — "54 of 100 sampled rows are empty or a placeholder". -
Mix several datasets into one — pick proportions (60/30/10) and Thinker blends and shuffles them into a single trainable set. Ratio is the real choice: empathetic dialogue teaches short curious replies, instruction data teaches long thorough ones, and the wrong mix lets one drown the other. The preview shows the composition you'd actually get, which isn't always the one you asked for — the smallest source caps the whole blend, and it names which.
-
Write a character's voice — the Voice page. No dataset on the Hub is your character, so this is the part that can't be automated: hand-write a set of exchanges that sound unmistakably like them, then have a local Ollama model fan them out into enough examples to train on. Generation is free and stays on your machine. Candidates are held for review rather than saved, because a teacher drifts toward its own voice and unreviewed drift is exactly how a character becomes a generic assistant with a name.
Two teachers are supported. Local Ollama is free and stays on your machine, but an M1 manages a few tokens a second — and use a small instruct model, since reasoning models spend their whole output on thinking tokens and return nothing. Claude (optional Anthropic key in Settings) holds a subtle voice far better and generates thousands of exchanges in minutes rather than hours; it also uses structured outputs, so the reply is guaranteed to parse instead of being scraped out of fenced prose. Billed by Anthropic separately from your Tinker credits — roughly a few dollars per thousand exchanges.
Quality compounds here: every generated example either sharpens the character or dilutes them, which is why the teacher is worth paying for even though the local one is free.
-
Train for real — three genuinely-implemented methods:
- Supervised — teach by example (prompt → answer).
- Preference (DPO) — teach what's better (chosen vs. rejected), via a real Bradley-Terry loss.
- Reinforcement (RL) — teach by trying, with a real
importance_samplingpolicy-gradient loop over sampled rollouts.
-
Watch it learn — live loss/reward curves stream over WebSocket while the job runs. If something goes wrong, the job fails with the real reason — Thinker never fakes a success.
-
Try your model — chat with it in the Playground, compare base vs. fine-tuned side by side, and turn 👍/👎 ratings into a preference dataset (closing the RLHF loop).
-
Multi-agent Arena — spin up several copies of a model and let them compete with real RL (tournament or evolutionary "swarm"), ranked on a live leaderboard.
-
Live model catalog — the current Tinker base models with real pricing, context windows, and vision/reasoning badges (fetched from Tinker, not hardcoded).
-
Export it to run on a phone — Tinker keeps a fine-tune as a LoRA adapter in its own storage. The Export page turns that into one set of merged, quantized weights in MLX format, the one Apple Silicon and iOS run natively (
weights.download→weights.build_hf_model→mlx_lm.convert -q).It preflights first, because merging loads the full base model in bf16 — a 4B fine-tune means an ~8 GB download and ~9 GB of RAM, a 20B one means ~40 GB and ~46 GB. So before downloading anything it answers two questions: will the result fit on a phone (sizes at 4/6/8/16-bit against real iPhone memory budgets), and can this machine actually do the conversion (disk, RAM, Apple Silicon, mlx-lm). A job this machine can't finish is refused up front rather than dying halfway through.
The numbers are deliberately unflattering: iOS only lets an app address part of physical RAM, so a 12 GB phone is not a 12 GB budget, and sizes include ~10% runtime headroom. Worth checking before you pick a base model — a 20B MoE is ~11 GB at 4-bit and fits no iPhone at any quantization, because every expert stays resident even when few are active.
Backend (/backend) — FastAPI + SQLite (persists datasets, models, jobs, and
per-step metrics). A real Tinker training engine (training/engine.py), a live
model catalog (catalog.py), HuggingFace import wired straight into training, and a
WebSocket hub for live updates. Tinker imports are lazy, so the app boots and all
non-training features work without the SDK installed.
Shared data logic lives in training/datautil.py — schema validation, field
mapping, and the "will this actually train?" fit check are written once and used
by every importer, so the local-file and HuggingFace paths can't drift apart.
training/secrets.py is the credential scanner, routes/export.py handles the
MLX export and its preflight, and routes/seeds.py holds the voice authoring +
expansion. Seeds live in storage/voice_seeds.json rather than the database —
there are only ever a few hundred, they're hand-authored, and plain text means
they can be edited, diffed, and backed up outside the app.
Frontend (/frontend) — React 18 + TypeScript + Vite + Tailwind. A friendly
"studio" design system (black / orange / white), a plain-language glossary with
tooltips on every training term, and Recharts for real loss/reward curves.
Views recover from backend outages on their own: failed requests retry with a backoff, and when the live WebSocket reconnects, any view still showing an error refetches. Reloading the page is not the fix.
./START_UI.shThis creates the backend virtualenv, installs dependencies, and launches both servers. Then open http://localhost:5173.
The script re-checks the Python dependencies on every run (not just when it
first creates .venv), so an interrupted install can't leave you with a venv
that exists but can't start. First run pulls PyTorch and the Tinker SDK, so
expect a few minutes.
Manual setup
# Backend — use Python 3.11–3.13 (the Tinker SDK isn't ready for 3.14+ yet)
cd backend
python3.12 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
# Exclude storage/ and logs/ from the reloader: the app writes its SQLite DB,
# datasets, and logs into this directory, and a plain --reload would restart
# the server every time a training step records a metric.
./.venv/bin/python -m uvicorn main:app --port 8000 \
--reload --reload-dir . \
--reload-exclude 'storage/*' --reload-exclude 'logs/*' \
--reload-exclude '*.db' --reload-exclude '*.log'
# Frontend (in another terminal)
cd frontend
npm install && npm run dev- To explore / learn: no. Demo mode runs a realistic, clearly-labeled practice run (it never saves a model or spends anything).
- To train or chat for real: yes — a
Tinker API key. Add it in Settings.
Real training also needs the
tinker+tinker_cookbookPython packages (installed fromrequirements.txt). - Optional: Ollama running locally makes the in-app training assistant conversational (otherwise it uses a simple built-in helper), and can act as a free teacher on the Voice page.
- Optional: an Anthropic API key enables Claude as the Voice teacher. Separate billing from Tinker; used nowhere else in the app, and never required.
"Can't reach the Thinker backend at http://localhost:8000"
The backend isn't running (or isn't up yet). The message is literal, not a UI glitch — if you see it on several pages at once, check the backend, not the app.
Thinker recovers on its own: failed requests retry with a backoff, and when the live WebSocket reconnects, any view still showing an error refetches. You should not have to reload the page. Confirm the backend directly with:
curl http://localhost:8000/api/healthBackend exits with No module named uvicorn
backend/.venv exists but is empty. Older versions of START_UI.sh only
installed dependencies when creating the venv, so an interrupted first run
left it unusable. The current script detects this; to fix it by hand:
cd backend && ./.venv/bin/python -m pip install -r requirements.txtPort 8000 or 5173 already in use
An earlier run is still alive: pkill -f 'uvicorn main:app' and pkill -f vite.
The Models page looks empty
"Your models" only lists models you've trained, so it's empty until your first run finishes. The 24 base models are under Model catalog — the page opens there automatically until you have models of your own.
Hugging Face search returns an error
Search needs huggingface-hub, and the API changed in 1.x. If you're on a
pinned older version, reinstall from requirements.txt.
Export says "this machine can't complete the export"
Working as intended — it checked before downloading. The usual blocker is disk: merging needs roughly 2.3× the base model's bf16 size (a 20B model wants ~92 GB). Free space up, or export a smaller base model. "RAM to merge" is a warning, not a blocker — it will still run, but it will swap and take considerably longer.
Export: "mlx-lm isn't installed"
Expected on a first run; the export installs it. MLX is Apple Silicon only, so the Export page won't work on an Intel Mac.
Everything says the backend is unreachable, but it's clearly running
Most likely a port collision on 8000. localhost resolves to either ::1
(IPv6) or 127.0.0.1 (IPv4), and uvicorn binds IPv4 only — so if any other
local project is listening on [::1]:8000, requests land there instead.
Thinker now defaults to the explicit http://127.0.0.1:8000 for this reason.
Check with:
lsof -nP -iTCP:8000 -sTCP:LISTENIf something else is squatting the port, stop it or change one of the two.
My upload says "needs field mapping"
Your columns aren't ones Thinker recognises (prompt/completion, or aliases
like instruction/response). Use the mapping step to point each required
field at the right column — the preview updates live, so you can see the real
training examples before importing.
A run sits at 0% for minutes
Watch the phase text under the progress bar — it names the exact step:
Authenticating with Tinker…, Asking Tinker for a <model> worker… (worker
allocation, legitimately slow — minutes is normal), or Loading the tokenizer….
Whichever it sits on is the one that's stuck. 0% CPU on the backend means it's
waiting on the network, not computing.
Hot reload doesn't pick up backend edits
--reload does not fire on every machine (watchfiles can fail to see changes
depending on the filesystem). The failure is silent and nasty: the server keeps
serving the old code, so fixes appear to do nothing. If an edit seems to have no
effect, restart the backend rather than trusting reload. To check, edit a .py
file and look for WatchFiles detected changes … Reloading in the output — if
it never appears, reload is not working for you.
A training run needs a real key
Demo mode needs nothing. Real runs need a Tinker key in Settings — but note
it's stored in your browser and sent per request, so it applies to that browser
only. Anything not driven from your browser session (a curl check, a
background job) will see no key and get a 401.
To make the key available server-side, copy .env.example to .env in the repo
root and set TINKER_API_KEY there. /api/health reports tinker_api_key for
whichever source applies to that request.
- HOW_TO_USE.md — a friendly walkthrough + glossary of training terms.
MIT