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.py の TokenVocab.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 > 50 → P2-2 (決定論的 ID 割当) を直接実装するのが妥当。
- P2-2 を実装済み (
TokenVocab.from_card_db)。vocab.size = 18631、real collision = 0 を確認。