2025, Sep 25 15:34

BGE‑M3 और Hugging Face टोकनाइज़र के साथ llama_index में सुसंगत टोकन गिनती

llama_index में BGE‑M3 की टोकन गिनती असंगति का कारण और समाधान: Hugging Face टोकनाइज़र, एडॉप्टर, TokenCountingHandler के साथ सटीक काउंटिंग सेटअप गाइड.

आपके टोकनाइज़र और एम्बेडिंग पाइपलाइन के बीच टोकन गिनती को संरेखित करना बैचिंग, चंकिंग और लागत आकलन को बना या बिगाड़ सकता है। यदि संख्याएँ मेल नहीं खातीं, तो आप या तो बहुत आक्रामक रूप से ट्रंकेट कर देते हैं या सीमाएँ लांघ जाते हैं। एक आम समस्या तब पैदा होती है जब कोई लाइब्रेरी टोकन गिनने के लिए उस टोकनाइज़र का उपयोग करती है जो उस मॉडल से अलग होता है जिसके साथ आप वास्तव में एम्बेड करते हैं। नीचे llama_index में BGE‑M3 के साथ यह क्यों होता है और बिना एम्बेडिंग स्टेप चलाए स्थिर गिनती कैसे पाएं, उसका संक्षिप्त मार्गदर्शन है।

असंगति को पुनः उत्पन्न करना

निम्न स्निपेट llama_index के टोकन काउंटर और BGE‑M3 के लिए Hugging Face टोकनाइज़र के बीच टोकन गिनती में अंतर दिखाता है। यह लोकल कैश मौजूद हो तो (./embeddings) उसी का उपयोग करता है, अन्यथा मॉडल डाउनलोड कर लेता है।

from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager, TokenCountingHandler
from transformers import AutoTokenizer
import os
# उदाहरण पाठ
sample_text = "Random words. This is a test! A very exciting test, indeed."
# चंक की लंबाई
max_chunk = 512
# एम्बेडिंग बैकएंड बनाएँ या लोड करें
def build_encoder(_limit=None):
    print("initializing embeddings...")
    if os.path.exists('./embeddings/models--BAAI--bge-m3'):
        cache_dir = f"./embeddings/models--BAAI--bge-m3/snapshots/{os.listdir('./embeddings/models--BAAI--bge-m3/snapshots')[0]}"
        encoder = HuggingFaceEmbedding(model_name=cache_dir)
    else:
        os.makedirs("./embeddings", exist_ok=True)
        repo_name = "BAAI/bge-m3"
        encoder = HuggingFaceEmbedding(
            model_name=repo_name,
            max_length=_limit,
            cache_folder='./embeddings'
        )
    print("embeddings ready")
    return encoder
# एम्बेडर इनिशियलाइज़ करें
embedding_backend = build_encoder(_limit=max_chunk)
# डिफ़ॉल्ट कॉन्फ़िगरेशन वाला टोकन काउंटर
count_hook = TokenCountingHandler()
hooks = CallbackManager([count_hook])
Settings.embed_model = embedding_backend
Settings.callback_manager = hooks
# एक एम्बेडिंग बनाएं और कॉलबैक के जरिए टोकन गिनें
_ = Settings.embed_model.get_text_embedding(sample_text)
embed_tok_count = count_hook.total_embedding_token_count
# HF टोकनाइज़र से टोकन गिनें
model_ref = "BAAI/bge-m3"
hf_tok = AutoTokenizer.from_pretrained(model_ref)
encoded = hf_tok(sample_text)
hf_tok_count = len(encoded["input_ids"])
print(f"Original text: {sample_text}")
print(f"Embedding pipeline token count: {embed_tok_count}")
print(f"HF tokenizer token count: {hf_tok_count}")

इस सेटअप में आप देखेंगे कि संख्याएँ भिन्न हैं।

असल में हो क्या रहा है

मुख्य मुद्दा यह है कि जब तक आप स्पष्ट रूप से ओवरराइड न करें, llama_index की टोकन गिनती आपका मॉडल वाला टोकनाइज़र उपयोग नहीं करती। डिफ़ॉल्ट रूप से यह tiktoken-आधारित टोकनाइज़र होता है। रनटाइम पर llama_index में क्या कॉन्फ़िगर है, यह आप इस तरह देख सकते हैं:

from llama_index.core import Settings
print(Settings.tokenizer)

आउटपुट में tiktoken एन्कोडर दिखाई देता है:

functools.partial(<bound method Encoding.encode of <Encoding 'cl100k_base'>>, allowed_special='all')

वहीं दूसरी ओर, BGE‑M3 Hugging Face टोकनाइज़र का उपयोग करता है। AutoTokenizer बस एक फ़ैक्टरी है, और BGE‑M3 के लिए यह XLMRobertaTokenizer पर रेज़ॉल्व होता है। टोकनाइज़ेशन नियमों का यही फर्क गिनतियों के अलग होने की वजह है।

समाधान: गिनती के लिए मॉडल का टोकनाइज़र ही इस्तेमाल करें

एम्बेडिंग स्टेप चलाए बिना मेल खाती गिनती पाने के लिए, मॉडल के टोकनाइज़र को llama_index के TokenCountingHandler में जोड़ें। एक बारीकी ध्यान देने योग्य है: llama_index में TokenCounter अपेक्षा करता है कि टोकनाइज़र input_ids की सूची लौटाए, जबकि Hugging Face टोकनाइज़र एक BatchEncoding ऑब्जेक्ट लौटाते हैं। एक छोटा‑सा एडॉप्टर इसे केवल input_ids वापस करके सुलझा देता है।

from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import Settings
from llama_index.core.callbacks import CallbackManager, TokenCountingHandler
from transformers import AutoTokenizer
from transformers import XLMRobertaTokenizerFast
# पाठ और मॉडल
sample_text = "Random words. This is a test! A very exciting test, indeed."
max_chunk = 512
model_ref = "BAAI/bge-m3"
# एडॉप्टर ताकि llama_index का काउंटर input_ids की सूची पाए
class LlamaIndexTokenizerShim(XLMRobertaTokenizerFast):
    def __call__(self, *args, **kwargs):
        return super().__call__(*args, **kwargs).input_ids
li_tokenizer = LlamaIndexTokenizerShim.from_pretrained(model_ref)
# HF एम्बेडर इनिशियलाइज़ करें
embedder = HuggingFaceEmbedding(model_name=model_ref, max_length=max_chunk)
# सही टोकनाइज़र को टोकन काउंटर से जोड़ें
counter = TokenCountingHandler(tokenizer=li_tokenizer)
manager = CallbackManager([counter])
Settings.embed_model = embedder
Settings.callback_manager = manager
# एम्बेडिंग कॉल के जरिए काउंटिंग ट्रिगर करें (डिफ़ॉल्ट टोकनाइज़र पर निर्भर होने की ज़रूरत नहीं)
_ = Settings.embed_model.get_text_embedding(sample_text)
li_count = counter.total_embedding_token_count
# सीधे HF के टोकनाइज़र से क्रॉस-चेक करें
hf_tokenizer = AutoTokenizer.from_pretrained(model_ref)
ref_count = len(hf_tokenizer(sample_text).input_ids)
print(f"Original text: {sample_text}")
print(f"Embedding pipeline token count: {li_count}")
print(f"HF tokenizer token count: {ref_count}")

इस बदलाव के बाद, दोनों गिनतियाँ एक‑जैसी हो जाती हैं।

यह क्यों मायने रखता है

सुसंगत टोकन गिनती तब महत्वपूर्ण होती है जब आप कंप्यूट बजट बनाते हैं, दस्तावेज़ों को टुकड़ों में विभाजित करते हैं, या max_length जैसी सीमाएँ लागू करते हैं। यदि काउंटर और मॉडल असहमत हों, तो आपकी बैचिंग लॉजिक अविश्वसनीय हो जाती है, जिससे अतिरिक्त कॉल, अप्रत्याशित ट्रंकेशन, या उपयोग मीट्रिक्स की गलत रिपोर्टिंग हो सकती है।

मुख्य सीख

एम्बेडिंग वर्कफ़्लो में टोकन गिनते समय अपना टोकनाइज़र स्पष्ट रूप से निर्दिष्ट करें। यदि आपका टूलिंग मॉडल से अलग किसी डिफ़ॉल्ट टोकनाइज़र पर टिका है, तो सही टोकनाइज़र प्लग करें और आवश्यक हो तो रिटर्न टाइप को अनुकूलित करें। जैसे ही दोनों पक्ष एक ही टोकनाइज़ेशन बोली का उपयोग करते हैं, गिनतियाँ मेल खाती हैं और आगे की लॉजिक भरोसेमंद रहती है।

यह लेख StackOverflow के प्रश्न (द्वारा ManBearPigeon) और cronoik के उत्तर पर आधारित है।