-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for dynamics randomization #61
Comments
Here's my proof of concept for a simple XPath-based implementation. Immediate observations
Benchmark results (MacBook Pro w/ 2 Core i5)(venv) kolmogorov:mjtest ryan$ time python bench.py
real 0m21.990s
user 0m20.750s
sys 0m0.786s (venv) kolmogorov:mjtest ryan$ time python no_mutation.py
real 0m11.601s
user 0m11.401s
sys 0m0.154s CodeXPath Implementation#!/usr/bin/env python3
"""
Benchmark model mutation for dynamics randomization
"""
import os
import os.path as osp
import xml.etree.ElementTree as ET
import numpy as np
from mujoco_py import load_model_from_xml
from mujoco_py import MjSim
from mujoco_py import MjViewer
MUJOCO_PY_PATH = "/Users/ryan/code/mujoco-py/"
TOSSER_XML = osp.join(MUJOCO_PY_PATH, "xmls/tosser.xml")
# Load original model text into memory
tosser = ET.parse(TOSSER_XML)
variants = {
"a1": {
"xpath": ".//motor[@name='a1']",
"elem": None,
"attrib": "gear",
"default": None,
"method": "coefficient",
"distribution": "uniform",
"range": (0.5, 1.5),
},
"a2": {
"xpath": ".//motor[@name='a2']",
"elem": None,
"attrib": "gear",
"default": None,
"method": "coefficient",
"distribution": "uniform",
"range": (0.5, 1.5),
},
"wr_js": {
"xpath": ".//joint[@name='wr_js']",
"elem": None,
"attrib": "damping",
"default": None,
"method": "absolute",
"distribution": "uniform",
"range": (5, 15),
},
}
# Retrieve defaults and cache etree elems
for k, v in variants.items():
e = tosser.find(v["xpath"])
v["elem"] = e
v["default"] = float(e.attrib[v["attrib"]])
for _ in range(1000):
# Mutate model randomly
for k, v in variants.items():
e = v["elem"]
if v["method"] == "coefficient":
c = np.random.uniform(low=v["range"][0], high=v["range"][1])
e.attrib[v["attrib"]] = str(c * v["default"])
elif v["method"] == "absolute":
c = np.random.uniform(low=v["range"][0], high=v["range"][1])
e.attrib[v["attrib"]] = str(c)
else:
raise NotImplementedError("Unknown method")
# Reify model
model_xml = ET.tostring(tosser.getroot()).decode("ascii")
# Run model loop
model = load_model_from_xml(model_xml)
sim = MjSim(model)
#viewer = MjViewer(sim)
#sim_state = sim.get_state()
#sim.set_state(sim_state)
for i in range(1000):
if i < 150:
sim.data.ctrl[:] = 0.0
else:
sim.data.ctrl[:] = -1.0
sim.step()
#viewer.render() Baseline without mutation#!/usr/bin/env python3
"""
Benchmark model mutation for dynamics randomization
No mutation baseline.
"""
import os.path as osp
from mujoco_py import load_model_from_path
from mujoco_py import MjSim
from mujoco_py import MjViewer
MUJOCO_PY_PATH = "/Users/ryan/code/mujoco-py/"
TOSSER_XML = osp.join(MUJOCO_PY_PATH, "xmls/tosser.xml")
# Load model
model = load_model_from_path(TOSSER_XML)
sim = MjSim(model)
#viewer = MjViewer(sim)
sim_state = sim.get_state()
# Model loop
for _ in range(1000):
sim.set_state(sim_state)
for i in range(1000):
if i < 150:
sim.data.ctrl[:] = 0.0
else:
sim.data.ctrl[:] = -1.0
sim.step()
#viewer.render() |
Profiler ResultsThe overhead is spread out between elements:
I speculate the that NumPy portions can probably be optimized to be negligible or removed entirely. Python's XML implementation is known to be non-optimal, and people looking for performance usually use lxml. The MuJoCo portions are a black box and probably not directly optimizable. For this I have 3 ideas:
with mutation(venv) kolmogorov:mjtest ryan$ python -m cProfile -s cumulative bench.py | head -40
5534300 function calls (5476484 primitive calls) in 22.939 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
317/1 0.010 0.000 22.940 22.940 {built-in method builtins.exec}
1 0.001 0.001 22.940 22.940 bench.py:4(<module>)
1 3.297 3.297 22.403 22.403 bench.py:46(main)
1000000 11.910 0.000 11.910 0.000 {method 'step' of 'mujoco_py.cymj.MjSim' objects}
1000 4.195 0.004 5.034 0.005 {mujoco_py.cymj.load_model_from_xml}
837000 0.173 0.000 1.507 0.000 numeric.py:424(asarray)
837076 1.251 0.000 1.335 0.000 {built-in method numpy.core.multiarray.array}
1000 0.010 0.000 1.006 0.001 ElementTree.py:1119(tostring)
1000 0.010 0.000 0.994 0.001 ElementTree.py:720(write)
17 0.000 0.000 0.862 0.051 __init__.py:1(<module>)
51000/1000 0.321 0.000 0.690 0.001 ElementTree.py:898(_serialize_xml)
357/4 0.002 0.000 0.536 0.134 <frozen importlib._bootstrap>:966(_find_and_load)
357/4 0.001 0.000 0.536 0.134 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
335/6 0.001 0.000 0.535 0.089 <frozen importlib._bootstrap>:651(_load_unlocked)
286/6 0.001 0.000 0.535 0.089 <frozen importlib._bootstrap_external>:672(exec_module)
470/5 0.000 0.000 0.535 0.107 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
400/44 0.000 0.000 0.348 0.008 {built-in method builtins.__import__}
1000 0.007 0.000 0.243 0.000 tempfile.py:522(NamedTemporaryFile)
1 0.000 0.000 0.240 0.240 builder.py:1(<module>)
1000 0.114 0.000 0.216 0.000 ElementTree.py:837(_namespaces)
1000 0.009 0.000 0.201 0.000 tempfile.py:249(_mkstemp_inner)
359000 0.161 0.000 0.189 0.000 {method 'write' of '_io.TextIOWrapper' objects}
1301/350 0.001 0.000 0.170 0.000 <frozen importlib._bootstrap>:997(_handle_fromlist)
1000 0.003 0.000 0.140 0.000 tempfile.py:500(__exit__)
4 0.000 0.000 0.138 0.034 __init__.py:15(<module>)
1001 0.135 0.000 0.135 0.000 {built-in method posix.open}
326/299 0.001 0.000 0.134 0.000 <frozen importlib._bootstrap>:564(module_from_spec)
1000 0.133 0.000 0.133 0.000 {method '__exit__' of '_io._IOBase' objects}
947606/947605 0.126 0.000 0.132 0.000 {built-in method builtins.isinstance}
1 0.000 0.000 0.127 0.127 mjviewer.py:1(<module>)
36/30 0.000 0.000 0.127 0.004 <frozen importlib._bootstrap_external>:919(create_module)
36/30 0.056 0.002 0.127 0.004 {built-in method _imp.create_dynamic}
1 0.000 0.000 0.116 0.116 __init__.py:106(<module>)
1 0.000 0.000 0.113 0.113 Distutils.py:1(<module>)
1 0.000 0.000 0.113 0.113 build_ext.py:1(<module>) no mutation(venv) kolmogorov:mjtest ryan$ python -m cProfile -s cumulative no_mutation.py | head -40
1510688 function calls (1502989 primitive calls) in 11.571 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
313/1 0.010 0.000 11.572 11.572 {built-in method builtins.exec}
1 0.001 0.001 11.572 11.572 no_mutation.py:6(<module>)
1 0.959 0.959 11.016 11.016 no_mutation.py:17(main)
1000000 10.025 0.000 10.025 0.000 {method 'step' of 'mujoco_py.cymj.MjSim' objects}
16 0.001 0.000 0.983 0.061 __init__.py:1(<module>)
352/1 0.002 0.000 0.555 0.555 <frozen importlib._bootstrap>:966(_find_and_load)
352/1 0.001 0.000 0.555 0.555 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
330/1 0.001 0.000 0.554 0.554 <frozen importlib._bootstrap>:651(_load_unlocked)
282/1 0.001 0.000 0.554 0.554 <frozen importlib._bootstrap_external>:672(exec_module)
462/1 0.000 0.000 0.554 0.554 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
1 0.000 0.000 0.378 0.378 builder.py:1(<module>)
397/42 0.000 0.000 0.339 0.008 {built-in method builtins.__import__}
1298/382 0.001 0.000 0.163 0.000 <frozen importlib._bootstrap>:997(_handle_fromlist)
321/294 0.001 0.000 0.140 0.000 <frozen importlib._bootstrap>:564(module_from_spec)
4 0.000 0.000 0.139 0.035 __init__.py:15(<module>)
35/30 0.000 0.000 0.132 0.004 <frozen importlib._bootstrap_external>:919(create_module)
35/30 0.061 0.002 0.132 0.004 {built-in method _imp.create_dynamic}
1 0.000 0.000 0.128 0.128 mjviewer.py:1(<module>)
1 0.000 0.000 0.110 0.110 Distutils.py:1(<module>)
1 0.000 0.000 0.110 0.110 __init__.py:6(<module>)
1 0.000 0.000 0.110 0.110 build_ext.py:1(<module>)
1 0.000 0.000 0.108 0.108 util.py:6(<module>)
1 0.000 0.000 0.107 0.107 __init__.py:16(<module>)
1 0.000 0.000 0.106 0.106 old_build_ext.py:7(<module>)
1 0.000 0.000 0.106 0.106 __init__.py:106(<module>)
3 0.000 0.000 0.100 0.033 old_build_ext.py:28(_check_stack)
3 0.000 0.000 0.100 0.033 inspect.py:1464(getouterframes)
162 0.000 0.000 0.100 0.001 inspect.py:1425(getframeinfo)
441/162 0.001 0.000 0.098 0.001 inspect.py:680(getsourcefile)
114 0.019 0.000 0.095 0.001 inspect.py:714(getmodule)
1 0.000 0.000 0.084 0.084 add_newdocs.py:10(<module>)
284/272 0.000 0.000 0.070 0.000 <frozen importlib._bootstrap_external>:393(_check_name_wrapper)
2 0.000 0.000 0.070 0.035 <frozen importlib._bootstrap>:674(_load)
1 0.000 0.000 0.070 0.070 builder.py:43(load_cython_ext)
1 0.000 0.000 0.070 0.070 builder.py:103(load_dynamic_ext) |
Updated profiler resultsI increased the runtime by 5x to chase out fixed overhead like imports. I was using my machine for other stuff while running these, which explains the variance in results. Conclusions:
I would be curious to see how a multithreaded implementation shakes out, since I'm not sure what synchronization is inside MuJoCo. If there is no locking overhead, in principle you could create an n-length queue with a mutator module as the source and the environment as the sink. Then the implementations would be like. Mutator: q = collections.dequeue(10)
while select(not q.is_full()):
q.push(make_new_mutation()) Dynamics Randomization Environment: class MuJoCoDynamicsRandomizationEnv:
def reset():
self._sim.close()
self._sim = q.pop()
self._sim.reset() Profiler resultsreplace xml with lxml
(venv) kolmogorov:mjtest ryan$ python -m cProfile -s cumulative bench_lxml_nonumpy.py | head -40
14762814 function calls (14754802 primitive calls) in 111.095 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
314/1 0.010 0.000 111.095 111.095 {built-in method builtins.exec}
1 0.001 0.001 111.095 111.095 bench_lxml_nonumpy.py:4(<module>)
1 18.594 18.594 110.554 110.554 bench_lxml_nonumpy.py:47(main)
5000000 61.572 0.000 61.572 0.000 {method 'step' of 'mujoco_py.cymj.MjSim' objects}
5000 20.672 0.004 24.982 0.005 {mujoco_py.cymj.load_model_from_xml}
4185000 0.950 0.000 7.177 0.000 numeric.py:424(asarray)
4185076 5.785 0.000 6.227 0.000 {built-in method numpy.core.multiarray.array}
5000 0.038 0.000 1.440 0.000 tempfile.py:522(NamedTemporaryFile)
5000 0.052 0.000 1.213 0.000 tempfile.py:249(_mkstemp_inner)
5001 0.833 0.000 0.833 0.000 {built-in method posix.open}
16 0.000 0.000 0.826 0.052 __init__.py:1(<module>)
5000 0.013 0.000 0.565 0.000 tempfile.py:500(__exit__)
359/4 0.002 0.000 0.541 0.135 <frozen importlib._bootstrap>:966(_find_and_load)
359/4 0.001 0.000 0.541 0.135 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
333/4 0.001 0.000 0.540 0.135 <frozen importlib._bootstrap>:651(_load_unlocked)
469/4 0.000 0.000 0.540 0.135 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
5000 0.533 0.000 0.533 0.000 {method '__exit__' of '_io._IOBase' objects}
283/5 0.001 0.000 0.527 0.105 <frozen importlib._bootstrap_external>:672(exec_module)
5000 0.027 0.000 0.442 0.000 _internal.py:447(_dtype_from_pep3118)
399/43 0.000 0.000 0.357 0.008 {built-in method builtins.__import__}
10000 0.005 0.000 0.244 0.000 tempfile.py:481(func_wrapper)
6126/6067 0.176 0.000 0.240 0.000 {built-in method builtins.__build_class__}
1 0.000 0.000 0.232 0.232 builder.py:1(<module>)
5000 0.137 0.000 0.231 0.000 _internal.py:490(__dtype_from_pep3118)
5000 0.230 0.000 0.230 0.000 {method 'flush' of '_io.BufferedRandom' objects}
10000 0.051 0.000 0.190 0.000 tempfile.py:473(__getattr__)
1308/316 0.001 0.000 0.187 0.001 <frozen importlib._bootstrap>:997(_handle_fromlist)
5001 0.012 0.000 0.182 0.000 {built-in method builtins.next}
5001 0.026 0.000 0.170 0.000 tempfile.py:157(__next__)
324/284 0.001 0.000 0.157 0.001 <frozen importlib._bootstrap>:564(module_from_spec)
37/29 0.000 0.000 0.151 0.005 <frozen importlib._bootstrap_external>:919(create_module)
37/29 0.061 0.002 0.150 0.005 {built-in method _imp.create_dynamic}
4 0.000 0.000 0.135 0.034 __init__.py:15(<module>)
5006 0.127 0.000 0.127 0.000 {built-in method io.open}
1 0.000 0.000 0.125 0.125 mjviewer.py:1(<module>) naive implementation
(venv) kolmogorov:mjtest ryan$ python -m cProfile -s cumulative bench.py | head -40
25666109 function calls (25408293 primitive calls) in 115.223 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
317/1 0.010 0.000 115.224 115.224 {built-in method builtins.exec}
1 0.001 0.001 115.224 115.224 bench.py:4(<module>)
1 17.506 17.506 114.674 114.674 bench.py:46(main)
5000000 60.450 0.000 60.450 0.000 {method 'step' of 'mujoco_py.cymj.MjSim' objects}
5000 21.251 0.004 25.600 0.005 {mujoco_py.cymj.load_model_from_xml}
4185000 0.879 0.000 7.764 0.000 numeric.py:424(asarray)
4185076 6.462 0.000 6.885 0.000 {built-in method numpy.core.multiarray.array}
5000 0.050 0.000 5.139 0.001 ElementTree.py:1119(tostring)
5000 0.049 0.000 5.079 0.001 ElementTree.py:720(write)
255000/5000 1.648 0.000 3.542 0.001 ElementTree.py:898(_serialize_xml)
5000 0.033 0.000 1.451 0.000 tempfile.py:522(NamedTemporaryFile)
5000 0.047 0.000 1.256 0.000 tempfile.py:249(_mkstemp_inner)
5000 0.580 0.000 1.097 0.000 ElementTree.py:837(_namespaces)
1795000 0.827 0.000 0.973 0.000 {method 'write' of '_io.TextIOWrapper' objects}
5001 0.911 0.000 0.911 0.000 {built-in method posix.open}
17 0.000 0.000 0.866 0.051 __init__.py:1(<module>)
4483642/4483641 0.600 0.000 0.628 0.000 {built-in method builtins.isinstance}
5000 0.013 0.000 0.582 0.000 tempfile.py:500(__exit__)
357/4 0.002 0.000 0.552 0.138 <frozen importlib._bootstrap>:966(_find_and_load)
357/4 0.001 0.000 0.552 0.138 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
5000 0.551 0.000 0.551 0.000 {method '__exit__' of '_io._IOBase' objects}
335/6 0.001 0.000 0.550 0.092 <frozen importlib._bootstrap>:651(_load_unlocked)
286/6 0.001 0.000 0.550 0.092 <frozen importlib._bootstrap_external>:672(exec_module)
470/5 0.000 0.000 0.547 0.109 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
15004 0.023 0.000 0.488 0.000 {built-in method builtins.next}
5000 0.028 0.000 0.424 0.000 _internal.py:447(_dtype_from_pep3118)
400/44 0.000 0.000 0.354 0.008 {built-in method builtins.__import__}
905000 0.340 0.000 0.340 0.000 ElementTree.py:1076(_escape_attrib)
10000 0.113 0.000 0.315 0.000 ElementTree.py:785(_get_writer)
6141/6082 0.190 0.000 0.255 0.000 {built-in method builtins.__build_class__}
10000 0.005 0.000 0.248 0.000 tempfile.py:481(func_wrapper)
1 0.000 0.000 0.242 0.242 builder.py:1(<module>)
5000 0.236 0.000 0.236 0.000 {method 'flush' of '_io.BufferedRandom' objects}
5000 0.006 0.000 0.218 0.000 contextlib.py:79(__enter__)
5000 0.117 0.000 0.197 0.000 _internal.py:490(__dtype_from_pep3118) baseline
(venv) kolmogorov:mjtest ryan$ python -m cProfile -s cumulative no_mutation.py | head -40
5571226 function calls (5563527 primitive calls) in 57.744 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
313/1 0.010 0.000 57.745 57.745 {built-in method builtins.exec}
1 0.001 0.001 57.745 57.745 no_mutation.py:6(<module>)
1 4.951 4.951 57.212 57.212 no_mutation.py:17(main)
5000000 52.094 0.000 52.094 0.000 {method 'step' of 'mujoco_py.cymj.MjSim' objects}
16 0.000 0.000 0.943 0.059 __init__.py:1(<module>)
352/1 0.002 0.000 0.533 0.533 <frozen importlib._bootstrap>:966(_find_and_load)
352/1 0.001 0.000 0.533 0.533 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
330/1 0.001 0.000 0.532 0.532 <frozen importlib._bootstrap>:651(_load_unlocked)
282/1 0.001 0.000 0.532 0.532 <frozen importlib._bootstrap_external>:672(exec_module)
462/1 0.000 0.000 0.532 0.532 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
1 0.000 0.000 0.355 0.355 builder.py:1(<module>)
397/42 0.000 0.000 0.330 0.008 {built-in method builtins.__import__}
5000 0.054 0.000 0.158 0.000 {method 'set_state' of 'mujoco_py.cymj.MjSim' objects}
1298/382 0.001 0.000 0.153 0.000 <frozen importlib._bootstrap>:997(_handle_fromlist)
4 0.000 0.000 0.140 0.035 __init__.py:15(<module>)
321/294 0.001 0.000 0.132 0.000 <frozen importlib._bootstrap>:564(module_from_spec)
1 0.000 0.000 0.129 0.129 mjviewer.py:1(<module>)
35/30 0.000 0.000 0.125 0.004 <frozen importlib._bootstrap_external>:919(create_module)
35/30 0.056 0.002 0.125 0.004 {built-in method _imp.create_dynamic}
1 0.000 0.000 0.111 0.111 __init__.py:6(<module>)
1 0.000 0.000 0.111 0.111 Distutils.py:1(<module>)
1 0.000 0.000 0.110 0.110 build_ext.py:1(<module>)
1 0.000 0.000 0.109 0.109 util.py:6(<module>)
1 0.000 0.000 0.108 0.108 __init__.py:16(<module>)
1 0.000 0.000 0.107 0.107 old_build_ext.py:7(<module>)
3 0.000 0.000 0.101 0.034 old_build_ext.py:28(_check_stack)
3 0.000 0.000 0.101 0.034 inspect.py:1464(getouterframes)
162 0.000 0.000 0.100 0.001 inspect.py:1425(getframeinfo)
441/162 0.001 0.000 0.099 0.001 inspect.py:680(getsourcefile)
1 0.000 0.000 0.098 0.098 __init__.py:106(<module>)
114 0.019 0.000 0.096 0.001 inspect.py:714(getmodule)
1 0.000 0.000 0.077 0.077 add_newdocs.py:10(<module>)
284/272 0.000 0.000 0.072 0.000 <frozen importlib._bootstrap_external>:393(_check_name_wrapper)
1 0.000 0.000 0.071 0.071 builder.py:43(load_cython_ext)
2 0.000 0.000 0.071 0.036 <frozen importlib._bootstrap>:674(_load) |
Me and Angel discussed about the interface. There is one question we want to confirm: How should we deal with the size value user set when it is incompatible with its shape?
Which way is better? |
https://blog.openai.com/generalizing-from-simulation/
We should probably not attempt this until #4 is done.
For now, only MuJoCo support is necessary. If MuJoCo is too burdensome, we can consider switching engines (e.g. Bullet)
Related issue openai/mujoco-py#148 suggests this may be nontrivial.
The text was updated successfully, but these errors were encountered: