Skip to content

feat: package issue 918 Numba roadmap independently - #929

Open
TATP-233 wants to merge 1 commit into
mainfrom
feat/issue-918-numba-independent
Open

feat: package issue 918 Numba roadmap independently#929
TATP-233 wants to merge 1 commit into
mainfrom
feat/issue-918-numba-independent

Conversation

@TATP-233

@TATP-233 TATP-233 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Relates to #918

Purpose

This PR repackages the complete #918 Numba roadmap as one independently reviewable change. It is intentionally not merged automatically; maintainers should review and manually merge it into refactor/issue-902-mjwarp-squashed-delivery.

Branch / history separation

After manual merge, the refactor branch will contain the mjwarp baseline plus this independently reviewable Numba roadmap.

Included roadmap work

The PR does not add backend, runner, NpEnv lifecycle, training ABI, checkpoint, sim2sim, or regular CI changes, and does not expand beyond the two pilot task families.

Validation

  • make test-all
    • 1667 passed, 37 skipped, 280 deselected, 1 xfailed
    • Ruff, mypy, Pyright, and benchmark smoke passed
  • the 9-file motion pilot acceptance and benchmark evidence from Work: 接入 motion tracking 自动 Numba 融合并完成双 pilot 收口 #923 is included in this squashed package
  • performance evidence retained from the child PRs:
    • G1 walk, 8 threads: N=2048 25.54M env/s (116.7% baseline), N=8192 29.80M env/s (275.0% baseline)
    • motion tracking, 8 threads: N=2048 10.43M env/s (97.3% baseline), N=8192 12.24M env/s (105.5% baseline)

Scope

  • 29 files
  • +3841 / -2002 lines
  • no mjwarp implementation changes are included in the diff; those remain in the refactor base branch

@TATP-233
TATP-233 requested a review from caozx1110 as a code owner August 6, 2026 11:58
@TATP-233
TATP-233 changed the base branch from refactor/issue-902-mjwarp-squashed-delivery to main August 6, 2026 14:20
@TATP-233

TATP-233 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Numba 效率复测:统计口径、前后对比与推断

测试环境

环境 CPU 内存 GPU
本机(仅记录环境,未作为本次远端结果) AMD Ryzen 9 9950X3D2,16C/32T 60 GiB NVIDIA GeForce RTX 4090,约 49 GiB
user@10.13.1.125(实际测试机) AMD Ryzen Threadripper 9980X,64C/128T 250 GiB 2 × NVIDIA RTX 6000D,约 85.7 GiB/卡

测试提交:PR #929 09702431;旧手写 Numba 基线:issue #918 开始前 b8ecb79e。配置为 sac_default,N=2048/8192,线程数 1/2/4/8/16/32/64;热切片每项 100 次迭代、10 次预热,parity 全部通过。

统计口径

  1. 热切片吞吐(M env/s

    • 公式:num_envs / mean(update_call_ms) × 1000,表示批量行处理吞吐,不是完整训练吞吐,也不是 learner samples/s。
    • G1 包含 reward、termination、actor/critic observation assembly。
    • Motion Tracking 包含报告中的相对变换、reward、termination、observation assembly。
    • 使用确定性的 synthetic backend arrays;不包含 physics、motion sampling、reset/RNG、policy inference、learner、replay。
    • 计时包含 compute_update_state 的 Python 侧绑定/scale 同步、Numba runtime 调用和日志 scratch 处理;首次 JIT 编译在预热/编译阶段完成,不计入均值。
    • 新分支的 numpy_dispatch 是新 term-plan NumPy 路径;旧分支的手写 Numba 是另一个基线,二者不能把“vs NumPy”直接当作旧手写 Numba 对比。
  2. Collector 端到端吞吐(M steps/s

    • 公式:num_envs × measured_steps / total_active_seconds
    • 活跃窗口包含 actor action selection、env.step(physics、update_state、reset 等)、terminal-observation 处理、replay 写入和 bookkeeping。
    • 不包含 env/actor 构造、初始 step、learner update;本次为 2 步预热 + 8 步测量,因此只能作方向性判断,不能替代长窗口统计。

前后对比

G1 热切片:新自动 assembled Numba vs 旧手写 Numba

env 数 旧最佳 新最佳 最佳吞吐变化 新实现同为 64T
2048 37.96M env/s @64T 20.26M env/s @32t −46.6% 17.93M env/s
8192 75.79M env/s @64T 29.34M env/s @32t −61.3% 27.93M env/s

串行路径反而变快:2048 env 为 0.474 → 0.214 ms(2.22×),8192 env 为 1.868 → 0.804 ms(2.32×)。因此主要问题不是单线程计算量,而是高线程扩展。

G1 新 Numba vs 新 NumPy fallback

env 数 新 NumPy 新 Numba 最佳 热切片加速
2048 1.87M env/s 20.26M env/s @32t 10.83×
8192 2.73M env/s 29.34M env/s @32t 10.76×

Motion Tracking 完整 update_state 数组路径

env 数 旧手写 Numba 新 plan Numba 变化
2048 0.145 ms(约 14.12M env/s) 0.162 ms(约 12.64M env/s) −10.5%
8192 0.425 ms(约 19.28M env/s) 0.389 ms(约 21.05M env/s) +9.3%

G1 真实 collector(新 NumPy vs 新 Numba)

env 数 collector NumPy collector Numba collector 变化 update_state 变化
2048 0.058M steps/s 0.059M steps/s +1.5% 3.814 → 2.576 ms(1.48×)
8192 0.118M steps/s 0.126M steps/s +6.7% 9.227 → 4.506 ms(2.05×)

性能下降的推断

  • 通用 fused kernel 的并行友好性低于旧手写 kernel。 新实现通过 inputs/parameters/workspace/observations 的 tuple 和通用 output routing 执行,每个 term 都有运行时 scale 分支;旧实现是固定参数、固定布局、直接写 observation 的任务专用 kernel。前者给 Numba/LLVM 的常量传播、内联、别名分析和寄存器/缓存优化空间更小。
  • 每步热切片前后增加了 host-side 工作和中间缓冲。 例如 G1 的 dof_pos_diff 会在 binder 中单独生成,随后再被 observation kernel 读取;新 runtime 还要做 input rebinding、scale 同步、workspace/日志管理。单线程时这些开销被 fused 计算收益覆盖,高线程时变成不可并行的固定成本。
  • 数据搬运/写回更重,导致内存带宽和缓存竞争提前饱和。 2048/8192 行在 32/64 线程下已出现调度、缓存和写回开销主导的迹象;新实现的并行效率只有 G1 32T 的约 6.6%/9.0%、64T 的约 2.9%/4.3%,而旧实现约为 25.0%/36.9% 和 13.7%/27.0%。
  • 线程数不是越大越好。 新实现的最佳点已经从旧实现的 64T 降到 32T;继续使用 64T 会增加 Numba prange 调度和共享缓存压力。
  • 端到端收益受非 Numba 部分限制。 8192 env 时 update_state 已提升 2.05×,但 physics 约 29 ms、collector 其余部分约 31 ms,因此总 collector 只提升 6.7%。2048 env 的 8 个测量样本中,other_ms 还从 22.50 增至 23.10 ms,不能据此断言稳定的端到端回退。

优化建议

  1. 先补分段计时:分别记录 _sync_scales_bind_inputs/中间数组、runtime.execute、日志归约;同时记录 kernel-only 的 1/2/4/8/16/32/64T 曲线,先确认退化是在 host binder、Numba kernel 还是日志路径。
  2. 按已解析 plan 做专门化:materialization 时把 active scales、term 顺序、observation offset 固化,生成无运行时 if scales[i] 的 kernel;尽量传递直接的 typed array 参数,减少嵌套 tuple/动态索引。
  3. 减少中间写回:消除 dof_pos_diff 等可以在 observation 写出时直接计算的临时数组;对同一输入的多个输出做 fused/common-subexpression 计算,避免重复遍历。
  4. 优化并行布局:评估 prange chunk/scheduling 和 threading layer;对 log_scratch 做 cache-line padding 或在 benchmark 中关闭日志归约,确认是否存在 false sharing;按 N 自适应选择线程上限,不能固定 64/128T。
  5. 提高 e2e 统计可信度:每个配置使用至少 100 个测量 step、多个独立进程/重复 run,报告 median/p95 和 CPU utilization,再决定 collector gate。
  6. 先修复可用性阻断:为 SAC/APPO/PPO Motrix/MuJoCo owner 配置补齐 term_plan,否则 Motion Tracking 的 Numba e2e 仍会直接构造失败。

当前结论:PR #929 相对 NumPy fallback 有明显热切片收益,但相对旧手写 Numba 的高线程扩展明显回退,且端到端收益有限;建议完成上述分解和优化后再评估合并。

@TATP-233

TATP-233 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

补充复测:165.245.137.171 双路 160 核服务器

补充上一条性能评论,在 unilab@165.245.137.171:/home/unilab/unilabsim/UniLab 使用相同提交复测:

测试通过 detached worktree 执行,使用服务器已有 venv 并设置 --no-sync;原工作树已有的 pyproject.toml/uv.lock 修改未触碰。热切片仍为 100 iterations + 10 warmup,G1 collector 提高为 100 measured steps + 10 warmup。成功路径 parity 均通过。

服务器有其他训练任务,测试开始时约占 5–8 个 CPU 核。由于这是双路 NUMA 机器,额外做了:

  1. 默认全机调度,线程扩展至 160T;
  2. 3 个独立进程重复 G1 关键规模;
  3. numactl 固定到 node 0/node 1 的敏感性测试。

1. G1 热切片:默认全机调度,3 次独立进程的最佳吞吐中位数

统计仍为 reward + termination + actor/critic observation assembly,单位 M env/s

env 数 旧手写 Numba,中位数(范围) 新 assembled Numba,中位数(范围) 新/旧变化
2048 10.97(10.41–11.36) 12.22(11.37–12.73) +11.3%
8192 15.24(13.58–16.24) 13.26(13.05–14.36) −13.0%

新方案相对同提交 NumPy fallback 仍有约 10.4×–12.2× 的热切片加速,但不能据此认定相对旧手写 Numba 全面更快:2048 env 有收益,8192 env 有回退。

2. NUMA 敏感性:G1 8192 env

放置方式 旧手写 Numba 最佳 新 assembled Numba 最佳 观察
固定 node 0 6.10M @32t 15.16M @16T 旧路径在该 socket 明显变慢
固定 node 1 15.46M @32t 15.59M @16T 基本持平
默认全机调度,3-run median 15.24M 13.26M 新路径约慢 13%

这组数据不能简单解读为 node 0 上“新方案快 2.5×”;它首先证明当前 benchmark/runtime 强烈受 CPU/memory NUMA placement 影响。观察上,新方案在两个 socket 间较稳定,而旧手写路径差异很大;默认调度仍会因为 worker 和 first-touch memory placement 产生变化。

线程扩展也再次显示:新 G1 在这台机器上的最佳点是 8–16T,32T 后没有收益,跨过单 node 的 80T 后继续下降;160T 时 2048/8192 env 只有约 3.20M/6.89M env/s。固定使用 64/128/160T 不合理。

3. Motion Tracking:完整 update_state 数组路径(固定 node 0)

这里比较完整的 relative transforms + reward + termination + observation assembly;不比较两份报告含义不同的 reward-only/plan-state 子切片。

env 数 旧手写 Numba 新 plan Numba 新方案吞吐变化
2048 0.368 ms @16T 0.365 ms @16T +0.8%
8192 1.054 ms @32t 0.988 ms @32t +6.6%

本机上 Motion 完整数组路径没有复现 10.13.1.125 的 2048-env 小幅回退,但收益也很有限;说明该差异具有硬件/NUMA依赖。

4. G1 collector:固定 node 0、100-step active window

新实现相对自身 NumPy fallback:

env 数 NumPy collector 新 Numba collector collector 变化 update_state 变化
2048 80.59K steps/s 84.54K steps/s +4.9% 3.094 → 1.520 ms(2.03×)
8192 99.31K steps/s 106.24K steps/s +7.0% 8.648 → 2.793 ms(3.10×)

直接比较新旧 Numba collector:

env 数 旧手写 Numba collector 新 Numba collector 新/旧变化
2048 88.06K steps/s 84.54K steps/s −4.0%
8192 106.44K steps/s 106.24K steps/s −0.2%

因此在控制 NUMA 后,新旧 Numba 的真实 collector 基本同档;新方案相对 NumPy 的 update_state 加速明确,但大部分收益仍被 physics、actor/replay 和 bookkeeping 稀释。

5. Motion e2e 阻断仍然存在

sac/g1_motion_tracking/motrixsim 在 PR #929 上仍构造失败:

ValueError: MotionTracking fused Numba execution requires env.term_plan

即 owner config 覆盖问题在第二台服务器上稳定复现,不是 10.13.1.125 的环境偶发问题。

更新后的结论与建议

  • 确认的收益:相对 NumPy fallback,G1 热切片约 10×–12×,100-step collector 约 +5%–7%;Motion 完整数组路径在该服务器约 +1%–7%。
  • 不能确认的收益:相对旧手写 Numba,结果随 env 规模、socket 和 NUMA placement 改变;G1 8192 默认调度仍回退约 13%,固定 node 后 collector 基本持平。
  • 新增判断:此前的高线程回退不仅是 kernel 结构问题,也叠加了未控制的 NUMA/first-touch/worker placement;但即使固定单 node,新方案最佳点仍只有 16–32T,说明并行扩展瓶颈真实存在。

建议在继续优化前先把 benchmark gate 固定为:显式 CPU affinity + memory policy、记录 threading layer/CPU utilization、每配置多进程重复并报告 median;runtime 为每个 num_envs 选择线程上限。代码侧继续拆分 binder/kernel/log 计时,减少中间缓冲和通用 tuple routing,并修复 Motion owner config 的 term_plan 覆盖。

合并建议不变:当前有明确的 NumPy fallback 收益,但没有稳定优于旧手写 Numba,且 Motion e2e 仍不可用,暂不建议合并。

原始结果保存在服务器:/tmp/unilab-pr929-bench.AU2tpk/results/

@TATP-233

TATP-233 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

补充:env_num=2^{14}–2^{18} 双机扩展测试

已在两台服务器上将 G1 与 Motion 的热切片测试扩展到 16384/32768/65536/131072/262144(即 2^{14}2^{18})。对比提交为:

硬件:

  • 10.13.1.125:AMD Ryzen Threadripper 9980X,64C/128T
  • 165.245.137.171:双路 Intel Xeon Platinum 8568Y+,160C/160T

统计口径:

  • 配置:sac_default
  • 线程数:1、2、4、8、16、32、64;表中取每个 env_num 在该线程扫中的最佳值
  • 每点 50 次迭代,前 10 次 warmup
  • G1:update_state 热切片,单位为最佳吞吐 M env/s
  • Motion:完整数组路径的 update_state,单位为中位耗时 ms;吞吐变化按 old_ms/new_ms - 1 计算
  • 所有 parity 检查通过,termination mismatch 均为 0
  • Motion 每个 env_num 使用独立进程,避免前一轮 benchmark 的线程设置影响后续点
  • 本轮没有运行大规模 collector e2e,因此结论仅覆盖上述 kernel/update_state 口径

G1(最佳吞吐,M env/s;新相对旧)

env_num 10.13.1.125 旧 变化 165.245.137.171 旧 变化
16,384 73.14 31.78 -56.5% 24.36 15.50 -36.4%
32,768 85.20 34.36 -59.7% 30.06 18.90 -37.1%
65,536 80.30 35.43 -55.9% 28.15 23.63 -16.1%
131,072 52.85 35.30 -33.2% 29.21 24.15 -17.3%
262,144 36.96 31.24 -15.5% 37.05 26.06 -29.7%

G1 在两台机器、全部 2^{14}2^{18} 规模均未超过旧手写 kernel。Threadripper 上规模增大后差距收窄,但到 2^{18} 仍慢 15.5%;因此不能把当前 assembled plan 宣称为 G1 的性能改进。

Motion(完整 update_state,ms;新吞吐相对旧)

env_num 10.13.1.125 旧 ms 新 ms 吞吐变化 165.245.137.171 旧 ms 新 ms 吞吐变化
16,384 0.896 0.841 +6.6% 1.654 1.688 -2.0%
32,768 3.005 1.673 +79.6% 3.803 2.537 +49.9%
65,536 7.035 3.883 +81.2% 6.794 4.232 +60.6%
131,072 13.047 8.911 +46.4% 12.852 9.924 +29.5%
262,144 29.572 18.771 +57.5% 37.275 23.259 +60.3%

Motion 从 2^{15} 开始在两台服务器均有明确大规模收益(约 +29% 至 +81%);2^{14} 基本持平。

结论与后续建议

  1. 性能表现是任务相关的,不能用统一的“assembled Numba 更快”概括:Motion 大规模收益明确,G1 则在所有规模回退。
  2. G1 应保留/恢复任务专门化优化,优先排查 observation routing、中间 buffer、tuple indirection,以及未能常量化/融合的 kernel 分支。
  3. Motion 的 preamble/workspace/observation 融合方案在大规模下有效,可作为 G1 优化的参考。
  4. 合并建议不变:Motion e2e 中 env.term_plan 阻断仍未修复,且 G1 相对旧实现存在稳定回退;在这两项解决并重新验证前,不建议合并 PR feat: package issue 918 Numba roadmap independently #929

@TATP-233

TATP-233 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

NumPy / 手写 Numba / assembled Numba 统一时延对比

已将三条路径统一放入同一张表,单位均为 ms/次批量 update_state 调用。这里的“一次”不是单个环境,而是一次同时处理表中 env_num 个环境的 full-batch 调用;数值越小越快。

提交与路径:

任务 服务器 env_num NumPy ms 手写 Numba ms assembled Numba ms assembled/手写
G1 walk 10.13.1.125 16,384 8.073 0.224 (64T) 0.516 (16T) 2.30x
G1 walk 10.13.1.125 32,768 12.966 0.385 (64T) 0.954 (32T) 2.48x
G1 walk 10.13.1.125 65,536 30.903 0.816 (64T) 1.850 (32T) 2.27x
G1 walk 10.13.1.125 131,072 60.847 2.480 (64T) 3.713 (64T) 1.50x
G1 walk 10.13.1.125 262,144 129.493 7.092 (64T) 8.391 (64T) 1.18x
G1 walk 165.245.137.171 16,384 11.919 0.673 (32T) 1.057 (32T) 1.57x
G1 walk 165.245.137.171 32,768 17.554 1.090 (64T) 1.734 (16T) 1.59x
G1 walk 165.245.137.171 65,536 40.309 2.328 (64T) 2.774 (32T) 1.19x
G1 walk 165.245.137.171 131,072 107.934 4.488 (64T) 5.427 (64T) 1.21x
G1 walk 165.245.137.171 262,144 241.817 7.074 (64T) 10.058 (64T) 1.42x
Motion tracking 10.13.1.125 16,384 23.226 0.896 (64T) 0.841 (64T) 0.94x
Motion tracking 10.13.1.125 32,768 49.834 3.005 (64T) 1.673 (64T) 0.56x
Motion tracking 10.13.1.125 65,536 112.298 7.035 (64T) 3.883 (32T) 0.55x
Motion tracking 10.13.1.125 131,072 260.184 13.047 (64T) 8.911 (32T) 0.68x
Motion tracking 10.13.1.125 262,144 579.946 29.572 (64T) 18.771 (32T) 0.63x
Motion tracking 165.245.137.171 16,384 39.299 1.654 (64T) 1.688 (64T) 1.02x
Motion tracking 165.245.137.171 32,768 84.495 3.803 (64T) 2.537 (64T) 0.67x
Motion tracking 165.245.137.171 65,536 207.223 6.794 (64T) 4.232 (64T) 0.62x
Motion tracking 165.245.137.171 131,072 461.658 12.852 (64T) 9.924 (64T) 0.77x
Motion tracking 165.245.137.171 262,144 1,020.320 37.275 (64T) 23.259 (64T) 0.62x

物理含义与计时范围

G1 walk 每次调用包括:

  1. 基于预生成的重力、基座高度、动作、关节状态和命令数组计算 termination。
  2. sac_default reward:速度跟踪、角速度/姿态/动作变化惩罚、pose、足部姿态、步态相位和 alive 等项,以及 reward log。
  3. actor/critic observation 拼接。

Motion tracking 每次调用包括:

  1. motion 与 robot body 的相对位置/姿态变换。
  2. termination 与 sac_default reward。
  3. motion anchor、joint-relative 特征以及 actor/critic observation 拼接。

三条路径都使用同一批已经物化的 deterministic synthetic arrays;因此时间包含上述数组计算、临时/工作区读写和输出数组写入。路径实现差异也包含在内:NumPy 包含 Python reward/observation dispatch 与 NumPy 向量化操作;手写 Numba 包含旧的任务专用手写 kernel;assembled Numba 包含新的 plan runtime dispatch、生成的融合 kernel 以及其运行时 workspace/output 操作。G1 的 NumPy 路径还包含 synthetic backend 的内存数组访问,但不代表真实后端 getter 成本。

以下内容不在计时范围内:MuJoCo/Motrix 物理积分、真实后端状态读取/数据搬运、环境构造、reset、domain randomization/RNG/noise、motion sampling、state replacement、policy inference、collector/replay/learner,以及 Numba 首次 JIT 编译(编译在正式计时前完成)。本轮也没有运行 collector e2e。

统计口径

  • 配置:sac_defaultenv_num=2^{14},2^{15},2^{16},2^{17},2^{18}
  • 每个点先 warmup 10 次,再计时 50 次;表中为 50 次 wall-clock 样本的算术平均 mean_ms,不是最小值或中位数。
  • Motion 每个 env_num 使用独立进程,避免前一轮线程设置污染后续点。
  • parity 均通过,termination mismatch 均为 0。
  • NumPy 列取 PR feat: package issue 918 Numba roadmap independently #929 结果中的 full-batch 基线;G1 的手写/assembled Numba 取各自提交中 compute_update_state 线程扫描的最佳平均耗时,Motion 的完整数组组件取各自提交中 plan-state 最佳线程下的平均耗时。因此该表用于说明三条路径的数量级和相对趋势,不能替代完整 collector 吞吐。

结论

  • assembled Numba 相对同一提交的 NumPy 仍有显著加速,但 G1 在所有规模都比旧手写 Numba 慢。
  • Motion 在 2^{15}2^{18} 相对旧手写 Numba 明显更快,2^{14} 基本持平。
  • 因此 NumPy 基线补充了“是否比纯 NumPy 快”的信息,但不改变 G1 回退和当前不建议合并 PR feat: package issue 918 Numba roadmap independently #929 的判断。

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