Skip to content

Commit 5987a8d

Browse files
authored
[webui] add seed controls for reproducibility (#10629)
Co-authored-by: Karunanidhi Mishra <11963379+kmishra1204@users.noreply.github.com>
1 parent a61cfa6 commit 5987a8d

9 files changed

Lines changed: 104 additions & 5 deletions

File tree

src/llamafactory/chat/hf_engine.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from typing import TYPE_CHECKING, Any, Optional, Union
2020

2121
import torch
22-
from transformers import GenerationConfig, TextIteratorStreamer
22+
from transformers import GenerationConfig, TextIteratorStreamer, set_seed
2323
from typing_extensions import override
2424

2525
from ..data import get_template_and_fix_tokenizer
@@ -128,6 +128,7 @@ def _process_args(
128128
skip_special_tokens: Optional[bool] = input_kwargs.pop("skip_special_tokens", None)
129129
max_length: Optional[int] = input_kwargs.pop("max_length", None)
130130
max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
131+
seed: Optional[int] = input_kwargs.pop("seed", None)
131132
stop: Optional[Union[str, list[str]]] = input_kwargs.pop("stop", None)
132133

133134
if stop is not None:
@@ -177,6 +178,8 @@ def _process_args(
177178
attention_mask=attention_mask,
178179
generation_config=GenerationConfig(**generating_args),
179180
)
181+
if seed is not None:
182+
gen_kwargs["_seed"] = seed
180183

181184
mm_inputs = template.mm_plugin.get_mm_inputs(**mm_input_dict, batch_ids=[prompt_ids], processor=processor)
182185
for key, value in mm_inputs.items():
@@ -237,6 +240,10 @@ def _chat(
237240
audios,
238241
input_kwargs,
239242
)
243+
seed = gen_kwargs.pop("_seed", None)
244+
if seed is not None:
245+
set_seed(seed)
246+
240247
generate_output = model.generate(**gen_kwargs)
241248
if isinstance(generate_output, tuple):
242249
generate_output = generate_output[1][0] # post-process the minicpm_o output
@@ -292,6 +299,10 @@ def _stream_chat(
292299
audios,
293300
input_kwargs,
294301
)
302+
seed = gen_kwargs.pop("_seed", None)
303+
if seed is not None:
304+
set_seed(seed)
305+
295306
streamer = TextIteratorStreamer(
296307
tokenizer,
297308
skip_prompt=True,

src/llamafactory/chat/sglang_engine.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ async def _generate(
171171
skip_special_tokens: Optional[bool] = input_kwargs.pop("skip_special_tokens", None)
172172
max_length: Optional[int] = input_kwargs.pop("max_length", None)
173173
max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
174+
seed: Optional[int] = input_kwargs.pop("seed", None)
174175
stop: Optional[Union[str, list[str]]] = input_kwargs.pop("stop", None)
175176

176177
if num_return_sequences != 1:
@@ -205,6 +206,8 @@ async def _generate(
205206
if skip_special_tokens is not None
206207
else self.generating_args["skip_special_tokens"],
207208
}
209+
if seed is not None:
210+
sampling_params["seed"] = seed
208211

209212
def stream_request():
210213
json_data = {

src/llamafactory/chat/vllm_engine.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ async def _generate(
144144
skip_special_tokens: Optional[bool] = input_kwargs.pop("skip_special_tokens", None)
145145
max_length: Optional[int] = input_kwargs.pop("max_length", None)
146146
max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
147+
seed: Optional[int] = input_kwargs.pop("seed", None)
147148
stop: Optional[Union[str, list[str]]] = input_kwargs.pop("stop", None)
148149

149150
if length_penalty is not None:
@@ -163,7 +164,7 @@ async def _generate(
163164
if max_new_tokens:
164165
max_tokens = max_new_tokens
165166

166-
sampling_params = SamplingParams(
167+
sampling_kwargs = dict(
167168
n=num_return_sequences,
168169
repetition_penalty=(
169170
repetition_penalty if repetition_penalty is not None else self.generating_args["repetition_penalty"]
@@ -179,6 +180,10 @@ async def _generate(
179180
if skip_special_tokens is not None
180181
else self.generating_args["skip_special_tokens"],
181182
)
183+
if seed is not None:
184+
sampling_kwargs["seed"] = seed
185+
186+
sampling_params = SamplingParams(**sampling_kwargs)
182187

183188
multi_modal_data = {}
184189
if images is not None: # add image features

src/llamafactory/webui/chatter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def stream(
203203
max_new_tokens: int,
204204
top_p: float,
205205
temperature: float,
206+
seed: float | int,
206207
skip_special_tokens: bool,
207208
escape_html: bool,
208209
enable_thinking: bool,
@@ -225,6 +226,7 @@ def stream(
225226
max_new_tokens=max_new_tokens,
226227
top_p=top_p,
227228
temperature=temperature,
229+
seed=None if seed is None or int(seed) < 0 else int(seed),
228230
skip_special_tokens=skip_special_tokens,
229231
):
230232
response += new_text

src/llamafactory/webui/components/chatbot.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def create_chat_box(
8585
max_new_tokens = gr.Slider(minimum=8, maximum=8192, value=1024, step=1)
8686
top_p = gr.Slider(minimum=0.01, maximum=1.0, value=0.7, step=0.01)
8787
temperature = gr.Slider(minimum=0.01, maximum=1.5, value=0.95, step=0.01)
88+
seed = gr.Number(value=-1, precision=0)
8889
skip_special_tokens = gr.Checkbox(value=True)
8990
escape_html = gr.Checkbox(value=True)
9091
enable_thinking = gr.Checkbox(value=True)
@@ -110,6 +111,7 @@ def create_chat_box(
110111
max_new_tokens,
111112
top_p,
112113
temperature,
114+
seed,
113115
skip_special_tokens,
114116
escape_html,
115117
enable_thinking,
@@ -135,6 +137,7 @@ def create_chat_box(
135137
max_new_tokens=max_new_tokens,
136138
top_p=top_p,
137139
temperature=temperature,
140+
seed=seed,
138141
skip_special_tokens=skip_special_tokens,
139142
escape_html=escape_html,
140143
enable_thinking=enable_thinking,

src/llamafactory/webui/components/eval.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ def create_eval_tab(engine: "Engine") -> dict[str, "Component"]:
5555
max_new_tokens = gr.Slider(minimum=8, maximum=4096, value=512, step=1)
5656
top_p = gr.Slider(minimum=0.01, maximum=1, value=0.7, step=0.01)
5757
temperature = gr.Slider(minimum=0.01, maximum=1.5, value=0.95, step=0.01)
58+
eval_seed = gr.Textbox(value="42")
5859
output_dir = gr.Textbox()
5960

60-
input_elems.update({max_new_tokens, top_p, temperature, output_dir})
61-
elem_dict.update(dict(max_new_tokens=max_new_tokens, top_p=top_p, temperature=temperature, output_dir=output_dir))
61+
input_elems.update({max_new_tokens, top_p, temperature, eval_seed, output_dir})
62+
elem_dict.update(
63+
dict(max_new_tokens=max_new_tokens, top_p=top_p, temperature=temperature, eval_seed=eval_seed, output_dir=output_dir)
64+
)
6265

6366
with gr.Row():
6467
cmd_preview_btn = gr.Button()

src/llamafactory/webui/components/train.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,17 @@ def create_train_tab(engine: "Engine") -> dict[str, "Component"]:
5252
learning_rate = gr.Textbox(value="5e-5")
5353
num_train_epochs = gr.Textbox(value="3.0")
5454
max_grad_norm = gr.Textbox(value="1.0")
55+
train_seed = gr.Textbox(value="42")
5556
max_samples = gr.Textbox(value="100000")
5657
compute_type = gr.Dropdown(choices=["bf16", "fp16", "fp32", "pure_bf16"], value="bf16")
5758

58-
input_elems.update({learning_rate, num_train_epochs, max_grad_norm, max_samples, compute_type})
59+
input_elems.update({learning_rate, num_train_epochs, max_grad_norm, train_seed, max_samples, compute_type})
5960
elem_dict.update(
6061
dict(
6162
learning_rate=learning_rate,
6263
num_train_epochs=num_train_epochs,
6364
max_grad_norm=max_grad_norm,
65+
train_seed=train_seed,
6466
max_samples=max_samples,
6567
compute_type=compute_type,
6668
)

src/llamafactory/webui/locales.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,28 @@
539539
"info": "勾配クリッピングのためのノルム。",
540540
},
541541
},
542+
"train_seed": {
543+
"en": {
544+
"label": "Seed",
545+
"info": "Random seed for training.",
546+
},
547+
"ru": {
548+
"label": "Seed",
549+
"info": "Random seed for training.",
550+
},
551+
"zh": {
552+
"label": "随机种子",
553+
"info": "训练使用的随机种子。",
554+
},
555+
"ko": {
556+
"label": "Seed",
557+
"info": "Random seed for training.",
558+
},
559+
"ja": {
560+
"label": "Seed",
561+
"info": "Random seed for training.",
562+
},
563+
},
542564
"max_samples": {
543565
"en": {
544566
"label": "Max samples",
@@ -2708,6 +2730,45 @@
27082730
"label": "温度",
27092731
},
27102732
},
2733+
"seed": {
2734+
"en": {
2735+
"label": "Generation seed (-1 for random)",
2736+
},
2737+
"ru": {
2738+
"label": "Generation seed (-1 = random)",
2739+
},
2740+
"zh": {
2741+
"label": "生成随机种子(-1 表示随机)",
2742+
},
2743+
"ko": {
2744+
"label": "Generation seed (-1 = random)",
2745+
},
2746+
"ja": {
2747+
"label": "Generation seed (-1 = random)",
2748+
},
2749+
},
2750+
"eval_seed": {
2751+
"en": {
2752+
"label": "Seed",
2753+
"info": "Random seed for evaluation and prediction.",
2754+
},
2755+
"ru": {
2756+
"label": "Seed",
2757+
"info": "Random seed for evaluation and prediction.",
2758+
},
2759+
"zh": {
2760+
"label": "随机种子",
2761+
"info": "评估和预测使用的随机种子。",
2762+
},
2763+
"ko": {
2764+
"label": "Seed",
2765+
"info": "Random seed for evaluation and prediction.",
2766+
},
2767+
"ja": {
2768+
"label": "Seed",
2769+
"info": "Random seed for evaluation and prediction.",
2770+
},
2771+
},
27112772
"skip_special_tokens": {
27122773
"en": {
27132774
"label": "Skip special tokens",

src/llamafactory/webui/runner.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
from .manager import Manager
5252

5353

54+
def _parse_seed(value: Any, default: int = 42) -> int:
55+
try:
56+
return int(value)
57+
except (TypeError, ValueError):
58+
return default
59+
60+
5461
class Runner:
5562
r"""A class to manage the running status of the trainers."""
5663

@@ -147,6 +154,7 @@ def _parse_train_args(self, data: dict["Component", Any]) -> dict[str, Any]:
147154
learning_rate=float(get("train.learning_rate")),
148155
num_train_epochs=float(get("train.num_train_epochs")),
149156
max_samples=int(get("train.max_samples")),
157+
seed=_parse_seed(get("train.train_seed")),
150158
per_device_train_batch_size=get("train.batch_size"),
151159
gradient_accumulation_steps=get("train.gradient_accumulation_steps"),
152160
lr_scheduler_type=get("train.lr_scheduler_type"),
@@ -316,6 +324,7 @@ def _parse_eval_args(self, data: dict["Component", Any]) -> dict[str, Any]:
316324
max_new_tokens=get("eval.max_new_tokens"),
317325
top_p=get("eval.top_p"),
318326
temperature=get("eval.temperature"),
327+
seed=_parse_seed(get("eval.eval_seed")),
319328
output_dir=get_save_dir(model_name, finetuning_type, get("eval.output_dir")),
320329
trust_remote_code=True,
321330
ddp_timeout=180000000,

0 commit comments

Comments
 (0)