File size: 8,655 Bytes
4d9b003 be62f78 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | # SPDX-License-Identifier: Apache-2.0
"""The exact rewrites of ``tt/params.py`` and the input packing of ``tt/inputs.py`` against the CPU reference
(float64, no device): every constant the ttnn graph holds is the reference's math rearranged.
TT_VISIBLE_DEVICES=none python -m pytest -q -p fake_ttnn_plugin code/tt_diffusion_planner/tests/test_tt_params_host.py
"""
from __future__ import annotations
import numpy as np
import pytest
from tt_diffusion_planner.host import pipeline as hp
from tt_diffusion_planner.host.solver import apply_prefix_constraint, dpm_solver_sample
from tt_diffusion_planner.reference import config as C
from tt_diffusion_planner.tt import config as T
from tt_diffusion_planner.tt import inputs as I
from tt_diffusion_planner.tt import params as P
torch = pytest.importorskip("torch")
F = torch.nn.functional
PKG = __import__("pathlib").Path(__file__).resolve().parents[1]
@pytest.fixture(scope="module")
def weights():
from tt_diffusion_planner.reference.weights import find_weights_dir, load_weights
wd = find_weights_dir()
if wd is None:
pytest.skip("Diffusion Planner v5.0 weights not found")
return load_weights(wd)
@pytest.fixture(scope="module")
def prepared(weights):
from tt_diffusion_planner.ttaw.io import load_named_arrays
raw = load_named_arrays(str(PKG / "samples" / "kashiwanoha_dense.npz"), C.INPUT_SCHEMA)
return hp.prepare(raw, weights.normalization.observation)
def d(a):
return np.asarray(a, np.float64)
def test_lane_aux_is_the_speed_and_attribute_embeddings(weights):
p = weights.params
rng = np.random.default_rng(0)
for cat, mod in (("lane", "encoder.lane_encoder"), ("route", "encoder.route_encoder")):
e = 40
speed = rng.random(e).astype(np.float32) * 2
has = rng.random(e) < 0.6
speed[~has] = 0.0
attr = (rng.random((e, C.LANE_ATTRIBUTE_DIM)) < 0.2).astype(np.float32)
got = d(P.lane_aux_features(speed, has, attr)) @ d(P.lane_aux(p, cat))
lin = d(speed)[:, None] @ d(p[f"{mod}.speed_limit_emb.w"]) + d(p[f"{mod}.speed_limit_emb.b"])
want = np.where(has[:, None], lin, d(p[f"{mod}.unknown_speed_emb"])[None]) \
+ d(attr) @ d(p[f"{mod}.attribute_emb.w"]) + d(p[f"{mod}.attribute_emb.b"])
np.testing.assert_allclose(got, want, rtol=1e-12, atol=1e-12)
def test_neighbor_aux_and_pos_aug(weights, prepared):
p = weights.params
f = prepared.features
t = d(f.neighbor_type)
got = np.concatenate([t, np.ones((t.shape[0], 1))], 1) @ d(P.neighbor_aux(p))
want = t @ d(p["encoder.neighbor_encoder.type_emb.w"]) + d(p["encoder.neighbor_encoder.type_emb.b"])
np.testing.assert_allclose(got, want, rtol=1e-12, atol=1e-12)
inp = I.plan_inputs(prepared)
pos = d(inp["pos_aug"][0, 0]) @ d(P.pos_aug(p))
tv = np.asarray(f.token_valid, bool)
ref = (d(f.pos) @ d(p["encoder.pos_emb.w"]) + d(p["encoder.pos_emb.b"])) * tv[:, None]
np.testing.assert_allclose(pos[:T.TOKENS_REAL], ref, rtol=1e-12, atol=1e-12)
assert not pos[T.TOKENS_REAL:].any()
def test_agent_rows_and_masked_projection(weights):
p = weights.params
rows = P.agent_rows(p)
emb = d(p["decoder.dit.agent_embedding"])
b2 = d(p["decoder.dit.preproj.fc2.b"])
np.testing.assert_allclose(d(rows[0]), b2 + emb[0], rtol=1e-6)
np.testing.assert_allclose(d(rows[1:]), np.repeat((b2 + emb[1])[None], T.AGENTS - 1, 0), rtol=1e-6)
lin = P.final_projection_masked(p)
assert not lin.w[:, :4].any() and not lin.b[:4].any()
np.testing.assert_array_equal(lin.w[:, 4:], np.asarray(p["decoder.dit.final_layer.proj.4.w"])[:, 4:])
def test_turn_weights_reproduce_the_head(weights):
from tt_diffusion_planner.reference.model import TurnHead, torch_params
p = weights.params
rng = np.random.default_rng(1)
enc = rng.standard_normal((T.TOKENS_REAL, C.HIDDEN_DIM))
x0 = rng.standard_normal((C.MAX_NUM_AGENTS, C.OUTPUT_T + 1, C.POSE_DIM))
w = P.turn_weights(p)
got = x0[0].reshape(-1) @ d(w["w_sel"]) + enc.sum(0) @ d(w["w_pool"]) + d(w["b"])
head = TurnHead(torch_params(p, torch.float64))
want = head.forward(torch.from_numpy(enc), x0).numpy()
np.testing.assert_allclose(got, want, rtol=1e-6, atol=1e-6)
def test_step_tables_and_solver_recursion(weights):
"""The y-recursion with (A, B, Cm) and the masked model output equals the node's DPM-Solver++(2M) with the
prefix constraint (dpm_solver_sample) on a deterministic stand-in model."""
tab = P.step_tables(weights.params)
assert tab.nfe == C.DPM_SOLVER_STEPS + 1 and len(tab.solver) == C.DPM_SOLVER_STEPS
assert len(tab.blocks) == tab.nfe and len(tab.blocks[0]) == C.DIT_DEPTH
rng = np.random.default_rng(2)
agents = 5
W = rng.standard_normal((324, 324)).astype(np.float32) * 0.05
cs = rng.standard_normal((agents, 4)).astype(np.float32)
x_T = rng.standard_normal((agents, 81, 4)).astype(np.float32)
def model(x, t):
return np.tanh(x.reshape(agents, -1) @ W + float(t)).reshape(agents, 81, 4).astype(np.float32)
ref = dpm_solver_sample(x_T, model, lambda x: apply_prefix_constraint(x, cs))
mask = np.ones((81, 4), np.float32)
mask[0] = 0
y = (x_T * mask).astype(np.float64)
m_prev = None
iters = []
for k in range(tab.nfe):
x = y + np.concatenate([cs[:, None], np.zeros((agents, 80, 4))], 1)
if k > 0:
iters.append(x)
m = model(x.astype(np.float32), tab.eval_times[k]).astype(np.float64) * mask
if k == tab.nfe - 1:
break
a, b, c = tab.solver[k]
y = a * y - b * m + (c * m_prev if m_prev is not None else 0.0)
m_prev = m
final = m + np.concatenate([cs[:, None], np.zeros((agents, 80, 4))], 1)
iters.append(final)
np.testing.assert_allclose(final, ref.final_x, rtol=1e-5, atol=1e-5)
for got, want in zip(iters, ref.denoising_steps):
np.testing.assert_allclose(got, want, rtol=1e-5, atol=1e-5)
assert np.allclose(tab.eval_times, ref.eval_times)
def test_plan_inputs_layout(prepared):
inp = I.plan_inputs(prepared)
assert set(inp) == set(I.INPUT_SPECS)
f = prepared.features
np.testing.assert_array_equal(inp["neighbor_x"][0], f.neighbor[:, 25:31])
assert not f.neighbor[:, :25].any() and not f.ego[6:].any() # the island's zero rows
np.testing.assert_array_equal(inp["ego_x"][0, 0], f.ego[:6])
row = inp["fusion_key_row"][0, 0, 0]
assert np.isneginf(row[T.TOKENS_REAL:]).all() and (row[:T.TOKENS_REAL][f.key_valid] == 0).all()
arow = inp["agent_key_row"][0, 0, 0]
assert np.isneginf(arow[C.MAX_NUM_AGENTS:]).all() and arow[0] == 0
cs, y0 = inp["cs"][0, 0], inp["y0"][0, 0]
np.testing.assert_array_equal(cs[:C.MAX_NUM_AGENTS, :4], prepared.decoder.current_states)
assert not cs[:, 4:].any() and not y0[:, :4].any() and not cs[C.MAX_NUM_AGENTS:].any()
np.testing.assert_array_equal(y0[:C.MAX_NUM_AGENTS, 4:], prepared.x_T.reshape(C.MAX_NUM_AGENTS, -1)[:, 4:])
warm = I.warmup_inputs()
assert all(warm[k].shape == v for k, v in I.INPUT_SPECS.items())
def test_compact_bucket_covers_every_needed_row():
"""``COMPACT``: the bucket holds the ego, every valid self-attention key and every emitted neighbour row (the
rows past it are masked keys, never read back), and falls back to the full 352 rows."""
from types import SimpleNamespace
from tt_diffusion_planner.tt.model import bucket_rows, needed_rows
def prep(valid_idx, emitted):
v = np.zeros(C.MAX_NUM_AGENTS, bool)
v[0] = True
v[list(valid_idx)] = True
nb = np.zeros(C.MAX_NUM_NEIGHBORS, bool)
nb[[i - 1 for i in valid_idx]] = True
return SimpleNamespace(decoder=SimpleNamespace(agent_valid=v), neighbor_rows=np.asarray(emitted, int),
features=SimpleNamespace(valid={"neighbor": nb}))
b = (32, 64, 96, 128, 192)
assert needed_rows(prep([], [])) == 1 and bucket_rows(prep([], []), b) == 32
assert needed_rows(prep([1, 2, 31], [0, 1, 30])) == 32 and bucket_rows(prep([31], []), b) == 32
assert bucket_rows(prep([32], []), b) == 64 # the 33rd row is a valid key
assert bucket_rows(prep([5], [70]), b) == 96 # emitted neighbour 70 = decoder row 71
assert bucket_rows(prep([200], []), b) == T.AGENTS
assert bucket_rows(prep([5], []), ()) == T.AGENTS # COMPACT off
p = prep([3], [])
p.features.valid["neighbor"][40] = True # a valid neighbour token (row 41)
assert needed_rows(p) == 42 and bucket_rows(p, b) == 64
|