gigatoken 0.1.1-aarch64-linux → 0.2.0-aarch64-linux
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 +4 -4
- data/README.md +43 -2
- data/lib/gigatoken/3.3/gigatoken_rb.so +0 -0
- data/lib/gigatoken/3.4/gigatoken_rb.so +0 -0
- data/lib/gigatoken/4.0/gigatoken_rb.so +0 -0
- data/lib/gigatoken/cli/bench.rb +4 -3
- data/lib/gigatoken/cli/support.rb +7 -4
- data/lib/gigatoken/cli/validate.rb +4 -3
- data/lib/gigatoken/encodings/PROVENANCE.md +110 -0
- data/lib/gigatoken/encodings/cl100k_base.tiktoken +100256 -0
- data/lib/gigatoken/encodings/o200k_base.tiktoken +199998 -0
- data/lib/gigatoken/encodings/r50k_base.tiktoken +50256 -0
- data/lib/gigatoken/encodings.rb +103 -0
- data/lib/gigatoken/tokenizer.rb +48 -11
- data/lib/gigatoken/version.rb +1 -1
- data/lib/gigatoken.rb +15 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc1e4fea36f3bb957cff72a6187587e56aeabb4251c93dd554946e3a6423103c
|
|
4
|
+
data.tar.gz: 31fd83175bb68f431114913cbccf36b47b3b176396f2294befc9689e1f53a254
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5ecfa8b1794d409df4f6a23fc5207815e662e4af5f049412c3187f0a9b442c39de1ea7d91ce294b29799aea693d58abd8c81508bb37be3b50d53d2d9cc1a68f
|
|
7
|
+
data.tar.gz: 3ffbf1a20d577fd0bb7a4fba0397e356f4981486e3e1c0eb0ce11a4669f1ee69fbf853e9e844d6235391c114e8227d1360b684aaae8f3e1f6e1c057818b8fbef
|
data/README.md
CHANGED
|
@@ -48,17 +48,50 @@ tok.vocab_size # => 50257
|
|
|
48
48
|
tok.special_tokens # => {"<|endoftext|>" => 50256}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
`load` takes a `tokenizer.json` path, a directory holding one, a HuggingFace Hub repo id, or a `.tiktoken` mergeable-ranks file, and dispatches on shape. Hub downloads run over socketry's `async-http` — no Python anywhere. Know what you have? Skip the dispatch:
|
|
51
|
+
`load` takes a `tokenizer.json` path, a directory holding one, a packaged tiktoken encoding name (`r50k_base`, `cl100k_base`, `o200k_base`), a HuggingFace Hub repo id, or a `.tiktoken` mergeable-ranks file, and dispatches on shape. Hub downloads run over socketry's `async-http` — no Python anywhere. Know what you have? Skip the dispatch:
|
|
52
52
|
|
|
53
53
|
```ruby
|
|
54
54
|
Gigatoken::Tokenizer.from_file("tokenizer.json")
|
|
55
55
|
Gigatoken::Tokenizer.from_hub("openai-community/gpt2", revision: "main")
|
|
56
|
-
Gigatoken::Tokenizer.from_tiktoken("
|
|
56
|
+
Gigatoken::Tokenizer.from_tiktoken("cl100k_base.tiktoken", pretokenizer: "gpt4", special_tokens: {"<|endoftext|>" => 100257})
|
|
57
57
|
Gigatoken::Tokenizer.from_json(File.binread("tokenizer.json"))
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
A `.tiktoken` file holds mergeable ranks only — its pretokenization scheme and special tokens live in the code that defines the encoding, not the file — so `pretokenizer:` is a required keyword (one of `Gigatoken::Native.pretokenizer_names`: `gpt2`/`r50k`, `gpt4`/`cl100k`, `qwen2`, `qwen35`, `olmo3`, `deepseek_v3`, `o200k`, `nemotron`, `kimi`) and `special_tokens:` defaults to none. Nothing is guessed: an unknown scheme raises `Gigatoken::Error` naming the valid ones, and `Tokenizer.load` on a `.tiktoken` path with no `pretokenizer:` raises rather than silently picking one.
|
|
61
|
+
|
|
60
62
|
SentencePiece-BPE models (Llama, Gemma, Mistral — any `tokenizer.json` with `byte_fallback: true`) load through the same entry points and pick the right backend automatically. One difference: the SentencePiece core decodes text, so it validates input and raises `Gigatoken::Error` on invalid UTF-8 instead of guessing.
|
|
61
63
|
|
|
64
|
+
### Packaged tiktoken encodings
|
|
65
|
+
|
|
66
|
+
`r50k_base`, `cl100k_base`, `o200k_base`, and `o200k_harmony` are vendored directly — mergeable ranks, pretokenizer scheme, and special-token table all shipped inside the gem (`lib/gigatoken/encodings/`; see `PROVENANCE.md` there for exact source URLs and hashes) — so all four resolve by name through both entry points entirely offline: no network access, no writable cache directory. `o200k_harmony` vendors no new file at all: it reuses `o200k_base.tiktoken`'s ranks and the `o200k` scheme verbatim, differing only in its special-token table (10 named control tokens — `<|start|>`, `<|message|>`, `<|end|>`, `<|return|>`, and so on — plus 1081 reserved slots; see `PROVENANCE.md` for the exact table). It's also the one packaged encoding not checked against `tiktoken_ruby`: that gem's 0.0.17 harmony table drops `<|endofprompt|>` where `openai/tiktoken` 0.9.0 keeps it at id 200018, so the oracle is the outlier here — `spec/gigatoken/differential_spec.rb` proves harmony instead by reduction to `o200k_base` plus a pinned special-token table.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
Gigatoken::Tokenizer.from_encoding("cl100k_base")
|
|
70
|
+
Gigatoken::Tokenizer.load("cl100k_base") # same result — packaged names are
|
|
71
|
+
# checked before the Hub-repo-id shape
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`p50k_base` and `p50k_edit` are deliberately not packaged: both load the same non-dense ranks (id 50256 is left free for `<|endoftext|>`), and the rank loader rejects non-dense ranks. Both entry points raise `Gigatoken::Error` explaining that, rather than `load` falling through to the Hub for a name that happens to look like a legacy repo id.
|
|
75
|
+
|
|
76
|
+
`encode` on a packaged tokenizer honours its special-token table: text containing `<|endoftext|>` (or any other literal special-token string) is tokenized as that special token, not as ordinary text. That matches [`tiktoken`](https://github.com/openai/tiktoken)'s `encode_with_special_tokens`, not its plain `encode`, which treats the same literal as ordinary text — a difference worth knowing if you're tokenizing untrusted input. To get tiktoken's non-honouring default instead, build a tokenizer from the same rank file with an empty special-token table:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
entry = Gigatoken::Encodings["cl100k_base"]
|
|
80
|
+
Gigatoken::Tokenizer.from_tiktoken(entry[:rank_file], pretokenizer: entry[:pretokenizer], special_tokens: {})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Encode-cache budget
|
|
84
|
+
|
|
85
|
+
Each tokenizer's pretoken cache is capped process-globally (512 MiB per worker by default) so long-lived processes — a Rails worker, say — don't grow it unbounded; a full cache wipes back toward its seed level and refills, which costs a bit of re-computation but never changes encode output. Tune it before building tokenizers you want the new budget to apply to:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
Gigatoken.max_cache_bytes # => 536870912 (512 MiB)
|
|
89
|
+
Gigatoken.max_cache_bytes = 64 << 20 # only tokenizers built after this see the new budget
|
|
90
|
+
Gigatoken.max_cache_bytes = nil # unbounded
|
|
91
|
+
|
|
92
|
+
tok.cache_entries # => cached pretoken/unit count right now
|
|
93
|
+
```
|
|
94
|
+
|
|
62
95
|
### Tokenize files without leaving Rust
|
|
63
96
|
|
|
64
97
|
`encode_files` reads and tokenizes files entirely on the native side — document contents never materialize as Ruby objects. `.gz` and `.zst` decompress transparently.
|
|
@@ -97,6 +130,14 @@ gigatoken validate openai-community/gpt2 owt_train.txt --doc-separator "<|endoft
|
|
|
97
130
|
|
|
98
131
|
`bench` reports MB/s and Mtok/s (`--packed` for the fused packed path, `--no-parallel` for the serial core). `validate` confirms native split-and-encode agrees with a Ruby-side split through `encode_batch`.
|
|
99
132
|
|
|
133
|
+
TOKENIZER also takes a bare `.tiktoken` file, which is where `--pretokenizer` comes in: the file carries mergeable ranks only, so the split regex has to come from the caller, same as `from_tiktoken` above. `--pretokenizer` takes one of the scheme names listed above for `pretokenizer:`:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
gigatoken bench lib/gigatoken/encodings/cl100k_base.tiktoken README.md --pretokenizer gpt4
|
|
137
|
+
```
|
|
138
|
+
|
|
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
|
+
|
|
100
141
|
## Development
|
|
101
142
|
|
|
102
143
|
```bash
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/gigatoken/cli/bench.rb
CHANGED
|
@@ -10,19 +10,20 @@ module Gigatoken
|
|
|
10
10
|
class Bench < Dry::CLI::Command
|
|
11
11
|
desc "Measure the time to encode FILES with TOKENIZER"
|
|
12
12
|
|
|
13
|
-
argument :tokenizer, required: true, desc: "tokenizer.json path or directory, HuggingFace repo id, or .tiktoken file"
|
|
13
|
+
argument :tokenizer, required: true, desc: "tokenizer.json path or directory, packaged encoding name (e.g. \"cl100k_base\"), HuggingFace repo id, or .tiktoken file (requires --pretokenizer)"
|
|
14
14
|
argument :files, type: :array, required: true, desc: "UTF-8 text files to encode"
|
|
15
15
|
|
|
16
16
|
option :doc_separator, desc: 'document separator to split the files on, e.g. "<|endoftext|>"; whole files are single documents otherwise'
|
|
17
17
|
option :limit_bytes, default: "none", desc: "cap the bytes benchmarked, e.g. 100MB; 'none' for everything (parallel mode only — ignored with --no-parallel)"
|
|
18
18
|
option :parallel, type: :boolean, default: true, desc: "encode on the worker pool instead of the fused serial core path"
|
|
19
19
|
option :packed, type: :boolean, default: false, desc: "time the fused native file path with a packed IO::Buffer result instead of per-document Ruby arrays (ignores --limit-bytes)"
|
|
20
|
+
option :pretokenizer, desc: "pretokenizer scheme, required when TOKENIZER is a .tiktoken file (one of #{Native.pretokenizer_names.join(", ")}); ignored otherwise"
|
|
20
21
|
|
|
21
|
-
def call(tokenizer:, files:, doc_separator: nil, limit_bytes: "none", parallel: true, packed: false, **)
|
|
22
|
+
def call(tokenizer:, files:, doc_separator: nil, limit_bytes: "none", parallel: true, packed: false, pretokenizer: nil, **)
|
|
22
23
|
limit = Support.parse_size(limit_bytes)
|
|
23
24
|
out.puts "#{label("cpu")}: #{Support.cpu_info}"
|
|
24
25
|
|
|
25
|
-
gt_tokenizer = Support.load_tokenizer(tokenizer)
|
|
26
|
+
gt_tokenizer = Support.load_tokenizer(tokenizer, pretokenizer: pretokenizer)
|
|
26
27
|
|
|
27
28
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
28
29
|
if packed
|
|
@@ -14,10 +14,13 @@ module Gigatoken
|
|
|
14
14
|
private_constant :SIZE_PATTERN
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
# Load TOKENIZER: a tokenizer.json path/directory, a
|
|
18
|
-
# repo id, or a .tiktoken file
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# Load TOKENIZER: a tokenizer.json path/directory, a packaged
|
|
18
|
+
# tiktoken encoding name, a HuggingFace repo id, or a .tiktoken file
|
|
19
|
+
# — see Gigatoken::Tokenizer.load. `pretokenizer:` is forwarded
|
|
20
|
+
# as-is; it's required for a bare .tiktoken path (which carries no
|
|
21
|
+
# scheme of its own) and ignored for the other shapes.
|
|
22
|
+
def load_tokenizer(spec, pretokenizer: nil)
|
|
23
|
+
Gigatoken::Tokenizer.load(spec, pretokenizer: pretokenizer)
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
# Parse a decimal byte size like "100MB", "2.5GB", or "1000000";
|
|
@@ -13,13 +13,14 @@ module Gigatoken
|
|
|
13
13
|
class Validate < Dry::CLI::Command
|
|
14
14
|
desc "Check that encode_files agrees with a Ruby-side split plus encode_batch on FILES"
|
|
15
15
|
|
|
16
|
-
argument :tokenizer, required: true, desc: "tokenizer.json path or directory, HuggingFace repo id, or .tiktoken file"
|
|
16
|
+
argument :tokenizer, required: true, desc: "tokenizer.json path or directory, packaged encoding name (e.g. \"cl100k_base\"), HuggingFace repo id, or .tiktoken file (requires --pretokenizer)"
|
|
17
17
|
argument :files, type: :array, required: true, desc: "UTF-8 text files to encode"
|
|
18
18
|
|
|
19
19
|
option :doc_separator, desc: 'document separator to split the files on, e.g. "<|endoftext|>"; whole files are single documents otherwise'
|
|
20
|
+
option :pretokenizer, desc: "pretokenizer scheme, required when TOKENIZER is a .tiktoken file (one of #{Native.pretokenizer_names.join(", ")}); ignored otherwise"
|
|
20
21
|
|
|
21
|
-
def call(tokenizer:, files:, doc_separator: nil, **)
|
|
22
|
-
gt_tokenizer = Support.load_tokenizer(tokenizer)
|
|
22
|
+
def call(tokenizer:, files:, doc_separator: nil, pretokenizer: nil, **)
|
|
23
|
+
gt_tokenizer = Support.load_tokenizer(tokenizer, pretokenizer: pretokenizer)
|
|
23
24
|
|
|
24
25
|
via_files = gt_tokenizer.encode_files(Support.text_file_source(files, doc_separator))
|
|
25
26
|
via_batch = gt_tokenizer.encode_batch(Support.split_docs(files, doc_separator))
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Vendored tiktoken encodings — provenance
|
|
2
|
+
|
|
3
|
+
The `.tiktoken` files in this directory are OpenAI's published BPE mergeable-rank
|
|
4
|
+
tables, vendored verbatim so `gigatoken` can resolve these encodings by name with
|
|
5
|
+
no network access and no writable cache directory.
|
|
6
|
+
|
|
7
|
+
Each file is a plain text table: one `base64(token_bytes) rank` pair per line. It
|
|
8
|
+
carries **mergeable ranks only** — the pretokenizer split regex and the special
|
|
9
|
+
tokens belong to the encoding's *definition*, not to the file, and live in code
|
|
10
|
+
(see the table below).
|
|
11
|
+
|
|
12
|
+
They live under `lib/` rather than a top-level `data/` directory because this
|
|
13
|
+
repo's `.gitignore` ignores `/data/` ("downloaded test data"); these are shipped
|
|
14
|
+
gem payload, not test fixtures.
|
|
15
|
+
|
|
16
|
+
## Files
|
|
17
|
+
|
|
18
|
+
| File | Bytes | sha256 |
|
|
19
|
+
|------|------:|--------|
|
|
20
|
+
| `r50k_base.tiktoken` | 835,554 | `306cd27f03c1a714eca7108e03d66b7dc042abe8c258b44c199a7ed9838dd930` |
|
|
21
|
+
| `cl100k_base.tiktoken` | 1,681,126 | `223921b76ee99bde995b7ff738513eef100fb51d18c93597a113bcffe865b2a7` |
|
|
22
|
+
| `o200k_base.tiktoken` | 3,613,922 | `446a9538cb6c348e3516120d7c08b09f57c36495e2acfffe59a5bf8b0cfb1a2d` |
|
|
23
|
+
|
|
24
|
+
## Source
|
|
25
|
+
|
|
26
|
+
Retrieved **2026-08-10** over HTTPS from OpenAI's public encodings endpoint:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
https://openaipublic.blob.core.windows.net/encodings/r50k_base.tiktoken
|
|
30
|
+
https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken
|
|
31
|
+
https://openaipublic.blob.core.windows.net/encodings/o200k_base.tiktoken
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
These are the same URLs `openai/tiktoken` itself fetches from, in
|
|
35
|
+
[`tiktoken_ext/openai_public.py`](https://github.com/openai/tiktoken/blob/main/tiktoken_ext/openai_public.py).
|
|
36
|
+
|
|
37
|
+
## Authenticity
|
|
38
|
+
|
|
39
|
+
Verified three independent ways at retrieval time:
|
|
40
|
+
|
|
41
|
+
1. **Transport** — HTTPS directly from `openaipublic.blob.core.windows.net`, the
|
|
42
|
+
origin OpenAI publishes and `tiktoken` itself downloads from.
|
|
43
|
+
2. **Publisher checksum** — each measured sha256 above matches the `expected_hash`
|
|
44
|
+
OpenAI publishes for that file in `openai_public.py`, fetched separately from
|
|
45
|
+
`github.com/openai/tiktoken`. Two independent channels agree on the bytes.
|
|
46
|
+
3. **Behavioral, against a third-party implementation** — loaded through
|
|
47
|
+
`gigatoken` with the pretokenizer and special tokens below, every token id
|
|
48
|
+
matched [`tiktoken_ruby`](https://github.com/IAPark/tiktoken_ruby) 0.0.17
|
|
49
|
+
(which embeds its own copy of these ranks) across a corpus covering ASCII,
|
|
50
|
+
CJK, ZWJ emoji sequences, combining accents, whitespace runs, source code,
|
|
51
|
+
and URLs. Resulting `vocab_size`: 50257 / 100277 / 200019.
|
|
52
|
+
|
|
53
|
+
## Encoding definitions
|
|
54
|
+
|
|
55
|
+
Transcribed from `openai_public.py` (each encoding's `pat_str` and
|
|
56
|
+
`special_tokens`), cross-checked against upstream gigatoken's own port in
|
|
57
|
+
`gigatoken/_load/tiktoken.py`. The scheme names are `PretokenizerType::NAMES`
|
|
58
|
+
values (`src/pretokenize/options.rs`).
|
|
59
|
+
|
|
60
|
+
| Encoding | Pretokenizer scheme | Special tokens |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `r50k_base` | `gpt2` | `<\|endoftext\|>`=50256 |
|
|
63
|
+
| `cl100k_base` | `gpt4` | `<\|endoftext\|>`=100257, `<\|fim_prefix\|>`=100258, `<\|fim_middle\|>`=100259, `<\|fim_suffix\|>`=100260, `<\|endofprompt\|>`=100276 |
|
|
64
|
+
| `o200k_base` | `o200k` | `<\|endoftext\|>`=199999, `<\|endofprompt\|>`=200018 |
|
|
65
|
+
|
|
66
|
+
`p50k_base` is deliberately absent: its ranks are not dense (50256 is left free
|
|
67
|
+
for `<|endoftext|>`), and the rank loader rejects non-dense ranks with
|
|
68
|
+
`"ranks must be dense"`. `p50k_edit` loads the same `p50k_base.tiktoken` ranks
|
|
69
|
+
and is absent for the identical reason.
|
|
70
|
+
|
|
71
|
+
## `o200k_harmony`
|
|
72
|
+
|
|
73
|
+
Packaged with **no new vendored file**: it reuses `o200k_base.tiktoken`'s
|
|
74
|
+
mergeable ranks and the `o200k` pretokenizer scheme verbatim. Confirmed
|
|
75
|
+
against `openai/tiktoken` 0.9.0 that `o200k_harmony()` in `openai_public.py`
|
|
76
|
+
calls `mergeable_ranks` with the same rank file and uses the same `pat_str` as
|
|
77
|
+
`o200k_base()` — only the special-token table differs.
|
|
78
|
+
|
|
79
|
+
That table (1091 entries: 10 named control tokens, 1081
|
|
80
|
+
`<|reserved_N|>` slots) is transcribed, not derived, from `o200k_harmony()`:
|
|
81
|
+
base specials `<|endoftext|>` 199999 and `<|endofprompt|>` 200018, then
|
|
82
|
+
`<|startoftext|>` 199998, `<|endoftext|>` 199999, `<|reserved_200000|>`
|
|
83
|
+
200000, `<|reserved_200001|>` 200001, `<|return|>` 200002, `<|constrain|>`
|
|
84
|
+
200003, `<|reserved_200004|>` 200004, `<|channel|>` 200005, `<|start|>`
|
|
85
|
+
200006, `<|end|>` 200007, `<|message|>` 200008, `<|reserved_200009|>` 200009,
|
|
86
|
+
`<|reserved_200010|>` 200010, `<|reserved_200011|>` 200011, `<|call|>`
|
|
87
|
+
200012, then `<|reserved_N|>` for `N` in `200013..201087`. The reserved range
|
|
88
|
+
is **not** contiguous from 200000 — the named control tokens sit inside
|
|
89
|
+
200000..200012, leaving reserved slots only at `{200000, 200001, 200004,
|
|
90
|
+
200009, 200010, 200011} ∪ [200013, 201087]`. A table built as "200000..201087
|
|
91
|
+
minus the named ids" invents `<|reserved_200002|>` and drops
|
|
92
|
+
`<|reserved_200018|>`; the transcription above avoids both.
|
|
93
|
+
|
|
94
|
+
**Not verified against `tiktoken_ruby`** (unlike the three encodings above):
|
|
95
|
+
`tiktoken_ruby` 0.0.17's own `o200k_harmony` table drops `<|endofprompt|>` —
|
|
96
|
+
it encodes the literal as six ordinary-text tokens rather than `[200018]` —
|
|
97
|
+
while treating `<|reserved_200018|>` as the sole literal at that id.
|
|
98
|
+
`openai/tiktoken` 0.9.0 keeps both `<|endofprompt|>` and
|
|
99
|
+
`<|reserved_200018|>`, at the same id, as Python dict construction preserves
|
|
100
|
+
both keys. `openai/tiktoken` is authoritative here; `tiktoken_ruby` is the
|
|
101
|
+
outlier. See `spec/gigatoken/differential_spec.rb` for the measurement and
|
|
102
|
+
the reduction-plus-pinning proof used in place of a `tiktoken_ruby`
|
|
103
|
+
comparison.
|
|
104
|
+
|
|
105
|
+
## Licence
|
|
106
|
+
|
|
107
|
+
The `tiktoken` project and its published encoding files are MIT licensed,
|
|
108
|
+
Copyright (c) 2022 OpenAI, Shantanu Jain. See
|
|
109
|
+
<https://github.com/openai/tiktoken/blob/main/LICENSE>. The files are vendored
|
|
110
|
+
here unmodified.
|