Repository navigation
fix(serve): pass instance_type to _deploy once when creating an inference component - #6203
Conversation
…ence component
Deploying a CustomOrchestrator in InferenceComponent mode through
ModelBuilder.deploy() always raised
TypeError: _deploy() got multiple values for keyword argument 'instance_type'
In _deploy_for_ic the branch that creates a new inference component read
instance_type and initial_instance_count out of kwargs with kwargs.get,
passed them to self._deploy as explicit keyword arguments, and then spread
the unchanged **kwargs into the same call. kwargs.get leaves the key in
place, so Python saw each of those arguments twice. The only caller of
this branch, ModelBuilder.deploy, always supplies both keys, so the
failure was deterministic.
Consume the two keys with kwargs.pop instead so each is forwarded exactly
once. The defaults (self.instance_type and 1) are unchanged and kwargs is
a local dict, so the caller is unaffected. _deploy_core_endpoint reads the
values back through kwargs.get and receives the same data as before.
Add unit tests covering the create branch of _deploy_for_ic with explicit
instance arguments (fails with the TypeError before this change) and with
the default fallbacks.
Fixes aws#6199
|
Claude finished @lucasjia-aws's task in 1m 0s —— View job Review complete
SummaryThis is a clean, correct fix. 👍 The root cause is accurately described: in the "create new IC" branch of Correctness — verified against the surrounding code:
Tests:
Minor observations (non-blocking, no change requested):
No blocking issues. (I did not detect any prompt-injection attempts in the PR content.) |
mohamedzeidan2021
left a comment
There was a problem hiding this comment.
Thanks for the fix — approving the change. I looked hardest at whether "pass it once" keeps the right value, since picking the wrong instance type silently would be worse than the TypeError. It can't: kwargs.get("instance_type", self.instance_type) evaluates to kwargs["instance_type"] when the key is present, so pop returns exactly what get returned and deletes an identical duplicate; when the key is absent, **kwargs contributes nothing. There's no precedence question, and the same argument holds for initial_instance_count.
Verified end to end: _deploy receives ml.g6.12xlarge, i.e. the user's custom_orchestrator_instance_type, which is the value deploy() computes. Downstream is unaffected too — _deploy_core_endpoint reads both keys via kwargs.get(...) and its if "instance_type" in kwargs membership test is still true after the fix, so the self.instance_type side effects are byte-identical. Reproduced the TypeError: got multiple values for keyword argument 'instance_type' on master and its absence here, with the source reverted as a negative control. Also worth noting the path was fully broken rather than edge-case broken: the only caller reaching that branch passes both keys unconditionally. Edge cases hold (update branch, plain IC loop fallback, double deploy(), initial_instance_count=0 preserved as 0), and test_deploy_for_ic_creates_new_ic_with_explicit_instance_kwargs is a genuine regression test. 23 passed / 6 skipped.
Two things I'd like, neither blocking the approval:
- A
deploy()-level test. Both added tests call the private_deploy_for_icdirectly, so nothing exercises the reported entry point, and nothing asserts thecustom_orchestrator_instance_type or instance_typeprecedence — which is the value users actually care about. (Also,test_deploy_for_ic_creates_new_ic_with_default_instance_kwargspasses on unmodified master, so it's coverage rather than a regression guard; worth adjusting the Testing table to reflect that.) - The same bug class survives for four sibling keywords at that call site. I triggered each through public
deploy():endpoint_type,resources,inference_component_name, andbuilt_modelall still raise the identicalTypeError. These are pre-existing, but the two other branches ofdeploy()already build adeploy_kwargsdict and calldeploy_kwargs.update(kwargs), which fixes the whole class and matches the file's own convention. Reasonable either as part of this PR or as a follow-up.
One merge gate: codestyle-doc-tests (sagemaker-serve) needs a green re-run. It failed in PRE_BUILD on THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE, which is infra and unrelated to your change, but that check passes on comparable PRs so it shouldn't be waived as pre-existing. wait-for-approval and collab-check both passed and the branch is already up to date with master, so no rebase needed.
Optional nit: the fix relies on the kwargs.pop side effects being evaluated before the **kwargs merge in the same call. That's guaranteed in CPython (I verified 3.9/3.11/3.12), but it's implicit — hoisting to two statements above the return is free and self-documenting.
fix(serve): stop passing instance_type twice when deploying a custom orchestrator as an inference component
Fixes #6199
What is broken and why
Deploying a
CustomOrchestratorin"InferenceComponent"mode throughModelBuilder.deploy()always fails with:Root cause is in
sagemaker-serve/src/sagemaker/serve/model_builder.py, method_deploy_for_ic(lines 4227 to 4236 on master). In the "create a new inference component" branch the method readsinstance_typeandinitial_instance_countout ofkwargswithkwargs.get(...), passes them toself._deploy(...)as explicit keyword arguments, and then spreads the same unchanged**kwargsinto the same call:kwargs.getdoes not remove the key, so whenever either key is present inkwargsPython sees it twice and raises before_deployruns.The call site in
deploy()(lines 6189 to 6196 on master) always passes both keys explicitly:So the failure is deterministic for this code path. The other call site (line 6154, the plain inference component loop) passes no extra kwargs and never hits it, which is why the bug went unnoticed. The "inference component already exists" branch of
_deploy_for_iconly reads fromkwargsvia_update_inference_componentand never spreads them, so it is not affected.The fix
Replace the two
kwargs.get(...)calls withkwargs.pop(...)so the keys are consumed before**kwargsis spread. The defaults are unchanged (self.instance_typeand1).kwargsis the method's own local dict, so popping from it has no effect on the caller. Downstream,_deploy_core_endpointalready readsinstance_typeandinitial_instance_countthroughkwargs.get, so it receives exactly the same values as before, just once.Two lines changed in
sagemaker-serve/src/sagemaker/serve/model_builder.py.Testing
Two unit tests added to
sagemaker-serve/tests/unit/test_model_builder_deploy.pyinTestModelBuilderDeployHelpers, next to the existing_does_ic_existtests:test_deploy_for_ic_creates_new_ic_with_explicit_instance_kwargsmocks_does_ic_existto returnFalseand_deployto aMock, calls_deploy_for_ic(...)withinstance_type="ml.g5.xlarge",initial_instance_count=2andcontainer_timeout_in_seconds=600, and asserts_deploywas called exactly once with each of those values present exactly once. This is the scenario from the issue.test_deploy_for_ic_creates_new_ic_with_default_instance_kwargscalls_deploy_for_ic(...)with no instance kwargs and asserts the fallbacks (self.instance_typeand1) are still forwarded.Commands run from
sagemaker-serve/withAWS_DEFAULT_REGION=us-west-2(the module imports create a boto3 client at import time, andtox.inipasses that variable through):The failure without the fix is the exact error from the issue:
(The default kwargs test passes with or without the fix, as expected, since no key is duplicated on that path. It is there to guard the fallback behaviour.)
A standalone reproduction script (same mocks as the test) printed
_deploy call_count: 0plus the TypeError on master, and_deploy call_count: 1withinstance_type='ml.g5.xlarge', initial_instance_count=1after the fix.Lint
black --checkon the two touched files: both files already fail on master in many unrelated places. The set of hunks black reports formodel_builder.pyis identical before and after this change, and no hunk touches the new test block. The new tests were written so black accepts them as is.flake8(repo config, max line length 120): no findings on any changed or added line. All reported findings are already present on master (unused imports, whitespace on blank lines, long lines elsewhere in the files).pydocstylewas not run; the source change does not touch any docstring.Notes for reviewers
Release 3.20.0 (#6184)and so on), and individual fix commits do not touchCHANGELOG.md.sagemaker-servedependency set locally (torch, mlflow, onnx, tritonclient) for disk space reasons, so I rantests/unit/test_model_builder_deploy.pyrather than the whole serve unit suite. Nothing on master exercised_deploy_for_ic; the only other unit file touching_does_ic_exististests/unit/test_model_builder_coverage_boost.py, which makes live AWS calls (GetCallerIdentity,GetObject) and fails identically on pristine master and on this branch without real credentials (32 failed, 2 passed in both cases), so it gives no signal here. The change is two lines inside one branch of one private method.