""" Проверка: попали ли новые кастомные токены (включая 4 tool-calling токена <|tool_call_start|>/<|tool_call_end|>/<|tool_result_start|>/<|tool_result_end|>) в зарезервированные слоты эмбеддинг-матрицы чекпоинта JiRack, или токенизатор физически расширился поверх неё. Ничего передавать не нужно -- просто: python3 check_tokenizer_reserve.py Скрипт сам обходит известные места на сервере и находит: - папку токенизатора (пробует несколько имён и папок) - .pt чекпоинт модели 8B Precision (первый подходящий найденный файл) Если хочешь всё же указать пути вручную -- можно как раньше: python3 check_tokenizer_reserve.py <путь_к_токенизатору> <путь_к_checkpoint.pt> """ import sys import os import glob EMB_KEY = "token_emb.weight" HEAD_KEY = "lm_head.weight" TOOL_TOKENS = ["<|tool_call_start|>", "<|tool_call_end|>", "<|tool_result_start|>", "<|tool_result_end|>"] TOKENIZER_SEARCH_DIRS = [ ".", "./qwen_ji_router_tokenizer", "./ji_precision_tokenizer", "/mnt/nfs_share/Qweb2_5_Tokenizer/qwen_ji_router_tokenizer", "/mnt/nfs_share/Qweb2_5_Tokenizer/ji_precision_tokenizer", "/mnt/nfs_share/Qweb2_5_Tokenizer", ] CHECKPOINT_SEARCH_GLOBS = [ "/mnt/nfs_share/JiRackPrecision_8b/*.pt", "/mnt/nfs_share/JiRackPrecision_8b/**/*.pt", "./*.pt", ] def looks_like_tokenizer_dir(d): if not os.path.isdir(d): return False names = os.listdir(d) return any(n in names for n in ("tokenizer_config.json", "tokenizer.json", "vocab.json")) def find_tokenizer_dir(): for d in TOKENIZER_SEARCH_DIRS: if looks_like_tokenizer_dir(d): return d return None def find_checkpoint(): for pattern in CHECKPOINT_SEARCH_GLOBS: hits = sorted(glob.glob(pattern, recursive=True)) if hits: return hits[0] return None def resolve_paths(argv): tok_path = argv[1] if len(argv) > 1 else None ckpt_path = argv[2] if len(argv) > 2 else None if not tok_path: tok_path = find_tokenizer_dir() if tok_path: print(f"(токенизатор не указан -- нашёл автоматически: {tok_path})") else: print("!!! Не смог автоматически найти папку токенизатора нигде из известных мест.") print(" Передай путь вручную первым аргументом.") sys.exit(1) if not ckpt_path: ckpt_path = find_checkpoint() if ckpt_path: print(f"(чекпоинт не указан -- нашёл автоматически: {ckpt_path})") else: print("(чекпоинт не найден автоматически -- сверка с реальной моделью будет пропущена)") return tok_path, ckpt_path def main(): from transformers import AutoTokenizer tok_path, ckpt_path = resolve_paths(sys.argv) print(f"\nЗагружаю токенизатор: {tok_path}") tok = AutoTokenizer.from_pretrained(tok_path) vocab = tok.get_vocab() all_ids = list(vocab.values()) max_id = max(all_ids) required_rows = max_id + 1 print(f" len(tokenizer) = {len(tok)}") print(f" max token id = {max_id}") print(f" required emb rows = {required_rows} (max_id + 1)") if len(all_ids) != len(set(all_ids)): print(" !!! ОШИБКА: два разных токена делят один и тот же id. " "Токенизатор сломан, надо пересобрать до дальнейших шагов.") sys.exit(1) print(" все id уникальны -- ок") added = tok.get_added_vocab() if added: a_ids = sorted(added.values()) print(f" добавленных спецтокенов: {len(added)}, id диапазон [{a_ids[0]} .. {a_ids[-1]}]") else: print(" ВНИМАНИЕ: get_added_vocab() пуст -- проверь, тот ли это токенизатор.") print("\n--- Проверка tool-calling токенов ---") all_tool_tokens_present = True for t in TOOL_TOKENS: if t in vocab: print(f" {t:25s} id={vocab[t]:6d} [найден]") else: all_tool_tokens_present = False print(f" {t:25s} НЕ НАЙДЕН в словаре! -> это не свежий токенизатор, " f"перегенерируй через make_tokenizator.py") if not ckpt_path: print("\n(Чекпоинт не найден -- сверки с реальной моделью не будет.)") return import torch print(f"\nЗагружаю чекпоинт: {ckpt_path}") ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False) sd = ckpt["model"] if "model" in ckpt else ckpt if EMB_KEY not in sd or HEAD_KEY not in sd: print(f" !!! Не нашёл ключи {EMB_KEY!r}/{HEAD_KEY!r} в state_dict. " f"Ключи в наличии (первые 10): {list(sd.keys())[:10]}") sys.exit(1) have_rows, hidden = sd[EMB_KEY].shape head_rows, head_hidden = sd[HEAD_KEY].shape print(f" {EMB_KEY}: [{have_rows}, {hidden}]") print(f" {HEAD_KEY}: [{head_rows}, {head_hidden}]") if have_rows != head_rows: print(f" !!! Несогласованность: token_emb {have_rows} строк, lm_head {head_rows} строк. " f"Это чинить раньше, чем что-либо ещё.") sys.exit(1) print("\n================ ВЕРДИКТ ================") if required_rows <= have_rows: spare = have_rows - required_rows print(f"RESIZE НЕ НУЖЕН.") print(f" Токенизатору нужно {required_rows} строк, в чекпоинте есть {have_rows}.") print(f" Все добавленные токены умещаются в резерв ({spare} строк ещё свободно).") print(f" VOCAB_SIZE в JiRackTernaryUltra_*.py / JiRackPrecision_*.py оставить как есть: {have_rows}") else: extra = required_rows - have_rows print(f"RESIZE ОБЯЗАТЕЛЕН.") print(f" Токенизатору нужно {required_rows} строк, в чекпоинте только {have_rows}.") print(f" Не хватает {extra} строк -> нужно расширять token_emb.weight и lm_head.weight.") if not all_tool_tokens_present: print("\nПРИМЕЧАНИЕ: не все tool-calling токены найдены -- перегенерируй токенизатор прежде чем доверять вердикту выше.") if __name__ == "__main__": main()