Commit Graph

2 Commits

Author SHA1 Message Date
m3ultra
bf8b1a35d5 Close all four open items: MoGe camera, manifold remesh, winding, UV bake
THE DECIMATION FLOOR WAS MISDIAGNOSED. I attributed it to ~180k boundary edges. It is
non-manifold edges. Measured on the shipped 500k mesh:

  boundary edges     32,370
  NON-MANIFOLD       81,112     <- the actual blocker, 2.5x more

Quadric decimation cannot collapse an edge shared by more than two faces. Upstream's
own fix (fill_holes, via CUDA-only cumesh) targets boundaries and caps at
max_hole_perimeter=3e-2, so it was never going to help: trimesh's equivalent moved
boundaries 32,370 -> 30,990 and the floor only 214k -> 210k. That falsified it.

--manifold: voxelise -> fill -> marching cubes. Removes BOTH classes at once and so
closes three of the four items in one change:

  as shipped   499,984 faces  bnd 32,370  nonmani 81,112  watertight=F  IoU 0.969
  remeshed   1,178,142 faces  bnd      0  nonmani      0  watertight=T  IoU 0.949
  -> 20k        19,998 faces  bnd      0  winding consistent            IoU 0.956

25x smaller, fully manifold, consistent winding, for 1.3% silhouette IoU. Lossy by
design - it gives up the dual grid's open-surface representation - so it is opt-in.

UV BAKE is unblocked by the same change: its cost is driven by face count, not by
remesh. 5.0s at 20k faces against >20min at 214k. No longer offline-only when paired
with manifold.

THE SCALING TRAP, worth knowing: marching_cubes returns vertices in VOXEL INDEX space.
Translating without apply_scale(pitch) leaves the mesh ~292x too large. It still
exports and renders as a plausible object; it silhouettes at IoU 0.08. That is how it
was caught.

MoGe-2 CAMERA is now wired and is the default, matching upstream; --fixed-fov keeps
the old constant. It runs once per image in torch/MPS, ~0.4s after load.

Reporting this one straight: it did NOT improve the samples. On 1_img, fixed 49.1 deg
scored 0.893 and MoGe's 29.7 deg scored 0.883. Two caveats keep it as the default
anyway - the silhouette metric projects with the SAME FOV used to generate, so a
wrong-but-consistent camera can still score well and the metric cannot fully arbitrate
camera correctness; and the bundled samples are synthetic renders, not the photographs
MoGe reads. Real photos are the intended input here, and upstream estimates too. But
the constant is one flag away and the measurement is on record rather than assumed.

Operator gains manifold, divisions, fixed_fov. README and PROFILE.md corrected where
they repeated the boundary-edge claim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 20:39:45 +10:00
m3ultra
202ddde31d Profile: neither planned optimisation is worth doing
The plan was to port the fused Metal spconv kernel and the 15 mx.compile sites down
from trellis-2-mrp-mlx into the shared core. Measured on the full textured cascade,
both are dead ends. PROFILE.md has the numbers; the short version:

BY BACKEND (204.3s total, peak 37.1GB)
  DiT transformer flows   ~173s   ~85%
  sparse conv (decoders)   ~26s   ~13%
  torch/MPS (DINOv3+NAF)   4.1s     2%
  o_voxel native           0.8s   0.4%

The Metal kernel can only touch the 13%. Verified from the checkpoints: the four flow
models contain ZERO 5-D tensors, so they never call sparse conv at all - only shape_dec
(40), tex_dec (40) and ss_dec (20, dense) do. A free 2x on ALL sparse conv returns 6%
of runtime. The kernel is still the right port if peak memory ever matters, which is
its stated prize, but it is not a speed fix.

mx.compile is no better, because the DiT loop is compute-bound rather than
dispatch-bound - the opposite of the launch-latency problem that motivated the fused
gather-matmul in the shared core:

  scaling      25% tokens -> 0.15x,  50% -> 0.36x,  100% -> 1.00x  (super-linear)
  per block    attention 1062 GFLOP 58.8% (O(n^2)), MLP 496, proj 248
               12.7 TFLOP/s achieved at 142 ms/block
  direct test  eager 23.9 ms vs mx.compile 23.7 ms  -> 0.8%, i.e. nothing

Attention already routes through mx.fast.scaled_dot_product_attention. mx.compile also
wants pure array-in/array-out functions while the sparse path threads SparseTensor
objects with Python-side layout, so it would mean restructuring for a measured ~0%.

The real lever is TOKEN COUNT: attention is O(n^2), half the tokens ran 2.8x faster,
and refine_coords already backs the grid off past max_num_tokens. That knob trades
resolution for time honestly.

m4pro cannot run this: peak 37.1GB against 24GB of RAM, and the weights alone are 24GB
before activations. Capacity, not tuning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:19:17 +10:00