modelforge-finetuning 3.0.1__tar.gz → 3.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/formatters/formatting_functions.py +48 -7
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/prompts.py +133 -21
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/wizard.py +1 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/schemas/training_schemas.py +3 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/qlora_strategy.py +26 -1
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/sft_strategy.py +26 -1
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/PKG-INFO +1 -1
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/PKG-INFO +1 -1
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/pyproject.toml +1 -1
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/LICENSE +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/MANIFEST.in +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/asset-manifest.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/favicon.ico +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/favicon.jpg +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/index.html +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/logo192.png +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/logo512.png +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/manifest.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/robots.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/css/main.5a3d6ea1.css +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/css/main.5a3d6ea1.css.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/css/main.9680427d.css +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/css/main.9680427d.css.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/453.6e4f956a.chunk.js +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/453.6e4f956a.chunk.js.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/453.ad6eb26e.chunk.js +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/453.ad6eb26e.chunk.js.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.2b0344e0.js +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.2b0344e0.js.LICENSE.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.2b0344e0.js.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.9cc9257a.js +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.9cc9257a.js.LICENSE.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.9cc9257a.js.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.9df15fbf.js +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.9df15fbf.js.LICENSE.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/static/js/main.9df15fbf.js.map +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/app.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/cli.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/database/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/database/database_manager.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/database/models.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/dependencies.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/evaluation/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/evaluation/dataset_validator.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/evaluation/metrics.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/exceptions.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/formatters/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/globals/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/globals/globals.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/globals/globals_instance.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/logging_config.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/model_configs/high_end.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/model_configs/low_end.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/model_configs/mid_range.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/progress.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/huggingface_provider.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/provider_factory.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/unsloth_provider.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/finetuning_router.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/hub_management_router.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/models_router.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/playground_router.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/schemas/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/hardware_service.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/model_service.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/training_service.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/dpo_strategy.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/rlhf_strategy.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/strategy_factory.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/low_qa_train_set.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/low_summarization_train_set.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/low_text_generation.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/med_construction.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/med_text_generation.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/question_answer-train-data-sample.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/test_dataset.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/test_financial_advisory_generation.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/test_datasets/test_financial_advisory_generation_bak.jsonl +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/chat_playground.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/configs/hardware_thresholds.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/configs/tasks.json +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/device_utils.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/finetuning/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/finetuning/quantization.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/finetuning/settings_builder.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/hardware_detection/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/hardware_detection/config_manager.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/hardware_detection/hardware_detector.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/hardware_detection/hardware_detector_factory.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/hardware_detection/model_recommendation.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/hardware_detection/model_validator.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/settings_managers/DBManager.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/settings_managers/FileManager.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/settings_managers/__init__.py +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/README.md +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/SOURCES.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/dependency_links.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/entry_points.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/requires.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/top_level.txt +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/setup.cfg +0 -0
- {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/tests/test_device_utils.py +0 -0
|
@@ -6,10 +6,23 @@ that are ready for training. Each function must return a list of strings
|
|
|
6
6
|
as required by Unsloth.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import Callable, List
|
|
9
|
+
from typing import Any, Callable, List, Optional
|
|
10
10
|
|
|
11
|
+
try:
|
|
12
|
+
from jinja2.exceptions import TemplateError
|
|
13
|
+
except ImportError:
|
|
14
|
+
TemplateError = Exception
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_text_generation_formatter(
|
|
22
|
+
eos_token: str,
|
|
23
|
+
tokenizer: Optional[Any] = None,
|
|
24
|
+
use_chat_template: bool = False,
|
|
25
|
+
) -> Callable:
|
|
13
26
|
"""
|
|
14
27
|
Create formatting function for text-generation tasks.
|
|
15
28
|
|
|
@@ -19,6 +32,8 @@ def create_text_generation_formatter(eos_token: str) -> Callable:
|
|
|
19
32
|
|
|
20
33
|
Args:
|
|
21
34
|
eos_token: The EOS token string from the tokenizer
|
|
35
|
+
tokenizer: Optional tokenizer instance (required when use_chat_template=True)
|
|
36
|
+
use_chat_template: If True, use the tokenizer's native chat template
|
|
22
37
|
|
|
23
38
|
Returns:
|
|
24
39
|
Formatting function that returns a list of formatted strings
|
|
@@ -27,9 +42,24 @@ def create_text_generation_formatter(eos_token: str) -> Callable:
|
|
|
27
42
|
prompt = example.get("prompt", "")
|
|
28
43
|
completion = example.get("completion", "")
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
if use_chat_template and tokenizer is not None:
|
|
46
|
+
try:
|
|
47
|
+
messages = [
|
|
48
|
+
{"role": "user", "content": prompt},
|
|
49
|
+
{"role": "assistant", "content": completion},
|
|
50
|
+
]
|
|
51
|
+
text = tokenizer.apply_chat_template(
|
|
52
|
+
messages, tokenize=False, add_generation_prompt=False
|
|
53
|
+
)
|
|
54
|
+
return [text]
|
|
55
|
+
except (AttributeError, TemplateError, Exception) as e:
|
|
56
|
+
logger.warning(
|
|
57
|
+
f"Chat template unavailable ({e}), "
|
|
58
|
+
f"falling back to USER/ASSISTANT format"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Default: USER/ASSISTANT conversation with EOS token
|
|
31
62
|
text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
|
|
32
|
-
|
|
33
63
|
return [text] # Must return list as required by Unsloth
|
|
34
64
|
|
|
35
65
|
return formatting_func
|
|
@@ -95,13 +125,20 @@ def create_qa_formatter(eos_token: str) -> Callable:
|
|
|
95
125
|
return formatting_func
|
|
96
126
|
|
|
97
127
|
|
|
98
|
-
def get_formatting_func(
|
|
128
|
+
def get_formatting_func(
|
|
129
|
+
task: str,
|
|
130
|
+
eos_token: str,
|
|
131
|
+
tokenizer: Optional[Any] = None,
|
|
132
|
+
use_chat_template: bool = False,
|
|
133
|
+
) -> Callable:
|
|
99
134
|
"""
|
|
100
135
|
Get the appropriate formatting function for a given task type.
|
|
101
136
|
|
|
102
137
|
Args:
|
|
103
138
|
task: Task type (e.g., "text-generation", "summarization", "extractive-question-answering")
|
|
104
139
|
eos_token: The EOS token string from the tokenizer
|
|
140
|
+
tokenizer: Optional tokenizer instance (used when use_chat_template=True)
|
|
141
|
+
use_chat_template: If True, use the tokenizer's native chat template for text-generation
|
|
105
142
|
|
|
106
143
|
Returns:
|
|
107
144
|
Formatting function for the specified task
|
|
@@ -109,8 +146,12 @@ def get_formatting_func(task: str, eos_token: str) -> Callable:
|
|
|
109
146
|
Raises:
|
|
110
147
|
ValueError: If the task type is not supported
|
|
111
148
|
"""
|
|
149
|
+
if task == "text-generation":
|
|
150
|
+
return create_text_generation_formatter(
|
|
151
|
+
eos_token, tokenizer=tokenizer, use_chat_template=use_chat_template
|
|
152
|
+
)
|
|
153
|
+
|
|
112
154
|
formatters = {
|
|
113
|
-
"text-generation": create_text_generation_formatter,
|
|
114
155
|
"summarization": create_summarization_formatter,
|
|
115
156
|
"extractive-question-answering": create_qa_formatter,
|
|
116
157
|
}
|
|
@@ -119,7 +160,7 @@ def get_formatting_func(task: str, eos_token: str) -> Callable:
|
|
|
119
160
|
if formatter_creator is None:
|
|
120
161
|
raise ValueError(
|
|
121
162
|
f"No formatter available for task: {task}. "
|
|
122
|
-
f"Supported tasks: {
|
|
163
|
+
f"Supported tasks: ['text-generation', {', '.join(repr(k) for k in formatters)}]"
|
|
123
164
|
)
|
|
124
165
|
|
|
125
166
|
return formatter_creator(eos_token)
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/prompts.py
RENAMED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Interactive prompt steps for the ModelForge notebook CLI wizard.
|
|
3
3
|
|
|
4
4
|
Each function handles one wizard step and returns a typed value.
|
|
5
|
-
|
|
5
|
+
Uses questionary in terminals; falls back to plain input() in notebooks.
|
|
6
|
+
Display panels use rich.
|
|
6
7
|
"""
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
@@ -19,6 +20,67 @@ if TYPE_CHECKING:
|
|
|
19
20
|
|
|
20
21
|
console = Console()
|
|
21
22
|
|
|
23
|
+
|
|
24
|
+
def _is_notebook() -> bool:
|
|
25
|
+
"""Return True when running inside a Jupyter/Colab/Databricks kernel."""
|
|
26
|
+
try:
|
|
27
|
+
from IPython import get_ipython
|
|
28
|
+
return get_ipython() is not None
|
|
29
|
+
except ImportError:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_IN_NOTEBOOK = _is_notebook()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Notebook-safe input() fallbacks ──────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
def _nb_select(message: str, choices: list) -> str:
|
|
39
|
+
"""Numbered-menu fallback for questionary.select()."""
|
|
40
|
+
print(f"\n{message}")
|
|
41
|
+
for i, choice in enumerate(choices, 1):
|
|
42
|
+
title = choice.title if hasattr(choice, "title") else str(choice)
|
|
43
|
+
print(f" {i}) {title}")
|
|
44
|
+
while True:
|
|
45
|
+
raw = input("Enter number: ").strip()
|
|
46
|
+
if raw.isdigit() and 1 <= int(raw) <= len(choices):
|
|
47
|
+
c = choices[int(raw) - 1]
|
|
48
|
+
return c.value if hasattr(c, "value") else c
|
|
49
|
+
print(f" Please enter a number between 1 and {len(choices)}.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _nb_text(message: str, default: str = "", validate=None) -> str:
|
|
53
|
+
"""Plain input() fallback for questionary.text()."""
|
|
54
|
+
while True:
|
|
55
|
+
raw = input(f"{message} ").strip()
|
|
56
|
+
if not raw and default:
|
|
57
|
+
raw = default
|
|
58
|
+
if validate is None:
|
|
59
|
+
return raw
|
|
60
|
+
result = validate(raw)
|
|
61
|
+
if result is True:
|
|
62
|
+
return raw
|
|
63
|
+
print(f" {result}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _nb_path(message: str, validate=None) -> str:
|
|
67
|
+
"""Plain input() fallback for questionary.path()."""
|
|
68
|
+
return _nb_text(message, validate=validate)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _nb_confirm(message: str, default: bool = True) -> bool:
|
|
72
|
+
"""y/n fallback for questionary.confirm()."""
|
|
73
|
+
hint = "Y/n" if default else "y/N"
|
|
74
|
+
while True:
|
|
75
|
+
raw = input(f"{message} [{hint}]: ").strip().lower()
|
|
76
|
+
if not raw:
|
|
77
|
+
return default
|
|
78
|
+
if raw in ("y", "yes"):
|
|
79
|
+
return True
|
|
80
|
+
if raw in ("n", "no"):
|
|
81
|
+
return False
|
|
82
|
+
print(" Please enter y or n.")
|
|
83
|
+
|
|
22
84
|
# ── Choices ────────────────────────────────────────────────────────────────
|
|
23
85
|
|
|
24
86
|
_TASK_CHOICES = [
|
|
@@ -58,6 +120,8 @@ _STRATEGY_FORMAT_HELP = {
|
|
|
58
120
|
|
|
59
121
|
def prompt_task() -> str:
|
|
60
122
|
"""Ask the user to select a fine-tuning task."""
|
|
123
|
+
if _IN_NOTEBOOK:
|
|
124
|
+
return _nb_select("Select fine-tuning task:", _TASK_CHOICES)
|
|
61
125
|
return questionary.select(
|
|
62
126
|
"Select fine-tuning task:",
|
|
63
127
|
choices=_TASK_CHOICES,
|
|
@@ -81,6 +145,15 @@ def prompt_model(recommended: str, alternatives: list[str]) -> str:
|
|
|
81
145
|
choices.append(questionary.Choice(alt, value=alt))
|
|
82
146
|
choices.append(questionary.Choice("Enter a custom model ID…", value="__custom__"))
|
|
83
147
|
|
|
148
|
+
if _IN_NOTEBOOK:
|
|
149
|
+
selection = _nb_select("Select a base model:", choices)
|
|
150
|
+
if selection == "__custom__":
|
|
151
|
+
selection = _nb_text(
|
|
152
|
+
"Enter HuggingFace model ID (e.g. meta-llama/Llama-3.2-1B):",
|
|
153
|
+
validate=lambda v: bool(v.strip()) or "Model ID cannot be empty",
|
|
154
|
+
)
|
|
155
|
+
return selection
|
|
156
|
+
|
|
84
157
|
selection = questionary.select(
|
|
85
158
|
"Select a base model:",
|
|
86
159
|
choices=choices,
|
|
@@ -111,11 +184,16 @@ def prompt_dataset(task: str, strategy: str, training_service: "TrainingService"
|
|
|
111
184
|
fmt = _STRATEGY_FORMAT_HELP.get(strategy) or _TASK_FORMAT_HELP.get(task, "JSON Lines file.")
|
|
112
185
|
console.print(f"\n[dim]Expected format:[/dim] {fmt}")
|
|
113
186
|
|
|
187
|
+
_path_validate = lambda v: os.path.isfile(v) or "File not found — please enter a valid path"
|
|
188
|
+
|
|
114
189
|
while True:
|
|
115
|
-
|
|
116
|
-
"Path to dataset file (.jsonl / .json):",
|
|
117
|
-
|
|
118
|
-
|
|
190
|
+
if _IN_NOTEBOOK:
|
|
191
|
+
path = _nb_path("Path to dataset file (.jsonl / .json):", validate=_path_validate)
|
|
192
|
+
else:
|
|
193
|
+
path = questionary.path(
|
|
194
|
+
"Path to dataset file (.jsonl / .json):",
|
|
195
|
+
validate=lambda v: os.path.isfile(v.strip()) or "File not found — please enter a valid path",,
|
|
196
|
+
).ask()
|
|
119
197
|
|
|
120
198
|
if path is not None:
|
|
121
199
|
path = path.strip()
|
|
@@ -134,13 +212,18 @@ def prompt_dataset(task: str, strategy: str, training_service: "TrainingService"
|
|
|
134
212
|
return path
|
|
135
213
|
except Exception as exc:
|
|
136
214
|
console.print(f" [red]✗[/red] Validation failed: {exc}")
|
|
137
|
-
|
|
215
|
+
if _IN_NOTEBOOK:
|
|
216
|
+
retry = _nb_confirm("Try a different file?", default=True)
|
|
217
|
+
else:
|
|
218
|
+
retry = questionary.confirm("Try a different file?", default=True).ask()
|
|
138
219
|
if not retry:
|
|
139
220
|
raise
|
|
140
221
|
|
|
141
222
|
|
|
142
223
|
def prompt_provider() -> str:
|
|
143
224
|
"""Ask the user to choose a training provider."""
|
|
225
|
+
if _IN_NOTEBOOK:
|
|
226
|
+
return _nb_select("Select training provider:", _PROVIDER_CHOICES)
|
|
144
227
|
return questionary.select(
|
|
145
228
|
"Select training provider:",
|
|
146
229
|
choices=_PROVIDER_CHOICES,
|
|
@@ -150,6 +233,8 @@ def prompt_provider() -> str:
|
|
|
150
233
|
|
|
151
234
|
def prompt_strategy() -> str:
|
|
152
235
|
"""Ask the user to choose a training strategy."""
|
|
236
|
+
if _IN_NOTEBOOK:
|
|
237
|
+
return _nb_select("Select training strategy:", _STRATEGY_CHOICES)
|
|
153
238
|
return questionary.select(
|
|
154
239
|
"Select training strategy:",
|
|
155
240
|
choices=_STRATEGY_CHOICES,
|
|
@@ -181,20 +266,28 @@ def prompt_hyperparams(defaults: dict) -> dict:
|
|
|
181
266
|
|
|
182
267
|
def _ask_int(key: str, question: str) -> int:
|
|
183
268
|
default = defaults.get(key)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
default=str(default),
|
|
187
|
-
|
|
188
|
-
|
|
269
|
+
validate = lambda v: v.isdigit() and int(v) >= 1 or "Must be a positive integer"
|
|
270
|
+
if _IN_NOTEBOOK:
|
|
271
|
+
raw = _nb_text(f"{question} [default: {default}]:", default=str(default), validate=validate)
|
|
272
|
+
else:
|
|
273
|
+
raw = questionary.text(
|
|
274
|
+
f"{question} [default: {default}]:",
|
|
275
|
+
default=str(default),
|
|
276
|
+
validate=validate,
|
|
277
|
+
).ask()
|
|
189
278
|
return int(raw)
|
|
190
279
|
|
|
191
280
|
def _ask_float(key: str, question: str, min_val: float = 0.0) -> float:
|
|
192
281
|
default = defaults.get(key)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
default=str(default),
|
|
196
|
-
|
|
197
|
-
|
|
282
|
+
validate = lambda v: _valid_float(v, min_val)
|
|
283
|
+
if _IN_NOTEBOOK:
|
|
284
|
+
raw = _nb_text(f"{question} [default: {default}]:", default=str(default), validate=validate)
|
|
285
|
+
else:
|
|
286
|
+
raw = questionary.text(
|
|
287
|
+
f"{question} [default: {default}]:",
|
|
288
|
+
default=str(default),
|
|
289
|
+
validate=validate,
|
|
290
|
+
).ask()
|
|
198
291
|
return float(raw)
|
|
199
292
|
|
|
200
293
|
def _valid_float(v: str, min_val: float) -> bool | str:
|
|
@@ -222,14 +315,30 @@ def prompt_hyperparams(defaults: dict) -> dict:
|
|
|
222
315
|
questionary.Choice("None (full precision / no bitsandbytes required)", value="none"),
|
|
223
316
|
]
|
|
224
317
|
default_quant = "4bit" if defaults.get("use_4bit") else ("8bit" if defaults.get("use_8bit") else "none")
|
|
225
|
-
|
|
226
|
-
f"Quantization [default: {default_quant}]:",
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
318
|
+
if _IN_NOTEBOOK:
|
|
319
|
+
quant = _nb_select(f"Quantization [default: {default_quant}]:", quant_choices)
|
|
320
|
+
else:
|
|
321
|
+
quant = questionary.select(
|
|
322
|
+
f"Quantization [default: {default_quant}]:",
|
|
323
|
+
choices=quant_choices,
|
|
324
|
+
default=quant_choices[["4bit", "8bit", "none"].index(default_quant)],
|
|
325
|
+
).ask()
|
|
230
326
|
params["use_4bit"] = quant == "4bit"
|
|
231
327
|
params["use_8bit"] = quant == "8bit"
|
|
232
328
|
|
|
329
|
+
if _IN_NOTEBOOK:
|
|
330
|
+
params["use_chat_template"] = _nb_confirm(
|
|
331
|
+
"Use model's native chat template for formatting? "
|
|
332
|
+
"(recommended for instruct models)",
|
|
333
|
+
default=False,
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
params["use_chat_template"] = questionary.confirm(
|
|
337
|
+
"Use model's native chat template for formatting? "
|
|
338
|
+
"(recommended for instruct models)",
|
|
339
|
+
default=False,
|
|
340
|
+
).ask()
|
|
341
|
+
|
|
233
342
|
return params
|
|
234
343
|
|
|
235
344
|
|
|
@@ -263,6 +372,7 @@ def prompt_confirm_config(config: dict) -> bool:
|
|
|
263
372
|
("max_seq_length", "Max seq length"),
|
|
264
373
|
("use_4bit", "4-bit quantization"),
|
|
265
374
|
("use_8bit", "8-bit quantization"),
|
|
375
|
+
("use_chat_template", "Use chat template"),
|
|
266
376
|
]
|
|
267
377
|
|
|
268
378
|
for key, label in display_keys:
|
|
@@ -271,6 +381,8 @@ def prompt_confirm_config(config: dict) -> bool:
|
|
|
271
381
|
|
|
272
382
|
console.print(table)
|
|
273
383
|
|
|
384
|
+
if _IN_NOTEBOOK:
|
|
385
|
+
return _nb_confirm("Start training with this configuration?", default=True)
|
|
274
386
|
return questionary.confirm(
|
|
275
387
|
"Start training with this configuration?", default=True
|
|
276
388
|
).ask()
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/wizard.py
RENAMED
|
@@ -225,6 +225,7 @@ class ModelForgeWizard:
|
|
|
225
225
|
"max_seq_length": hyperparams.get("max_seq_length", 2048),
|
|
226
226
|
"use_4bit": hyperparams.get("use_4bit", True),
|
|
227
227
|
"use_8bit": hyperparams.get("use_8bit", False),
|
|
228
|
+
"use_chat_template": hyperparams.get("use_chat_template", False),
|
|
228
229
|
# TrainingConfig defaults for the rest
|
|
229
230
|
"lora_dropout": TrainingConfig.model_fields["lora_dropout"].default,
|
|
230
231
|
"bnb_4bit_compute_dtype": TrainingConfig.model_fields["bnb_4bit_compute_dtype"].default,
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/qlora_strategy.py
RENAMED
|
@@ -4,6 +4,12 @@ QLoRA combines 4-bit quantization with LoRA for memory-efficient fine-tuning.
|
|
|
4
4
|
Uses TRL's SFTTrainer with QLoRA-optimized defaults.
|
|
5
5
|
"""
|
|
6
6
|
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from jinja2.exceptions import TemplateError
|
|
10
|
+
except ImportError:
|
|
11
|
+
TemplateError = Exception
|
|
12
|
+
|
|
7
13
|
from peft import LoraConfig, TaskType, prepare_model_for_kbit_training
|
|
8
14
|
|
|
9
15
|
# Import unsloth first to prevent EOS token corruption
|
|
@@ -114,12 +120,31 @@ class QLoRAStrategy:
|
|
|
114
120
|
task = config.get("task", "text-generation")
|
|
115
121
|
eos_token = tokenizer.eos_token or tokenizer.sep_token or ""
|
|
116
122
|
|
|
123
|
+
use_chat_template = config.get("use_chat_template", False)
|
|
124
|
+
|
|
117
125
|
def format_to_text(example):
|
|
118
126
|
"""Format each example into a single text field for SFTTrainer."""
|
|
119
127
|
if task == "text-generation":
|
|
120
128
|
prompt = example.get('prompt', '')
|
|
121
129
|
completion = example.get('completion', '')
|
|
122
|
-
|
|
130
|
+
|
|
131
|
+
if use_chat_template:
|
|
132
|
+
try:
|
|
133
|
+
messages = [
|
|
134
|
+
{"role": "user", "content": prompt},
|
|
135
|
+
{"role": "assistant", "content": completion},
|
|
136
|
+
]
|
|
137
|
+
text = tokenizer.apply_chat_template(
|
|
138
|
+
messages, tokenize=False, add_generation_prompt=False
|
|
139
|
+
)
|
|
140
|
+
except (AttributeError, TemplateError, Exception) as e:
|
|
141
|
+
logger.warning(
|
|
142
|
+
f"Chat template unavailable ({e}), "
|
|
143
|
+
f"falling back to USER/ASSISTANT format"
|
|
144
|
+
)
|
|
145
|
+
text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
|
|
146
|
+
else:
|
|
147
|
+
text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
|
|
123
148
|
|
|
124
149
|
elif task == "summarization":
|
|
125
150
|
input_text = example.get('input', '')
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/sft_strategy.py
RENAMED
|
@@ -3,6 +3,12 @@ Supervised Fine-Tuning (SFT) strategy implementation.
|
|
|
3
3
|
Uses TRL's SFTTrainer for standard supervised fine-tuning with LoRA.
|
|
4
4
|
"""
|
|
5
5
|
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from jinja2.exceptions import TemplateError
|
|
9
|
+
except ImportError:
|
|
10
|
+
TemplateError = Exception
|
|
11
|
+
|
|
6
12
|
from peft import LoraConfig, TaskType, prepare_model_for_kbit_training
|
|
7
13
|
|
|
8
14
|
# Import unsloth first to prevent EOS token corruption
|
|
@@ -93,12 +99,31 @@ class SFTStrategy:
|
|
|
93
99
|
task = config.get("task", "text-generation")
|
|
94
100
|
eos_token = tokenizer.eos_token or tokenizer.sep_token or ""
|
|
95
101
|
|
|
102
|
+
use_chat_template = config.get("use_chat_template", False)
|
|
103
|
+
|
|
96
104
|
def format_to_text(example):
|
|
97
105
|
"""Format each example into a single text field for SFTTrainer."""
|
|
98
106
|
if task == "text-generation":
|
|
99
107
|
prompt = example.get('prompt', '')
|
|
100
108
|
completion = example.get('completion', '')
|
|
101
|
-
|
|
109
|
+
|
|
110
|
+
if use_chat_template:
|
|
111
|
+
try:
|
|
112
|
+
messages = [
|
|
113
|
+
{"role": "user", "content": prompt},
|
|
114
|
+
{"role": "assistant", "content": completion},
|
|
115
|
+
]
|
|
116
|
+
text = tokenizer.apply_chat_template(
|
|
117
|
+
messages, tokenize=False, add_generation_prompt=False
|
|
118
|
+
)
|
|
119
|
+
except (AttributeError, TemplateError, Exception) as e:
|
|
120
|
+
logger.warning(
|
|
121
|
+
f"Chat template unavailable ({e}), "
|
|
122
|
+
f"falling back to USER/ASSISTANT format"
|
|
123
|
+
)
|
|
124
|
+
text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
|
|
125
|
+
else:
|
|
126
|
+
text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
|
|
102
127
|
|
|
103
128
|
elif task == "summarization":
|
|
104
129
|
input_text = example.get('input', '')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelforge-finetuning
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: ModelForge: A no-code toolkit for fine-tuning HuggingFace models
|
|
5
5
|
Author-email: R3tr0 M1ll3r <r3tr0.m1ll3r@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://modelforge-finetuning.readthedocs.io
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/modelforge_finetuning.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelforge-finetuning
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: ModelForge: A no-code toolkit for fine-tuning HuggingFace models
|
|
5
5
|
Author-email: R3tr0 M1ll3r <r3tr0.m1ll3r@gmail.com>
|
|
6
6
|
Project-URL: Documentation, https://modelforge-finetuning.readthedocs.io
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/favicon.ico
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/favicon.jpg
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/index.html
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/logo192.png
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/logo512.png
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/manifest.json
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/Frontend/build/robots.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/database/database_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/evaluation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/evaluation/metrics.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/formatters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/globals/globals_instance.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/model_configs/high_end.json
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/model_configs/low_end.json
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/model_configs/mid_range.json
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/__init__.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/notebook_cli/progress.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/provider_factory.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/providers/unsloth_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/finetuning_router.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/models_router.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/routers/playground_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/hardware_service.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/model_service.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/services/training_service.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/__init__.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/dpo_strategy.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/strategies/rlhf_strategy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/__init__.py
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/chat_playground.py
RENAMED
|
File without changes
|
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/configs/tasks.json
RENAMED
|
File without changes
|
{modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.2}/ModelForge/utilities/device_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|