perf(merge bpe): improvement suggestions - #2275
Conversation
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
544c602 to
e9fe3ca
Compare
ArthurZucker
left a comment
There was a problem hiding this comment.
Thanks a lot for the great PR 🤗
| //! A pass builds the new word in the same array that holds the old one, using two cursors that both start at index 0. | ||
| //! The read cursor marks the start of what is left of the old word. | ||
| //! The write cursor marks the end of the new word built so far. | ||
| //! Every step writes exactly one symbol: a copy moves both cursors by one, and a merge writes one symbol but consumes two, so the read cursor moves ahead. |
There was a problem hiding this comment.
| //! Every step writes exactly one symbol: a copy moves both cursors by one, and a merge writes one symbol but consumes two, so the read cursor moves ahead. | |
| //! Every step writes exactly one symbol: a copy (a no merge for a pair just results in copying the id that was just read) moves both cursors by one, and a merge writes one symbol but consumes two, so the read cursor moves by 2. |
| //! The read cursor marks the start of what is left of the old word. | ||
| //! The write cursor marks the end of the new word built so far. | ||
| //! Every step writes exactly one symbol: a copy moves both cursors by one, and a merge writes one symbol but consumes two, so the read cursor moves ahead. | ||
| //! The write cursor never gets ahead of the read cursor, so a write only ever lands on a slot that was already read. |
There was a problem hiding this comment.
| //! The write cursor never gets ahead of the read cursor, so a write only ever lands on a slot that was already read. |
| //! ┌───┬───┬────┬───┐ | ||
| //! │ h │ e │ ll │ o │ | ||
| //! └───┴───┴────┴───┘ |
There was a problem hiding this comment.
I would leave the last slot since it will just get a u32::MAX
| //! pass 2 target (h,e): [ h │ e │ ll │ o ] -> [ he │ ll │ o ] lowest written pair: (ll,o) | ||
| //! pass 3 target (ll,o): [ he │ ll │ o ] -> [ he │ llo ] lowest written pair: (he,llo) | ||
| //! pass 4 target (he,llo): [ he │ llo ] -> [ hello ] no pair left to rank: done |
There was a problem hiding this comment.
same here! Its a nit but important IMO to show that there is "padding"
| //! | ||
| //! # Batching and the `SAFE` bit | ||
| //! | ||
| //! The target can occur several times in the word. When its merge is `SAFE`, one pass merges every occurrence. |
There was a problem hiding this comment.
| //! The target can occur several times in the word. When its merge is `SAFE`, one pass merges every occurrence. | |
| //! The target merge can occur several times in the word (for example "hello lots", the pair "lo" appears twice). When its merge is `SAFE`, one pass merges every occurrence. |
| write_cursor: 0, | ||
| target_merge, | ||
| batched, | ||
| has_merged: false, |
| /// Looks up the pair at the read cursor and writes one symbol: the pair's product id when | ||
| /// its value equals the target and the pair may still merge, the left symbol otherwise. A | ||
| /// merge consumes both symbols of the pair, a copy only the left one. | ||
| /// | ||
| /// `&mut [u32]` rather than `&mut Vec<u32>` so the length is a local and the reads can have | ||
| /// their bounds checks removed.. | ||
| /// The return value becomes the next call's `known_pair_value`. After two copies in a row, | ||
| /// the pair the second write ranks is the (`left_symbol`, `right_symbol`) the first call | ||
| /// already looked up, so the second write reuses that value instead of looking it up again. | ||
| /// A merge returns `None`: the product id it writes is a new symbol, and no pair containing | ||
| /// it has been looked up yet. |
There was a problem hiding this comment.
badly formulated, but yes you are winning one lookup! let's put this in better framing!
| known_pair_value = state.step(tables, symbols, known_pair_value); | ||
| } | ||
| if state.read_cursor < len { | ||
| state.copy_last(tables, symbols, known_pair_value); |
There was a problem hiding this comment.
not sure we need the copy last func but nit
| let pair_value = tables.get_value(&left_symbol, &right_symbol); | ||
| let should_merge = pair_value == self.target_merge && (self.batched || !self.has_merged); | ||
| if should_merge { | ||
| self.has_merged = true; |
There was a problem hiding this comment.
| self.has_merged = true; | |
| self.was_merged = true; |
| let merge_rank = self.tables.get_value(&to_merge[write_id - 1], &written); | ||
| running_min = std::cmp::min(running_min, merge_rank); | ||
| self.read_cursor += 1; | ||
| self.write(tables, symbols, left_symbol, known_pair_value); |
There was a problem hiding this comment.
known_paire_value naming is a bit weird / not explicit that it will just not be looked up and None will be?
Brings in the BPE merge work from #2241 and #2275: the multipass and two-tier-queue engines, the rank tables and the pretoken->rank conversion. `models/bpe/model.rs` is gone -- #2241 split it into `bpe_model.rs`, `bpe_scratch.rs` and the merge engines -- so the word cache and the batched `tokenize_spans` this branch added to it are re-wired onto the new engine: `BpeScratch` carries `word_cache` again, and both pipeline entry points emit through `tables.unmap` instead of `Word::get_chars_iter`.
No description provided.