kiln-ai 0.14.0__py3-none-any.whl → 0.16.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.
- kiln_ai/adapters/eval/base_eval.py +7 -2
- kiln_ai/adapters/eval/eval_runner.py +5 -64
- kiln_ai/adapters/eval/g_eval.py +3 -3
- kiln_ai/adapters/fine_tune/base_finetune.py +6 -3
- kiln_ai/adapters/fine_tune/dataset_formatter.py +128 -38
- kiln_ai/adapters/fine_tune/finetune_registry.py +2 -0
- kiln_ai/adapters/fine_tune/fireworks_finetune.py +2 -1
- kiln_ai/adapters/fine_tune/test_base_finetune.py +7 -0
- kiln_ai/adapters/fine_tune/test_dataset_formatter.py +267 -10
- kiln_ai/adapters/fine_tune/test_fireworks_tinetune.py +1 -1
- kiln_ai/adapters/fine_tune/test_vertex_finetune.py +586 -0
- kiln_ai/adapters/fine_tune/vertex_finetune.py +217 -0
- kiln_ai/adapters/ml_model_list.py +817 -62
- kiln_ai/adapters/model_adapters/base_adapter.py +33 -10
- kiln_ai/adapters/model_adapters/litellm_adapter.py +51 -12
- kiln_ai/adapters/model_adapters/test_base_adapter.py +74 -2
- kiln_ai/adapters/model_adapters/test_litellm_adapter.py +65 -1
- kiln_ai/adapters/model_adapters/test_saving_adapter_results.py +3 -2
- kiln_ai/adapters/model_adapters/test_structured_output.py +4 -6
- kiln_ai/adapters/parsers/base_parser.py +0 -3
- kiln_ai/adapters/parsers/parser_registry.py +5 -3
- kiln_ai/adapters/parsers/r1_parser.py +17 -2
- kiln_ai/adapters/parsers/request_formatters.py +40 -0
- kiln_ai/adapters/parsers/test_parser_registry.py +2 -2
- kiln_ai/adapters/parsers/test_r1_parser.py +44 -1
- kiln_ai/adapters/parsers/test_request_formatters.py +76 -0
- kiln_ai/adapters/prompt_builders.py +14 -1
- kiln_ai/adapters/provider_tools.py +25 -1
- kiln_ai/adapters/repair/test_repair_task.py +3 -2
- kiln_ai/adapters/test_prompt_builders.py +24 -3
- kiln_ai/adapters/test_provider_tools.py +86 -1
- kiln_ai/datamodel/__init__.py +2 -0
- kiln_ai/datamodel/datamodel_enums.py +14 -0
- kiln_ai/datamodel/dataset_filters.py +69 -1
- kiln_ai/datamodel/dataset_split.py +4 -0
- kiln_ai/datamodel/eval.py +8 -0
- kiln_ai/datamodel/finetune.py +1 -0
- kiln_ai/datamodel/json_schema.py +24 -7
- kiln_ai/datamodel/prompt_id.py +1 -0
- kiln_ai/datamodel/task_output.py +10 -6
- kiln_ai/datamodel/task_run.py +68 -12
- kiln_ai/datamodel/test_basemodel.py +3 -7
- kiln_ai/datamodel/test_dataset_filters.py +82 -0
- kiln_ai/datamodel/test_dataset_split.py +2 -0
- kiln_ai/datamodel/test_example_models.py +158 -3
- kiln_ai/datamodel/test_json_schema.py +22 -3
- kiln_ai/datamodel/test_model_perf.py +3 -2
- kiln_ai/datamodel/test_models.py +50 -2
- kiln_ai/utils/async_job_runner.py +106 -0
- kiln_ai/utils/dataset_import.py +80 -18
- kiln_ai/utils/test_async_job_runner.py +199 -0
- kiln_ai/utils/test_dataset_import.py +242 -10
- {kiln_ai-0.14.0.dist-info → kiln_ai-0.16.0.dist-info}/METADATA +3 -2
- kiln_ai-0.16.0.dist-info/RECORD +108 -0
- kiln_ai/adapters/test_generate_docs.py +0 -69
- kiln_ai-0.14.0.dist-info/RECORD +0 -103
- {kiln_ai-0.14.0.dist-info → kiln_ai-0.16.0.dist-info}/WHEEL +0 -0
- {kiln_ai-0.14.0.dist-info → kiln_ai-0.16.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import vertexai
|
|
5
|
+
from google.cloud import storage
|
|
6
|
+
from google.cloud.aiplatform_v1beta1 import types as gca_types
|
|
7
|
+
from vertexai.tuning import sft
|
|
8
|
+
|
|
9
|
+
from kiln_ai.adapters.fine_tune.base_finetune import (
|
|
10
|
+
BaseFinetuneAdapter,
|
|
11
|
+
FineTuneParameter,
|
|
12
|
+
FineTuneStatus,
|
|
13
|
+
FineTuneStatusType,
|
|
14
|
+
)
|
|
15
|
+
from kiln_ai.adapters.fine_tune.dataset_formatter import DatasetFormat, DatasetFormatter
|
|
16
|
+
from kiln_ai.datamodel import DatasetSplit, StructuredOutputMode, Task
|
|
17
|
+
from kiln_ai.utils.config import Config
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VertexFinetune(BaseFinetuneAdapter):
|
|
23
|
+
"""
|
|
24
|
+
A fine-tuning adapter for Vertex AI.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
async def status(self) -> FineTuneStatus:
|
|
28
|
+
"""
|
|
29
|
+
Get the status of the fine-tune.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Update the datamodel with the latest status if it has changed
|
|
33
|
+
status = await self._status()
|
|
34
|
+
if status.status != self.datamodel.latest_status:
|
|
35
|
+
self.datamodel.latest_status = status.status
|
|
36
|
+
if self.datamodel.path:
|
|
37
|
+
self.datamodel.save_to_file()
|
|
38
|
+
return status
|
|
39
|
+
|
|
40
|
+
async def _status(self) -> FineTuneStatus:
|
|
41
|
+
if not self.datamodel or not self.datamodel.provider_id:
|
|
42
|
+
return FineTuneStatus(
|
|
43
|
+
status=FineTuneStatusType.pending,
|
|
44
|
+
message="This fine-tune has not been started or has not been assigned a provider ID.",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
response = sft.SupervisedTuningJob(self.datamodel.provider_id)
|
|
48
|
+
# If the fine-tuned model ID has been updated, update the datamodel
|
|
49
|
+
try:
|
|
50
|
+
if self.datamodel.fine_tune_model_id != response.tuned_model_endpoint_name:
|
|
51
|
+
self.datamodel.fine_tune_model_id = response.tuned_model_endpoint_name
|
|
52
|
+
if self.datamodel.path:
|
|
53
|
+
self.datamodel.save_to_file()
|
|
54
|
+
except Exception as e:
|
|
55
|
+
# Don't let this error crash the status call
|
|
56
|
+
logger.warning(f"Error updating fine-tune model ID: {e}")
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
error = response.error
|
|
60
|
+
if error and error.code != 0:
|
|
61
|
+
return FineTuneStatus(
|
|
62
|
+
status=FineTuneStatusType.failed,
|
|
63
|
+
message=f"Fine Tune Job Error: {error.message} [{error.code}]",
|
|
64
|
+
)
|
|
65
|
+
state = response.state
|
|
66
|
+
if state in [
|
|
67
|
+
gca_types.JobState.JOB_STATE_FAILED,
|
|
68
|
+
gca_types.JobState.JOB_STATE_EXPIRED,
|
|
69
|
+
]:
|
|
70
|
+
return FineTuneStatus(
|
|
71
|
+
status=FineTuneStatusType.failed,
|
|
72
|
+
message="Fine Tune Job Failed",
|
|
73
|
+
)
|
|
74
|
+
if state in [
|
|
75
|
+
gca_types.JobState.JOB_STATE_CANCELLED,
|
|
76
|
+
gca_types.JobState.JOB_STATE_CANCELLING,
|
|
77
|
+
]:
|
|
78
|
+
return FineTuneStatus(
|
|
79
|
+
status=FineTuneStatusType.failed, message="Fine Tune Job Cancelled"
|
|
80
|
+
)
|
|
81
|
+
if state in [
|
|
82
|
+
gca_types.JobState.JOB_STATE_PENDING,
|
|
83
|
+
gca_types.JobState.JOB_STATE_QUEUED,
|
|
84
|
+
]:
|
|
85
|
+
return FineTuneStatus(
|
|
86
|
+
status=FineTuneStatusType.pending, message="Fine Tune Job Pending"
|
|
87
|
+
)
|
|
88
|
+
if state in [
|
|
89
|
+
gca_types.JobState.JOB_STATE_RUNNING,
|
|
90
|
+
]:
|
|
91
|
+
return FineTuneStatus(
|
|
92
|
+
status=FineTuneStatusType.running,
|
|
93
|
+
message="Fine Tune Job Running",
|
|
94
|
+
)
|
|
95
|
+
if state in [
|
|
96
|
+
gca_types.JobState.JOB_STATE_SUCCEEDED,
|
|
97
|
+
gca_types.JobState.JOB_STATE_PARTIALLY_SUCCEEDED,
|
|
98
|
+
]:
|
|
99
|
+
return FineTuneStatus(
|
|
100
|
+
status=FineTuneStatusType.completed, message="Fine Tune Job Completed"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if state not in [
|
|
104
|
+
gca_types.JobState.JOB_STATE_UPDATING,
|
|
105
|
+
gca_types.JobState.JOB_STATE_UNSPECIFIED,
|
|
106
|
+
gca_types.JobState.JOB_STATE_PAUSED,
|
|
107
|
+
]:
|
|
108
|
+
# While the above states map to "unknown", they are expected unknowns. Log if some new state appears we aren't expecting
|
|
109
|
+
logger.warning(f"Unknown Vertex AI Fine Tune Status: [{state}]")
|
|
110
|
+
|
|
111
|
+
return FineTuneStatus(
|
|
112
|
+
status=FineTuneStatusType.unknown, message=f"Unknown state: [{state}]"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def _start(self, dataset: DatasetSplit) -> None:
|
|
116
|
+
task = self.datamodel.parent_task()
|
|
117
|
+
if not task:
|
|
118
|
+
raise ValueError("Task is required to start a fine-tune")
|
|
119
|
+
|
|
120
|
+
# Use chat format for unstructured output, and JSON for formatted output
|
|
121
|
+
format = DatasetFormat.VERTEX_GEMINI
|
|
122
|
+
if task.output_json_schema:
|
|
123
|
+
self.datamodel.structured_output_mode = StructuredOutputMode.json_mode
|
|
124
|
+
train_file_id = await self.generate_and_upload_jsonl(
|
|
125
|
+
dataset, self.datamodel.train_split_name, task, format
|
|
126
|
+
)
|
|
127
|
+
validation_file_id = None
|
|
128
|
+
if self.datamodel.validation_split_name:
|
|
129
|
+
validation_file_id = await self.generate_and_upload_jsonl(
|
|
130
|
+
dataset, self.datamodel.validation_split_name, task, format
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
hyperparameters = self.datamodel.parameters
|
|
134
|
+
|
|
135
|
+
project, location = self.get_vertex_provider_location()
|
|
136
|
+
vertexai.init(project=project, location=location)
|
|
137
|
+
|
|
138
|
+
sft_tuning_job = sft.train(
|
|
139
|
+
source_model=self.datamodel.base_model_id,
|
|
140
|
+
train_dataset=train_file_id,
|
|
141
|
+
validation_dataset=validation_file_id,
|
|
142
|
+
tuned_model_display_name=f"kiln_finetune_{self.datamodel.id}",
|
|
143
|
+
# It is recommended to use auto-selection and leave them unset
|
|
144
|
+
epochs=hyperparameters.get("epochs", None), # type: ignore
|
|
145
|
+
adapter_size=hyperparameters.get("adapter_size", None), # type: ignore
|
|
146
|
+
learning_rate_multiplier=hyperparameters.get(
|
|
147
|
+
"learning_rate_multiplier", None
|
|
148
|
+
), # type: ignore
|
|
149
|
+
labels={
|
|
150
|
+
"source": "kiln",
|
|
151
|
+
"kiln_finetune_id": str(self.datamodel.id),
|
|
152
|
+
"kiln_task_id": str(task.id),
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
self.datamodel.provider_id = sft_tuning_job.resource_name
|
|
156
|
+
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
async def generate_and_upload_jsonl(
|
|
160
|
+
self, dataset: DatasetSplit, split_name: str, task: Task, format: DatasetFormat
|
|
161
|
+
) -> str:
|
|
162
|
+
formatter = DatasetFormatter(
|
|
163
|
+
dataset, self.datamodel.system_message, self.datamodel.thinking_instructions
|
|
164
|
+
)
|
|
165
|
+
path = formatter.dump_to_file(split_name, format, self.datamodel.data_strategy)
|
|
166
|
+
|
|
167
|
+
project, location = self.get_vertex_provider_location()
|
|
168
|
+
storage_client = storage.Client(project=project)
|
|
169
|
+
|
|
170
|
+
bucket_name = "kiln-ai-data"
|
|
171
|
+
|
|
172
|
+
# Check if bucket exists and create it if it doesn't
|
|
173
|
+
if not storage_client.lookup_bucket(bucket_name):
|
|
174
|
+
bucket = storage_client.create_bucket(bucket_name, location=location)
|
|
175
|
+
else:
|
|
176
|
+
bucket = storage_client.bucket(bucket_name)
|
|
177
|
+
|
|
178
|
+
# Create a blob and upload
|
|
179
|
+
epoch_timestamp = int(time.time())
|
|
180
|
+
blob_name = f"{epoch_timestamp}/{path.name}"
|
|
181
|
+
blob = bucket.blob(blob_name)
|
|
182
|
+
blob.upload_from_filename(path)
|
|
183
|
+
|
|
184
|
+
return f"gs://{bucket.name}/{blob.name}"
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def available_parameters(cls) -> list[FineTuneParameter]:
|
|
188
|
+
return [
|
|
189
|
+
FineTuneParameter(
|
|
190
|
+
name="learning_rate_multiplier",
|
|
191
|
+
type="float",
|
|
192
|
+
description="Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting. Defaults to 1.0 (don't scale vertex's learning rate).",
|
|
193
|
+
optional=True,
|
|
194
|
+
),
|
|
195
|
+
FineTuneParameter(
|
|
196
|
+
name="epochs",
|
|
197
|
+
type="int",
|
|
198
|
+
description="The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. Defaults to 'auto'",
|
|
199
|
+
optional=True,
|
|
200
|
+
),
|
|
201
|
+
FineTuneParameter(
|
|
202
|
+
name="adapter_size",
|
|
203
|
+
type="int",
|
|
204
|
+
description="The size of the adapter to use for the fine-tune. One of 1, 4, 8, or 16. By default Vertex will auto-select a size.",
|
|
205
|
+
optional=True,
|
|
206
|
+
),
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def get_vertex_provider_location(cls) -> tuple[str, str]:
|
|
211
|
+
project = Config.shared().vertex_project_id
|
|
212
|
+
location = Config.shared().vertex_location
|
|
213
|
+
if not project or not location:
|
|
214
|
+
raise ValueError(
|
|
215
|
+
"Google Vertex project and location must be set in Kiln settings to fine tune."
|
|
216
|
+
)
|
|
217
|
+
return project, location
|