File size: 4,334 Bytes
be99550
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// 🌌 [BioPhys 5.0 λ„€μ΄ν‹°λΈŒ BPE λ°”μ΄νŠΈνŽ˜μ–΄ ν† ν¬λ‚˜μ΄μ € μ—”μ§„] (src/native_bpe_tokenizer.rs)
// μ™ΈλΆ€ 파이썬 μ˜μ‘΄μ„± 0%! μž„μ˜μ˜ ν•œκ΅­μ–΄/μ˜μ–΄/기호 ν…μŠ€νŠΈλ₯Ό BPE λ°”μ΄νŠΈ λ‹¨μœ„λ‘œ λΆ„μ ˆ 및 μ •μˆ˜ ID 인코딩/λ””μ½”λ”©
use std::collections::HashMap;

pub struct NativeBpeTokenizer {
    pub token_to_id: HashMap<String, usize>,
    pub id_to_token: Vec<String>,
    pub byte_tokens: Vec<String>,
}

impl NativeBpeTokenizer {
    pub fn new() -> Self {
        let mut token_to_id = HashMap::new();
        let mut id_to_token = Vec::new();

        // 1. 특수 토큰 등둝
        let special_tokens = vec!["<PAD>", "<BOS>", "<EOS>", "<UNK>", "<MASK>"];
        for &tok in &special_tokens {
            let id = id_to_token.len();
            id_to_token.push(tok.to_string());
            token_to_id.insert(tok.to_string(), id);
        }

        // 2. 256개 κΈ°λ³Έ λ°”μ΄νŠΈ 토큰 등둝 (0x00 ~ 0xFF)
        let mut byte_tokens = Vec::new();
        for b in 0..=255u8 {
            let tok_str = format!("<0x{:02X}>", b);
            let id = id_to_token.len();
            id_to_token.push(tok_str.clone());
            token_to_id.insert(tok_str.clone(), id);
            byte_tokens.push(tok_str);
        }

        // 3. μ£Όμš” ν•œκ΅­μ–΄ 음절 및 기술 ν‚€μ›Œλ“œ μ„œλΈŒμ›Œλ“œ 등둝
        let common_korean_subwords = vec![
            "인곡지λŠ₯", "λΈ”λž™ν™€", "μ‚¬κ±΄μ˜", "지평선", "μŠ€νŒŒμ΄ν‚Ή", "μ–‘μžν™”", "λ‰΄λŸ°",
            "κ°€μ€‘μΉ˜", "μ••μΆ•", "μ΄ˆμ €μ§€μ—°", "μ—”μ§„", "Rust", "Svelte", "Tauri",
            "ν•œκ΅­μ–΄", "문법", "항상성", "특이점", "물리", "동역학", "μž…λ‹ˆλ‹€", "ν•©λ‹ˆλ‹€",
            "으둜", "μ—μ„œ", "의", "을", "λ₯Ό", "이", "κ°€", "은", "λŠ”", "κ³Ό", "와"
        ];

        for &word in &common_korean_subwords {
            let id = id_to_token.len();
            id_to_token.push(word.to_string());
            token_to_id.insert(word.to_string(), id);
        }

        NativeBpeTokenizer {
            token_to_id,
            id_to_token,
            byte_tokens,
        }
    }

    /// [ν…μŠ€νŠΈ βž” μ •κ·œ BPE 토큰 ID 벑터 인코딩]
    pub fn encode(&self, text: &str) -> Vec<usize> {
        let mut token_ids = Vec::new();
        token_ids.push(1); // <BOS>

        let mut chars = text.chars().peekable();
        let mut current_buf = String::new();

        while let Some(c) = chars.next() {
            current_buf.push(c);
            
            // λ“±λ‘λœ μ„œλΈŒμ›Œλ“œμΈμ§€ 탐색
            if let Some(&id) = self.token_to_id.get(&current_buf) {
                token_ids.push(id);
                current_buf.clear();
            } else if c.is_whitespace() || chars.peek().is_none() {
                // 곡백 λ˜λŠ” 단어 λμ—μ„œ λ°”μ΄νŠΈ λ‹¨μœ„ 폴백
                for b in current_buf.as_bytes() {
                    let byte_tok = format!("<0x{:02X}>", b);
                    let id = self.token_to_id.get(&byte_tok).copied().unwrap_or(3); // <UNK>
                    token_ids.push(id);
                }
                current_buf.clear();
            }
        }

        token_ids.push(2); // <EOS>
        token_ids
    }

    /// [토큰 ID 벑터 βž” μ‚¬λžŒμ΄ 읽을 수 μžˆλŠ” UTF-8 ν…μŠ€νŠΈ λ””μ½”λ”©]
    pub fn decode(&self, token_ids: &[usize]) -> String {
        let mut byte_buffer = Vec::new();
        let mut decoded_text = String::new();

        for &id in token_ids {
            if id >= self.id_to_token.len() { continue; }
            let tok_str = &self.id_to_token[id];

            if tok_str == "<BOS>" || tok_str == "<EOS>" || tok_str == "<PAD>" {
                continue;
            }

            if tok_str.starts_with("<0x") && tok_str.ends_with(">") {
                if let Ok(b) = u8::from_str_radix(&tok_str[3..5], 16) {
                    byte_buffer.push(b);
                }
            } else {
                if !byte_buffer.is_empty() {
                    decoded_text.push_str(&String::from_utf8_lossy(&byte_buffer));
                    byte_buffer.clear();
                }
                decoded_text.push_str(tok_str);
            }
        }

        if !byte_buffer.is_empty() {
            decoded_text.push_str(&String::from_utf8_lossy(&byte_buffer));
        }

        decoded_text
    }
}