test_verify_cap.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. """CPU checks for adapter/verify_cap.py and block_verify's live= path.
  2. 1. Exactness: on a toy language model (V=5, gamma=3), block verification with a per-step verify
  3. length chosen by a stopping rule (include draft j+1 iff prod_{i<=j} q_i(X_i) >= THR, i.e. it
  4. never looks at the draft it drops) still reproduces the target's joint distribution (chi-square).
  5. 2. The dead-row map sends rows past each request's live length to that request's anchor row.
  6. 3. The patches refuse drifted engine classes.
  7. """
  8. import itertools
  9. import math
  10. import os
  11. import types
  12. os.environ["DSV41_VERIFY_CAP"] = "3"
  13. import torch # noqa: E402
  14. import block_verify as bv # noqa: E402
  15. import verify_cap as vc # noqa: E402
  16. V, G = 5, 3
  17. N = int(os.environ.get("VC_TEST_N", "150000"))
  18. THR = 0.35
  19. gen = torch.Generator().manual_seed(1)
  20. table_p, table_q = {}, {}
  21. def p_of(prefix):
  22. key = tuple(prefix)
  23. if key not in table_p:
  24. table_p[key] = torch.softmax(torch.randn(V, generator=gen) * 1.5, -1)
  25. return table_p[key]
  26. def q_of(prefix):
  27. key = tuple(prefix)
  28. if key not in table_q:
  29. table_q[key] = torch.softmax(torch.log(p_of(prefix)) + 0.8 * torch.randn(V, generator=gen), -1)
  30. return table_q[key]
  31. def run(capped):
  32. out = torch.zeros(N, 3, dtype=torch.long)
  33. produced = torch.zeros(N, dtype=torch.long)
  34. seqs = [[] for _ in range(N)]
  35. acc = steps = 0
  36. while (produced < 3).any():
  37. act = [i for i in range(N) if produced[i] < 3]
  38. bs = len(act)
  39. X = torch.zeros(bs, G, dtype=torch.long)
  40. TP = torch.zeros(bs, G + 1, V)
  41. DP = torch.zeros(bs, G, V)
  42. for r, i in enumerate(act):
  43. pre = list(seqs[i])
  44. for j in range(G):
  45. DP[r, j], TP[r, j] = q_of(pre), p_of(pre)
  46. X[r, j] = int(torch.multinomial(DP[r, j], 1, generator=gen))
  47. pre.append(int(X[r, j]))
  48. TP[r, G] = p_of(pre)
  49. live = None
  50. if capped:
  51. cum = torch.cumprod(DP.gather(-1, X.unsqueeze(-1)).squeeze(-1), 1)
  52. live = 1 + (cum[:, :G - 1] >= THR).long().cumprod(1).sum(1)
  53. tau, corr = bv.block_accept_probs(X, TP, DP, live=live)
  54. acc += int(tau.sum())
  55. steps += bs
  56. for r, i in enumerate(act):
  57. for t in X[r, :int(tau[r])].tolist() + [int(corr[r])]:
  58. seqs[i].append(t)
  59. if produced[i] < 3:
  60. out[i, produced[i]] = t
  61. produced[i] += 1
  62. return out, acc / steps
  63. def chi2_z(out):
  64. counts = torch.zeros(V, V, V)
  65. for a, b, c in out.tolist():
  66. counts[a, b, c] += 1
  67. stat, dof = 0.0, 0
  68. for a, b, c in itertools.product(range(V), repeat=3):
  69. e = N * float(p_of([])[a] * p_of([a])[b] * p_of([a, b])[c])
  70. if e >= 5:
  71. stat += (counts[a, b, c] - e) ** 2 / e
  72. dof += 1
  73. dof -= 1
  74. return (stat - dof) / math.sqrt(2 * dof)
  75. def main():
  76. full, acc_full = run(False)
  77. cut, acc_cut = run(True)
  78. z_full, z_cut = chi2_z(full), chi2_z(cut)
  79. print(f"block: {acc_full:.4f} drafts/step (z={z_full:+.2f}); stopping-rule cut: {acc_cut:.4f} (z={z_cut:+.2f})")
  80. assert abs(z_full) < 4.5 and abs(z_cut) < 4.5, "output distribution differs from the target"
  81. assert acc_cut < acc_full, "the cut did not shorten any block"
  82. vc._state["live"] = torch.tensor([6, 2, 4] + [6] * (vc.MAX_BS - 3))
  83. vc._state["src"] = None
  84. src = vc._src_rows(18, "cpu").tolist()
  85. assert src[:6] == list(range(6))
  86. assert src[6:12] == [6, 7, 6, 6, 6, 6]
  87. assert src[12:18] == [12, 13, 14, 15, 12, 12]
  88. w, i = torch.rand(18, 6), torch.randint(0, 384, (18, 6))
  89. vc._state["src"] = None
  90. kernel, vc._remap_kernel = vc._remap_kernel, None # CPU: the index_select path
  91. w2, i2 = vc.remap_dead_rows(w, i)
  92. vc._remap_kernel = kernel
  93. assert torch.equal(i2[8], i[6]) and torch.equal(w2[17], w[12]) and torch.equal(i2[13], i[13])
  94. for fn, arg in ((vc.install_verify, types.SimpleNamespace()), (vc.install_model, types.SimpleNamespace())):
  95. try:
  96. fn(arg)
  97. except AttributeError:
  98. continue
  99. raise AssertionError(f"{fn.__name__} accepted a drifted module")
  100. print("test_verify_cap: ok")
  101. if __name__ == "__main__":
  102. main()