bevdet-p150 / code /tt_bevdet /tests /test_reference_host.py
changh95's picture
tt-model push bevdet-p150 (container)
77e3c67 verified
Raw History Blame Contribute Delete
6.25 kB
# SPDX-License-Identifier: Apache-2.0
"""The CPU reference's building blocks that need torch but neither onnxruntime nor the full network run:
weights addressed by consuming node + the deployed-graph check, the three plugins against the research numpy
implementations, and the TT port's exact rewrites (``reference.port_form``) against the deployed form.
TT_VISIBLE_DEVICES=none python -m pytest -q code/tt_bevdet/tests/test_reference_host.py
Needs the pinned ONNX (``tests/paths.py``: ``$BEVDET_WEIGHTS_DIR``, the HF cache or the workspace download); the
weight tests skip without it. The end-to-end reference against ONNX Runtime is ``test_reference_cpu.py``.
"""
from __future__ import annotations
import numpy as np
import pytest
torch = pytest.importorskip("torch")
from tt_bevdet.reference import plugins, port_form # noqa: E402
from tt_bevdet.reference.config import HEAD_NAMES, default_config # noqa: E402
from tt_bevdet.reference.weights import ONNX_SHA256, BEVDetWeights, module_names # noqa: E402
from tt_bevdet.tests import paths # noqa: E402
from tt_bevdet.ttaw.metrics import pcc # noqa: E402
CFG = default_config()
DN = "img_view_transformer.depth_net."
@pytest.fixture(scope="module")
def weights():
path = paths.weights_onnx()
if path is None:
pytest.skip("bevdet_one_lt_d.onnx not found (set BEVDET_WEIGHTS_DIR)")
pytest.importorskip("onnx")
return BEVDetWeights(path)
@pytest.fixture(scope="module")
def net(weights):
from tt_bevdet.reference.model import BEVDetNet
torch.set_num_threads(2)
return BEVDetNet(weights, CFG)
def test_weights_by_consuming_node(weights):
assert weights.sha256 == ONNX_SHA256
names = module_names()
n_conv = sum(len(v) for k, v in names.items() if k != "depth_net_linear")
assert n_conv == 107 == len(weights.onnx.nodes("Conv")) # SPEC 4.2: Conv 107
c = weights.conv("img_backbone.conv1")
assert c.node == "Conv_1" and c.weight.shape == (64, 3, 7, 7) and c.stride == (2, 2) and c.padding == (3, 3)
assert [weights.conv(DN + f"depth_conv.3.aspp{i}.atrous_conv").dilation for i in (2, 3, 4)] == \
[(6, 6), (12, 12), (18, 18)]
assert weights.conv("img_bev_encoder_backbone.layers.0.0.downsample").stride == (2, 2)
w, b = weights.linear(DN + "context_mlp.fc1")
assert w.shape == (512, 27) and b.shape == (512,)
bn = weights.depth_bn()
assert bn.channels == 27 and bn.eps == pytest.approx(1e-5)
n_params = sum(c.weight.size + (0 if c.bias is None else c.bias.size) for _, c in weights.iter_convs())
n_params += sum(a.size for m in names["depth_net_linear"] for a in weights.linear(m)) + 4 * 27
assert n_params == 76_187_718 # SPEC 4.3
def test_align_gather_bevpool_vs_research():
bc = paths.import_research()
if bc is None:
pytest.skip("research reference not present")
rng = np.random.default_rng(2)
adj = rng.normal(size=(1, 8, 4, 20, 24)).astype(np.float32)
trs = np.zeros((1, 8, 6), np.float32)
for n in range(8):
a = rng.uniform(-0.3, 0.3)
trs[0, n] = [np.cos(a), -np.sin(a), rng.uniform(-6, 6), np.sin(a), np.cos(a), rng.uniform(-6, 6)]
trs[0, 3] = [1, 0, 0, 0, 1, 0]
got = plugins.align_bev(adj, trs).numpy()
np.testing.assert_array_equal(got, bc.align_bev(adj, trs)) # bit for bit
np.testing.assert_array_equal(got[0, 3], adj[0, 3]) # identity = copy
curr = rng.normal(size=(1, 4, 20, 24)).astype(np.float32)
for flag in (0, 1):
np.testing.assert_array_equal(plugins.gather_bev(got, curr, flag).numpy(), bc.gather_bev(got, curr, flag))
def test_camera_gates_host_fold_vs_graph(weights, net):
rng = np.random.default_rng(4)
cp = np.zeros((1, 6, 27), np.float32)
cp[0, :, :4] = rng.uniform([1000, 1000, 780, 430], [1300, 1300, 830, 470], size=(6, 4))
cp[0, :, 4:15] = [0.44, 0, 0, 0, 0.44, -140, 1, 0, 0, 1, 1]
cp[0, :, 15:] = rng.normal(size=(6, 12))
g_ctx, g_depth = port_form.camera_gates(weights, cp)
t_ctx, t_depth = net.camera_gates(torch.from_numpy(cp))
assert np.abs(g_ctx - t_ctx.numpy()).max() < 1e-5 and np.abs(g_depth - t_depth.numpy()).max() < 1e-5
w, b = weights.linear(DN + "depth_mlp.fc1")
bn = weights.depth_bn()
wf, bf = port_form.fold_bn_into_linear_input(w, b, bn)
x = cp[0].astype(np.float64)
np.testing.assert_allclose(x @ wf.T + bf, bn.apply(x, axis=1) @ w.astype(np.float64).T + b, rtol=1e-9, atol=1e-9)
def test_depthnet_port_form_matches_deployed(weights, net):
"""1x3 ASPP branch 4, GAP branch as a per-frame bias, depth logits padded to 128 with -1e4, host gates."""
rng = np.random.default_rng(6)
x = torch.from_numpy((rng.normal(size=(2, 512, 16, 44)) * 0.5).astype(np.float32))
gates = (rng.uniform(0, 1, (2, 512)).astype(np.float32), rng.uniform(0, 1, (2, 512)).astype(np.float32))
with torch.no_grad():
d_ref, f_ref = net.depth_net(x, gates=tuple(torch.from_numpy(g) for g in gates))
d_port, f_port = port_form.forward_depth_net_port(net, x, gates, weights, CFG)
np.testing.assert_array_equal(f_port.numpy(), f_ref.numpy())
assert d_port.shape == d_ref.shape and np.abs(d_port.numpy() - d_ref.numpy()).max() < 2e-6
assert pcc(d_port.numpy(), d_ref.numpy()) > 0.99999999
pad = port_form.pad_depth_logits(weights)
assert pad.weight.shape[0] == 128 and pad.n_valid == 118 and np.all(pad.bias[118:] == -1e4)
assert float(np.exp(np.float32(-1e4) - np.float32(-50.0))) == 0.0 # the pad columns vanish in fp32
def test_merged_head_matches_deployed(weights, net):
rng = np.random.default_rng(8)
x = torch.from_numpy(rng.normal(size=(1, 256, 32, 32)).astype(np.float32))
with torch.no_grad():
ref = net.center_head(x)
got = port_form.forward_center_head_port(net, x, weights)
m = port_form.merge_center_head(weights)
assert m.w1.shape == (384, 64, 3, 3) and m.w2.shape == (20, 384, 3, 3) and [s[0] for s in m.splits] == \
list(HEAD_NAMES)
for k in HEAD_NAMES:
assert np.abs(got[k].numpy() - ref[k].numpy()).max() < 5e-5, k
assert pcc(got[k].numpy(), ref[k].numpy()) > 0.9999999, k