together 2.0.0a14__py3-none-any.whl → 2.0.0a16__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.
- together/_base_client.py +134 -11
- together/_client.py +7 -0
- together/_models.py +16 -1
- together/_types.py +9 -0
- together/_version.py +1 -1
- together/constants.py +34 -0
- together/error.py +15 -0
- together/lib/_google_colab.py +39 -0
- together/lib/cli/__init__.py +73 -0
- together/lib/cli/api/{utils.py → _utils.py} +4 -4
- together/lib/cli/api/beta/clusters/__init__.py +47 -0
- together/lib/cli/api/beta/{clusters.py → clusters/create.py} +5 -179
- together/lib/cli/api/beta/clusters/delete.py +35 -0
- together/lib/cli/api/beta/clusters/get_credentials.py +151 -0
- together/lib/cli/api/beta/clusters/list.py +24 -0
- together/lib/cli/api/beta/clusters/list_regions.py +37 -0
- together/lib/cli/api/beta/clusters/retrieve.py +31 -0
- together/lib/cli/api/beta/clusters/storage/__init__.py +19 -0
- together/lib/cli/api/beta/clusters/storage/create.py +49 -0
- together/lib/cli/api/beta/clusters/storage/delete.py +38 -0
- together/lib/cli/api/beta/clusters/storage/list.py +42 -0
- together/lib/cli/api/beta/clusters/storage/retrieve.py +34 -0
- together/lib/cli/api/beta/clusters/update.py +54 -0
- together/lib/cli/api/endpoints/__init__.py +56 -0
- together/lib/cli/api/endpoints/availability_zones.py +26 -0
- together/lib/cli/api/endpoints/create.py +159 -0
- together/lib/cli/api/endpoints/delete.py +15 -0
- together/lib/cli/api/endpoints/hardware.py +40 -0
- together/lib/cli/api/endpoints/list.py +66 -0
- together/lib/cli/api/endpoints/retrieve.py +23 -0
- together/lib/cli/api/endpoints/start.py +25 -0
- together/lib/cli/api/endpoints/stop.py +25 -0
- together/lib/cli/api/endpoints/update.py +77 -0
- together/lib/cli/api/evals/__init__.py +19 -0
- together/lib/cli/api/{evals.py → evals/create.py} +6 -129
- together/lib/cli/api/evals/list.py +58 -0
- together/lib/cli/api/evals/retrieve.py +21 -0
- together/lib/cli/api/evals/status.py +20 -0
- together/lib/cli/api/files/__init__.py +23 -0
- together/lib/cli/api/files/check.py +21 -0
- together/lib/cli/api/files/delete.py +20 -0
- together/lib/cli/api/files/list.py +34 -0
- together/lib/cli/api/files/retrieve.py +20 -0
- together/lib/cli/api/files/retrieve_content.py +25 -0
- together/lib/cli/api/files/upload.py +38 -0
- together/lib/cli/api/fine_tuning/__init__.py +27 -0
- together/lib/cli/api/fine_tuning/cancel.py +28 -0
- together/lib/cli/api/{fine_tuning.py → fine_tuning/create.py} +5 -257
- together/lib/cli/api/fine_tuning/delete.py +29 -0
- together/lib/cli/api/fine_tuning/download.py +94 -0
- together/lib/cli/api/fine_tuning/list.py +44 -0
- together/lib/cli/api/fine_tuning/list_checkpoints.py +42 -0
- together/lib/cli/api/fine_tuning/list_events.py +35 -0
- together/lib/cli/api/fine_tuning/retrieve.py +27 -0
- together/lib/cli/api/models/__init__.py +15 -0
- together/lib/cli/api/models/list.py +51 -0
- together/lib/cli/api/{models.py → models/upload.py} +4 -51
- together/lib/types/fine_tuning.py +3 -0
- together/resources/beta/clusters/clusters.py +36 -28
- together/resources/beta/clusters/storage.py +30 -21
- together/types/__init__.py +13 -1
- together/types/beta/__init__.py +0 -2
- together/types/beta/cluster_create_params.py +3 -3
- together/types/beta/clusters/__init__.py +0 -1
- together/types/beta/clusters/cluster_storage.py +4 -0
- together/types/chat_completions.py +7 -0
- together/types/endpoints.py +4 -0
- together/types/files.py +8 -0
- together/types/fine_tuning_cancel_response.py +3 -0
- together/types/fine_tuning_list_response.py +3 -0
- together/types/finetune.py +27 -0
- together/types/finetune_response.py +2 -0
- together/types/models.py +2 -0
- {together-2.0.0a14.dist-info → together-2.0.0a16.dist-info}/METADATA +45 -2
- {together-2.0.0a14.dist-info → together-2.0.0a16.dist-info}/RECORD +79 -36
- together-2.0.0a16.dist-info/entry_points.txt +2 -0
- together/lib/cli/api/__init__.py +0 -0
- together/lib/cli/api/beta/clusters_storage.py +0 -152
- together/lib/cli/api/endpoints.py +0 -467
- together/lib/cli/api/files.py +0 -133
- together/lib/cli/cli.py +0 -73
- together/types/beta/cluster_create_response.py +0 -9
- together/types/beta/cluster_update_response.py +0 -9
- together/types/beta/clusters/storage_create_response.py +0 -9
- together-2.0.0a14.dist-info/entry_points.txt +0 -2
- /together/lib/cli/api/beta/{beta.py → __init__.py} +0 -0
- {together-2.0.0a14.dist-info → together-2.0.0a16.dist-info}/WHEEL +0 -0
- {together-2.0.0a14.dist-info → together-2.0.0a16.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import json
|
|
5
|
-
from typing import Any, Dict, List, Union, Literal
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from datetime import datetime, timezone
|
|
8
|
-
from textwrap import wrap
|
|
3
|
+
from typing import Any, Literal
|
|
9
4
|
|
|
10
5
|
import click
|
|
11
6
|
from rich import print as rprint
|
|
12
|
-
from
|
|
13
|
-
from rich.json import JSON
|
|
14
|
-
from click.core import ParameterSource # type: ignore[attr-defined]
|
|
7
|
+
from click.core import ParameterSource
|
|
15
8
|
|
|
16
9
|
from together import Together
|
|
17
10
|
from together.types import fine_tuning_estimate_price_params as pe_params
|
|
18
|
-
from together._types import NOT_GIVEN, NotGiven
|
|
19
11
|
from together.lib.utils import log_warn
|
|
20
|
-
from together.lib.
|
|
21
|
-
from together.lib.cli.api.utils import INT_WITH_MAX, BOOL_WITH_AUTO, generate_progress_bar
|
|
22
|
-
from together.lib.resources.files import DownloadManager
|
|
23
|
-
from together.lib.utils.serializer import datetime_serializer
|
|
24
|
-
from together.types.finetune_response import TrainingTypeFullTrainingType, TrainingTypeLoRaTrainingType
|
|
12
|
+
from together.lib.cli.api._utils import INT_WITH_MAX, BOOL_WITH_AUTO, handle_api_errors
|
|
25
13
|
from together.lib.resources.fine_tuning import get_model_limits
|
|
26
14
|
|
|
27
15
|
_CONFIRMATION_MESSAGE = (
|
|
@@ -41,17 +29,8 @@ _WARNING_MESSAGE_INSUFFICIENT_FUNDS = (
|
|
|
41
29
|
"Consider increasing your credit limit at https://api.together.xyz/settings/profile\n"
|
|
42
30
|
)
|
|
43
31
|
|
|
44
|
-
_FT_JOB_WITH_STEP_REGEX = r"^ft-[\dabcdef-]+:\d+$"
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
@click.group(name="fine-tuning")
|
|
48
|
-
@click.pass_context
|
|
49
|
-
def fine_tuning(ctx: click.Context) -> None:
|
|
50
|
-
"""Fine-tunes API commands"""
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@fine_tuning.command()
|
|
33
|
+
@click.command()
|
|
55
34
|
@click.pass_context
|
|
56
35
|
@click.option(
|
|
57
36
|
"--training-file",
|
|
@@ -216,6 +195,7 @@ def fine_tuning(ctx: click.Context) -> None:
|
|
|
216
195
|
default=None,
|
|
217
196
|
help="HF repo to upload the fine-tuned model to",
|
|
218
197
|
)
|
|
198
|
+
@handle_api_errors("Fine-tuning")
|
|
219
199
|
def create(
|
|
220
200
|
ctx: click.Context,
|
|
221
201
|
training_file: str,
|
|
@@ -411,235 +391,3 @@ def create(
|
|
|
411
391
|
rprint(report_string)
|
|
412
392
|
else:
|
|
413
393
|
click.echo("No confirmation received, stopping job launch")
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
@fine_tuning.command()
|
|
417
|
-
@click.pass_context
|
|
418
|
-
def list(ctx: click.Context) -> None:
|
|
419
|
-
"""List fine-tuning jobs"""
|
|
420
|
-
client: Together = ctx.obj
|
|
421
|
-
|
|
422
|
-
response = client.fine_tuning.list()
|
|
423
|
-
|
|
424
|
-
response.data = response.data or []
|
|
425
|
-
|
|
426
|
-
# Use a default datetime for None values to make sure the key function always returns a comparable value
|
|
427
|
-
epoch_start = datetime.fromtimestamp(0, tz=timezone.utc)
|
|
428
|
-
response.data.sort(key=lambda x: x.created_at or epoch_start)
|
|
429
|
-
|
|
430
|
-
display_list: List[Dict[str, Any]] = []
|
|
431
|
-
for i in response.data:
|
|
432
|
-
display_list.append(
|
|
433
|
-
{
|
|
434
|
-
"Fine-tune ID": i.id,
|
|
435
|
-
"Model Output Name": "\n".join(wrap(i.x_model_output_name or "", width=30)),
|
|
436
|
-
"Status": i.status,
|
|
437
|
-
"Created At": i.created_at,
|
|
438
|
-
"Price": f"""${
|
|
439
|
-
finetune_price_to_dollars(float(str(i.total_price)))
|
|
440
|
-
}""", # convert to string for mypy typing
|
|
441
|
-
"Progress": generate_progress_bar(i, datetime.now().astimezone(), use_rich=False),
|
|
442
|
-
}
|
|
443
|
-
)
|
|
444
|
-
table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
|
|
445
|
-
|
|
446
|
-
click.echo(table)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
@fine_tuning.command()
|
|
450
|
-
@click.pass_context
|
|
451
|
-
@click.argument("fine_tune_id", type=str, required=True)
|
|
452
|
-
def retrieve(ctx: click.Context, fine_tune_id: str) -> None:
|
|
453
|
-
"""Retrieve fine-tuning job details"""
|
|
454
|
-
client: Together = ctx.obj
|
|
455
|
-
|
|
456
|
-
response = client.fine_tuning.retrieve(fine_tune_id)
|
|
457
|
-
|
|
458
|
-
# remove events from response for cleaner output
|
|
459
|
-
response.events = None
|
|
460
|
-
|
|
461
|
-
rprint(JSON.from_data(response.model_json_schema()))
|
|
462
|
-
progress_text = generate_progress_bar(response, datetime.now().astimezone(), use_rich=True)
|
|
463
|
-
prefix = f"Status: [bold]{response.status}[/bold],"
|
|
464
|
-
rprint(f"{prefix} {progress_text}")
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
@fine_tuning.command()
|
|
468
|
-
@click.pass_context
|
|
469
|
-
@click.argument("fine_tune_id", type=str, required=True)
|
|
470
|
-
@click.option("--quiet", is_flag=True, help="Do not prompt for confirmation before cancelling job")
|
|
471
|
-
def cancel(ctx: click.Context, fine_tune_id: str, quiet: bool = False) -> None:
|
|
472
|
-
"""Cancel fine-tuning job"""
|
|
473
|
-
client: Together = ctx.obj
|
|
474
|
-
if not quiet:
|
|
475
|
-
confirm_response = input(
|
|
476
|
-
"You will be billed for any completed training steps upon cancellation. "
|
|
477
|
-
f"Do you want to cancel job {fine_tune_id}? [y/N]"
|
|
478
|
-
)
|
|
479
|
-
if "y" not in confirm_response.lower():
|
|
480
|
-
click.echo({"status": "Cancel not submitted"})
|
|
481
|
-
return
|
|
482
|
-
response = client.fine_tuning.cancel(fine_tune_id)
|
|
483
|
-
|
|
484
|
-
click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4, default=datetime_serializer))
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
@fine_tuning.command()
|
|
488
|
-
@click.pass_context
|
|
489
|
-
@click.argument("fine_tune_id", type=str, required=True)
|
|
490
|
-
def list_events(ctx: click.Context, fine_tune_id: str) -> None:
|
|
491
|
-
"""List fine-tuning events"""
|
|
492
|
-
client: Together = ctx.obj
|
|
493
|
-
|
|
494
|
-
response = client.fine_tuning.list_events(fine_tune_id)
|
|
495
|
-
|
|
496
|
-
response.data = response.data or []
|
|
497
|
-
|
|
498
|
-
display_list: List[Dict[str, Any]] = []
|
|
499
|
-
for i in response.data:
|
|
500
|
-
display_list.append(
|
|
501
|
-
{
|
|
502
|
-
"Message": "\n".join(wrap(i.message or "", width=50)),
|
|
503
|
-
"Type": i.type,
|
|
504
|
-
"Created At": i.created_at,
|
|
505
|
-
"Hash": i.hash,
|
|
506
|
-
}
|
|
507
|
-
)
|
|
508
|
-
table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
|
|
509
|
-
|
|
510
|
-
click.echo(table)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
@fine_tuning.command()
|
|
514
|
-
@click.pass_context
|
|
515
|
-
@click.argument("fine_tune_id", type=str, required=True)
|
|
516
|
-
def list_checkpoints(ctx: click.Context, fine_tune_id: str) -> None:
|
|
517
|
-
"""List available checkpoints for a fine-tuning job"""
|
|
518
|
-
client: Together = ctx.obj
|
|
519
|
-
|
|
520
|
-
checkpoints = client.fine_tuning.list_checkpoints(fine_tune_id)
|
|
521
|
-
|
|
522
|
-
display_list: List[Dict[str, Any]] = []
|
|
523
|
-
for checkpoint in checkpoints.data:
|
|
524
|
-
name = (
|
|
525
|
-
f"{fine_tune_id}:{checkpoint.step}"
|
|
526
|
-
if "intermediate" in checkpoint.checkpoint_type.lower()
|
|
527
|
-
else fine_tune_id
|
|
528
|
-
)
|
|
529
|
-
display_list.append(
|
|
530
|
-
{
|
|
531
|
-
"Type": checkpoint.checkpoint_type,
|
|
532
|
-
"Timestamp": format_timestamp(checkpoint.created_at),
|
|
533
|
-
"Name": name,
|
|
534
|
-
}
|
|
535
|
-
)
|
|
536
|
-
|
|
537
|
-
if display_list:
|
|
538
|
-
click.echo(f"Job {fine_tune_id} contains the following checkpoints:")
|
|
539
|
-
table = tabulate(display_list, headers="keys", tablefmt="grid")
|
|
540
|
-
click.echo(table)
|
|
541
|
-
click.echo("\nTo download a checkpoint, use `together fine-tuning download`")
|
|
542
|
-
else:
|
|
543
|
-
click.echo(f"No checkpoints found for job {fine_tune_id}")
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
@fine_tuning.command(name="download")
|
|
547
|
-
@click.pass_context
|
|
548
|
-
@click.argument("fine_tune_id", type=str, required=True)
|
|
549
|
-
@click.option(
|
|
550
|
-
"--output_dir",
|
|
551
|
-
"-o",
|
|
552
|
-
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
553
|
-
required=False,
|
|
554
|
-
default=None,
|
|
555
|
-
help="Output directory",
|
|
556
|
-
)
|
|
557
|
-
@click.option(
|
|
558
|
-
"--checkpoint-step",
|
|
559
|
-
"-s",
|
|
560
|
-
type=int,
|
|
561
|
-
required=False,
|
|
562
|
-
default=None,
|
|
563
|
-
help="Download fine-tuning checkpoint. Defaults to latest.",
|
|
564
|
-
)
|
|
565
|
-
@click.option(
|
|
566
|
-
"--checkpoint-type",
|
|
567
|
-
type=click.Choice(["merged", "adapter", "default"]),
|
|
568
|
-
required=False,
|
|
569
|
-
default="merged",
|
|
570
|
-
help="Specifies checkpoint type. 'merged' and 'adapter' options work only for LoRA jobs.",
|
|
571
|
-
)
|
|
572
|
-
def download(
|
|
573
|
-
ctx: click.Context,
|
|
574
|
-
fine_tune_id: str,
|
|
575
|
-
output_dir: str | None = None,
|
|
576
|
-
checkpoint_step: Union[int, NotGiven] = NOT_GIVEN,
|
|
577
|
-
checkpoint_type: Literal["default", "merged", "adapter"] | NotGiven = NOT_GIVEN,
|
|
578
|
-
) -> None:
|
|
579
|
-
"""Download fine-tuning checkpoint"""
|
|
580
|
-
client: Together = ctx.obj
|
|
581
|
-
|
|
582
|
-
if re.match(_FT_JOB_WITH_STEP_REGEX, fine_tune_id) is not None:
|
|
583
|
-
if checkpoint_step is NOT_GIVEN:
|
|
584
|
-
checkpoint_step = int(fine_tune_id.split(":")[1])
|
|
585
|
-
fine_tune_id = fine_tune_id.split(":")[0]
|
|
586
|
-
else:
|
|
587
|
-
raise ValueError(
|
|
588
|
-
"Fine-tuning job ID {fine_tune_id} contains a colon to specify the step to download, but `checkpoint_step` "
|
|
589
|
-
"was also set. Remove one of the step specifiers to proceed."
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
ft_job = client.fine_tuning.retrieve(fine_tune_id)
|
|
593
|
-
|
|
594
|
-
loosely_typed_checkpoint_type: str | NotGiven = checkpoint_type
|
|
595
|
-
if isinstance(ft_job.training_type, TrainingTypeFullTrainingType):
|
|
596
|
-
if checkpoint_type != "default":
|
|
597
|
-
raise ValueError("Only DEFAULT checkpoint type is allowed for FullTrainingType")
|
|
598
|
-
loosely_typed_checkpoint_type = "model_output_path"
|
|
599
|
-
elif isinstance(ft_job.training_type, TrainingTypeLoRaTrainingType):
|
|
600
|
-
if checkpoint_type == "default":
|
|
601
|
-
loosely_typed_checkpoint_type = "merged"
|
|
602
|
-
|
|
603
|
-
if checkpoint_type not in {
|
|
604
|
-
"merged",
|
|
605
|
-
"adapter",
|
|
606
|
-
}:
|
|
607
|
-
raise ValueError(f"Invalid checkpoint type for LoRATrainingType: {checkpoint_type}")
|
|
608
|
-
|
|
609
|
-
remote_name = ft_job.x_model_output_name
|
|
610
|
-
|
|
611
|
-
url = f"/finetune/download?ft_id={fine_tune_id}&checkpoint={loosely_typed_checkpoint_type}"
|
|
612
|
-
output: Path | None = None
|
|
613
|
-
if isinstance(output_dir, str):
|
|
614
|
-
output = Path(output_dir)
|
|
615
|
-
|
|
616
|
-
file_path, file_size = DownloadManager(client).download(
|
|
617
|
-
url=url,
|
|
618
|
-
output=output,
|
|
619
|
-
remote_name=remote_name,
|
|
620
|
-
fetch_metadata=True,
|
|
621
|
-
)
|
|
622
|
-
|
|
623
|
-
click.echo(json.dumps({"object": "local", "id": fine_tune_id, "filename": file_path, "size": file_size}, indent=4))
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
@fine_tuning.command()
|
|
627
|
-
@click.pass_context
|
|
628
|
-
@click.argument("fine_tune_id", type=str, required=True)
|
|
629
|
-
@click.option("--force", is_flag=True, help="Force deletion without confirmation")
|
|
630
|
-
@click.option("--quiet", is_flag=True, help="Do not prompt for confirmation before deleting job")
|
|
631
|
-
def delete(ctx: click.Context, fine_tune_id: str, force: bool = False, quiet: bool = False) -> None:
|
|
632
|
-
"""Delete fine-tuning job"""
|
|
633
|
-
client: Together = ctx.obj
|
|
634
|
-
|
|
635
|
-
if not quiet:
|
|
636
|
-
confirm_response = input(
|
|
637
|
-
f"Are you sure you want to delete fine-tuning job {fine_tune_id}? This action cannot be undone. [y/N] "
|
|
638
|
-
)
|
|
639
|
-
if confirm_response.lower() != "y":
|
|
640
|
-
click.echo("Deletion cancelled")
|
|
641
|
-
return
|
|
642
|
-
|
|
643
|
-
response = client.fine_tuning.delete(fine_tune_id, force=force)
|
|
644
|
-
|
|
645
|
-
click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from together import Together
|
|
6
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.pass_context
|
|
11
|
+
@click.argument("fine_tune_id", type=str, required=True)
|
|
12
|
+
@click.option("--force", is_flag=True, help="Force deletion without confirmation")
|
|
13
|
+
@click.option("--quiet", is_flag=True, help="Do not prompt for confirmation before deleting job")
|
|
14
|
+
@handle_api_errors("Fine-tuning")
|
|
15
|
+
def delete(ctx: click.Context, fine_tune_id: str, force: bool = False, quiet: bool = False) -> None:
|
|
16
|
+
"""Delete fine-tuning job"""
|
|
17
|
+
client: Together = ctx.obj
|
|
18
|
+
|
|
19
|
+
if not quiet:
|
|
20
|
+
confirm_response = input(
|
|
21
|
+
f"Are you sure you want to delete fine-tuning job {fine_tune_id}? This action cannot be undone. [y/N] "
|
|
22
|
+
)
|
|
23
|
+
if confirm_response.lower() != "y":
|
|
24
|
+
click.echo("Deletion cancelled")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
response = client.fine_tuning.delete(fine_tune_id, force=force)
|
|
28
|
+
|
|
29
|
+
click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4))
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import json
|
|
5
|
+
from typing import Union, Literal
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from together import NOT_GIVEN, NotGiven, Together
|
|
11
|
+
from together.lib import DownloadManager
|
|
12
|
+
from together.types.finetune_response import TrainingTypeFullTrainingType, TrainingTypeLoRaTrainingType
|
|
13
|
+
|
|
14
|
+
_FT_JOB_WITH_STEP_REGEX = r"^ft-[\dabcdef-]+:\d+$"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command()
|
|
18
|
+
@click.pass_context
|
|
19
|
+
@click.argument("fine_tune_id", type=str, required=True)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--output_dir",
|
|
22
|
+
"-o",
|
|
23
|
+
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
24
|
+
required=False,
|
|
25
|
+
default=None,
|
|
26
|
+
help="Output directory",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--checkpoint-step",
|
|
30
|
+
"-s",
|
|
31
|
+
type=int,
|
|
32
|
+
required=False,
|
|
33
|
+
default=None,
|
|
34
|
+
help="Download fine-tuning checkpoint. Defaults to latest.",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--checkpoint-type",
|
|
38
|
+
type=click.Choice(["merged", "adapter", "default"]),
|
|
39
|
+
required=False,
|
|
40
|
+
default="merged",
|
|
41
|
+
help="Specifies checkpoint type. 'merged' and 'adapter' options work only for LoRA jobs.",
|
|
42
|
+
)
|
|
43
|
+
def download(
|
|
44
|
+
ctx: click.Context,
|
|
45
|
+
fine_tune_id: str,
|
|
46
|
+
output_dir: str | None = None,
|
|
47
|
+
checkpoint_step: Union[int, NotGiven] = NOT_GIVEN,
|
|
48
|
+
checkpoint_type: Literal["default", "merged", "adapter"] | NotGiven = NOT_GIVEN,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Download fine-tuning checkpoint"""
|
|
51
|
+
client: Together = ctx.obj
|
|
52
|
+
|
|
53
|
+
if re.match(_FT_JOB_WITH_STEP_REGEX, fine_tune_id) is not None:
|
|
54
|
+
if checkpoint_step is NOT_GIVEN:
|
|
55
|
+
checkpoint_step = int(fine_tune_id.split(":")[1])
|
|
56
|
+
fine_tune_id = fine_tune_id.split(":")[0]
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"Fine-tuning job ID {fine_tune_id} contains a colon to specify the step to download, but `checkpoint_step` "
|
|
60
|
+
"was also set. Remove one of the step specifiers to proceed."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
ft_job = client.fine_tuning.retrieve(fine_tune_id)
|
|
64
|
+
|
|
65
|
+
loosely_typed_checkpoint_type: str | NotGiven = checkpoint_type
|
|
66
|
+
if isinstance(ft_job.training_type, TrainingTypeFullTrainingType):
|
|
67
|
+
if checkpoint_type != "default":
|
|
68
|
+
raise ValueError("Only DEFAULT checkpoint type is allowed for FullTrainingType")
|
|
69
|
+
loosely_typed_checkpoint_type = "model_output_path"
|
|
70
|
+
elif isinstance(ft_job.training_type, TrainingTypeLoRaTrainingType):
|
|
71
|
+
if checkpoint_type == "default":
|
|
72
|
+
loosely_typed_checkpoint_type = "merged"
|
|
73
|
+
|
|
74
|
+
if checkpoint_type not in {
|
|
75
|
+
"merged",
|
|
76
|
+
"adapter",
|
|
77
|
+
}:
|
|
78
|
+
raise ValueError(f"Invalid checkpoint type for LoRATrainingType: {checkpoint_type}")
|
|
79
|
+
|
|
80
|
+
remote_name = ft_job.x_model_output_name
|
|
81
|
+
|
|
82
|
+
url = f"/finetune/download?ft_id={fine_tune_id}&checkpoint={loosely_typed_checkpoint_type}"
|
|
83
|
+
output: Path | None = None
|
|
84
|
+
if isinstance(output_dir, str):
|
|
85
|
+
output = Path(output_dir)
|
|
86
|
+
|
|
87
|
+
file_path, file_size = DownloadManager(client).download(
|
|
88
|
+
url=url,
|
|
89
|
+
output=output,
|
|
90
|
+
remote_name=remote_name,
|
|
91
|
+
fetch_metadata=True,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
click.echo(json.dumps({"object": "local", "id": fine_tune_id, "filename": file_path, "size": file_size}, indent=4))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from textwrap import wrap
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from tabulate import tabulate
|
|
7
|
+
|
|
8
|
+
from together import Together
|
|
9
|
+
from together.lib.utils import finetune_price_to_dollars
|
|
10
|
+
from together.lib.cli.api._utils import handle_api_errors, generate_progress_bar
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.pass_context
|
|
15
|
+
@handle_api_errors("Fine-tuning")
|
|
16
|
+
def list(ctx: click.Context) -> None:
|
|
17
|
+
"""List fine-tuning jobs"""
|
|
18
|
+
client: Together = ctx.obj
|
|
19
|
+
|
|
20
|
+
response = client.fine_tuning.list()
|
|
21
|
+
|
|
22
|
+
response.data = response.data or []
|
|
23
|
+
|
|
24
|
+
# Use a default datetime for None values to make sure the key function always returns a comparable value
|
|
25
|
+
epoch_start = datetime.fromtimestamp(0, tz=timezone.utc)
|
|
26
|
+
response.data.sort(key=lambda x: x.created_at or epoch_start)
|
|
27
|
+
|
|
28
|
+
display_list: List[Dict[str, Any]] = []
|
|
29
|
+
for i in response.data:
|
|
30
|
+
display_list.append(
|
|
31
|
+
{
|
|
32
|
+
"Fine-tune ID": i.id,
|
|
33
|
+
"Model Output Name": "\n".join(wrap(i.x_model_output_name or "", width=30)),
|
|
34
|
+
"Status": i.status,
|
|
35
|
+
"Created At": i.created_at,
|
|
36
|
+
"Price": f"""${
|
|
37
|
+
finetune_price_to_dollars(float(str(i.total_price)))
|
|
38
|
+
}""", # convert to string for mypy typing
|
|
39
|
+
"Progress": generate_progress_bar(i, datetime.now().astimezone(), use_rich=False),
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
|
|
43
|
+
|
|
44
|
+
click.echo(table)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from tabulate import tabulate
|
|
5
|
+
|
|
6
|
+
from together import Together
|
|
7
|
+
from together.lib.utils.tools import format_timestamp
|
|
8
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.pass_context
|
|
13
|
+
@click.argument("fine_tune_id", type=str, required=True)
|
|
14
|
+
@handle_api_errors("Fine-tuning")
|
|
15
|
+
def list_checkpoints(ctx: click.Context, fine_tune_id: str) -> None:
|
|
16
|
+
"""List available checkpoints for a fine-tuning job"""
|
|
17
|
+
client: Together = ctx.obj
|
|
18
|
+
|
|
19
|
+
checkpoints = client.fine_tuning.list_checkpoints(fine_tune_id)
|
|
20
|
+
|
|
21
|
+
display_list: List[Dict[str, Any]] = []
|
|
22
|
+
for checkpoint in checkpoints.data:
|
|
23
|
+
name = (
|
|
24
|
+
f"{fine_tune_id}:{checkpoint.step}"
|
|
25
|
+
if "intermediate" in checkpoint.checkpoint_type.lower()
|
|
26
|
+
else fine_tune_id
|
|
27
|
+
)
|
|
28
|
+
display_list.append(
|
|
29
|
+
{
|
|
30
|
+
"Type": checkpoint.checkpoint_type,
|
|
31
|
+
"Timestamp": format_timestamp(checkpoint.created_at),
|
|
32
|
+
"Name": name,
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if display_list:
|
|
37
|
+
click.echo(f"Job {fine_tune_id} contains the following checkpoints:")
|
|
38
|
+
table = tabulate(display_list, headers="keys", tablefmt="grid")
|
|
39
|
+
click.echo(table)
|
|
40
|
+
click.echo("\nTo download a checkpoint, use `together fine-tuning download`")
|
|
41
|
+
else:
|
|
42
|
+
click.echo(f"No checkpoints found for job {fine_tune_id}")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
from textwrap import wrap
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
|
|
7
|
+
from together import Together
|
|
8
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.pass_context
|
|
13
|
+
@click.argument("fine_tune_id", type=str, required=True)
|
|
14
|
+
@handle_api_errors("Fine-tuning")
|
|
15
|
+
def list_events(ctx: click.Context, fine_tune_id: str) -> None:
|
|
16
|
+
"""List fine-tuning events"""
|
|
17
|
+
client: Together = ctx.obj
|
|
18
|
+
|
|
19
|
+
response = client.fine_tuning.list_events(fine_tune_id)
|
|
20
|
+
|
|
21
|
+
response.data = response.data or []
|
|
22
|
+
|
|
23
|
+
display_list: List[Dict[str, Any]] = []
|
|
24
|
+
for i in response.data:
|
|
25
|
+
display_list.append(
|
|
26
|
+
{
|
|
27
|
+
"Message": "\n".join(wrap(i.message or "", width=50)),
|
|
28
|
+
"Type": i.type,
|
|
29
|
+
"Created At": i.created_at,
|
|
30
|
+
"Hash": i.hash,
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
|
|
34
|
+
|
|
35
|
+
click.echo(table)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich import print as rprint
|
|
5
|
+
from rich.json import JSON
|
|
6
|
+
|
|
7
|
+
from together import Together
|
|
8
|
+
from together.lib.cli.api._utils import handle_api_errors, generate_progress_bar
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.pass_context
|
|
13
|
+
@click.argument("fine_tune_id", type=str, required=True)
|
|
14
|
+
@handle_api_errors("Fine-tuning")
|
|
15
|
+
def retrieve(ctx: click.Context, fine_tune_id: str) -> None:
|
|
16
|
+
"""Retrieve fine-tuning job details"""
|
|
17
|
+
client: Together = ctx.obj
|
|
18
|
+
|
|
19
|
+
response = client.fine_tuning.retrieve(fine_tune_id)
|
|
20
|
+
|
|
21
|
+
# remove events from response for cleaner output
|
|
22
|
+
response.events = None
|
|
23
|
+
|
|
24
|
+
rprint(JSON.from_data(response.model_json_schema()))
|
|
25
|
+
progress_text = generate_progress_bar(response, datetime.now().astimezone(), use_rich=True)
|
|
26
|
+
prefix = f"Status: [bold]{response.status}[/bold],"
|
|
27
|
+
rprint(f"{prefix} {progress_text}")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from .list import list
|
|
4
|
+
from .upload import upload
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
@click.pass_context
|
|
9
|
+
def models(ctx: click.Context) -> None:
|
|
10
|
+
"""Models API commands"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
models.add_command(list)
|
|
15
|
+
models.add_command(upload)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import json as json_lib
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from tabulate import tabulate
|
|
6
|
+
|
|
7
|
+
from together import Together, omit
|
|
8
|
+
from together._models import BaseModel
|
|
9
|
+
from together._response import APIResponse as APIResponse
|
|
10
|
+
from together.lib.cli.api._utils import handle_api_errors
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option(
|
|
15
|
+
"--type",
|
|
16
|
+
type=click.Choice(["dedicated"]),
|
|
17
|
+
help="Filter models by type (dedicated: models that can be deployed as dedicated endpoints)",
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
"--json",
|
|
21
|
+
is_flag=True,
|
|
22
|
+
help="Output in JSON format",
|
|
23
|
+
)
|
|
24
|
+
@click.pass_context
|
|
25
|
+
@handle_api_errors("Models")
|
|
26
|
+
def list(ctx: click.Context, type: Optional[str], json: bool) -> None:
|
|
27
|
+
"""List models"""
|
|
28
|
+
client: Together = ctx.obj
|
|
29
|
+
|
|
30
|
+
models_list = client.models.list(dedicated=type == "dedicated" if type else omit)
|
|
31
|
+
|
|
32
|
+
display_list: List[Dict[str, Any]] = []
|
|
33
|
+
model: BaseModel
|
|
34
|
+
for model in models_list:
|
|
35
|
+
display_list.append(
|
|
36
|
+
{
|
|
37
|
+
"ID": model.id,
|
|
38
|
+
"Name": model.display_name,
|
|
39
|
+
"Organization": model.organization,
|
|
40
|
+
"Type": model.type,
|
|
41
|
+
"Context Length": model.context_length,
|
|
42
|
+
"License": model.license,
|
|
43
|
+
"Input per 1M token": model.pricing.input if model.pricing else None,
|
|
44
|
+
"Output per 1M token": model.pricing.output if model.pricing else None,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if json:
|
|
49
|
+
click.echo(json_lib.dumps(display_list, indent=2))
|
|
50
|
+
else:
|
|
51
|
+
click.echo(tabulate(display_list, headers="keys", tablefmt="plain"))
|