agentos-project/agentos

`pcs.Component.body()` should not do a deepcopy of all attributes

Closed this issue · 0 comments

andyk commented

This comes up when we set the Output.return_value attribute to a Tensorflow graph output, because that result output does not support deepcopy() (see test failure output below). I'm currently working around this by having Output test that a return_value supports deepcopy(), but this is a hacky workaround. Components should not need to have their registered attributes support deepcopy() since they [should just] stringify all attributes with a type that is not in Component.OK_LEAF_ATTR_TYPES.

Currently, Component.body() makes a deep copy of each attribute and then searches and replaces leaf nodes that are not instances of either Component or a type in Component.OK_LEAF_ATTR_TYPES.

...
   def body(self, dependencies_as_strings=False) -> Dict:
        attributes = {}
        for name in self._spec_attr_names:
            attr = {name: copy.deepcopy(getattr(self, name, None))}
...

We only need to do a deepcopy() here because find_and_replace_leaves() is written in a such a way as to mutate the data structure passed into it rather than to create a new data structure with the transformed data. This isn't fundamental and find_and_replace_leaves() can be rewritten to (or a new similar function added to) support the copy_and_replace() functionality.

Ultimately, we should consider using more of YAML or Pickle's built-in functionality for serializing our Components to Specs (e.g. Pickle already supports a way to register per-type handlers https://docs.python.org/3/library/copyreg.html for recursive serialization).

FAILED                                 [100%]Running the following: run sb3_agent --registry-file /Users/andyk/Development/agentos/example_agents/sb3_agent/components.yaml --function-name learn --arg-set-kwargs {"total_timesteps": 100}

tests/example_agents/test_sb3.py:16 (test_sb3_agent_learn)
cli_runner = <click.testing.CliRunner object at 0x131193370>

    def test_sb3_agent_learn(cli_runner):
        kwargs = {k: v for k, v in test_kwargs.items()}
        kwargs["--function-name"] = "learn"
        kwargs["--arg-set-kwargs"] = '{"total_timesteps": 100}'
>       run_test_command(cli_runner, run, cli_args=test_args, cli_kwargs=kwargs)

test_sb3.py:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../utils.py:32: in run_test_command
    result = cli_runner.invoke(
../conftest.py:26: in wrapper
    result = f(*args, **kwargs)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/site-packages/click/testing.py:408: in invoke
    return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/site-packages/click/core.py:1062: in main
    rv = self.invoke(ctx)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/site-packages/click/core.py:1404: in invoke
    return ctx.invoke(self.callback, **ctx.params)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/site-packages/click/core.py:763: in invoke
    return __callback(*args, **kwargs)
../../agentos/cli.py:187: in run
    output = comp.run_with_arg_set(
../../pcs/object_manager.py:104: in run_with_arg_set
    output.log_return_value(res, return_value_log_format)
../../pcs/output.py:149: in log_return_value
    filename_base = tmp_dir_path / (self.identifier + "-return_value")
../../pcs/component.py:105: in identifier
    return self.sha1()
../../pcs/component.py:80: in sha1
    body = self.body(dependencies_as_strings=True)
../../pcs/component.py:110: in body
    attr = {name: copy.deepcopy(getattr(self, name, None))}
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:172: in deepcopy
    y = _reconstruct(x, memo, *rv)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:270: in _reconstruct
    state = deepcopy(state, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:146: in deepcopy
    y = copier(x, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:230: in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
/opt/homebrew/Caskroom/miniforge/base/envs/agentos_dev2/lib/python3.9/copy.py:153: in deepcopy
    y = copier(memo)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = tensor([[-1.1172, -0.3963],
        [-0.4330, -1.0458],
        [-0.4053, -1.0990],
        [-0.5333, -0.8836],
      ... -0.6189],
        [-0.4361, -1.0400],
        [-0.5812, -0.8192],
        [-0.3943, -1.1214]], grad_fn=<SubBackward0>)
memo = {5120478272: <pcs.command.Command object at 0x16acdc400>, 5118831168: {'_spec_attr_names': ['type', 'component', 'func...ent_id', 'info', 'data', 'command']}, <pcs.output.Output object at 0x131338af0>, ['type', 'module', 'name'], ...], ...}

    def __deepcopy__(self, memo):
        if has_torch_function_unary(self):
            return handle_torch_function(Tensor.__deepcopy__, (self,), self, memo)
        if not self.is_leaf:
>           raise RuntimeError("Only Tensors created explicitly by the user "
                               "(graph leaves) support the deepcopy protocol at the moment")
E           RuntimeError: Only Tensors created explicitly by the user (graph leaves) support the deepcopy protocol at the moment

../../../../.agentos/cache/requirements_cache/python3.9/418783088367885193b618330b455aabbc5f7808da7ddf362f72d8a2017108f0/lib/python3.9/site-packages/torch/_tensor.py:86: RuntimeError