Repository navigation
[0.1.0] (2026-10-05)
Changes developed from 2026-10-03 to 2026-10-05. Release artifacts are the wheel and source distribution
on GitHub Releases.
Review follow-up (2026-10-05)
- Rotation coefficients use squared-angle Taylor branches near zero, avoiding NaN in second derivatives at the
identity. Euler extraction handles all twelve conventions at exact gimbal lock by setting the last angle to zero;
the selected inverse at a singularity is not smooth. Inactive atan2 branches are also protected for older PyTorch. - Skinning supports
torch.func.jacrev,vmapandjvp. Dynamo currently rejects custom JVPs, so compilation uses
equivalent native operations that Inductor can fuse; eager execution keeps the custom backward. - FK template rotations and wrist-closed faces are refreshed after state-dict loading, without changing persistent keys.
- Anatomy loss copies and validates configurations and vectorizes its interval penalties, preserving its output order,
reduction modes and editable configuration lists. Device/dtype changes initialize its nonpersistent limit buffers;
explicit module dtype conversions invalidate rounded limits so they are rebuilt from their degree strings. - Model discovery also accepts flat asset folders and legacy
*_new.pkl/*_np.pklfiles. A restricted Latin-1 bytes
reconstruction supports protocol-2 NumPy pickles without importing scipy or arbitrary codecs. - Added regression coverage, explicit
--require-manovalidation, fixed Ruff rules, CPU CI and installed-wheel resource
checks. Minimum supported PyTorch is now 2.0.1, validated with Python 3.10 / NumPy 1.26; CUDA validation uses 2.11. - Added frozen-target MANO fitting benchmarks, selected ARCTIC/HO-Cap validation samples and downstream numerical audits.
Raw licensed data stays outside version control. Rewrote the anatomical-feature documentation and corrected the
float64 precision claim: model buffers are initially rounded to float32. - Historical timings below describe the original optimization round. Follow-up performance and convergence results
are documented in README anddoc/benchmark.md; additional zero-rotation safeguards change eager operation counts. - Eager rotation coefficients are evaluated together, sharing Taylor/sqrt/sinc work. The protected small-angle
polynomial drops a term below float64 epsilon, retaining zero-angle second derivatives. The CUDA rotation probe
decreases from 42 to 28 launches; interleaved full-mesh measurements reduce forward time by 6.1–9.1% and
forward+backward by 7.5–10.6% versus the first correctness fix. Added a pinned eager comparison with upstream,
manopth and smplx inscripts/benchmark_layers.pyand README's "Other MANO layers". - The error-correction demo now uses an opaque, smooth-shaded hand surface. Regenerated its README GIF and
added descriptive captions and alternative text for the demo animations. - Added an interleaved complete eager fitting benchmark with and without anatomy loss, using matched frozen
targets, basis buffers, limits and Adam state across current/Claude/upstream manotorch. README reports full-loop
timings;scripts/README.mddocuments dependencies, commands, input/output formats and scope for every script. - Added a tag-triggered GitHub Release workflow: public CPU tests and lint must pass before the wheel and source
distribution are built, checked and published. Licensed MANO models are excluded from release artifacts. - Release code and documentation are in English. The development branch may retain its acceptance report;
CI prevents that report from entering master or release tags and checks other source documents for CJK text.
ManoLayer keeps its constructor arguments (including **kargs), its th_* buffers with their names, shapes and state-dict keys, its methods and MANOOutput. Outputs match the previous version to float32 rounding and the official MANO code to 1.7e-4 mm in float32 (1.3e-5 mm in float64). Incompatible changes are listed under Behavior changes and Removed.
Performance
ManoLayer (45 PCA components, pose and shape gradients) before (ad515f0) and after, on the same RTX 4090 with PyTorch 2.7 (shared machine, rounded):
| before | after | |
|---|---|---|
| CUDA forward, batch 1 | 6.8 ms | 3.3 ms (0.09 ms with torch.compile(mode="reduce-overhead"), PyTorch 2.11) |
| CUDA forward + backward, batch 1 | 23 ms | 13 ms |
| CUDA forward, batch 8192 | 17 ms | 7.9 ms |
| CUDA forward + backward, batch 8192 | 33 ms | 20 ms |
| CUDA peak memory, forward + backward, batch 8192 | 1593 MiB | 1059 MiB |
| CPU (8 threads) forward / forward + backward, batch 1024 | 150 / 250 ms | 50 / 105 ms |
- Rewritten forward: pose and shape blend shapes as single matrix products, forward kinematics on rotations and translations over the five finger chains, and skinning without the
(B, 4, 4, 778)intermediate. The last skinning step is a smalltorch.autograd.Function(elementwise forward and backward, itself differentiable). - No host-device synchronization on the GPU (in
ManoLayer,AxisLayerFK,AnchorLayerandAnatomyConstraintLossEE), and the layer compiles into a single graph withtorch.compile. manotorch/utils/geometry.pyrewritten from the textbook formulas (Rodrigues, Shepperd, closed-form Euler angles):
the original optimizedaxis_angle_to_matrixlaunched 18 CUDA kernels instead of 53. The October 5 follow-up's
zero-angle second-derivative safeguards initially increased this to 42 in an eager(128,16,3)probe; the subsequent
coefficient batching reduces it to 28 while retaining those safeguards. Compile can fuse operations.- New
joints_only=Trueforward for callers that need only the joints: batch 16384 in 1.7 ms instead of 15 ms and 104 MiB instead of 1.4 GiB.
Added
ManoLayer.forward(pose, betas, transl): a translation in meters added after thecenter_idxcentering (upstream issue lixiny#19; withcenter_idx=None, as manopth'sth_trans).ManoLayer.forward(..., joints_only=True): skins only the 5 fingertip vertices and returnsverts=None.ManoLayer(fix_left_shapedirs=False): when True, mirrors the x component of the left-hand shape blend shapes (smplx#48). Off by default, matching the official model.ManoLayer.th_closed_faces: the wrist-closed faces as a buffer that follows the layer's device (upstream issue lixiny#4).- Model loading without chumpy, scipy or the MANO
webusercode (manotorch/utils/mano_io.py): the official pickles are read through a restricted unpickler that refuses any class a MANO file does not contain.MANO_{SIDE}.npzfiles (no pickle) are preferred when present;tools/mano_pkl_to_npz.pycreates them. - Tests (
tests/): against an independent float64 MANO implementation, model loaders, rotation conversions, left/right mirror symmetry, first- and second-order gradients, and runtime guards (no GPU synchronization, no warnings). scripts/compare_mano_layers.pyand a README table comparing the conventions and accuracy of manopth, smplxMANO/MANOLayer, upstream manotorch and the official MANO code.
Behavior changes
AxisLayerFK.forward()returns left-hand Euler angles in the right-hand convention (twist and spread change sign): mirrored hands give equal angles,compose()invertsforward()for both hands, and theAnatomyConstraintLossEElimits act in the same anatomical direction for both hands.AxisLayerFK.compose()no longer modifies its input.AnchorLayer()loads the anchors shipped with the package (manotorch/assets/anchor); theanchor_rootdefault changed from"assets/anchor"toNone.- Rotation conversions:
matrix_to_quaternionreturnsw >= 0;quaternion_to_axis_angleaccepts non-unit quaternions and returns angles in[0, pi];matrix_to_euler_anglesno longer returns NaN at gimbal lock. - Buffers are float32 regardless of
torch.set_default_dtype; deprecatedtorch.norm,torch.crossandtorch.Tensor(ndarray)calls replaced by their PyTorch 2.x equivalents.
Removed
AxisLayer,manotorch/utils/quatutils.py,manotorch/utils/rodrigues.py(deprecated),manotorch/utils/visutils.py(Open3D viewers), the bundled MANO code (mano/webuser) andtools/clean_ch.py.- Dependencies
chumpy,deprecation,open3d,opencv-python,scipyandmatplotlib;pyvista,trimeshandtqdmmoved to thevisextra.
Packaging, demos and documentation
- Packaging in
pyproject.tomlonly (PEP 621;setup.pyremoved); core dependencies aretorchandnumpy. - The conda environment files are replaced by a uv setup:
uv synccreates Python 3.12 + PyTorch 2.11.0 (CUDA 12.6) fromuv.lock. - Demo scripts fixed (missing
zero_gradinsimple_anatomy_loss.py, fingers sampled into the palm insimple_app.py), rendered with pyvista (--giffor off-screen GIFs), and the README GIFs regenerated. README.mdrewritten for the fork; the previous one is kept asREADME.old.md.
License (2026-10-03)
- The fork follows upstream's relicensing from GPL-3.0 to the Apache License 2.0 (upstream commit
a2a70c5, 2026-02-03, merged here). Versions of this fork published before this change remain available under GPL-3.0. - Added
NOTICE(manotorch and manopth origin, third-party code, MANO license), shipped with the package.