Merge remote-tracking branch 'origin/nightly' into feature/fixes-client
This commit is contained in:
commit
2ccb75f2b7
101 changed files with 551 additions and 105 deletions
|
|
@ -2,6 +2,7 @@
|
|||
# Used for models without specific configurations
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -47,6 +48,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.7
|
||||
top_p: 0.95
|
||||
top_k: -1
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/ERNIE-4.5-21B-A3B-PT
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: true
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -48,6 +49,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: true
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: tiiuae/Falcon-H1-0.5B-Instruct, unsloth/Falcon-H1-0.5B-Instruct
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from Ollama
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 4096
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,5 +45,6 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0
|
||||
top_p: 0.9
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 4096
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# Based on Gemma2_(9B)-Alpaca.ipynb (same defaults for larger models)
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -41,3 +42,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/gemma-2-2b-bnb-4bit, google/gemma-2-2b
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 2
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 1024
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
audio_input: true
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 2
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
audio_input: true
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_k: 64
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 4096
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_p: 1.0
|
||||
top_k: 0
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 1024
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.0
|
||||
top_p: 1.0
|
||||
top_k: 0
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -46,6 +47,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: 0
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -46,6 +47,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.0
|
||||
top_p: 1.0
|
||||
top_k: 0
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Llama-3.2-1B-Instruct-unsloth-bnb-4bit, meta-llama/Llama-3.2-1B-Instruct, unsloth/Llama-3.2-1B-Instruct-bnb-4bit, RedHatAI/Llama-3.2-1B-Instruct-FP8, unsloth/Llama-3.2-1B-Instruct-FP8-Block, unsloth/Llama-3.2-1B-Instruct-FP8-Dynamic
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 5
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Meta-Llama-3.1-8B-bnb-4bit, unsloth/Meta-Llama-3.1-8B-unsloth-bnb-4bit, meta-llama/Meta-Llama-3.1-8B, unsloth/Meta-Llama-3.1-8B, unsloth/Meta-Llama-3.1-70B, meta-llama/Meta-Llama-3.1-70B, unsloth/Meta-Llama-3.1-405B-bnb-4bit, meta-llama/Meta-Llama-3.1-405B
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: "unsloth/Meta-Llama-3.1-8B-Instruct-unsloth-bnb-4bit", "meta-llama/Meta-Llama-3.1-8B-Instruct", "unsloth/Meta-Llama-3.1-8B-Instruct","RedHatAI/Llama-3.1-8B-Instruct-FP8","unsloth/Llama-3.1-8B-Instruct-FP8-Block","unsloth/Llama-3.1-8B-Instruct-FP8-Dynamic"
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 8192
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,4 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/llama-3-8b-Instruct, meta-llama/Meta-Llama-3-8B-Instruct
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/llama-3-8b, meta-llama/Meta-Llama-3-8B
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -39,6 +40,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.2
|
||||
top_p: 1.2
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -48,6 +49,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.7
|
||||
min_p: 0.01
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -48,6 +49,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.15
|
||||
top_p: default
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: "unsloth/Mistral-Nemo-Base-2407", "mistralai/Mistral-Nemo-Base-2407", "unsloth/Mistral-Nemo-Instruct-2407-bnb-4bit", "unsloth/Mistral-Nemo-Instruct-2407", "mistralai/Mistral-Nemo-Instruct-2407",
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,4 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Mistral-Small-Instruct-2409-bnb-4bit, mistralai/Mistral-Small-Instruct-2409
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/mistral-7b-instruct-v0.3, mistralai/Mistral-7B-Instruct-v0.3
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,4 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# Based on Mistral_v0.3_(7B)-Alpaca.ipynb
|
||||
# Also applies to: "unsloth/mistral-7b-v0.3", "mistralai/Mistral-7B-v0.3",
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -41,4 +42,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
audio_type: dac
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
eval_steps: 0
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.4
|
||||
top_k: 40
|
||||
top_p: 0.9
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
audio_type: bicodec
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
eval_steps: 0
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
|
|
@ -47,6 +48,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.8
|
||||
top_k: 50
|
||||
top_p: 1.0
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
audio_type: csm
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
eval_steps: 0
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
|
|
@ -45,3 +46,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/GLM-4.7-Flash-unsloth-bnb-4bit, unsloth/GLM-4.7-Flash-bnb-4bit, THUDM/GLM-4.7-Flash
|
||||
|
||||
training:
|
||||
trust_remote_code: true
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: true
|
||||
temperature: 0.7
|
||||
top_p: 0.8
|
||||
top_k: 20
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -38,6 +39,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.3
|
||||
min_p: 0.15
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: true
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -46,6 +47,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: true
|
||||
temperature: 1.0
|
||||
top_p: 1.0
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: true
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -48,6 +49,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: true
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# Based on bert_classification.ipynb
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 1
|
||||
num_epochs: 0
|
||||
|
|
@ -41,3 +42,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
audio_type: snac
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
eval_steps: 0
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
|
|
@ -47,6 +48,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_p: 0.95
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: TinyLlama/TinyLlama-1.1B-intermediate-step-1431k-3T
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 4096
|
||||
# num_epochs: 1
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ audio_type: whisper
|
|||
audio_input: true
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
eval_steps: 5
|
||||
max_seq_length: 448
|
||||
# num_epochs: 4
|
||||
|
|
@ -41,3 +42,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: "unsloth/Phi-3-medium-4k-instruct-bnb-4bit", "microsoft/Phi-3-medium-4k-instruct",
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,4 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: "unsloth/Phi-3.5-mini-instruct-bnb-4bit", "microsoft/Phi-3.5-mini-instruct"
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,4 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.8
|
||||
top_p: 0.95
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# MoE model - includes gate_up_proj for MoE layers
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -45,6 +46,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_k: 20
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Qwen2-7B-bnb-4bit, Qwen/Qwen2-7B
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Qwen2.5-1.5B-Instruct-unsloth-bnb-4bit, Qwen/Qwen2.5-1.5B-Instruct, unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 4096
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Qwen2.5-7B-unsloth-bnb-4bit, Qwen/Qwen2.5-7B, unsloth/Qwen2.5-7B-bnb-4bit
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Qwen2.5-Coder-1.5B-Instruct-bnb-4bit, Qwen/Qwen2.5-Coder-1.5B-Instruct
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# Also applies to: unsloth/Qwen2.5-Coder-7B-Instruct, Qwen/Qwen2.5-Coder-7B-Instruct
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 32768
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,3 +43,5 @@ logging:
|
|||
tensorboard_dir: "runs"
|
||||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth notebook
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 1.5
|
||||
min_p: 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from Ollama
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 1024
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_k: 20
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from Ollama
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_k: 20
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from Ollama
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_k: 20
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# MoE model - includes gate_up_proj for MoE layers
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -45,6 +46,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_k: 20
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from Ollama
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_k: 20
|
||||
top_p: 0.95
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.7
|
||||
top_p: 0.80
|
||||
top_k: 20
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -44,6 +45,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.6
|
||||
top_p: 0.95
|
||||
top_k: 20
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# added inference parameters from unsloth guides
|
||||
|
||||
training:
|
||||
trust_remote_code: false
|
||||
max_seq_length: 2048
|
||||
# num_epochs: 4
|
||||
num_epochs: 0
|
||||
|
|
@ -42,6 +43,7 @@ logging:
|
|||
log_frequency: 10
|
||||
|
||||
inference:
|
||||
trust_remote_code: false
|
||||
temperature: 0.7
|
||||
top_p: 0.8
|
||||
top_k: 20
|
||||
|
|
|
|||
|
|
@ -138,7 +138,8 @@ class ExportBackend:
|
|||
def load_checkpoint(self,
|
||||
checkpoint_path: str,
|
||||
max_seq_length: int = 2048,
|
||||
load_in_4bit: bool = True) -> Tuple[bool, str]:
|
||||
load_in_4bit: bool = True,
|
||||
trust_remote_code: bool = False) -> Tuple[bool, str]:
|
||||
"""
|
||||
Load a checkpoint for export.
|
||||
|
||||
|
|
@ -178,6 +179,7 @@ class ExportBackend:
|
|||
dtype=None,
|
||||
auto_model=CsmForConditionalGeneration,
|
||||
load_in_4bit=False,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
elif self._audio_type == 'whisper':
|
||||
|
|
@ -189,6 +191,7 @@ class ExportBackend:
|
|||
dtype=None,
|
||||
load_in_4bit=False,
|
||||
auto_model=WhisperForConditionalGeneration,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
elif self._audio_type == 'snac':
|
||||
|
|
@ -198,6 +201,7 @@ class ExportBackend:
|
|||
max_seq_length=max_seq_length,
|
||||
dtype=None,
|
||||
load_in_4bit=load_in_4bit,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
elif self._audio_type == 'bicodec':
|
||||
|
|
@ -208,6 +212,7 @@ class ExportBackend:
|
|||
max_seq_length=max_seq_length,
|
||||
dtype=torch.float32,
|
||||
load_in_4bit=False,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
elif self._audio_type == 'dac':
|
||||
|
|
@ -217,6 +222,7 @@ class ExportBackend:
|
|||
model_name=checkpoint_path,
|
||||
max_seq_length=max_seq_length,
|
||||
load_in_4bit=False,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
elif self.is_vision:
|
||||
|
|
@ -226,6 +232,7 @@ class ExportBackend:
|
|||
max_seq_length=max_seq_length,
|
||||
dtype=None,
|
||||
load_in_4bit=load_in_4bit,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
tokenizer = processor # For vision models, processor acts as tokenizer
|
||||
|
||||
|
|
@ -236,6 +243,7 @@ class ExportBackend:
|
|||
max_seq_length=max_seq_length,
|
||||
dtype=None,
|
||||
load_in_4bit=load_in_4bit,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
# Check if PEFT model
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ class ExportOrchestrator:
|
|||
checkpoint_path: str,
|
||||
max_seq_length: int = 2048,
|
||||
load_in_4bit: bool = True,
|
||||
trust_remote_code: bool = False,
|
||||
) -> Tuple[bool, str]:
|
||||
"""Load a checkpoint for export.
|
||||
|
||||
|
|
@ -225,6 +226,7 @@ class ExportOrchestrator:
|
|||
"checkpoint_path": checkpoint_path,
|
||||
"max_seq_length": max_seq_length,
|
||||
"load_in_4bit": load_in_4bit,
|
||||
"trust_remote_code": trust_remote_code,
|
||||
}
|
||||
|
||||
# Always kill existing subprocess and spawn fresh.
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ def _handle_load(backend, cmd: dict, resp_queue: Any) -> None:
|
|||
checkpoint_path = cmd["checkpoint_path"]
|
||||
max_seq_length = cmd.get("max_seq_length", 2048)
|
||||
load_in_4bit = cmd.get("load_in_4bit", True)
|
||||
trust_remote_code = cmd.get("trust_remote_code", False)
|
||||
|
||||
try:
|
||||
_send_response(resp_queue, {
|
||||
|
|
@ -96,6 +97,7 @@ def _handle_load(backend, cmd: dict, resp_queue: Any) -> None:
|
|||
checkpoint_path=checkpoint_path,
|
||||
max_seq_length=max_seq_length,
|
||||
load_in_4bit=load_in_4bit,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
_send_response(resp_queue, {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ class InferenceBackend:
|
|||
max_seq_length: int = 2048,
|
||||
dtype = None,
|
||||
load_in_4bit: bool = True,
|
||||
hf_token: Optional[str] = None) -> bool:
|
||||
hf_token: Optional[str] = None,
|
||||
trust_remote_code: bool = False) -> bool:
|
||||
"""
|
||||
Load any model: base, LoRA adapter, text, or vision.
|
||||
"""
|
||||
|
|
@ -110,6 +111,7 @@ class InferenceBackend:
|
|||
auto_model=CsmForConditionalGeneration,
|
||||
load_in_4bit=False,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
FastModel.for_inference(model)
|
||||
self.models[model_name]["model"] = model
|
||||
|
|
@ -139,6 +141,7 @@ class InferenceBackend:
|
|||
dtype=torch.float32,
|
||||
load_in_4bit=False,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
else:
|
||||
# Base model: download full HF repo, then load from /LLM subfolder
|
||||
|
|
@ -155,6 +158,7 @@ class InferenceBackend:
|
|||
dtype=torch.float32,
|
||||
load_in_4bit=False,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
FastModel.for_inference(model)
|
||||
|
|
@ -169,6 +173,7 @@ class InferenceBackend:
|
|||
max_seq_length=max_seq_length,
|
||||
load_in_4bit=False,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
FastModel.for_inference(model)
|
||||
self.models[model_name]["model"] = model
|
||||
|
|
@ -184,6 +189,7 @@ class InferenceBackend:
|
|||
whisper_task="transcribe",
|
||||
load_in_4bit=False,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
FastModel.for_inference(model)
|
||||
model.eval()
|
||||
|
|
@ -209,6 +215,7 @@ class InferenceBackend:
|
|||
max_seq_length=max_seq_length,
|
||||
load_in_4bit=False,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
FastLanguageModel.for_inference(model)
|
||||
self.models[model_name]["model"] = model
|
||||
|
|
@ -240,6 +247,7 @@ class InferenceBackend:
|
|||
dtype=dtype,
|
||||
load_in_4bit=load_in_4bit,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
# Apply inference optimization
|
||||
|
|
@ -270,6 +278,7 @@ class InferenceBackend:
|
|||
processor = AutoProcessor.from_pretrained(
|
||||
processor_source,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info(f"Loaded {type(processor).__name__} from {processor_source}")
|
||||
|
||||
|
|
@ -285,6 +294,7 @@ class InferenceBackend:
|
|||
dtype=dtype,
|
||||
load_in_4bit=load_in_4bit,
|
||||
token=hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
|
||||
# Apply inference optimization
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ class InferenceOrchestrator:
|
|||
dtype=None,
|
||||
load_in_4bit: bool = True,
|
||||
hf_token: Optional[str] = None,
|
||||
trust_remote_code: bool = False,
|
||||
) -> bool:
|
||||
"""Load a model for inference.
|
||||
|
||||
|
|
@ -282,6 +283,7 @@ class InferenceOrchestrator:
|
|||
"load_in_4bit": load_in_4bit,
|
||||
"hf_token": hf_token or "",
|
||||
"gguf_variant": getattr(config, "gguf_variant", None),
|
||||
"trust_remote_code": trust_remote_code,
|
||||
}
|
||||
|
||||
# Always kill existing subprocess and spawn fresh.
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ def _handle_load(backend, config: dict, resp_queue: Any) -> None:
|
|||
max_seq_length=config.get("max_seq_length", 2048),
|
||||
load_in_4bit=load_in_4bit,
|
||||
hf_token=hf_token,
|
||||
trust_remote_code=config.get("trust_remote_code", False),
|
||||
)
|
||||
|
||||
if success:
|
||||
|
|
|
|||
|
|
@ -329,9 +329,11 @@ class UnslothTrainer:
|
|||
load_in_4bit: bool = True,
|
||||
hf_token: Optional[str] = None,
|
||||
is_dataset_image: bool = False,
|
||||
is_dataset_audio: bool = False) -> bool:
|
||||
is_dataset_audio: bool = False,
|
||||
trust_remote_code: bool = False) -> bool:
|
||||
"""Load model for training (supports both text and vision models)"""
|
||||
self.load_in_4bit = load_in_4bit # Store for training_meta.json
|
||||
self.trust_remote_code = trust_remote_code # For AutoProcessor etc. used during training
|
||||
try:
|
||||
if self.model is not None:
|
||||
del self.model
|
||||
|
|
@ -378,6 +380,9 @@ class UnslothTrainer:
|
|||
self.is_audio = self._audio_type is not None
|
||||
self.is_audio_vlm = False
|
||||
|
||||
if not self.is_audio and not self.is_audio_vlm:
|
||||
self._cuda_audio_used = False
|
||||
|
||||
# VLM: vision model with image dataset (mutually exclusive with audio paths)
|
||||
vision = is_vision_model(model_name) if not self.is_audio else False
|
||||
self.is_vlm = not self.is_audio_vlm and vision and is_dataset_image
|
||||
|
|
@ -450,6 +455,7 @@ class UnslothTrainer:
|
|||
auto_model=CsmForConditionalGeneration,
|
||||
load_in_4bit=False,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info("Loaded CSM audio model")
|
||||
|
||||
|
|
@ -465,6 +471,7 @@ class UnslothTrainer:
|
|||
whisper_language="English",
|
||||
whisper_task="transcribe",
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
# Configure generation settings (notebook lines 100-105)
|
||||
self.model.generation_config.language = "<|en|>"
|
||||
|
|
@ -481,6 +488,7 @@ class UnslothTrainer:
|
|||
dtype=None,
|
||||
load_in_4bit=load_in_4bit,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info(f"Loaded {self._audio_type} audio model (FastLanguageModel)")
|
||||
|
||||
|
|
@ -514,6 +522,7 @@ class UnslothTrainer:
|
|||
dtype=torch.float32, # Spark-TTS requires float32
|
||||
load_in_4bit=False,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info("Loaded Spark-TTS (bicodec) model")
|
||||
|
||||
|
|
@ -525,6 +534,7 @@ class UnslothTrainer:
|
|||
max_seq_length=max_seq_length,
|
||||
load_in_4bit=False,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info("Loaded OuteTTS (dac) model (FastModel)")
|
||||
|
||||
|
|
@ -538,6 +548,7 @@ class UnslothTrainer:
|
|||
dtype=None,
|
||||
load_in_4bit=load_in_4bit,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info("Loaded audio VLM model (FastModel)")
|
||||
|
||||
|
|
@ -549,6 +560,7 @@ class UnslothTrainer:
|
|||
dtype=None, # Auto-detect
|
||||
load_in_4bit=load_in_4bit,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info("Loaded vision model")
|
||||
|
||||
|
|
@ -568,6 +580,7 @@ class UnslothTrainer:
|
|||
dtype=None, # Auto-detect
|
||||
load_in_4bit=load_in_4bit,
|
||||
token=hf_token,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
logger.info("Loaded text model")
|
||||
|
||||
|
|
@ -588,7 +601,7 @@ class UnslothTrainer:
|
|||
self._source_code_retried = True
|
||||
print(f"\n'could not get source code' — retrying once...\n")
|
||||
return self.load_model(model_name, max_seq_length, load_in_4bit, hf_token,
|
||||
is_dataset_image, is_dataset_audio)
|
||||
is_dataset_image, is_dataset_audio, trust_remote_code)
|
||||
error_msg = str(e)
|
||||
error_lower = error_msg.lower()
|
||||
if any(k in error_lower for k in ("gated repo", "access to it at", "401", "403", "unauthorized", "forbidden")):
|
||||
|
|
@ -987,7 +1000,10 @@ class UnslothTrainer:
|
|||
from datasets import Audio
|
||||
import torch
|
||||
|
||||
processor = AutoProcessor.from_pretrained(self.model_name)
|
||||
processor = AutoProcessor.from_pretrained(
|
||||
self.model_name,
|
||||
trust_remote_code=getattr(self, "trust_remote_code", False),
|
||||
)
|
||||
|
||||
# Strip pad_to_multiple_of from tokenizer init_kwargs — fine-tuned models
|
||||
# (e.g. keanteng/sesame-csm-elise) save it in tokenizer_config.json, and
|
||||
|
|
@ -1786,18 +1802,20 @@ class UnslothTrainer:
|
|||
eval_enabled = eval_steps is not None and eval_steps > 0
|
||||
|
||||
if local_datasets:
|
||||
# Load local datasets
|
||||
all_data = []
|
||||
# Load local datasets using load_dataset() so the result is
|
||||
# Arrow-backed (has cache files). Dataset.from_list() creates
|
||||
# an in-memory dataset with no cache, which forces num_proc=1
|
||||
# during tokenization/map because sharding requires Arrow files.
|
||||
all_files: list[str] = []
|
||||
for dataset_file in local_datasets:
|
||||
# dataset_file may already be an absolute path from routes/training.py
|
||||
if os.path.isabs(dataset_file):
|
||||
file_path = dataset_file
|
||||
else:
|
||||
# Fallback: try relative to assets/datasets
|
||||
file_path = _ASSETS_DATASETS_ROOT / dataset_file
|
||||
file_path = str(_ASSETS_DATASETS_ROOT / dataset_file)
|
||||
|
||||
file_path_obj = Path(file_path)
|
||||
file_path_str = str(file_path_obj)
|
||||
|
||||
if file_path_obj.is_dir():
|
||||
parquet_dir = (
|
||||
|
|
@ -1807,36 +1825,41 @@ class UnslothTrainer:
|
|||
)
|
||||
parquet_files = sorted(parquet_dir.glob("*.parquet"))
|
||||
if parquet_files:
|
||||
for parquet_file in parquet_files:
|
||||
df = pd.read_parquet(parquet_file)
|
||||
all_data.extend(df.to_dict("records"))
|
||||
all_files.extend(str(p) for p in parquet_files)
|
||||
continue
|
||||
# Fall through to single-file detection for dirs with json/csv
|
||||
candidates: list[Path] = []
|
||||
for ext in ('.json', '.jsonl', '.csv', '.parquet'):
|
||||
candidates.extend(sorted(file_path_obj.glob(f"*{ext}")))
|
||||
if candidates:
|
||||
all_files.extend(str(c) for c in candidates)
|
||||
continue
|
||||
raise ValueError(f"No supported data files in directory: {file_path_obj}")
|
||||
else:
|
||||
all_files.append(str(file_path_obj))
|
||||
|
||||
if file_path_str.endswith('.json'):
|
||||
with open(file_path_obj, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
if isinstance(data, list):
|
||||
all_data.extend(data)
|
||||
else:
|
||||
all_data.append(data)
|
||||
elif file_path_str.endswith('.csv'):
|
||||
df = pd.read_csv(file_path_obj)
|
||||
all_data.extend(df.to_dict('records'))
|
||||
elif file_path_str.endswith('.parquet'):
|
||||
df = pd.read_parquet(file_path_obj)
|
||||
all_data.extend(df.to_dict('records'))
|
||||
continue
|
||||
if all_files:
|
||||
# Determine loader type from the first file extension
|
||||
first_ext = Path(all_files[0]).suffix.lower()
|
||||
if first_ext in ('.json', '.jsonl'):
|
||||
loader = 'json'
|
||||
elif first_ext == '.csv':
|
||||
loader = 'csv'
|
||||
elif first_ext == '.parquet':
|
||||
loader = 'parquet'
|
||||
else:
|
||||
raise ValueError(f"Unsupported local dataset format: {all_files[0]}")
|
||||
|
||||
if all_data:
|
||||
dataset = Dataset.from_list(all_data)
|
||||
dataset = load_dataset(loader, data_files=all_files, split='train')
|
||||
|
||||
# Check if stopped during dataset loading
|
||||
if self.should_stop:
|
||||
print("Stopped during dataset loading\n")
|
||||
return None
|
||||
|
||||
self._update_progress(status_message=f"Loaded {len(all_data)} samples from local files")
|
||||
print(f"Loaded {len(all_data)} samples from local files\n")
|
||||
self._update_progress(status_message=f"Loaded {len(dataset)} samples from local files")
|
||||
print(f"Loaded {len(dataset)} samples from local files\n")
|
||||
print(f"[DEBUG] Dataset cache_files: {dataset.cache_files}\n")
|
||||
|
||||
elif dataset_source:
|
||||
# Load from Hugging Face
|
||||
|
|
@ -1855,7 +1878,8 @@ class UnslothTrainer:
|
|||
|
||||
# Resolve eval split from a separate HF split (explicit or auto-detected)
|
||||
if eval_enabled:
|
||||
if eval_split:
|
||||
effective_train = train_split or "train"
|
||||
if eval_split and eval_split != effective_train:
|
||||
# Explicit eval split provided - load it directly
|
||||
print(f"Loading explicit eval split: '{eval_split}'\n")
|
||||
eval_load_kwargs = {"path": dataset_source, "split": eval_split}
|
||||
|
|
@ -1864,6 +1888,9 @@ class UnslothTrainer:
|
|||
eval_dataset = load_dataset(**eval_load_kwargs)
|
||||
has_separate_eval_source = True
|
||||
print(f"Loaded eval split '{eval_split}' with {len(eval_dataset)} rows\n")
|
||||
elif eval_split and eval_split == effective_train:
|
||||
# Same split as training — will do 80/20 split after formatting
|
||||
print(f"Eval split '{eval_split}' is the same as train split — will split 80/20\n")
|
||||
else:
|
||||
# Auto-detect eval split from HF (returns a separate dataset, or None)
|
||||
eval_dataset = self._auto_detect_eval_split_from_hf(
|
||||
|
|
@ -2360,6 +2387,7 @@ class UnslothTrainer:
|
|||
"dataset_num_proc": 1 if (self.is_audio or self.is_audio_vlm or self._cuda_audio_used) else safe_num_proc(max(1, os.cpu_count() // 4)),
|
||||
"max_seq_length": training_args.get('max_seq_length', 2048),
|
||||
}
|
||||
print(f"[DEBUG] dataset_num_proc={config_args['dataset_num_proc']} (is_audio={self.is_audio}, is_audio_vlm={self.is_audio_vlm}, _cuda_audio_used={self._cuda_audio_used})")
|
||||
|
||||
# On Windows with transformers 5.x, disable DataLoader multiprocessing
|
||||
# to avoid issues with modified sys.path (.venv_t5) in spawned workers.
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ class TrainingBackend:
|
|||
"wandb_project": kwargs.get("wandb_project", "unsloth-training"),
|
||||
"enable_tensorboard": kwargs.get("enable_tensorboard", False),
|
||||
"tensorboard_dir": kwargs.get("tensorboard_dir", "runs"),
|
||||
"trust_remote_code": kwargs.get("trust_remote_code", False),
|
||||
}
|
||||
|
||||
# Derive load_in_4bit from training_type
|
||||
|
|
@ -383,6 +384,9 @@ class TrainingBackend:
|
|||
self.eval_step_history.append(step)
|
||||
self.eval_enabled = True
|
||||
|
||||
elif etype == "eval_configured":
|
||||
self.eval_enabled = True
|
||||
|
||||
elif etype == "status":
|
||||
self._progress.status_message = event.get("message", "")
|
||||
self._progress.is_training = True
|
||||
|
|
|
|||
|
|
@ -135,7 +135,9 @@ def run_training_process(
|
|||
|
||||
# Wire up progress callback → event_queue
|
||||
def _on_progress(progress: TrainingProgress):
|
||||
if progress.step >= 0 and progress.loss > 0:
|
||||
has_train_loss = progress.step >= 0 and progress.loss > 0
|
||||
has_eval_loss = progress.eval_loss is not None
|
||||
if has_train_loss or has_eval_loss:
|
||||
event_queue.put({
|
||||
"type": "progress",
|
||||
"step": progress.step,
|
||||
|
|
@ -192,14 +194,16 @@ def run_training_process(
|
|||
hf_token=hf_token,
|
||||
is_dataset_image=config.get("is_dataset_image", False),
|
||||
is_dataset_audio=config.get("is_dataset_audio", False),
|
||||
trust_remote_code=config.get("trust_remote_code", False),
|
||||
)
|
||||
if not success or trainer.should_stop:
|
||||
if trainer.should_stop:
|
||||
event_queue.put({"type": "complete", "output_dir": None, "ts": time.time()})
|
||||
else:
|
||||
error_msg = trainer.training_progress.error or "Failed to load model"
|
||||
event_queue.put({
|
||||
"type": "error",
|
||||
"error": trainer.training_progress.error or "Failed to load model",
|
||||
"error": error_msg,
|
||||
"stack": "", "ts": time.time(),
|
||||
})
|
||||
return
|
||||
|
|
@ -265,6 +269,14 @@ def run_training_process(
|
|||
if eval_steps is not None and float(eval_steps) <= 0:
|
||||
eval_dataset = None
|
||||
|
||||
# Tell the parent process that eval is configured so the frontend
|
||||
# shows "Waiting for first evaluation step..." instead of "not configured"
|
||||
if eval_dataset is not None:
|
||||
event_queue.put({
|
||||
"type": "eval_configured",
|
||||
"ts": time.time(),
|
||||
})
|
||||
|
||||
if dataset is None or trainer.should_stop:
|
||||
if trainer.should_stop:
|
||||
event_queue.put({"type": "complete", "output_dir": None, "ts": time.time()})
|
||||
|
|
|
|||
|
|
@ -41,6 +41,12 @@ class CheckFormatResponse(BaseModel):
|
|||
warning: Optional[str] = None
|
||||
|
||||
|
||||
class UploadDatasetResponse(BaseModel):
|
||||
"""Response with stored dataset path for training."""
|
||||
filename: str = Field(..., description="Original filename")
|
||||
stored_path: str = Field(..., description="Absolute path stored on backend")
|
||||
|
||||
|
||||
class LocalDatasetItem(BaseModel):
|
||||
class Metadata(BaseModel):
|
||||
actual_num_records: Optional[int] = None
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ class LoadCheckpointRequest(BaseModel):
|
|||
True,
|
||||
description="Whether to load the model in 4-bit quantization",
|
||||
)
|
||||
trust_remote_code: bool = Field(
|
||||
False,
|
||||
description="Allow loading models with custom code. Only enable for checkpoints/base models you trust.",
|
||||
)
|
||||
|
||||
|
||||
class ExportStatusResponse(BaseModel):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ class LoadRequest(BaseModel):
|
|||
load_in_4bit: bool = Field(True, description="Load model in 4-bit quantization")
|
||||
is_lora: bool = Field(False, description="Whether this is a LoRA adapter")
|
||||
gguf_variant: Optional[str] = Field(None, description="GGUF quantization variant (e.g. 'Q4_K_M')")
|
||||
trust_remote_code: bool = Field(
|
||||
False,
|
||||
description="Allow loading models with custom code (e.g. NVIDIA Nemotron). Only enable for repos you trust.",
|
||||
)
|
||||
|
||||
|
||||
class UnloadRequest(BaseModel):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ class TrainingStartRequest(BaseModel):
|
|||
hf_token: Optional[str] = Field(None, description="HuggingFace token")
|
||||
load_in_4bit: bool = Field(True, description="Load model in 4-bit quantization")
|
||||
max_seq_length: int = Field(2048, description="Maximum sequence length")
|
||||
trust_remote_code: bool = Field(
|
||||
False,
|
||||
description="Allow loading models with custom code (e.g. NVIDIA Nemotron). Only enable for repos you trust.",
|
||||
)
|
||||
|
||||
# Dataset parameters
|
||||
hf_dataset: Optional[str] = Field(None, description="HuggingFace dataset identifier")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import io
|
|||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from uuid import uuid4
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||
import logging
|
||||
|
||||
# Add backend directory to path
|
||||
|
|
@ -36,6 +37,7 @@ from models.datasets import (
|
|||
CheckFormatResponse,
|
||||
LocalDatasetItem,
|
||||
LocalDatasetsResponse,
|
||||
UploadDatasetResponse,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -89,8 +91,10 @@ DATA_EXTS = (
|
|||
'.zip',
|
||||
)
|
||||
LOCAL_FILE_EXTS = ('.json', '.jsonl', '.csv', '.parquet')
|
||||
LOCAL_UPLOAD_EXTS = {".csv", ".json", ".jsonl", ".parquet"}
|
||||
BACKEND_ROOT = Path(__file__).resolve().parents[1]
|
||||
LOCAL_DATASETS_ROOT = BACKEND_ROOT / "assets" / "datasets"
|
||||
DATASET_UPLOAD_DIR = LOCAL_DATASETS_ROOT / "uploads"
|
||||
|
||||
|
||||
def _safe_read_metadata(path: Path) -> dict | None:
|
||||
|
|
@ -252,6 +256,50 @@ def _load_local_preview_slice(*, dataset_path: Path, train_split: str, preview_s
|
|||
return preview_slice, total_rows
|
||||
|
||||
|
||||
def _sanitize_filename(filename: str) -> str:
|
||||
name = Path(filename).name.strip().replace("\x00", "")
|
||||
if not name:
|
||||
return "dataset_upload"
|
||||
return name
|
||||
|
||||
|
||||
@router.post("/upload", response_model=UploadDatasetResponse)
|
||||
async def upload_dataset(
|
||||
file: UploadFile,
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
) -> UploadDatasetResponse:
|
||||
filename = _sanitize_filename(file.filename or "dataset_upload")
|
||||
ext = Path(filename).suffix.lower()
|
||||
if ext not in LOCAL_UPLOAD_EXTS:
|
||||
allowed = ", ".join(sorted(LOCAL_UPLOAD_EXTS))
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Unsupported file type: {ext}. Allowed: {allowed}",
|
||||
)
|
||||
|
||||
max_size_bytes = 512 * 1024 * 1024
|
||||
DATASET_UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
stem = Path(filename).stem
|
||||
stored_name = f"{uuid4().hex}_{stem}{ext}"
|
||||
stored_path = DATASET_UPLOAD_DIR / stored_name
|
||||
|
||||
# Stream file to disk in chunks to avoid holding entire file in memory
|
||||
size = 0
|
||||
with open(stored_path, "wb") as f:
|
||||
while chunk := await file.read(1024 * 1024):
|
||||
size += len(chunk)
|
||||
if size > max_size_bytes:
|
||||
stored_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=413, detail="File too large (max 512MB)")
|
||||
f.write(chunk)
|
||||
|
||||
if size == 0:
|
||||
stored_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=400, detail="Empty upload payload")
|
||||
|
||||
return UploadDatasetResponse(filename=filename, stored_path=str(stored_path))
|
||||
|
||||
|
||||
@router.get("/local", response_model=LocalDatasetsResponse)
|
||||
def list_local_datasets(
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ async def load_checkpoint(
|
|||
checkpoint_path=request.checkpoint_path,
|
||||
max_seq_length=request.max_seq_length,
|
||||
load_in_4bit=request.load_in_4bit,
|
||||
trust_remote_code=request.trust_remote_code,
|
||||
)
|
||||
|
||||
if not success:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ try:
|
|||
from core.inference.llama_cpp import LlamaCppBackend
|
||||
from utils.models import ModelConfig
|
||||
from utils.inference import load_inference_config
|
||||
from utils.models.model_config import load_model_defaults
|
||||
except ImportError:
|
||||
parent_backend = backend_path.parent / "backend"
|
||||
if str(parent_backend) not in sys.path:
|
||||
|
|
@ -34,6 +35,7 @@ except ImportError:
|
|||
from core.inference.llama_cpp import LlamaCppBackend
|
||||
from utils.models import ModelConfig
|
||||
from utils.inference import load_inference_config
|
||||
from utils.models.model_config import load_model_defaults
|
||||
|
||||
from models.inference import (
|
||||
LoadRequest,
|
||||
|
|
@ -228,9 +230,22 @@ async def load_model(
|
|||
max_seq_length=request.max_seq_length,
|
||||
load_in_4bit=load_in_4bit,
|
||||
hf_token=request.hf_token,
|
||||
trust_remote_code=request.trust_remote_code,
|
||||
)
|
||||
|
||||
if not success:
|
||||
# Check if YAML says this model needs trust_remote_code
|
||||
if not request.trust_remote_code:
|
||||
model_defaults = load_model_defaults(config.identifier)
|
||||
yaml_trust = model_defaults.get("inference", {}).get("trust_remote_code", False)
|
||||
if yaml_trust:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
f"Model '{config.display_name}' requires trust_remote_code to be enabled. "
|
||||
f"Please enable 'Trust remote code' in Chat Settings and try again."
|
||||
),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to load model: {config.display_name}"
|
||||
|
|
@ -476,7 +491,7 @@ async def get_status(
|
|||
|
||||
|
||||
@router.post("/audio/generate")
|
||||
async def generate_audio(payload: ChatCompletionRequest, request: Request):
|
||||
async def generate_audio(payload: ChatCompletionRequest, request: Request, current_subject: str = Depends(get_current_subject)):
|
||||
"""
|
||||
Generate audio (TTS) from the latest user message.
|
||||
Returns a JSON response with base64-encoded WAV audio.
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ if str(backend_path) not in sys.path:
|
|||
# Import backend functions
|
||||
try:
|
||||
from core.training import get_training_backend
|
||||
from utils.models.model_config import load_model_defaults
|
||||
except ImportError:
|
||||
# Fallback: try to import from parent directory
|
||||
parent_backend = backend_path.parent / "backend"
|
||||
if str(parent_backend) not in sys.path:
|
||||
sys.path.insert(0, str(parent_backend))
|
||||
from core.training import get_training_backend
|
||||
from utils.models.model_config import load_model_defaults
|
||||
|
||||
# Auth
|
||||
from auth.authentication import get_current_subject
|
||||
|
|
@ -200,8 +202,19 @@ async def start_training(
|
|||
"wandb_project": request.wandb_project or "",
|
||||
"enable_tensorboard": request.enable_tensorboard,
|
||||
"tensorboard_dir": request.tensorboard_dir or "",
|
||||
"trust_remote_code": request.trust_remote_code,
|
||||
}
|
||||
|
||||
# Training page has no trust_remote_code toggle — the value comes from
|
||||
# YAML model defaults applied when the user selects a model. As a safety
|
||||
# net, consult the YAML directly so models that need it always get it.
|
||||
if not training_kwargs["trust_remote_code"]:
|
||||
model_defaults = load_model_defaults(request.model_name)
|
||||
yaml_trust = model_defaults.get("training", {}).get("trust_remote_code", False)
|
||||
if yaml_trust:
|
||||
logger.info(f"YAML config sets trust_remote_code=True for {request.model_name}")
|
||||
training_kwargs["trust_remote_code"] = True
|
||||
|
||||
# Free GPU memory: shut down any running inference/export subprocesses
|
||||
# before training starts (they'd compete for VRAM otherwise)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ def load_inference_config(model_identifier: str) -> Dict[str, Any]:
|
|||
"top_p": model_inference.get("top_p", default_inference.get("top_p", 0.95)),
|
||||
"top_k": model_inference.get("top_k", default_inference.get("top_k", -1)),
|
||||
"min_p": model_inference.get("min_p", default_inference.get("min_p", 0.01)),
|
||||
"trust_remote_code": model_inference.get("trust_remote_code", default_inference.get("trust_remote_code", False)),
|
||||
}
|
||||
|
||||
return inference_config
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ export const DEFAULT_HYPERPARAMS = {
|
|||
enableTensorboard: false,
|
||||
tensorboardDir: "runs",
|
||||
logFrequency: 10,
|
||||
trustRemoteCode: false,
|
||||
finetuneVisionLayers: true,
|
||||
finetuneLanguageLayers: true,
|
||||
finetuneAttentionModules: true,
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ export function ChatSettingsPanel({
|
|||
...p.params,
|
||||
systemPrompt: params.systemPrompt,
|
||||
checkpoint: params.checkpoint,
|
||||
trustRemoteCode: params.trustRemoteCode,
|
||||
});
|
||||
setActivePreset(name);
|
||||
}
|
||||
|
|
@ -330,17 +331,31 @@ export function ChatSettingsPanel({
|
|||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection icon={Settings02Icon} label="Settings">
|
||||
<div className="flex items-center justify-between gap-3 py-1">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium">Auto title</div>
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
Generate short title after reply.
|
||||
<div className="flex flex-col gap-3 py-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium">Auto title</div>
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
Generate short title after reply.
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={autoTitle}
|
||||
onCheckedChange={onAutoTitleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium">Trust remote code</div>
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
Allow models with custom code (e.g. Nemotron). Only enable for repos you trust.
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={params.trustRemoteCode ?? false}
|
||||
onCheckedChange={set("trustRemoteCode")}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
checked={autoTitle}
|
||||
onCheckedChange={onAutoTitleChange}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -124,6 +124,10 @@ function mergeRecommendedInference(
|
|||
topP: toFiniteNumber(inference?.top_p) ?? current.topP,
|
||||
topK: toFiniteNumber(inference?.top_k) ?? current.topK,
|
||||
minP: toFiniteNumber(inference?.min_p) ?? current.minP,
|
||||
trustRemoteCode:
|
||||
typeof inference?.trust_remote_code === "boolean"
|
||||
? inference.trust_remote_code
|
||||
: current.trustRemoteCode,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -232,6 +236,7 @@ export function useChatModelRuntime() {
|
|||
previousWasUnloaded = true;
|
||||
}
|
||||
|
||||
const paramsBeforeLoad = useChatRuntimeStore.getState().params;
|
||||
const loadResponse = await loadModel({
|
||||
model_path: modelId,
|
||||
hf_token: null,
|
||||
|
|
@ -239,6 +244,7 @@ export function useChatModelRuntime() {
|
|||
load_in_4bit: true,
|
||||
is_lora: isLora,
|
||||
gguf_variant: ggufVariant ?? null,
|
||||
trust_remote_code: paramsBeforeLoad.trustRemoteCode ?? false,
|
||||
});
|
||||
|
||||
const currentParams = useChatRuntimeStore.getState().params;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ export interface LoadModelRequest {
|
|||
load_in_4bit: boolean;
|
||||
is_lora: boolean;
|
||||
gguf_variant?: string | null;
|
||||
/** Allow loading models with custom code (e.g. NVIDIA Nemotron). Only enable for repos you trust. */
|
||||
trust_remote_code?: boolean;
|
||||
}
|
||||
|
||||
export interface ValidateModelResponse {
|
||||
|
|
@ -74,6 +76,7 @@ export interface LoadModelResponse {
|
|||
top_p?: number;
|
||||
top_k?: number;
|
||||
min_p?: number;
|
||||
trust_remote_code?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ export interface InferenceParams {
|
|||
maxTokens: number;
|
||||
systemPrompt: string;
|
||||
checkpoint: string;
|
||||
/** Allow loading models with custom code (e.g. NVIDIA Nemotron). Only enable for repos you trust. */
|
||||
trustRemoteCode?: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_INFERENCE_PARAMS: InferenceParams = {
|
||||
|
|
@ -18,6 +20,7 @@ export const DEFAULT_INFERENCE_PARAMS: InferenceParams = {
|
|||
maxTokens: 4092,
|
||||
systemPrompt: "",
|
||||
checkpoint: "",
|
||||
trustRemoteCode: false,
|
||||
};
|
||||
|
||||
export interface ChatModelSummary {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ export async function loadCheckpoint(params: {
|
|||
checkpoint_path: string;
|
||||
max_seq_length?: number;
|
||||
load_in_4bit?: boolean;
|
||||
/** Allow loading models with custom code. Only enable for checkpoints you trust. */
|
||||
trust_remote_code?: boolean;
|
||||
}): Promise<ExportOperationResponse> {
|
||||
const response = await authFetch("/api/export/load-checkpoint", {
|
||||
method: "POST",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import {
|
|||
} from "@/hooks";
|
||||
import {
|
||||
HfDatasetSubsetSplitSelectors,
|
||||
uploadTrainingDataset,
|
||||
useDatasetPreviewDialogStore,
|
||||
useTrainingConfigStore,
|
||||
} from "@/features/training";
|
||||
|
|
@ -52,7 +53,8 @@ import {
|
|||
ViewIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
|
||||
const SEARCH_INPUT_REASONS = new Set(["input-change", "input-paste", "input-clear"]);
|
||||
|
|
@ -72,7 +74,11 @@ function deriveLocalDatasetName(path: string): string {
|
|||
const parts = normalized.split("/").filter(Boolean);
|
||||
const parquetIndex = parts.lastIndexOf("parquet-files");
|
||||
if (parquetIndex > 0) return parts[parquetIndex - 1];
|
||||
return parts[parts.length - 1] ?? path;
|
||||
const basename = parts[parts.length - 1] ?? path;
|
||||
// Strip UUID prefix from uploaded files (format: {32hex}_{original})
|
||||
const uuidPrefixMatch = basename.match(/^[a-f0-9]{32}_(.+)$/);
|
||||
if (uuidPrefixMatch) return uuidPrefixMatch[1];
|
||||
return basename;
|
||||
}
|
||||
|
||||
function formatUpdatedDate(timestamp: number | null): string {
|
||||
|
|
@ -286,6 +292,8 @@ export function DatasetSection() {
|
|||
if (datasetSource !== "upload") return;
|
||||
if (!uploadedFile) return;
|
||||
if (selectedLocalDataset) return;
|
||||
// Don't clear if this is a direct file upload (not a recipe directory)
|
||||
if (isLikelyLocalDatasetRef(uploadedFile)) return;
|
||||
selectLocalDataset(null);
|
||||
}, [
|
||||
datasetSource,
|
||||
|
|
@ -320,11 +328,49 @@ export function DatasetSection() {
|
|||
const selectedLocalUpdatedAt = selectedLocalDataset?.updated_at ?? null;
|
||||
|
||||
const comboboxAnchorRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { scrollRef, sentinelRef } = useInfiniteScroll(
|
||||
fetchMore,
|
||||
hfResults.length,
|
||||
);
|
||||
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const handleUploadButtonClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleDatasetFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = "";
|
||||
if (!file) return;
|
||||
|
||||
const MAX_SIZE_BYTES = 512 * 1024 * 1024;
|
||||
if (file.size > MAX_SIZE_BYTES) {
|
||||
toast.error("File too large", {
|
||||
description: "Maximum upload size is 512 MB.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
try {
|
||||
const uploaded = await uploadTrainingDataset(file);
|
||||
|
||||
selectLocalDataset(uploaded.stored_path);
|
||||
|
||||
toast.success("Dataset uploaded", {
|
||||
description: uploaded.filename,
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error("Upload failed", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-tour="studio-dataset" className="col-span-1 xl:col-span-4">
|
||||
<SectionCard
|
||||
|
|
@ -596,7 +642,7 @@ export function DatasetSection() {
|
|||
datasetEvalSplit={datasetEvalSplit}
|
||||
setDatasetEvalSplit={setDatasetEvalSplit}
|
||||
/>
|
||||
) : datasetSource === "upload" ? (
|
||||
) : datasetSource === "upload" && selectedLocalDataset ? (
|
||||
<div className="rounded-lg border bg-muted/20 px-3.5 py-3">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
|
|
@ -609,45 +655,39 @@ export function DatasetSection() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{uploadedFile ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs">
|
||||
<MetadataRow
|
||||
label="Rows"
|
||||
value={
|
||||
typeof selectedLocalRows === "number"
|
||||
? selectedLocalRows.toLocaleString()
|
||||
: "--"
|
||||
}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Columns"
|
||||
value={
|
||||
selectedLocalColumns.length > 0
|
||||
? String(selectedLocalColumns.length)
|
||||
: "--"
|
||||
}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Batches"
|
||||
value={
|
||||
typeof selectedLocalMetadata?.num_completed_batches === "number" &&
|
||||
typeof selectedLocalMetadata?.total_num_batches === "number"
|
||||
? `${selectedLocalMetadata.num_completed_batches}/${selectedLocalMetadata.total_num_batches}`
|
||||
: "--"
|
||||
}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Updated"
|
||||
value={formatUpdatedDate(selectedLocalUpdatedAt)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs">
|
||||
<MetadataRow
|
||||
label="Rows"
|
||||
value={
|
||||
typeof selectedLocalRows === "number"
|
||||
? selectedLocalRows.toLocaleString()
|
||||
: "--"
|
||||
}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Columns"
|
||||
value={
|
||||
selectedLocalColumns.length > 0
|
||||
? String(selectedLocalColumns.length)
|
||||
: "--"
|
||||
}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Batches"
|
||||
value={
|
||||
typeof selectedLocalMetadata?.num_completed_batches === "number" &&
|
||||
typeof selectedLocalMetadata?.total_num_batches === "number"
|
||||
? `${selectedLocalMetadata.num_completed_batches}/${selectedLocalMetadata.total_num_batches}`
|
||||
: "--"
|
||||
}
|
||||
/>
|
||||
<MetadataRow
|
||||
label="Updated"
|
||||
value={formatUpdatedDate(selectedLocalUpdatedAt)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Select a local dataset to view metadata.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
|
@ -839,9 +879,15 @@ export function DatasetSection() {
|
|||
variant="outline"
|
||||
size="sm"
|
||||
className="cursor-pointer gap-1.5"
|
||||
disabled={isUploading}
|
||||
onClick={handleUploadButtonClick}
|
||||
>
|
||||
<HugeiconsIcon icon={CloudUploadIcon} className="size-3.5" />
|
||||
Upload
|
||||
{isUploading ? (
|
||||
<Spinner className="size-3.5" />
|
||||
) : (
|
||||
<HugeiconsIcon icon={CloudUploadIcon} className="size-3.5" />
|
||||
)}
|
||||
{isUploading ? "Uploading..." : "Upload"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -855,6 +901,15 @@ export function DatasetSection() {
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json,.jsonl,.csv,.parquet"
|
||||
className="hidden"
|
||||
onChange={(event) => {
|
||||
void handleDatasetFileChange(event);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SectionCard>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
serializeConfigToYaml,
|
||||
useTrainingActions,
|
||||
useTrainingConfigStore,
|
||||
validateTrainingConfig,
|
||||
} from "@/features/training";
|
||||
import {
|
||||
Archive04Icon,
|
||||
|
|
@ -43,7 +44,8 @@ export function TrainingSection() {
|
|||
const { isStarting, startError, startTrainingRun } = useTrainingActions();
|
||||
const isIncompatible =
|
||||
!store.isVisionModel && store.isDatasetImage === true;
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const configValidation = validateTrainingConfig(store);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
|
|
@ -150,7 +152,7 @@ export function TrainingSection() {
|
|||
data-tour="studio-start"
|
||||
className="w-full cursor-pointer bg-gradient-to-r from-emerald-500 to-teal-500 text-white hover:from-emerald-600 hover:to-teal-600"
|
||||
onClick={() => void startTrainingRun()}
|
||||
disabled={isStarting || isIncompatible}
|
||||
disabled={isStarting || isIncompatible || !configValidation.ok}
|
||||
>
|
||||
<HugeiconsIcon icon={Rocket01Icon} className="size-4" />
|
||||
{isStarting ? "Starting..." : "Start Training"}
|
||||
|
|
@ -163,6 +165,9 @@ export function TrainingSection() {
|
|||
Text model is not compatible with a multimodal dataset. Switch to a vision model or choose a text-only dataset.
|
||||
</p>
|
||||
)}
|
||||
{!configValidation.ok && configValidation.message && !isIncompatible && (
|
||||
<p className="text-xs text-red-500 leading-relaxed">{configValidation.message}</p>
|
||||
)}
|
||||
|
||||
{/* Upload / Save / Reset */}
|
||||
<p className="text-xs text-muted-foreground">Training Config</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type {
|
||||
CheckFormatResponse,
|
||||
LocalDatasetsResponse,
|
||||
UploadDatasetResponse,
|
||||
} from "../types/datasets";
|
||||
import { authFetch } from "@/features/auth";
|
||||
|
||||
|
|
@ -39,6 +40,25 @@ export async function checkDatasetFormat({
|
|||
return res.json();
|
||||
}
|
||||
|
||||
export async function uploadTrainingDataset(
|
||||
file: File,
|
||||
): Promise<UploadDatasetResponse> {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
|
||||
const res = await authFetch("/api/datasets/upload", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => null);
|
||||
throw new Error(body?.detail || `Upload failed (${res.status})`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function listLocalDatasets(): Promise<LocalDatasetsResponse> {
|
||||
const res = await authFetch("/api/datasets/local");
|
||||
if (!res.ok) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export function buildTrainingStartPayload(
|
|||
hf_token: config.hfToken.trim() || null,
|
||||
load_in_4bit: adapterMethod ? isQloraMethod : false,
|
||||
max_seq_length: config.contextLength,
|
||||
trust_remote_code: config.trustRemoteCode ?? false,
|
||||
hf_dataset: hfDataset,
|
||||
subset: hfDataset ? config.datasetSubset : null,
|
||||
train_split: hfDataset ? config.datasetSplit : null,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface BackendTrainingDefaults {
|
|||
packing?: boolean;
|
||||
train_on_completions?: boolean;
|
||||
gradient_checkpointing?: "none" | "true" | "unsloth";
|
||||
trust_remote_code?: boolean;
|
||||
}
|
||||
|
||||
interface BackendLoraDefaults {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ export { useTrainingActions } from "./hooks/use-training-actions";
|
|||
export { useTrainingRuntimeLifecycle } from "./hooks/use-training-runtime-lifecycle";
|
||||
export { HfDatasetSubsetSplitSelectors } from "./components/hf-dataset-subset-split-selectors";
|
||||
export { useDatasetPreviewDialogStore } from "./stores/dataset-preview-dialog-store";
|
||||
export { uploadTrainingDataset } from "./api/datasets-api";
|
||||
export { listLocalModels } from "./api/models-api";
|
||||
export type { LocalModelInfo } from "./api/models-api";
|
||||
export type { TrainingPhase } from "./types/runtime";
|
||||
export { parseYamlConfig, serializeConfigToYaml } from "./lib/yaml-config";
|
||||
export { validateTrainingConfig } from "./lib/validation";
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type ModelDefaultsPatch = Partial<
|
|||
| "tensorboardDir"
|
||||
| "logFrequency"
|
||||
| "finetuneVisionLayers"
|
||||
| "trustRemoteCode"
|
||||
| "finetuneLanguageLayers"
|
||||
| "finetuneAttentionModules"
|
||||
| "finetuneMLPModules"
|
||||
|
|
@ -133,6 +134,9 @@ export function mapBackendModelConfigToTrainingPatch(
|
|||
patch.gradientCheckpointing = gradientCheckpointing;
|
||||
}
|
||||
|
||||
const trustRemoteCode = toBoolean(training?.trust_remote_code);
|
||||
if (trustRemoteCode !== undefined) patch.trustRemoteCode = trustRemoteCode;
|
||||
|
||||
const loraRank = toNumber(lora?.lora_r);
|
||||
if (loraRank !== undefined) patch.loraRank = loraRank;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,18 +16,22 @@ export function validateTrainingConfig(
|
|||
if (!config.dataset) {
|
||||
return { ok: false, message: "Select a Hugging Face dataset first." };
|
||||
}
|
||||
return { ok: true, message: null };
|
||||
}
|
||||
|
||||
if (config.datasetSource === "upload") {
|
||||
} else if (config.datasetSource === "upload") {
|
||||
if (!config.uploadedFile) {
|
||||
return { ok: false, message: "Select a local dataset first." };
|
||||
}
|
||||
return { ok: true, message: null };
|
||||
} else {
|
||||
return { ok: false, message: "Unsupported dataset source." };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
message: "Unsupported dataset source.",
|
||||
};
|
||||
// Eval steps requires an eval split to be selected
|
||||
if (config.evalSteps > 0 && !config.datasetEvalSplit) {
|
||||
return {
|
||||
ok: false,
|
||||
message:
|
||||
"Eval Steps is set but no Eval Split is selected. Choose an Eval Split or set Eval Steps to 0.",
|
||||
};
|
||||
}
|
||||
|
||||
return { ok: true, message: null };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,6 +285,9 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
|
|||
uploadedFile,
|
||||
...resetDatasetState(),
|
||||
});
|
||||
if (uploadedFile) {
|
||||
runDatasetCheck(uploadedFile, "train");
|
||||
}
|
||||
},
|
||||
setDatasetFormat: (datasetFormat) => set({ datasetFormat }),
|
||||
setDataset: (dataset) => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export interface TrainingStartRequest {
|
|||
hf_token: string | null;
|
||||
load_in_4bit: boolean;
|
||||
max_seq_length: number;
|
||||
/** Allow loading models with custom code. Only enable for repos you trust. */
|
||||
trust_remote_code?: boolean;
|
||||
hf_dataset: string | null;
|
||||
subset: string | null;
|
||||
train_split: string | null;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ export interface TrainingConfigState {
|
|||
isCheckingDataset: boolean;
|
||||
isDatasetImage: boolean | null;
|
||||
isDatasetAudio: boolean;
|
||||
trustRemoteCode: boolean;
|
||||
finetuneVisionLayers: boolean;
|
||||
finetuneLanguageLayers: boolean;
|
||||
finetuneAttentionModules: boolean;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue