Skip to content

Conversation

@newokaerinasai
Copy link
Contributor

Have you read the Contributing Guidelines?

Issue #

Describe your changes

This adds the price estimation functionality to the client. This also adds a warning if the estimated price is too high.

@newokaerinasai newokaerinasai marked this pull request as ready for review November 25, 2025 13:22
"greater than your current credit limit and balance. "
"It will likely fail due to insufficient funds. "
"Please consider increasing your credit limit at https://api.together.xyz/settings/profile\n"
"You can pass `-y` or `--confirm` to your command to skip this message.\n\n"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also warn that a job will fail anyway? And not like execute with negative balance

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wdym? There's "It will likely fail due to insufficient funds" is above?

Returns:
FinetunePriceEstimationResponse: Object containing the estimated price.
"""
training_type_cls: TrainingType | None = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to set them None here, as they're defined below in all branches (or exception). You can keep type definition if you want to

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mypy complains

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bit weird, all the possible branches are covered below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it complain when you don't define the type or when you don't set it to None?

model: str
n_epochs: int | None = None
n_evals: int | None = None
training_type: TrainingType | None = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be none? Same goes for n_epoch and n_evals?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be None. If I implement it in any other way, it would be an unrelated change -- I'd just need to change it for all other data types for FT

self,
*,
training_file: str,
model: str | None,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these fields be none?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can in principle, but the price estimation API doesn't seem to support it. Therefore, we need to fix the API, disable calling the estimation when model is None, but set this field to always be defined (i.e., remove the None option)

Copy link
Member

@mryab mryab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to supplement the client changes with some simple unit tests!

Comment on lines 50 to 51
"It will likely fail due to insufficient funds. "
"Please proceed at your own risk."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"It will likely fail due to insufficient funds. "
"Please proceed at your own risk."
"It will likely get cancelled due to insufficient funds. "
"Proceed at your own risk."

Returns:
FinetunePriceEstimationResponse: Object containing the estimated price.
"""
training_type_cls: TrainingType | None = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it complain when you don't define the type or when you don't set it to None?

@artek0chumak artek0chumak self-requested a review December 1, 2025 10:55
hf_api_token=hf_api_token,
hf_output_repo_name=hf_output_repo_name,
)
if from_checkpoint is None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if from_checkpoint is None:
if from_checkpoint is None and from_hf_model is None:

I don't think you support from_hf_model either (estimated price will be incorect due to wrong parameter count)

Copy link

@nikita-smetanin nikita-smetanin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Estimates the price of a fine-tuning job
Args:
request (FinetunePriceEstimationRequest): Request object containing the parameters for the price estimation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring is not consistent with the actual arguments

Estimates the price of a fine-tuning job
Args:
request (FinetunePriceEstimationRequest): Request object containing the parameters for the price estimation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Comment on lines 1059 to 1066
# unsupported case
price_estimation_result = FinetunePriceEstimationResponse(
estimated_total_price=0.0,
allowed_to_proceed=True,
estimated_train_token_count=0,
estimated_eval_token_count=0,
user_limit=0.0,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would create a separate variable price_limit_passed and assign price_limit_passed = price_estimation_result.allowed_to_proceed when there is no CFT but just price_limit_passed = True in the unsupported case. This would prevent creating a dummy object with arbitrary values across the file

)

assert mock_requestor.request.call_count == 3
assert response.id == "ft-12345678-1234-1234-1234-1234567890ab"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's extract this into a constant perhaps? It's used 3 times across the file, might easily diverge or not get used in future tests

Comment on lines 65 to 66
else:
return (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to explicitly check that it's equal to /limits (since this is what we return here) and raise ValueError if none of the options would match. A dispatch table pattern is a good option here

Comment on lines 395 to 408
mocker.return_value = (
TogetherResponse(
data={
"estimated_total_price": 100,
"allowed_to_proceed": True,
"estimated_train_token_count": 1000,
"estimated_eval_token_count": 100,
"user_limit": 1000,
},
headers={},
),
None,
None,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it feels weird that we can't reuse mock_request here and have to define the mocking logic once again. I wonder if we can generalize somehow…

@newokaerinasai newokaerinasai merged commit e465a0e into main Dec 4, 2025
11 checks passed
@newokaerinasai newokaerinasai deleted the ruslan/price-estimation branch December 4, 2025 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants