コンテンツにスキップ

Token hash 衝突率レポート (P1-1)

計測日: 2026-06-28

2026-06-28-policy-value-model-refactor.md の P1-1 (衝突率実測) の結果。

計測方法

../../scripts/measure_token_collisions.py を実行。CABT を介した実 self-play ではなく、encoder の token 化ロジック (../../src/pca/features/encoder.py) から到達しうる (prefix, value) ペアを (EN_Card_Data.csv の 1267 カードを含めて) 保守的に列挙し、各 TOKEN_BUCKETS 値で stable_token を呼んで衝突を集計している。

実 game では未到達の値が混ざる可能性があるが、衝突源の主役 (card 関連) は完全列挙できているため、本数字は実際よりむしろやや厳しめ (悲観的) な見積もり。

カテゴリ別 unique 文字列数

カテゴリ unique 文字列
card 4451
zone 89
pokemon_state 78
player_state 230
log 960
current 114
option 846
合計 (重複除く) 6768

バケット数別 衝突統計

TOKEN_BUCKETS unique str 使用 bucket 衝突 bucket 衝突した str max str/bucket load factor
8192 6768 4592 1642 3818 5 0.826
16384 6768 5543 1073 2298 5 0.413
32768 6768 6093 620 1295 4 0.207
65536 6768 6448 307 627 3 0.103
131072 6768 6583 183 368 3 0.052
  • 衝突 bucket: ≥2 個の文字列が同居している bucket の数。
  • 衝突した str: そのような bucket に入っている文字列の総数 (NN 上で Embedding を強制共有させられる文字列数)。
  • load factor: unique_strings / buckets。0.1 を超えると衝突が顕在化しやすい。

衝突サンプル (TOKEN_BUCKETS = 8192, 上位 15 bucket)

unique_strings が多い順に抜粋。意味的に無関係な prefix が同居していることが見て取れる。

  • bucket 151 (5 strings): card:1001, log.cardId:1103, log.cardId:1182, log.value:385, option.cardId:217
  • bucket 205 (5 strings): card:115, card:541, card:64, option.attackId:153, option.attackId:318
  • bucket 1381 (5 strings): card:59, log.cardId:546, log.cardId:624, log.value:53, opp.paralyzed:False
  • bucket 2515 (5 strings): card:551, card_attack_cost:{W}{L}, log.cardId:130, log.cardId:464, log.value:146
  • bucket 2968 (5 strings): card:118, card_prev_stage:Nymble, option.attackId:33, option.cardId:620, option.cardId:737
  • bucket 3746 (5 strings): card:1083, card:666, log.attackId:70, log.cardId:188, turn_action_count:36
  • bucket 4289 (5 strings): log.cardId:477, log.value:231, opp.deck_count:24, opp.deck_count:9, option.cardId:688
  • bucket 5383 (5 strings): card_category:Trainer's Pokémon(Hop), log.attackId:137, log.value:105, log.value:220, option.cardId:112
  • bucket 5770 (5 strings): card:60, card:869, log.attackId:279, option.cardId:825, turn:24
  • bucket 6409 (5 strings): card:1231, log.cardId:349, log.fromArea:12, log.value:301, option.cardId:678
  • bucket 6950 (5 strings): card:163, option.attackId:104, option.attackId:69, option.cardId:965, self.deck_count:16
  • bucket 7156 (5 strings): log.attackId:53, log.cardId:554, log.cardId:850, log.cardId:871, option.cardId:1238
  • bucket 7509 (5 strings): card:1258, log.value:208, log.value:362, option.cardId:221, option.cardId:561
  • bucket 37 (4 strings): card:607, card:662, log.cardId:467, option.cardId:1197
  • bucket 440 (4 strings): log.attackId:75, log.attackId:94, log.cardId:29, option.attackId:297

P2-2 検証: 決定論的 TokenVocab

../../src/pca/features/vocab.pyTokenVocab.from_card_db で生成した vocab で同じ全 string を ID 化したときの統計。card, log.cardId, option.cardId意図的に同じ ID を共有するので、「同一 (family, value) で複数 prefix が同居している bucket」は real collision に数えない。

指標
vocab.size 18631
unique 文字列 6768
割り当てユニーク ID 4232
real collision bucket 0
real collision strings 0
意図的共有 bucket (card_id 等) 1268
fallback hash に落ちた string 0

real collision = 0 が成立。Embedding サイズも 18631 で済み、現状 8192 比 2.2 倍程度。予期せぬ prefix への fallback hash 経路は残してあるので安全弁も担保される。

結論

  • 現在 (TOKEN_BUCKETS = 8192): unique 文字列 6768 / 衝突 bucket 1642 / 衝突文字列 3818 / 最大同居 5 / load factor 0.83
  • 65536 への引き上げ案 (P2-1): 衝突 bucket 307 / 衝突文字列 627 / load factor 0.103

P2 への示唆 (refactor plan の P2-2 セクション 判断ルールに照らして)

  • 衝突 bucket 1642 > 50P2-2 (決定論的 ID 割当) を直接実装するのが妥当。
  • P2-2 を実装済み (TokenVocab.from_card_db)。vocab.size = 18631、real collision = 0 を確認。