modelforge-finetuning 3.0.1__tar.gz → 3.0.3__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.
Files changed (109) hide show
  1. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/formatters/formatting_functions.py +48 -7
  2. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/notebook_cli/prompts.py +133 -21
  3. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/notebook_cli/wizard.py +1 -0
  4. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/schemas/training_schemas.py +3 -0
  5. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/strategies/qlora_strategy.py +26 -1
  6. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/strategies/sft_strategy.py +26 -1
  7. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/PKG-INFO +1 -1
  8. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/modelforge_finetuning.egg-info/PKG-INFO +1 -1
  9. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/pyproject.toml +1 -1
  10. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/LICENSE +0 -0
  11. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/MANIFEST.in +0 -0
  12. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/asset-manifest.json +0 -0
  13. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/favicon.ico +0 -0
  14. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/favicon.jpg +0 -0
  15. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/index.html +0 -0
  16. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/logo192.png +0 -0
  17. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/logo512.png +0 -0
  18. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/manifest.json +0 -0
  19. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/robots.txt +0 -0
  20. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/css/main.5a3d6ea1.css +0 -0
  21. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/css/main.5a3d6ea1.css.map +0 -0
  22. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/css/main.9680427d.css +0 -0
  23. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/css/main.9680427d.css.map +0 -0
  24. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/453.6e4f956a.chunk.js +0 -0
  25. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/453.6e4f956a.chunk.js.map +0 -0
  26. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/453.ad6eb26e.chunk.js +0 -0
  27. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/453.ad6eb26e.chunk.js.map +0 -0
  28. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.2b0344e0.js +0 -0
  29. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.2b0344e0.js.LICENSE.txt +0 -0
  30. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.2b0344e0.js.map +0 -0
  31. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.9cc9257a.js +0 -0
  32. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.9cc9257a.js.LICENSE.txt +0 -0
  33. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.9cc9257a.js.map +0 -0
  34. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.9df15fbf.js +0 -0
  35. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.9df15fbf.js.LICENSE.txt +0 -0
  36. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/Frontend/build/static/js/main.9df15fbf.js.map +0 -0
  37. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/__init__.py +0 -0
  38. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/app.py +0 -0
  39. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/cli.py +0 -0
  40. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/database/__init__.py +0 -0
  41. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/database/database_manager.py +0 -0
  42. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/database/models.py +0 -0
  43. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/dependencies.py +0 -0
  44. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/evaluation/__init__.py +0 -0
  45. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/evaluation/dataset_validator.py +0 -0
  46. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/evaluation/metrics.py +0 -0
  47. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/exceptions.py +0 -0
  48. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/formatters/__init__.py +0 -0
  49. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/globals/__init__.py +0 -0
  50. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/globals/globals.py +0 -0
  51. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/globals/globals_instance.py +0 -0
  52. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/logging_config.py +0 -0
  53. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/model_configs/high_end.json +0 -0
  54. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/model_configs/low_end.json +0 -0
  55. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/model_configs/mid_range.json +0 -0
  56. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/notebook_cli/__init__.py +0 -0
  57. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/notebook_cli/progress.py +0 -0
  58. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/providers/__init__.py +0 -0
  59. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/providers/huggingface_provider.py +0 -0
  60. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/providers/provider_factory.py +0 -0
  61. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/providers/unsloth_provider.py +0 -0
  62. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/routers/__init__.py +0 -0
  63. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/routers/finetuning_router.py +0 -0
  64. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/routers/hub_management_router.py +0 -0
  65. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/routers/models_router.py +0 -0
  66. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/routers/playground_router.py +0 -0
  67. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/schemas/__init__.py +0 -0
  68. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/services/__init__.py +0 -0
  69. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/services/hardware_service.py +0 -0
  70. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/services/model_service.py +0 -0
  71. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/services/training_service.py +0 -0
  72. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/strategies/__init__.py +0 -0
  73. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/strategies/dpo_strategy.py +0 -0
  74. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/strategies/rlhf_strategy.py +0 -0
  75. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/strategies/strategy_factory.py +0 -0
  76. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/low_qa_train_set.jsonl +0 -0
  77. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/low_summarization_train_set.jsonl +0 -0
  78. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/low_text_generation.jsonl +0 -0
  79. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/med_construction.jsonl +0 -0
  80. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/med_text_generation.jsonl +0 -0
  81. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/question_answer-train-data-sample.jsonl +0 -0
  82. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/test_dataset.json +0 -0
  83. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/test_financial_advisory_generation.jsonl +0 -0
  84. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/test_datasets/test_financial_advisory_generation_bak.jsonl +0 -0
  85. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/__init__.py +0 -0
  86. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/chat_playground.py +0 -0
  87. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/configs/hardware_thresholds.json +0 -0
  88. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/configs/tasks.json +0 -0
  89. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/device_utils.py +0 -0
  90. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/finetuning/__init__.py +0 -0
  91. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/finetuning/quantization.py +0 -0
  92. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/finetuning/settings_builder.py +0 -0
  93. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/hardware_detection/__init__.py +0 -0
  94. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/hardware_detection/config_manager.py +0 -0
  95. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/hardware_detection/hardware_detector.py +0 -0
  96. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/hardware_detection/hardware_detector_factory.py +0 -0
  97. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/hardware_detection/model_recommendation.py +0 -0
  98. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/hardware_detection/model_validator.py +0 -0
  99. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/settings_managers/DBManager.py +0 -0
  100. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/settings_managers/FileManager.py +0 -0
  101. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/ModelForge/utilities/settings_managers/__init__.py +0 -0
  102. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/README.md +0 -0
  103. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/modelforge_finetuning.egg-info/SOURCES.txt +0 -0
  104. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/modelforge_finetuning.egg-info/dependency_links.txt +0 -0
  105. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/modelforge_finetuning.egg-info/entry_points.txt +0 -0
  106. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/modelforge_finetuning.egg-info/requires.txt +0 -0
  107. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/modelforge_finetuning.egg-info/top_level.txt +0 -0
  108. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/setup.cfg +0 -0
  109. {modelforge_finetuning-3.0.1 → modelforge_finetuning-3.0.3}/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
- def create_text_generation_formatter(eos_token: str) -> Callable:
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
- # Format as USER/ASSISTANT conversation with EOS token
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(task: str, eos_token: str) -> Callable:
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: {list(formatters.keys())}"
163
+ f"Supported tasks: ['text-generation', {', '.join(repr(k) for k in formatters)}]"
123
164
  )
124
165
 
125
166
  return formatter_creator(eos_token)
@@ -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
- All prompts use questionary; display panels use rich.
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
- path = questionary.path(
116
- "Path to dataset file (.jsonl / .json):",
117
- validate=lambda v: os.path.isfile(v.strip()) or "File not found — please enter a valid path",
118
- ).ask()
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
- retry = questionary.confirm("Try a different file?", default=True).ask()
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
- raw = questionary.text(
185
- f"{question} [default: {default}]:",
186
- default=str(default),
187
- validate=lambda v: v.isdigit() and int(v) >= 1 or f"Must be a positive integer",
188
- ).ask()
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
- raw = questionary.text(
194
- f"{question} [default: {default}]:",
195
- default=str(default),
196
- validate=lambda v: _valid_float(v, min_val),
197
- ).ask()
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
- quant = questionary.select(
226
- f"Quantization [default: {default_quant}]:",
227
- choices=quant_choices,
228
- default=quant_choices[["4bit", "8bit", "none"].index(default_quant)],
229
- ).ask()
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()
@@ -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,
@@ -59,6 +59,9 @@ class TrainingConfig(BaseModel):
59
59
  # Sequence settings
60
60
  max_seq_length: Optional[int] = None
61
61
 
62
+ # Chat template settings
63
+ use_chat_template: bool = False
64
+
62
65
  # Evaluation settings
63
66
  eval_split: float = 0.2
64
67
  eval_steps: int = 100
@@ -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
- text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
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', '')
@@ -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
- text = f"USER: {prompt}\nASSISTANT: {completion}{eos_token}"
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.1
3
+ Version: 3.0.3
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelforge-finetuning
3
- Version: 3.0.1
3
+ Version: 3.0.3
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "modelforge-finetuning"
3
- version = "3.0.1"
3
+ version = "3.0.3"
4
4
  description = "ModelForge: A no-code toolkit for fine-tuning HuggingFace models"
5
5
  authors = [
6
6
  {name = "R3tr0 M1ll3r", email = "r3tr0.m1ll3r@gmail.com"},