hamer-p150 / code /hamer /checkpoint.py
changh95's picture
Add files using upload-large-folder tool
2ed2aa1 verified
Raw History Blame Contribute Delete
4.11 kB
# SPDX-FileCopyrightText: © 2026 Tenstorrent Inc.
# SPDX-License-Identifier: Apache-2.0
"""Adapter that loads the upstream HaMeR Lightning checkpoint into the
self-contained torch reference shipped in :mod:`hamer.reference.hamer`.
The upstream ``mano_head.transformer.transformer.layers.{i}.{0|1|2}.{norm|fn}``
naming (and the dropout-padded ``Sequential`` indices) are remapped onto the
reference module's flatter ``head.transformer.layers[i].{norm_sa|sa|...}``
layout. No new dependencies — pure ``torch`` + ``numpy``.
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Dict
import numpy as np
import torch
def _remap_key(k: str) -> str | None:
"""Translate one upstream key to the reference module's name; return
``None`` for keys we don't model (e.g. discriminator)."""
if k.startswith("backbone."):
return k
m = re.match(r"mano_head\.transformer\.transformer\.layers\.(\d+)\.(\d+)\.(.+)", k)
if m:
block, sub, rest = int(m.group(1)), int(m.group(2)), m.group(3)
slot = ("sa", "ca", "ff")[sub]
if rest.startswith("norm."):
slot_norm = ("norm_sa", "norm_ca", "norm_ff")[sub]
return f"head.transformer.layers.{block}.{slot_norm}." + rest[len("norm.") :]
if rest.startswith("fn."):
inner = rest[len("fn.") :]
# FFN: net.0/net.3 (with dropouts) → net.0/net.2 (no dropouts)
if slot == "ff":
inner = inner.replace("net.3.", "net.2.")
# Linear-in-Sequential out projection: to_out.0.* → to_out.*
inner = inner.replace("to_out.0.", "to_out.")
return f"head.transformer.layers.{block}.{slot}." + inner
return None
if k.startswith("mano_head.transformer.to_token_embedding."):
return "head.transformer.to_token_embedding." + k.split(".", 3)[3]
if k == "mano_head.transformer.pos_embedding":
return "head.transformer.pos_embedding"
if k.startswith("mano_head.decpose.") or k.startswith("mano_head.decshape.") or k.startswith("mano_head.deccam."):
return "head." + k.split(".", 1)[1]
if k.startswith("mano_head.init_"):
return "head." + k.split(".", 1)[1]
return None
def load_hamer_checkpoint(
ref_module,
ckpt_path: Path | str,
mean_params_path: Path | str | None = None,
weights_only: bool = True,
) -> dict:
"""Load the official HaMeR checkpoint into the reference ``Hamer`` module.
Returns a dict reporting how many tensors were mapped, missing, and extra.
Also overwrites the ``init_*`` buffers with the upstream MANO mean params
if ``mean_params_path`` is provided.
``weights_only=True`` (default) refuses arbitrary pickle globals. The
released ``hamer.ckpt`` only references ``collections.OrderedDict`` and
torch storage/rebuild helpers, so it loads under the safe loader; pass
``weights_only=False`` only for a checkpoint you trust that does not.
"""
ckpt = torch.load(str(ckpt_path), map_location="cpu", weights_only=weights_only)
src = ckpt["state_dict"]
remapped: Dict[str, torch.Tensor] = {}
skipped = []
for k, v in src.items():
new_k = _remap_key(k)
if new_k is None:
skipped.append(k)
continue
remapped[new_k] = v
missing, unexpected = ref_module.load_state_dict(remapped, strict=False)
if mean_params_path is not None:
mp = np.load(str(mean_params_path))
with torch.no_grad():
ref_module.head.init_hand_pose.copy_(torch.from_numpy(mp["pose"].astype("float32")).unsqueeze(0))
ref_module.head.init_betas.copy_(torch.from_numpy(mp["shape"].astype("float32")).unsqueeze(0))
ref_module.head.init_cam.copy_(torch.from_numpy(mp["cam"].astype("float32")).unsqueeze(0))
return {
"loaded": len(remapped),
"skipped_upstream": len(skipped),
"missing_in_ref": len(missing),
"unexpected_in_ref": len(unexpected),
"missing_examples": missing[:5],
"unexpected_examples": unexpected[:5],
}