Repository navigation
Studio: fine-tune Laya and Cloudflare Clef decision models and serve them - #12585
Conversation
NilayYadav
left a comment
There was a problem hiding this comment.
Review + reproduction evidence
Pinned review: f7c5a510a6. 4 findings, fixed in 6fad6e5 and f7c5a51; 2 later suggestions listed below.
I ran the Train flow by hand on fresh upstream main and on this PR (Modal NVIDIA L4, Chromium 1440x900): pick convaiinnovations/laya, pick LocalLLaMA/typed-decisions, 30 steps, Start Training.
- main
faca4e2: Laya is set up as a chat model (QLoRA 4-bit, context 2,048, LR 2e-4, "Couldn't verify this model's settings"). Start Training opens the chat column mapping ("assign user and assistant") with Continue disabled, so no run starts. - PR
f7c5a51: Checkpoint picker (Multilingual), LoRA 16-bit, LR 8e-4. The run completes with "Held-out accuracy 0.20 -> 0.73, calibration error 0.20". Use in Decision API selectslaya-ft:convaiinnovations_laya-multilingual_1790990503, and Settings > API lists it under the stock models.
- Decision API on 300 test decisions (
agent_trace_observabilitytest split): fine-tune 171/300 (57.0%), stocklaya-multilingual87/300 (29.0%). - With the Decision API on GPU, the loaded device goes
cuda, thencpuduring a training run, thencudaafter it. - Chat models are unchanged: Qwen3-0.6B picks LoRA 16-bit 5e-5 on main and on the PR, and Qwen -> Laya -> Qwen and Llama-3.2-3B -> Laya -> Llama restore the chat settings exactly, also after a reload.
- Tests on Modal: backend 285 passed (main 56 failed); library 32 passed on L4.
Fixed in this PR
use_dora/use_loftqon a decision run were accepted and silently trained plain LoRA (a request withuse_dora: truereturned 200). It now returns 400: "Decision models train with plain LoRA, so DoRA and LoftQ are not available for them."- The Hub preflight checked only the repo root for
rl_agent_config.json, so a checkpoint inmodel_subfolderwas refused. It now checks the subfolder. FastDecisionModel.for_training(model)never turned gradient checkpointing back on. On the real checkpoint it wasFalseafter the call; it is nowTrue.test_toy_task_beats_the_base_modelfailed on transformers 5.16.1 (assert 1.0 > (0.8222 + 0.25)): the untrained tiny model already scored 0.82. The assertion now caps at 1.0.
Suggestions, not reproduced
unsloth/models/decision.py:496: the LoRA save deep-copies the encoder on the GPU before merging, about one extra encoder (0.6 to 0.8 GB). A run that only just fits could OOM at save time.studio/backend/routes/training.py:1109: a nested checkpoint behind a rootadapter_config.jsonor GGUF-only root is refused before the subfolder is checked.- A keyless caller with a mistyped model now gets 403 instead of 400 "Unknown model". This looks deliberate, so callers can't probe fine-tune names.
Not covered: Actions A/B (fork mirrors still queued), Mac, Windows, AMD, a non-owner account, and Stop/Cancel mid-run.
Conclusion: the feature works end to end on Linux + NVIDIA, and chat training is unchanged. It is ready once upstream CI is green.
|
Of the reds on head |
NilayYadav
left a comment
There was a problem hiding this comment.
Review follow-up
Pinned: 79527a56d0. The automated review rounds are now clean on this head. Three more findings are fixed since my last comment:
- LoRA save copied the encoder on the GPU (
95ed4bf). Measured on the real checkpoint, peak GPU memory duringsave_pretrained_mergedwas +0.64 GiB (Multilingual) and +0.88 GiB (English). With the merge on a CPU copy it is +0.00 GiB, the encoder stays on the GPU and the adapters are kept. - A decision checkpoint in a subfolder was refused when the repo root held an adapter or GGUF (
95ed4bf). Decision requests now go straight to the subfolder check. - TensorBoard was ignored for decision runs (
79527a5). On a real Studio, a run withenable_tensorboard: truecompleted with no event files. It now writesruns/decision-tb/events.out.tfevents.*. transformers 5.5 ignoreslogging_dir, so the run setsTENSORBOARD_LOGGING_DIR. The chat trainer passeslogging_dirthe same way on main, which may be worth a separate look.
Tests on Modal: backend 287 passed (58 fail on main), library 33 passed on L4.
Conclusion: no open findings. Ready once upstream CI is green.
…memory test on a real CUDA device
Clef and Clef-flash repos (config.json plus joint_head.*) are detected as decision models next to Laya. Training goes through FastDecisionModel with QLoRA allowed and the Qwen3.5 fast-path kernel setup the worker skips for Laya. The Decision API lists clef and clef-flash and Clef fine-tunes, and serves them from a spawn child that loads the backbone through Unsloth, so unloading returns the GPU memory. Clef waits for the GPU while a run trains instead of loading on CPU.
…2778 and the merged branch
…, and stop flagging exact-fit Clef prompts as truncated
|
Ran it again at The task is "does this chat message need a calculator": 1,200 train / 300 held-out synthetic rows, each with all three question types (choice: calculator / none / web_search / code; noul: does the user expect an exact number; score 0 to 2: how hard is the arithmetic). The point was to exercise the three heads and the serving side, not the dataset.
The merged folder reloads with identical numbers, and Per kind: every kind the dataset marks as calculator goes to 100%. The two drops are the dataset's doing, not the library's: Left: training loss over the 40 steps. Right: mean p(calculator) per kind before and after. The six arithmetic kinds move right, the rest stay put or move left. Two things from reading
Both are code reads at |
…ke decision tests portable to multi-GPU, Windows and Apple Silicon hosts
…run's training record when loading a Clef checkpoint
…er as soon as training starts



Studio can serve Laya decision models through the Decision API, but there is no way to train one on your own decisions, and Cloudflare's Clef and Clef-flash decision models are not supported at all. Picking convaiinnovations/laya or Cloudflare/clef-flash on the Train page treats it as a chat model, and
FastDecisionModel.from_pretrained("Cloudflare/clef-flash")does not exist.This PR adds decision model fine-tuning for both families, in Studio and in Python, and serves the results through the Decision API.
What you get
state,questionsandgoldcolumns. Decisions that cannot be used are skipped and counted.Held-out accuracy 0.68 -> 0.78, calibration error 0.03.A saved Clef fine-tune keeps Cloudflare's layout, so it also loads with
load_release_modelfrom the Clef repo, with the calibrated temperature folded into the head.Clef details
joint_schema_model.py(Apache-2.0) is vendored unmodified underunsloth/_vendor/clefwith its license and a sha256 manifest.unsloth/models/clef.pyimports the record encoding from it unchanged and adds a faster head forward.FastModel, so Qwen3.5 uses the flash-linear-attention gated delta kernels, causal-conv1d and Unsloth's compiled modules. On Clef-flash, against the plain torch path on one B200:in_projlayers stay in 16-bit, as in unsloth/Qwen3.8-27B-unsloth-bnb-4bit). Clef's backbone has a fine-tune merged into it, so it is quantized on load rather than taken from the stock Qwen 4-bit repos.DecisionTrainerterms from Cloudflare's write-up:label_smoothing,brier_weight,ordinal_weight,permute_fieldsandkl_weight. None beat seed noise on accuracy and calibration together in our runs, so all are off by default.Results
Clef-flash on LocalLLaMA/typed-decisions, 60 steps, LoRA r16, held out on 1,500 test decisions, one B200:
A QLoRA fine-tune saved merged and reloaded scores 78.0%. Clef (27B) QLoRA after 20 steps goes from 65.3% to 68.7% on 300 decisions with 26.4 GB peak.
Laya Multilingual on the same dataset, scored on its test split (2,000 decisions) on one NVIDIA L4:
Through Studio's Decision API over HTTP, stock Clef-flash answers the Clef README request in 84 to 160 ms once warm on a B200.
Other fixes
FastLanguageModelorFastModel, soDecisionTrainernow applies Unsloth's gradient accumulation fix itself. Without it, transformers 5.0 to 5.5 (Studio's default) divided accumulated gradients bygradient_accumulation_stepstwice.Tested on
Follow-ups: #12772 turns a plain language model (for example Qwen3.5-2B) into a decision model, and #12778 compiles the Laya encoder during training (about 1.23x faster LoRA steps).
Training needs Python 3.10 or newer and uses one GPU. It is not offered on Apple Silicon. If the Decision API is set to GPU, Laya answers on CPU while a run trains and moves back to the GPU when the run ends; Clef answers 503 while a run holds the GPU. Chat models, their training and the stock Decision API models work as before.
Screenshots
Before, on main: Laya is treated as a chat model.
After: the Train page shows Laya as a decision model, with a checkpoint picker and LoRA settings.
A finished run reports held-out accuracy and offers Use in Decision API.
Settings > API lists the fine-tunes next to the stock models.
UI: Decision API model choices
Settings > API > Decision API, the Model select (BEFORE = main at the merge base, AFTER = this branch). The three Laya checkpoints are unchanged; Clef-flash (19 GB) and Clef (55 GB) are added, and
/api/settings/systemonelists 5 models instead of 3.