Buckets:
| # WordPiece tokenization | |
| <CourseFloatingBanner chapter={6} | |
| classNames="absolute z-10 right-0 top-0" | |
| notebooks={[ | |
| {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section6.ipynb"}, | |
| {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section6.ipynb"}, | |
| ]} /> | |
| WordPiece เป็นอัลกอริทึมสำหรับ tokenization ที่สร้างโดย Google เพื่อ pretrain โมเดล BERT หลังจากนั้นมันได้ถูกนำมาใช้กับโมเดลประเภท Transformer หลายตัวที่เป็นประเภทเดียวกับ BERT เช่น DistilBERT, MobileBERT, Funnel Transformers, และ MPNET | |
| WordPiece มีความคล้ายกับ BPE ในวิธีการเทรน แต่วิธีการแยกคำนั้นแตกต่างกัน | |
| <Youtube id="qpv6ms_t_1A"/> | |
| > [!TIP] | |
| > 💡 บทนี้จะพูดถึง WordPiece อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น | |
| ## Training algorithm | |
| > [!WARNING] | |
| > ⚠️ เนื่องจาก Google ไม่เปิดเผยโค้ดสำหรับการเทรน WordPiece ดังนั้นโค้ดที่เราจะสอนคุณต่อจากนี้ มาจากการพยายามทำตามข้อมูลที่บอกไว้ใน paper แปลว่าโค้ดอาจจะไม่แม่นยำ 100% | |
| เช่นเดียวกับ BPE อัลกอริทึม WordPiece เริ่มจาก vocabulary ขนาดเล็ก ที่ประกอบไปด้วย token พิเศษที่โมเดลใช้ และตัวอักษรตั้งต้น | |
| เพื่อที่โมเดลจะได้รู้ว่าคำไหนเป็นคำย่อย มันจะเขียน prefix เช่น `##` (ใช้ใน BERT) ไว้ข้างหน้าของแต่ละคำย่อย ในขั้นตอนแรก แต่ละคำจะถูกแบ่งออกเป็นตัวอักษร โดยตัวอักษรที่ไม่ใช่ตัวแรกจะมี prefix นี้ | |
| ตัวอย่างเช่น คำว่า `"word"` จะถูกแบ่งดังนี้ : | |
| ``` | |
| w ##o ##r ##d | |
| ``` | |
| ดังนั้น vocabulary ตั้งต้น จะประกอบไปด้วยทุกๆตัวอักษรที่อยู่เริ่มต้นของแต่ละคำ และตัวอักษรอื่นๆที่อยู่ข้างในคำนั้น ซึ่งนำหน้าด้วย prefix พิเศษ | |
| เช่นเดียวกันกับ BPE เป้าหมายในการเทรน WordPiece คือเรียนกฎเพื่อการ merge แต่ความแตกต่างคือหลักการในการเลือกคู่ token ที่จะนำมา merge แทนที่จะเลือกคู่ที่พบบ่อยที่สุด WordPiece จะคำนวณ score ให้แต่ละคู่ โดยใช้สูตรต่อไปนี้ | |
| $$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ | |
| การที่เราหารความถี่ของคู่ token ด้วยผลคูณของความถี่ของแต่ละ token ในคู่ จะทำให้อัลกอริทึมให้คะแนนคู่ ที่แต่ละ token มีความถี่ไม่สูง | |
| ตัวอย่างเช่น เราไม่จำเป็นจำต้อง merge `("un", "##able")` ถึงแม้ว่าคู่นี้จะมีจำนวนมากที่สุดใน vocabulary เพราะว่าทั้ง `"un"` และ `"##able"` ต่างพบได้กับคำอื่นๆด้วย และแต่ละตัวก็มีจำนวนค่อนข้างสูง | |
| ตรงกันข้ามกับ คู่เช่น `("hu", "##gging")` ซึ่งอาจจะถูกรวมเร็วกว่า (ในกรณีที่ "hugging" มีจำนวนสูงใน vocabulary ) เพราะว่า ทั้ง`"hu"` และ `"##gging" ต่างก็พบได้ไม่บ่อย | |
| เราจะใช้ตัวอย่าง เดียวกันกับที่เราใช้ใน BPE : | |
| ``` | |
| ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) | |
| ``` | |
| หลังจากการแยกแต่ละคำ เราจะได้ : | |
| ``` | |
| ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) | |
| ``` | |
| ดังนั้น vocabulary ตั้งต้น คือ `["b", "h", "p", "##g", "##n", "##s", "##u"]` (เราขอละไม่พูดถึง token พิเศษในที่นี้) | |
| คู่ที่พบบ่อยที่สุดคือ `("##u", "##g")` ซึ่งพบ 20 ครั้ง แต่ว่าถ้านับจำนวนของแต่ละ token `"##u"` จะมีจำนวนค่อนข้างสูง ทำให้ score ของคู่นี้ไม่ได้สูงที่สุด (1 / 36) | |
| ทุกๆคู่ที่ประกอบด้วย `"##u"` จะได้ score เดียวกันซึ่งคือ (1 / 36) ดังนั้น score ที่สูงที่สุดจึงมาจากคู่ `("##g", "##s")` เพราะว่ามันไม่มี `"##u"` ซึ่งมี score เป็น 1 / 20 และกฎแรกที่เราได้ก็คือ `("##g", "##s") -> ("##gs")` | |
| โปรดสังเกตว่า เวลาที่เรา merge เราจะลบ ตัว `##` ออกระหว่าง token สองตัวที่เราต้องการจะ merge แปลว่าเราจะได้ `"##gs"` และเราจะเพิ่ม token นี้เข้าไปใน vocabulary จากนั้นเราก็จะใช้กฎนี้กับทุกๆคำใน corpus ด้วย : | |
| ``` | |
| Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] | |
| Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) | |
| ``` | |
| ตอนนี้ `"##u"` มีอยู่ในทุกๆคู่ แปลว่า ทุกคู่จะมี score เท่ากัน | |
| ในกรณีนี้ เราจะเลือกกฎใดกฎหนึ่งเพื่อ merge ต่อไป เราจะเลือก `("h", "##u") -> "hu"` และได้ผลลัพธ์ต่อไปนี้ : | |
| ``` | |
| Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] | |
| Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) | |
| ``` | |
| ตอนนี้คู่ที่มี score สูงที่สุดคือ `("hu", "##g")` และ `("hu", "##gs")` ซึ่งทั้งสองมี score เท่ากับ 1/15 (ส่วนตัวอื่นๆที่เหลือ มี score เท่ากับ 1/21) เราจะเลือกคู่แรกใน list มาใช้ เพื่อ merge : | |
| ``` | |
| Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] | |
| Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) | |
| ``` | |
| เราจะทำแบบนี้จนกว่าจะได้ vocabulary ที่มีขนาดใหญ่มากพอ | |
| > [!TIP] | |
| > ✏️ **ตาคุณบ้างแล้ว!** กฎ merge ต่อไปคืออะไร | |
| ## Tokenization algorithm | |
| การ tokenization ใน WordPiece แตกต่างจาก BPE ตรงที่ WordPiece จะบันทึกเฉพาะ vocabulary สุดท้ายเท่านั้น และไม่ได้บันทึก กฎ merge | |
| หากเราจะ tokenize คำใดคำหนึ่ง WordPiece จะหาคำย่อยที่ยาวที่สุด ที่พบใน vocabulary จากนั้นจะแยกคำออกตามนั้น | |
| ตัวอย่างเช่น ถ้าเราใช้ vocabulary ที่เทรนแล้วจากตัวอย่างด้านบน และต้องการ tokenize คำว่า `"hugs"` คำย่อยที่ยาวที่สุดก็คือ `"hug"` ดังนั้น เราจะแบ่งมันออกเป็น `["hug", "##s"]` | |
| จากนั้นเราก็จะดูที่ `"##s"` เราพบว่าเป็น token ที่อยู่ใน vocabulary ดังนั้น เราจึงได้ `["hug", "##s"]` | |
| ถ้าเราใช้ BPE เราจะใช้กฎที่เทรนมาตามลำดับ ซึ่งมันจะ tokenize ตัวอย่างของเราออกเป็น `["hu", "##gs"]` | |
| มาดูอีกตัวอย่างกัน เช่นคำว่า `"bugs"` | |
| เราจะเริ่มอ่านจากข้างหน้าของคำไปข้างหลัง คุณจะเห็นว่า `"b"` เป็นคำย่อยที่ยาวที่สุดที่พบใน vocabulary ดังนั้น เราจะแยกคำตรงนี้ และเราจะได้ `["b", "##ugs"]` | |
| จากนั้นเราจะดูคำว่า `"##ugs"` สำหรับคำนี้เราพบว่า `"##u"` คือคำย่อยที่ยาวที่สุดที่พบใน vocabulary ดังนั้น เราจึงจะแยกคำตรงนี้ และได้ผลลัพธ์เป็น `["b", "##u, "##gs"]` | |
| สุดท้ายเราจะดูที่คำว่า `"##gs"` ซึ่งเราพบว่า มันอยู่ใน vocabulary แล้ว ดังนั้นเราไม่ต้องแบ่งมันอีก | |
| ในกรณีที่เราไม่สามารถหาคำย่อยที่อยู่ใน vocabulary ได้เลย คำหลักที่เรากำลังอ่านนั้นจะถูกแปลงเป็นคำ unknown | |
| ตัวอย่างเช่นคำว่า `"mug"` จะถูก tokenize ให้เป็น `["[UNK]"]` เช่นเดียวกันกับคำว่า `"bum"` ถึงแม้ว่าเราจะเจอ `"b"` และ `"##u"` ใน vocabulary แต่ว่า `"##m"` ไม่ได้อยู่ใน vocabulary เราจะแปลงทั้งคำเป็น `["[UNK]"]` และจะไม่แยกมันเป็น `["b", "##u", "[UNK]"]` | |
| นี่เป็นสิ่งหนึ่งที่แตกต่างจาก BPE โดย BPE จะดูที่แต่ละตัวอักษร และถ้าตัวไหนไม่พบใน vocabulary ก็จะถูกคัดว่าเป็น unknown | |
| > [!TIP] | |
| > ✏️ **ถึงตาคุณแล้ว!** คำว่า `"pugs"` จะถูก tokenize อย่างไร? | |
| ## Implementing WordPiece | |
| มาดูกันว่า เราจะ implement อัลกอริทึม WordPiece ได้อย่างไร | |
| เช่นเดียวกับตอนที่เราสอนเรื่อง BPE สิ่งที่เราจะสอนต่อไปนี้เป็นเพียงตัวอย่าง เพื่อให้คุณเข้าใจการทำงานของอัลกอริทึม โค้ดที่ได้อาจจะไม่สามารถใช้ได้กับ corpus ใหญ่ๆ | |
| เราจะใช้ corpus ตัวอย่างเดียวกับที่ใช้ในบท BPE : | |
| ```python | |
| corpus = [ | |
| "This is the Hugging Face course.", | |
| "This chapter is about tokenization.", | |
| "This section shows several tokenizer algorithms.", | |
| "Hopefully, you will be able to understand how they are trained and generate tokens.", | |
| ] | |
| ``` | |
| ก่อนอื่นคุณจะต้อง pre-tokenize corpus เพื่อแยกข้อความเป็นคำๆ เนื่องจากเราจะจำลองการทำงานของ WordPiece tokenizer (เช่น BERT) เราจะใช้ `bert-base-cased` tokenizer ในการ pre-tokenize | |
| ```python | |
| from transformers import AutoTokenizer | |
| tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") | |
| ``` | |
| จากนั้นคำนวณความถี่ของแต่ละคำใน corpus : | |
| ```python | |
| from collections import defaultdict | |
| word_freqs = defaultdict(int) | |
| for text in corpus: | |
| words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) | |
| new_words = [word for word, offset in words_with_offsets] | |
| for word in new_words: | |
| word_freqs[word] += 1 | |
| word_freqs | |
| ``` | |
| ```python out | |
| defaultdict( | |
| int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, | |
| 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, | |
| ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, | |
| 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) | |
| ``` | |
| เราจะมาสร้างเซ็ตของ alphabet กัน ซึ่งคือเซ็ตที่ประกอบไปด้วยตัวอักษรแรกของแต่ละคำ และอักษรอื่นๆที่ไม่ใช่ตัวแรกจะมีการใส่ `##` ไว้ข้างหน้า : | |
| ```python | |
| alphabet = [] | |
| for word in word_freqs.keys(): | |
| if word[0] not in alphabet: | |
| alphabet.append(word[0]) | |
| for letter in word[1:]: | |
| if f"##{letter}" not in alphabet: | |
| alphabet.append(f"##{letter}") | |
| alphabet.sort() | |
| alphabet | |
| print(alphabet) | |
| ``` | |
| ```python out | |
| ['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', | |
| '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', | |
| 'w', 'y'] | |
| ``` | |
| เราจะเพิ่ม token พิเศษ เข้าไปด้านหน้าของ list นี้ด้วย สำหรับ BERT คือ `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : | |
| ```python | |
| vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() | |
| ``` | |
| จากนั้น เราจะทำการแยกแต่ละคำกัน โดยแยกตัวอักษรแรกออกมา และตัวที่เหลือจะเพิ่ม `##` ไว้ข้างหน้า : | |
| ```python | |
| splits = { | |
| word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] | |
| for word in word_freqs.keys() | |
| } | |
| ``` | |
| ตอนนี้เราก็พร้อมที่จะเทรนแล้ว เราจะมาเขียนฟังก์ชันเพื่อคำนวณ score ให้แต่ละคู่ tokenกัน : | |
| ```python | |
| def compute_pair_scores(splits): | |
| letter_freqs = defaultdict(int) | |
| pair_freqs = defaultdict(int) | |
| for word, freq in word_freqs.items(): | |
| split = splits[word] | |
| if len(split) == 1: | |
| letter_freqs[split[0]] += freq | |
| continue | |
| for i in range(len(split) - 1): | |
| pair = (split[i], split[i + 1]) | |
| letter_freqs[split[i]] += freq | |
| pair_freqs[pair] += freq | |
| letter_freqs[split[-1]] += freq | |
| scores = { | |
| pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) | |
| for pair, freq in pair_freqs.items() | |
| } | |
| return scores | |
| ``` | |
| มาดูกันว่าผลลัพธ์ที่ได้หลังจากการรันครั้งแรกเป็นอย่างไร : | |
| ```python | |
| pair_scores = compute_pair_scores(splits) | |
| for i, key in enumerate(pair_scores.keys()): | |
| print(f"{key}: {pair_scores[key]}") | |
| if i >= 5: | |
| break | |
| ``` | |
| ```python out | |
| ('T', '##h'): 0.125 | |
| ('##h', '##i'): 0.03409090909090909 | |
| ('##i', '##s'): 0.02727272727272727 | |
| ('i', '##s'): 0.1 | |
| ('t', '##h'): 0.03571428571428571 | |
| ('##h', '##e'): 0.011904761904761904 | |
| ``` | |
| จากนั้น เราจะหาคู่ที่มี score สูงที่สุด โดยใช้ loop ง่ายๆ ดังนี้ : | |
| ```python | |
| best_pair = "" | |
| max_score = None | |
| for pair, score in pair_scores.items(): | |
| if max_score is None or max_score < score: | |
| best_pair = pair | |
| max_score = score | |
| print(best_pair, max_score) | |
| ``` | |
| ```python out | |
| ('a', '##b') 0.2 | |
| ``` | |
| กฎที่ได้จากการเทรนครั้งแรกคือ `('a', '##b') -> 'ab'` ดังนั้นเราจะเพิ่ม `'ab'` เข้าไปใน vocabulary : | |
| ```python | |
| vocab.append("ab") | |
| ``` | |
| ก่อนที่จะทำต่อ เราจะต้องเพิ่มตัวที่ถูก merge เข้าไปใน dictionary `splits` ก่อน โดยเราจะเขียนฟังก์ชันเพื่อการคำนวณนี้ : | |
| ```python | |
| def merge_pair(a, b, splits): | |
| for word in word_freqs: | |
| split = splits[word] | |
| if len(split) == 1: | |
| continue | |
| i = 0 | |
| while i < len(split) - 1: | |
| if split[i] == a and split[i + 1] == b: | |
| merge = a + b[2:] if b.startswith("##") else a + b | |
| split = split[:i] + [merge] + split[i + 2 :] | |
| else: | |
| i += 1 | |
| splits[word] = split | |
| return splits | |
| ``` | |
| นี่คือผลลัพธ์ของการ merge ครั้งแรก : | |
| ```py | |
| splits = merge_pair("a", "##b", splits) | |
| splits["about"] | |
| ``` | |
| ```python out | |
| ['ab', '##o', '##u', '##t'] | |
| ``` | |
| ตอนนี้เราก็มีทุกฟังก์ชันที่จำเป็นสำหรับการเทรนแล้ว เราจะเทรนจนกว่า tokenizer ได้เรียนเกี่ยวกับทุกๆ merge ที่เราต้องการ เราจะตั้งค่าขนาด vocabulary เป็น 70 สำหรับตัวอย่างนี้ : | |
| ```python | |
| vocab_size = 70 | |
| while len(vocab) < vocab_size: | |
| scores = compute_pair_scores(splits) | |
| best_pair, max_score = "", None | |
| for pair, score in scores.items(): | |
| if max_score is None or max_score < score: | |
| best_pair = pair | |
| max_score = score | |
| splits = merge_pair(*best_pair, splits) | |
| new_token = ( | |
| best_pair[0] + best_pair[1][2:] | |
| if best_pair[1].startswith("##") | |
| else best_pair[0] + best_pair[1] | |
| ) | |
| vocab.append(new_token) | |
| ``` | |
| มาดูผลลัพธ์ของ vocabulary กัน : | |
| ```py | |
| print(vocab) | |
| ``` | |
| ```python out | |
| ['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', | |
| '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', | |
| 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', | |
| 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', | |
| '##ut'] | |
| ``` | |
| ถ้าเทียบกับ BPE คุณจะเห็นว่า tokenizer ตัวนี้สามารถเรียนเกี่ยวกับคำย่อยได้เร็วกว่านิดหน่อย | |
| > [!TIP] | |
| > 💡 ถ้าคุณใช้ `train_new_from_iterator()` กับ corpus ตัวอย่างนี้ คุณอาจจะไม่ได้ vocabulary เดียวกัน นั่นก็เพราะ 🤗 Tokenizers library ไม่ได้ใช้ WordPiece ในการเทรน แต่เราใช้ BPE | |
| เมื่อคุณต้องการ tokenize ข้อความใหม่ คุณจะต้องทำการ pre-tokenize ข้อความแล้วจากนั้นจึง tokenize แต่ละคำ ตามหลักการของอัลกอริทึมนี้ | |
| เราจะมองหาคำย่อยที่ยาวที่สุด โดยอ่านจากข้างหน้าคำไปข้างหลัง จากนั้นเราจะแยกคำหลักออกตรงคำย่อยนี้ จากนั้นทำขั้นตอนนี้ซ้ำกับส่วนต่อๆไปของคำนั้น แล้วทำเช่นเดียวกันกับคำต่อไป | |
| ```python | |
| def encode_word(word): | |
| tokens = [] | |
| while len(word) > 0: | |
| i = len(word) | |
| while i > 0 and word[:i] not in vocab: | |
| i -= 1 | |
| if i == 0: | |
| return ["[UNK]"] | |
| tokens.append(word[:i]) | |
| word = word[i:] | |
| if len(word) > 0: | |
| word = f"##{word}" | |
| return tokens | |
| ``` | |
| มาทดลอง tokenize คำที่มีใน vocabulary และอีกคำที่ไม่ได้อยู่ใน vocabulary กัน : | |
| ```python | |
| print(encode_word("Hugging")) | |
| print(encode_word("HOgging")) | |
| ``` | |
| ```python out | |
| ['Hugg', '##i', '##n', '##g'] | |
| ['[UNK]'] | |
| ``` | |
| ตอนนี้เราจะต้องเขียนฟังก์ชันเพื่อ tokenize ข้อความกัน : | |
| ```python | |
| def tokenize(text): | |
| pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) | |
| pre_tokenized_text = [word for word, offset in pre_tokenize_result] | |
| encoded_words = [encode_word(word) for word in pre_tokenized_text] | |
| return sum(encoded_words, []) | |
| ``` | |
| ทดลองฟังก์ชันของเรากับประโยคตัวอย่าง : | |
| ```python | |
| tokenize("This is the Hugging Face course!") | |
| ``` | |
| ```python out | |
| ['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', | |
| '##e', '[UNK]'] | |
| ``` | |
| นี่คือทั้งหมดเกี่ยวกับ WordPiece ในบทถัดไปเราจะมาเรียนเกี่ยวกับ Unigram กัน | |
| <EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/6.mdx" /> |
Xet Storage Details
- Size:
- 24.1 kB
- Xet hash:
- 1c8421293e2b6a1595f1e017c76adb900534202c8d062ce56a4b9c14289f0231
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.