Skip to content

Commit f6592a9

Browse files
authored
Merge branch 'main' into timkpaine-patch-2
2 parents 3360266 + cc9f253 commit f6592a9

19 files changed

Lines changed: 529 additions & 171 deletions

‎README.md‎

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,43 @@
77
> [!NOTE]
88
> ## 🚀 Together Python SDK 2.0 is now available!
99
>
10+
> V1 is now considered deprecated and will be maintained in maintanence mode. All new features and development will occur in the 2.0 SDK.
11+
>
1012
> Check out the new SDK: **[together-py](https://github.com/togethercomputer/together-py)**
1113
>
1214
> 📖 **Migration Guide:** [https://docs.together.ai/docs/pythonv2-migration-guide](https://docs.together.ai/docs/pythonv2-migration-guide)
1315
>
14-
> ### Install the Beta
16+
> ### Upgrade
1517
>
1618
> **Using uv (Recommended):**
1719
> ```bash
18-
> # Install uv if you haven't already
19-
> curl -LsSf https://astral.sh/uv/install.sh | sh
20-
>
21-
> # Install together python SDK
22-
> uv add together --prerelease allow
23-
>
24-
> # Or upgrade an existing installation
25-
> uv sync --upgrade-package together --prerelease allow
20+
> uv sync --upgrade-package together
2621
> ```
2722
>
2823
> **Using pip:**
2924
> ```bash
30-
> pip install --pre together
25+
> pip install --upgrade together
3126
> ```
3227
>
33-
> This package will be maintained until January 2026.
3428
35-
# Together Python API library
29+
# Together V1
3630
3731
[![PyPI version](https://img.shields.io/pypi/v/together.svg)](https://pypi.org/project/together/)
3832
[![Discord](https://dcbadge.limes.pink/api/server/https://discord.gg/9Rk6sSeWEG?style=flat&theme=discord-inverted)](https://discord.com/invite/9Rk6sSeWEG)
3933
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/togethercompute.svg?style=social&label=Follow%20%40togethercompute)](https://twitter.com/togethercompute)
4034
35+
> Note: You are looking at the codebase for Together Python V1. The latest Together Python SDK can be found **[here.](https://github.com/togethercomputer/together-py)**
36+
4137
The [Together Python API Library](https://pypi.org/project/together/) is the official Python client for Together's API platform, providing a convenient way for interacting with the REST APIs and enables easy integrations with Python 3.10+ applications with easy to use synchronous and asynchronous clients.
4238
4339
4440
4541
## Installation
4642
47-
> 🚧
48-
> The Library was rewritten in v1.0.0 released in April of 2024. There were significant changes made.
49-
5043
To install Together Python Library from PyPI, simply run:
5144
5245
```shell Shell
53-
pip install --upgrade together
46+
pip install together
5447
```
5548
5649
### Setting up API Key

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ build-backend = "poetry.masonry.api"
1212

1313
[tool.poetry]
1414
name = "together"
15-
version = "1.5.33"
15+
version = "1.5.35"
1616
authors = ["Together AI <support@together.ai>"]
1717
description = "Python client for Together's Cloud Platform! Note: SDK 2.0 is now available at https://github.com/togethercomputer/together-py"
1818
readme = "README.md"

‎src/together/__init__.py‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
================================================================================
1111
Together Python SDK 2.0 is now available!
1212
13-
Install: pip install --pre together
13+
Install: pip install together --upgrade
14+
Install: uv sync --upgrade-package together
1415
New SDK: https://github.com/togethercomputer/together-py
1516
Migration guide: https://docs.together.ai/docs/pythonv2-migration-guide
1617
17-
This package will be maintained until January 2026.
18+
Together V1 is now deprecated and will be maintained in maintanence mode.
19+
All new features and development will occur in the 2.0 SDK.
1820
================================================================================
1921
"""
2022

@@ -28,15 +30,14 @@
2830
console.print(
2931
Panel(
3032
"[bold cyan]Together Python SDK 2.0 is now available![/bold cyan]\n\n"
31-
"Install the beta:\n"
32-
"[green]pip install --pre together[/green] or "
33-
"[green]uv add together --prerelease allow[/green]\n\n"
33+
"Upgrade to the latest version:\n"
34+
"[green]pip install together --upgrade[/green] or "
35+
"[green]uv sync --upgrade-package together[/green]\n\n"
3436
"New SDK: [link=https://github.com/togethercomputer/together-py]"
3537
"https://github.com/togethercomputer/together-py[/link]\n"
3638
"Migration guide: [link=https://docs.together.ai/docs/pythonv2-migration-guide]"
3739
"https://docs.together.ai/docs/pythonv2-migration-guide[/link]\n\n"
38-
"[dim]This package will be maintained until January 2026.\n"
39-
"Set TOGETHER_NO_BANNER=1 to hide this message.[/dim]",
40+
"[dim]Together V1 is now deprecated and will be maintained in maintanence mode. All new features and development will occur in the 2.0 SDK.[/dim]\n",
4041
title="🚀 New SDK Available",
4142
border_style="cyan",
4243
)

‎src/together/cli/api/endpoints.py‎

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from __future__ import annotations
22

33
import json
4+
import re
45
import sys
56
from functools import wraps
6-
from typing import Any, Callable, Dict, List, Literal, TypeVar, Union
7+
from typing import Any, Callable, Dict, List, Literal, Sequence, TypeVar, Union
78

89
import click
10+
from tabulate import tabulate
911

1012
from together import Together
1113
from together.error import InvalidRequestError
1214
from together.types import DedicatedEndpoint, ListEndpoint
15+
from together.types.endpoints import HardwareWithStatus
1316

1417

1518
def print_endpoint(
@@ -98,7 +101,7 @@ def endpoints(ctx: click.Context) -> None:
98101
)
99102
@click.option(
100103
"--gpu",
101-
type=click.Choice(["h100", "a100", "l40", "l40s", "rtx-6000"]),
104+
type=click.Choice(["b200", "h200", "h100", "a100", "l40", "l40s", "rtx-6000"]),
102105
required=True,
103106
help="GPU type to use for inference",
104107
)
@@ -161,6 +164,8 @@ def create(
161164
"""Create a new dedicated inference endpoint."""
162165
# Map GPU types to their full hardware ID names
163166
gpu_map = {
167+
"b200": "nvidia_b200_180gb_sxm",
168+
"h200": "nvidia_h200_140gb_sxm",
164169
"h100": "nvidia_h100_80gb_sxm",
165170
"a100": "nvidia_a100_80gb_pcie" if gpu_count == 1 else "nvidia_a100_80gb_sxm",
166171
"l40": "nvidia_l40",
@@ -184,12 +189,18 @@ def create(
184189
availability_zone=availability_zone,
185190
)
186191
except InvalidRequestError as e:
187-
print_api_error(e)
188-
if "check the hardware api" in str(e).lower():
192+
if (
193+
"check the hardware api" in str(e.args[0]).lower()
194+
or "invalid hardware provided" in str(e.args[0]).lower()
195+
or "the selected configuration" in str(e.args[0]).lower()
196+
):
197+
click.secho("Invalid hardware selected.", fg="red", err=True)
198+
click.echo("\nAvailable hardware options:")
189199
fetch_and_print_hardware_options(
190200
client=client, model=model, print_json=False, available=True
191201
)
192-
202+
else:
203+
print_api_error(e)
193204
sys.exit(1)
194205

195206
# Print detailed information to stderr
@@ -256,28 +267,85 @@ def hardware(client: Together, model: str | None, json: bool, available: bool) -
256267
fetch_and_print_hardware_options(client, model, json, available)
257268

258269

270+
def _format_hardware_options(
271+
hardware_options: Sequence[HardwareWithStatus],
272+
show_availability: bool = True,
273+
) -> None:
274+
"""Print hardware options in a formatted table using tabulate."""
275+
if not hardware_options:
276+
click.echo(" No hardware options found.", err=True)
277+
return
278+
279+
display_list: List[Dict[str, Any]] = []
280+
281+
for hw in hardware_options:
282+
data: Dict[str, Any] = {
283+
"Hardware ID": hw.id,
284+
"GPU": (
285+
re.sub(r"\-\d+[a-zA-Z][a-zA-Z]$", "", hw.specs.gpu_type)
286+
if hw.specs and hw.specs.gpu_type
287+
else "N/A"
288+
),
289+
"Memory": f"{int(hw.specs.gpu_memory)}GB" if hw.specs else "N/A",
290+
"Count": hw.specs.gpu_count if hw.specs else "N/A",
291+
"Price (per minute)": (
292+
f"${hw.pricing.cents_per_minute / 100:.2f}" if hw.pricing else "N/A"
293+
),
294+
}
295+
296+
if show_availability:
297+
status_display = "—"
298+
if hw.availability:
299+
status = hw.availability.status
300+
# Add visual indicators for status
301+
if status == "available":
302+
status_display = click.style("✓ available", fg="green")
303+
elif status == "unavailable":
304+
status_display = click.style("✗ unavailable", fg="red")
305+
else: # insufficient
306+
status_display = click.style("⚠ insufficient", fg="yellow")
307+
data["Availability"] = status_display
308+
309+
display_list.append(data)
310+
311+
click.echo(tabulate(display_list, headers="keys", numalign="left"))
312+
313+
259314
def fetch_and_print_hardware_options(
260315
client: Together, model: str | None, print_json: bool, available: bool
261316
) -> None:
262317
"""Print hardware options for a model."""
263-
264-
message = "Available hardware options:" if available else "All hardware options:"
265-
click.echo(message, err=True)
266318
hardware_options = client.endpoints.list_hardware(model)
319+
267320
if available:
268321
hardware_options = [
269322
hardware
270323
for hardware in hardware_options
271324
if hardware.availability is not None
272325
and hardware.availability.status == "available"
273326
]
327+
message = (
328+
f"Available hardware options for model '{model}':"
329+
if model
330+
else "Available hardware options:"
331+
)
332+
else:
333+
message = (
334+
f"Hardware options for model '{model}':"
335+
if model
336+
else "All hardware options:"
337+
)
338+
339+
click.echo(message, err=True)
340+
click.echo("", err=True)
274341

275342
if print_json:
276343
json_output = [hardware.model_dump() for hardware in hardware_options]
277344
click.echo(json.dumps(json_output, indent=2))
278345
else:
279-
for hardware in hardware_options:
280-
click.echo(f" {hardware.id}", err=True)
346+
# Show availability column only when model is specified (availability info is only returned with model filter)
347+
show_availability = model is not None
348+
_format_hardware_options(hardware_options, show_availability=show_availability)
281349

282350

283351
@endpoints.command()

‎src/together/cli/api/finetune.py‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import json
4-
import re
54
from datetime import datetime, timezone
65
from textwrap import wrap
76
from typing import Any, Literal
@@ -14,18 +13,11 @@
1413

1514
from together import Together
1615
from together.cli.api.utils import BOOL_WITH_AUTO, INT_WITH_MAX, generate_progress_bar
17-
from together.types.finetune import (
18-
DownloadCheckpointType,
19-
FinetuneEventType,
20-
FinetuneTrainingLimits,
21-
FullTrainingType,
22-
LoRATrainingType,
23-
)
16+
from together.types.finetune import DownloadCheckpointType, FinetuneTrainingLimits
2417
from together.utils import (
2518
finetune_price_to_dollars,
2619
format_timestamp,
2720
log_warn,
28-
log_warn_once,
2921
parse_timestamp,
3022
)
3123

@@ -203,6 +195,12 @@ def fine_tuning(ctx: click.Context) -> None:
203195
help="Whether to mask the user messages in conversational data or prompts in instruction data. "
204196
"`auto` will automatically determine whether to mask the inputs based on the data format.",
205197
)
198+
@click.option(
199+
"--train-vision",
200+
type=bool,
201+
default=False,
202+
help="Whether to train the vision encoder. Only supported for multimodal models.",
203+
)
206204
@click.option(
207205
"--from-checkpoint",
208206
type=str,
@@ -258,6 +256,7 @@ def create(
258256
lora_dropout: float,
259257
lora_alpha: float,
260258
lora_trainable_modules: str,
259+
train_vision: bool,
261260
suffix: str,
262261
wandb_api_key: str,
263262
wandb_base_url: str,
@@ -299,6 +298,7 @@ def create(
299298
lora_dropout=lora_dropout,
300299
lora_alpha=lora_alpha,
301300
lora_trainable_modules=lora_trainable_modules,
301+
train_vision=train_vision,
302302
suffix=suffix,
303303
wandb_api_key=wandb_api_key,
304304
wandb_base_url=wandb_base_url,
@@ -368,6 +368,10 @@ def create(
368368
"You have specified a number of evaluation loops but no validation file."
369369
)
370370

371+
if model_limits.supports_vision:
372+
# Don't show price estimation for multimodal models yet
373+
confirm = True
374+
371375
finetune_price_estimation_result = client.fine_tuning.estimate_price(
372376
training_file=training_file,
373377
validation_file=validation_file,

‎src/together/cli/api/utils.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ def generate_progress_bar(
103103
progress = "Progress: [bold red]unavailable[/bold red]"
104104
if finetune_job.status in COMPLETED_STATUSES:
105105
progress = "Progress: [bold green]completed[/bold green]"
106-
elif finetune_job.updated_at is not None:
106+
elif finetune_job.started_at is not None:
107107
# Replace 'Z' with '+00:00' for Python 3.10 compatibility
108-
updated_at_str = finetune_job.updated_at.replace("Z", "+00:00")
109-
update_at = datetime.fromisoformat(updated_at_str).astimezone()
108+
started_at_str = finetune_job.started_at.replace("Z", "+00:00")
109+
started_at = datetime.fromisoformat(started_at_str).astimezone()
110110

111111
if finetune_job.progress is not None:
112-
if current_time < update_at:
112+
if current_time < started_at:
113113
return progress
114114

115115
if not finetune_job.progress.estimate_available:
@@ -118,7 +118,7 @@ def generate_progress_bar(
118118
if finetune_job.progress.seconds_remaining <= 0:
119119
return progress
120120

121-
elapsed_time = (current_time - update_at).total_seconds()
121+
elapsed_time = (current_time - started_at).total_seconds()
122122
ratio_filled = min(
123123
elapsed_time / finetune_job.progress.seconds_remaining, 1.0
124124
)

‎src/together/constants.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import enum
22

3+
34
# Session constants
45
TIMEOUT_SECS = 600
56
MAX_SESSION_LIFETIME_SECS = 180
@@ -40,6 +41,11 @@
4041
# the number of bytes in a gigabyte, used to convert bytes to GB for readable comparison
4142
NUM_BYTES_IN_GB = 2**30
4243

44+
# Multimodal limits
45+
MAX_IMAGES_PER_EXAMPLE = 10
46+
MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10MB
47+
# Max length = Header length + base64 factor (4/3) * image bytes
48+
MAX_BASE64_IMAGE_LENGTH = len("data:image/jpeg;base64,") + 4 * MAX_IMAGE_BYTES // 3
4349

4450
# expected columns for Parquet files
4551
PARQUET_EXPECTED_COLUMNS = ["input_ids", "attention_mask", "labels"]

‎src/together/resources/audio/speech.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
AudioLanguage,
1111
AudioResponseEncoding,
1212
AudioSpeechStreamChunk,
13-
AudioSpeechStreamEvent,
1413
AudioSpeechStreamResponse,
1514
TogetherClient,
1615
TogetherRequest,

0 commit comments

Comments
 (0)