Add UMA-S-1.2 support (charge-balanced channels) + edge-frame & energy-head fixes - #1
Add UMA-S-1.2 support (charge-balanced channels) + edge-frame & energy-head fixes#1janweinreich wants to merge 132 commits into
Conversation
…it, dirs - Apache-2.0 LICENSE + NOTICE clarifying weights are not included and separately licensed. - pyproject: package tt_atom; deps numpy<2/torch/ase; fairchem-core as optional 'reference' extra (separate env, numpy>=2 conflict documented). - .gitignore: exclude weights/checkpoints; allow small committed golden fixtures in tests/data. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tests/gen_golden.py builds the fairchem eSCNMDBackbone (eSEN/eSCN-MD/UMA backbone) with deterministic random weights, runs forward + autograd forces on a molecule (ethanol) and a periodic system (rattled Si), and dumps a single npz with inputs, host-precomputed geometric terms (wigner, edge_envelope, x_edge, init node feats), full weights, per-module activations, and outputs (node_embedding, energy, forces). Runs in the separate reference env (numpy>=2); the ttnn port (numpy<2) compares against the npz on disk, decoupling the two environments. Committed tiny-config goldens (sphere_channels=32, lmax=mmax=2, 2 layers): tests/data/golden_tiny.npz ethanol, 9 atoms / 72 edges tests/data/golden_bulk_tiny.npz rattled Si diamond, 4 atoms / 112 edges Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ase 3 start) Single place for the numerics policy (HiFi4 + fp32_dest_acc + packer_l1_acc, validated at matmul PCC 0.999996 on p150) and device/mesh open with program cache. ttnn imported lazily. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1.0 on p150) The compute heart. Flattened [E, (lmax+1)^2 * C] layout puts each order m in a contiguous tile-aligned column block, so the SO(2) conv is column slices + per-m GEMMs + elementwise -- no tile-dim-1 slicing. Includes the on-device RadialMLP (Linear/LayerNorm/SiLU) that produces per-m radial weights. Config-driven (any lmax/mmax/channels). tests/test_so2.py validates so2_conv_1 (with radial + extra-m0 gating) and so2_conv_2 against the fairchem golden on real hardware: out PCC 0.99999/1.0, gating PCC 0.99999. tests/util.py + conftest.py provide the golden loader and a session-scoped device fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors EquivariantRMSNormArraySphericalHarmonicsV2: l=0 centering, degree-balanced component RMS, per-degree affine, l=0 bias -- pure reductions/elementwise. tests/test_norm.py: PCC 0.990 vs golden block0.norm_1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mwise + assembly) gen_golden.py now also dumps host@to_grid_mat/from_grid_mat (fixed SO3_Grid transforms for GridAtomwise) and host@sys_node_embedding. Regenerated tiny goldens; existing SO2/norm parity tests still pass (3 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the remaining device modules and assemble the backbone:
- grid.py GridAtomwise (transpose-matmul to/from S2 grid + 3-layer MLP); PCC 0.99999
- activation.py GateActivation (m-primed sigmoid gate via slice+concat gather)
- edgewise.py Edgewise message block (embedding gather, bmm rotate, SO2x2 + gate,
envelope, one-hot-matmul scatter-add); PCC 0.99999
- model.py GraphContext (host geometric terms -> device) + Backbone + energy head
End-to-end vs fairchem golden (tiny cfg, random weights): node_embedding PCC 0.99998,
energy -1.3906 vs -1.3826 (0.6% rel). All 7 parity tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…9998 Analytic forces (F=-dE/dpos), production path, NOT finite-diff. The heavy dE/dfeature terms are the transposes of the forward GEMMs and run on device; the radial-MLP backward (its LayerNorms) is finished on host autograd, cutting the device tape at the radial output. - tests/mirror.py: bit-exact PyTorch transcription of the device forward (PCC 1.0 / energy bit-identical vs golden) -> the autograd oracle for the VJP and the future host force. - forces.py: hand-written VJPs (rmsnorm/gate/so2/grid/bmm/scatter/energy-head) producing device adjoints g_x_init, g_wigner, g_wigner_inv, g_envelope + per-conv g_rad. - forward modules cache tape intermediates; GraphContext gains source one-hot for the gather-backward matmul. Validated vs mirror autograd (tiny cfg, random w): g_x_init 0.99997, g_wigner 0.99995, g_wigner_inv 0.99991, g_envelope 0.99988, g_x_edge 0.99997. 8 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0.99998
Close the analytic-force path (phase 4). NOT finite-difference.
- geometry.py: differentiable host pos->{wigner, x_edge, envelope, edge-degree x_init} map
(vendored pure-torch e3nn/fairchem rotation math, MIT). gamma roll fixed (gauge-invariant).
Forward terms match golden exactly (dist/x_edge/envelope PCC 1.0; energy gauge-invariant
bit-exact).
- forces.energy_and_forces: device reverse VJP (dE/d geometric inputs, heavy transpose-matmuls
on device) + torch.autograd through host geometry (cheap d(geometric)/dpos) -> F=-dE/dpos.
- gen_golden: also dumps to_m, gaussian offset/coeff, edge_distance(_vec).
End-to-end vs fairchem golden (tiny, random w): energy 0.6% rel; force PCC 0.99998,
cosine 0.99998, MAE 3.3e-4 (|F|max 0.21). 10 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- weights.py: WeightBundle loader (.npz: state_dict + fixed geometry buffers) with verify_coverage() for real-checkpoint drop-in checking. ttnn-env loadable. - tools/export_weights.py: fairchem checkpoint (or random) -> bundle, run in fairchem env; bridges the numpy<2 (ttnn) / numpy>=2 (fairchem) split. Roundtrip verified. - geometry.py: csd_embedding (system charge/spin/dataset, PCC 1.0 vs golden) + radius_graph. - calculator.py: TTAtomCalculator (ASE) — radius graph + sys embedding + device energy and analytic forces. - examples/relax.py + model_tiny_demo.npz (732K, random weights): FIRE relaxation of ethanol CONVERGES on TT (E -1.36 -> -1.58 eV, 39 steps, fmax 0.042 < 0.05). - lazy package API; editable install. 12 tests pass (incl calculator relaxation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real, reproducible measurement harness (warm device-resident eval, Medges/s, end-to-end vs PyTorch-CPU reference via the bit-exact mirror). Honest pre-optimization baseline: the forward is currently memory-movement/op-count bound, not FLOP-bound — matmuls run at <1 TFLOP/s and the per-edge Wigner bmm + so2 data-movement dominate. Optimization (matmul configs, op reduction, layout, trace, batching, multi-card) is the next focused effort; no perf claims until measured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uracy-safe bf8 weights: forward+forces PCC 0.99997 (no accuracy loss). bf8 Wigner operands on the per-edge rotation bmm (the single costliest op): ~3x faster in isolation, ~14% end-to-end, PCC 0.99997 — enabled via GraphContext(fast=True) for the energy-throughput path. The analytic force backward stays bf16 (bf8 would mix dtypes in the transpose-matmul adjoints). Threaded fast through TTAtomCalculator/Backbone. 12 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-m so2_m_conv reshaped its [E,2K] real|imag block to [E,2,K] before the matmul, which made the length-2 part-dim a tile dimension (padded 2->32: a 16x data blowup + a per-edge batched matmul). Replacing it with two plain 2D GEMMs on the sliced halves is the identical math and runs ~80x faster on that path. Measured (p150, N=250 E=4834, full cfg C=128): so2_1 55.8->4.5ms, so2_2 30->2.5ms, block 125->46ms (2.7x); N=54 dev 44.9->18ms. so2_bw updated to the matching flat VJP. All 12 tests pass; forces PCC preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-edge Wigner bmm [E,9,9]x[E,9,C] was launch-bound (~2.9us/edge: tiny 9x9 tile-padded matmuls, one batched launch per edge). W has a fixed sparsity pattern (block-diagonal in l, permuted by to_m), so the rotation is now elementwise column-block MACs in flat [E,9*C] layout -- one launch over all edges per nonzero. GraphContext packs W to its structural nonzeros; the whole edgewise/SO2 chain now runs flat [E,9*C] (no [E,9,C] tile padding). Forces VJP updated: the rotation backward emits per-edge coefficient adjoints, scattered to dense dW for the host dW/dpos autograd (pattern-only is exact for the force -- off-pattern dW/dpos==0). Measured (p150, N=250 E=4834, full cfg): block 46->24ms; full forward 251->48ms (5.2x vs the pre-optimization baseline). Trace now gives 2.3x at small E (dispatch-bound). 12 tests pass; end-to-end force PCC still >=0.98. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…apes SO2Convolution now returns flat [E,nsph*H] and GateActivation operates flat [E,nsph*H], so the edgewise message chain (gather->rotate->so2->gate->so2-> envelope->rotate->scatter) is flat [E,nsph*C] end to end with no 9->32 tile padding. gate_bw/so2_bw updated to the flat VJP. Measured (p150, N=250 E=4834): full forward 48->34ms; gate 3.8->~1ms/block. Trace replay 2.9x at small E (9ms, dispatch-bound), 1.5x at medium. 12 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MultiCard runs one worker process per Tenstorrent card (pinned with TT_VISIBLE_DEVICES, weights resident), streaming independent systems through a shared queue. Each worker pins torch to one host thread: the host geometry otherwise grabs every core, so N workers oversubscribe the CPU and throttle each other (4-card was *slower* than 1 until this). bench_multicard.py measures 1- vs 4-card aggregate throughput. Measured (p150 x4, 128-atom Si, program-cache-warm): 0.022 -> 0.088 Medges/s, 3.95x scaling (near-linear). End-to-end throughput is host-geometry-bound. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- bench_throughput.py: --fast flag + honest energy-rel-err column + device-vs-CPU speedup; bench_multicard.py + plot.py generate the release charts from real JSON. - assets/: device_vs_cpu.png (speedup grows to 5.3x at 250 atoms), multicard_ scaling.png (3.95x on 4 cards) -- generated from measured data, not hardcoded. - README.md: honest accuracy status (random-weight validated, real weights drop-in, none shipped), measured perf table, design, quickstart, reproduce steps, license. - RELEASE.md: draft announcement + social post with the real numbers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the golden harness to the released facebook/UMA uma-s-1 checkpoint: load EMA weights, host MoLE merge via fairchem's own merge_mole=True path -> a plain eSCNMDBackbone, capture per-module activations (spectral atomwise), host geometric terms, and the per-task energy normalizer (rmsd scale + element refs). Merge correctness ANCHOR (ethanol, omol), measured on this box: merged plain backbone vs unmerged-MoE oracle: E rel err 1.3e-12, F PCC 1.0, F MAE 2.1e-7 eV/A (target PCC>0.999). recompute via energy head + E=rmsd*E_raw+sum refs[Z]: E rel 1.1e-7 vs oracle. Goldens dumped to ~/.ttatom_run/goldens_real (NOT committed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tic-force VJP uma-s-1 uses the spectral FF (SpectralAtomwise: two per-degree SO3_Linear GEMMs with a gate between, gate driven by an l=0 scalar MLP) where TT-Atom only had the grid FF. New tt_atom/spectral.py keeps features in the SH basis (per-l shared GEMM, bias on l=0, non-m-primed gate). _Block selects grid vs spectral by cfg ff_type; grid.py path untouched. forces.py gains spectral_bw (reverse VJP) selected in block_bw. Validated on the p150 vs the real uma-s-1 golden (ethanol, block 0): forward: gating/so3_linear_1/atomwise-out all PCC 0.99999 (target >=0.98) backward: spectral_bw vs torch autograd PCC 0.99999, rel-L2 0.0044 All 12 existing (random-weight, grid) tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…coverage Host-side gaps for real uma-s-1 weights (all cheap, pos->device inputs unchanged): - geometry.csd_embedding now dispatches pos_emb vs rand_emb (uma-s-1 uses rand_emb: charge lookup at idx charge+100, spin at idx spin) and takes the per-task dataset token (omol/omat/...). Validated vs golden sys_node_embedding: PCC 1.0, rl2 0.0. - calculator applies the energy normalizer E=rmsd*E_raw+mean+sum refs[Z], F=rmsd*F_raw (rmsd/mean/elem_refs from the bundle; identity for legacy random bundles). - weights.py: WeightBundle exposes task/scale_rmsd/scale_mean/elem_refs; expected_keys branches on ff_type (spectral so3_linear/scalar_mlp) and chg_spin_emb_type (rand_emb). verify_coverage on the real merged bundle: ok=True, 145 keys, none missing. All 12 existing tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bundle) 5 tests gated on a real-weight golden bundle (TTATOM_REAL_GOLDEN, default ~/.ttatom_run/goldens_real/ethanol_omol.npz); auto-skip when absent so the suite stays green without UMA access. Covers: config is real uma-s-1, MoLE merge anchor (PCC>0.999 vs oracle), verify_coverage, spectral atomwise per-module (PCC>=0.98), and end-to-end device E (rel<1e-2) + forces (PCC>0.99) vs the fairchem oracle. Measured on p150: all pass. Full suite 17 passed (12 + 5); 5 skipped without bundle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
README/RELEASE: replace the 'real-weight accuracy pending checkpoint access' caveat with measured uma-s-1 numbers (single p150, ethanol/omol, vs fairchem oracle): merge anchor E rel 1.3e-12 / F PCC 1.0; end-to-end E rel 1.8e-7, F PCC 0.99958, MAE 3.4e-3; FIRE relaxation converges (fmax->0.049 in 58 steps). States clearly: validated on uma-s-1 via fairchem reference, single-p150; perf/multi-card unchanged. tools/export_weights.py gains --uma-s-1: host MoLE-merge (fairchem merge_mole path) to a plain backbone for a fixed composition, writes a clean WeightBundle with the real energy head + per-task normalizer (scale@*), no activations. Verified: bundle verify_coverage ok, calculator E=-4218.474 eV (rel 4.7e-7 vs oracle), |F|max 0.559. No weights committed/redistributed; checkpoint stays gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
radius_graph now takes (cell, pbc) and returns (edge_index, cell_shift): the cartesian per-edge image offset, matching fairchem radius_graph_pbc + get_pbc_distances exactly. HostGeometry/forces/calculator thread the shift into edge_vec (constant wrt pos, so analytic forces are unaffected). Materials tasks now work. Validated on bulk Si diamond (omat, real uma-s-1, p150): graph reproduces fairchem edge_index + edge_distance_vec bit-for-bit (172 edges); E rel err 2.99e-4, force PCC 0.99999, MAE 6.5e-3 eV/A vs fairchem oracle. tests/test_periodic.py (auto-skips without the gated golden). 20 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TTAtomCalculator gains task_name (mirrors FAIRChemCalculator); validated against the bundle's baked-in task. MoLE routing consumes the dataset token, so the task is fixed at merge/export time — task_name mismatch raises rather than silently using the wrong normalizer. Exposes .task_name. - gen_golden_real.py: 'slab' system (Cu(100)+H, oc20, mixed pbc=[T,T,F]) + stores in@pbc. - tests/test_periodic.py parametrized over periodic tasks (omat bulk + oc20 slab). Task heads validated end-to-end vs fairchem (real uma-s-1, p150), all graph regimes: omol ethanol E rel 1.8e-7 F PCC 0.99958 omat bulk Si E rel 3.0e-4 F PCC 0.99999 oc20 CuH slab E rel 8.6e-5 F PCC 1.00000 odac/omc use the identical data-driven path (dataset token + per-task normalizer from the checkpoint); enable by exporting a bundle with --task. 21 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
uma-m-1p1 export succeeds (merged-only, 315 tensors, fits host RAM) but its architecture is lmax=4/mmax=2 — it uses spherical-harmonic coefficient subselection (25->19 coeffs) that the port does not implement (uma-s-1 has lmax==mmax==2). Backbone now raises a clear NotImplementedError instead of a cryptic einsum shape error, naming uma-s-1 as the supported default. uma-m ceiling (honest): (1) needs the mmax<lmax subselection path (not implemented); (2) the oracle+merge parity harness OOMs on this 30GB host (the merged-only export fits, the 2x-load golden gen does not). uma-s-1 stays default. gen_golden_real.py --ckpt lets the golden harness target any UMA checkpoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Profiling (real uma-s-1, ethanol, p150) shows the device forward+backward is ~96% of a calculate() (host geometry only ~3 ms) and is host-DISPATCH-bound for these graph sizes — the ttnn-trace lever, not host amortization. New tt_atom/trace.py TracedEngine captures the fwd+bwd device op-stream once for a fixed topology and replays it, refreshing only the pos-dependent inputs (Wigner coefs, radial edge emb, envelope, node init) in place each step. TTAtomCalculator(trace=True): captures on first call, re-captures automatically when the neighbour list changes (atom crosses cutoff) so results stay correct. Fixes required for capture: - energy_bw's dE/dE seed (ttnn.ones) + zero-pad now cached on the backbone once; ttnn.ones does a device fill that trace capture forbids (hangs). (zeros memset is fine — the forward's rotation zeros already captured cleanly.) - must execute_trace after capture too: begin/end_trace_capture RECORDS without executing, so the capture-step outputs are uninitialized until the first replay. Measured (p150): forward-only replay 2.6x; full fwd+bwd e2e per step 86->40 ms (2.14x); real FIRE relaxation identical 46-step trajectory, same final energy (diff 0.0), 119.5->51.4 ms/step = 2.33x wall clock. Forces bit-for-bit eager. tests/test_trace.py (uses committed demo bundle) + benchmarks/bench_trace.py. conftest reserves a trace region on the shared device. 22 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…+ examples - tt_atom/cli.py + [project.scripts] tt-atom: info / verify / relax / md / convert-checkpoint. relax & md take --trace; relax/md accept --input (ASE file) or --molecule. - Converter now embeds the fairchem reference E/F+input (ref@*) in the bundle so closes the roundtrip ON DEVICE without a fairchem env (the two numpy worlds can't share a process). Verified uma-s-1 ethanol: device E rel 4.75e-7, F PCC 0.99945 -> PASS. - convert-checkpoint runs the export in the reference env; if fairchem is absent it prints the exact reference-env command instead of failing obscurely. - pyproject: torch pin, explicit note that ttnn is not a PyPI dep (obtain from tt-metal wheel), console entry. - examples/: md.py (Langevin, --trace), periodic.py (bulk Si PBC), batch.py (multi-card fan-out). All smoke-tested on the demo bundle. 22 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lead with the one-line fairchem->TT-Atom swap (TTAtomCalculator mirrors FAIRChemCalculator's task_name surface). Real uma-s-1 accuracy table across 3 validated tasks (omol/omat/oc20, all graph regimes, all meeting E rel<1e-3 & F PCC>0.99); trace MD perf table (2.14x per step, 2.33x FIRE); ttnn-not-on-PyPI install note; uma-m-1p1 ceiling; CLI + examples documented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pyflakes-clean across tt_atom/ (unused torch imports in activation/grid/ spectral/cli, unused compute_kernel_config + backbone_bw ttnn in forces, unused VJP locals Cin/nc/parts/N/x; dead imports in tests/benchmarks). No behaviour change; 22 tests pass. Random-weight scaffolding kept — it is the always- available (ungated) CI + demo path (grid FF, module parity tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… release gate, install docs - Single-source tt_atom.__version__ from package metadata (was hardcoded) - CI: host-only build/import/metadata checks (device/accuracy/OOM/perf gate stays on hardware) - release.yml: tag v* -> build, tag/version guard, GitHub Release + wheel; dormant PyPI Trusted-Publishing job (un-gate via PYPI_ENABLED) - CHANGELOG.md + RELEASING.md (explicit release gate: accuracy parity, no OOM, no perf regression) - README install: pin a tested release tag, not master; nightly/source fallback
…ed by Trusted Publisher setup
…backs) tt-atom is now the custom-kernel-only, highest-performance build for uma-s. The per-edge Wigner rotation always runs the custom ttnn.experimental.fused_rotate kernel; the slow addcmul-MAC fallback is gone and unsupported shapes raise a clear error instead of silently degrading. rotation.py: - Remove all env gating and A/B/ablate knobs (TT_ATOM_FUSED_ROTATE, _FUSED, _FUSED_BW, TT_ATOM_FUSED_GC, _FUSED_GC, TT_ATOM_GC_MIN_EDGES, TT_ATOM_FUSED_NOCACHE, TT_ATOM_ABLATE_GC). fused_rotate is unconditional for the shapes it supports. - Delete the addcmul-MAC fallback in rotate/rotate_bw. A shape the kernel cannot run (uma-m: rectangular 19x25, W=256 overflows L1) now raises a clear RuntimeError. - Coefficient adjoint (rotate_bw gc): keep BOTH the fused gc kernel (large graphs) and the ones_bd segment-sum GEMM (small/mid-N), edge-gated by a fixed constant (_GC_MIN_EDGES, no longer an env var). The gc kernel's product CB clashes with the trace's L1-resident tensors at small/mid-N, so the GEMM is the correct path there. This keeps uma-s correct with no L1 clash / OOM at any size. - Fix a latent id-recycling bug in _group's cache (surfaced now that the kernel path is always taken): add an identity guard so a recycled id(ij) can't return a stale grouping. trace.py: drop the expanded-coef cache right before trace capture so the on-device coef expansion is recorded INTO the trace and refreshed on every replay from the in-place compact coef. Without this, the warmup-built expansion was frozen at the capture step and traced forces went stale after a refresh (bug fixed: traced==eager to PCC 1.0 / maxdF 0). pyproject.toml: drop the ttnn wheel dep (ttnn now comes from the source tt-metal build with the custom op). Keep numpy<2, torch, ase and all versioning/CI/release machinery. custom_kernels/: sync the op backup with the authoritative source (fused_rotate + gc + gate + ln_bw, one library, 4 kernels) and document the exact build recipe (tt-metal b5522097 + 3 registration edits). README Install rewritten for the from-source path. tests/test_umam.py: uma-m is unsupported in this build -> assert it raises the clear error. Verified on a Blackhole p150a (real uma-s-1): pytest 51 passed; energy rel-err 1.8e-7..5.4e-4 and force PCC 0.99959..0.99999 across molecular/periodic/slab goldens; fused_rotate 4.3x the split+addcmul MAC in isolation (PCC 0.999995); end-to-end trace MD 1.4-1.7x faster than the MAC path with no regression at any size; OOM ceiling unchanged (works to N=2662, E~93.7k). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rnel-only changelog
…backs; source-ttnn required) Verified on Blackhole p150a: pytest 51 passed/1 skipped; fused_rotate loads; rotation 4.3x isolated, 1.4-1.68x e2e traced MD (N=54-2662), no OOM/accuracy/perf regression. uma-m raises (unsupported in this build). ttnn no longer a pip dep (source build). Versioning intact.
…-kernel build needs source ttnn; a pip wheel can't run standalone)
…PyPI Trusted Publisher config
…ls branch The fused_rotate op is now pre-integrated on tt-metal's moritztng/tt-atom-kernels branch, so install no longer copies the op source or applies the 3 registration edits by hand — just clone that branch, build, install tt-atom, run. custom_kernels/README stays as the authoritative op-source backup, reframed as the recipe for re-integrating the op onto a newer tt-metal commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rnels) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified on hardware (moritztng/tt-atom @ 8d759240): build_metal.sh's install target already auto-stages _ttnn.so into ttnn/ttnn/, and its baked RPATH finds _ttnncpp.so in build_Release/lib without any copy — the old `cp build/lib/_ttnn.so ttnn/ttnn/_ttnn.so` step was dead weight. `pip install -e .` (tt-metal's own dev-install path, ~24s, no rebuild) replaces it and also drops the need for manual PYTHONPATH exports. Confirmed via `import ttnn`, op registration, and TT-Atom's on-device end2end/forces/edgewise pytest suite (all pass) under the new flow.
Investigated uma-l support (README "Compared to fairchem" listed it as a gap to close). Meta has never released a uma-l checkpoint -- facebook/UMA on HF ships only uma-s-1(.1/.2) and uma-m-1p1, and the UMA paper abstract describes scaling small/medium via MoLE rather than a third dense tier. There is nothing to load or validate. Along the way, found the README's "What it supports"/"Accuracy"/"Compared to fairchem" sections still claimed uma-m-1p1 passes parity -- true when written, but the later custom-kernel-only refactor (59619cf) dropped the MAC fallback uma-m needed, and now tt_atom/rotation.py raises for its rectangular rotation (tests/test_umam.py already anchors this). Fixed those three sections to match reality and added a "Model coverage" section explaining both facts: uma-l doesn't exist, and uma-m -- the closer, real precedent -- is unsupported by this kernel for a documented L1-budget reason a larger checkpoint would only make worse. No code changes: bundle_cache.build_bundle already raises a clear ValueError for any model string other than "uma-s-1", so an unknown checkpoint name was never a silent failure mode.
The pypi-publish job was removed in f8028e8 (tt-atom needs a source tt-metal build, so a pip wheel can't run standalone), but the workflow's top-of-file comment still said it "(once enabled) publishes to PyPI via Trusted Publishing" — contradicting the NOTE at the bottom of the same file and RELEASING.md. Update the header to match actual behavior.
- test_multicard.py: parity test - MultiCard sharding 8/10 independent systems across up to 4 cards must give bit-exact energies vs running the same systems sequentially on 1 card. Sharding is bit-exact by construction (each system is evaluated once, on one worker, no cross-system batching unlike disjoint-union batching), so this exercises the real queue/dispatch/gather path. PASSED on qb2. - bench_multicard.py: sweep 1..N cards (was only 1 vs 4) for a fuller scaling curve. - README.md: note the TT_MESH_GRAPH_DESC_PATH workaround needed on qb2, where UMD misreads a single-chip P150 board as a dual-chip P300 and blocks opening any device (single-card included) until the mesh graph descriptor path is set explicitly - also needed in the parent process since MultiCard workers inherit env from it. Measured on qb2 (4x P150, examples/model_tiny_demo.npz, 64 systems x 128 atoms): 1/2/3/4 cards -> 0.196/0.338/0.457/0.579 Medges/s, i.e. 1.72x/2.33x/ 2.95x scaling vs 1 card. Sub-linear (vs the original 3.95x@4 measured elsewhere with a larger model): fixed per-worker overhead (JIT compile/dispatch) is a larger fraction of this tiny demo models per-system compute, so it amortizes less across cards - matches the fixed-overhead pattern already seen in tt-bio predict/esmc multi-card fanout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ied on qb2 (4x P150, 2.95x@4cards)
Adds fairchem charge_balanced_channels, required by uma-s-1.2 (missing -> force PCC ~0.83). balance_l0 is a self-adjoint per-system l=0 mean-subtraction on channels [cs:ce] re-balanced to the system charge after each block; threaded through the forward (Backbone.node_embedding/GraphContext.node_meanM), the reverse VJP (backbone_bw), the disjoint batch (BatchedGraph.charge), and the traced loop (single + batched). calculator/from_uma already source charge/spin (recent upstream fix); this threads the numeric charge into the force calls for the balance target. export_weights writes charge_channel_start/end (cs==ce => disabled = uma-s-1). Also emits the energy-head output + node-sum (and the batch segment-matmul) in fp32: the per-node reference-subtracted energy was re-quantized to bf16 (~2^-8*|raw|/N), the dominant device energy error for high-|raw| systems (heavy metals, radicals, MgO). Forces are unaffected (their VJP seeds from the head weights, not the value). Rebased onto the current on-device-backward master; balance/head hook into the unchanged Backbone loop + GraphContext. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored fairchem Wigner-D coefficient table is a needed source asset caught by the blanket *.pt ignore; add a !tt_atom/assets/*.pt negation mirroring !tests/data/*.npz. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the singular ZYZ-Euler edge->+Y frame (geometry._wigner), whose +-Y-axis coordinate singularity gave wrong analytic forces at exactly-symmetric geometries (~300 meV/A, ethylene/acetone/pyridine). Ports fairchem_s DEFAULT two-chart quaternion frame + l<=4 quaternion->Wigner kernels (vendored MIT, byte-identical to fairchem) into tt_atom/quaternion.py; geometry._wigner dispatches to it (Euler kept behind use_quaternion=False for A/B). The trace-width crash the Euler zeros caused is independently handled upstream now (rotation.gather_coef at the cached sparsity pattern), so only the frame is ported here. A residual bf16 gauge-leak remained for molecules whose symmetry axis lands on a coordinate plane; the model is rotationally equivariant, so the calculator evaluates molecules in a fixed generic orientation and rotates the force back (deterministic; molecules only, periodic/stress untouched). Result: symmetric-molecule force MAE ~300 -> <8 meV/A. Host-only regression in tests/test_symmetry.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d cells) The on-device radial-MLP LayerNorm backward (RadialMLP._ln_bw: mean, x-mu, rsqrt) runs in bf16 by default, unlike the SH-norm (rmsnorm_bw) and gate (gate_bw) backwards which reuse fp32-accurate stats. On an out-of-distribution geometry the bf16 radial VJP mis-directs the force: el_Sn_cmp (2-atom compressed beta-Sn) drops a whole force component -> traj force PCC 0.729, MAE 230 meV/A, while the energy stays correct (forward fine, only the gradient wrong). Diagnosed vs an fp64 mirror.radial_mlp oracle: device rad.bw PCC 0.35-0.60; the same math in fp64 from the *same bf16 cache* recovers PCC 1.0 -> it is the bf16 arithmetic, not the cache. Run the default (non-fused) radial backward in fp32 (fp32 weight copies incl. the transformed w6; upcast cache/g_out; return fp32 so the accuracy survives to the force). Forward stays bf16; the opt-in bf16-only fused_ln_bw kernel path is unchanged (documented tradeoff). Result: device rad.bw matches the oracle (PCC 1.0000); el_Sn_cmp 0.729 -> 0.9997 (9.5 meV/A); ~+4% eager. Adds the missing assertion to tests/test_forces.py that acc["x_edge"] (device radial finish) matches the oracle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tests/test_realweight_uma_s_1p2.py mirrors test_realweight.py for uma-s-1.2: device energy + analytic forces vs the fairchem oracle, asserting charge balancing is active (cs<ce). Auto-skips when the gated golden is absent. gen_golden_real.py writes charge_channel_start/end into the golden config and takes --ckpt-path to build from a local checkpoint file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documents running uma-s-1.2 via UMA(atoms, checkpoint=uma-s-1p2.pt) + docs/uma-s-1p2-validation.md: full 757-system A/B on current master with these fixes -- 691/757 pass (above the 672 the pre-perf-pass backbone reached), no genuine force errors (741/757 force PCC >0.999; el_Sn_cmp recovered by the fp32 radial backward), stress median 0.99996, all misses the bf16 energy floor under chemical accuracy. Performance: up to 5.5x a 16-thread CPU at 216 atoms; master\047s kernel work restores the trace path at scale and reaches the 512-atom cell in eager. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44ca7a4 to
deb0288
Compare
|
Rebased onto current The one real force regression I also ran a max-effort self-review and hardened the new charge-balancing across the batched paths it hadn't reached: MultiCard now builds the per-system mean operator (was a hang on uma-s-1.2), the traced batch uses the true per-system Still a draft — happy to split the radial-backward fix into its own PR if you'd prefer it separate from the uma-s-1.2 feature. |
Summary
Adds uma-s-1.2 support to TT-Atom (run via
UMA(atoms, checkpoint="uma-s-1p2.pt")), plus fixes uncovered while validating it — rebased onto currentmaster(after its recent perf/kernel overhaul). Full 757-system A/B vs fairchem: no genuine force errors, and 691/757 pass the strict bar (above the 672 the pre-perf-pass backbone reached). Draft, for review.Six commits on current
master:Feature
uma-s-1.2 support: charge_balanced_channels + fp32 energy head— uma-s-1.2 needs fairchem'scharge_balanced_channels(missing → force PCC ~0.83): a self-adjoint per-systeml=0mean-subtraction of channels[cs:ce], re-balanced to the system charge after each block. Threaded through the forward (Backbone.node_embedding/GraphContext.node_meanM), the reverse VJP (backbone_bw), the disjoint batch, and the traced loop (single + batched);export_weightswritescharge_channel_start/end.calculate/from_umaalready source charge/spin, so this threads the numeric charge into the force calls. A self-review also hardened the batched/multi-card paths: MultiCard builds the mean operator (no hang on uma-s-1.2), the traced batch uses the true per-systemnatoms, charged batches of unequal atom counts are rejected with a clear error, and the vendored Wigner-D asset now ships in the wheel (pyprojectpackage-data). Also emits the energy head + node-sum in fp32 (the bf16-rounded reference-subtracted energy was the dominant device energy error on high-|raw| systems).Correctness fixes
smooth quaternion edge frame + generic eval rotation— replaces the singular ZYZ-Euler edge→+Y frame (±Y-axis singularity → ~300 meV/Å forces at exactly-symmetric geometries) with fairchem's default two-chart quaternion frame (vendored, MIT). Symmetric-molecule force MAE ~300 → <8 meV/Å. (The trace-width crash the Euler zeros caused is independently handled onmasternow viarotation.gather_coefat the cached sparsity pattern, so only the frame is ported.)fp32 radial-MLP backward— fixes a force regression inmaster's on-device-backward work. The radial-MLP LayerNorm VJP (RadialMLP._ln_bw) runs in bf16 by default, unlike the SH-norm/gate backwards which reuse fp32-accurate stats. On an OOD geometry the bf16 radial VJP mis-directs the force:el_Sn_cmp(2-atom compressed β-Sn) dropped a whole component → traj PCC 0.73 / 230 meV/Å, energy still correct. Diagnosed vs an fp64mirror.radial_mlporacle (devicerad.bwPCC 0.35; same math in fp64 from the same bf16 cache → PCC 1.0, so it's the bf16 arithmetic). Fix: run the small radial backward in fp32 (fp32 weight copies incl. the transformedw6; forward stays bf16; the opt-in bf16-onlyfused_ln_bwkernel path is unchanged). Result:el_Sn_cmp→ 1.0000, ~+4% eager. Adds the missingtest_forces.pyassertion that the device radial finish matches the oracle.Docs / tests
chore— track the vendoredtt_atom/assets/wigner_d_coefficients.pt.test— gated uma-s-1.2 real-weight parity (auto-skips without the checkpoint) +gen_golden_real.py --ckpt-path.docs— README note +docs/uma-s-1p2-validation.md.Validation — full 757-system A/B vs fairchem on CPU (
uma-s-1p2.pt), on currentmaster757 systems, 2–99 atoms, ~63 elements, molecular + 491 periodic (omol/omat/oc20/odac/omc; 175 oc20 surfaces, 40 nanoparticles, S22, radicals, charged TM complexes, EOS curves).
master's bf16 radial backward introduced (el_Sn_cmp) is fixed by commit 3.master's numerics plus the fp32 head net lower the bf16 energy floor. The misses are that floor on the highest-|raw| systems (heavy-metal diatomics/crystals, up to ~11 meV/atom), forces PCC ~1.0, all under chemical accuracy (~43).Performance — MgO supercells, ms/call, TT (Wormhole,
ttnn0.68.0) vs 16-thread CPUUp to 5.5× at 216 atoms (trace); ~2.2× faster than the pre-perf-pass backbone.
master's kernel work restores the trace path at scale (captures through 216-atom periodic / 192-atom molecular; molecular ethanol 90→47 ms trace, ~1.9×) and reaches the 512-atom cell in eager — only the 512 trace still exceeds single-card DRAM. The fp32 radial-backward fix adds ~4%. Full writeup indocs/uma-s-1p2-validation.md.Testing
tests/test_symmetry.py(no card). Card random-weight suite green;test_forces.pynow also asserts the device radial finish vs the fp64 oracle. Gatedtests/test_realweight_uma_s_1p2.py: 3 passed with a generated golden; auto-skips otherwise.Notes
ttnn0.68.0. uma-s-1.2 via--checkpoint/UMA(checkpoint=...);bundle_cacheauto-build staysuma-s-1.tt_atom/quaternion.pyvendored verbatim from fairchem (MIT). UMA weights gated, not included.🤖 Generated with Claude Code