kiln-ai 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
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.
Potentially problematic release.
This version of kiln-ai might be problematic. Click here for more details.
- kiln_ai/adapters/__init__.py +4 -0
- kiln_ai/adapters/adapter_registry.py +153 -28
- kiln_ai/adapters/eval/__init__.py +28 -0
- kiln_ai/adapters/eval/eval_runner.py +4 -1
- kiln_ai/adapters/eval/g_eval.py +2 -1
- kiln_ai/adapters/eval/test_base_eval.py +1 -0
- kiln_ai/adapters/eval/test_eval_runner.py +1 -0
- kiln_ai/adapters/eval/test_g_eval.py +1 -0
- kiln_ai/adapters/fine_tune/base_finetune.py +16 -2
- kiln_ai/adapters/fine_tune/finetune_registry.py +2 -0
- kiln_ai/adapters/fine_tune/test_together_finetune.py +531 -0
- kiln_ai/adapters/fine_tune/together_finetune.py +325 -0
- kiln_ai/adapters/ml_model_list.py +638 -155
- kiln_ai/adapters/model_adapters/__init__.py +2 -4
- kiln_ai/adapters/model_adapters/base_adapter.py +14 -11
- kiln_ai/adapters/model_adapters/litellm_adapter.py +391 -0
- kiln_ai/adapters/model_adapters/litellm_config.py +13 -0
- kiln_ai/adapters/model_adapters/test_litellm_adapter.py +407 -0
- kiln_ai/adapters/model_adapters/test_structured_output.py +23 -5
- kiln_ai/adapters/ollama_tools.py +3 -2
- kiln_ai/adapters/parsers/r1_parser.py +19 -14
- kiln_ai/adapters/parsers/test_r1_parser.py +17 -5
- kiln_ai/adapters/provider_tools.py +50 -58
- kiln_ai/adapters/repair/test_repair_task.py +3 -3
- kiln_ai/adapters/run_output.py +1 -1
- kiln_ai/adapters/test_adapter_registry.py +17 -20
- kiln_ai/adapters/test_generate_docs.py +2 -2
- kiln_ai/adapters/test_prompt_adaptors.py +30 -19
- kiln_ai/adapters/test_provider_tools.py +26 -81
- kiln_ai/datamodel/basemodel.py +2 -0
- kiln_ai/datamodel/datamodel_enums.py +2 -0
- kiln_ai/datamodel/json_schema.py +1 -1
- kiln_ai/datamodel/task_output.py +13 -6
- kiln_ai/datamodel/test_basemodel.py +9 -0
- kiln_ai/datamodel/test_datasource.py +19 -0
- kiln_ai/utils/config.py +37 -0
- kiln_ai/utils/dataset_import.py +232 -0
- kiln_ai/utils/test_dataset_import.py +596 -0
- {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.0.dist-info}/METADATA +51 -7
- {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.0.dist-info}/RECORD +42 -39
- kiln_ai/adapters/model_adapters/langchain_adapters.py +0 -309
- kiln_ai/adapters/model_adapters/openai_compatible_config.py +0 -10
- kiln_ai/adapters/model_adapters/openai_model_adapter.py +0 -289
- kiln_ai/adapters/model_adapters/test_langchain_adapter.py +0 -343
- kiln_ai/adapters/model_adapters/test_openai_model_adapter.py +0 -216
- {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.0.dist-info}/WHEEL +0 -0
- {kiln_ai-0.12.0.dist-info → kiln_ai-0.13.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
from typing import Literal, Tuple
|
|
2
|
+
|
|
3
|
+
from together import Together
|
|
4
|
+
from together.types.files import FilePurpose
|
|
5
|
+
from together.types.finetune import FinetuneJobStatus as TogetherFinetuneJobStatus
|
|
6
|
+
|
|
7
|
+
from kiln_ai.adapters.fine_tune.base_finetune import (
|
|
8
|
+
BaseFinetuneAdapter,
|
|
9
|
+
FineTuneParameter,
|
|
10
|
+
FineTuneStatus,
|
|
11
|
+
FineTuneStatusType,
|
|
12
|
+
)
|
|
13
|
+
from kiln_ai.adapters.fine_tune.dataset_formatter import DatasetFormat, DatasetFormatter
|
|
14
|
+
from kiln_ai.datamodel import DatasetSplit, StructuredOutputMode, Task
|
|
15
|
+
from kiln_ai.datamodel import Finetune as FinetuneModel
|
|
16
|
+
from kiln_ai.utils.config import Config
|
|
17
|
+
|
|
18
|
+
_pending_statuses = [
|
|
19
|
+
TogetherFinetuneJobStatus.STATUS_PENDING,
|
|
20
|
+
TogetherFinetuneJobStatus.STATUS_QUEUED,
|
|
21
|
+
]
|
|
22
|
+
_running_statuses = [
|
|
23
|
+
TogetherFinetuneJobStatus.STATUS_RUNNING,
|
|
24
|
+
TogetherFinetuneJobStatus.STATUS_COMPRESSING,
|
|
25
|
+
TogetherFinetuneJobStatus.STATUS_UPLOADING,
|
|
26
|
+
]
|
|
27
|
+
_completed_statuses = [TogetherFinetuneJobStatus.STATUS_COMPLETED]
|
|
28
|
+
_failed_statuses = [
|
|
29
|
+
TogetherFinetuneJobStatus.STATUS_CANCELLED,
|
|
30
|
+
TogetherFinetuneJobStatus.STATUS_CANCEL_REQUESTED,
|
|
31
|
+
TogetherFinetuneJobStatus.STATUS_ERROR,
|
|
32
|
+
TogetherFinetuneJobStatus.STATUS_USER_ERROR,
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TogetherFinetune(BaseFinetuneAdapter):
|
|
37
|
+
"""
|
|
38
|
+
A fine-tuning adapter for Together.ai.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, datamodel: FinetuneModel):
|
|
42
|
+
super().__init__(datamodel)
|
|
43
|
+
api_key = Config.shared().together_api_key
|
|
44
|
+
if not api_key:
|
|
45
|
+
raise ValueError("Together.ai API key not set")
|
|
46
|
+
self.client = Together(api_key=api_key)
|
|
47
|
+
|
|
48
|
+
async def status(self) -> FineTuneStatus:
|
|
49
|
+
status, _ = await self._status()
|
|
50
|
+
# update the datamodel if the status has changed
|
|
51
|
+
if self.datamodel.latest_status != status.status:
|
|
52
|
+
self.datamodel.latest_status = status.status
|
|
53
|
+
if self.datamodel.path:
|
|
54
|
+
self.datamodel.save_to_file()
|
|
55
|
+
return status
|
|
56
|
+
|
|
57
|
+
async def _status(self) -> Tuple[FineTuneStatus, str | None]:
|
|
58
|
+
try:
|
|
59
|
+
fine_tuning_job_id = self.datamodel.provider_id
|
|
60
|
+
if not fine_tuning_job_id:
|
|
61
|
+
return FineTuneStatus(
|
|
62
|
+
status=FineTuneStatusType.unknown,
|
|
63
|
+
message="Fine-tuning job ID not set. Can not retrieve status.",
|
|
64
|
+
), None
|
|
65
|
+
|
|
66
|
+
# retrieve the fine-tuning job
|
|
67
|
+
together_finetune = self.client.fine_tuning.retrieve(id=fine_tuning_job_id)
|
|
68
|
+
|
|
69
|
+
status = together_finetune.status
|
|
70
|
+
if status in _pending_statuses:
|
|
71
|
+
return FineTuneStatus(
|
|
72
|
+
status=FineTuneStatusType.pending,
|
|
73
|
+
message=f"Fine-tuning job is pending [{status}]",
|
|
74
|
+
), fine_tuning_job_id
|
|
75
|
+
elif status in _running_statuses:
|
|
76
|
+
return FineTuneStatus(
|
|
77
|
+
status=FineTuneStatusType.running,
|
|
78
|
+
message=f"Fine-tuning job is running [{status}]",
|
|
79
|
+
), fine_tuning_job_id
|
|
80
|
+
elif status in _completed_statuses:
|
|
81
|
+
return FineTuneStatus(
|
|
82
|
+
status=FineTuneStatusType.completed,
|
|
83
|
+
message="Fine-tuning job completed",
|
|
84
|
+
), fine_tuning_job_id
|
|
85
|
+
elif status in _failed_statuses:
|
|
86
|
+
return FineTuneStatus(
|
|
87
|
+
status=FineTuneStatusType.failed,
|
|
88
|
+
message=f"Fine-tuning job failed [{status}]",
|
|
89
|
+
), fine_tuning_job_id
|
|
90
|
+
else:
|
|
91
|
+
return FineTuneStatus(
|
|
92
|
+
status=FineTuneStatusType.unknown,
|
|
93
|
+
message=f"Unknown fine-tuning job status [{status}]",
|
|
94
|
+
), fine_tuning_job_id
|
|
95
|
+
except Exception as e:
|
|
96
|
+
return FineTuneStatus(
|
|
97
|
+
status=FineTuneStatusType.unknown,
|
|
98
|
+
message=f"Error retrieving fine-tuning job status: {e}",
|
|
99
|
+
), None
|
|
100
|
+
|
|
101
|
+
async def _start(self, dataset: DatasetSplit) -> None:
|
|
102
|
+
task = self.datamodel.parent_task()
|
|
103
|
+
if not task:
|
|
104
|
+
raise ValueError("Task is required to start a fine-tune")
|
|
105
|
+
|
|
106
|
+
format = DatasetFormat.OPENAI_CHAT_JSONL
|
|
107
|
+
if task.output_json_schema:
|
|
108
|
+
# This formatter will check it's valid JSON, and normalize the output (chat format just uses exact string).
|
|
109
|
+
format = DatasetFormat.OPENAI_CHAT_JSON_SCHEMA_JSONL
|
|
110
|
+
# Together doesn't support JSON-mode for fine-tunes, so we use JSON instructions in the system message. However not our standard json_instructions mode.
|
|
111
|
+
# Instead we augment the system message with custom JSON instructions for a fine-tune (see augment_system_message). A nice simple instructions.
|
|
112
|
+
# Why: Fine-tunes tend to need less coaching to get JSON format correct, as they have seen examples. And they are often on smaller models that have trouble following longer/complex JSON-schema prompts so our default is a poor choice.
|
|
113
|
+
# We save json_custom_instructions mode so it knows what to do at call time.
|
|
114
|
+
self.datamodel.structured_output_mode = (
|
|
115
|
+
StructuredOutputMode.json_custom_instructions
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
train_file_id = await self.generate_and_upload_jsonl(
|
|
119
|
+
dataset, self.datamodel.train_split_name, task, format
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Their library defaults to "" and not None, so we follow that convention.
|
|
123
|
+
validation_file_id = ""
|
|
124
|
+
if self.datamodel.validation_split_name:
|
|
125
|
+
validation_file_id = await self.generate_and_upload_jsonl(
|
|
126
|
+
dataset, self.datamodel.validation_split_name, task, format
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
together_finetune = self.client.fine_tuning.create(
|
|
130
|
+
training_file=train_file_id,
|
|
131
|
+
validation_file=validation_file_id,
|
|
132
|
+
model=self.datamodel.base_model_id,
|
|
133
|
+
**self._build_finetune_parameters(),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# 2 different IDs, output_name is the name of the model that results from the fine-tune job, the finetune_job_id is the ID of the fine-tune job
|
|
137
|
+
self.datamodel.provider_id = together_finetune.id
|
|
138
|
+
self.datamodel.fine_tune_model_id = together_finetune.output_name
|
|
139
|
+
|
|
140
|
+
if self.datamodel.path:
|
|
141
|
+
self.datamodel.save_to_file()
|
|
142
|
+
|
|
143
|
+
def _build_finetune_parameters(self) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
Build the parameters dictionary for fine-tuning with Together.ai.
|
|
146
|
+
Only includes parameters that exist in the datamodel and have valid types.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
train_file_id: The ID of the uploaded training file
|
|
150
|
+
display_name: The display name for the fine-tune job
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary of parameters to pass to Together's fine-tuning API
|
|
154
|
+
"""
|
|
155
|
+
parameters = self.datamodel.parameters
|
|
156
|
+
|
|
157
|
+
# Start with required parameters
|
|
158
|
+
properties = {
|
|
159
|
+
# Force LoRA for now. We only support serverless Loras at the moment. We can remove this later when we add support for full-finetunes.
|
|
160
|
+
"lora": True,
|
|
161
|
+
# Suffix must be truncated to 40 characters
|
|
162
|
+
"suffix": f"kiln_ai_{self.datamodel.id}",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Add optional parameters only if they exist and have correct types
|
|
166
|
+
if "epochs" in parameters and isinstance(parameters["epochs"], int):
|
|
167
|
+
properties["n_epochs"] = parameters["epochs"]
|
|
168
|
+
|
|
169
|
+
if "learning_rate" in parameters and isinstance(
|
|
170
|
+
parameters["learning_rate"], float
|
|
171
|
+
):
|
|
172
|
+
properties["learning_rate"] = parameters["learning_rate"]
|
|
173
|
+
|
|
174
|
+
if "batch_size" in parameters and isinstance(parameters["batch_size"], int):
|
|
175
|
+
properties["batch_size"] = parameters["batch_size"]
|
|
176
|
+
|
|
177
|
+
if "num_checkpoints" in parameters and isinstance(
|
|
178
|
+
parameters["num_checkpoints"], int
|
|
179
|
+
):
|
|
180
|
+
properties["n_checkpoints"] = parameters["num_checkpoints"]
|
|
181
|
+
|
|
182
|
+
if "min_lr_ratio" in parameters and isinstance(
|
|
183
|
+
parameters["min_lr_ratio"], float
|
|
184
|
+
):
|
|
185
|
+
properties["min_lr_ratio"] = parameters["min_lr_ratio"]
|
|
186
|
+
|
|
187
|
+
if "warmup_ratio" in parameters and isinstance(
|
|
188
|
+
parameters["warmup_ratio"], float
|
|
189
|
+
):
|
|
190
|
+
properties["warmup_ratio"] = parameters["warmup_ratio"]
|
|
191
|
+
|
|
192
|
+
if "max_grad_norm" in parameters and isinstance(
|
|
193
|
+
parameters["max_grad_norm"], float
|
|
194
|
+
):
|
|
195
|
+
properties["max_grad_norm"] = parameters["max_grad_norm"]
|
|
196
|
+
|
|
197
|
+
if "weight_decay" in parameters and isinstance(
|
|
198
|
+
parameters["weight_decay"], float
|
|
199
|
+
):
|
|
200
|
+
properties["weight_decay"] = parameters["weight_decay"]
|
|
201
|
+
|
|
202
|
+
if "lora_rank" in parameters and isinstance(parameters["lora_rank"], int):
|
|
203
|
+
properties["lora_r"] = parameters["lora_rank"]
|
|
204
|
+
|
|
205
|
+
if "lora_dropout" in parameters and isinstance(
|
|
206
|
+
parameters["lora_dropout"], float
|
|
207
|
+
):
|
|
208
|
+
properties["lora_dropout"] = parameters["lora_dropout"]
|
|
209
|
+
|
|
210
|
+
if "lora_alpha" in parameters and isinstance(parameters["lora_alpha"], float):
|
|
211
|
+
properties["lora_alpha"] = parameters["lora_alpha"]
|
|
212
|
+
|
|
213
|
+
return properties
|
|
214
|
+
|
|
215
|
+
@classmethod
|
|
216
|
+
def augment_system_message(cls, system_message: str, task: Task) -> str:
|
|
217
|
+
"""
|
|
218
|
+
Augment the system message with custom JSON instructions for a fine-tune.
|
|
219
|
+
|
|
220
|
+
This is a shorter version of the JSON instructions, as fine-tunes tend to need less coaching to get JSON format correct. Plus smaller models are often finetuned, and don't do well following our detailed JSON-schema instructions from json_instructions.
|
|
221
|
+
|
|
222
|
+
Together doesn't support JSON-mode for fine-tunes, so this is needed where it isn't needed with other providers.
|
|
223
|
+
"""
|
|
224
|
+
if task.output_json_schema:
|
|
225
|
+
return (
|
|
226
|
+
system_message
|
|
227
|
+
+ "\n\nReturn only JSON. Do not include any non JSON text.\n"
|
|
228
|
+
)
|
|
229
|
+
return system_message
|
|
230
|
+
|
|
231
|
+
async def generate_and_upload_jsonl(
|
|
232
|
+
self, dataset: DatasetSplit, split_name: str, task: Task, format: DatasetFormat
|
|
233
|
+
) -> str:
|
|
234
|
+
formatter = DatasetFormatter(
|
|
235
|
+
dataset=dataset,
|
|
236
|
+
system_message=self.datamodel.system_message,
|
|
237
|
+
thinking_instructions=self.datamodel.thinking_instructions,
|
|
238
|
+
)
|
|
239
|
+
path = formatter.dump_to_file(split_name, format, self.datamodel.data_strategy)
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
together_file = self.client.files.upload(
|
|
243
|
+
file=path,
|
|
244
|
+
purpose=FilePurpose.FineTune,
|
|
245
|
+
check=True,
|
|
246
|
+
)
|
|
247
|
+
return together_file.id
|
|
248
|
+
except Exception as e:
|
|
249
|
+
raise ValueError(f"Failed to upload dataset: {e}")
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def available_parameters(cls) -> list[FineTuneParameter]:
|
|
253
|
+
return [
|
|
254
|
+
FineTuneParameter(
|
|
255
|
+
name="epochs",
|
|
256
|
+
description="Number of epochs to fine-tune for. Default: 1, Min: 1, Max: 20.",
|
|
257
|
+
type="int",
|
|
258
|
+
optional=True,
|
|
259
|
+
),
|
|
260
|
+
FineTuneParameter(
|
|
261
|
+
name="learning_rate",
|
|
262
|
+
description="Learning rate to use for fine-tuning. Defaults to 0.00001",
|
|
263
|
+
type="float",
|
|
264
|
+
optional=True,
|
|
265
|
+
),
|
|
266
|
+
FineTuneParameter(
|
|
267
|
+
name="batch_size",
|
|
268
|
+
description="Batch size used in training. Can be configured with a positive value less than 1024 and in power of 2. If not specified, defaults to the max supported value for this model. See together model pages for min/max values for each model.",
|
|
269
|
+
type="int",
|
|
270
|
+
optional=True,
|
|
271
|
+
),
|
|
272
|
+
FineTuneParameter(
|
|
273
|
+
name="num_checkpoints",
|
|
274
|
+
description="Number of checkpoints to save during training. Defaults to 1.",
|
|
275
|
+
type="int",
|
|
276
|
+
optional=True,
|
|
277
|
+
),
|
|
278
|
+
FineTuneParameter(
|
|
279
|
+
name="min_lr_ratio",
|
|
280
|
+
description="The ratio of the final learning rate to the peak learning rate. Default: 0.0, Min: 0.0, Max: 1.0.",
|
|
281
|
+
type="float",
|
|
282
|
+
optional=True,
|
|
283
|
+
),
|
|
284
|
+
FineTuneParameter(
|
|
285
|
+
name="warmup_ratio",
|
|
286
|
+
description="The percent of steps at the start of training to linearly increase the learning rate. Defaults to 0.0, Min: 0.0, Max: 1.0.",
|
|
287
|
+
type="float",
|
|
288
|
+
optional=True,
|
|
289
|
+
),
|
|
290
|
+
FineTuneParameter(
|
|
291
|
+
name="max_grad_norm",
|
|
292
|
+
description="Max gradient norm for gradient clipping. Set to 0 to disable. Defaults to 1.0, Min: 0.0.",
|
|
293
|
+
type="float",
|
|
294
|
+
optional=True,
|
|
295
|
+
),
|
|
296
|
+
FineTuneParameter(
|
|
297
|
+
name="weight_decay",
|
|
298
|
+
description="Weight decay. Defaults to 0.0.",
|
|
299
|
+
type="float",
|
|
300
|
+
optional=True,
|
|
301
|
+
),
|
|
302
|
+
FineTuneParameter(
|
|
303
|
+
name="lora_rank",
|
|
304
|
+
description="Rank of LoRA adapters. Default: 8, Min: 1, Max: 64",
|
|
305
|
+
type="int",
|
|
306
|
+
optional=True,
|
|
307
|
+
),
|
|
308
|
+
FineTuneParameter(
|
|
309
|
+
name="lora_dropout",
|
|
310
|
+
description="Dropout rate for LoRA adapters. Default: 0.0, Min: 0.0, Max: 1.0.",
|
|
311
|
+
type="float",
|
|
312
|
+
optional=True,
|
|
313
|
+
),
|
|
314
|
+
FineTuneParameter(
|
|
315
|
+
name="lora_alpha",
|
|
316
|
+
description="Alpha value for LoRA adapter training. Default: 8. Min: 1. If a value less than 1 is given, it will default to lora_rank value to follow the recommendation of 1:1 scaling.",
|
|
317
|
+
type="float",
|
|
318
|
+
optional=True,
|
|
319
|
+
),
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
async def _deploy(self) -> bool:
|
|
323
|
+
# Together is awesome. Auto deploys!
|
|
324
|
+
# If I add support for non-Lora serverless I'll have to modify this, but good for now.
|
|
325
|
+
return True
|