Download code/tt_bevfusion/tests/test_reference_structure_host.py from changh95/bevfusion-p150: direct link, hf CLI and curl.
- Browser
- Download file 6.19 kB
-
https://huggingface.co/changh95/bevfusion-p150/resolve/main/code/tt_bevfusion/tests/test_reference_structure_host.py
- Command line
-
hf download hf://changh95/bevfusion-p150/code/tt_bevfusion/tests/test_reference_structure_host.py
-
curl -L -o test_reference_structure_host.py https://huggingface.co/changh95/bevfusion-p150/resolve/main/code/tt_bevfusion/tests/test_reference_structure_host.py
6.19 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Structure of the weights the reference (and later the device graph) reads from ``bevfusion_lidar.onnx``: every | |
| wiring assertion of ``reference/weights.py`` holds (it raises otherwise), the layer plan matches the host rulebook | |
| plan, every BatchNorm fold is exact (float64 folded vs unfolded on random inputs, per layer kind), the parameter | |
| count after the folds equals an independent count over the graph, constants (``bev_pos``, pooled classes, K, heads) | |
| are the SPEC's. numpy + onnx only (no torch, no onnxruntime): runs in tt-env with the fake ttnn and in the research | |
| venv; skipped without the weights. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| import pytest | |
| pytest.importorskip("onnx") | |
| pytest.importorskip("yaml") | |
| from tt_bevfusion.host.preprocess import STRIDED_LAYERS, SUBM_LAYERS # noqa: E402 | |
| from tt_bevfusion.reference.config import BEVFusionConfig # noqa: E402 | |
| from tt_bevfusion.reference.weights import ENCODER_PLAN, load_weights # noqa: E402 | |
| from tt_bevfusion.tests import _data # noqa: E402 | |
| from tt_bevfusion.ttaw.weights import OnnxWeights # noqa: E402 | |
| def loaded(): | |
| d = _data.weights_dir() | |
| cfg = BEVFusionConfig.from_model_dir(d) | |
| return cfg, load_weights(d, cfg), OnnxWeights(d / _data.ONNX) | |
| def test_config_from_the_weights(loaded): | |
| cfg, _, _ = loaded | |
| assert cfg.version == "v2.0" and cfg.class_names == ("CAR", "TRUCK", "BUS", "BICYCLE", "PEDESTRIAN") | |
| assert cfg.grid == (1440, 1440, 40) and cfg.num_proposals == 500 and cfg.max_voxels == 256000 | |
| assert cfg.max_points_per_voxel == 10 and not cfg.use_intensity and cfg.out_size_factor == 8 | |
| assert cfg.remapper.apply(np.array([1, 1, 2]), np.array([5.0, 10.0, 10.0]), np.array([3.0, 4.0, 4.0])).tolist() \ | |
| == [2, 4, 4] | |
| def test_layer_plan(loaded): | |
| _, w, _ = loaded | |
| assert len(w.encoder) == 21 and [x.name for x in w.encoder] == [p[0] for p in ENCODER_PLAN] | |
| maps = {x.map for x in w.encoder} | |
| assert maps == set(SUBM_LAYERS) | set(STRIDED_LAYERS) | |
| chans = [(x.in_channels, x.out_channels, x.kernel_volume) for x in w.encoder] | |
| assert chans[0] == (4, 16, 27) and chans[-1] == (128, 128, 3) | |
| assert [x.name for x in w.encoder if x.residual] == [f"sparse.layer{s}.{b}" for s in range(1, 5) for b in range(2)] | |
| assert [len(b) for b in w.blocks] == [6, 6] and w.blocks[1][0].stride == 2 | |
| assert [(d.kind, d.kernel, d.stride) for d in w.deblocks] == [("conv", 1, 1), ("conv_transpose", 2, 2)] | |
| h = w.head | |
| assert h.pooled_classes == (0, 1, 2, 3) and h.num_proposals == 500 and h.nms_kernel == 3 | |
| assert h.decoder.self_attn.num_heads == 8 and h.decoder.self_attn.head_dim == 16 | |
| assert h.decoder.cross_attn.q_divisor == 4.0 | |
| assert h.bev_pos[181].tolist() == [1.5, 1.5] and h.bev_pos[-1].tolist() == [179.5, 179.5] | |
| def test_parameter_count_after_the_folds(loaded): | |
| """ONNX float32 weights (8,308,692, S:bevfusion:218) minus what each BatchNorm fold removes: its 4 C statistics | |
| become a C bias (-3 C) when the folded conv had no bias, or merge into the existing bias (-4 C).""" | |
| _, w, o = loaded | |
| import onnx | |
| from onnx import numpy_helper | |
| inits = onnx.load(str(_data.weights_dir() / _data.ONNX)).graph.initializer | |
| total = sum(numpy_helper.to_array(t).size for t in inits if t.data_type == onnx.TensorProto.FLOAT) | |
| removed = 0 | |
| for bn in o.nodes("BatchNormalization"): | |
| prod = o.producer(bn.inputs[0]) | |
| c = o.array(bn.inputs[1]).size | |
| has_bias = prod.op_type in ("Conv", "ConvTranspose") and len(prod.inputs) > 2 and prod.inputs[2] | |
| removed += 4 * c if has_bias else 3 * c | |
| assert total == 8_308_692 | |
| assert w.num_params() == total - removed | |
| def _bn_apply(y, o, bn_name, axis): | |
| bn = o.batchnorm(bn_name) | |
| shape = [1] * y.ndim | |
| shape[axis] = -1 | |
| return (y - bn.mean.reshape(shape)) / np.sqrt(bn.var.reshape(shape) + bn.eps) * bn.gamma.reshape(shape) \ | |
| + bn.beta.reshape(shape) | |
| def test_bn_folds_are_exact(loaded): | |
| _, w, o = loaded | |
| rng = np.random.default_rng(0) | |
| # sparse KRSC: y = x_im2col @ W^T per tap, BN over Cout | |
| for layer in (w.encoder[0], w.encoder[6], w.encoder[-1]): | |
| node = o.node(layer.onnx_node) | |
| W = np.asarray(o.array(node.inputs[1]), np.float64) | |
| x = rng.normal(size=(7, layer.kernel_volume, layer.in_channels)) | |
| y = np.einsum("nkc,okc->no", x, W.reshape(W.shape[0], -1, W.shape[-1])) | |
| ref = _bn_apply(y, o, layer.bn_node, 1) | |
| got = np.einsum("nkc,okc->no", x, layer.weight.astype(np.float64).reshape(W.shape[0], -1, W.shape[-1])) \ | |
| + layer.bias | |
| np.testing.assert_allclose(got, ref, rtol=2e-6, atol=2e-5) | |
| # dense conv (1x1 deblock as a matmul) and conv1d heads: same fold rule along the output channels | |
| d0 = w.deblocks[0] | |
| W = np.asarray(o.conv(d0.onnx_node).weight, np.float64)[:, :, 0, 0] | |
| x = rng.normal(size=(5, W.shape[1])) | |
| np.testing.assert_allclose(x @ d0.weight[:, :, 0, 0].astype(np.float64).T + d0.bias, | |
| _bn_apply(x @ W.T, o, d0.bn_node, 1), rtol=2e-6, atol=2e-5) | |
| # ConvTranspose k == s: per output pixel a matmul over Cin with W[:, :, i, j]; BN over Cout (axis 1 of W) | |
| d1 = w.deblocks[1] | |
| W = np.asarray(o.conv(d1.onnx_node).weight, np.float64) | |
| x = rng.normal(size=(5, W.shape[0])) | |
| np.testing.assert_allclose(x @ d1.weight[:, :, 1, 0].astype(np.float64) + d1.bias, | |
| _bn_apply(x @ W[:, :, 1, 0], o, d1.bn_node, 1), rtol=2e-6, atol=2e-5) | |
| name, hid, _ = w.head.prediction_heads[0] | |
| c = o.conv(hid.onnx_node) | |
| x = rng.normal(size=(5, 128)) | |
| y = x @ np.asarray(c.weight, np.float64)[:, :, 0].T + (0 if c.bias is None else c.bias) | |
| np.testing.assert_allclose(x @ hid.weight[:, :, 0].astype(np.float64).T + hid.bias, | |
| _bn_apply(y, o, hid.bn_node, 1), rtol=2e-6, atol=2e-5) | |
| def test_onnx_taps_name_real_tensors(loaded): | |
| _, w, o = loaded | |
| produced = {t for node in o.nodes() for t in node.outputs} | |
| missing = {k: v for k, v in w.onnx_taps.items() if v not in produced} | |
| assert not missing, missing | |