Skip to content

feat(manager)!: replace the Numba manager path with a generic Torch executor - #157

Open
wlgys8 wants to merge 26 commits into
mainfrom
feat/backend-neutral-manager
Open

wlgys8 wants to merge 26 commits into
mainfrom
feat/backend-neutral-manager

Conversation

@wlgys8

@wlgys8 wlgys8 commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

摘要

移除 ManagerEnv 的 Numba fused-kernel 执行路径,替换为通用批量 Torch 执行器;ManagerEnv host 生命周期与 term 配置 API 保持不变。

主要变更

  • TorchManagerRuntime(motrix_env_core/torch_manager/runtime.py):复用 resolver 元数据(read plan / layout / invocations),按原 fused-kernel 语句顺序批量调度全部 reward/termination/observation/reset term 与命令钩子;per-lane SplitMix64 RNG 逐位保持。默认 torch.compile(evaluate/observe 静态图、reset 静态全批掩码图),compile=False 可回退 eager 调试。
  • 全部 manager MDP term 批量化:core mdp、humanoid walk、WBT motion command(含 sequential/non-sequential/adaptive sampling)、ball balance;命令钩子为 static update/advance/reset_env(receiver, ctx),参数类型完整标注。
  • Numba 完全移除:编译器/codegen/缓存、per-lane dispatch、TBB 预加载、numba/tbb 依赖(pyproject + uv.lock);src/tests 零 numba import。
  • 命名空间与死代码清理:基础设施归位 motrix_env_core/manager/;通用四元数/地形数学归位 core(math/quaternion_array.py、heightfield_lookup);声明式 definition lowering 死链(build_definition 全家族)删除。
  • 文档:wiki 设计文档同步更新(torch-executor.md 含性能特性节)。

性能(g1-wbt-dance,2048 envs)

机器 Torch(本分支) Numba(main 基线)
32 核桌面 16.1–17.0 ms/step 16.98 ms/step
192 核 EPYC 14.0–14.5 ms/step 12.99 ms/step

EPYC 残余 ~1ms 归因:物理后冷 kernel 惩罚(已直接测量)+ reset 掩码设计 3x lane 计算;关键修复:Inductor cpp.min_chunk_size 4096→256(默认值会把 manager 规模循环串行化为 omp-single)。

验收

  • 数值 parity(迁移期 vs Numba 同 seed):core bit-exact;walk 2.4e-7;WBT 2.4e-7(含全部 advance/reset 分支);ball balance 3e-8;RNG 逐位一致
  • 训练效果对比(g1-wbt-dance/FastSAC/2048 envs/seed=1/50k iters):最终平滑 return 47.33 vs main 46.36,曲线与收敛统计等价(该对比还抓出并修复了动作 echo 观测的 gather bug,含回归测试)
  • 测试:python -m pytest motrix_env_core/tests motrix_envs/tests tests -m "not slow and not integration" → 763 passed
  • 冒烟:全部 15 个 manager 注册环境(init + steps + 部分复位)通过

🤖 Generated with Claude Code

…xecutor

Remove the Numba fused-kernel execution path from ManagerEnv entirely and
make a generic batched Torch executor the only numerical backend, keeping
the ManagerEnv host lifecycle and term-configuration API unchanged.

- Add TorchManagerRuntime (motrix_env_core/torch_manager/runtime.py): it
  consumes the resolver metadata (read plan, layout, invocations) and
  schedules every reward/termination/observation/reset term and command
  hook in the exact fused-kernel statement order with one batched tensor
  call per term; per-lane SplitMix64 RNG semantics are preserved bit for
  bit, and reset kernels run on lane-gathered contexts with compact
  sim-reset writes.
- Rewrite all manager MDP terms (core mdp, humanoid walk, WBT motion
  command incl. sequential/non-sequential/adaptive sampling, ball
  balance) as batched tensor implementations with fully typed
  signatures; command hooks are static update/advance/reset_env(receiver,
  ctx).
- Compile each kernel's term schedule with torch.compile by default
  (Inductor; RNG draw order preserved). Zero-dim scalar leaves resolve to
  Python scalars so config branches specialize at trace time and the
  graphs stay unbroken; Inductor cpp.min_chunk_size is lowered to 256
  (env MOTRIX_TORCH_MIN_CHUNK) because the default 4096 serialized all
  manager-sized loops into single-threaded omp-single regions. Observe
  compiles one graph per observation group over per-term contiguous
  buffers with a single fused assembly copy; reset compiles a static
  full-batch masked graph (RNG state gated by where(mask, ...) so
  selected lanes keep their exact SplitMix64 sequence); output tensors
  are graph arguments so the double-buffered slots share compiled
  graphs. Eager debugging via TorchManagerRuntime(..., compile=False).
  g1-wbt-dance 2048 envs step median (vs Numba main): 32-core desktop
  16.8-17.0 vs 16.98 ms, 192-core EPYC 14.0-14.5 vs 12.99 ms
  (evaluate 1.66 vs 1.78 ms; remaining gap is the measured
  after-physics cold-kernel penalty and reset's masked 3x lane work).
- Extract the backend-neutral manager resolver (manager/resolver.py)
  from the Numba compiler and delete the Numba path: compiler codegen and
  caches, NumbaTaskProgram, per-lane dispatch twins, literally helpers,
  TBB preload, the WBT torch pilot runtime, and the numba/tbb extras from
  pyproject and uv.lock. No src or test file imports numba anymore.
- Rehome the surviving manager infrastructure out of the stale numba/
  namespace under motrix_env_core/manager/ (term base classes,
  kernel_data layout package, KernelOutputs, fingerprint helper;
  BuildContext and ManagerContext merged into manager/context.py; the
  manager facade keeps its public exports). Shared batch quaternion math
  moved to motrix_env_core/math/quaternion_array.py and namespace.py.
- Annotate the term protocol with its runtime types: reset sim_writes
  buffers are Map[torch.Tensor] (zero-copy views over the numpy
  write-program buffers), observation outputs are torch.Tensor slices,
  and reset noise amplitudes travel as prebuilt torch tensors instead of
  scalar tuples.
- Update tests (lifecycle contracts migrated to the torch executor;
  numba-only suites removed) and wiki design docs (torch-executor.md,
  runtime.md, kernel-abi.md, plan) for the single-executor design.

Validated with same-seed parity against the previous Numba path (core
bit-exact; walk 2.4e-7; WBT 2.4e-7 across all advance/reset branches;
ball balance 3e-8; RNG bit-identical), 755 fast-suite tests, and a
registry smoke of all 15 manager environments (init + steps + partial
reset).
Its experiment (one batch source across NumPy/Torch to decide the
unified-executor direction) concluded with the torch executor landing;
the shared-math contract stays protected by test_batch_mdp.py.
The torch executor PR removed the tbb extra, the numba pytest marker,
and the numba kernel cache; align tests.yml (fast/slow suites, no cache
step), the workflow README, and one unused-local ruff error.
@wlgys8
wlgys8 force-pushed the feat/backend-neutral-manager branch from 4d2597f to 97327f0 Compare November 5, 2026 15:18
The eager NumPy/Torch primitive dispatch (array/primitives.py, mx,
array_api Array wrapper) served the deleted dual-lowering architecture;
production manager terms call torch ops directly. Drop it with its tests
and facade exports; align the CI workflow README.
…functions

The term-entry marker was redundant with the resolver's signature
contract checks (ManagerContext / torch.Tensor / Map[torch.Tensor]
annotations, positional arity). @dispatch remains solely as the marker
for kernel_data class methods that join the layout method proxy table
and fingerprint (ActionState views, CommandTerm hooks).
@wlgys8
wlgys8 force-pushed the feat/backend-neutral-manager branch from 8a23fa9 to 68f97b3 Compare November 5, 2026 15:34
wlgys8 added 21 commits November 5, 2026 23:37
…it is

The runtime already hands ndarray term arguments over as cached tensor
views; rebuilding them through np.asarray/torch.as_tensor per step was a
defensive leftover and cost two needless conversions.
Leftover typing surface from the deleted dual-lowering architecture; the
context contract is expressed by ManagerContext (resolver-validated
signature annotation) and the executor's runtime contexts.
The Any annotation was a dual-executor migration leftover; the Numba
program is gone and TorchTaskProgram is the only implementation. Its
kernel fields are now typed as callables with their full signatures.
The single-attribute structural Protocol was another dual-executor
leftover; TorchTaskProgram is the only implementation.
The ndarray/tensor/query branches were identity pass-throughs next to
the scalar branch; one accepted-leaves tuple expresses the same contract
with less ceremony.
The namespace module predates inlining the batch cores and had exactly
one consumer left; the Dynamo-safe isinstance dispatch now lives beside
its only caller.
binding.py collapsed to a one-line ManagerProgramResolver delegation
after the resolver extraction; call it directly from _build_task_program.
torch.tensor accepts sequences; routing them through numpy was a
migration habit from the numba scalar-tuple literals.
…er graph

The eager fallback duplicated the evaluate/observe schedules verbatim
and reset kept an entirely different legacy gather implementation, so
debug mode exercised different code than production. Builders now
return the raw closure and apply torch.compile only when enabled; the
eager path runs the same closure uncompiled.
…t kernel ABI

The flat input layer existed for the deleted Numba lane ABI and lane
gathering; the torch executor consumes the manager context and term
arguments as resolved trees (path -> leaf value + scope). Removes
KernelInputSource, input offsets, expression strings and their regex
parser, scalar/array buffer wrappers, and the kernel_data lowered-proxy
machinery; the resolver emits typed argument descriptors and a
tree-structural program fingerprint of equivalent invalidation strength.
Scalar-leaf Python specialization, RNG order, per-group observe graphs,
masked reset, warmup clone isolation, and performance are preserved.
…m calls

The generic invocation record and its kind-string dispatch become
structural: the resolver emits CommandUpdateHook/CommandAdvanceHook/
CommandResetHook, RewardTermCall/TerminationTermCall (with resolver-
assigned buffer indices), ObservationTermCall, and SimResetTermCall;
ResolvedManagerProgram carries the evaluate phases (updates, rewards,
terminations, advances), reset hooks, sim-reset terms, and per-group
observations directly. Execution order, indices, arg resolution, and
fingerprint invalidation are unchanged.
The masked reset cloned every PER_ENV-scoped leaf; it now clones exactly
the leaves a reset term may write — each reset hook's receiver command
tree, the RNG state, and metrics (lane-shaped tensors only) — and passes
sim views, action state, clip corpora, and shared parameters through
untouched. Reset no longer reads KernelDataScope. Verified: bit-identical
compiled-vs-eager obs/terminated/rand, zero graph breaks, bench median
15.70 ms, and a full 50k-iteration FastSAC training run matching the
baseline curve (final smoothed return 46.7 vs 47.3, seed-variance band).
…inery

The reset write-contract change removed the last runtime consumer of
leaf scopes; drop the annotation (44 sites back to plain ndarray), the
KernelDataScope enum, scope fields in layouts and resolved leaves, the
scope-based batch-dim validation, and the fingerprint scope hash.
Existing compiled-plan caches invalidate once by design.
… tensors

Carriers become plain frozen dataclasses recognized structurally
(dataclass minus arrays/tensors/queries); their array fields flip from
numpy to zero-copy torch views over the existing arenas — the SimBackend
read program still rewrites the numpy storage in place, rand.state is an
int64 view over the uint64 bits, and ArrayEnvState outputs stay numpy.
Host paths (action queue, command hooks, metrics views, warmup clone)
operate through the views with identical semantics.

Verified: RNG state, obs, and reward streams bitwise identical to the
previous commit over 21 steps + reset; per-step compiled-vs-eager parity
bitwise on obs/terminated; zero graph breaks (wbt/ball); bench median
15.7-15.9 ms; full 50k-iteration FastSAC training matches the baseline
curve (final smoothed return 47.16 vs 47.33, seed-variance band).
ManagerEnv now owns its outputs as torch storage — observation
double-buffer and final-obs slots, reward, terminated, reward-term and
termination-mask buffers — and the compiled kernels write tensors
directly with no numpy boundary wrapping. ArrayEnvState is dual-typed
(producer-selected: Manager emits tensors, DirectEnv keeps numpy); the
lifecycle is duck-typed with allocation hooks instead of a state
subclass. Provider adapters pass tensors through zero-copy (fastsac,
skrl torch, rslrl), the skrl-jax path takes np.asarray views, and
metrics keep the numpy contract via views.

Verified: obs and RNG streams bitwise identical to the previous commit;
zero graph breaks; bench median 15.8-16.0 ms; 50k-iteration FastSAC
training final smoothed return 46.75 (baseline band 46-48.5); skrl JAX
manager-env smoke clean; 1130 fast + 4 slow tests green.

This branch has not been deployed

No deployments
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.

1 participant