Spaces:
Running on Zero
Running on Zero
File size: 8,678 Bytes
d58cfe6 | 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 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | """Trace geometry, segmentation and persistence, without a model."""
import numpy as np
import pytest
import ulens
from ulens import runtime
from ulens.runtime import Trace, _segment
OPEN, CLOSE = 900, 901
def make_trace(n_blocks=5, T=20, k=4, prompt_len=6, eot=14, seed=0, **kw):
rng = np.random.default_rng(seed)
opts: dict = {
"model": "qwen3.5-4b", "prompt": "p",
"tokens": list(range(T)), "token_strs": [f"t{i}" for i in range(T)],
"prompt_len": prompt_len, "eot": eot,
"entropy": np.concatenate(
[[np.nan], rng.uniform(0.1, 2.0, size=T - 1)]).astype(np.float32),
"field": rng.normal(size=(n_blocks, T, k)).astype(np.float32),
"blocks": list(range(10, 10 + n_blocks)),
"names": ulens.terms.categories()[:k],
"readout_block": 12, "generated": "g", "truncated": False,
"config": {"seed": 1}}
opts.update(kw)
return Trace(**opts)
# ------------------------------------------------------------------ segmentation
def test_block_already_open_at_the_prompt_end():
"""The usual case: the chat template opens the channel, the generation closes it."""
ids = [1, 2, OPEN] + [5, 6, 7, CLOSE, 8, 9]
eot, trunc = _segment(ids, 3, OPEN, CLOSE)
assert eot == 6 and ids[eot] == CLOSE and not trunc
def test_block_opened_inside_the_generation():
ids = [1, 2, 3] + [OPEN, 5, 6, CLOSE, 8]
eot, trunc = _segment(ids, 3, OPEN, CLOSE)
assert eot == 6 and ids[eot] == CLOSE and not trunc
def test_opened_and_never_closed_is_all_thinking():
"""What a token budget produces. It must not read as a zero-token answer."""
ids = [1, 2, OPEN] + [5, 6, 7, 8]
eot, trunc = _segment(ids, 3, OPEN, CLOSE)
assert eot == len(ids) - 1 and trunc
tr = make_trace(T=len(ids), prompt_len=3, eot=eot, truncated=trunc)
assert tr.think_len == len(ids) - 3
assert (tr.segments == "answer").sum() == 0
def test_no_thinking_block_at_all_is_all_answer():
ids = [1, 2, 3] + [5, 6, 7]
eot, trunc = _segment(ids, 3, OPEN, CLOSE)
assert eot == 2 and not trunc
tr = make_trace(T=6, prompt_len=3, eot=eot)
assert tr.think_len == 0
assert list(tr.segments) == ["prompt"] * 3 + ["answer"] * 3
def test_a_closer_in_the_prompt_does_not_count():
"""A prompt that mentions the closer must not end the block before it starts."""
ids = [1, CLOSE, 2, OPEN] + [5, 6, CLOSE, 9]
eot, _ = _segment(ids, 4, OPEN, CLOSE)
assert eot == 6
def test_a_reopened_block_uses_the_last_opener():
ids = [OPEN, 1, CLOSE, 2, OPEN] + [5, 6, CLOSE]
eot, trunc = _segment(ids, 5, OPEN, CLOSE)
assert eot == 7 and not trunc
def test_without_a_closer_token_everything_is_thinking():
ids = [1, 2, 3, 4, 5]
eot, trunc = _segment(ids, 2, OPEN, None)
assert eot == len(ids) - 1 and trunc
# ------------------------------------------------------------------ geometry
def test_segments_partition_the_sequence():
tr = make_trace()
s = tr.segments
assert len(s) == tr.n_tokens
assert (s == "prompt").sum() == tr.prompt_len
assert (s == "think").sum() == tr.think_len
assert (s == "answer").sum() == tr.n_tokens - tr.eot - 1
def test_a_cone_series_matches_the_readout_at_the_eot():
tr = make_trace()
assert tr.a_cone == pytest.approx(tr.a_cone_series()[tr.eot])
z = tr.projections()[tr.eot]
assert tr.a_cone == pytest.approx(float(ulens.a_cone_of(z)))
def test_the_field_agrees_with_a_cone_computed_from_states():
"""a_cone_of on stored projections equals a_cone on the states they came from."""
rng = np.random.default_rng(1)
U = ulens._math.lowdin(rng.normal(size=(32, 4)).astype(np.float32))
H = rng.normal(size=(7, 32)).astype(np.float32)
z = ulens.projections(H, U)
np.testing.assert_allclose(ulens.a_cone_of(z), ulens.a_cone(H, U), rtol=1e-6)
def test_surface_is_a_cone_over_depth_and_token():
tr = make_trace()
S = tr.surface()
assert S.shape == (len(tr.blocks), tr.n_tokens)
np.testing.assert_allclose(S[tr.block_index()], tr.a_cone_series(), rtol=1e-6)
def test_components_are_the_rectified_projections():
tr = make_trace()
np.testing.assert_allclose(tr.components(), ulens.rectify(tr.projections()), rtol=1e-6)
assert np.all(tr.components() > 0)
def test_think_entropy_spans_the_thinking_block_inclusive_of_the_eot():
tr = make_trace()
want = tr.entropy[tr.prompt_len: tr.eot + 1].mean()
assert tr.think_entropy == pytest.approx(want)
assert tr.u_lens == pytest.approx(tr.a_cone * tr.think_entropy)
def test_the_first_token_has_no_entropy_and_it_is_excluded():
"""Nothing predicted token 0, so its entropy is NaN — and must not reach the mean."""
tr = make_trace()
assert np.isnan(tr.entropy[0])
assert not np.isnan(tr.think_entropy)
def test_an_uncaptured_block_is_refused():
tr = make_trace()
with pytest.raises(KeyError, match="was not captured"):
tr.projections(999)
def test_summary_and_axis_table_render():
tr = make_trace()
assert "U-Lens" in tr.summary() and "readout block 12" in tr.summary()
assert "closed" in tr.summary() and tr.readout_valid
assert "argmax" in tr.axis_table()
def test_a_truncated_trace_says_the_readout_is_not_the_published_one():
tr = make_trace(truncated=True)
assert not tr.readout_valid
assert "TRUNCATED" in tr.summary() and "not the published one" in tr.summary()
assert np.isfinite(tr.u_lens) # still computed; the field is real
def test_a_trace_with_no_thinking_block_is_not_a_valid_readout():
tr = make_trace(prompt_len=6, eot=5)
assert tr.think_len == 0 and not tr.readout_valid
# ------------------------------------------------------------------ persistence
def test_trace_round_trips_and_needs_no_model(tmp_path):
tr = make_trace()
back = runtime.load(tr.save(tmp_path / "t.npz"))
np.testing.assert_allclose(back.field, tr.field)
np.testing.assert_array_equal(np.isnan(back.entropy), np.isnan(tr.entropy))
assert back.u_lens == pytest.approx(tr.u_lens)
assert back.tokens == tr.tokens and back.token_strs == tr.token_strs
assert back.blocks == tr.blocks and back.names == tr.names
assert back.eot == tr.eot and back.truncated == tr.truncated
assert back.config == tr.config
def test_the_sidecar_carries_the_scores(tmp_path):
import json
tr = make_trace()
p = tr.save(tmp_path / "t.npz")
side = json.loads(p.with_suffix(".json").read_text())
assert side["scores"]["u_lens"] == pytest.approx(tr.u_lens)
assert side["readout_block"] == tr.readout_block
def test_the_sidecar_is_strict_json_without_a_thinking_block(tmp_path):
"""Thinking off: no thinking-block entropy, no score. The sidecar says null, not NaN."""
import json
tr = make_trace(prompt_len=6, eot=5)
assert tr.think_len == 0
p = tr.save(tmp_path / "t.npz")
text = p.with_suffix(".json").read_text()
assert "NaN" not in text
side = json.loads(text, parse_constant=lambda c: pytest.fail(f"non-finite {c} in sidecar"))
assert side["scores"]["think_entropy"] is None and side["scores"]["u_lens"] is None
assert side["scores"]["a_cone"] == pytest.approx(tr.a_cone)
# ------------------------------------------------------------------ cone stacks
def test_cone_stack_round_trips_and_slices(tmp_path):
rng = np.random.default_rng(2)
cats = ulens.terms.categories()
cones = {b: ulens.axes.from_axes(
[ulens.Axis(c, rng.normal(size=12).astype(np.float32)) for c in cats],
model="m", block=b) for b in (3, 5, 9)}
st = ulens.axes.stack(cones, model="m", meta={"terms_version": "v1"})
assert st.blocks == [3, 5, 9] and st.U.shape == (3, 12, 4)
assert st.nearest(6) == 5 and st.nearest(100) == 9
np.testing.assert_array_equal(st.at(5).U, cones[5].U)
back = ulens.axes.load_stack(st.save(tmp_path / "s.npz"))
np.testing.assert_allclose(back.U, st.U)
assert back.blocks == st.blocks and back.names == st.names
def test_cone_stack_rejects_mismatched_axis_names():
v = np.ones(8, np.float32)
a = ulens.axes.from_axes([ulens.Axis("x", v)], block=1)
b = ulens.axes.from_axes([ulens.Axis("y", v)], block=2)
with pytest.raises(ValueError, match="expected"):
ulens.axes.stack({1: a, 2: b})
with pytest.raises(ValueError, match="at least one cone"):
ulens.axes.stack({})
def test_stack_index_refuses_an_uncovered_block():
v = np.ones(8, np.float32)
st = ulens.axes.stack({4: ulens.axes.from_axes([ulens.Axis("x", v)], block=4)})
with pytest.raises(KeyError, match="not in this stack"):
st.index(5)
|