Skip to content

Python client changes for json inputs - #232

Merged
seanshi-scale merged 19 commits into
hmi-releasefrom
seanshi/json-inputs
Mar 4, 2022
Merged

Python client changes for json inputs#232
seanshi-scale merged 19 commits into
hmi-releasefrom
seanshi/json-inputs

Conversation

@seanshi-scale

@seanshi-scale seanshi-scale commented Feb 26, 2022

Copy link
Copy Markdown
Contributor

Python client handles json/serialized inputs + outputs, also add a bundle_location_fn notion.

Added an EndpointRequest class to encapsulate the options for a request to an endpoint

Comment thread nucleus/deploy/model_endpoint.py Outdated
return inner_url, inner_inference_request
if request.request_id is not None:
request_key = request.request_id
elif request.url is not None:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

maybe this stuff really isn't necessary :P

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wdym?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

there's a lot of logic to try to figure out a reasonable request_id if the user didn't pass one in, e.g. use the url, or the args, which seems "a bit extra"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh ok, as per my other comment (18 hours ago), can we just generate a UUID?

@seanshi-scale seanshi-scale self-assigned this Mar 2, 2022
TASK_FAILURE_STATE = "FAILURE"


class EndpointRequest:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

since we have a function to predict on multiple inputs, I added this so people could pass in a mix of urls/jsons as inputs. Not sure if that'll happen

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might be good to add an EndpointResponse class for symmetry?

@seanshi-scale
seanshi-scale marked this pull request as ready for review March 2, 2022 22:33

@yixu34 yixu34 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't merge yet still commenting

Comment thread nucleus/deploy/client.py Outdated
) -> str:
self,
endpoint_id: str,
url: Optional[str],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Think you typically do = None for Optional stuff.

Comment thread nucleus/deploy/client.py Outdated
A signedUrl that contains a cloudpickled Python object, the result of running inference on the model input
A dictionary with key either "result_url" or "result", depending on the value of `return_pickled`.
If `return_pickled` is true, the key will be "result_url", otherwise the key will be "result".
If the key is "result_url", the value is a signedUrl that contains a cloudpickled Python object,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would group these conditions together:

If `return_pickled` is True, the key will be "result_url" and the value will be a signedUrl that <blah>
If `return_pickled` is False, the key will be "result" and the value is the arbitrary JSON returned by the endpoint's `predict` function.

@seanshi-scale
seanshi-scale requested a review from yixu34 March 2, 2022 23:58
TASK_FAILURE_STATE = "FAILURE"


class EndpointRequest:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might be good to add an EndpointResponse class for symmetry?

Comment thread nucleus/deploy/model_endpoint.py Outdated

def predict(self, url):
return self.client.sync_request(self.endpoint_id, url)
def predict(self, request: EndpointRequest):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Per above, -> EndpointResponse might be a handy result and annotation.

Comment thread nucleus/deploy/model_endpoint.py Outdated
return inner_url, inner_inference_request
if request.request_id is not None:
request_key = request.request_id
elif request.url is not None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wdym?

Comment thread nucleus/deploy/model_endpoint.py Outdated
predict_fn(foo, bar), then the keys in the dictionary should be 'foo' and 'bar'. Values must be native Python
objects.
return_pickled: Whether the output should be a pickled python object, or directly returned serialized json
request_id: A user-specifiable id for requests. Should be unique.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When/why would a user specify this? I was imagining this would be populated by us server-side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The thing this request_id is trying to solve is that a user needs to be able to associate model inference results with a given model input. For example, for the nucleus integration we need to maintain some map from s3URL of input images to task results, so this request_id can be set to that s3URL. Before, I was implicitly using the url as the request_id, but we don't necessarily have urls anymore.

I suppose that we could just keep the association implicitly in the order of some list somewhere but then the user would have to do that too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok it sounds like you're basically using this as a reference_id then? Can we just have our client generate a UUID? Then the nucleus integration (which one day will be server-side) can just maintain that Request -> Response mapping, with the hash key being this UUID.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I left in the ability for clients to specify their own request_ids, but otherwise the client will generate a uuid for the request_id

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would err on the more restrictive side and not allow request_ids, because I'm imagining a hapless user passing in "42" hardcoded and never changing it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

(probably should have specified this but) The uniqueness condition is actually more permissive than "globally unique", but instead is "the EndpointRequests producing a given BatchResponse must have distinct request_ids". I think that mostly gets rid of the hardcoding problem? (i.e. it doesn't matter if the user runs the script twice with the same value of request_id, but if the user hardcodes a value for request_id somewhere and copies the code that would be a problem)

I do think we should allow users to set request_ids though, since otherwise it's much more annoying for users to keep track of an association from EndpointRequest/EndpointResponse to something a user cares about, e.g. if the user wants to run inference on N images, they need to keep track of which EndpointResponse gets associated with which image.

With user-specified request_ids the user can do endpoint_response_for_image_i = job.responses[request_id_for_image_i]; with non-user-specified request_ids the user would need to do

# Go through the list of EndpointRequests to find the request_id corresponding to a given image, comparing either `url` or `args`. Comparing values seems reasonable in the `url` case but ugly in the `args` case.
# job.responses[the request_id we found]

or use the ordering of EndpointRequest fed in and the corresponding ordering of EndpointResponses (currently EndpointResponses are returned in a dictionary though)
(or something I haven't thought of)

One simple thing we could do to catch errors earlier is just to check that the user passes a set of distinct request_ids to the batch_predict function at the very least

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh I see. I think this is happening because the client library is still operating under the paradigm where batch jobs are tied to endpoints instead of being standalone. In the eventual state, I would expect the batch request to be its own class (not a predict_batch method off of an endpoint class), and result would be a url to a file/directory that contains outputs, with the relative order not being guaranteed anyway. In other words, this is needed only because have to support the current janky client-side integration. In that case I guess this is fine, but prior to launch I definitely would want request_id to be gone altogether from the client, so please add a TODO.

I was tempted to suggest that we call this reference_id, but that's misleading in a different way (Nucleus actually persists those, so for us to use them as ephemeral client-side IDs could be confusing due to inconsistency).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah it's pretty much there because of the async batch class

def __init__(
self,
url: Optional[str] = None,
args: Optional[Dict] = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wonder if we should have these two things be a Union (we'd still have to do the XOR check because Union is really just a hint, from a type annotation perspective).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I sorta am leaning away from putting the two into a Union. We'd have to keep track of whether the data represents a url or args (which we technically could do by looking at their types, but this feels wrong), and keeping them separate seems like the easiest way to do this. The stuff stored at url/args gets put into separate fields in the eventual http request's json anyway, so I think it's simpler to keep things separate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yup that makes sense.

Comment thread nucleus/deploy/model_endpoint.py Outdated
return_pickled: Optional[bool] = True,
request_id: Optional[str] = None,
):
if url is None and args is None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Related to the Union comment, think this is happening enough where we'd want a helper function.

@seanshi-scale
seanshi-scale requested a review from yixu34 March 3, 2022 20:46
Comment thread nucleus/deploy/nucleus_integration.py Outdated
def __init__(
self,
hmi_async_job: AsyncModelEndpointResponse,
hmi_async_job: AsyncModelEndpointBatchResponse,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not really part of this PR but maybe we can take a pass at removing all "hmi" stuff from user-facing names?

@seanshi-scale
seanshi-scale merged commit e1ec1fa into hmi-release Mar 4, 2022
@seanshi-scale
seanshi-scale deleted the seanshi/json-inputs branch March 4, 2022 23:06
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.

3 participants