From e2b7b4b54cfe13c6fe9acc7aec020fba9daba200 Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Thu, 19 Feb 2026 06:02:16 +0000 Subject: [PATCH 01/14] change train_on_completions to true --- studio/backend/assets/configs/model_defaults/default.yaml | 2 +- .../model_defaults/ernie/unsloth_ERNIE-4.5-VL-28B-A3B-PT.yaml | 2 +- .../model_defaults/falcon/tiiuae_Falcon-H1-0.5B-Instruct.yaml | 2 +- .../model_defaults/gemma/unsloth_codegemma-7b-bnb-4bit.yaml | 2 +- .../model_defaults/gemma/unsloth_gemma-2-27b-bnb-4bit.yaml | 2 +- .../assets/configs/model_defaults/gemma/unsloth_gemma-2-2b.yaml | 2 +- .../configs/model_defaults/gemma/unsloth_gemma-3-270m-it.yaml | 2 +- .../configs/model_defaults/gemma/unsloth_gemma-3-27b-it.yaml | 2 +- .../configs/model_defaults/gemma/unsloth_gemma-3-4b-it.yaml | 2 +- .../configs/model_defaults/gemma/unsloth_gemma-3-4b-pt.yaml | 2 +- .../configs/model_defaults/gemma/unsloth_gemma-3n-E4B-it.yaml | 2 +- .../configs/model_defaults/gemma/unsloth_gemma-3n-E4B.yaml | 2 +- .../configs/model_defaults/gpt-oss/unsloth_gpt-oss-120b.yaml | 2 +- .../llama/unsloth_Llama-3.2-11B-Vision-Instruct.yaml | 2 +- .../model_defaults/llama/unsloth_Llama-3.2-1B-Instruct.yaml | 2 +- .../model_defaults/llama/unsloth_Llama-3.2-3B-Instruct.yaml | 2 +- .../model_defaults/llama/unsloth_Llama-3.3-70B-Instruct.yaml | 2 +- .../llama/unsloth_Meta-Llama-3.1-70B-bnb-4bit.yaml | 2 +- .../llama/unsloth_Meta-Llama-3.1-8B-Instruct-bnb-4bit.yaml | 2 +- .../llama/unsloth_llama-3-8b-Instruct-bnb-4bit.yaml | 2 +- .../model_defaults/llama/unsloth_llama-3-8b-bnb-4bit.yaml | 2 +- .../assets/configs/model_defaults/llasa/unsloth_Llasa-3B.yaml | 2 +- .../mistral/unsloth_Magistral-Small-2509-unsloth-bnb-4bit.yaml | 2 +- .../mistral/unsloth_Ministral-3-3B-Instruct-2512.yaml | 2 +- .../mistral/unsloth_Mistral-Nemo-Base-2407-bnb-4bit.yaml | 2 +- .../mistral/unsloth_Mistral-Small-Instruct-2409.yaml | 2 +- .../model_defaults/mistral/unsloth_Pixtral-12B-2409.yaml | 2 +- .../mistral/unsloth_mistral-7b-instruct-v0.3-bnb-4bit.yaml | 2 +- .../mistral/unsloth_mistral-7b-v0.3-bnb-4bit.yaml | 2 +- .../model_defaults/other/OuteAI_Llama-OuteTTS-1.0-1B.yaml | 2 +- .../assets/configs/model_defaults/other/Spark-TTS-0.5B_LLM.yaml | 2 +- .../assets/configs/model_defaults/other/sesame_csm-1b.yaml | 2 +- .../configs/model_defaults/other/unsloth_PaddleOCR-VL.yaml | 2 +- .../other/unsloth_answerdotai_ModernBERT-large.yaml | 2 +- .../configs/model_defaults/other/unsloth_orpheus-3b-0.1-ft.yaml | 2 +- .../model_defaults/other/unsloth_tinyllama-bnb-4bit.yaml | 2 +- .../configs/model_defaults/other/unsloth_whisper-large-v3.yaml | 2 +- .../model_defaults/phi/unsloth_Phi-3-medium-4k-instruct.yaml | 2 +- .../model_defaults/phi/unsloth_Phi-3.5-mini-instruct.yaml | 2 +- .../configs/model_defaults/phi/unsloth_Phi-4-14B-Instruct.yaml | 2 +- .../assets/configs/model_defaults/phi/unsloth_Phi-4.yaml | 2 +- .../assets/configs/model_defaults/qwen/unsloth_Qwen2-7B.yaml | 2 +- .../model_defaults/qwen/unsloth_Qwen2-VL-7B-Instruct.yaml | 2 +- .../model_defaults/qwen/unsloth_Qwen2.5-1.5B-Instruct.yaml | 2 +- .../assets/configs/model_defaults/qwen/unsloth_Qwen2.5-7B.yaml | 2 +- .../qwen/unsloth_Qwen2.5-Coder-1.5B-Instruct.yaml | 2 +- .../model_defaults/qwen/unsloth_Qwen2.5-Coder-14B-Instruct.yaml | 2 +- .../qwen/unsloth_Qwen2.5-Coder-7B-Instruct-bnb-4bit.yaml | 2 +- .../qwen/unsloth_Qwen2.5-VL-7B-Instruct-bnb-4bit.yaml | 2 +- .../assets/configs/model_defaults/qwen/unsloth_Qwen3-0.6B.yaml | 2 +- .../qwen/unsloth_Qwen3-14B-Base-unsloth-bnb-4bit.yaml | 2 +- .../assets/configs/model_defaults/qwen/unsloth_Qwen3-14B.yaml | 2 +- .../assets/configs/model_defaults/qwen/unsloth_Qwen3-32B.yaml | 2 +- .../model_defaults/qwen/unsloth_Qwen3-4B-Instruct-2507.yaml | 2 +- .../qwen/unsloth_Qwen3-VL-8B-Instruct-unsloth-bnb-4bit.yaml | 2 +- 55 files changed, 55 insertions(+), 55 deletions(-) diff --git a/studio/backend/assets/configs/model_defaults/default.yaml b/studio/backend/assets/configs/model_defaults/default.yaml index 7b918b9387..ceb332bc14 100644 --- a/studio/backend/assets/configs/model_defaults/default.yaml +++ b/studio/backend/assets/configs/model_defaults/default.yaml @@ -14,7 +14,7 @@ training: weight_decay: 0.01 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/ernie/unsloth_ERNIE-4.5-VL-28B-A3B-PT.yaml b/studio/backend/assets/configs/model_defaults/ernie/unsloth_ERNIE-4.5-VL-28B-A3B-PT.yaml index ef417c8410..1032449e8c 100644 --- a/studio/backend/assets/configs/model_defaults/ernie/unsloth_ERNIE-4.5-VL-28B-A3B-PT.yaml +++ b/studio/backend/assets/configs/model_defaults/ernie/unsloth_ERNIE-4.5-VL-28B-A3B-PT.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/falcon/tiiuae_Falcon-H1-0.5B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/falcon/tiiuae_Falcon-H1-0.5B-Instruct.yaml index d45526bcf7..eafc7065d5 100644 --- a/studio/backend/assets/configs/model_defaults/falcon/tiiuae_Falcon-H1-0.5B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/falcon/tiiuae_Falcon-H1-0.5B-Instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.01 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: false optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_codegemma-7b-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_codegemma-7b-bnb-4bit.yaml index 185b91ecf4..c4c73ac028 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_codegemma-7b-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_codegemma-7b-bnb-4bit.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-27b-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-27b-bnb-4bit.yaml index f3ed24fd2e..55f503d11b 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-27b-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-27b-bnb-4bit.yaml @@ -14,7 +14,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-2b.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-2b.yaml index 70c86dad1b..9374a2cc82 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-2b.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-2-2b.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.01 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-270m-it.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-270m-it.yaml index ccdc19111e..f1aa18e9d2 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-270m-it.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-270m-it.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-27b-it.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-27b-it.yaml index db1db080a8..e1ed581ca4 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-27b-it.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-27b-it.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-it.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-it.yaml index a4130eb8b7..bf7457831e 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-it.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-it.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-pt.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-pt.yaml index f679b86b81..5f0a7b26ce 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-pt.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3-4b-pt.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: true optim: "adamw_torch_fused" lr_scheduler_type: "cosine" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B-it.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B-it.yaml index 23ef35603d..74c58d139d 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B-it.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B-it.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B.yaml b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B.yaml index ebad334d5e..1ca686aea7 100644 --- a/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B.yaml +++ b/studio/backend/assets/configs/model_defaults/gemma/unsloth_gemma-3n-E4B.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: true optim: "adamw_torch_fused" lr_scheduler_type: "cosine" diff --git a/studio/backend/assets/configs/model_defaults/gpt-oss/unsloth_gpt-oss-120b.yaml b/studio/backend/assets/configs/model_defaults/gpt-oss/unsloth_gpt-oss-120b.yaml index da41fb3009..f8a8f5a3c9 100644 --- a/studio/backend/assets/configs/model_defaults/gpt-oss/unsloth_gpt-oss-120b.yaml +++ b/studio/backend/assets/configs/model_defaults/gpt-oss/unsloth_gpt-oss-120b.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-11B-Vision-Instruct.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-11B-Vision-Instruct.yaml index 6d9f2c8ccb..1a7a91e56f 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-11B-Vision-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-11B-Vision-Instruct.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-1B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-1B-Instruct.yaml index e00d5b4cb4..61a2033ee8 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-1B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-1B-Instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.01 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: true optim: "adamw_torch" lr_scheduler_type: "cosine" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-3B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-3B-Instruct.yaml index ad427a345a..7117aa5ec0 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-3B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.2-3B-Instruct.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.3-70B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.3-70B-Instruct.yaml index 8d676bbbc9..7d998ed1c8 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.3-70B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_Llama-3.3-70B-Instruct.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-70B-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-70B-bnb-4bit.yaml index 6b3158da02..633869022a 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-70B-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-70B-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-8B-Instruct-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-8B-Instruct-bnb-4bit.yaml index dcf5a5f702..cbf432dacb 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-8B-Instruct-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_Meta-Llama-3.1-8B-Instruct-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-Instruct-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-Instruct-bnb-4bit.yaml index d2e398d03d..f148814e96 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-Instruct-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-Instruct-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-bnb-4bit.yaml index ad3bd0b879..bb235595f6 100644 --- a/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/llama/unsloth_llama-3-8b-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/llasa/unsloth_Llasa-3B.yaml b/studio/backend/assets/configs/model_defaults/llasa/unsloth_Llasa-3B.yaml index 41217c6c9f..b315a9aaf7 100644 --- a/studio/backend/assets/configs/model_defaults/llasa/unsloth_Llasa-3B.yaml +++ b/studio/backend/assets/configs/model_defaults/llasa/unsloth_Llasa-3B.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Magistral-Small-2509-unsloth-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Magistral-Small-2509-unsloth-bnb-4bit.yaml index 882395745c..2d4832e839 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Magistral-Small-2509-unsloth-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Magistral-Small-2509-unsloth-bnb-4bit.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Ministral-3-3B-Instruct-2512.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Ministral-3-3B-Instruct-2512.yaml index 4c7ead9985..ca6609cda8 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Ministral-3-3B-Instruct-2512.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Ministral-3-3B-Instruct-2512.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Nemo-Base-2407-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Nemo-Base-2407-bnb-4bit.yaml index 99fc67dcff..934959fc83 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Nemo-Base-2407-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Nemo-Base-2407-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Small-Instruct-2409.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Small-Instruct-2409.yaml index e0146c374e..1d712a6c8f 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Small-Instruct-2409.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Mistral-Small-Instruct-2409.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Pixtral-12B-2409.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Pixtral-12B-2409.yaml index 828ba79c76..bcd0d20c8c 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_Pixtral-12B-2409.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_Pixtral-12B-2409.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "paged_adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-instruct-v0.3-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-instruct-v0.3-bnb-4bit.yaml index 74527b21af..a02f8db2f3 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-instruct-v0.3-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-instruct-v0.3-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-v0.3-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-v0.3-bnb-4bit.yaml index 26810445bf..c0762d9864 100644 --- a/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-v0.3-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/mistral/unsloth_mistral-7b-v0.3-bnb-4bit.yaml @@ -14,7 +14,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/OuteAI_Llama-OuteTTS-1.0-1B.yaml b/studio/backend/assets/configs/model_defaults/other/OuteAI_Llama-OuteTTS-1.0-1B.yaml index 9db476f4dc..7a594b7eaf 100644 --- a/studio/backend/assets/configs/model_defaults/other/OuteAI_Llama-OuteTTS-1.0-1B.yaml +++ b/studio/backend/assets/configs/model_defaults/other/OuteAI_Llama-OuteTTS-1.0-1B.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/Spark-TTS-0.5B_LLM.yaml b/studio/backend/assets/configs/model_defaults/other/Spark-TTS-0.5B_LLM.yaml index bf6b1f7247..3c8215ba25 100644 --- a/studio/backend/assets/configs/model_defaults/other/Spark-TTS-0.5B_LLM.yaml +++ b/studio/backend/assets/configs/model_defaults/other/Spark-TTS-0.5B_LLM.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/sesame_csm-1b.yaml b/studio/backend/assets/configs/model_defaults/other/sesame_csm-1b.yaml index 803eeebb75..d3249ef257 100644 --- a/studio/backend/assets/configs/model_defaults/other/sesame_csm-1b.yaml +++ b/studio/backend/assets/configs/model_defaults/other/sesame_csm-1b.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/unsloth_PaddleOCR-VL.yaml b/studio/backend/assets/configs/model_defaults/other/unsloth_PaddleOCR-VL.yaml index a971f080b6..e1fbc08e4d 100644 --- a/studio/backend/assets/configs/model_defaults/other/unsloth_PaddleOCR-VL.yaml +++ b/studio/backend/assets/configs/model_defaults/other/unsloth_PaddleOCR-VL.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/unsloth_answerdotai_ModernBERT-large.yaml b/studio/backend/assets/configs/model_defaults/other/unsloth_answerdotai_ModernBERT-large.yaml index c79841390d..bb30a2052c 100644 --- a/studio/backend/assets/configs/model_defaults/other/unsloth_answerdotai_ModernBERT-large.yaml +++ b/studio/backend/assets/configs/model_defaults/other/unsloth_answerdotai_ModernBERT-large.yaml @@ -14,7 +14,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/unsloth_orpheus-3b-0.1-ft.yaml b/studio/backend/assets/configs/model_defaults/other/unsloth_orpheus-3b-0.1-ft.yaml index de5fb640ba..94596b65b8 100644 --- a/studio/backend/assets/configs/model_defaults/other/unsloth_orpheus-3b-0.1-ft.yaml +++ b/studio/backend/assets/configs/model_defaults/other/unsloth_orpheus-3b-0.1-ft.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/unsloth_tinyllama-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/other/unsloth_tinyllama-bnb-4bit.yaml index dd4525bba3..d35ac75b81 100644 --- a/studio/backend/assets/configs/model_defaults/other/unsloth_tinyllama-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/other/unsloth_tinyllama-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.1 random_seed: 3407 packing: true - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/other/unsloth_whisper-large-v3.yaml b/studio/backend/assets/configs/model_defaults/other/unsloth_whisper-large-v3.yaml index f0c49f363d..3a97baebd9 100644 --- a/studio/backend/assets/configs/model_defaults/other/unsloth_whisper-large-v3.yaml +++ b/studio/backend/assets/configs/model_defaults/other/unsloth_whisper-large-v3.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3-medium-4k-instruct.yaml b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3-medium-4k-instruct.yaml index c39368fbd2..3c428c3406 100644 --- a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3-medium-4k-instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3-medium-4k-instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3.5-mini-instruct.yaml b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3.5-mini-instruct.yaml index 076165c12a..8e13ef8a45 100644 --- a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3.5-mini-instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-3.5-mini-instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4-14B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4-14B-Instruct.yaml index 91d53180fa..4d67de8bab 100644 --- a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4-14B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4-14B-Instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4.yaml b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4.yaml index 3b829649a8..9159ce4dc7 100644 --- a/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4.yaml +++ b/studio/backend/assets/configs/model_defaults/phi/unsloth_Phi-4.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-7B.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-7B.yaml index 56d4f35998..e0aff114ca 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-7B.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-7B.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-VL-7B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-VL-7B-Instruct.yaml index 86cb03ff25..6cee3d0949 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-VL-7B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2-VL-7B-Instruct.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-1.5B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-1.5B-Instruct.yaml index 3a3259c8fd..1034f6d37f 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-1.5B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-1.5B-Instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 42 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-7B.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-7B.yaml index 2a8f023e76..876514e456 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-7B.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-7B.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-1.5B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-1.5B-Instruct.yaml index a92d0d6047..d41e70d25f 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-1.5B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-1.5B-Instruct.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-14B-Instruct.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-14B-Instruct.yaml index eff25af51c..4af3af5dfc 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-14B-Instruct.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-14B-Instruct.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "paged_adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-7B-Instruct-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-7B-Instruct-bnb-4bit.yaml index 3ab14078a9..ca1b355c40 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-7B-Instruct-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-Coder-7B-Instruct-bnb-4bit.yaml @@ -15,7 +15,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-VL-7B-Instruct-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-VL-7B-Instruct-bnb-4bit.yaml index 4cfa83f853..bd54b1d015 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-VL-7B-Instruct-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen2.5-VL-7B-Instruct-bnb-4bit.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-0.6B.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-0.6B.yaml index d8f9d3a73b..b1179b2ce6 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-0.6B.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-0.6B.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B-Base-unsloth-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B-Base-unsloth-bnb-4bit.yaml index 7f66711413..77d7c8527e 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B-Base-unsloth-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B-Base-unsloth-bnb-4bit.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B.yaml index 0f3403e09e..7b92c767de 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-14B.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-32B.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-32B.yaml index f019922017..15e8dab80e 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-32B.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-32B.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-4B-Instruct-2507.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-4B-Instruct-2507.yaml index a8552cc85c..2724719294 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-4B-Instruct-2507.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-4B-Instruct-2507.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" diff --git a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-VL-8B-Instruct-unsloth-bnb-4bit.yaml b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-VL-8B-Instruct-unsloth-bnb-4bit.yaml index 5530047541..5b47c3bdd2 100644 --- a/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-VL-8B-Instruct-unsloth-bnb-4bit.yaml +++ b/studio/backend/assets/configs/model_defaults/qwen/unsloth_Qwen3-VL-8B-Instruct-unsloth-bnb-4bit.yaml @@ -16,7 +16,7 @@ training: weight_decay: 0.001 random_seed: 3407 packing: false - train_on_completions: false + train_on_completions: true gradient_checkpointing: "unsloth" optim: "adamw_8bit" lr_scheduler_type: "linear" From 21ee32df408b1c77d62ae9297ed7f7325de83966 Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Thu, 19 Feb 2026 06:40:40 +0000 Subject: [PATCH 02/14] Auto-set trainOnCompletions based on vision/multimodal state, default all model configs to true, and re-fetch model defaults on page reload --- .../training/stores/training-config-store.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/studio/frontend/src/features/training/stores/training-config-store.ts b/studio/frontend/src/features/training/stores/training-config-store.ts index b98d2a3c7f..136cf3a8db 100644 --- a/studio/frontend/src/features/training/stores/training-config-store.ts +++ b/studio/frontend/src/features/training/stores/training-config-store.ts @@ -43,11 +43,16 @@ let _datasetCheckController: AbortController | null = null; // AbortController for in-flight model default loads. let _modelConfigController: AbortController | null = null; +// Track whether the user has manually toggled trainOnCompletions +// since the last auto-set (model load or dataset change). +let _trainOnCompletionsManuallySet = false; + const NON_PERSISTED_STATE_KEYS: ReadonlySet = new Set([ "modelType", "isCheckingVision", "isLoadingModelDefaults", "modelDefaultsError", + "modelDefaultsAppliedFor", "isCheckingDataset", ]); @@ -102,6 +107,7 @@ export const useTrainingConfigStore = create()( if (controller.signal.aborted) return; if (get().selectedModel !== modelName) return; + _trainOnCompletionsManuallySet = false; set({ ...mapBackendModelConfigToTrainingPatch(modelDetails.config), isVisionModel: modelDetails.is_vision, @@ -196,6 +202,7 @@ export const useTrainingConfigStore = create()( setDataset: (dataset) => { _datasetCheckController?.abort(); _datasetCheckController = null; + _trainOnCompletionsManuallySet = false; set({ dataset, datasetSubset: null, @@ -208,6 +215,7 @@ export const useTrainingConfigStore = create()( setDatasetSubset: (datasetSubset) => { _datasetCheckController?.abort(); _datasetCheckController = null; + _trainOnCompletionsManuallySet = false; set({ datasetSubset, datasetSplit: null, @@ -245,10 +253,21 @@ export const useTrainingConfigStore = create()( }) .then((res) => { if (controller.signal.aborted) return; - set({ - isDatasetMultimodal: !!res.is_multimodal, + const isMultimodal = !!res.is_multimodal; + const updates: Record = { + isDatasetMultimodal: isMultimodal, isCheckingDataset: false, - }); + }; + // Auto-set trainOnCompletions unless the user manually toggled it. + if (!_trainOnCompletionsManuallySet) { + const { isVisionModel } = get(); + if (isVisionModel && isMultimodal) { + updates.trainOnCompletions = false; + } + // For non-vision or vision+text, keep the backend default + // (already applied on model load). + } + set(updates); }) .catch(() => { if (controller.signal.aborted) return; @@ -274,8 +293,10 @@ export const useTrainingConfigStore = create()( setSaveSteps: (saveSteps) => set({ saveSteps }), setEvalSteps: (evalSteps) => set({ evalSteps }), setPacking: (packing) => set({ packing }), - setTrainOnCompletions: (trainOnCompletions) => - set({ trainOnCompletions }), + setTrainOnCompletions: (trainOnCompletions) => { + _trainOnCompletionsManuallySet = true; + set({ trainOnCompletions }); + }, setGradientCheckpointing: (gradientCheckpointing) => set({ gradientCheckpointing }), setRandomSeed: (randomSeed) => set({ randomSeed }), From ee6d33fa324893aaa40c117e817f5044639c3235 Mon Sep 17 00:00:00 2001 From: imagineer99 Date: Thu, 19 Feb 2026 08:13:16 +0000 Subject: [PATCH 03/14] feat: add download progress indicators for dataset preview and training overlay --- .../features/studio/sections/dataset-preview-dialog.tsx | 8 +++++++- .../src/features/studio/sections/progress-section-lib.ts | 6 ++++++ studio/frontend/src/features/studio/training-view.tsx | 2 ++ studio/frontend/src/features/training/types/runtime.ts | 2 ++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx b/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx index 648adfbc89..e135f301af 100644 --- a/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx +++ b/studio/frontend/src/features/studio/sections/dataset-preview-dialog.tsx @@ -63,6 +63,7 @@ export function DatasetPreviewDialog({ const mappingOk = !!manualMapping.input && !!manualMapping.output; const leftLabel = isVlm ? "Image" : "Input"; const rightLabel = isVlm ? "Text" : "Output"; + const isHfDataset = !!datasetName && datasetName.includes("/"); useEffect(() => { if (!manualMapping.input || !manualMapping.output) return; @@ -266,8 +267,13 @@ export function DatasetPreviewDialog({

- Loading preview... + {isHfDataset ? "Fetching dataset preview from Hugging Face..." : "Loading preview..."}

+ {isHfDataset && ( +

+ This may take a moment for large datasets +

+ )} )} diff --git a/studio/frontend/src/features/studio/sections/progress-section-lib.ts b/studio/frontend/src/features/studio/sections/progress-section-lib.ts index d22212f70b..bff5daed81 100644 --- a/studio/frontend/src/features/studio/sections/progress-section-lib.ts +++ b/studio/frontend/src/features/studio/sections/progress-section-lib.ts @@ -2,6 +2,8 @@ import type { TrainingPhase } from "@/features/training"; export const phaseLabel: Record = { idle: "Idle", + downloading_model: "Downloading model", + downloading_dataset: "Downloading dataset", loading_model: "Loading model", loading_dataset: "Loading dataset", configuring: "Configuring", @@ -13,6 +15,10 @@ export const phaseLabel: Record = { export const phaseColors: Record = { idle: "bg-muted text-muted-foreground", + downloading_model: + "bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-300", + downloading_dataset: + "bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-300", loading_model: "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300", loading_dataset: diff --git a/studio/frontend/src/features/studio/training-view.tsx b/studio/frontend/src/features/studio/training-view.tsx index 9e83a2f662..fe616ea88a 100644 --- a/studio/frontend/src/features/studio/training-view.tsx +++ b/studio/frontend/src/features/studio/training-view.tsx @@ -19,6 +19,8 @@ export function TrainingView(): ReactElement { ); const isPreparingPhase = + runtime.phase === "downloading_model" || + runtime.phase === "downloading_dataset" || runtime.phase === "loading_model" || runtime.phase === "loading_dataset" || runtime.phase === "configuring"; diff --git a/studio/frontend/src/features/training/types/runtime.ts b/studio/frontend/src/features/training/types/runtime.ts index df24c020ca..7ebf09518d 100644 --- a/studio/frontend/src/features/training/types/runtime.ts +++ b/studio/frontend/src/features/training/types/runtime.ts @@ -1,5 +1,7 @@ export type TrainingPhase = | "idle" + | "downloading_model" + | "downloading_dataset" | "loading_model" | "loading_dataset" | "configuring" From 18c41c2b0846c3e9d0f60e57d882826d5259e17d Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Thu, 19 Feb 2026 11:25:54 +0000 Subject: [PATCH 04/14] Simplify dataset check to 2-tier, improve multimodal detection, auto-set trainOnCompletions, recheck dataset on reload --- studio/backend/routes/datasets.py | 130 ++++++++++++------ .../utils/datasets/format_detection.py | 71 +++++++++- .../src/features/studio/studio-page.tsx | 6 +- .../training/stores/training-config-store.ts | 93 ++++++++----- .../src/features/training/types/config.ts | 1 + 5 files changed, 222 insertions(+), 79 deletions(-) diff --git a/studio/backend/routes/datasets.py b/studio/backend/routes/datasets.py index 8b94ddefdc..39119f1123 100644 --- a/studio/backend/routes/datasets.py +++ b/studio/backend/routes/datasets.py @@ -70,33 +70,48 @@ def _serialize_preview_rows(rows): # --- Endpoints --- +# Recognized data-file extensions for the single-file fallback approach. +DATA_EXTS = ( + '.parquet', + '.json', '.jsonl', + '.csv', '.tsv', + '.txt', + '.arrow', + '.tar', '.tar.gz', '.tgz', + '.gz', '.zst', + '.zip', +) + + @router.post("/check-format", response_model=CheckFormatResponse) -async def check_format(request: CheckFormatRequest): +def check_format(request: CheckFormatRequest): """ Check if a dataset requires manual column mapping. - - This is a lightweight check that streams only the first N rows, - runs format detection, and (if processable) returns processed - preview samples. The full dataset is re-processed at training time. - - For HuggingFace datasets we use streaming mode so we never download - the entire dataset — only the rows we actually need are fetched. + + Strategy for HuggingFace datasets: + 1. list_repo_files → pick the first data file → load_dataset(data_files=[…]) + Avoids resolving thousands of files; typically ~2-4 s. + 2. Full streaming load_dataset as a last-resort fallback. + + Local files are loaded directly. + + Using a plain `def` (not async) so FastAPI runs this in a thread-pool, + preventing any blocking IO from freezing the event loop. """ try: from itertools import islice from datasets import Dataset, load_dataset from utils.datasets import format_dataset - + PREVIEW_SIZE = 10 - + logger.info(f"Checking format for dataset: {request.dataset_name}") - - # Load dataset + dataset_path = Path(request.dataset_name) total_rows = None - + if dataset_path.exists(): - # Local dataset — direct load is fine (files are local) + # ── Local file ────────────────────────────────────────── if dataset_path.suffix in ['.json', '.jsonl']: dataset = load_dataset('json', data_files=str(dataset_path), split=request.train_split) elif dataset_path.suffix == '.csv': @@ -111,54 +126,83 @@ async def check_format(request: CheckFormatRequest): total_rows = len(dataset) preview_slice = dataset.select(range(min(PREVIEW_SIZE, total_rows))) else: - # HuggingFace dataset — use STREAMING to avoid downloading everything - load_kwargs = {"path": request.dataset_name, "split": request.train_split, "streaming": True} - if request.subset: - load_kwargs["name"] = request.subset - if request.hf_token: - load_kwargs["token"] = request.hf_token - - streamed_ds = load_dataset(**load_kwargs) - - # Take only the first PREVIEW_SIZE rows from the stream - rows = list(islice(streamed_ds, PREVIEW_SIZE)) - if not rows: - raise HTTPException( - status_code=400, - detail="Dataset appears to be empty or could not be streamed" + # ── HuggingFace dataset ───────────────────────────────── + # Tier 1: list_repo_files → load only the first data file + preview_slice = None + + try: + from huggingface_hub import HfApi + api = HfApi() + repo_files = api.list_repo_files( + request.dataset_name, + repo_type="dataset", + token=request.hf_token or None, ) - - # Convert list-of-dicts into a proper Dataset for downstream compat - preview_slice = Dataset.from_list(rows) - # total_rows unknown in streaming mode + data_files = [f for f in repo_files if any(f.endswith(ext) for ext in DATA_EXTS)] + + if data_files: + first_file = data_files[0] + logger.info(f"Tier 1: loading single file {first_file}") + load_kwargs = { + "path": request.dataset_name, + "data_files": [first_file], + "split": "train", + "streaming": True, + } + if request.hf_token: + load_kwargs["token"] = request.hf_token + + streamed_ds = load_dataset(**load_kwargs) + rows = list(islice(streamed_ds, PREVIEW_SIZE)) + if rows: + preview_slice = Dataset.from_list(rows) + except Exception as e: + logger.warning(f"Tier 1 (single-file) failed: {e}") + + if preview_slice is None: + # Tier 2: full streaming (resolves all files — slow for large repos) + logger.info("Tier 2: falling back to full streaming load_dataset") + load_kwargs = {"path": request.dataset_name, "split": request.train_split, "streaming": True} + if request.subset: + load_kwargs["name"] = request.subset + if request.hf_token: + load_kwargs["token"] = request.hf_token + + streamed_ds = load_dataset(**load_kwargs) + + rows = list(islice(streamed_ds, PREVIEW_SIZE)) + if not rows: + raise HTTPException( + status_code=400, + detail="Dataset appears to be empty or could not be streamed" + ) + + preview_slice = Dataset.from_list(rows) total_rows = None - + # Run lightweight format check on the preview slice result = check_dataset_format(preview_slice, is_vlm=request.is_vlm) - - logger.info(f"Format check result: requires_mapping={result['requires_manual_mapping']}, format={result['detected_format']}") - + + logger.info(f"Format check result: requires_mapping={result['requires_manual_mapping']}, format={result['detected_format']}, is_multimodal={result.get('is_multimodal', False)}") + # Generate preview samples preview_samples = None if not result["requires_manual_mapping"]: - # Format detected — return processed preview try: format_result = format_dataset( preview_slice, format_type="auto", custom_format_mapping=result.get("suggested_mapping"), + num_proc=1, # Only 10 preview rows — no need for multiprocessing ) processed = format_result["dataset"] preview_samples = _serialize_preview_rows(processed) except Exception as e: logger.warning(f"Processed preview generation failed (non-fatal): {e}") - # Fall back to raw samples so frontend still has something preview_samples = _serialize_preview_rows(preview_slice) else: - # Format detection failed — return raw samples so user can - # see actual data and map columns in the frontend preview_samples = _serialize_preview_rows(preview_slice) - + return CheckFormatResponse( requires_manual_mapping=result["requires_manual_mapping"], detected_format=result["detected_format"], @@ -171,7 +215,7 @@ async def check_format(request: CheckFormatRequest): preview_samples=preview_samples, total_rows=total_rows, ) - + except HTTPException: raise except Exception as e: diff --git a/studio/backend/utils/datasets/format_detection.py b/studio/backend/utils/datasets/format_detection.py index ae1b6a388d..9283ea5d55 100644 --- a/studio/backend/utils/datasets/format_detection.py +++ b/studio/backend/utils/datasets/format_detection.py @@ -328,6 +328,11 @@ def detect_multimodal_dataset(dataset): """ Detects if dataset contains multimodal data (images/vision). + Two-pass approach: + 1. Column-name heuristic (fast): checks for keywords like 'image', 'img', 'pixel'. + 2. Value-type inspection (reliable): checks if actual values are PIL Images, + bytes with image headers, or HF Image-feature dicts. + Returns: dict: { "is_multimodal": bool, @@ -339,11 +344,16 @@ def detect_multimodal_dataset(dataset): column_names = list(sample.keys()) # Keywords that indicate multimodal/image data - multimodal_keywords = ['image', 'img', 'pixel'] + multimodal_keywords = [ + 'image', 'img', 'pixel', + 'jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'tiff', 'svg', + 'photo', 'pic', 'picture', 'visual', + ] multimodal_columns = [] modality_types = set() + # ── Pass 1: column-name heuristic ─────────────────────── for col_name in column_names: col_lower = col_name.lower() @@ -353,6 +363,17 @@ def detect_multimodal_dataset(dataset): modality_types.add(keyword) break # Don't check other keywords for this column + # ── Pass 2: inspect actual values ─────────────────────── + # Catches columns with non-obvious names (e.g. "jpg", "photo", "pic") + already_detected = set(multimodal_columns) + for col_name in column_names: + if col_name in already_detected: + continue + value = sample[col_name] + if _is_image_value(value): + multimodal_columns.append(col_name) + modality_types.add("image") + return { "is_multimodal": len(multimodal_columns) > 0, "multimodal_columns": multimodal_columns, @@ -360,6 +381,54 @@ def detect_multimodal_dataset(dataset): } +def _is_image_value(value) -> bool: + """Check if a single sample value looks like image data.""" + if value is None: + return False + + # PIL Image instance + try: + from PIL.Image import Image as PILImage + if isinstance(value, PILImage): + return True + except ImportError: + pass + + # HF datasets Image feature stores decoded images as PIL or dicts with + # {"bytes": b"...", "path": "..."} when not yet decoded + if isinstance(value, dict): + if "bytes" in value and "path" in value: + return True + + # Raw bytes with a known image magic header + if isinstance(value, (bytes, bytearray)): + return _has_image_header(value) + + return False + + +def _has_image_header(data: bytes) -> bool: + """Quick magic-byte check for common image formats.""" + if len(data) < 4: + return False + # JPEG + if data[:2] == b'\xff\xd8': + return True + # PNG + if data[:4] == b'\x89PNG': + return True + # GIF + if data[:3] == b'GIF': + return True + # WebP + if data[:4] == b'RIFF' and len(data) >= 12 and data[8:12] == b'WEBP': + return True + # BMP + if data[:2] == b'BM': + return True + return False + + def detect_vlm_dataset_structure(dataset): """ Detects if VLM dataset is: diff --git a/studio/frontend/src/features/studio/studio-page.tsx b/studio/frontend/src/features/studio/studio-page.tsx index e9f01901b7..26353b9b7e 100644 --- a/studio/frontend/src/features/studio/studio-page.tsx +++ b/studio/frontend/src/features/studio/studio-page.tsx @@ -37,6 +37,9 @@ export function StudioPage(): ReactElement { const ensureModelDefaultsLoaded = useTrainingConfigStore( (s) => s.ensureModelDefaultsLoaded, ); + const ensureDatasetChecked = useTrainingConfigStore( + (s) => s.ensureDatasetChecked, + ); const dialogOpen = useDatasetPreviewDialogStore((s) => s.open); const dialogMode = useDatasetPreviewDialogStore((s) => s.mode); const dialogInitial = useDatasetPreviewDialogStore((s) => s.initialData); @@ -65,7 +68,8 @@ export function StudioPage(): ReactElement { useEffect(() => { ensureModelDefaultsLoaded(); - }, [selectedModel, ensureModelDefaultsLoaded]); + ensureDatasetChecked(); + }, [selectedModel, ensureModelDefaultsLoaded, ensureDatasetChecked]); return (
diff --git a/studio/frontend/src/features/training/stores/training-config-store.ts b/studio/frontend/src/features/training/stores/training-config-store.ts index 136cf3a8db..67317dbc09 100644 --- a/studio/frontend/src/features/training/stores/training-config-store.ts +++ b/studio/frontend/src/features/training/stores/training-config-store.ts @@ -54,6 +54,8 @@ const NON_PERSISTED_STATE_KEYS: ReadonlySet = new Set "modelDefaultsError", "modelDefaultsAppliedFor", "isCheckingDataset", + "isDatasetMultimodal", + "trainOnCompletions", ]); function partializePersistedState( @@ -108,8 +110,16 @@ export const useTrainingConfigStore = create()( if (get().selectedModel !== modelName) return; _trainOnCompletionsManuallySet = false; + const patch = mapBackendModelConfigToTrainingPatch(modelDetails.config); + + // If vision model + multimodal dataset already known, override + // trainOnCompletions to false regardless of backend default. + if (modelDetails.is_vision && get().isDatasetMultimodal === true) { + patch.trainOnCompletions = false; + } + set({ - ...mapBackendModelConfigToTrainingPatch(modelDetails.config), + ...patch, isVisionModel: modelDetails.is_vision, isLoadingModelDefaults: false, isCheckingVision: false, @@ -145,6 +155,40 @@ export const useTrainingConfigStore = create()( }); }; + const runDatasetCheck = (datasetName: string, split: string) => { + _datasetCheckController?.abort(); + const controller = new AbortController(); + _datasetCheckController = controller; + set({ isCheckingDataset: true }); + + const state = get(); + checkDatasetFormat({ + datasetName, + hfToken: state.hfToken.trim() || null, + subset: state.datasetSubset, + split, + }) + .then((res) => { + if (controller.signal.aborted) return; + const isMultimodal = !!res.is_multimodal; + const updates: Record = { + isDatasetMultimodal: isMultimodal, + isCheckingDataset: false, + }; + if (!_trainOnCompletionsManuallySet) { + const { isVisionModel } = get(); + if (isVisionModel && isMultimodal) { + updates.trainOnCompletions = false; + } + } + set(updates); + }) + .catch(() => { + if (controller.signal.aborted) return; + set({ isDatasetMultimodal: null, isCheckingDataset: false }); + }); + }; + return { ...initialState, setStep: (step) => set({ currentStep: step }), @@ -225,8 +269,6 @@ export const useTrainingConfigStore = create()( }); }, setDatasetSplit: (datasetSplit) => { - _datasetCheckController?.abort(); - _datasetCheckController = null; set({ datasetSplit, datasetManualMapping: emptyManualMapping(), @@ -241,38 +283,21 @@ export const useTrainingConfigStore = create()( : state.uploadedFile; if (!datasetName) return; - const controller = new AbortController(); - _datasetCheckController = controller; - set({ isCheckingDataset: true }); + runDatasetCheck(datasetName, datasetSplit || "train"); + }, + ensureDatasetChecked: () => { + const state = get(); + if (state.isCheckingDataset) return; + if (state.isDatasetMultimodal !== null) return; - checkDatasetFormat({ - datasetName, - hfToken: state.hfToken.trim() || null, - subset: state.datasetSubset, - split: datasetSplit || "train", - }) - .then((res) => { - if (controller.signal.aborted) return; - const isMultimodal = !!res.is_multimodal; - const updates: Record = { - isDatasetMultimodal: isMultimodal, - isCheckingDataset: false, - }; - // Auto-set trainOnCompletions unless the user manually toggled it. - if (!_trainOnCompletionsManuallySet) { - const { isVisionModel } = get(); - if (isVisionModel && isMultimodal) { - updates.trainOnCompletions = false; - } - // For non-vision or vision+text, keep the backend default - // (already applied on model load). - } - set(updates); - }) - .catch(() => { - if (controller.signal.aborted) return; - set({ isDatasetMultimodal: null, isCheckingDataset: false }); - }); + const datasetName = + state.datasetSource === "huggingface" + ? state.dataset + : state.uploadedFile; + if (!datasetName) return; + + const split = state.datasetSplit || "train"; + runDatasetCheck(datasetName, split); }, setDatasetManualMapping: (datasetManualMapping) => set({ datasetManualMapping }), diff --git a/studio/frontend/src/features/training/types/config.ts b/studio/frontend/src/features/training/types/config.ts index 67f1d00edc..53beabd383 100644 --- a/studio/frontend/src/features/training/types/config.ts +++ b/studio/frontend/src/features/training/types/config.ts @@ -72,6 +72,7 @@ export interface TrainingConfigActions { setModelType: (type: ModelType) => void; setSelectedModel: (model: string | null) => void; ensureModelDefaultsLoaded: () => void; + ensureDatasetChecked: () => void; setTrainingMethod: (method: TrainingMethod) => void; setHfToken: (token: string) => void; setDatasetSource: (source: DatasetSource) => void; From d486a2ef6f17013f6511620d119216d844f25e1d Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Thu, 19 Feb 2026 17:59:22 +0000 Subject: [PATCH 05/14] rename cli studio command to use new FastAPI backend and add unsloth-ui alias for backwards compatibility --- cli/commands/studio.py | 33 ++++++++++++++++++++++++--------- setup.sh | 16 ++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/cli/commands/studio.py b/cli/commands/studio.py index 4287c756a8..2489676844 100644 --- a/cli/commands/studio.py +++ b/cli/commands/studio.py @@ -1,19 +1,34 @@ +import time +from pathlib import Path +from typing import Optional + import typer def studio( port: int = typer.Option(8000, "--port", "-p", help="Port to run the UI server on."), host: str = typer.Option("0.0.0.0", "--host", "-H", help="Host address to bind to."), - share: bool = typer.Option(True, "--share", "-s", help="Create a public Gradio share link."), + frontend: Optional[Path] = typer.Option(None, "--frontend", "-f", help="Path to frontend build directory."), + silent: bool = typer.Option(False, "--silent", "-q", help="Suppress startup messages."), ): - """Launch the Unsloth web UI for training, inference, and export.""" - from app import demo, script_dir + """Launch the Unsloth web UI backend server.""" + from studio.backend.run import run_server - typer.echo(f"Starting Unsloth UI on http://{host}:{port}") + if not silent: + from studio.backend.run import _resolve_external_ip + display_host = _resolve_external_ip() if host == "0.0.0.0" else host + typer.echo(f"Starting Unsloth Studio on http://{display_host}:{port}") - demo.launch( - share=share, - server_port=port, - server_name=host, - favicon_path=f"{script_dir}/assets/favicon-32x32.png", + run_server( + host=host, + port=port, + frontend_path=frontend, + silent=silent, ) + + # Keep running until interrupted + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + typer.echo("\nShutting down...") diff --git a/setup.sh b/setup.sh index c493d357ee..5f7d96eff3 100755 --- a/setup.sh +++ b/setup.sh @@ -212,38 +212,38 @@ USER_SHELL="$(basename "${SHELL:-/bin/bash}")" case "$USER_SHELL" in zsh) SHELL_RC="$HOME/.zshrc" - ALIAS_BLOCK="alias unsloth-ui='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py ui -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; fish) SHELL_RC="$HOME/.config/fish/config.fish" # fish uses 'abbr' or 'function'; a simple alias works via 'alias' in config.fish - ALIAS_BLOCK="alias unsloth-ui '${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py ui -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio '${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; ksh) SHELL_RC="$HOME/.kshrc" - ALIAS_BLOCK="alias unsloth-ui='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py ui -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; *) # Default to bash for bash and any other POSIX-compatible shell SHELL_RC="$HOME/.bashrc" - ALIAS_BLOCK="alias unsloth-ui='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py ui -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; esac echo " Detected shell: $USER_SHELL → $SHELL_RC" ALIAS_ADDED=false -if ! grep -qF "unsloth-ui" "$SHELL_RC" 2>/dev/null; then +if ! grep -qF "unsloth-studio" "$SHELL_RC" 2>/dev/null; then mkdir -p "$(dirname "$SHELL_RC")" # needed for fish's nested config path cat >> "$SHELL_RC" < Date: Thu, 19 Feb 2026 18:41:10 +0000 Subject: [PATCH 06/14] add unsloth-ui shell alias for backwards compatibility alongside unsloth-studio --- setup.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/setup.sh b/setup.sh index 5f7d96eff3..2db0c7f5a5 100755 --- a/setup.sh +++ b/setup.sh @@ -212,21 +212,25 @@ USER_SHELL="$(basename "${SHELL:-/bin/bash}")" case "$USER_SHELL" in zsh) SHELL_RC="$HOME/.zshrc" - ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist' +alias unsloth-ui='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; fish) SHELL_RC="$HOME/.config/fish/config.fish" # fish uses 'abbr' or 'function'; a simple alias works via 'alias' in config.fish - ALIAS_BLOCK="alias unsloth-studio '${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio '${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist' +alias unsloth-ui '${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; ksh) SHELL_RC="$HOME/.kshrc" - ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist' +alias unsloth-ui='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; *) # Default to bash for bash and any other POSIX-compatible shell SHELL_RC="$HOME/.bashrc" - ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" + ALIAS_BLOCK="alias unsloth-studio='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist' +alias unsloth-ui='${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py studio -f ${REPO_DIR}/studio/frontend/dist'" ;; esac @@ -240,10 +244,10 @@ if ! grep -qF "unsloth-studio" "$SHELL_RC" 2>/dev/null; then # Unsloth Studio launcher $ALIAS_BLOCK UNSLOTH_EOF - echo "✅ Alias 'unsloth-studio' added to $SHELL_RC" + echo "✅ Aliases 'unsloth-studio' and 'unsloth-ui' added to $SHELL_RC" ALIAS_ADDED=true else - echo "✅ Alias 'unsloth-studio' already exists in $SHELL_RC" + echo "✅ Aliases 'unsloth-studio' and 'unsloth-ui' already exist in $SHELL_RC" fi fi # End of "if not Colab" for shell alias setup From fdeccec259fa7843d367631d88abd453a24592c2 Mon Sep 17 00:00:00 2001 From: Manan17 Date: Thu, 19 Feb 2026 20:15:44 +0000 Subject: [PATCH 07/14] Fixing compare feature --- studio/backend/core/inference/inference.py | 158 ++++++++++++--------- 1 file changed, 89 insertions(+), 69 deletions(-) diff --git a/studio/backend/core/inference/inference.py b/studio/backend/core/inference/inference.py index 31e23f5370..99a7c485e7 100644 --- a/studio/backend/core/inference/inference.py +++ b/studio/backend/core/inference/inference.py @@ -38,9 +38,13 @@ class InferenceBackend: ] self.device = get_device().value - # Thread safety + # Thread safety — _generation_lock serializes model.generate() calls. + # Must be a regular Lock (NOT RLock) because in async FastAPI, multiple + # requests share the same event-loop thread, so RLock reentrancy lets + # concurrent compare-mode requests race on the GPU. The lock is + # acquired by the *background generation thread*, not the event-loop. import threading - self._generation_lock = threading.RLock() + self._generation_lock = threading.Lock() self._model_state_lock = threading.Lock() logger.info(f"InferenceBackend initialized on {self.device}") @@ -448,9 +452,10 @@ class InferenceBackend: """ Apply adapter state before generation. Must be called under _generation_lock. - Uses revert_to_base_model() / activate_lora_adapter() which work correctly - for models loaded by Unsloth as complete PeftModels (via model.unload() / - model.load_adapter()), matching the proven pattern from the Gradio eval page. + Uses PEFT's disable_adapter_layers() / enable_adapter_layers() which toggle + a boolean flag on each LoRA layer. Unsloth's fast_linear_forward checks this + flag (proj.disable_adapters) and skips LoRA computation when True. + This is non-destructive — no model unloading/reloading needed. Args: use_adapter: None = no change, False = disable (base model), @@ -464,32 +469,34 @@ class InferenceBackend: return model_info = self.models[base] + model = model_info.get("model") + if model is None: + return if use_adapter is False: - # Revert to pure base model by unloading adapter weights - logger.info(f"Compare mode: reverting '{base}' to base model for generation") - self.revert_to_base_model(base) + # Disable LoRA layers → base model output + if isinstance(model, (PeftModel, PeftModelForCausalLM)): + logger.info(f"Compare mode: disabling adapters on '{base}' for base model generation") + model.base_model.disable_adapter_layers() + else: + logger.info(f"Compare mode: model '{base}' is not a PeftModel, already base") elif use_adapter is True: - # Activate the LoRA adapter from the original model path - lora_path = model_info.get("model_path") - if lora_path and model_info.get("is_lora"): - logger.info(f"Compare mode: activating LoRA adapter from '{lora_path}' on '{base}'") - self.activate_lora_adapter(base, lora_path) + # Re-enable LoRA layers → adapter output + if isinstance(model, (PeftModel, PeftModelForCausalLM)): + logger.info(f"Compare mode: enabling adapters on '{base}' for LoRA generation") + model.base_model.enable_adapter_layers() else: - # Fallback for dynamically attached adapters - loaded = model_info.get("loaded_adapters", {}) - if loaded: - adapter_name = list(loaded.keys())[-1] - logger.info(f"Compare mode: enabling adapter '{adapter_name}' on '{base}'") - self.set_active_adapter(base, adapter_name) - else: - logger.warning("use_adapter=true but no adapter path/adapters on model") + logger.warning("use_adapter=true but model is not a PeftModel") elif isinstance(use_adapter, str): - # Activate a specific adapter by path - logger.info(f"Compare mode: activating specific adapter '{use_adapter}' on '{base}'") - self.activate_lora_adapter(base, use_adapter) + # Enable adapters and set the specific one active + if isinstance(model, (PeftModel, PeftModelForCausalLM)): + logger.info(f"Compare mode: enabling adapter '{use_adapter}' on '{base}'") + model.base_model.enable_adapter_layers() + self.set_active_adapter(base, use_adapter) + else: + logger.warning(f"use_adapter='{use_adapter}' but model is not a PeftModel") def generate_with_adapter_control( self, @@ -500,18 +507,18 @@ class InferenceBackend: """ Thread-safe generation with optional adapter toggling. - Acquires the generation lock, applies adapter state, then generates. - This ensures adapter toggle + generation are atomic — critical for - compare mode where base and LoRA panes fire concurrently. + The adapter toggle + model.generate() are serialized by _generation_lock + inside the background generation thread — NOT in the event-loop thread. + This prevents the RLock-reentrant race that occurs when two async SSE + handlers share the same event-loop thread. Args: use_adapter: Adapter control (None/False/True/str). See _apply_adapter_state. **gen_kwargs: Forwarded to generate_chat_response. """ - with self._generation_lock: - self._apply_adapter_state(use_adapter) - # Delegate to the lock-free generation path - yield from self._generate_chat_response_inner(cancel_event=cancel_event, **gen_kwargs) + yield from self._generate_chat_response_inner( + cancel_event=cancel_event, _adapter_state=use_adapter, **gen_kwargs + ) def generate_chat_response(self, messages: list, @@ -526,22 +533,20 @@ class InferenceBackend: cancel_event=None) -> Generator[str, None, None]: """ Generate response for text or vision models. - Acquires the generation lock. For adapter-controlled generation, - use generate_with_adapter_control() instead. + The generation lock is acquired by the background generation thread. """ - with self._generation_lock: - yield from self._generate_chat_response_inner( - messages=messages, - system_prompt=system_prompt, - image=image, - temperature=temperature, - top_p=top_p, - top_k=top_k, - min_p=min_p, - max_new_tokens=max_new_tokens, - repetition_penalty=repetition_penalty, - cancel_event=cancel_event, - ) + yield from self._generate_chat_response_inner( + messages=messages, + system_prompt=system_prompt, + image=image, + temperature=temperature, + top_p=top_p, + top_k=top_k, + min_p=min_p, + max_new_tokens=max_new_tokens, + repetition_penalty=repetition_penalty, + cancel_event=cancel_event, + ) def _generate_chat_response_inner(self, messages: list, @@ -553,10 +558,14 @@ class InferenceBackend: min_p: float = 0.0, max_new_tokens: int = 256, repetition_penalty: float = 1.1, - cancel_event=None) -> Generator[str, None, None]: + cancel_event=None, + _adapter_state=None) -> Generator[str, None, None]: """ - Inner generation logic (no lock). Called by both generate_chat_response + Inner generation logic. Called by both generate_chat_response and generate_with_adapter_control. + + _adapter_state is passed to generate_stream/vision so the background + thread can toggle adapters under the generation lock. """ if not self.active_model_name: yield "Error: No active model" @@ -616,6 +625,7 @@ class InferenceBackend: yield from self.generate_stream( formatted_prompt, temperature, top_p, top_k, min_p, max_new_tokens, repetition_penalty, cancel_event=cancel_event, + _adapter_state=_adapter_state, ) def _generate_vision_response(self, messages, system_prompt, image, @@ -677,6 +687,7 @@ class InferenceBackend: streamer=streamer, max_new_tokens=max_new_tokens, use_cache=True, + do_sample=temperature > 0, temperature=temperature, top_p=top_p, top_k=top_k, @@ -686,16 +697,17 @@ class InferenceBackend: err: dict[str, str] = {} def generate_fn(): - try: - model.generate(**generation_kwargs) - except Exception as e: - err["msg"] = str(e) - logger.error(f"Vision generation error in thread: {e}") - finally: + with self._generation_lock: try: - streamer.end() - except Exception: - pass + model.generate(**generation_kwargs) + except Exception as e: + err["msg"] = str(e) + logger.error(f"Vision generation error in thread: {e}") + finally: + try: + streamer.end() + except Exception: + pass thread = threading.Thread(target=generate_fn) thread.start() @@ -741,8 +753,13 @@ class InferenceBackend: min_p: float = 0.0, max_new_tokens: int = 256, repetition_penalty: float = 1.1, - cancel_event=None) -> Generator[str, None, None]: - """Generate streaming text response (text models only).""" + cancel_event=None, + _adapter_state=None) -> Generator[str, None, None]: + """Generate streaming text response (text models only). + + _adapter_state: if not None, the background thread toggles adapters + before model.generate(), all under _generation_lock. + """ if not self.active_model_name: yield "Error: No active model" return @@ -773,7 +790,7 @@ class InferenceBackend: top_k=top_k, min_p=min_p, repetition_penalty=repetition_penalty, - do_sample=True, + do_sample=temperature > 0, eos_token_id=tokenizer.eos_token_id, pad_token_id=tokenizer.eos_token_id if tokenizer.pad_token_id is None else tokenizer.pad_token_id, ) @@ -795,16 +812,19 @@ class InferenceBackend: ) def generate_fn(): - try: - model.generate(**generation_kwargs) - except Exception as e: - err["msg"] = str(e) - logger.error(f"Generation error: {e}") - finally: + with self._generation_lock: try: - streamer.end() - except Exception: - pass + if _adapter_state is not None: + self._apply_adapter_state(_adapter_state) + model.generate(**generation_kwargs) + except Exception as e: + err["msg"] = str(e) + logger.error(f"Generation error: {e}") + finally: + try: + streamer.end() + except Exception: + pass err: dict[str, str] = {} thread = threading.Thread(target=generate_fn) From 93b31f0db2bfcdc2e73bb458d8c1c65035cead6b Mon Sep 17 00:00:00 2001 From: samit Date: Thu, 19 Feb 2026 15:03:23 -0800 Subject: [PATCH 08/14] added optim in the frontend --- studio/frontend/src/config/training.ts | 10 +++++ .../studio/sections/params-section.tsx | 43 ++++++++++++++++++- .../studio/sections/progress-section.tsx | 7 +++ .../src/features/training/api/mappers.ts | 2 +- .../training/stores/training-config-store.ts | 6 ++- .../src/features/training/types/config.ts | 2 + 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/studio/frontend/src/config/training.ts b/studio/frontend/src/config/training.ts index 8840b9363c..9ad09ba813 100644 --- a/studio/frontend/src/config/training.ts +++ b/studio/frontend/src/config/training.ts @@ -73,10 +73,20 @@ export const TARGET_MODULES = [ "down_proj", ]; +export const OPTIMIZER_OPTIONS: ReadonlyArray<{ value: string; label: string }> = [ + { value: "adamw_8bit", label: "AdamW 8-bit" }, + { value: "paged_adamw_8bit", label: "Paged AdamW 8-bit" }, + { value: "adamw_bnb_8bit", label: "AdamW BNB 8-bit" }, + { value: "paged_adamw_32bit", label: "Paged AdamW 32-bit" }, + { value: "adamw_torch", label: "AdamW (PyTorch)" }, + { value: "adamw_torch_fused", label: "AdamW (PyTorch Fused)" }, +]; + export const DEFAULT_HYPERPARAMS = { epochs: 3, contextLength: 2048, learningRate: 2e-4, + optimizerType: "adamw_8bit", loraRank: 16, loraAlpha: 32, loraDropout: 0.05, diff --git a/studio/frontend/src/features/studio/sections/params-section.tsx b/studio/frontend/src/features/studio/sections/params-section.tsx index 2069da7a90..cf25d5ba54 100644 --- a/studio/frontend/src/features/studio/sections/params-section.tsx +++ b/studio/frontend/src/features/studio/sections/params-section.tsx @@ -20,7 +20,11 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; -import { CONTEXT_LENGTHS, TARGET_MODULES } from "@/config/training"; +import { + CONTEXT_LENGTHS, + OPTIMIZER_OPTIONS, + TARGET_MODULES, +} from "@/config/training"; import { useTrainingConfigStore } from "@/features/training"; import type { GradientCheckpointing } from "@/types/training"; import { @@ -508,6 +512,43 @@ export function ParamsSection(): ReactElement { value="optimization" className="mt-3 flex flex-col gap-3" > + + Optimization algorithm. 8-bit variants reduce memory usage. + Fused is recommended for vision models.{" "} + + Read more + + + } + > + + o.value === config.optimizerType)?.label ?? + config.optimizerType; + const configItems = [ { section: "Hyperparams", @@ -133,6 +139,7 @@ export function ProgressSection(): ReactElement { ["Epochs", config.epochs], ["Batch size", config.batchSize], ["Learning rate", config.learningRate], + ["Optimizer", optimizerLabel], ["Max steps", config.maxSteps], ["Context length", config.contextLength], ["Warmup steps", config.warmupSteps], diff --git a/studio/frontend/src/features/training/api/mappers.ts b/studio/frontend/src/features/training/api/mappers.ts index 510cc542b8..78386f197b 100644 --- a/studio/frontend/src/features/training/api/mappers.ts +++ b/studio/frontend/src/features/training/api/mappers.ts @@ -40,7 +40,7 @@ export function buildTrainingStartPayload( weight_decay: config.weightDecay, random_seed: config.randomSeed, packing: config.packing, - optim: "adamw_8bit", + optim: config.optimizerType, lr_scheduler_type: "linear", use_lora: adapterMethod, lora_r: config.loraRank, diff --git a/studio/frontend/src/features/training/stores/training-config-store.ts b/studio/frontend/src/features/training/stores/training-config-store.ts index 67317dbc09..62d4808b2c 100644 --- a/studio/frontend/src/features/training/stores/training-config-store.ts +++ b/studio/frontend/src/features/training/stores/training-config-store.ts @@ -305,6 +305,7 @@ export const useTrainingConfigStore = create()( setEpochs: (epochs) => set({ epochs }), setContextLength: (contextLength) => set({ contextLength }), setLearningRate: (learningRate) => set({ learningRate }), + setOptimizerType: (optimizerType) => set({ optimizerType }), setLoraRank: (loraRank) => set({ loraRank }), setLoraAlpha: (loraAlpha) => set({ loraAlpha }), setLoraDropout: (loraDropout) => set({ loraDropout }), @@ -346,7 +347,7 @@ export const useTrainingConfigStore = create()( }, { name: "unsloth_training_config_v1", - version: 3, + version: 4, migrate: (persisted, version) => { const s = persisted as Record; if (version < 2 && s.datasetSubset == null && s.datasetConfig != null) { @@ -356,6 +357,9 @@ export const useTrainingConfigStore = create()( if (version < 3 && s.modelDefaultsAppliedFor == null) { s.modelDefaultsAppliedFor = null; } + if (version < 4 && s.optimizerType == null) { + s.optimizerType = DEFAULT_HYPERPARAMS.optimizerType; + } return s as unknown as TrainingConfigStore; }, partialize: partializePersistedState, diff --git a/studio/frontend/src/features/training/types/config.ts b/studio/frontend/src/features/training/types/config.ts index 53beabd383..8557739b06 100644 --- a/studio/frontend/src/features/training/types/config.ts +++ b/studio/frontend/src/features/training/types/config.ts @@ -30,6 +30,7 @@ export interface TrainingConfigState { epochs: number; contextLength: number; learningRate: number; + optimizerType: string; loraRank: number; loraAlpha: number; loraDropout: number; @@ -85,6 +86,7 @@ export interface TrainingConfigActions { setEpochs: (epochs: number) => void; setContextLength: (length: number) => void; setLearningRate: (rate: number) => void; + setOptimizerType: (value: string) => void; setLoraRank: (rank: number) => void; setLoraAlpha: (alpha: number) => void; setLoraDropout: (dropout: number) => void; From 035f765130535a4653cc5905bb2d375e28529317 Mon Sep 17 00:00:00 2001 From: samit Date: Thu, 19 Feb 2026 21:05:27 -0800 Subject: [PATCH 09/14] added hf token validation --- .../components/steps/dataset-step.tsx | 22 +++++++ .../components/steps/model-selection-step.tsx | 22 +++++++ .../studio/sections/dataset-section.tsx | 22 +++++++ .../studio/sections/model-section.tsx | 22 +++++++ studio/frontend/src/hooks/index.ts | 1 + .../src/hooks/use-hf-token-validation.ts | 59 +++++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 studio/frontend/src/hooks/use-hf-token-validation.ts diff --git a/studio/frontend/src/features/onboarding/components/steps/dataset-step.tsx b/studio/frontend/src/features/onboarding/components/steps/dataset-step.tsx index 0275e5a6da..484b10ddb4 100644 --- a/studio/frontend/src/features/onboarding/components/steps/dataset-step.tsx +++ b/studio/frontend/src/features/onboarding/components/steps/dataset-step.tsx @@ -35,6 +35,7 @@ import { import { useDebouncedValue, useHfDatasetSearch, + useHfTokenValidation, useInfiniteScroll, } from "@/hooks"; import { cn, formatCompact } from "@/lib/utils"; @@ -103,10 +104,14 @@ export function DatasetStep() { isLoading, isLoadingMore, fetchMore, + error: hfSearchError, } = useHfDatasetSearch(debouncedQuery, { accessToken: hfToken || undefined, }); + const { error: tokenValidationError, isChecking: isCheckingToken } = + useHfTokenValidation(hfToken); + const resultIds = useMemo(() => hfResults.map((r) => r.id), [hfResults]); const comboboxAnchorRef = useRef(null); @@ -179,6 +184,23 @@ export function DatasetStep() { onChange={(e) => setHfToken(e.target.value)} /> + {(tokenValidationError ?? hfSearchError) && ( +

+ {tokenValidationError ?? hfSearchError} + {" — "} + + Get or update token + +

+ )} + {isCheckingToken && ( +

Checking token…

+ )} diff --git a/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx b/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx index 3f63775ff9..ce506b6085 100644 --- a/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx +++ b/studio/frontend/src/features/onboarding/components/steps/model-selection-step.tsx @@ -34,6 +34,7 @@ import { MODEL_TYPE_TO_HF_TASK } from "@/config/training"; import { useDebouncedValue, useHfModelSearch, + useHfTokenValidation, useInfiniteScroll, } from "@/hooks"; import { formatCompact } from "@/lib/utils"; @@ -80,11 +81,15 @@ export function ModelSelectionStep() { isLoading, isLoadingMore, fetchMore, + error: hfSearchError, } = useHfModelSearch(debouncedQuery, { task, accessToken: hfToken || undefined, }); + const { error: tokenValidationError, isChecking: isCheckingToken } = + useHfTokenValidation(hfToken); + const resultIds = useMemo(() => hfResults.map((r) => r.id), [hfResults]); const comboboxAnchorRef = useRef(null); @@ -126,6 +131,23 @@ export function ModelSelectionStep() { onChange={(e) => setHfToken(e.target.value)} /> + {(tokenValidationError ?? hfSearchError) && ( +

+ {tokenValidationError ?? hfSearchError} + {" — "} + + Get or update token + +

+ )} + {isCheckingToken && ( +

Checking token…

+ )}
diff --git a/studio/frontend/src/features/studio/sections/dataset-section.tsx b/studio/frontend/src/features/studio/sections/dataset-section.tsx index e3f50efb40..d7b97c7ad0 100644 --- a/studio/frontend/src/features/studio/sections/dataset-section.tsx +++ b/studio/frontend/src/features/studio/sections/dataset-section.tsx @@ -25,6 +25,7 @@ import { import { useDebouncedValue, useHfDatasetSearch, + useHfTokenValidation, useInfiniteScroll, } from "@/hooks"; import { formatCompact } from "@/lib/utils"; @@ -102,10 +103,14 @@ export function DatasetSection() { isLoading, isLoadingMore, fetchMore, + error: hfSearchError, } = useHfDatasetSearch(debouncedQuery, { accessToken: hfToken || undefined, }); + const { error: tokenValidationError, isChecking: isCheckingToken } = + useHfTokenValidation(hfToken); + const resultIds = useMemo(() => { const ids = hfResults.map((r) => r.id); if (dataset && !ids.includes(dataset)) { @@ -239,6 +244,23 @@ export function DatasetSection() {
+ {(tokenValidationError ?? hfSearchError) && ( +

+ {tokenValidationError ?? hfSearchError} + {" — "} + + Get or update token + +

+ )} + {isCheckingToken && ( +

Checking token…

+ )} { const ids = hfResults.map((r) => r.id); if (selectedModel && !ids.includes(selectedModel)) { @@ -555,6 +560,23 @@ export function ModelSection() { onChange={(e) => setHfToken(e.target.value)} /> + {(tokenValidationError ?? hfSearchError) && ( +

+ {tokenValidationError ?? hfSearchError} + {" — "} + + Get or update token + +

+ )} + {isCheckingToken && ( +

Checking token…

+ )} diff --git a/studio/frontend/src/hooks/index.ts b/studio/frontend/src/hooks/index.ts index 16c2c82b75..b6c4fcad14 100644 --- a/studio/frontend/src/hooks/index.ts +++ b/studio/frontend/src/hooks/index.ts @@ -5,4 +5,5 @@ export { useHardwareInfo } from "./use-hardware-info"; export { useHfModelSearch } from "./use-hf-model-search"; export { useHfDatasetSearch } from "./use-hf-dataset-search"; export { useHfDatasetSplits } from "./use-hf-dataset-splits"; +export { useHfTokenValidation } from "./use-hf-token-validation"; export { useInfiniteScroll } from "./use-infinite-scroll"; diff --git a/studio/frontend/src/hooks/use-hf-token-validation.ts b/studio/frontend/src/hooks/use-hf-token-validation.ts new file mode 100644 index 0000000000..1882745eca --- /dev/null +++ b/studio/frontend/src/hooks/use-hf-token-validation.ts @@ -0,0 +1,59 @@ +import { whoAmI } from "@huggingface/hub"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useDebouncedValue } from "./use-debounced-value"; + +export interface HfTokenValidationState { + isValid: boolean | null; + error: string | null; + isChecking: boolean; +} + +const INITIAL: HfTokenValidationState = { + isValid: null, + error: null, + isChecking: false, +}; + +/** + * Validates the Hugging Face token by calling the whoami-v2 API. + * Debounces the token to avoid excessive requests while typing. + * Returns validation state: isValid (null = not checked), error message, and isChecking. + */ +export function useHfTokenValidation(token: string): HfTokenValidationState { + const debouncedToken = useDebouncedValue(token.trim(), 500); + const [state, setState] = useState(INITIAL); + const versionRef = useRef(0); + + const runCheck = useCallback(async (t: string) => { + if (!t) { + setState({ isValid: null, error: null, isChecking: false }); + return; + } + + const v = ++versionRef.current; + setState((prev) => ({ ...prev, isChecking: true, error: null })); + + try { + await whoAmI({ accessToken: t }); + if (versionRef.current !== v) return; + setState({ isValid: true, error: null, isChecking: false }); + } catch { + if (versionRef.current !== v) return; + setState({ + isValid: false, + error: "invalid or expired token", + isChecking: false, + }); + } + }, []); + + useEffect(() => { + if (!debouncedToken) { + setState(INITIAL); + return; + } + runCheck(debouncedToken); + }, [debouncedToken, runCheck]); + + return state; +} From 798bfb8f6fc89ce061444f95afbaabda7720e472 Mon Sep 17 00:00:00 2001 From: Manan17 Date: Fri, 20 Feb 2026 06:32:01 +0000 Subject: [PATCH 10/14] Setting it to total cpu_count // 4 --- studio/backend/core/training/trainer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/backend/core/training/trainer.py b/studio/backend/core/training/trainer.py index e93ba097d1..c36fa235ed 100644 --- a/studio/backend/core/training/trainer.py +++ b/studio/backend/core/training/trainer.py @@ -867,6 +867,7 @@ class UnslothTrainer: self.trainer, instruction_part=instruction_part, response_part=response_part, + num_proc=config_args.get("dataset_num_proc", max(1, os.cpu_count() // 4)), ) print("Train on responses only configured successfully\n") except Exception as e: From 68028bf7f36edbcfdff6b7c3c31b60175420b3a3 Mon Sep 17 00:00:00 2001 From: samit Date: Thu, 19 Feb 2026 23:31:46 -0800 Subject: [PATCH 11/14] added lr_scheduler type to the frontend --- studio/frontend/src/config/training.ts | 6 +++ .../studio/sections/params-section.tsx | 38 +++++++++++++++++++ .../src/features/training/api/mappers.ts | 2 +- .../src/features/training/api/models-api.ts | 2 + .../features/training/lib/model-defaults.ts | 8 ++++ .../training/stores/training-config-store.ts | 6 ++- .../src/features/training/types/config.ts | 2 + 7 files changed, 62 insertions(+), 2 deletions(-) diff --git a/studio/frontend/src/config/training.ts b/studio/frontend/src/config/training.ts index 9ad09ba813..da60328d40 100644 --- a/studio/frontend/src/config/training.ts +++ b/studio/frontend/src/config/training.ts @@ -82,11 +82,17 @@ export const OPTIMIZER_OPTIONS: ReadonlyArray<{ value: string; label: string }> { value: "adamw_torch_fused", label: "AdamW (PyTorch Fused)" }, ]; +export const LR_SCHEDULER_OPTIONS: ReadonlyArray<{ value: string; label: string }> = [ + { value: "linear", label: "Linear" }, + { value: "cosine", label: "Cosine" }, +]; + export const DEFAULT_HYPERPARAMS = { epochs: 3, contextLength: 2048, learningRate: 2e-4, optimizerType: "adamw_8bit", + lrSchedulerType: "linear", loraRank: 16, loraAlpha: 32, loraDropout: 0.05, diff --git a/studio/frontend/src/features/studio/sections/params-section.tsx b/studio/frontend/src/features/studio/sections/params-section.tsx index cf25d5ba54..edc21e0e0e 100644 --- a/studio/frontend/src/features/studio/sections/params-section.tsx +++ b/studio/frontend/src/features/studio/sections/params-section.tsx @@ -22,6 +22,7 @@ import { } from "@/components/ui/tooltip"; import { CONTEXT_LENGTHS, + LR_SCHEDULER_OPTIONS, OPTIMIZER_OPTIONS, TARGET_MODULES, } from "@/config/training"; @@ -549,6 +550,43 @@ export function ParamsSection(): ReactElement { + + How the learning rate changes over training. Linear decays + steadily; cosine decays in a curve.{" "} + + Read more + + + } + > + + ()( setContextLength: (contextLength) => set({ contextLength }), setLearningRate: (learningRate) => set({ learningRate }), setOptimizerType: (optimizerType) => set({ optimizerType }), + setLrSchedulerType: (lrSchedulerType) => set({ lrSchedulerType }), setLoraRank: (loraRank) => set({ loraRank }), setLoraAlpha: (loraAlpha) => set({ loraAlpha }), setLoraDropout: (loraDropout) => set({ loraDropout }), @@ -347,7 +348,7 @@ export const useTrainingConfigStore = create()( }, { name: "unsloth_training_config_v1", - version: 4, + version: 5, migrate: (persisted, version) => { const s = persisted as Record; if (version < 2 && s.datasetSubset == null && s.datasetConfig != null) { @@ -360,6 +361,9 @@ export const useTrainingConfigStore = create()( if (version < 4 && s.optimizerType == null) { s.optimizerType = DEFAULT_HYPERPARAMS.optimizerType; } + if (version < 5 && s.lrSchedulerType == null) { + s.lrSchedulerType = DEFAULT_HYPERPARAMS.lrSchedulerType; + } return s as unknown as TrainingConfigStore; }, partialize: partializePersistedState, diff --git a/studio/frontend/src/features/training/types/config.ts b/studio/frontend/src/features/training/types/config.ts index 8557739b06..b268a08b79 100644 --- a/studio/frontend/src/features/training/types/config.ts +++ b/studio/frontend/src/features/training/types/config.ts @@ -31,6 +31,7 @@ export interface TrainingConfigState { contextLength: number; learningRate: number; optimizerType: string; + lrSchedulerType: string; loraRank: number; loraAlpha: number; loraDropout: number; @@ -87,6 +88,7 @@ export interface TrainingConfigActions { setContextLength: (length: number) => void; setLearningRate: (rate: number) => void; setOptimizerType: (value: string) => void; + setLrSchedulerType: (value: string) => void; setLoraRank: (rank: number) => void; setLoraAlpha: (alpha: number) => void; setLoraDropout: (dropout: number) => void; From 5ba8edf9fe7934a142bc54bd2076f877463f40bd Mon Sep 17 00:00:00 2001 From: samit Date: Fri, 20 Feb 2026 00:49:38 -0800 Subject: [PATCH 12/14] added the copy on mac --- .../src/components/assistant-ui/thread.tsx | 51 +++++++++++-------- studio/frontend/src/lib/copy-to-clipboard.ts | 44 ++++++++++++++++ 2 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 studio/frontend/src/lib/copy-to-clipboard.ts diff --git a/studio/frontend/src/components/assistant-ui/thread.tsx b/studio/frontend/src/components/assistant-ui/thread.tsx index c109c92ead..aaaca0eeb3 100644 --- a/studio/frontend/src/components/assistant-ui/thread.tsx +++ b/studio/frontend/src/components/assistant-ui/thread.tsx @@ -8,6 +8,7 @@ import { Reasoning, ReasoningGroup } from "@/components/assistant-ui/reasoning"; import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { Button } from "@/components/ui/button"; +import { copyToClipboard } from "@/lib/copy-to-clipboard"; import { cn } from "@/lib/utils"; import { ActionBarMorePrimitive, @@ -38,7 +39,7 @@ import { RefreshCwIcon, SquareIcon, } from "lucide-react"; -import { type FC, useRef } from "react"; +import { type FC, useRef, useState } from "react"; export const Thread: FC<{ hideComposer?: boolean; hideWelcome?: boolean }> = ({ hideComposer, @@ -275,6 +276,32 @@ const AssistantMessage: FC = () => { ); }; +const COPY_RESET_MS = 2000; + +const CopyButton: FC = () => { + const aui = useAui(); + const [copied, setCopied] = useState(false); + const resetTimeoutRef = useRef | null>(null); + + const handleCopy = () => { + const text = aui.message().getCopyText(); + if (copyToClipboard(text)) { + setCopied(true); + if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current); + resetTimeoutRef.current = setTimeout(() => { + setCopied(false); + resetTimeoutRef.current = null; + }, COPY_RESET_MS); + } + }; + + return ( + + {copied ? : } + + ); +}; + const AssistantActionBar: FC = () => { return ( { autohideFloat="single-branch" className="aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm" > - - - message.isCopied}> - - - !message.isCopied}> - - - - + @@ -352,16 +370,7 @@ const UserActionBar: FC = () => { autohide="not-last" className="aui-user-action-bar-root flex items-center" > - - - message.isCopied}> - - - !message.isCopied}> - - - - + diff --git a/studio/frontend/src/lib/copy-to-clipboard.ts b/studio/frontend/src/lib/copy-to-clipboard.ts new file mode 100644 index 0000000000..3ef3df1177 --- /dev/null +++ b/studio/frontend/src/lib/copy-to-clipboard.ts @@ -0,0 +1,44 @@ +/** + * Copy text to clipboard in a way that works on Mac/Safari. + * Uses a synchronous textarea + execCommand fallback so the copy runs in the + * same user gesture as the click (required by Safari's clipboard security). + */ +export function copyToClipboard(text: string): boolean { + if (typeof text !== "string" || text.length === 0) { + return false; + } + + // Synchronous fallback: works in Safari/Mac when clipboard API fails + // because it runs entirely within the user gesture (click) stack. + if (document.queryCommandSupported?.("copy") !== false) { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.top = "0"; + textarea.style.left = "0"; + textarea.style.opacity = "0"; + textarea.setAttribute("aria-hidden", "true"); + document.body.appendChild(textarea); + textarea.focus({ preventScroll: true }); + textarea.select(); + try { + const ok = document.execCommand("copy"); + document.body.removeChild(textarea); + return ok; + } catch { + document.body.removeChild(textarea); + return false; + } + } + + // Modern API only when fallback not available (e.g. non-browser) + if (typeof navigator?.clipboard?.writeText === "function") { + navigator.clipboard.writeText(text).then( + () => {}, + () => {} + ); + return true; + } + + return false; +} From 3d403c6c992885ba4ca7951a21c8b6c77f6e3da2 Mon Sep 17 00:00:00 2001 From: samit Date: Fri, 20 Feb 2026 01:29:37 -0800 Subject: [PATCH 13/14] edited the font of the new parameters --- .../src/features/studio/sections/params-section.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/studio/frontend/src/features/studio/sections/params-section.tsx b/studio/frontend/src/features/studio/sections/params-section.tsx index edc21e0e0e..de82fb0fb5 100644 --- a/studio/frontend/src/features/studio/sections/params-section.tsx +++ b/studio/frontend/src/features/studio/sections/params-section.tsx @@ -534,7 +534,7 @@ export function ParamsSection(): ReactElement { value={store.optimizerType} onValueChange={(v) => store.setOptimizerType(v)} > - + @@ -542,7 +542,6 @@ export function ParamsSection(): ReactElement { {opt.label} @@ -571,7 +570,7 @@ export function ParamsSection(): ReactElement { value={store.lrSchedulerType} onValueChange={(v) => store.setLrSchedulerType(v)} > - + @@ -579,7 +578,6 @@ export function ParamsSection(): ReactElement { {opt.label} From 77b7e8a9bab4d48c9691e0a8c5c6a2de0ee31687 Mon Sep 17 00:00:00 2001 From: imagineer99 Date: Fri, 20 Feb 2026 15:35:06 +0000 Subject: [PATCH 14/14] fix: remove warmup text inference status --- studio/frontend/src/features/chat/api/chat-adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/frontend/src/features/chat/api/chat-adapter.ts b/studio/frontend/src/features/chat/api/chat-adapter.ts index 8997570271..8e9e7df0e0 100644 --- a/studio/frontend/src/features/chat/api/chat-adapter.ts +++ b/studio/frontend/src/features/chat/api/chat-adapter.ts @@ -159,7 +159,7 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { if (abortSignal.aborted) return; warmupToastShown = true; toast.promise(firstTokenPromise, { - loading: "Warming up model", + loading: "Generating", success: "Generating", error: (err) => err instanceof Error && err.message ? err.message : "Generation failed",