gigatoken 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae6f31ee2b6996844f0f7460616ed7a3c4802993256fc68b231c7aeabf870df6
4
- data.tar.gz: d5e9eb5dc28c10f26b4dbb29f04f5f2156ae1a50697687251fb2a1e1ad5f8d1b
3
+ metadata.gz: 41dcafd85415b4ee0ee40e7c77414557b1c6913676d4846e0d5934f689f3657a
4
+ data.tar.gz: fddc7d15a5a7e1b9cf9be794a0468294dc31c251ad90dd540d16e9ede344b3a2
5
5
  SHA512:
6
- metadata.gz: 49ad46adee00b833739bd523905fc3f44d59374267aeff242be355246cbc173e6efdfefe8f509c287d429929df36413ccbdac487ec8a9dc6b9c5f3d758d06779
7
- data.tar.gz: 80d49b5c3246c91028cb669eb4ff534f8a49af393fe974e6537b6e3fdd33c916e6c0070eaabbae8014df6d9ece96318dae1c07352568d5108426714d93d57f54
6
+ metadata.gz: 20b5c1fdb98dda4eff0fa0323adbf72d334ddeff672b9b1a3ae1085fdc1454ff1b00158b2aa7ea876be39188c7873c57b7df9d5aeeee99d641f3da22f88699ce
7
+ data.tar.gz: 1c57fc8322d4bb6531cec42def1e6194506742d9491df4b8c2daaf1fb5a1beb29877520935654a955c684d28fcab3e76ba81b5288849092dec5d217d48d190c7
data/Cargo.lock CHANGED
@@ -771,7 +771,7 @@ dependencies = [
771
771
 
772
772
  [[package]]
773
773
  name = "gigatoken-rb"
774
- version = "0.2.2"
774
+ version = "0.3.0"
775
775
  dependencies = [
776
776
  "gigatoken",
777
777
  "magnus",
data/README.md CHANGED
@@ -15,7 +15,7 @@ Zero-copy Ruby bindings for [marcelroed/gigatoken](https://github.com/marcelroed
15
15
 
16
16
  Mac Studio M4 Max, OpenWebText, GPT-2 tokenizer; every library produces the same tokenization, gigatoken just does it faster. The Python row includes a fix for [marcelroed/gigatoken#38](https://github.com/marcelroed/gigatoken/issues/38) — a hidden memcpy in `shrink_to_fit()` we found while chasing the last of the Ruby–Python gap and sent upstream (one-line fix; without it the wheel lands around 7.4 GB/s).
17
17
 
18
- **340x faster** than the fastest existing Ruby gem (tiktoken_ruby) and **1,050x faster** than the tokenizers gem. Full methodology & exact counts: [docs/rb/benchmarks.md](docs/rb/benchmarks.md).
18
+ **340x faster** than the fastest existing Ruby gem (tiktoken_ruby) and **1,050x faster** than the tokenizers gem. Full methodology & exact counts: [docs/explanation/benchmarks.md](docs/explanation/benchmarks.md).
19
19
 
20
20
  ## Install
21
21
 
@@ -119,7 +119,7 @@ packed[3] # => document 3's ids as an Array, on demand
119
119
 
120
120
  ### Async
121
121
 
122
- `encode_batch` and `encode_files` release the GVL for the whole encode; the parallelism runs on the engine's rayon pool, not Ruby threads. Under `Async`, give the fiber scheduler a worker pool (`ASYNC_SCHEDULER_WORKER_POOL=true`) and the calling fiber yields to the reactor too. Design notes: [docs/rb/async.md](docs/rb/async.md).
122
+ `encode_batch` and `encode_files` release the GVL for the whole encode; the parallelism runs on the engine's rayon pool, not Ruby threads. Under `Async`, give the fiber scheduler a worker pool (`ASYNC_SCHEDULER_WORKER_POOL=true`) and the calling fiber yields to the reactor too. Design notes: [docs/how-to/run-under-async.md](docs/how-to/run-under-async.md).
123
123
 
124
124
  ## CLI
125
125
 
@@ -138,6 +138,16 @@ gigatoken bench lib/gigatoken/encodings/cl100k_base.tiktoken README.md --pretoke
138
138
 
139
139
  Leave it off against a `.tiktoken` TOKENIZER and both commands raise `Gigatoken::Error` naming the valid schemes instead of crashing; for every other TOKENIZER shape (`tokenizer.json`, a packaged name, a Hub repo id) `--pretokenizer` is accepted but ignored.
140
140
 
141
+ ## Documentation
142
+
143
+ In-depth docs live under [`docs/`](docs/README.md), organized by
144
+ [Diátaxis](https://diataxis.fr/):
145
+
146
+ - **Tutorial:** [Getting started](docs/tutorials/getting-started.md)
147
+ - **How-to:** [Load a tokenizer](docs/how-to/load-a-tokenizer.md), [Tokenize files](docs/how-to/tokenize-files.md), [Packed results](docs/how-to/use-packed-results.md), [Cache budget](docs/how-to/tune-the-cache-budget.md), [Async](docs/how-to/run-under-async.md), [Measure](docs/how-to/measure-time-and-allocations.md)
148
+ - **Reference:** [`Tokenizer`](docs/reference/tokenizer.md), [`PackedResult`](docs/reference/packed-result.md), [File sources](docs/reference/file-sources.md), [Encodings and settings](docs/reference/encodings-and-settings.md), [CLI](docs/reference/cli.md)
149
+ - **Explanation:** [Benchmarks](docs/explanation/benchmarks.md), [Allocations](docs/explanation/allocations.md), [Async design](docs/explanation/async-design.md)
150
+
141
151
  ## Development
142
152
 
143
153
  ```bash
@@ -145,6 +155,7 @@ bundle install
145
155
  bundle exec rake compile # native extension (Rust nightly, via rust-toolchain.toml)
146
156
  bundle exec rspec
147
157
  bundle exec standardrb
158
+ ruby -Ilib bench/operations.rb # every operation: i/s, objects and malloc per call
148
159
  ```
149
160
 
150
161
  The Ruby layer is fiber-first throughout — no `Thread`, no `Mutex`; all parallelism lives in the core's rayon pool. CI runs ubuntu + macos × Ruby 3.3/3.4/4.0, and `release.yml` cross-builds the precompiled native gems (arm64-darwin, x86_64-linux, aarch64-linux).
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "gigatoken-rb"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -17,7 +17,7 @@
17
17
  //! (`~/architect/src/github.com/socketry/io-event/ext/io/event/worker_pool.c:309-316`).
18
18
  //! Without such a scheduler (or with one lacking a worker pool), `rb_nogvl`
19
19
  //! degrades to exactly today's behavior: release the GVL, block this thread.
20
- //! See `docs/rb/async-design.md` and `docs/rb/async.md` for the full design
20
+ //! See `docs/explanation/async-design.md` and `docs/how-to/run-under-async.md` for the full design
21
21
  //! and gotchas (worker pool is opt-in, defaults to one background worker).
22
22
  //!
23
23
  //! `func`/`data1` may now run on a different OS thread than the caller (the
@@ -43,7 +43,7 @@ pub(crate) fn ragged_result(ruby: &Ruby, flat: Vec<u32>, lens: Vec<i64>) -> Resu
43
43
  let mut offset = 0usize;
44
44
  for len in lens {
45
45
  let len = len as usize;
46
- result.push(flat[offset..offset + len].to_vec())?;
46
+ result.push(ruby.ary_from_iter(flat[offset..offset + len].iter().copied()))?;
47
47
  offset += len;
48
48
  }
49
49
  Ok(result)
@@ -460,7 +460,7 @@ impl BPETokenizer {
460
460
  /// Before you re-inline this "to simplify": rerun the evidence rather than
461
461
  /// trusting a number. `ruby -Ilib bench/encode_ab.rb` with the attributes
462
462
  /// stripped and again with them restored, and read
463
- /// `docs/rb/benchmarks.md` first — no size resolves this on the hardware
463
+ /// `docs/explanation/benchmarks.md` first — no size resolves this on the hardware
464
464
  /// measured so far. The instrument is honest (an interleaved same-build
465
465
  /// run never calls a size faster or slower, at any size) and has power to
466
466
  /// catch a couple-percent effect reliably, but the attributes' real
data/lib/gigatoken/hub.rb CHANGED
@@ -99,6 +99,12 @@ module Gigatoken
99
99
  revision.match?(/\A[0-9a-f]{40}\z/)
100
100
  end
101
101
 
102
+ # The Hub endpoint, resolved like huggingface_hub does it: HF_ENDPOINT,
103
+ # then https://huggingface.co.
104
+ def default_endpoint
105
+ env("HF_ENDPOINT") || DEFAULT_ENDPOINT
106
+ end
107
+
102
108
  private
103
109
 
104
110
  def env(key)
@@ -125,8 +131,9 @@ module Gigatoken
125
131
 
126
132
  # @parameter endpoint [String] the Hub endpoint to fetch from — override
127
133
  # for pointing at a local server in tests (dependency injection, not a
128
- # mock).
129
- def initialize(endpoint: DEFAULT_ENDPOINT)
134
+ # mock); defaults to Hub.default_endpoint (HF_ENDPOINT, then
135
+ # huggingface.co).
136
+ def initialize(endpoint: self.class.default_endpoint)
130
137
  @endpoint = endpoint.chomp("/")
131
138
  @internet = Async::HTTP::Internet.new
132
139
  end
@@ -28,9 +28,13 @@ module Gigatoken
28
28
  lens.sum
29
29
  end
30
30
 
31
- # Array of token ids for document `i`, materialized on demand.
31
+ # Array of token ids for document `i`, materialized on demand; negative
32
+ # indices count from the end and out-of-range ones give nil, like Array.
32
33
  def [](i)
33
- buffer.get_values(Array.new(lens[i], :u32), @offsets[i] * 4)
34
+ i += size if i.negative?
35
+ return unless i >= 0 && i < size
36
+
37
+ buffer.values(:u32, @offsets[i] * 4, lens[i])
34
38
  end
35
39
 
36
40
  def each
@@ -42,7 +46,7 @@ module Gigatoken
42
46
  # A ragged Array of Arrays, one per document — the same shape
43
47
  # `encode_batch`/`encode_files` return with `packed: false`.
44
48
  def to_a
45
- each.to_a
49
+ Array.new(size) { |i| self[i] }
46
50
  end
47
51
  end
48
52
  end
@@ -49,8 +49,8 @@ module Gigatoken
49
49
 
50
50
  # Load tokenizer.json from HuggingFace Hub repo `repo_id` at `revision`
51
51
  # (downloaded directly; huggingface_hub is not required).
52
- def self.from_hub(repo_id, revision: "main", hub: Hub.new)
53
- from_file(hub.hub_file(repo_id, "tokenizer.json", revision: revision))
52
+ def self.from_hub(repo_id, revision: "main", hub: nil)
53
+ from_file((hub || Hub.new).hub_file(repo_id, "tokenizer.json", revision: revision))
54
54
  end
55
55
 
56
56
  # Load from any of the supported source shapes: an existing file or
@@ -67,7 +67,7 @@ module Gigatoken
67
67
  # here too, raising the same explanation from_encoding gives rather than
68
68
  # reaching the Hub — but only those; an unrecognized bare name like
69
69
  # "gpt2" still dispatches to the Hub.
70
- def self.load(source, pretokenizer: nil, special_tokens: {}, revision: "main", hub: Hub.new)
70
+ def self.load(source, pretokenizer: nil, special_tokens: {}, revision: "main", hub: nil)
71
71
  source = source.to_s
72
72
  if source.end_with?(".tiktoken")
73
73
  unless pretokenizer
@@ -83,8 +83,12 @@ module Gigatoken
83
83
  raise Error, "#{source.inspect}: no such file or directory, not a .tiktoken path, and doesn't look like a HuggingFace Hub repo id"
84
84
  end
85
85
 
86
+ # `data` is UTF-8 JSON whatever its encoding tag says (File.binread tags
87
+ # binary; a US-ASCII default_external tags that), so retag rather than let
88
+ # JSON.parse transcode from the tag; a UTF-8-tagged String needs no copy.
86
89
  def self.special_tokens_from_json(data)
87
- added = JSON.parse(data.dup.force_encoding(Encoding::UTF_8))["added_tokens"] || []
90
+ data = data.dup.force_encoding(Encoding::UTF_8) unless data.encoding == Encoding::UTF_8
91
+ added = JSON.parse(data)["added_tokens"] || []
88
92
  added.each_with_object({}) { |t, h| h[t["content"]] = t["id"] if t["special"] }
89
93
  end
90
94
  private_class_method :special_tokens_from_json
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gigatoken
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
data/rust-toolchain.toml CHANGED
@@ -1,5 +1,5 @@
1
1
  [toolchain]
2
- channel = "nightly"
2
+ channel = "nightly-2026-09-18"
3
3
  # Cross triples for the precompiled-gem builds (ruby-gem.yml). rustup's
4
4
  # toolchain-file auto-install is the only thing that provisions nightly
5
5
  # inside the rb-sys-dock containers, so the targets must be declared here —
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gigatoken
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Jacobs