Atlas Ai commited on
Commit
d2a1a41
·
verified ·
1 Parent(s): 52d165c

Upload evaluated Quasar weights with portable inference and pinned codecs

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +62 -24
  2. codecs/audio/OFFICIAL_LICENSE +21 -0
  3. codecs/audio/OFFICIAL_WEIGHTS_TERMS.md +189 -0
  4. codecs/audio/README.md +199 -0
  5. codecs/audio/config.json +31 -0
  6. codecs/audio/model.safetensors +3 -0
  7. codecs/vision/LICENSE +201 -0
  8. codecs/vision/README.md +92 -0
  9. codecs/vision/config.json +31 -0
  10. codecs/vision/configuration_emu3visionvq.py +106 -0
  11. codecs/vision/image_processing_emu3visionvq.py +442 -0
  12. codecs/vision/model.safetensors +3 -0
  13. codecs/vision/modeling_emu3visionvq.py +822 -0
  14. codecs/vision/preprocessor_config.json +29 -0
  15. config.json +174 -0
  16. files.sha256.json +302 -0
  17. infer.py +60 -0
  18. model.safetensors +3 -0
  19. pyproject.toml +24 -0
  20. quasar/__init__.py +0 -0
  21. quasar/data/__init__.py +0 -0
  22. quasar/data/adapters.py +87 -0
  23. quasar/data/collator.py +35 -0
  24. quasar/data/curated.py +195 -0
  25. quasar/data/datasets.py +249 -0
  26. quasar/data/download.py +180 -0
  27. quasar/data/preprocessing.py +250 -0
  28. quasar/data/synthetic.py +383 -0
  29. quasar/data/text_preparation.py +514 -0
  30. quasar/evaluation/__init__.py +0 -0
  31. quasar/inference/__init__.py +0 -0
  32. quasar/inference/decoding.py +163 -0
  33. quasar/inference/extended.py +148 -0
  34. quasar/inference/generate.py +285 -0
  35. quasar/inference/universal.py +103 -0
  36. quasar/model/__init__.py +0 -0
  37. quasar/model/attention.py +58 -0
  38. quasar/model/axiom.py +124 -0
  39. quasar/model/config.py +73 -0
  40. quasar/model/layers.py +42 -0
  41. quasar/model/transformer.py +119 -0
  42. quasar/tokenizers/__init__.py +0 -0
  43. quasar/tokenizers/audio.py +48 -0
  44. quasar/tokenizers/audio_aligned.py +35 -0
  45. quasar/tokenizers/audio_complete.py +23 -0
  46. quasar/tokenizers/mesh.py +35 -0
  47. quasar/tokenizers/multimodal.py +197 -0
  48. quasar/tokenizers/multires_emu.py +49 -0
  49. quasar/tokenizers/restricted_emu.py +52 -0
  50. quasar/tokenizers/text.py +47 -0
README.md CHANGED
@@ -31,10 +31,12 @@ hybrid recurrence/attention backbone. Text, visual codes, audio codes, triangle
31
  meshes and tool-call proposals share one vocabulary and model. Frozen codecs
32
  convert between media and discrete tokens.
33
 
34
- **Release status: documentation and evaluation evidence only. No model weights,
35
- training data, codec weights or runnable inference service are included in this
36
- repository.** This card describes the preserved v5 model and a completed
37
- curriculum-research branch as of **21 October 2026**. Separate architecture
 
 
38
  experiments are still being evaluated and are excluded from these results.
39
 
40
  **Quality status:** a focused research branch produces recognizable images and
@@ -71,13 +73,14 @@ excludes that auxiliary term.
71
 
72
  ## Checkpoint identity
73
 
74
- The measurements below refer to these local artifacts, not to downloadable Hub
75
- weights. SHA-256 values identify the exact `model.safetensors` files.
 
76
 
77
  | Artifact | Role | SHA-256 |
78
  |---|---|---|
79
- | v5 full image continuation | Preserved comparison model | `e20242e9971b6a7cef7ca23a65c25faa70a007807a55a2c0cfe35f4f5682a643` |
80
- | `joint_repair/final` | Isolated, unpromoted curriculum-research model | `de4846e5e63c0004cf0b6331127ab421d6843f6efde04243c6d5448ab4545240` |
81
 
82
  ## Intended use and limitations
83
 
@@ -218,23 +221,39 @@ research session and is **not generated-image quality**. The earlier 64px
218
  real-photo probe measured 18.1176 dB; its original aspirational 20 dB gate remained
219
  false and was not relabeled as passed.
220
 
221
- ## Local usage requirements
222
-
223
- There is no `from_pretrained("Atlas-AI-research/Quasar")` weights release here.
224
- Running the model requires the matching local Quasar implementation, a compatible
225
- checkpoint directory with `config.json`, `model.safetensors` and tokenizer metadata,
226
- and independently available pinned codecs. The project is not a self-contained
227
- standard Transformers model, and local codec references must be resolved explicitly.
 
 
 
 
 
 
 
 
 
 
 
228
 
229
- With those prerequisites present, the existing local API is:
 
 
 
 
 
230
 
231
  ```python
232
  from PIL import Image
233
  from quasar.inference.extended import ExtendedGenerator
234
  from quasar.inference.decoding import Sampling
235
 
236
- # Point to a verified local checkpoint; this documentation repo has no weights.
237
- generator = ExtendedGenerator("PATH_TO_LOCAL_QUASAR_CHECKPOINT", "cuda")
238
  sampling = Sampling(temperature=0.5, top_k=64, top_p=0.9, seed=20261021)
239
  pixels = generator.generate_image("make a picture of a cat", sampling, resolution=128)
240
  Image.fromarray(pixels).save("quasar_cat.png")
@@ -243,15 +262,30 @@ frames = generator.generate_video(
243
  )
244
  ```
245
 
246
- This is a usage example, not a claim of acceptable output for arbitrary prompts.
247
- GPU use must be exclusive with local training/evaluation workers. No inference
248
- server, model weights or codec binaries are uploaded by creating this card.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
  ## Licensing and provenance
251
 
252
- The local Quasar project does not currently supply a project/weight distribution
253
- license. This card does not assign one or grant a weights license. No permissive
254
- license badge is implied by using PyTorch or permissively licensed components.
 
255
 
256
  The existing frozen Emu3 visual tokenizer is pinned at revision
257
  `c81f916ad371289e205310a7539255e8a9396488` under Apache-2.0. The local Descript DAC
@@ -261,6 +295,10 @@ Quasar weights license. Dataset, underlying page, photograph and per-object term
261
  remain separate; for example, the FineWeb database license does not replace
262
  underlying web-page rights.
263
 
 
 
 
 
264
  ## Reproducibility and next work
265
 
266
  The completed R&D snapshot passed 146 CPU tests with 1 skip; the legacy integration
 
31
  meshes and tool-call proposals share one vocabulary and model. Frozen codecs
32
  convert between media and discrete tokens.
33
 
34
+ **Release status: private, runnable research checkpoint release.** The default
35
+ root weights are the completed curriculum-research checkpoint shown below; the
36
+ preserved v5 comparison weights are in `variants/v5`. Matching source code,
37
+ text tokenizers and pinned frozen visual/audio codecs are included. Training
38
+ datasets, optimizer states and an inference service are not included.
39
+ This card describes these two snapshots as of **21 October 2026**. Separate architecture
40
  experiments are still being evaluated and are excluded from these results.
41
 
42
  **Quality status:** a focused research branch produces recognizable images and
 
73
 
74
  ## Checkpoint identity
75
 
76
+ The downloadable weights match the exact artifacts measured below. SHA-256 values
77
+ identify each unchanged `model.safetensors` file; packaging only makes metadata
78
+ paths portable and supplies the matching runtime.
79
 
80
  | Artifact | Role | SHA-256 |
81
  |---|---|---|
82
+ | `variants/v5/model.safetensors` | Preserved v5 comparison model | `e20242e9971b6a7cef7ca23a65c25faa70a007807a55a2c0cfe35f4f5682a643` |
83
+ | Root `model.safetensors` | Unpromoted `joint_repair/final` research model | `de4846e5e63c0004cf0b6331127ab421d6843f6efde04243c6d5448ab4545240` |
84
 
85
  ## Intended use and limitations
86
 
 
221
  real-photo probe measured 18.1176 dB; its original aspirational 20 dB gate remained
222
  false and was not relabeled as passed.
223
 
224
+ ## Download and run
225
+
226
+ This is a custom PyTorch checkpoint package, not a Transformers `AutoModel`.
227
+ Use the included `quasar` implementation rather than
228
+ `AutoModel.from_pretrained("Atlas-AI-research/Quasar")`. Authentication is needed
229
+ while the repository is private. Preserve a working CUDA-compatible PyTorch
230
+ installation; the measured environment used PyTorch 2.11.0+cu128. Install a suitable
231
+ PyTorch build separately when needed, then install the remaining inference dependencies:
232
+
233
+ ```bash
234
+ hf auth login
235
+ hf download Atlas-AI-research/Quasar --local-dir Quasar
236
+ cd Quasar
237
+ python -m pip install -r requirements-inference.txt
238
+ python verify_release.py --codecs
239
+ python infer.py --task image --prompt "make a picture of a cat" --output cat.png
240
+ python infer.py --task video --prompt "a small cyan square moving left on a gray background" --output motion.gif
241
+ ```
242
 
243
+ The default inference device is CUDA; the CLI refuses a busy GPU. Verification
244
+ uses CPU only. CPU inference is available with `--device cpu`; it does not train
245
+ the model. Select the preserved baseline with `--checkpoint variants/v5`.
246
+ CPU-only entry points hide CUDA and exclude the unused optional TorchAO quantizer
247
+ within their process, avoiding GPU probing by an unrelated installed backend.
248
+ The included package can also be imported directly from the downloaded directory:
249
 
250
  ```python
251
  from PIL import Image
252
  from quasar.inference.extended import ExtendedGenerator
253
  from quasar.inference.decoding import Sampling
254
 
255
+ # Root contains the exact research model shown in this card.
256
+ generator = ExtendedGenerator(".", "cuda")
257
  sampling = Sampling(temperature=0.5, top_k=64, top_p=0.9, seed=20261021)
258
  pixels = generator.generate_image("make a picture of a cat", sampling, resolution=128)
259
  Image.fromarray(pixels).save("quasar_cat.png")
 
262
  )
263
  ```
264
 
265
+ These examples do not establish acceptable output for arbitrary prompts. GPU use
266
+ must be exclusive with local training/evaluation workers. No inference server is
267
+ deployed. About 1.96 GiB of model/package files include both 361 MB Quasar weight
268
+ files and one shared copy of each frozen codec; training data and optimizer states
269
+ are omitted. The image/code mappings and original trained weights are unchanged.
270
+
271
+ The runtime is frozen from source commit
272
+ `4afbdccdbd3b0967b161a68658cfd3b92c42636f`, before the separate architecture
273
+ experiments. Two packaging-only edits resolve visual/audio codec paths relative
274
+ to the downloaded checkpoint. The included pinned visual codec Python is loaded
275
+ locally with `trust_remote_code=True`; review it as executable source. Inference
276
+ does not fetch additional model code or weights. Source identities and file hashes
277
+ are in [release.json](release.json) and [files.sha256.json](files.sha256.json).
278
+ The packaged release passed CPU finite-forward/parameter/hash checks for both
279
+ checkpoints and CPU encode/decode checks for both pinned codecs; see
280
+ [release_validation.json](release_validation.json). This verification does not
281
+ claim new semantic quality or replace the held-out results above.
282
 
283
  ## Licensing and provenance
284
 
285
+ This private upload does not select a distribution license for Quasar's code or
286
+ weights. No permissive license badge is implied by using PyTorch or permissively
287
+ licensed components. Component notices remain separate and are included with
288
+ their codec files; this upload does not make the repository public.
289
 
290
  The existing frozen Emu3 visual tokenizer is pinned at revision
291
  `c81f916ad371289e205310a7539255e8a9396488` under Apache-2.0. The local Descript DAC
 
295
  remain separate; for example, the FineWeb database license does not replace
296
  underlying web-page rights.
297
 
298
+ The visual component retains upstream copyright headers and Apache-2.0 terms in
299
+ `codecs/vision/LICENSE`. The audio component retains the official MIT license and
300
+ the pinned upstream weights statement under `codecs/audio`.
301
+
302
  ## Reproducibility and next work
303
 
304
  The completed R&D snapshot passed 146 CPU tests with 1 skip; the legacy integration
codecs/audio/OFFICIAL_LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present, Descript
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
codecs/audio/OFFICIAL_WEIGHTS_TERMS.md ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Descript Audio Codec (.dac): High-Fidelity Audio Compression with Improved RVQGAN
2
+
3
+ This repository contains training and inference scripts
4
+ for the Descript Audio Codec (.dac), a high fidelity general
5
+ neural audio codec, introduced in the paper titled **High-Fidelity Audio Compression with Improved RVQGAN**.
6
+
7
+ ![](https://static.arxiv.org/static/browse/0.3.4/images/icons/favicon-16x16.png) [arXiv Paper: High-Fidelity Audio Compression with Improved RVQGAN
8
+ ](http://arxiv.org/abs/2306.06546) <br>
9
+ 📈 [Demo Site](https://descript.notion.site/Descript-Audio-Codec-11389fce0ce2419891d6591a68f814d5)<br>
10
+ ⚙ [Model Weights](https://github.com/descriptinc/descript-audio-codec/releases/download/0.0.1/weights.pth)
11
+
12
+ 👉 With Descript Audio Codec, you can compress **44.1 KHz audio** into discrete codes at a **low 8 kbps bitrate**. <br>
13
+ 🤌 That's approximately **90x compression** while maintaining exceptional fidelity and minimizing artifacts. <br>
14
+ 💪 Our universal model works on all domains (speech, environment, music, etc.), making it widely applicable to generative modeling of all audio. <br>
15
+ 👌 It can be used as a drop-in replacement for EnCodec for all audio language modeling applications (such as AudioLMs, MusicLMs, MusicGen, etc.) <br>
16
+
17
+ <p align="center">
18
+ <img src="./assets/comparsion_stats.png" alt="Comparison of compressions approaches. Our model achieves a higher compression factor compared to all baseline methods. Our model has a ~90x compression factor compared to 32x compression factor of EnCodec and 64x of SoundStream. Note that we operate at a target bitrate of 8 kbps, whereas EnCodec operates at 24 kbps and SoundStream at 6 kbps. We also operate at 44.1 kHz, whereas EnCodec operates at 48 kHz and SoundStream operates at 24 kHz." width=35%></p>
19
+
20
+
21
+ ## Usage
22
+
23
+ ### Installation
24
+ ```
25
+ pip install descript-audio-codec
26
+ ```
27
+ OR
28
+
29
+ ```
30
+ pip install git+https://github.com/descriptinc/descript-audio-codec
31
+ ```
32
+
33
+ ### Weights
34
+ Weights are released as part of this repo under MIT license.
35
+ We release weights for models that can natively support 16 kHz, 24kHz, and 44.1kHz sampling rates.
36
+ Weights are automatically downloaded when you first run `encode` or `decode` command. You can cache them using one of the following commands
37
+ ```bash
38
+ python3 -m dac download # downloads the default 44kHz variant
39
+ python3 -m dac download --model_type 44khz # downloads the 44kHz variant
40
+ python3 -m dac download --model_type 24khz # downloads the 24kHz variant
41
+ python3 -m dac download --model_type 16khz # downloads the 16kHz variant
42
+ ```
43
+ We provide a Dockerfile that installs all required dependencies for encoding and decoding. The build process caches the default model weights inside the image. This allows the image to be used without an internet connection. [Please refer to instructions below.](#docker-image)
44
+
45
+
46
+ ### Compress audio
47
+ ```
48
+ python3 -m dac encode /path/to/input --output /path/to/output/codes
49
+ ```
50
+
51
+ This command will create `.dac` files with the same name as the input files.
52
+ It will also preserve the directory structure relative to input root and
53
+ re-create it in the output directory. Please use `python -m dac encode --help`
54
+ for more options.
55
+
56
+ ### Reconstruct audio from compressed codes
57
+ ```
58
+ python3 -m dac decode /path/to/output/codes --output /path/to/reconstructed_input
59
+ ```
60
+
61
+ This command will create `.wav` files with the same name as the input files.
62
+ It will also preserve the directory structure relative to input root and
63
+ re-create it in the output directory. Please use `python -m dac decode --help`
64
+ for more options.
65
+
66
+ ### Programmatic Usage
67
+ ```py
68
+ import dac
69
+ from audiotools import AudioSignal
70
+
71
+ # Download a model
72
+ model_path = dac.utils.download(model_type="44khz")
73
+ model = dac.DAC.load(model_path)
74
+
75
+ model.to('cuda')
76
+
77
+ # Load audio signal file
78
+ signal = AudioSignal('input.wav')
79
+
80
+ # Encode audio signal as one long file
81
+ # (may run out of GPU memory on long files)
82
+ signal.to(model.device)
83
+
84
+ x = model.preprocess(signal.audio_data, signal.sample_rate)
85
+ z, codes, latents, _, _ = model.encode(x)
86
+
87
+ # Decode audio signal
88
+ y = model.decode(z)
89
+
90
+ # Alternatively, use the `compress` and `decompress` functions
91
+ # to compress long files.
92
+
93
+ signal = signal.cpu()
94
+ x = model.compress(signal)
95
+
96
+ # Save and load to and from disk
97
+ x.save("compressed.dac")
98
+ x = dac.DACFile.load("compressed.dac")
99
+
100
+ # Decompress it back to an AudioSignal
101
+ y = model.decompress(x)
102
+
103
+ # Write to file
104
+ y.write('output.wav')
105
+ ```
106
+
107
+ ### Docker image
108
+ We provide a dockerfile to build a docker image with all the necessary
109
+ dependencies.
110
+ 1. Building the image.
111
+ ```
112
+ docker build -t dac .
113
+ ```
114
+ 2. Using the image.
115
+
116
+ Usage on CPU:
117
+ ```
118
+ docker run dac <command>
119
+ ```
120
+
121
+ Usage on GPU:
122
+ ```
123
+ docker run --gpus=all dac <command>
124
+ ```
125
+
126
+ `<command>` can be one of the compression and reconstruction commands listed
127
+ above. For example, if you want to run compression,
128
+
129
+ ```
130
+ docker run --gpus=all dac python3 -m dac encode ...
131
+ ```
132
+
133
+
134
+ ## Training
135
+ The baseline model configuration can be trained using the following commands.
136
+
137
+ ### Pre-requisites
138
+ Please install the correct dependencies
139
+ ```
140
+ pip install -e ".[dev]"
141
+ ```
142
+
143
+ ## Environment setup
144
+
145
+ We have provided a Dockerfile and docker compose setup that makes running experiments easy.
146
+
147
+ To build the docker image do:
148
+
149
+ ```
150
+ docker compose build
151
+ ```
152
+
153
+ Then, to launch a container, do:
154
+
155
+ ```
156
+ docker compose run -p 8888:8888 -p 6006:6006 dev
157
+ ```
158
+
159
+ The port arguments (`-p`) are optional, but useful if you want to launch a Jupyter and Tensorboard instances within the container. The
160
+ default password for Jupyter is `password`, and the current directory
161
+ is mounted to `/u/home/src`, which also becomes the working directory.
162
+
163
+ Then, run your training command.
164
+
165
+
166
+ ### Single GPU training
167
+ ```
168
+ export CUDA_VISIBLE_DEVICES=0
169
+ python scripts/train.py --args.load conf/ablations/baseline.yml --save_path runs/baseline/
170
+ ```
171
+
172
+ ### Multi GPU training
173
+ ```
174
+ export CUDA_VISIBLE_DEVICES=0,1
175
+ torchrun --nproc_per_node gpu scripts/train.py --args.load conf/ablations/baseline.yml --save_path runs/baseline/
176
+ ```
177
+
178
+ ## Testing
179
+ We provide two test scripts to test CLI + training functionality. Please
180
+ make sure that the trainig pre-requisites are satisfied before launching these
181
+ tests. To launch these tests please run
182
+ ```
183
+ python -m pytest tests
184
+ ```
185
+
186
+ ## Results
187
+
188
+ <p align="left">
189
+ <img src="./assets/objective_comparisons.png" width=75%></p>
codecs/audio/README.md ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ pipeline_tag: feature-extraction
4
+ ---
5
+
6
+ # Model Card for Model ID
7
+
8
+ <!-- Provide a quick summary of what the model is/does. -->
9
+
10
+
11
+
12
+ ## Model Details
13
+
14
+ ### Model Description
15
+
16
+ <!-- Provide a longer summary of what this model is. -->
17
+
18
+ This is the model card of a 🤗 transformers model that has been pushed on the Hub. This model card has been automatically generated.
19
+
20
+ - **Developed by:** [More Information Needed]
21
+ - **Funded by [optional]:** [More Information Needed]
22
+ - **Shared by [optional]:** [More Information Needed]
23
+ - **Model type:** [More Information Needed]
24
+ - **Language(s) (NLP):** [More Information Needed]
25
+ - **License:** [More Information Needed]
26
+ - **Finetuned from model [optional]:** [More Information Needed]
27
+
28
+ ### Model Sources [optional]
29
+
30
+ <!-- Provide the basic links for the model. -->
31
+
32
+ - **Repository:** [More Information Needed]
33
+ - **Paper [optional]:** [More Information Needed]
34
+ - **Demo [optional]:** [More Information Needed]
35
+
36
+ ## Uses
37
+
38
+ <!-- Address questions around how the model is intended to be used, including the foreseeable users of the model and those affected by the model. -->
39
+
40
+ ### Direct Use
41
+
42
+ <!-- This section is for the model use without fine-tuning or plugging into a larger ecosystem/app. -->
43
+
44
+ [More Information Needed]
45
+
46
+ ### Downstream Use [optional]
47
+
48
+ <!-- This section is for the model use when fine-tuned for a task, or when plugged into a larger ecosystem/app -->
49
+
50
+ [More Information Needed]
51
+
52
+ ### Out-of-Scope Use
53
+
54
+ <!-- This section addresses misuse, malicious use, and uses that the model will not work well for. -->
55
+
56
+ [More Information Needed]
57
+
58
+ ## Bias, Risks, and Limitations
59
+
60
+ <!-- This section is meant to convey both technical and sociotechnical limitations. -->
61
+
62
+ [More Information Needed]
63
+
64
+ ### Recommendations
65
+
66
+ <!-- This section is meant to convey recommendations with respect to the bias, risk, and technical limitations. -->
67
+
68
+ Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.
69
+
70
+ ## How to Get Started with the Model
71
+
72
+ Use the code below to get started with the model.
73
+
74
+ [More Information Needed]
75
+
76
+ ## Training Details
77
+
78
+ ### Training Data
79
+
80
+ <!-- This should link to a Dataset Card, perhaps with a short stub of information on what the training data is all about as well as documentation related to data pre-processing or additional filtering. -->
81
+
82
+ [More Information Needed]
83
+
84
+ ### Training Procedure
85
+
86
+ <!-- This relates heavily to the Technical Specifications. Content here should link to that section when it is relevant to the training procedure. -->
87
+
88
+ #### Preprocessing [optional]
89
+
90
+ [More Information Needed]
91
+
92
+
93
+ #### Training Hyperparameters
94
+
95
+ - **Training regime:** [More Information Needed] <!--fp32, fp16 mixed precision, bf16 mixed precision, bf16 non-mixed precision, fp16 non-mixed precision, fp8 mixed precision -->
96
+
97
+ #### Speeds, Sizes, Times [optional]
98
+
99
+ <!-- This section provides information about throughput, start/end time, checkpoint size if relevant, etc. -->
100
+
101
+ [More Information Needed]
102
+
103
+ ## Evaluation
104
+
105
+ <!-- This section describes the evaluation protocols and provides the results. -->
106
+
107
+ ### Testing Data, Factors & Metrics
108
+
109
+ #### Testing Data
110
+
111
+ <!-- This should link to a Dataset Card if possible. -->
112
+
113
+ [More Information Needed]
114
+
115
+ #### Factors
116
+
117
+ <!-- These are the things the evaluation is disaggregating by, e.g., subpopulations or domains. -->
118
+
119
+ [More Information Needed]
120
+
121
+ #### Metrics
122
+
123
+ <!-- These are the evaluation metrics being used, ideally with a description of why. -->
124
+
125
+ [More Information Needed]
126
+
127
+ ### Results
128
+
129
+ [More Information Needed]
130
+
131
+ #### Summary
132
+
133
+
134
+
135
+ ## Model Examination [optional]
136
+
137
+ <!-- Relevant interpretability work for the model goes here -->
138
+
139
+ [More Information Needed]
140
+
141
+ ## Environmental Impact
142
+
143
+ <!-- Total emissions (in grams of CO2eq) and additional considerations, such as electricity usage, go here. Edit the suggested text below accordingly -->
144
+
145
+ Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700).
146
+
147
+ - **Hardware Type:** [More Information Needed]
148
+ - **Hours used:** [More Information Needed]
149
+ - **Cloud Provider:** [More Information Needed]
150
+ - **Compute Region:** [More Information Needed]
151
+ - **Carbon Emitted:** [More Information Needed]
152
+
153
+ ## Technical Specifications [optional]
154
+
155
+ ### Model Architecture and Objective
156
+
157
+ [More Information Needed]
158
+
159
+ ### Compute Infrastructure
160
+
161
+ [More Information Needed]
162
+
163
+ #### Hardware
164
+
165
+ [More Information Needed]
166
+
167
+ #### Software
168
+
169
+ [More Information Needed]
170
+
171
+ ## Citation [optional]
172
+
173
+ <!-- If there is a paper or blog post introducing the model, the APA and Bibtex information for that should go in this section. -->
174
+
175
+ **BibTeX:**
176
+
177
+ [More Information Needed]
178
+
179
+ **APA:**
180
+
181
+ [More Information Needed]
182
+
183
+ ## Glossary [optional]
184
+
185
+ <!-- If relevant, include terms and calculations in this section that can help readers understand the model or model card. -->
186
+
187
+ [More Information Needed]
188
+
189
+ ## More Information [optional]
190
+
191
+ [More Information Needed]
192
+
193
+ ## Model Card Authors [optional]
194
+
195
+ [More Information Needed]
196
+
197
+ ## Model Card Contact
198
+
199
+ [More Information Needed]
codecs/audio/config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "DacModel"
4
+ ],
5
+ "codebook_dim": 8,
6
+ "codebook_loss_weight": 1.0,
7
+ "codebook_size": 1024,
8
+ "commitment_loss_weight": 0.25,
9
+ "decoder_hidden_size": 1536,
10
+ "downsampling_ratios": [
11
+ 2,
12
+ 4,
13
+ 5,
14
+ 8
15
+ ],
16
+ "encoder_hidden_size": 64,
17
+ "hidden_size": 1024,
18
+ "hop_length": 512,
19
+ "model_type": "dac",
20
+ "n_codebooks": 32,
21
+ "quantizer_dropout": 0.0,
22
+ "sampling_rate": 24000,
23
+ "torch_dtype": "float32",
24
+ "transformers_version": "4.42.0.dev0",
25
+ "upsampling_ratios": [
26
+ 8,
27
+ 5,
28
+ 4,
29
+ 2
30
+ ]
31
+ }
codecs/audio/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7452e3fc6972991da871ae1a1c9d3e8e219aa247cc0f757018867d6a7fe59aaf
3
+ size 298652268
codecs/vision/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
codecs/vision/README.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: transformers
4
+ ---
5
+
6
+ <div align='center'>
7
+ <h1>Emu3: Next-Token Prediction is All You Need</h1h1>
8
+ <h3></h3>
9
+
10
+ [Emu3 Team, BAAI](https://www.baai.ac.cn/english.html)
11
+
12
+ | [Project Page](https://emu.baai.ac.cn) | [Paper](https://huggingface.co/papers/2409.18869) | [🤗HF Models](https://huggingface.co/collections/BAAI/emu3-66f4e64f70850ff358a2e60f) | [github](https://github.com/baaivision/Emu3) | [Demo](https://huggingface.co/spaces/BAAI/Emu3) |
13
+
14
+
15
+ </div>
16
+
17
+ <div align='center'>
18
+ <img src="https://github.com/baaivision/Emu3/blob/main/assets/arch.png?raw=True" class="interpolation-image" alt="arch." height="80%" width="70%" />
19
+ </div>
20
+
21
+ We introduce **Emu3**, a new suite of state-of-the-art multimodal models trained solely with **<i>next-token prediction</i>**! By tokenizing images, text, and videos into a discrete space, we train a single transformer from scratch on a mixture of multimodal sequences.
22
+
23
+ ### Emu3 excels in both generation and perception
24
+ **Emu3** outperforms several well-established task-specific models in both generation and perception tasks, surpassing flagship open models such as SDXL, LLaVA-1.6 and OpenSora-1.2, while eliminating the need for diffusion or compositional architectures.
25
+
26
+ <div align='center'>
27
+ <img src="https://github.com/baaivision/Emu3/blob/main//assets/comparison.png?raw=True" class="interpolation-image" alt="comparison." height="80%" width="80%" />
28
+ </div>
29
+
30
+ ### Highlights
31
+
32
+ - **Emu3** is capable of generating high-quality images following the text input, by simply predicting the next vision token. The model naturally supports flexible resolutions and styles.
33
+ - **Emu3** shows strong vision-language understanding capabilities to see the physical world and provides coherent text responses. Notably, this capability is achieved without depending on a CLIP and a pretrained LLM.
34
+ - **Emu3** simply generates a video causally by predicting the next token in a video sequence, unlike the video diffusion model as in Sora. With a video in context, Emu3 can also naturally extend the video and predict what will happen next.
35
+
36
+ ### Quickstart for Autoencoding
37
+ ```python
38
+ import os
39
+ import os.path as osp
40
+
41
+ from PIL import Image
42
+ import torch
43
+ from transformers import AutoModel, AutoImageProcessor
44
+
45
+ MODEL_HUB = "BAAI/Emu3-VisionTokenizer"
46
+
47
+ model = AutoModel.from_pretrained(MODEL_HUB, trust_remote_code=True).eval().cuda()
48
+ processor = AutoImageProcessor.from_pretrained(MODEL_HUB, trust_remote_code=True)
49
+
50
+ # TODO: you need to modify the path here
51
+ VIDEO_FRAMES_PATH = "YOUR_VIDEO_FRAMES_PATH"
52
+
53
+ video = os.listdir(VIDEO_FRAMES_PATH)
54
+ video.sort()
55
+ video = [Image.open(osp.join(VIDEO_FRAMES_PATH, v)) for v in video]
56
+
57
+ images = processor(video, return_tensors="pt")["pixel_values"]
58
+ images = images.unsqueeze(0).cuda()
59
+
60
+ # image autoencode
61
+ image = images[:, 0]
62
+ print(image.shape)
63
+ with torch.no_grad():
64
+ # encode
65
+ codes = model.encode(image)
66
+ # decode
67
+ recon = model.decode(codes)
68
+
69
+ recon = recon.view(-1, *recon.shape[2:])
70
+ recon_image = processor.postprocess(recon)["pixel_values"][0]
71
+ recon_image.save("recon_image.png")
72
+
73
+ # video autoencode
74
+ images = images.view(
75
+ -1,
76
+ model.config.temporal_downsample_factor,
77
+ *images.shape[2:],
78
+ )
79
+
80
+ print(images.shape)
81
+ with torch.no_grad():
82
+ # encode
83
+ codes = model.encode(images)
84
+ # decode
85
+ recon = model.decode(codes)
86
+
87
+ recon = recon.view(-1, *recon.shape[2:])
88
+ recon_images = processor.postprocess(recon)["pixel_values"]
89
+ for idx, im in enumerate(recon_images):
90
+ im.save(f"recon_video_{idx}.png")
91
+
92
+ ```
codecs/vision/config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Emu3VisionVQModel"
4
+ ],
5
+ "attn_resolutions": [
6
+ 3
7
+ ],
8
+ "auto_map": {
9
+ "AutoConfig": "configuration_emu3visionvq.Emu3VisionVQConfig",
10
+ "AutoModel": "modeling_emu3visionvq.Emu3VisionVQModel"
11
+ },
12
+ "ch": 256,
13
+ "ch_mult": [
14
+ 1,
15
+ 2,
16
+ 2,
17
+ 4
18
+ ],
19
+ "codebook_size": 32768,
20
+ "double_z": false,
21
+ "dropout": 0.0,
22
+ "embed_dim": 4,
23
+ "in_channels": 3,
24
+ "model_type": "Emu3VisionVQ",
25
+ "num_res_blocks": 2,
26
+ "out_channels": 3,
27
+ "temporal_downsample_factor": 4,
28
+ "torch_dtype": "float32",
29
+ "transformers_version": "4.44.0",
30
+ "z_channels": 4
31
+ }
codecs/vision/configuration_emu3visionvq.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2024 The Emu team, BAAI and The HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """ Emu3VisionVQ model configuration """
16
+
17
+ from typing import List
18
+
19
+ from transformers.configuration_utils import PretrainedConfig
20
+ from transformers.utils import logging
21
+
22
+
23
+ logger = logging.get_logger(__name__)
24
+
25
+
26
+ class Emu3VisionVQConfig(PretrainedConfig):
27
+ r"""
28
+ This is the configuration class to store the configuration of a [`Emu3VisionVQ`]. It is used to instantiate an video movq
29
+ model according to the specified arguments, defining the model architecture. Instantiating a configuration with the
30
+ defaults will yield a configuration to the VQ model presented in Emu3 paper.
31
+
32
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
33
+ documentation from [`PretrainedConfig`] for more information.
34
+
35
+
36
+ Args:
37
+ codebook_size (`int`, *optional*, defaults to 32768):
38
+ Codebook size of the VQ model.
39
+ embed_dim (`int`, *optional*, defaults to 4):
40
+ Dimension of the quantized vector in codebook.
41
+ z_channels (`int`, *optional*, defaults to 4):
42
+ Dimension of the output channel of encoder and the input channel of decoder
43
+ double_z (`bool`, *optional*, defaults to False):
44
+ Whether double the output dim of the encoder.
45
+ in_channels (`int`, *optional*, defaults to 3):
46
+ Input channel of encoder.
47
+ out_channels (`int`, *optional*, defaults to 3):
48
+ Output channel of decoder.
49
+ temporal_downsample_factor (`int`, *optional*, defaults to 4):
50
+ Temporal downsample factor.
51
+ ch (`int`, *optional*, defaults to 256):
52
+ Basic channel number of the intermediate blocks.
53
+ ch_mult (`List[int]`, *optional*, defaults to `[1, 2, 2, 4]`):
54
+ Channel scaling factor of the intermediate blocks.
55
+ num_res_blocks (`int`, *optional*, defaults to 2):
56
+ Residual block number in each stage.
57
+ attn_resolutions (`List[int]`, *optional*, defaults to 3):
58
+ Stage indices to apply attention.
59
+ dropout (`float`, *optional*, defaults to 0.0):
60
+ Dropout probability.
61
+
62
+ ```python
63
+ >>> from transformers import Emu3VisionVQ, Emu3VisionVQConfig
64
+
65
+ >>> # Initializing a video VQ model of Emu3 configuration
66
+ >>> configuration = Emu3VisionVQConfig()
67
+
68
+ >>> # Initializing a model from the Emu3 VQ model style configuration
69
+ >>> model = Emu3VisionVQModel(configuration)
70
+
71
+ >>> # Accessing the model configuration
72
+ >>> configuration = model.config
73
+ ```"""
74
+
75
+ model_type = "Emu3VisionVQ"
76
+
77
+ def __init__(
78
+ self,
79
+ codebook_size: int = 32768,
80
+ embed_dim: int = 4,
81
+ z_channels: int = 4,
82
+ double_z: bool = False,
83
+ in_channels: int = 3,
84
+ out_channels: int = 3,
85
+ temporal_downsample_factor: int = 4,
86
+ ch: int = 256,
87
+ ch_mult: List[int] = [1, 2, 2, 4],
88
+ num_res_blocks: int = 2,
89
+ attn_resolutions: List[int] = [3],
90
+ dropout: float = 0.0,
91
+ **kwargs,
92
+ ):
93
+ super().__init__(**kwargs)
94
+
95
+ self.codebook_size = codebook_size
96
+ self.embed_dim = embed_dim
97
+ self.z_channels = z_channels
98
+ self.double_z = double_z
99
+ self.in_channels = in_channels
100
+ self.out_channels = out_channels
101
+ self.temporal_downsample_factor = temporal_downsample_factor
102
+ self.ch = ch
103
+ self.ch_mult = ch_mult
104
+ self.num_res_blocks = num_res_blocks
105
+ self.attn_resolutions = attn_resolutions
106
+ self.dropout = dropout
codecs/vision/image_processing_emu3visionvq.py ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2024 The Emu team, BAAI and The HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Image processor class for Emu3VisionVQ."""
16
+
17
+
18
+ import math
19
+ from typing import Dict, List, Optional, Union
20
+
21
+ import numpy as np
22
+
23
+ from transformers.image_processing_utils import BaseImageProcessor, BatchFeature
24
+ from transformers.image_transforms import (
25
+ convert_to_rgb,
26
+ resize,
27
+ to_channel_dimension_format,
28
+ )
29
+ from transformers.image_utils import (
30
+ IMAGENET_STANDARD_MEAN,
31
+ IMAGENET_STANDARD_STD,
32
+ ChannelDimension,
33
+ ImageInput,
34
+ PILImageResampling,
35
+ get_image_size,
36
+ infer_channel_dimension_format,
37
+ is_scaled_image,
38
+ make_list_of_images,
39
+ to_numpy_array,
40
+ valid_images,
41
+ validate_preprocess_arguments,
42
+ )
43
+ from transformers.utils import TensorType, is_vision_available, logging
44
+
45
+
46
+ logger = logging.get_logger(__name__)
47
+
48
+
49
+ if is_vision_available():
50
+ from PIL import Image
51
+
52
+
53
+ def smart_resize(
54
+ height: int, width: int, factor: int = 8, min_pixels: int = 512 * 512, max_pixels: int = 1024 * 1024
55
+ ):
56
+ """Rescales the image so that the following conditions are met:
57
+
58
+ 1. Both dimensions (height and width) are divisible by 'factor'.
59
+
60
+ 2. The total number of pixels is within the range ['min_pixels', 'max_pixels'].
61
+
62
+ 3. The aspect ratio of the image is maintained as closely as possible.
63
+
64
+ """
65
+ if height < factor or width < factor:
66
+ raise ValueError(f"height:{height} or width:{width} must be larger than factor:{factor}")
67
+ elif max(height, width) / min(height, width) > 5:
68
+ raise ValueError(
69
+ f"absolute aspect ratio must be smaller than 5, got {max(height, width) / min(height, width)}"
70
+ )
71
+
72
+ h_bar = round(height / factor) * factor
73
+ w_bar = round(width / factor) * factor
74
+ if h_bar * w_bar > max_pixels:
75
+ beta = math.sqrt((height * width) / max_pixels)
76
+ h_bar = math.floor(height / beta / factor) * factor
77
+ w_bar = math.floor(width / beta / factor) * factor
78
+ elif h_bar * w_bar < min_pixels:
79
+ beta = math.sqrt(min_pixels / (height * width))
80
+ h_bar = math.ceil(height * beta / factor) * factor
81
+ w_bar = math.ceil(width * beta / factor) * factor
82
+
83
+ return h_bar, w_bar
84
+
85
+
86
+ class Emu3VisionVQImageProcessor(BaseImageProcessor):
87
+ r"""
88
+ Constructs a Emu3VisionVQ image processor that dynamically resizes images based on the original images.
89
+
90
+ Args:
91
+ do_resize (`bool`, *optional*, defaults to `True`):
92
+ Whether to resize the image's (height, width) dimensions.
93
+ resample (`PILImageResampling`, *optional*, defaults to `Resampling.BICUBIC`):
94
+ Resampling filter to use when resizing the image.
95
+ do_rescale (`bool`, *optional*, defaults to `True`):
96
+ Whether to rescale the image by the specified scale `rescale_factor`.
97
+ rescale_factor (`int` or `float`, *optional*, defaults to `1/255`):
98
+ Scale factor to use if rescaling the image.
99
+ do_normalize (`bool`, *optional*, defaults to `True`):
100
+ Whether to normalize the image.
101
+ image_mean (`float` or `List[float]`, *optional*, defaults to `[0.5, 0.5, 0.5]`):
102
+ Mean to use if normalizing the image. This is a float or list of floats for each channel in the image.
103
+ image_std (`float` or `List[float]`, *optional*, defaults to `[0.5, 0.5, 0.5]`):
104
+ Standard deviation to use if normalizing the image. This is a float or list of floats for each channel in the image.
105
+ do_convert_rgb (`bool`, *optional*, defaults to `True`):
106
+ Whether to convert the image to RGB.
107
+ min_pixels (`int`, *optional*, defaults to `512 * 512`):
108
+ The min pixels of the image to resize the image.
109
+ max_pixels (`int`, *optional*, defaults to `1024 * 1024`):
110
+ The max pixels of the image to resize the image.
111
+ spatial_factor (`int`, *optional*, defautls to 8):
112
+ The spatial downsample factor the image will be downsampled in feature extracting phase
113
+ """
114
+
115
+ model_input_names = ["pixel_values"]
116
+
117
+ def __init__(
118
+ self,
119
+ do_resize: bool = True,
120
+ resample: PILImageResampling = PILImageResampling.BICUBIC,
121
+ do_rescale: bool = True,
122
+ rescale_factor: Union[int, float] = 1 / 255,
123
+ do_normalize: bool = True,
124
+ image_mean: Optional[Union[float, List[float]]] = None,
125
+ image_std: Optional[Union[float, List[float]]] = None,
126
+ do_convert_rgb: bool = True,
127
+ min_pixels: int = 512 * 512,
128
+ max_pixels: int = 1024 * 1024,
129
+ spatial_factor: int = 8,
130
+ **kwargs,
131
+ ) -> None:
132
+ super().__init__(**kwargs)
133
+ self.do_resize = do_resize
134
+ self.resample = resample
135
+ self.do_rescale = do_rescale
136
+ self.rescale_factor = rescale_factor
137
+ self.do_normalize = do_normalize
138
+ self.image_mean = image_mean if image_mean is not None else IMAGENET_STANDARD_MEAN
139
+ self.image_std = image_std if image_std is not None else IMAGENET_STANDARD_STD
140
+ self.min_pixels = min_pixels
141
+ self.max_pixels = max_pixels
142
+ self.size = {"min_pixels": min_pixels, "max_pixels": max_pixels}
143
+ self.do_convert_rgb = do_convert_rgb
144
+ self.spatial_factor = spatial_factor
145
+
146
+ def _preprocess(
147
+ self,
148
+ images: ImageInput,
149
+ do_resize: Optional[bool] = None,
150
+ resample: PILImageResampling = None,
151
+ do_rescale: Optional[bool] = None,
152
+ rescale_factor: Optional[float] = None,
153
+ do_normalize: Optional[bool] = None,
154
+ image_mean: Optional[Union[float, List[float]]] = None,
155
+ image_std: Optional[Union[float, List[float]]] = None,
156
+ do_convert_rgb: Optional[bool] = None,
157
+ spatial_factor: Optional[int] = None,
158
+ input_data_format: Optional[Union[str, ChannelDimension]] = None,
159
+ output_data_format: Optional[Union[str, ChannelDimension]] = ChannelDimension.FIRST,
160
+ ):
161
+ """
162
+ Preprocess an image or batch of images. Copy of the `preprocess` method from `CLIPImageProcessor`.
163
+
164
+ Args:
165
+ images (`ImageInput`):
166
+ Image or batch of images to preprocess. Expects pixel values ranging from 0 to 255. If pixel values range from 0 to 1, set `do_rescale=False`.
167
+ do_resize (`bool`, *optional*, defaults to `self.do_resize`):
168
+ Whether to resize the image.
169
+ resample (`PILImageResampling`, *optional*, defaults to `self.resample`):
170
+ Resampling filter to use if resizing the image. This can be one of the `PILImageResampling` enums.
171
+ do_rescale (`bool`, *optional*, defaults to `self.do_rescale`):
172
+ Whether to rescale the image.
173
+ rescale_factor (`float`, *optional*, defaults to `self.rescale_factor`):
174
+ Scale factor to use if rescaling the image.
175
+ do_normalize (`bool`, *optional*, defaults to `self.do_normalize`):
176
+ Whether to normalize the image.
177
+ image_mean (`float` or `List[float]`, *optional*, defaults to `self.image_mean`):
178
+ Mean to use if normalizing the image. Can be a float or a list of floats corresponding to the number of channels in the image.
179
+ image_std (`float` or `List[float]`, *optional*, defaults to `self.image_std`):
180
+ Standard deviation to use if normalizing the image. Can be a float or a list of floats corresponding to the number of channels in the image.
181
+ do_convert_rgb (`bool`, *optional*, defaults to `self.do_convert_rgb`):
182
+ Whether to convert the image to RGB.
183
+ spatial_factor (`int`, *optional*, defaults to `self.spatial_factor`):
184
+ The spatial downsample factor the image will be downsampled in feature extracting phase
185
+ input_data_format (`ChannelDimension` or `str`, *optional*):
186
+ The channel dimension format for the input image. Can be one of:
187
+ - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
188
+ - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
189
+ - `"none"` or `ChannelDimension.NONE`: image in (height, width) format. - `"none"` or `ChannelDimension.NONE`: image in (height, width) format.
190
+ output_data_format (`ChannelDimension`, *optional*, defaults to `ChannelDimension.FIRST`):
191
+ The channel dimension format for the output image. Can be one of:
192
+ - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
193
+ - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
194
+ - Unset: Use the channel dimension format of the input image.
195
+ """
196
+ spatial_factor = spatial_factor if spatial_factor is not None else self.spatial_factor
197
+
198
+ images = make_list_of_images(images)
199
+ if do_convert_rgb:
200
+ images = [convert_to_rgb(image) for image in images]
201
+
202
+ # All transformations expect numpy arrays.
203
+ images = [to_numpy_array(image) for image in images]
204
+
205
+ if is_scaled_image(images[0]) and do_rescale:
206
+ logger.warning_once(
207
+ "It looks like you are trying to rescale already rescaled images. If the input"
208
+ "pixel_values.append()images have pixel values between 0 and 1, set `do_rescale=False` to avoid rescaling them again."
209
+ )
210
+
211
+ if input_data_format is None:
212
+ # We assume that all images have the same channel dimension format.
213
+ input_data_format = infer_channel_dimension_format(images[0])
214
+
215
+ height, width = get_image_size(images[0], channel_dim=input_data_format)
216
+ resized_height, resized_width = height, width
217
+ processed_images = []
218
+ for image in images:
219
+ if do_resize:
220
+ resized_height, resized_width = smart_resize(
221
+ height,
222
+ width,
223
+ factor=spatial_factor,
224
+ min_pixels=self.min_pixels,
225
+ max_pixels=self.max_pixels,
226
+ )
227
+ image = resize(
228
+ image, size=(resized_height, resized_width), resample=resample, input_data_format=input_data_format
229
+ )
230
+
231
+ if do_rescale:
232
+ image = self.rescale(image, scale=rescale_factor, input_data_format=input_data_format)
233
+
234
+ if do_normalize:
235
+ image = self.normalize(
236
+ image=image, mean=image_mean, std=image_std, input_data_format=input_data_format
237
+ )
238
+
239
+ image = to_channel_dimension_format(image, output_data_format, input_channel_dim=input_data_format)
240
+ processed_images.append(image)
241
+
242
+ image = np.array(processed_images)
243
+ return image
244
+
245
+ def preprocess(
246
+ self,
247
+ images: ImageInput,
248
+ do_resize: Optional[bool] = None,
249
+ resample: PILImageResampling = None,
250
+ do_rescale: Optional[bool] = None,
251
+ rescale_factor: Optional[float] = None,
252
+ do_normalize: Optional[bool] = None,
253
+ image_mean: Optional[Union[float, List[float]]] = None,
254
+ image_std: Optional[Union[float, List[float]]] = None,
255
+ do_convert_rgb: Optional[bool] = None,
256
+ spatial_factor: Optional[int] = None,
257
+ return_tensors: Optional[Union[str, TensorType]] = None,
258
+ input_data_format: Optional[Union[str, ChannelDimension]] = None,
259
+ output_data_format: Optional[Union[str, ChannelDimension]] = ChannelDimension.FIRST,
260
+ ):
261
+ """
262
+ Args:
263
+ images (`ImageInput`):
264
+ Image to preprocess. Expects a single or batch of images with pixel values ranging from 0 to 255. If
265
+ passing in images with pixel values between 0 and 1, set `do_rescale=False`.
266
+ do_resize (`bool`, *optional*, defaults to `self.do_resize`):
267
+ Whether to resize the image.
268
+ resample (`int`, *optional*, defaults to `self.resample`):
269
+ Resampling filter to use if resizing the image. This can be one of the enum `PILImageResampling`. Only
270
+ has an effect if `do_resize` is set to `True`.
271
+ do_rescale (`bool`, *optional*, defaults to `self.do_rescale`):
272
+ Whether to rescale the image.
273
+ rescale_factor (`float`, *optional*, defaults to `self.rescale_factor`):
274
+ Rescale factor to rescale the image by if `do_rescale` is set to `True`.
275
+ do_normalize (`bool`, *optional*, defaults to `self.do_normalize`):
276
+ Whether to normalize the image.
277
+ image_mean (`float` or `List[float]`, *optional*, defaults to `self.image_mean`):
278
+ Image mean to use for normalization. Only has an effect if `do_normalize` is set to `True`.
279
+ image_std (`float` or `List[float]`, *optional*, defaults to `self.image_std`):
280
+ Image standard deviation to use for normalization. Only has an effect if `do_normalize` is set to `True`.
281
+ do_convert_rgb (`bool`, *optional*, defaults to `self.do_convert_rgb`):
282
+ Whether to convert the image to RGB.
283
+ spatial_factor (`int`, *optional*, defaults to `self.spatial_factor`):
284
+ The spatial downsample factor the image will be downsampled in feature extracting phase
285
+ return_tensors (`str` or `TensorType`, *optional*):
286
+ The type of tensors to return. Can be one of:
287
+ - Unset: Return a list of `np.ndarray`.
288
+ - `TensorType.PYTORCH` or `'pt'`: Return a batch of type `torch.Tensor`.
289
+ - `TensorType.NUMPY` or `'np'`: Return a batch of type `np.ndarray`.
290
+ input_data_format (`ChannelDimension` or `str`, *optional*):
291
+ The channel dimension format for the input image. If unset, the channel dimension format is inferred
292
+ from the input image. Can be one of:
293
+ - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
294
+ - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
295
+ - `"none"` or `ChannelDimension.NONE`: image in (height, width) format.
296
+ output_data_format (`ChannelDimension` or `str`, *optional*, defaults to `ChannelDimension.FIRST`):
297
+ The channel dimension format for the output image. Can be one of:
298
+ - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
299
+ - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
300
+ - Unset: Use the channel dimension format of the input image.
301
+ """
302
+ do_resize = do_resize if do_resize is not None else self.do_resize
303
+ resample = resample if resample is not None else self.resample
304
+ do_rescale = do_rescale if do_rescale is not None else self.do_rescale
305
+ rescale_factor = rescale_factor if rescale_factor is not None else self.rescale_factor
306
+ do_normalize = do_normalize if do_normalize is not None else self.do_normalize
307
+ image_mean = image_mean if image_mean is not None else self.image_mean
308
+ image_std = image_std if image_std is not None else self.image_std
309
+ do_convert_rgb = do_convert_rgb if do_convert_rgb is not None else self.do_convert_rgb
310
+ spatial_factor = spatial_factor if spatial_factor is not None else self.spatial_factor
311
+
312
+ images = make_list_of_images(images)
313
+ if images is None or not valid_images(images):
314
+ raise ValueError(
315
+ "Invalid image type. Must be of type PIL.Image.Image, numpy.ndarray, "
316
+ "torch.Tensor, tf.Tensor or jax.ndarray."
317
+ )
318
+
319
+ validate_preprocess_arguments(
320
+ rescale_factor=rescale_factor,
321
+ do_normalize=do_normalize,
322
+ image_mean=image_mean,
323
+ image_std=image_std,
324
+ do_resize=do_resize,
325
+ size=self.size,
326
+ resample=resample,
327
+ )
328
+
329
+ pixel_values = []
330
+ for image in images:
331
+ norm_image = self._preprocess(
332
+ image,
333
+ do_resize=do_resize,
334
+ resample=resample,
335
+ do_rescale=do_rescale,
336
+ rescale_factor=rescale_factor,
337
+ do_normalize=do_normalize,
338
+ image_mean=image_mean,
339
+ image_std=image_std,
340
+ do_convert_rgb=do_convert_rgb,
341
+ spatial_factor=spatial_factor,
342
+ input_data_format=input_data_format,
343
+ output_data_format=output_data_format,
344
+ )
345
+ pixel_values.extend(norm_image)
346
+ pixel_values = np.array(pixel_values)
347
+ data = {"pixel_values": pixel_values}
348
+
349
+ return BatchFeature(data=data, tensor_type=return_tensors)
350
+
351
+ def postprocess(
352
+ self,
353
+ images: ImageInput,
354
+ do_rescale: Optional[bool] = None,
355
+ rescale_factor: Optional[float] = None,
356
+ do_normalize: Optional[bool] = None,
357
+ image_mean: Optional[Union[float, List[float]]] = None,
358
+ image_std: Optional[Union[float, List[float]]] = None,
359
+ return_tensors: str | TensorType = "PIL.Image.Image",
360
+ input_data_format: Optional[Union[str, ChannelDimension]] = None,
361
+ ):
362
+ """
363
+ Postprocess an image or batch of images tensor. Postprocess is the reverse process of preprocess.
364
+ The parameters should be same as in preprocess.
365
+
366
+ Args:
367
+ images (`ImageInput`):
368
+ Image to postprocess. Expects a single or batch of images with pixel values ranging from -1 to 1.
369
+ do_rescale (`bool`, *optional*, defaults to `self.do_rescale`):
370
+ Whether to rescale the image.
371
+ rescale_factor (`float`, *optional*, defaults to `self.rescale_factor`):
372
+ Rescale factor to rescale the image by if `do_rescale` is set to `True`.
373
+ do_normalize (`bool`, *optional*, defaults to `self.do_normalize`):
374
+ Whether to normalize the image.
375
+ image_mean (`float` or `List[float]`, *optional*, defaults to `self.image_mean`):
376
+ Image mean to use for normalization. Only has an effect if `do_normalize` is set to `True`.
377
+ image_std (`float` or `List[float]`, *optional*, defaults to `self.image_std`):
378
+ Image standard deviation to use for normalization. Only has an effect if `do_normalize` is set to `True`.
379
+ return_tensors (`str` or `TensorType`, *optional*):
380
+ The type of tensors to return. Can be one of:
381
+ - Unset: Return a list of `np.ndarray`.
382
+ - `TensorType.PYTORCH` or `'pt'`: Return a batch of type `torch.Tensor`.
383
+ - `TensorType.NUMPY` or `'np'`: Return a batch of type `np.ndarray`.
384
+ input_data_format (`ChannelDimension` or `str`, *optional*):
385
+ The channel dimension format for the input image. If unset, the channel dimension format is inferred
386
+ from the input image. Can be one of:
387
+ - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.
388
+ - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.
389
+ - `"none"` or `ChannelDimension.NONE`: image in (height, width) format.
390
+ """
391
+ do_rescale = do_rescale if do_rescale is not None else self.do_rescale
392
+ rescale_factor = rescale_factor if rescale_factor is not None else self.rescale_factor
393
+ rescale_factor = 1 / rescale_factor
394
+
395
+ do_normalize = do_normalize if do_normalize is not None else self.do_normalize
396
+ image_mean = image_mean if image_mean is not None else self.image_mean
397
+ image_std = image_std if image_std is not None else self.image_std
398
+ image_mean, image_std = self.inverse_meanstd(image_mean, image_std)
399
+
400
+ images = make_list_of_images(images)
401
+ if isinstance(images[0], Image.Image):
402
+ return images if len(images) > 1 else images[0]
403
+
404
+ if input_data_format is None:
405
+ # We assume that all images have the same channel dimension format.
406
+ input_data_format = infer_channel_dimension_format(images[0])
407
+
408
+ pixel_values = []
409
+ for image in images:
410
+ image = to_numpy_array(image)
411
+ if do_normalize:
412
+ image = self.normalize(image=image, mean=image_mean, std=image_std, input_data_format=input_data_format)
413
+
414
+ if do_rescale:
415
+ image = self.rescale(image, scale=rescale_factor, input_data_format=input_data_format)
416
+ image = image.clip(0, 255).astype(np.uint8)
417
+
418
+ if do_normalize and do_rescale and return_tensors == "PIL.Image.Image":
419
+ image = to_channel_dimension_format(image, ChannelDimension.LAST, input_channel_dim=input_data_format)
420
+ pixel_values.append(Image.fromarray(image))
421
+ else:
422
+ pixel_values.extend(image)
423
+
424
+ data = {"pixel_values": pixel_values}
425
+ return_tensors = return_tensors if return_tensors != "PIL.Image.Image" else None
426
+
427
+ return BatchFeature(data=data, tensor_type=return_tensors)
428
+
429
+ def inverse_meanstd(self, image_mean, image_std):
430
+ image_mean = self.to_tuple(image_mean)
431
+ image_std = self.to_tuple(image_std)
432
+
433
+ rev_image_mean = tuple(-m / s for m, s in zip(image_mean, image_std))
434
+ rev_image_std = tuple(1 / s for s in image_std)
435
+
436
+ return rev_image_mean, rev_image_std
437
+
438
+ def to_tuple(self, value, dim=3):
439
+ if isinstance(value, int | float):
440
+ return (value,) * dim
441
+
442
+ return tuple(value)
codecs/vision/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89536431c69b08b10b449ec309f52dcea22f14b7647317f30f5715273392bbf1
3
+ size 1083015124
codecs/vision/modeling_emu3visionvq.py ADDED
@@ -0,0 +1,822 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2024 The Emu team, BAAI and The HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """ Emu3VisionVQ model """
16
+
17
+ import math
18
+ from typing import Optional, Tuple, Union
19
+
20
+ import torch
21
+ from torch import nn
22
+ from torch.nn import functional as F
23
+ from transformers.modeling_utils import PreTrainedModel
24
+
25
+ from .configuration_emu3visionvq import Emu3VisionVQConfig
26
+
27
+
28
+ class Emu3VisionVQActivation(nn.Module):
29
+
30
+ def __init__(self):
31
+ super().__init__()
32
+
33
+ def __call__(self, x: torch.Tensor):
34
+ return x * torch.sigmoid(x)
35
+
36
+
37
+ class Emu3VisionVQUpsample(nn.Module):
38
+
39
+ def __init__(self, in_channels: int):
40
+ super().__init__()
41
+ self.conv = nn.Conv2d(
42
+ in_channels,
43
+ in_channels,
44
+ kernel_size=3,
45
+ stride=1,
46
+ padding=1,
47
+ )
48
+
49
+ def forward(self, x: torch.Tensor):
50
+ x = F.interpolate(x, scale_factor=2.0, mode="nearest")
51
+ x = self.conv(x)
52
+ return x
53
+
54
+
55
+ class Emu3VisionVQDownsample(nn.Module):
56
+
57
+ def __init__(self, in_channels: int):
58
+ super().__init__()
59
+ self.conv = nn.Conv2d(
60
+ in_channels,
61
+ in_channels,
62
+ kernel_size=3,
63
+ stride=2,
64
+ padding=0,
65
+ )
66
+
67
+ def forward(self, x: torch.Tensor):
68
+ pad = (0, 1, 0, 1)
69
+ x = F.pad(x, pad, mode="constant", value=0)
70
+ x = self.conv(x)
71
+ return x
72
+
73
+
74
+ class Emu3VisionVQCausalConv3d(nn.Module):
75
+
76
+ def __init__(
77
+ self,
78
+ in_channel: int,
79
+ out_channel: int,
80
+ kernel_size: Union[int, Tuple[int, ...]] = (3, 1, 1),
81
+ stride: Union[int, Tuple[int, ...]] = (1, 1, 1),
82
+ ):
83
+ super().__init__()
84
+
85
+ if isinstance(kernel_size, int):
86
+ kernel_size = (kernel_size,) * 3
87
+ if isinstance(stride, int):
88
+ stride = (stride,) * 3
89
+
90
+ hw_pad = [k - s for k, s in zip(kernel_size[1:], stride[1:])]
91
+ self.padding = tuple()
92
+ for p in hw_pad[::-1]:
93
+ self.padding += (p // 2 + p % 2, p // 2)
94
+ self.padding += (2, 0)
95
+
96
+ self.conv = nn.Conv3d(
97
+ in_channel,
98
+ out_channel,
99
+ kernel_size,
100
+ stride=stride,
101
+ )
102
+
103
+ def forward(self, x: torch.Tensor):
104
+ x = F.pad(x, self.padding)
105
+ x = self.conv(x)
106
+ return x
107
+
108
+
109
+ class Emu3VisionVQResnetTemporalBlock(nn.Module):
110
+
111
+ def __init__(
112
+ self,
113
+ in_channels: int,
114
+ out_channels: Optional[int] = None,
115
+ conv_shortcut: bool = False,
116
+ dropout: float = 0.0,
117
+ ):
118
+ super().__init__()
119
+ self.in_channels = in_channels
120
+ out_channels = in_channels if out_channels is None else out_channels
121
+ self.out_channels = out_channels
122
+ self.use_conv_shortcut = conv_shortcut
123
+
124
+ stride = (1, 1, 1)
125
+ kernel_size = (3, 3, 3)
126
+
127
+ self.norm1 = nn.BatchNorm3d(in_channels)
128
+ self.conv1 = Emu3VisionVQCausalConv3d(
129
+ in_channels,
130
+ out_channels,
131
+ kernel_size=kernel_size,
132
+ stride=stride,
133
+ )
134
+ self.norm2 = nn.BatchNorm3d(out_channels)
135
+ self.dropout = nn.Dropout(dropout)
136
+ self.conv2 = Emu3VisionVQCausalConv3d(
137
+ out_channels,
138
+ out_channels,
139
+ kernel_size=kernel_size,
140
+ stride=stride,
141
+ )
142
+ self.act = Emu3VisionVQActivation()
143
+
144
+ if self.in_channels != self.out_channels:
145
+ if self.use_conv_shortcut:
146
+ self.conv_shortcut = Emu3VisionVQCausalConv3d(
147
+ in_channels,
148
+ out_channels,
149
+ kernel_size=kernel_size,
150
+ stride=stride,
151
+ )
152
+ else:
153
+ self.nin_shortcut = nn.Conv3d(
154
+ in_channels,
155
+ out_channels,
156
+ kernel_size=1,
157
+ stride=1,
158
+ padding=0,
159
+ )
160
+
161
+ def forward(self, x: torch.Tensor):
162
+ h = self.norm1(x)
163
+ h = self.act(h)
164
+ h = self.conv1(h)
165
+
166
+ h = self.norm2(h)
167
+ h = self.act(h)
168
+ h = self.dropout(h)
169
+ h = self.conv2(h)
170
+
171
+ if self.in_channels != self.out_channels:
172
+ if self.use_conv_shortcut:
173
+ x = self.conv_shortcut(x)
174
+ else:
175
+ x = self.nin_shortcut(x)
176
+
177
+ return x + h
178
+
179
+
180
+ class Emu3VisionVQSpatialNorm(nn.Module):
181
+
182
+ def __init__(
183
+ self,
184
+ f_channels: int,
185
+ zq_channels: int,
186
+ norm_layer: nn.Module = nn.GroupNorm,
187
+ add_conv: bool = False,
188
+ num_groups: int = 32,
189
+ eps: float = 1e-6,
190
+ affine: bool = True,
191
+ ):
192
+ super().__init__()
193
+ self.norm_layer = norm_layer(
194
+ num_channels=f_channels,
195
+ num_groups=num_groups,
196
+ eps=eps,
197
+ affine=affine,
198
+ )
199
+
200
+ self.add_conv = add_conv
201
+ if self.add_conv:
202
+ self.conv = nn.Conv2d(
203
+ zq_channels,
204
+ zq_channels,
205
+ kernel_size=3,
206
+ stride=1,
207
+ padding=1,
208
+ )
209
+
210
+ self.conv_y = nn.Conv2d(
211
+ zq_channels,
212
+ f_channels,
213
+ kernel_size=1,
214
+ stride=1,
215
+ padding=0,
216
+ )
217
+ self.conv_b = nn.Conv2d(
218
+ zq_channels,
219
+ f_channels,
220
+ kernel_size=1,
221
+ stride=1,
222
+ padding=0,
223
+ )
224
+
225
+ def forward(self, x: torch.Tensor, zq: torch.Tensor):
226
+ zq = F.interpolate(zq, size=x.shape[-2:], mode="nearest")
227
+
228
+ if self.add_conv:
229
+ zq = self.conv(zq)
230
+
231
+ x = self.norm_layer(x)
232
+ x = x * self.conv_y(zq) + self.conv_b(zq)
233
+ return x
234
+
235
+
236
+ class Emu3VisionVQResnetBlock(nn.Module):
237
+
238
+ def __init__(
239
+ self,
240
+ in_channels: int,
241
+ out_channels: Optional[int] = None,
242
+ conv_shortcut: bool = False,
243
+ dropout: float = 0.0,
244
+ zq_ch: Optional[int] = None,
245
+ add_conv: bool = False,
246
+ ):
247
+ super().__init__()
248
+ self.in_channels = in_channels
249
+ out_channels = in_channels if out_channels is None else out_channels
250
+ self.out_channels = out_channels
251
+ self.use_conv_shortcut = conv_shortcut
252
+ self.zq_ch = zq_ch
253
+
254
+ if zq_ch is None:
255
+ norm_kwargs = dict(num_groups=32, eps=1e-6, affine=True)
256
+ self.norm1 = nn.GroupNorm(num_channels=in_channels, **norm_kwargs)
257
+ self.norm2 = nn.GroupNorm(num_channels=out_channels, **norm_kwargs)
258
+ else:
259
+ self.norm1 = Emu3VisionVQSpatialNorm(in_channels, zq_ch, add_conv=add_conv)
260
+ self.norm2 = Emu3VisionVQSpatialNorm(out_channels, zq_ch, add_conv=add_conv)
261
+
262
+ self.conv1 = nn.Conv2d(
263
+ in_channels,
264
+ out_channels,
265
+ kernel_size=3,
266
+ stride=1,
267
+ padding=1,
268
+ )
269
+
270
+ self.dropout = nn.Dropout(dropout)
271
+ self.conv2 = nn.Conv2d(
272
+ out_channels,
273
+ out_channels,
274
+ kernel_size=3,
275
+ stride=1,
276
+ padding=1,
277
+ )
278
+
279
+ self.act = Emu3VisionVQActivation()
280
+
281
+ if self.in_channels != self.out_channels:
282
+ if self.use_conv_shortcut:
283
+ self.conv_shortcut = nn.Conv2d(
284
+ in_channels,
285
+ out_channels,
286
+ kernel_size=3,
287
+ stride=1,
288
+ padding=1,
289
+ )
290
+ else:
291
+ self.nin_shortcut = nn.Conv2d(
292
+ in_channels,
293
+ out_channels,
294
+ kernel_size=1,
295
+ stride=1,
296
+ padding=0,
297
+ )
298
+
299
+ def forward(self, x: torch.Tensor, zq: Optional[torch.Tensor] = None):
300
+ norm_args = tuple() if self.zq_ch is None else (zq, )
301
+
302
+ h = self.norm1(x, *norm_args)
303
+ h = self.act(h)
304
+ h = self.conv1(h)
305
+
306
+ h = self.norm2(h, *norm_args)
307
+ h = self.act(h)
308
+ h = self.dropout(h)
309
+ h = self.conv2(h)
310
+
311
+ if self.in_channels != self.out_channels:
312
+ if self.use_conv_shortcut:
313
+ x = self.conv_shortcut(x)
314
+ else:
315
+ x = self.nin_shortcut(x)
316
+
317
+ return x + h
318
+
319
+
320
+ class Emu3VisionVQAttnBlock(nn.Module):
321
+
322
+ def __init__(
323
+ self,
324
+ in_channels: int,
325
+ zq_ch: Optional[int] = None,
326
+ add_conv: bool = False
327
+ ):
328
+ super().__init__()
329
+ self.in_channels = in_channels
330
+ self.zq_ch = zq_ch
331
+
332
+ if zq_ch is None:
333
+ norm_kwargs = dict(num_groups=32, eps=1e-6, affine=True)
334
+ self.norm = nn.GroupNorm(num_channels=in_channels, **norm_kwargs)
335
+ else:
336
+ self.norm = Emu3VisionVQSpatialNorm(in_channels, zq_ch, add_conv=add_conv)
337
+
338
+ self.q = nn.Conv2d(
339
+ in_channels,
340
+ in_channels,
341
+ kernel_size=1,
342
+ stride=1,
343
+ padding=0,
344
+ )
345
+ self.k = nn.Conv2d(
346
+ in_channels,
347
+ in_channels,
348
+ kernel_size=1,
349
+ stride=1,
350
+ padding=0,
351
+ )
352
+ self.v = nn.Conv2d(
353
+ in_channels,
354
+ in_channels,
355
+ kernel_size=1,
356
+ stride=1,
357
+ padding=0,
358
+ )
359
+ self.proj_out = nn.Conv2d(
360
+ in_channels,
361
+ in_channels,
362
+ kernel_size=1,
363
+ stride=1,
364
+ padding=0,
365
+ )
366
+
367
+ def forward(self, x: torch.Tensor, zq: Optional[torch.Tensor] = None):
368
+ norm_args = tuple() if self.zq_ch is None else (zq, )
369
+
370
+ nx = self.norm(x, *norm_args)
371
+ q = self.q(nx)
372
+ k = self.k(nx)
373
+ v = self.v(nx)
374
+
375
+ # compute attention
376
+ b, c, h, w = q.shape
377
+ q = q.reshape(b, c, h * w)
378
+ k = k.reshape(b, c, h * w)
379
+ score = torch.bmm(q.permute(0, 2, 1), k)
380
+ score = score / (c ** 0.5)
381
+ score = F.softmax(score, dim=2)
382
+
383
+ # attend to values
384
+ v = v.reshape(b, c, h * w)
385
+ v = torch.bmm(v, score.permute(0, 2, 1))
386
+ v = v.reshape(b, c, h, w)
387
+
388
+ v = self.proj_out(v)
389
+
390
+ return x + v
391
+
392
+
393
+ class Emu3VisionVQTemporalUpsample(nn.Module):
394
+
395
+ def __init__(
396
+ self,
397
+ in_channel: int,
398
+ out_channel: int,
399
+ kernel_size: Tuple[int, ...] = (3, 3, 3),
400
+ stride: Tuple[int, ...] = (1, 1, 1)
401
+ ):
402
+ super().__init__()
403
+ self.in_channel = in_channel
404
+ self.out_channel = out_channel
405
+ self.conv = Emu3VisionVQCausalConv3d(
406
+ in_channel,
407
+ out_channel,
408
+ kernel_size,
409
+ stride=stride,
410
+ )
411
+
412
+ def forward(self, x: torch.Tensor):
413
+ b, c, t, h, w = x.shape
414
+ x = x.permute(0, 1, 3, 4, 2).contiguous().view(b, -1, t)
415
+ x = F.interpolate(x, scale_factor=2.0, mode="nearest")
416
+ x = x.view(b, c, h, w, -1).permute(0, 1, 4, 2, 3).contiguous()
417
+ x = self.conv(x)
418
+ return x
419
+
420
+
421
+ class Emu3VisionVQTemporalDownsample(nn.Module):
422
+
423
+ def __init__(
424
+ self,
425
+ in_channel: int,
426
+ out_channel: int,
427
+ kernel_size: Tuple[int, ...] = (4, 3, 3),
428
+ stride: Tuple[int, ...] = (2, 1, 1),
429
+ ):
430
+ super().__init__()
431
+ self.in_channel = in_channel
432
+ self.out_channel = out_channel
433
+ self.kernel_size = kernel_size
434
+
435
+ self.conv = Emu3VisionVQCausalConv3d(
436
+ in_channel,
437
+ out_channel,
438
+ kernel_size=kernel_size,
439
+ stride=stride,
440
+ )
441
+
442
+ def forward(self, x: torch.Tensor):
443
+ x = self.conv(x)
444
+ return x
445
+
446
+
447
+ class Emu3VisionVQVectorQuantizer(nn.Module):
448
+
449
+ def __init__(self, config: Emu3VisionVQConfig):
450
+ super().__init__()
451
+ self.embedding = nn.Embedding(config.codebook_size, config.embed_dim)
452
+ self.embedding.weight.data.uniform_(-1.0 / config.codebook_size, 1.0 / config.codebook_size)
453
+
454
+ def forward(self, x: torch.Tensor):
455
+ # b t c h w -> b t h w c
456
+ b, t, c, h, w = x.shape
457
+ x = x.permute(0, 1, 3, 4, 2).contiguous()
458
+ x_flattened = x.view(-1, c)
459
+
460
+ codebook = self.embedding.weight
461
+
462
+ d = torch.sum(x_flattened ** 2, dim=1, keepdim=True) + \
463
+ torch.sum(codebook ** 2, dim=1) - 2 * \
464
+ torch.einsum('bd,dn->bn', x_flattened, codebook.permute(1, 0))
465
+
466
+ indices = torch.argmin(d, dim=1)
467
+ indices = indices.view(b, t, h, w)
468
+ return indices
469
+
470
+
471
+ class Emu3VisionVQEncoder(nn.Module):
472
+
473
+ def __init__(self, config: Emu3VisionVQConfig):
474
+ super().__init__()
475
+ self.ch = config.ch
476
+ self.num_resolutions = len(config.ch_mult)
477
+ self.num_res_blocks = config.num_res_blocks
478
+ self.in_channels = config.in_channels
479
+
480
+ # downsampling
481
+ self.conv_in = nn.Conv2d(
482
+ self.in_channels,
483
+ self.ch,
484
+ kernel_size=3,
485
+ stride=1,
486
+ padding=1
487
+ )
488
+
489
+ in_ch_mult = (1,) + tuple(config.ch_mult)
490
+ self.down = nn.ModuleList()
491
+ for i_level in range(self.num_resolutions):
492
+ block = nn.ModuleList()
493
+ attn = nn.ModuleList()
494
+ block_in = config.ch * in_ch_mult[i_level]
495
+ block_out = config.ch * config.ch_mult[i_level]
496
+ for i_block in range(self.num_res_blocks):
497
+ block.append(
498
+ Emu3VisionVQResnetBlock(
499
+ in_channels=block_in,
500
+ out_channels=block_out,
501
+ dropout=config.dropout,
502
+ )
503
+ )
504
+ block_in = block_out
505
+ if i_level in config.attn_resolutions:
506
+ attn.append(Emu3VisionVQAttnBlock(block_in))
507
+
508
+ down = nn.Module()
509
+ down.block = block
510
+ down.attn = attn
511
+ if i_level != self.num_resolutions - 1:
512
+ down.downsample = Emu3VisionVQDownsample(block_in)
513
+
514
+ self.down.append(down)
515
+
516
+ # middle
517
+ self.mid = nn.Module()
518
+ self.mid.block_1 = Emu3VisionVQResnetBlock(
519
+ in_channels=block_in,
520
+ out_channels=block_in,
521
+ dropout=config.dropout,
522
+ )
523
+ self.mid.attn_1 = Emu3VisionVQAttnBlock(block_in)
524
+ self.mid.block_2 = Emu3VisionVQResnetBlock(
525
+ in_channels=block_in,
526
+ out_channels=block_in,
527
+ dropout=config.dropout,
528
+ )
529
+
530
+ # end
531
+ self.norm_out = nn.GroupNorm(num_channels=block_in, num_groups=32, eps=1e-6, affine=True)
532
+
533
+ out_z_channels = 2 * config.z_channels if config.double_z else config.z_channels
534
+ self.conv_out = nn.Conv2d(
535
+ block_in,
536
+ out_z_channels,
537
+ kernel_size=3,
538
+ stride=1,
539
+ padding=1,
540
+ )
541
+
542
+ temporal_down_blocks = int(math.log2(config.temporal_downsample_factor))
543
+ self.time_conv = nn.ModuleList()
544
+
545
+ for i in range(temporal_down_blocks):
546
+ conv = Emu3VisionVQTemporalDownsample(out_z_channels, out_z_channels)
547
+ self.time_conv.append(conv)
548
+
549
+ self.time_res_stack = nn.Sequential(*[
550
+ Emu3VisionVQResnetTemporalBlock(
551
+ in_channels=out_z_channels,
552
+ out_channels=out_z_channels,
553
+ dropout=config.dropout,
554
+ ) for _ in range(self.num_res_blocks)
555
+ ])
556
+
557
+ self.act = Emu3VisionVQActivation()
558
+
559
+ def forward(self, x: torch.Tensor):
560
+ t = x.shape[1]
561
+ x = x.reshape(-1, *x.shape[2:])
562
+
563
+ # downsampling
564
+ h = self.conv_in(x)
565
+ for i_level in range(self.num_resolutions):
566
+ for i_block in range(self.num_res_blocks):
567
+ h = self.down[i_level].block[i_block](h)
568
+ if len(self.down[i_level].attn) > 0:
569
+ h = self.down[i_level].attn[i_block](h)
570
+
571
+ if i_level != self.num_resolutions - 1:
572
+ h = self.down[i_level].downsample(h)
573
+
574
+ h = self.mid.block_1(h)
575
+ h = self.mid.attn_1(h)
576
+ h = self.mid.block_2(h)
577
+
578
+ # end
579
+ h = self.norm_out(h)
580
+ h = self.act(h)
581
+
582
+ h = self.conv_out(h)
583
+
584
+ h = h.reshape(-1, t, *h.shape[1:])
585
+ h = h.permute(0, 2, 1, 3, 4)
586
+
587
+ for conv in self.time_conv:
588
+ h = self.act(conv(h))
589
+
590
+ h = self.time_res_stack(h)
591
+ h = h.permute(0, 2, 1, 3, 4)
592
+
593
+ return h
594
+
595
+
596
+ class Emu3VisionVQDecoder(nn.Module):
597
+
598
+ def __init__(self, config: Emu3VisionVQConfig):
599
+ super().__init__()
600
+ self.ch = config.ch
601
+ self.num_resolutions = len(config.ch_mult)
602
+ self.num_res_blocks = config.num_res_blocks
603
+
604
+ in_ch_mult = (1,) + tuple(config.ch_mult)
605
+ zq_ch = config.embed_dim
606
+
607
+ block_in = config.ch * config.ch_mult[-1]
608
+ self.time_res_stack = nn.Sequential(*[
609
+ Emu3VisionVQResnetTemporalBlock(
610
+ in_channels=config.z_channels,
611
+ out_channels=config.z_channels,
612
+ dropout=config.dropout,
613
+ ) for _ in range(config.num_res_blocks)
614
+ ])
615
+
616
+ tempo_upsample_block_num = int(math.log2(config.temporal_downsample_factor))
617
+ self.time_conv = nn.ModuleList()
618
+ for i in range(tempo_upsample_block_num):
619
+ conv = Emu3VisionVQTemporalUpsample(config.z_channels, config.z_channels)
620
+ self.time_conv.append(conv)
621
+
622
+ self.conv_in = nn.Conv2d(
623
+ config.z_channels,
624
+ block_in,
625
+ kernel_size=3,
626
+ stride=1,
627
+ padding=1,
628
+ )
629
+
630
+ # middle
631
+ self.mid = nn.Module()
632
+ self.mid.block_1 = Emu3VisionVQResnetBlock(
633
+ in_channels=block_in,
634
+ out_channels=block_in,
635
+ dropout=config.dropout,
636
+ zq_ch=zq_ch,
637
+ )
638
+ self.mid.attn_1 = Emu3VisionVQAttnBlock(block_in, zq_ch)
639
+ self.mid.block_2 = Emu3VisionVQResnetBlock(
640
+ in_channels=block_in,
641
+ out_channels=block_in,
642
+ dropout=config.dropout,
643
+ zq_ch=zq_ch,
644
+ )
645
+
646
+ # upsampling
647
+ self.up = nn.ModuleList()
648
+ for i_level in reversed(range(self.num_resolutions)):
649
+ block = nn.ModuleList()
650
+ attn = nn.ModuleList()
651
+ block_out = config.ch * config.ch_mult[i_level]
652
+ for i_block in range(self.num_res_blocks + 1):
653
+ block.append(
654
+ Emu3VisionVQResnetBlock(
655
+ in_channels=block_in,
656
+ out_channels=block_out,
657
+ dropout=config.dropout,
658
+ zq_ch=zq_ch,
659
+ )
660
+ )
661
+ block_in = block_out
662
+ if i_level in config.attn_resolutions:
663
+ attn.append(Emu3VisionVQAttnBlock(block_in, zq_ch))
664
+
665
+ up = nn.Module()
666
+ up.block = block
667
+ up.attn = attn
668
+ if i_level != 0:
669
+ up.upsample = Emu3VisionVQUpsample(block_in)
670
+
671
+ self.up.insert(0, up)
672
+
673
+ self.act = Emu3VisionVQActivation()
674
+
675
+ self.norm_out = Emu3VisionVQSpatialNorm(block_in, zq_ch)
676
+ self.conv_out = nn.Conv2d(
677
+ block_in,
678
+ config.out_channels,
679
+ kernel_size=3,
680
+ stride=1,
681
+ padding=1,
682
+ )
683
+
684
+ def forward(self, z: torch.Tensor, zq: torch.Tensor):
685
+ z_zq = torch.cat((z, zq), dim=0)
686
+ z_zq = z_zq.permute(0, 2, 1, 3, 4)
687
+ z_zq = self.time_res_stack(z_zq)
688
+
689
+ for conv in self.time_conv:
690
+ z_zq = self.act(conv(z_zq))
691
+
692
+ z_zq = z_zq.permute(0, 2, 1, 3, 4)
693
+
694
+ h, zq = torch.chunk(z_zq, 2, dim=0)
695
+
696
+ h = h.reshape(-1, *h.shape[2:])
697
+ zq = zq.reshape(-1, *zq.shape[2:])
698
+
699
+ h = self.conv_in(h)
700
+
701
+ # middle
702
+ h = self.mid.block_1(h, zq)
703
+ h = self.mid.attn_1(h, zq)
704
+ h = self.mid.block_2(h, zq)
705
+
706
+ # upsampling
707
+ for i_level in reversed(range(self.num_resolutions)):
708
+ for i_block in range(self.num_res_blocks+1):
709
+ h = self.up[i_level].block[i_block](h, zq)
710
+ if len(self.up[i_level].attn) > 0:
711
+ h = self.up[i_level].attn[i_block](h, zq)
712
+
713
+ if i_level != 0:
714
+ h = self.up[i_level].upsample(h)
715
+
716
+ h = self.norm_out(h, zq)
717
+ h = self.act(h)
718
+ h = self.conv_out(h)
719
+
720
+ return h
721
+
722
+
723
+ class Emu3VisionVQPretrainedModel(PreTrainedModel):
724
+ """
725
+ An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
726
+ models.
727
+ """
728
+
729
+ config_class = Emu3VisionVQConfig
730
+ base_model_prefix = "emuvideovq"
731
+ main_input_name = "pixel_values"
732
+ _no_split_modules = ["Emu3VisionVQResnetBlock", "Emu3VisionVQAttnBlock", "Emu3VisionVQResnetTemporalBlock"]
733
+
734
+ def _init_weights(self, module):
735
+ if isinstance(module, (nn.Conv2d, nn.Conv3d)):
736
+ nn.init.kaiming_normal_(module.weight, mode="fan_out", nonlinearity="relu")
737
+ # copied from the `reset_parameters` method of `class Linear(Module)` in `torch`.
738
+ elif isinstance(module, nn.Linear):
739
+ nn.init.kaiming_uniform_(module.weight, a=math.sqrt(5))
740
+ if module.bias is not None:
741
+ fan_in, _ = nn.init._calculate_fan_in_and_fan_out(module.weight)
742
+ bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
743
+ nn.init.uniform_(module.bias, -bound, bound)
744
+ elif isinstance(module, (nn.BatchNorm2d, nn.BatchNorm3d, nn.GroupNorm)):
745
+ nn.init.constant_(module.weight, 1)
746
+ nn.init.constant_(module.bias, 0)
747
+
748
+
749
+ class Emu3VisionVQModel(Emu3VisionVQPretrainedModel):
750
+
751
+ def __init__(self, config):
752
+ super().__init__(config)
753
+ self.config = config
754
+
755
+ self.encoder = Emu3VisionVQEncoder(config)
756
+ self.decoder = Emu3VisionVQDecoder(config)
757
+ self.quantize = Emu3VisionVQVectorQuantizer(config)
758
+
759
+ self.quant_conv = Emu3VisionVQCausalConv3d(config.z_channels, config.embed_dim)
760
+ self.post_quant_conv = Emu3VisionVQCausalConv3d(config.embed_dim, config.z_channels)
761
+
762
+ self.spatial_scale_factor = 2 ** (len(config.ch_mult) - 1)
763
+
764
+ self.post_init()
765
+
766
+ def encode(self, x: torch.Tensor):
767
+ ndim = x.ndim
768
+ if ndim == 4:
769
+ t = self.config.temporal_downsample_factor
770
+ b, c, h, w = x.shape
771
+ x = x.unsqueeze(1).repeat(1, t, 1, 1, 1)
772
+ elif ndim == 5:
773
+ b, t, c, h, w = x.shape
774
+
775
+ h = self.encoder(x)
776
+
777
+ # b t c h w -> b c t h w
778
+ h = h.permute(0, 2, 1, 3, 4)
779
+ h = self.quant_conv(h)
780
+ # b c t h w -> b t c h w
781
+ h = h.permute(0, 2, 1, 3, 4)
782
+
783
+ codes = self.quantize(h)
784
+
785
+ if ndim == 4:
786
+ codes = codes.squeeze(1)
787
+
788
+ return codes
789
+
790
+ def decode(self, x: torch.Tensor):
791
+ ndim = x.ndim
792
+ if ndim == 3:
793
+ x = x.unsqueeze(1)
794
+
795
+ b, t, h, w = x.shape
796
+ quant = self.quantize.embedding(x.flatten())
797
+ c = quant.shape[-1]
798
+ quant = quant.view(b, t, h, w, c).permute(0, 4, 1, 2, 3).contiguous()
799
+ quant2 = self.post_quant_conv(quant)
800
+
801
+ quant = quant.permute(0, 2, 1, 3, 4)
802
+ quant2 = quant2.permute(0, 2, 1, 3, 4)
803
+
804
+ video = self.decoder(quant2, quant)
805
+ video = video.reshape(
806
+ b,
807
+ t * self.config.temporal_downsample_factor,
808
+ self.config.out_channels,
809
+ h * self.spatial_scale_factor,
810
+ w * self.spatial_scale_factor,
811
+ )
812
+ if ndim == 3:
813
+ return video[:, 0]
814
+ return video
815
+
816
+ @property
817
+ def device(self):
818
+ return next(self.parameters()).device
819
+
820
+ @property
821
+ def dtype(self):
822
+ return next(self.parameters()).dtype
codecs/vision/preprocessor_config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "auto_map": {
3
+ "AutoImageProcessor": "image_processing_emu3visionvq.Emu3VisionVQImageProcessor"
4
+ },
5
+ "do_convert_rgb": true,
6
+ "do_normalize": true,
7
+ "do_rescale": true,
8
+ "do_resize": true,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_processor_type": "Emu3VisionVQImageProcessor",
15
+ "image_std": [
16
+ 0.5,
17
+ 0.5,
18
+ 0.5
19
+ ],
20
+ "max_pixels": 1048576,
21
+ "min_pixels": 262144,
22
+ "resample": 3,
23
+ "rescale_factor": 0.00392156862745098,
24
+ "size": {
25
+ "max_pixels": 1048576,
26
+ "min_pixels": 262144
27
+ },
28
+ "spatial_factor": 8
29
+ }
config.json ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": {
3
+ "architecture": "axiom1_1",
4
+ "vocab_size": 54144,
5
+ "d_model": 640,
6
+ "n_layers": 12,
7
+ "n_heads": 10,
8
+ "n_kv_heads": 10,
9
+ "ffn_mult": 2.6666666666666665,
10
+ "max_seq_len": 1024,
11
+ "rope_theta": 10000.0,
12
+ "norm_eps": 1e-06,
13
+ "vis_positions": 256,
14
+ "visual_offset": 16640,
15
+ "visual_size": 32768,
16
+ "tie_embeddings": true,
17
+ "init_std": 0.02,
18
+ "grad_checkpoint": true,
19
+ "attention_layers": [
20
+ 3,
21
+ 7,
22
+ 11
23
+ ],
24
+ "d_cell": 1280,
25
+ "n_cells": 2,
26
+ "top_k": 2,
27
+ "scan_chunk": 64,
28
+ "balance_weight": 0.01
29
+ },
30
+ "vocab": {
31
+ "text_size": 16384,
32
+ "visual_size": 32768,
33
+ "audio_size": 1024,
34
+ "audio_books": 4,
35
+ "coordinate_bins": 512,
36
+ "max_vertices": 128,
37
+ "max_faces": 128,
38
+ "frame_tokens": 64,
39
+ "max_frames": 8,
40
+ "max_audio_frames": 160,
41
+ "schema_version": 3,
42
+ "special_tokens": [
43
+ "pad",
44
+ "bos",
45
+ "eos",
46
+ "input",
47
+ "output",
48
+ "text",
49
+ "/text",
50
+ "image",
51
+ "/image",
52
+ "video",
53
+ "/video",
54
+ "frame",
55
+ "/frame",
56
+ "audio",
57
+ "/audio",
58
+ "mesh",
59
+ "/mesh",
60
+ "vertices",
61
+ "faces",
62
+ "tools",
63
+ "/tools",
64
+ "calls",
65
+ "/calls",
66
+ "task:text",
67
+ "task:chat_text",
68
+ "task:t2i",
69
+ "task:i2t",
70
+ "task:t2v",
71
+ "task:v2t",
72
+ "task:vcont",
73
+ "task:t2a",
74
+ "task:a2t",
75
+ "task:t2m",
76
+ "task:m2t",
77
+ "task:tools",
78
+ "task:mixed",
79
+ "task:coding",
80
+ "time:0",
81
+ "time:1",
82
+ "time:2",
83
+ "time:3",
84
+ "time:4",
85
+ "time:5",
86
+ "time:6",
87
+ "time:7",
88
+ "resolution:128"
89
+ ]
90
+ },
91
+ "meta": {
92
+ "stage": "joint_repair",
93
+ "step": 512,
94
+ "tokens_seen": 807174144,
95
+ "task_target_tokens": {
96
+ "coding": 373488,
97
+ "chat_text": 24092354,
98
+ "text": 257928814,
99
+ "i2t": 11852250,
100
+ "t2i": 39056667,
101
+ "vcont": 6105359,
102
+ "v2t": 252329,
103
+ "t2v": 19027778,
104
+ "t2m": 5739237,
105
+ "a2t": 207450,
106
+ "tools": 1430210,
107
+ "m2t": 477100,
108
+ "t2a": 10952145,
109
+ "replay/chat_text": 66428,
110
+ "replay/text": 225433,
111
+ "coverage/vcont": 52303,
112
+ "coverage/t2v": 384846
113
+ },
114
+ "stage_task_target_tokens": {
115
+ "coverage/vcont": 52303,
116
+ "t2i": 481000,
117
+ "coverage/t2v": 384846,
118
+ "replay/chat_text": 23241,
119
+ "replay/text": 74112
120
+ },
121
+ "stages_completed": [
122
+ "pilot",
123
+ "text",
124
+ "image_pilot",
125
+ "image",
126
+ "video",
127
+ "audio_mesh_pilot",
128
+ "audio_mesh",
129
+ "unified",
130
+ "pilot",
131
+ "image",
132
+ "video_coverage",
133
+ "joint_repair"
134
+ ],
135
+ "num_params": 90247794,
136
+ "precision": "bf16",
137
+ "image_128_trained": true,
138
+ "image_128_target_tokens": 16775415,
139
+ "env": {
140
+ "torch": "2.11.0+cu128",
141
+ "cuda": "12.8",
142
+ "python": "3.13.15",
143
+ "gpu": "NVIDIA GeForce RTX 5070"
144
+ },
145
+ "research_only": true,
146
+ "research_trial": "joint_repair",
147
+ "research_note": "Experimental branch; not a production promotion or validated semantic capability.",
148
+ "data": {
149
+ "preparation": {
150
+ "audio_codec": {
151
+ "path": "codecs/audio",
152
+ "revision": "6ba020b5ba7d9d8076fb90db7e67f27e31980f6e",
153
+ "sha256": {
154
+ "config.json": "3af86549c509e5491367dab3f7093014bd559f441ba31fa7427b5f94232368d7",
155
+ "model.safetensors": "7452e3fc6972991da871ae1a1c9d3e8e219aa247cc0f757018867d6a7fe59aaf",
156
+ "README.md": "6b1db50f4974b1544fb795491b6fd57a20320ff17686565c50a08407f06075b1"
157
+ },
158
+ "codebooks": 4,
159
+ "boundary_guard_samples": 320,
160
+ "alignment_samples": -8,
161
+ "admission_sha256": "7968bc0f22c01b433bd516acc4562ed9ef7543a6e93b8c7c7d7d697a7157bf62",
162
+ "implementation": "quasar.tokenizers.audio_aligned.AlignedAudioTokenizer"
163
+ }
164
+ }
165
+ },
166
+ "release_provenance": {
167
+ "source_weights_sha256": "de4846e5e63c0004cf0b6331127ab421d6843f6efde04243c6d5448ab4545240",
168
+ "original_config_sha256": "1d7254fcaaca82c66d2eab32ba39cabf673c9c63ed16aabede5d6b1092aeabb1",
169
+ "runtime_source_commit": "4afbdccdbd3b0967b161a68658cfd3b92c42636f",
170
+ "metadata_changes": "Portable codec paths; runtime-required metadata retained; workspace paths and preparation-only metadata omitted.",
171
+ "weights_changed": false
172
+ }
173
+ }
174
+ }
files.sha256.json ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "config.json": {
3
+ "bytes": 4547,
4
+ "sha256": "0a5a60a1af2eb406e8195beed8fb8602aade8252dd3c5ef5bb9c8d4c7a20083c"
5
+ },
6
+ "evaluation.json": {
7
+ "bytes": 3456,
8
+ "sha256": "70681a6e629a7731f74d3dfa8c0b5692ba61a7cfb520ba307119d5dd20237bd6"
9
+ },
10
+ "infer.py": {
11
+ "bytes": 2730,
12
+ "sha256": "cdf04813be3d8505cefbaeeee604ff53d022e09fbec7fc321af3544d295b95e6"
13
+ },
14
+ "model.safetensors": {
15
+ "bytes": 361008568,
16
+ "sha256": "de4846e5e63c0004cf0b6331127ab421d6843f6efde04243c6d5448ab4545240"
17
+ },
18
+ "pyproject.toml": {
19
+ "bytes": 784,
20
+ "sha256": "320d43b1c10d23a898b64a6fd66253c78b3bbb7a74fd2c4d0f60b40fe3d9943a"
21
+ },
22
+ "README.md": {
23
+ "bytes": 16894,
24
+ "sha256": "ed5b08fa100594fc8c795a6b8188d99d0563ca736ddcc2cb071c67c909a266c2"
25
+ },
26
+ "release.json": {
27
+ "bytes": 783,
28
+ "sha256": "2734d29b5d10e09c5de8c6f2cbcc27f205ae1bedc8d46638030262b5aae84896"
29
+ },
30
+ "release_validation.json": {
31
+ "bytes": 660,
32
+ "sha256": "ffeb7f7738e459e1c9132d284072d39d367b04c751079f7d67d4746ca34c005e"
33
+ },
34
+ "requirements-inference.txt": {
35
+ "bytes": 256,
36
+ "sha256": "4d38e10bd30904cec18c0025edbe57299ff8034243ed9b670e7ace7657e97b8d"
37
+ },
38
+ "verify_release.py": {
39
+ "bytes": 4126,
40
+ "sha256": "af41074f5c74ff4090be59bb1d656f5238bd1d31d2c1b2d4612990f1eef7285a"
41
+ },
42
+ "assets/attributions.json": {
43
+ "bytes": 2391,
44
+ "sha256": "afd033dfe7cc8822899d9946ebec9bb872314127e4ec864e99a03b3fed5e62e3"
45
+ },
46
+ "assets/image_comparison.png": {
47
+ "bytes": 210993,
48
+ "sha256": "49f61b6696e34dc3589c3cf24aa4da55a5475a369bfbf5ccdefb602a0b41a216"
49
+ },
50
+ "assets/motion_comparison.gif": {
51
+ "bytes": 281900,
52
+ "sha256": "9184db2e2abe62c33c57de75d08d5c8bf6273600dfadc80842c5ecd5fcfce436"
53
+ },
54
+ "quasar/__init__.py": {
55
+ "bytes": 0,
56
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
57
+ },
58
+ "tokenizers/text_tokenizer.json": {
59
+ "bytes": 1136009,
60
+ "sha256": "bf620532636ec536d19191eddde091645856e1d6f262d52fdc9f7e9b43ee843a"
61
+ },
62
+ "variants/v5/config.json": {
63
+ "bytes": 4351,
64
+ "sha256": "5e10caa317356974650e2fa9ecc5bc39db73237ed08a0bcbf7f7a61fedc66c63"
65
+ },
66
+ "variants/v5/model.safetensors": {
67
+ "bytes": 361008568,
68
+ "sha256": "e20242e9971b6a7cef7ca23a65c25faa70a007807a55a2c0cfe35f4f5682a643"
69
+ },
70
+ "variants/v5/tokenizers/text_tokenizer.json": {
71
+ "bytes": 1136009,
72
+ "sha256": "bf620532636ec536d19191eddde091645856e1d6f262d52fdc9f7e9b43ee843a"
73
+ },
74
+ "variants/v5/tokenizers/vq/vq.safetensors": {
75
+ "bytes": 524448,
76
+ "sha256": "0ed59aaface626b762214262572da4d9b0eb841175859fe26c925e606a82069b"
77
+ },
78
+ "variants/v5/tokenizers/vq/vq_config.json": {
79
+ "bytes": 2186,
80
+ "sha256": "4eae4e47af92536dacc30559498eed3c1318a80fbf603ab6a3a5da6e500e8448"
81
+ },
82
+ "tokenizers/vq/vq.safetensors": {
83
+ "bytes": 524448,
84
+ "sha256": "0ed59aaface626b762214262572da4d9b0eb841175859fe26c925e606a82069b"
85
+ },
86
+ "tokenizers/vq/vq_config.json": {
87
+ "bytes": 2180,
88
+ "sha256": "a720078321fd0c30cdedc241ca47c05474d5e91945bb4f1feb100452dffe03de"
89
+ },
90
+ "quasar/data/adapters.py": {
91
+ "bytes": 5338,
92
+ "sha256": "93dcfca754d0d5d58fefef7c23eb49f91538ad822231422ede0ad2aafe36a1ab"
93
+ },
94
+ "quasar/data/collator.py": {
95
+ "bytes": 1444,
96
+ "sha256": "dbc6b411df217460ef6578394d4ca3f1a4f6e8be672cdc71771eaf28b1a4a675"
97
+ },
98
+ "quasar/data/curated.py": {
99
+ "bytes": 11234,
100
+ "sha256": "036bcb4f95a90c3bc338deae2491fc5c416acd3454a862494347712e08c7737e"
101
+ },
102
+ "quasar/data/datasets.py": {
103
+ "bytes": 13870,
104
+ "sha256": "2a0fccbb576c69351e12e9a60f1c74348cce385e6d06a1786ec87f8bb82f3785"
105
+ },
106
+ "quasar/data/download.py": {
107
+ "bytes": 9387,
108
+ "sha256": "e98607b44e557c6b9a86ed1d06d487ac810ca2f7458c29f08abbb6fd5a15f19f"
109
+ },
110
+ "quasar/data/preprocessing.py": {
111
+ "bytes": 11794,
112
+ "sha256": "5a802f8fc5da21699136ffd9941e556474200a3f5038667a1e90293b3af0de25"
113
+ },
114
+ "quasar/data/synthetic.py": {
115
+ "bytes": 18794,
116
+ "sha256": "4f6046fa0fa8fb426820c8970628ff723d84c6b9539cbf1e02f3bc80b8f3bd05"
117
+ },
118
+ "quasar/data/text_preparation.py": {
119
+ "bytes": 29675,
120
+ "sha256": "777fb661fa86ef7602e0fca0a707efbc3e1eaa168de3fbda2177506462051fac"
121
+ },
122
+ "quasar/data/__init__.py": {
123
+ "bytes": 0,
124
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
125
+ },
126
+ "quasar/evaluation/__init__.py": {
127
+ "bytes": 0,
128
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
129
+ },
130
+ "quasar/inference/decoding.py": {
131
+ "bytes": 7170,
132
+ "sha256": "936f834f079dfb2763091897095d4cc65a5e6b6ff16fcbe844e60cdf95bd4152"
133
+ },
134
+ "quasar/inference/extended.py": {
135
+ "bytes": 8322,
136
+ "sha256": "5ce1f4bd1023f3fb77b1853e2f36d5410ae0595349bf5f9d03aae99d52f1ebab"
137
+ },
138
+ "quasar/inference/generate.py": {
139
+ "bytes": 16454,
140
+ "sha256": "00a31f84a6917d58d3ebbc09bb04e796b98aa8fc8aef8e32d278652e5aa24e24"
141
+ },
142
+ "quasar/inference/universal.py": {
143
+ "bytes": 6151,
144
+ "sha256": "50569a27c60c91d5b5a998e018f2e18d63b9483b082faa7225e4ec6a808120eb"
145
+ },
146
+ "quasar/inference/__init__.py": {
147
+ "bytes": 0,
148
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
149
+ },
150
+ "quasar/model/attention.py": {
151
+ "bytes": 2905,
152
+ "sha256": "22bfd305f5dba981438913d0a8418e4ec9d5e62672740e458db1ce2e8c11585c"
153
+ },
154
+ "quasar/model/axiom.py": {
155
+ "bytes": 6040,
156
+ "sha256": "1919f42a57cf3f4f0601cb79846f7fe91af826b802a188c63cf0c1a02a4e539d"
157
+ },
158
+ "quasar/model/config.py": {
159
+ "bytes": 3031,
160
+ "sha256": "be2218d6e051120af3be8ee53aa0ccdd052a316588e69c7d8684540f029d0591"
161
+ },
162
+ "quasar/model/layers.py": {
163
+ "bytes": 1704,
164
+ "sha256": "859edfce4cce98453da2bd6bc3e24b45ad188d908d3351e0074e90cf55ab4b9f"
165
+ },
166
+ "quasar/model/transformer.py": {
167
+ "bytes": 6531,
168
+ "sha256": "43ff8614eeb1e04d0d69e20cf235e1e0b5c1a4b8c022554ec21e2a4033440c51"
169
+ },
170
+ "quasar/model/__init__.py": {
171
+ "bytes": 0,
172
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
173
+ },
174
+ "quasar/tokenizers/audio.py": {
175
+ "bytes": 2726,
176
+ "sha256": "e587422428a3925841754b7a8a928abd0c3bb0fa82bd259b8a17534075f93a55"
177
+ },
178
+ "quasar/tokenizers/audio_aligned.py": {
179
+ "bytes": 1852,
180
+ "sha256": "d32dbd18faa8eb75b40f7ae1cc64d7e5fffa8f634bb2998f399ed1c0f24adfb2"
181
+ },
182
+ "quasar/tokenizers/audio_complete.py": {
183
+ "bytes": 1068,
184
+ "sha256": "d50959abc6130c58d4935b4846bc2916475cd51cd26a35a4bcad498d7f51917d"
185
+ },
186
+ "quasar/tokenizers/mesh.py": {
187
+ "bytes": 2144,
188
+ "sha256": "148cc80bcb900eed289926b3130dcd1d35f1eae2c000511d13a80f3de056ed26"
189
+ },
190
+ "quasar/tokenizers/multimodal.py": {
191
+ "bytes": 9485,
192
+ "sha256": "5c895527a02624489e7c4ced5912eb2cf0d41912945071ff6795770d740ca5a8"
193
+ },
194
+ "quasar/tokenizers/multires_emu.py": {
195
+ "bytes": 2840,
196
+ "sha256": "764a04ff63106146cbc0d910b5ff293baf54335ffa93305531d2b971c77da353"
197
+ },
198
+ "quasar/tokenizers/restricted_emu.py": {
199
+ "bytes": 2511,
200
+ "sha256": "add63d1042dc72dadfd79ec0ad3796e1a6cac2a9023ebf498a54a1d6d3ab8bf8"
201
+ },
202
+ "quasar/tokenizers/text.py": {
203
+ "bytes": 1781,
204
+ "sha256": "d97bc0c634dab8318fc7528def8d9fb1eb17e21da0c92e0812368c02d313135b"
205
+ },
206
+ "quasar/tokenizers/universal.py": {
207
+ "bytes": 12299,
208
+ "sha256": "1607e4c9b7eeefe8ee7eacc70bbf53a2bdf934f7636823c5abed2d0a0151dc8c"
209
+ },
210
+ "quasar/tokenizers/vision.py": {
211
+ "bytes": 7566,
212
+ "sha256": "10f239e83334a06dc49b47fdab7bde9188ade3e5a4612ee1175e0680ba329ca2"
213
+ },
214
+ "quasar/tokenizers/__init__.py": {
215
+ "bytes": 0,
216
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
217
+ },
218
+ "quasar/training/checkpoint.py": {
219
+ "bytes": 3473,
220
+ "sha256": "39c02d1b30da98b8f3861d304f9db5eea0660cec701d8c58cdb4ef14f1beaefd"
221
+ },
222
+ "quasar/training/compilation.py": {
223
+ "bytes": 2062,
224
+ "sha256": "812443fb70939fd06d3f8c96e3d75dc8f9beabcbcaef11623cc571291b437877"
225
+ },
226
+ "quasar/training/hardware.py": {
227
+ "bytes": 2776,
228
+ "sha256": "d9e47b5246c61d09e54b699d9de09d3e42cda29992894b1a58b5b4042e956c2a"
229
+ },
230
+ "quasar/training/metrics.py": {
231
+ "bytes": 2505,
232
+ "sha256": "19189212aa5513def43109142213f9d1ef8c759162395d26d02d1104b5bbc947"
233
+ },
234
+ "quasar/training/migration.py": {
235
+ "bytes": 1381,
236
+ "sha256": "0db3c61f34fe0cc202d6bee9a7d4e4a5f58c6da0f23049d58adcf794239b4f00"
237
+ },
238
+ "quasar/training/trainer.py": {
239
+ "bytes": 21181,
240
+ "sha256": "5a00a53173ca9ee21cce763f9345af1db045b1f0898c073c692aeeb2dbf1c8f2"
241
+ },
242
+ "quasar/training/validation_guard.py": {
243
+ "bytes": 2101,
244
+ "sha256": "81b2916c4879e06025447fba99111cdbacdcdf4065dc033e1e509b434fdacbdd"
245
+ },
246
+ "quasar/training/__init__.py": {
247
+ "bytes": 0,
248
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
249
+ },
250
+ "codecs/audio/config.json": {
251
+ "bytes": 542,
252
+ "sha256": "3af86549c509e5491367dab3f7093014bd559f441ba31fa7427b5f94232368d7"
253
+ },
254
+ "codecs/audio/model.safetensors": {
255
+ "bytes": 298652268,
256
+ "sha256": "7452e3fc6972991da871ae1a1c9d3e8e219aa247cc0f757018867d6a7fe59aaf"
257
+ },
258
+ "codecs/audio/OFFICIAL_LICENSE": {
259
+ "bytes": 1095,
260
+ "sha256": "32277f65d28ee0be285c9e2d12970d92bd1331ae9fad490801c3a79243fdb21b"
261
+ },
262
+ "codecs/audio/OFFICIAL_WEIGHTS_TERMS.md": {
263
+ "bytes": 6557,
264
+ "sha256": "3178619f84b8524105b10518d0527600ca74c01386885995f58bc633da2fcbd9"
265
+ },
266
+ "codecs/audio/README.md": {
267
+ "bytes": 5198,
268
+ "sha256": "6b1db50f4974b1544fb795491b6fd57a20320ff17686565c50a08407f06075b1"
269
+ },
270
+ "codecs/vision/config.json": {
271
+ "bytes": 592,
272
+ "sha256": "6717e1bd03d980c6b5e88aa0a47a557d193b0c33afbdb9d24e53a92593ee7473"
273
+ },
274
+ "codecs/vision/configuration_emu3visionvq.py": {
275
+ "bytes": 4176,
276
+ "sha256": "e4a9c0ee78c5196219a5ab89223ba82d2d20844687d19f877b261e5026ef4c11"
277
+ },
278
+ "codecs/vision/image_processing_emu3visionvq.py": {
279
+ "bytes": 22487,
280
+ "sha256": "506392861a6842fa3c9a0bb320b3be0228309c93a1f3b45e4aa2caf501141ea4"
281
+ },
282
+ "codecs/vision/LICENSE": {
283
+ "bytes": 11558,
284
+ "sha256": "1eb85fc97224598dad1852b5d6483bbcf0aa8608790dcc657a5a2a761ae9c8c6"
285
+ },
286
+ "codecs/vision/model.safetensors": {
287
+ "bytes": 1083015124,
288
+ "sha256": "89536431c69b08b10b449ec309f52dcea22f14b7647317f30f5715273392bbf1"
289
+ },
290
+ "codecs/vision/modeling_emu3visionvq.py": {
291
+ "bytes": 24362,
292
+ "sha256": "07558b036f40ec751de174487b15fe5c03823ba2edf29449bfa29be6f7f01afc"
293
+ },
294
+ "codecs/vision/preprocessor_config.json": {
295
+ "bytes": 556,
296
+ "sha256": "98a019cd8820290dbdc53e7366f7e447ee55d085824b63fdb68b2d642dd9edbf"
297
+ },
298
+ "codecs/vision/README.md": {
299
+ "bytes": 3531,
300
+ "sha256": "5763e0d54ed458232a8a4edb94cbf8b3b7fcf7d76372c2c0ea2c4c7b6e6a4e78"
301
+ }
302
+ }
infer.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Explicit local Quasar inference. No model/codec download or tool execution."""
2
+ from pathlib import Path
3
+ import argparse
4
+ import os
5
+ import sys
6
+
7
+ ROOT = Path(__file__).resolve().parent
8
+ sys.path.insert(0, str(ROOT))
9
+
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser()
13
+ parser.add_argument('--checkpoint', type=Path, default=ROOT)
14
+ parser.add_argument('--task', choices=('text', 'image', 'video'), required=True)
15
+ parser.add_argument('--prompt', required=True)
16
+ parser.add_argument('--device', choices=('cpu', 'cuda'), default='cuda')
17
+ parser.add_argument('--output', type=Path)
18
+ parser.add_argument('--resolution', type=int, choices=(64, 128), default=128)
19
+ parser.add_argument('--frames', type=int, default=8)
20
+ parser.add_argument('--seed', type=int, default=20261021)
21
+ parser.add_argument('--max-new-tokens', type=int, default=64)
22
+ args = parser.parse_args()
23
+ if args.device == 'cpu':
24
+ os.environ['CUDA_VISIBLE_DEVICES'] = ''
25
+ os.environ.setdefault('TORCHINDUCTOR_TRITON_DISABLE_DEVICE_DETECTION', '1')
26
+ # These FP32/BF16 weights do not use the optional GPU quantizer.
27
+ sys.modules['torchao'] = None
28
+ import torch
29
+ torch.set_num_threads(2)
30
+ if args.device == 'cuda':
31
+ from quasar.training.hardware import check_gpu
32
+ check_gpu({'require_cuda': True, 'exclusive_gpu': True, 'min_free_vram_gb': 4})
33
+ from quasar.inference.extended import ExtendedGenerator
34
+ from quasar.inference.decoding import Sampling
35
+ generator = ExtendedGenerator(args.checkpoint.resolve(), args.device)
36
+ sampling = Sampling(temperature=.5, top_k=64, top_p=.9, seed=args.seed, max_new_tokens=args.max_new_tokens)
37
+ if args.task == 'text':
38
+ result = generator.generate_text(args.prompt, sampling)
39
+ if args.output:
40
+ args.output.parent.mkdir(parents=True, exist_ok=True)
41
+ args.output.write_text(result, encoding='utf-8')
42
+ print(result)
43
+ return
44
+ from PIL import Image
45
+ output = args.output or Path('generated.png' if args.task == 'image' else 'generated.gif')
46
+ output.parent.mkdir(parents=True, exist_ok=True)
47
+ if args.task == 'image':
48
+ pixels = generator.generate_image(args.prompt, sampling, resolution=args.resolution)
49
+ Image.fromarray(pixels).save(output)
50
+ else:
51
+ if output.suffix.lower() != '.gif':
52
+ parser.error('Video preview output must use .gif')
53
+ frames = generator.generate_video(args.prompt, args.frames, sampling)
54
+ images = [Image.fromarray(frame) for frame in frames]
55
+ images[0].save(output, save_all=True, append_images=images[1:], duration=250, loop=0, disposal=2)
56
+ print(output.resolve())
57
+
58
+
59
+ if __name__ == '__main__':
60
+ main()
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:de4846e5e63c0004cf0b6331127ab421d6843f6efde04243c6d5448ab4545240
3
+ size 361008568
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "quasar"
3
+ version = "0.1.0"
4
+ description = "Quasar: a unified autoregressive text/image/video foundation-model prototype"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "torch>=2.6", "numpy>=1.26", "pillow>=10.0", "pyyaml>=6.0", "safetensors>=0.4",
8
+ "tokenizers>=0.19", "huggingface_hub>=0.24", "pyarrow>=14", "imageio-ffmpeg>=0.5", "requests>=2.31",
9
+ ]
10
+
11
+ [project.optional-dependencies]
12
+ app = ["gradio==6.30.0", "psutil>=7.0"]
13
+ dev = ["pytest>=8.0"]
14
+ multimodal = ["jsonschema>=4.20", "transformers==5.5.0", "soundfile>=0.12", "trimesh>=4.0", "scipy>=1.14"]
15
+
16
+ [build-system]
17
+ requires = ["setuptools>=68"]
18
+ build-backend = "setuptools.build_meta"
19
+
20
+ [tool.setuptools.packages.find]
21
+ include = ["quasar*"]
22
+
23
+ [tool.pytest.ini_options]
24
+ testpaths = ["tests"]
quasar/__init__.py ADDED
File without changes
quasar/data/__init__.py ADDED
File without changes
quasar/data/adapters.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Offline adapters for the selected native records.
2
+
3
+ Media bindings are prepared separately with real codecs and audited rights.
4
+ An adapter never downloads an image URL or assumes a dataset card licenses it.
5
+ All reversals of a pair keep the same source group and media assets.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import ast
11
+ from .curated import digest, validate_calls
12
+
13
+
14
+ def _json(x):
15
+ return json.loads(x) if isinstance(x, str) else x
16
+
17
+
18
+ def normalize(dataset: str, row: dict, provenance: dict, binding: dict | None = None) -> list[dict]:
19
+ """`provenance` supplies id/revision/license/attribution/source_url/split.
20
+
21
+ binding: {packet: encoded modality packet, assets: [...rights/checksums...],
22
+ group: source object/video/speaker/uploader ID, caption: aligned caption}
23
+ For media, `group` is mandatory: no clip-level or view-level random split.
24
+ """
25
+ def record(task, ins, outs, group):
26
+ return {**provenance, "dataset": dataset, "task": task, "group": group, "inputs": ins, "outputs": outs,
27
+ **({"assets": binding["assets"], "codec": binding.get("codec", {})} if binding else {})}
28
+ text = lambda s: {"type": "text", "text": str(s).strip()}
29
+ if dataset == "mbpp":
30
+ source_split = provenance.get("source_split")
31
+ expected = {"train": "train", "validation": "val", "test": "test"}
32
+ if source_split not in expected or provenance.get("split") != expected[source_split]:
33
+ raise ValueError("MBPP official train/validation/test provenance must be preserved")
34
+ code = row["code"].strip()
35
+ ast.parse(code) # syntax validation only; dataset code is never executed by this adapter
36
+ examples = "\n".join(row.get("test_list", []))
37
+ prompt = row["text"].strip() + ("\n\nExamples:\n" + examples if examples else "")
38
+ return [record("coding", [text(prompt)], [text("```python\n" + code + "\n```")], str(row["task_id"]))]
39
+ if dataset in ("builtin", "user"):
40
+ if not row.get("group"): raise ValueError("local records require a stable source group")
41
+ return [record(row["task"], row["inputs"], row["outputs"], row["group"])]
42
+ if dataset == "fineweb_edu":
43
+ return [record("text", [], [text(row["text"])], row.get("url") or digest(row["text"]))]
44
+ if dataset == "smoltalk":
45
+ messages = row["messages"]
46
+ if not messages or messages[-1]["role"] != "assistant":
47
+ raise ValueError("SmolTalk row must end with a complete assistant response")
48
+ context = "\n".join(f"{m['role']}: {m['content']}" for m in messages[:-1])
49
+ if not context.strip(): raise ValueError("conversation has no conditioning")
50
+ return [record("chat_text", [text(context)], [text(messages[-1]["content"])], digest(messages))]
51
+ if dataset == "xlam":
52
+ tools = _json(row["tools"])
53
+ calls = _json(row["answers"])
54
+ validate_calls(tools, calls)
55
+ # Whole tool schemas (including function descriptions/parameters) define a heldout family.
56
+ group = digest(sorted(tools, key=lambda x: x.get("function", x)["name"]))
57
+ return [record("tools", [{"type": "tools", "value": tools}, text(row["query"])], [{"type": "calls", "value": calls}], group)]
58
+ if binding is None or not binding.get("group") or not binding.get("assets"):
59
+ raise ValueError("media adapter requires an encoded packet, group ID and audited asset binding")
60
+ packet, group = binding["packet"], binding["group"]
61
+ if dataset == "openimages_narratives":
62
+ if not str(row["dataset_id"]).startswith("open_images"):
63
+ raise ValueError("only the Open Images subset is selected; COCO/Flickr/ADE rights differ")
64
+ cap, a, b, kind = row["caption"], "t2i", "i2t", "image"
65
+ elif dataset == "finevideo":
66
+ # Required exact-window caption is selected from scene/activity metadata, not a whole-video title.
67
+ if not binding.get("caption_aligned") or not binding.get("window_seconds"):
68
+ raise ValueError("FineVideo needs scene-aligned window caption and timestamps")
69
+ cap, a, b, kind = binding["caption"], "t2v", "v2t", "video"
70
+ elif dataset == "libritts_r":
71
+ if not binding.get("complete_utterance"):
72
+ raise ValueError("speech pair must retain the complete aligned utterance")
73
+ cap, a, b, kind = row["text_normalized"], "t2a", "a2t", "audio"
74
+ elif dataset == "fsd50k":
75
+ # Weak class-label conditioning; never report this as free-form human captioning.
76
+ cap, a, b, kind = "sound of " + ", ".join(row["labels"]), "t2a", "a2t", "audio"
77
+ elif dataset == "objaverse_cap3d":
78
+ cap, a, b, kind = row["caption"], "t2m", "m2t", "mesh"
79
+ else:
80
+ raise ValueError(f"no enabled adapter for {dataset}")
81
+ if packet["type"] != kind or not str(cap).strip(): raise ValueError("packet kind or caption mismatch")
82
+ out = [record(a, [text(cap)], [packet], group), record(b, [packet], [text(cap)], group)]
83
+ if dataset == "finevideo" and len(packet["codes"]) >= 4:
84
+ mid = len(packet["codes"]) // 2
85
+ out.append(record("vcont", [{"type":"video", "codes":packet["codes"][:mid]}],
86
+ [{"type":"video", "codes":packet["codes"][mid:]}], group))
87
+ return out
quasar/data/collator.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dynamic padding + next-token label construction."""
2
+ from __future__ import annotations
3
+
4
+ import numpy as np
5
+ import torch
6
+
7
+ from ..tokenizers.multimodal import Sample
8
+
9
+
10
+ def collate(samples: list[Sample], pad_id: int, multiple: int = 8, *, target_length: int | None = None,
11
+ batch_size: int | None = None) -> tuple[torch.Tensor, torch.Tensor]:
12
+ """Right-pad to the batch max (rounded up to `multiple`) and build shifted labels.
13
+
14
+ labels[j] = tokens[j+1] where j+1 >= target_start, else -100 (prompt and padding are not
15
+ trained on). Right padding is safe under causal attention: real tokens never attend to it.
16
+ """
17
+ L = max(len(s.tokens) for s in samples) - 1
18
+ if target_length is not None:
19
+ if target_length < L:
20
+ raise ValueError("fixed padding cannot truncate a sample")
21
+ L = target_length
22
+ else:
23
+ L = -(-L // multiple) * multiple
24
+ B = len(samples) if batch_size is None else batch_size
25
+ if B < len(samples):
26
+ raise ValueError("fixed batch size cannot discard samples")
27
+ inp = np.full((B, L), pad_id, dtype=np.int64)
28
+ lab = np.full((B, L), -100, dtype=np.int64)
29
+ for i, s in enumerate(samples):
30
+ t = s.tokens.astype(np.int64)
31
+ n = len(t) - 1
32
+ inp[i, :n] = t[:-1]
33
+ lab[i, :n] = t[1:]
34
+ lab[i, : max(0, s.target_start - 1)] = -100
35
+ return torch.from_numpy(inp), torch.from_numpy(lab)
quasar/data/curated.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Offline normalized records -> validated, deduplicated, tokenized v2 caches.
2
+
3
+ No network access, model updates, tool execution or implicit license assumptions.
4
+ Records retain dataset, revision, group, split, attribution, asset checksums and
5
+ packet inputs/outputs. Paired generation/understanding rows share one split key.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections import Counter, defaultdict
10
+ import hashlib
11
+ import json
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+
16
+ from ..tokenizers.multimodal import Sample
17
+ from ..tokenizers.universal import UniversalVocab
18
+ from .preprocessing import sha256_file
19
+
20
+
21
+ def canonical(value) -> str:
22
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
23
+
24
+
25
+ def digest(value) -> str:
26
+ return hashlib.sha256(canonical(value).encode()).hexdigest()
27
+
28
+
29
+ def validate_calls(tools: list[dict], calls: list[dict]) -> None:
30
+ """Validate tool names and arguments with full JSON Schema; never execute tools."""
31
+ from jsonschema import Draft202012Validator
32
+ names = {}
33
+ for tool in tools:
34
+ f = tool.get("function", tool)
35
+ name = f["name"]
36
+ if name in names:
37
+ raise ValueError(f"duplicate tool name: {name}")
38
+ schema = f["parameters"]
39
+ Draft202012Validator.check_schema(schema)
40
+ names[name] = Draft202012Validator(schema)
41
+ for call in calls:
42
+ if call["name"] not in names:
43
+ raise ValueError(f"unknown tool: {call['name']}")
44
+ if not isinstance(call["arguments"], dict):
45
+ raise ValueError("tool arguments must be a JSON object")
46
+ names[call["name"]].validate(call["arguments"])
47
+
48
+
49
+ def verify_record(r: dict, catalog: dict):
50
+ for key in ("id", "dataset", "revision", "group", "license", "attribution", "source_url", "task", "inputs", "outputs"):
51
+ if key not in r or r[key] in (None, "", []):
52
+ # Text pretraining legitimately has no input conditioning.
53
+ if key == "inputs" and r.get(key) == []:
54
+ continue
55
+ raise ValueError(f"missing provenance/content field: {key}")
56
+ ds = catalog["datasets"].get(r["dataset"])
57
+ if ds is None or ds["role"] != "core":
58
+ raise ValueError("dataset is unknown or disabled/reserve")
59
+ if r["task"] not in ds["tasks"]:
60
+ raise ValueError("task not registered for dataset")
61
+ if r["dataset"] == "mbpp":
62
+ expected = {"train": "train", "validation": "val", "test": "test"}
63
+ if r.get("source_split") not in expected or r.get("split") != expected[r["source_split"]]:
64
+ raise ValueError("MBPP official splits must be retained; held-out solutions cannot be relabeled train")
65
+ if r["revision"] != ds.get("revision", ds.get("release")):
66
+ raise ValueError("record revision differs from the pinned catalog")
67
+ if ds.get("caption_revision") and r.get("caption_revision") != ds["caption_revision"]:
68
+ raise ValueError("record must pin its caption source revision as well as its asset source")
69
+ allowed = catalog["policy"]["allowed_asset_licenses"]
70
+ licenses = r["license"] if isinstance(r["license"], list) else [r["license"]]
71
+ for lic in licenses:
72
+ if lic not in allowed:
73
+ raise ValueError(f"unapproved or ambiguous license: {lic}")
74
+ declared = ds.get("license")
75
+ if declared and declared not in licenses and not (declared == "CC-BY" and any(x.startswith("CC-BY-") for x in licenses)):
76
+ raise ValueError("record license differs from pinned dataset terms")
77
+ media = [s for s in r["inputs"] + r["outputs"] if s["type"] in ("image", "video", "audio", "mesh")]
78
+ if media:
79
+ for asset in r.get("assets", []):
80
+ for key in ("sha256", "license", "attribution", "source_url", "rights_evidence"):
81
+ if not asset.get(key):
82
+ raise ValueError(f"media asset missing {key}")
83
+ if asset["license"] not in allowed:
84
+ raise ValueError("unapproved media asset license")
85
+ if len(asset["sha256"]) != 64 or any(c not in "0123456789abcdef" for c in asset["sha256"]):
86
+ raise ValueError("invalid asset checksum")
87
+ if not r.get("assets"):
88
+ raise ValueError("media record needs asset rights evidence and checksums")
89
+ if r["task"] == "tools":
90
+ tools = next(s["value"] for s in r["inputs"] if s["type"] == "tools")
91
+ calls = next(s["value"] for s in r["outputs"] if s["type"] == "calls")
92
+ validate_calls(tools, calls)
93
+ inputs = {s["type"] for s in r["inputs"]}
94
+ outputs = {s["type"] for s in r["outputs"]}
95
+ expected = {"t2i": ("text", "image"), "i2t": ("image", "text"), "t2v": ("text", "video"),
96
+ "v2t": ("video", "text"), "vcont": ("video", "video"), "t2a": ("text", "audio"),
97
+ "a2t": ("audio", "text"), "t2m": ("text", "mesh"), "m2t": ("mesh", "text"),
98
+ "chat_text": ("text", "text"), "coding": ("text", "text"), "tools": ("tools", "calls")}
99
+ if r["task"] in expected:
100
+ a, b = expected[r["task"]]
101
+ if a not in inputs or b not in outputs: raise ValueError("packet types do not match the registered task")
102
+ if r["task"] == "text" and outputs != {"text"}: raise ValueError("text task needs text targets")
103
+
104
+
105
+ def compile_manifest(manifest: str | Path, out: str | Path, vocab: UniversalVocab, tok, catalog: dict,
106
+ context: int = 1024, max_disk_bytes: int = 20_000_000_000) -> dict:
107
+ """Stream into uint16 token files and per-row indices; reject overlong samples without truncation.
108
+
109
+ Existing output directories are refused. Failure preserves an INCOMPLETE marker;
110
+ a loader refuses that directory. Provenance is saved separately from compact indices.
111
+ """
112
+ manifest, out = Path(manifest), Path(out)
113
+ if out.exists():
114
+ raise FileExistsError(f"refusing to overwrite cache {out}; use a new versioned directory")
115
+ if tok.vocab_size != vocab.text_size or vocab.size > 65536:
116
+ raise ValueError("tokenizer size/vocabulary mismatch for uint16 cache")
117
+ out.mkdir(parents=True)
118
+ (out / "INCOMPLETE").write_text("preparation in progress")
119
+ counts, offsets = Counter(), defaultdict(int)
120
+ groups, assets, seen, seen_split = {}, {}, set(), {}
121
+ report = {"training_started": False, "records": 0, "target_tokens": 0}
122
+ handles = {}
123
+ try:
124
+ for split in ("train", "val", "test"):
125
+ handles[split] = ((out / f"{split}.bin").open("wb"), (out / f"{split}.index.jsonl").open("w", encoding="utf-8"))
126
+ with manifest.open(encoding="utf-8") as src, (out / "provenance.jsonl").open("w", encoding="utf-8") as prov:
127
+ for lineno, line in enumerate(src, 1):
128
+ if not line.strip(): continue
129
+ try:
130
+ r = json.loads(line)
131
+ verify_record(r, catalog)
132
+ group = r["dataset"] + ":" + str(r["group"])
133
+ # Explicit official splits win. Otherwise hash source groups, never windows/captions.
134
+ split = r.get("split") or ("val" if int(digest(group)[:8], 16) % 100 < 2 else "train")
135
+ if split not in handles: raise ValueError("split must be train, val or test")
136
+ if group in groups and groups[group] != split: raise ValueError("source group leaks across splits")
137
+ groups[group] = split
138
+ for a in r.get("assets", []):
139
+ h = a["sha256"]
140
+ if h in assets and assets[h] != split: raise ValueError("asset content leaks across splits")
141
+ assets[h] = split
142
+ key = digest([r["task"], r["inputs"], r["outputs"]])
143
+ if key in seen:
144
+ if seen_split[key] != split: raise ValueError("duplicate example leaks across splits")
145
+ counts["duplicates_skipped"] += 1
146
+ continue
147
+ seen.add(key)
148
+ seen_split[key] = split
149
+ sample = vocab.build(r["task"], r["inputs"], r["outputs"], tok.encode, context)
150
+ if (sum(offsets.values()) + len(sample.tokens)) * 2 > max_disk_bytes:
151
+ raise ValueError("prepared token disk budget exceeded")
152
+ sample.tokens.astype("<u2").tofile(handles[split][0])
153
+ idx = {"offset": offsets[split], "length": len(sample.tokens), "target_start": sample.target_start,
154
+ "task": r["task"], "id": r["id"], "dataset": r["dataset"], "group": r["group"]}
155
+ handles[split][1].write(canonical(idx) + "\n")
156
+ prov.write(canonical({k: r[k] for k in r if k not in ("inputs", "outputs")}) + "\n")
157
+ offsets[split] += len(sample.tokens)
158
+ counts[split + "/" + r["task"]] += 1
159
+ report["records"] += 1
160
+ report["target_tokens"] += len(sample.tokens) - sample.target_start
161
+ except Exception as e:
162
+ raise ValueError(f"{manifest}:{lineno}: {e}") from e
163
+ finally:
164
+ for pair in handles.values():
165
+ for f in pair: f.close()
166
+ tok.save(out / "text_tokenizer.json")
167
+ (out / "vocab_v2.json").write_text(json.dumps(vocab.to_dict(), indent=2))
168
+ (out / "catalog.json").write_text(json.dumps(catalog, indent=2))
169
+ report |= {"counts": dict(counts), "manifest_sha256": sha256_file(manifest), "catalog_sha256": digest(catalog),
170
+ "token_bytes": sum(offsets.values()) * 2, "context": context, "tokenizer_sha256": sha256_file(out / "text_tokenizer.json")}
171
+ (out / "stats.json").write_text(json.dumps(report, indent=2))
172
+ (out / "INCOMPLETE").unlink()
173
+ return report
174
+
175
+
176
+ class UniversalSources:
177
+ """Same sample/available interface as TaskSources; consumed by existing MixtureLoader."""
178
+ def __init__(self, cache: str | Path, split: str, vocab: UniversalVocab, seq_len: int = 1024):
179
+ cache = Path(cache)
180
+ if (cache / "INCOMPLETE").exists(): raise ValueError("incomplete v2 cache")
181
+ if json.loads((cache / "vocab_v2.json").read_text()) != vocab.to_dict(): raise ValueError("v2 vocabulary mismatch")
182
+ self.vocab, self.seq_len, self.rows = vocab, seq_len, defaultdict(list)
183
+ path = cache / f"{split}.bin"
184
+ self.tokens = np.memmap(path, dtype="<u2", mode="r") if path.stat().st_size else np.array([], dtype=np.uint16)
185
+ for line in (cache / f"{split}.index.jsonl").read_text().splitlines():
186
+ r = json.loads(line)
187
+ if r["length"] - 1 > seq_len: raise ValueError("cached sample exceeds requested context")
188
+ self.rows[r["task"]].append(r)
189
+
190
+ def available(self): return list(self.rows)
191
+
192
+ def sample(self, task: str, rng: np.random.Generator) -> Sample:
193
+ rows = self.rows[task]
194
+ r = rows[int(rng.integers(len(rows)))]
195
+ return Sample(np.array(self.tokens[r["offset"]:r["offset"] + r["length"]], dtype=np.int32), r["target_start"])
quasar/data/datasets.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Task sources over the pretokenized caches and a seeded mixture loader.
2
+
3
+ Every micro-batch holds a single task (so lengths are similar and padding is small) and is
4
+ drawn from an RNG seeded by (seed, step, micro_index): sampling is a pure function of the step,
5
+ which makes runs reproducible and resumable without storing data-loader state.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import queue
11
+ import threading
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import torch
16
+
17
+ from ..tokenizers.multimodal import Sample, UnifiedVocab
18
+ from ..tokenizers.text import TextTokenizer
19
+ from .collator import collate
20
+
21
+ BASE_TASKS = ("text", "t2i", "i2t", "i2i", "t2v", "v2t", "vcont")
22
+ CHAT_TASKS = ("chat_text", "chat_mm")
23
+ TASK_MODALITY = {"text": "text", "chat_text": "text", "t2i": "image", "i2t": "image", "i2i": "image",
24
+ "t2v": "video", "v2t": "video", "vcont": "video", "chat_mm": "multimodal"}
25
+
26
+ CHAT_PROMPTS = {
27
+ "t2i": ["Generate an image of {c}.", "Draw {c}.", "Create a picture of {c}.", "Show me {c}.", "Can you make an image of {c}?"],
28
+ "t2v": ["Make a video of {c}.", "Generate a video of {c}.", "Create a short clip of {c}.", "Animate {c}."],
29
+ "i2t": ["Describe this image.", "What do you see in this picture?", "What is in this image?"],
30
+ "v2t": ["Describe this video.", "What is happening in this video?", "What happens in this clip?"],
31
+ "vcont": ["Continue the movement in this video.", "Continue this video.", "What happens next? Show me."],
32
+ "i2v": ["Animate this image: {c}.", "Bring this picture to life: {c}."],
33
+ }
34
+ CHAT_KINDS = ("t2i", "t2v", "i2t", "v2t", "vcont", "i2i", "i2v")
35
+
36
+
37
+ def _load_media(cache_dir: Path, name: str, split: str, vocab: UnifiedVocab, keys: tuple[str, ...]) -> tuple[dict, dict] | None:
38
+ """Load <name>_<split>.npz/.json, dropping rows whose codes are out of range (corruption guard)."""
39
+ npz, js = cache_dir / f"{name}_{split}.npz", cache_dir / f"{name}_{split}.json"
40
+ if not npz.exists():
41
+ return None
42
+ try:
43
+ with np.load(npz) as z:
44
+ arrs = {k: z[k] for k in keys}
45
+ meta = json.loads(js.read_text())
46
+ except (OSError, ValueError, KeyError) as e:
47
+ raise RuntimeError(f"cache {npz} is unreadable ({e}); regenerate with scripts/generate_dataset.py") from e
48
+ ok = np.ones(len(arrs[keys[0]]), dtype=bool)
49
+ for a in arrs.values():
50
+ ok &= (a.reshape(len(a), -1) < vocab.visual_size).all(1)
51
+ if not ok.all():
52
+ print(f"[data] {npz.name}: dropping {int((~ok).sum())} corrupted rows")
53
+ keep = np.nonzero(ok)[0]
54
+ return {k: a[keep] for k, a in arrs.items()}, {k: [v[i] for i in keep] for k, v in meta.items()}
55
+
56
+
57
+ class TaskSources:
58
+ """Builds one training Sample for any task from the caches of a given split."""
59
+
60
+ def __init__(self, cache_dir: str | Path, split: str, vocab: UnifiedVocab, tok: TextTokenizer, seq_len: int, caption_dropout: float = 0.1):
61
+ cache_dir = Path(cache_dir)
62
+ self.vocab, self.tok, self.seq_len = vocab, tok, seq_len
63
+ # Blank the t2i/t2v caption this often so the model also learns p(media) -> classifier-free guidance.
64
+ self.caption_dropout = caption_dropout if split == "train" else 0.0
65
+ tp = cache_dir / f"text_{split}.bin"
66
+ self.text = np.memmap(tp, dtype=np.uint16, mode="r") if tp.exists() else None
67
+ self.images = _load_media(cache_dir, "images", split, vocab, ("codes",))
68
+ self.edits = _load_media(cache_dir, "edits", split, vocab, ("src", "tgt"))
69
+ self.videos = _load_media(cache_dir, "videos", split, vocab, ("codes",))
70
+ cp = cache_dir / f"chat_{split}.json"
71
+ self.chat = json.loads(cp.read_text()) if cp.exists() else None
72
+ # indices with a caption (user videos may be uncaptioned: continuation-only)
73
+ cap_idx = lambda m: np.array([i for i, c in enumerate(m[1]["captions"]) if c], dtype=np.int64) if m else np.zeros(0, np.int64)
74
+ self.img_cap, self.vid_cap = cap_idx(self.images), cap_idx(self.videos)
75
+ need = {"t2i": len(self.img_cap), "i2t": len(self.img_cap), "t2v": len(self.vid_cap), "v2t": len(self.vid_cap),
76
+ "vcont": self.videos is not None, "i2i": self.edits is not None, "i2v": len(self.vid_cap)}
77
+ self.chat_kinds = [k for k in CHAT_KINDS if need[k]]
78
+
79
+ def available(self) -> list[str]:
80
+ have = {"text": self.text is not None and len(self.text) > self.seq_len + 1, "chat_text": bool(self.chat),
81
+ "t2i": len(self.img_cap) > 0, "i2t": len(self.img_cap) > 0, "i2i": self.edits is not None,
82
+ "t2v": len(self.vid_cap) > 0, "v2t": len(self.vid_cap) > 0, "vcont": self.videos is not None,
83
+ "chat_mm": bool(self.chat_kinds)}
84
+ return [t for t, ok in have.items() if ok]
85
+
86
+ def enc(self, s: str) -> list[int]:
87
+ return self.tok.encode(s)
88
+
89
+ def _prompt(self, caps: list[str], rng: np.random.Generator) -> list[int]:
90
+ return [] if rng.random() < self.caption_dropout else self.enc(str(rng.choice(caps)))
91
+
92
+ def sample(self, task: str, rng: np.random.Generator) -> Sample:
93
+ v = self.vocab
94
+ if task == "text":
95
+ off = int(rng.integers(0, len(self.text) - self.seq_len - 1))
96
+ return Sample(np.asarray(self.text[off : off + self.seq_len + 1], dtype=np.int32), 0)
97
+ if task in ("t2i", "i2t"):
98
+ (arr, meta), i = self.images, int(self.img_cap[rng.integers(len(self.img_cap))])
99
+ caps = meta["captions"][i]
100
+ if task == "t2i":
101
+ return v.build("t2i", self._prompt(caps, rng), image=arr["codes"][i])
102
+ return v.build("i2t", self.enc(caps[0]), image=arr["codes"][i])
103
+ if task == "i2i":
104
+ (arr, meta), i = self.edits, int(rng.integers(len(self.edits[0]["src"])))
105
+ return v.build("i2i", self.enc(meta["instructions"][i]), image=arr["src"][i], image2=arr["tgt"][i])
106
+ if task in ("t2v", "v2t", "vcont"):
107
+ pool = self.vid_cap if task != "vcont" else np.arange(len(self.videos[0]["codes"]))
108
+ (arr, meta), i = self.videos, int(pool[rng.integers(len(pool))])
109
+ caps = meta["captions"][i]
110
+ if task == "t2v":
111
+ return v.build("t2v", self._prompt(caps, rng), video=arr["codes"][i])
112
+ if task == "v2t":
113
+ return v.build("v2t", self.enc(caps[0]), video=arr["codes"][i])
114
+ return v.build("vcont", video=arr["codes"][i])
115
+ if task == "chat_text":
116
+ r = self.chat[int(rng.integers(len(self.chat)))]
117
+ s = v.chat(self.enc(r["user"]), self.enc(r["assistant"]))
118
+ return Sample(s.tokens[: self.seq_len + 1], s.target_start)
119
+ if task == "chat_mm":
120
+ return self.chat_mm(str(rng.choice(self.chat_kinds)), rng)
121
+ raise KeyError(task)
122
+
123
+ def chat_mm(self, kind: str, rng: np.random.Generator) -> Sample:
124
+ """Instruction-formatted multimodal conversation built from the media caches."""
125
+ v, enc = self.vocab, self.enc
126
+ pick = lambda xs: str(rng.choice(xs))
127
+ if kind in ("t2i", "i2t"):
128
+ (arr, meta), i = self.images, int(self.img_cap[rng.integers(len(self.img_cap))])
129
+ cap, img = meta["captions"][i][0], arr["codes"][i]
130
+ if kind == "t2i":
131
+ return v.chat(enc(pick(CHAT_PROMPTS["t2i"]).format(c=pick(meta["captions"][i]))), enc(f"Here is an image of {cap}.") + v.image_tokens(img))
132
+ return v.chat(v.image_tokens(img) + enc(" " + pick(CHAT_PROMPTS["i2t"])), enc(f"The image shows {cap}."))
133
+ if kind == "i2i":
134
+ (arr, meta), i = self.edits, int(rng.integers(len(self.edits[0]["src"])))
135
+ instr = meta["instructions"][i]
136
+ return v.chat(v.image_tokens(arr["src"][i]) + enc(" " + instr[0].upper() + instr[1:] + "."), enc("Here is the edited image.") + v.image_tokens(arr["tgt"][i]))
137
+ pool = self.vid_cap if kind != "vcont" else np.arange(len(self.videos[0]["codes"]))
138
+ (arr, meta), i = self.videos, int(pool[rng.integers(len(pool))])
139
+ cap, vid = (meta["captions"][i] or [""])[0], arr["codes"][i]
140
+ if kind == "t2v":
141
+ return v.chat(enc(pick(CHAT_PROMPTS["t2v"]).format(c=pick(meta["captions"][i]))), enc(f"Here is a video of {cap}.") + v.video_tokens(vid))
142
+ if kind == "v2t":
143
+ return v.chat(v.video_tokens(vid) + enc(" " + pick(CHAT_PROMPTS["v2t"])), enc(f"The video shows {cap}."))
144
+ if kind == "vcont":
145
+ k = len(vid) // 2
146
+ return v.chat(v.video_tokens(vid[:k]) + enc(" " + pick(CHAT_PROMPTS["vcont"])), enc("Here is the continuation.") + v.video_tokens(vid[k:], first_index=k))
147
+ # i2v: the image is frame 0; the answer is the rest of the clip
148
+ return v.chat(v.image_tokens(vid[0]) + enc(" " + pick(CHAT_PROMPTS["i2v"]).format(c=cap)), enc("Here is the animation.") + v.video_tokens(vid[1:], first_index=1))
149
+
150
+
151
+ def split_task(name: str) -> tuple[str, str]:
152
+ """'user/t2i' -> ('user', 't2i'); 't2i' -> ('', 't2i') (source prefix selects the cache)."""
153
+ src, _, task = name.rpartition("/")
154
+ return src, task
155
+
156
+
157
+ def available_tasks(sources: dict[str, TaskSources]) -> list[str]:
158
+ return [f"{k}/{t}" if k else t for k, src in sources.items() for t in src.available()]
159
+
160
+
161
+ def compile_bucket_length(length: int, context: int) -> int:
162
+ """At most four fixed sequence sizes at context 1024; no content truncation."""
163
+ if not 0 < length <= context:
164
+ raise ValueError("sample length is outside the model context")
165
+ return min(context, max(128, 1 << (length - 1).bit_length()))
166
+
167
+
168
+ class MixtureLoader:
169
+ """Yields (task, input_ids, labels) micro-batches; one task per micro-batch, ~`micro_tokens` tokens each.
170
+
171
+ `sources` maps a source name to its TaskSources ('' = built-in cache, 'user' = imported data);
172
+ mixture keys address them as 't2i' or 'user/t2i', so every source/task share is an explicit weight."""
173
+
174
+ def __init__(self, sources: dict[str, TaskSources] | TaskSources, weights: dict[str, float], micro_tokens: int, seed: int, prefetch: int = 6,
175
+ compile_buckets: bool = False):
176
+ sources = sources if isinstance(sources, dict) else {"": sources}
177
+ missing = [t for t, w in weights.items() if w > 0 and t not in available_tasks(sources)]
178
+ if missing:
179
+ raise ValueError(f"mixture uses tasks with no cache: {missing}; available: {available_tasks(sources)}")
180
+ self.sources, self.seed, self.micro_tokens = sources, seed, micro_tokens
181
+ self.vocab = next(iter(sources.values())).vocab
182
+ self.tasks = [t for t, w in weights.items() if w > 0]
183
+ p = np.array([weights[t] for t in self.tasks], dtype=np.float64)
184
+ self.p = p / p.sum()
185
+ self.prefetch = prefetch
186
+ self.compile_buckets = compile_buckets
187
+
188
+ def batch(self, step: int, micro: int) -> tuple[str, torch.Tensor, torch.Tensor]:
189
+ rng = np.random.default_rng([self.seed, step, micro])
190
+ task = self.tasks[int(rng.choice(len(self.tasks), p=self.p))]
191
+ samples: list[Sample] = []
192
+ longest = 0
193
+ src, base = split_task(task)
194
+ while True:
195
+ s = self.sources[src].sample(base, rng)
196
+ proposed = max(longest, len(s.tokens) - 1 if self.compile_buckets else len(s.tokens))
197
+ padded = compile_bucket_length(proposed, self.sources[src].seq_len) if self.compile_buckets else proposed
198
+ if self.compile_buckets and padded > self.micro_tokens:
199
+ raise ValueError("compiled batch token budget is smaller than its padded context")
200
+ if samples and (len(samples) + 1) * padded > self.micro_tokens:
201
+ break
202
+ longest = proposed
203
+ samples.append(s)
204
+ if self.compile_buckets:
205
+ length = compile_bucket_length(longest, self.sources[src].seq_len)
206
+ # Empty rows have no target labels and do not change the supervised objective.
207
+ inp, lab = collate(samples, self.vocab.pad, target_length=length,
208
+ batch_size=self.micro_tokens // length)
209
+ else:
210
+ inp, lab = collate(samples, self.vocab.pad)
211
+ return task, inp, lab
212
+
213
+ def iterate(self, start_step: int, grad_accum: int):
214
+ """Background-thread prefetching iterator of (step, micro, task, inp, lab)."""
215
+ q: queue.Queue = queue.Queue(maxsize=self.prefetch)
216
+ stop = threading.Event()
217
+
218
+ def work():
219
+ step = start_step
220
+ while not stop.is_set():
221
+ for m in range(grad_accum):
222
+ item = (step, m, *self.batch(step, m))
223
+ while not stop.is_set():
224
+ try:
225
+ q.put(item, timeout=0.5)
226
+ break
227
+ except queue.Full:
228
+ continue
229
+ step += 1
230
+
231
+ th = threading.Thread(target=work, daemon=True)
232
+ th.start()
233
+ try:
234
+ while True:
235
+ yield q.get()
236
+ finally:
237
+ stop.set()
238
+
239
+
240
+ def fixed_eval_batches(sources: dict[str, TaskSources] | TaskSources, tasks: list[str], micro_tokens: int, n_batches: int, seed: int = 999) -> dict[str, list]:
241
+ """Deterministic held-out batches per task (same every evaluation)."""
242
+ sources = sources if isinstance(sources, dict) else {"": sources}
243
+ out = {}
244
+ for ti, task in enumerate(tasks):
245
+ if task not in available_tasks(sources):
246
+ continue
247
+ ml = MixtureLoader(sources, {task: 1.0}, micro_tokens, seed)
248
+ out[task] = [ml.batch(10_000 + ti, i)[1:] for i in range(n_batches)]
249
+ return out
quasar/data/download.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Resumable HTTP acquisition with disk reserve, hashes and atomic progress snapshots.
2
+
3
+ This module never extracts archives, evaluates remote code or initializes CUDA.
4
+ Dataset payloads remain separate from licensed/validated training caches.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ import hashlib
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+ import shutil
14
+ import threading
15
+ import time
16
+
17
+ import requests
18
+
19
+
20
+ class AccessBlocked(RuntimeError): pass
21
+ class DiskBudget(RuntimeError): pass
22
+ class FileTooLarge(ValueError): pass
23
+
24
+
25
+ def _comparable_path(path: Path) -> Path:
26
+ """Normalize Windows namespace aliases after resolving symlinks/junctions.
27
+
28
+ Non-strict realpath can retain the extended prefix when a parent directory
29
+ appears concurrently. Compare both spellings consistently; do not skip the
30
+ resolved-path containment check.
31
+ """
32
+ if os.name == "nt":
33
+ value = str(path)
34
+ if value.startswith("\\\\?\\UNC\\"):
35
+ return Path("\\\\" + value[8:])
36
+ if value.startswith("\\\\?\\") and len(value) >= 7 and value[5:7] == ":\\":
37
+ return Path(value[4:])
38
+ return path
39
+
40
+
41
+ def exception_summary(exc: Exception, limit: int = 180) -> str:
42
+ lines = str(exc).splitlines()
43
+ return type(exc).__name__ + ": " + (lines[0][:limit] if lines else "no additional detail")
44
+
45
+
46
+ class Downloads:
47
+ def __init__(self, root: str | Path, max_gib: float = 70, reserve_gib: float = 25):
48
+ self.root = Path(root).resolve()
49
+ self.root.mkdir(parents=True, exist_ok=True)
50
+ self.maximum, self.reserve = int(max_gib * 2**30), int(reserve_gib * 2**30)
51
+ self.lock = threading.RLock()
52
+ self.last_save = 0.0
53
+ self.bytes = sum(p.stat().st_size for p in self.root.rglob("*") if p.is_file())
54
+ self.state = {"pid": os.getpid(), "started": time.time(), "training_started": False,
55
+ "max_gib": max_gib, "reserve_gib": reserve_gib, "sources": {}}
56
+ self.current: dict = {}
57
+
58
+ def safe_path(self, rel: str) -> Path:
59
+ p = (self.root / rel).resolve()
60
+ if not _comparable_path(p).is_relative_to(_comparable_path(self.root)):
61
+ raise ValueError("download path escapes acquisition directory")
62
+ return p
63
+
64
+ def save(self, force=False):
65
+ with self.lock:
66
+ if not force and time.time() - self.last_save < 3: return
67
+ self.last_save = time.time()
68
+ self.state |= {"updated": time.time(), "stored_bytes": self.bytes,
69
+ "free_bytes": shutil.disk_usage(self.root).free, "current_files": dict(self.current)}
70
+ tmp = self.root / "download_status.json.tmp"
71
+ tmp.write_text(json.dumps(self.state, indent=2), encoding="utf-8")
72
+ # Windows readers briefly deny replacement while their file handle
73
+ # is open (for example, the live dashboard). Keep the last complete
74
+ # snapshot visible and retry this atomic swap for a bounded period.
75
+ for attempt in range(8):
76
+ try:
77
+ os.replace(tmp, self.root / "download_status.json")
78
+ break
79
+ except PermissionError:
80
+ if attempt == 7:
81
+ raise
82
+ time.sleep(0.01 * 2**attempt)
83
+
84
+ def event(self, source, status, **kwargs):
85
+ with self.lock:
86
+ old = self.state["sources"].setdefault(source, {})
87
+ old.update({"status": status, "updated": time.time(), **kwargs})
88
+ self.save(force=True)
89
+ print(json.dumps({"source": source, "status": status, **kwargs}), flush=True)
90
+
91
+ def _claim(self, amount):
92
+ with self.lock:
93
+ if self.bytes + amount > self.maximum: raise DiskBudget(f"{self.maximum / 2**30:g} GiB acquisition cap reached")
94
+ if shutil.disk_usage(self.root).free - amount < self.reserve: raise DiskBudget("25 GiB free-space reserve reached")
95
+ self.bytes += amount
96
+
97
+ def _record(self, record):
98
+ with self.lock:
99
+ with (self.root / "files.jsonl").open("a", encoding="utf-8") as f:
100
+ f.write(json.dumps(record) + "\n")
101
+
102
+ def get(self, url: str, rel: str, expected_size: int | None = None, checksum: str | None = None,
103
+ headers: dict | None = None, max_file_bytes: int = 12 * 2**30, retries: int = 3) -> Path:
104
+ """Resume .part with validated Content-Range. HTTP credentials are never logged."""
105
+ path = self.safe_path(rel)
106
+ path.parent.mkdir(parents=True, exist_ok=True)
107
+ if path.exists():
108
+ if expected_size is not None and path.stat().st_size != expected_size: raise ValueError(f"existing size mismatch: {rel}")
109
+ if checksum:
110
+ alg, value = checksum.split(":", 1)
111
+ if self.hash(path, alg) != value: raise ValueError(f"existing checksum mismatch: {rel}")
112
+ return path
113
+ part = path.with_name(path.name + ".part")
114
+ for attempt in range(retries):
115
+ try:
116
+ offset = part.stat().st_size if part.exists() else 0
117
+ h = {**(headers or {}), "Accept-Encoding": "identity"}
118
+ if offset: h["Range"] = f"bytes={offset}-"
119
+ with requests.get(url, headers=h, stream=True, timeout=(25, 90)) as response:
120
+ if response.status_code in (401, 403): raise AccessBlocked(f"access approval required for {rel} (HTTP {response.status_code})")
121
+ response.raise_for_status()
122
+ if offset and response.status_code != 206:
123
+ # Server ignored range. Restart this owned partial file, accounting for the old bytes.
124
+ with self.lock: self.bytes -= offset
125
+ offset = 0
126
+ if response.status_code == 206 and not response.headers.get("Content-Range", "").startswith(f"bytes {offset}-"):
127
+ raise ValueError("server returned a mismatched byte range")
128
+ length = int(response.headers.get("Content-Length", "0"))
129
+ if offset + length > max_file_bytes or (expected_size is not None and expected_size > max_file_bytes):
130
+ raise FileTooLarge(f"file exceeds per-file limit: {rel}")
131
+ if expected_size is not None and offset + length and length and expected_size != offset + length:
132
+ raise ValueError(f"unexpected remote size for {rel}")
133
+ with part.open("ab" if offset else "wb") as f:
134
+ for chunk in response.iter_content(1024 * 1024):
135
+ if not chunk: continue
136
+ if f.tell() + len(chunk) > max_file_bytes: raise FileTooLarge("stream exceeded per-file limit")
137
+ self._claim(len(chunk))
138
+ try: f.write(chunk)
139
+ except Exception:
140
+ with self.lock: self.bytes -= len(chunk)
141
+ raise
142
+ with self.lock: self.current[rel] = {"bytes": f.tell(), "expected": expected_size or offset + length or None}
143
+ self.save()
144
+ if expected_size is not None and part.stat().st_size != expected_size: raise ValueError(f"truncated file: {rel}")
145
+ if length and part.stat().st_size != offset + length: raise ValueError(f"truncated HTTP body: {rel}")
146
+ sha = self.hash(part)
147
+ if checksum:
148
+ alg, value = checksum.split(":", 1)
149
+ actual = sha if alg == "sha256" else self.hash(part, alg)
150
+ if actual != value: raise ValueError(f"checksum mismatch: {rel}; partial retained for inspection")
151
+ os.replace(part, path)
152
+ self._record({"path": rel, "bytes": path.stat().st_size, "sha256": sha, "source_url": url,
153
+ "publisher_checksum": checksum, "completed": time.time()})
154
+ with self.lock: self.current.pop(rel, None)
155
+ self.save(force=True)
156
+ return path
157
+ except (AccessBlocked, DiskBudget, FileTooLarge): raise
158
+ except Exception:
159
+ if attempt + 1 == retries: raise
160
+ time.sleep(min(2**attempt, 8))
161
+ raise AssertionError("unreachable")
162
+
163
+ @staticmethod
164
+ def hash(path, alg="sha256"):
165
+ h = hashlib.new(alg)
166
+ with Path(path).open("rb") as f:
167
+ for chunk in iter(lambda: f.read(4 * 1024 * 1024), b""): h.update(chunk)
168
+ return h.hexdigest()
169
+
170
+ def batch(self, jobs: list[dict], workers=3):
171
+ errors = []
172
+ with ThreadPoolExecutor(workers) as pool:
173
+ futures = {pool.submit(self.get, **job): job["rel"] for job in jobs}
174
+ for future in as_completed(futures):
175
+ try: future.result()
176
+ except Exception as e:
177
+ errors.append({"path": futures[future], "error": exception_summary(e)})
178
+ if isinstance(e, DiskBudget):
179
+ for f in futures: f.cancel()
180
+ return errors
quasar/data/preprocessing.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Corpus download (pinned + licensed), text pretokenization, dataset validation and statistics.
2
+
3
+ Every external corpus is listed in SOURCES with a pinned revision and its license, and the
4
+ provenance record is written next to the downloaded files so caches can be audited later.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ import json
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ from PIL import Image
14
+
15
+ SOURCES: dict[str, dict] = {
16
+ "tinystories": {
17
+ "repo_id": "roneneldan/TinyStories",
18
+ "repo_type": "dataset",
19
+ "revision": "f54c09fd23315a6f9c86f9dc80f725de7d8f9c64",
20
+ "files": [
21
+ "data/train-00000-of-00004-2d5a1467fff1081b.parquet",
22
+ "data/validation-00000-of-00001-869c898b519ad725.parquet",
23
+ ],
24
+ "license": "CDLA-Sharing-1.0",
25
+ "url": "https://huggingface.co/datasets/roneneldan/TinyStories",
26
+ "notes": "Synthetic children's stories (GPT-3.5/4 generated). Only 1 of 4 train shards is used.",
27
+ },
28
+ "dolly": {
29
+ "repo_id": "databricks/databricks-dolly-15k",
30
+ "repo_type": "dataset",
31
+ "revision": "bdd27f4d94b9c1f951818a7da7fd7aeea5dbff1a",
32
+ "files": ["databricks-dolly-15k.jsonl"],
33
+ "license": "CC-BY-SA-3.0",
34
+ "url": "https://huggingface.co/datasets/databricks/databricks-dolly-15k",
35
+ "notes": "Human-written instruction/response pairs; used for instruction tuning only.",
36
+ },
37
+ }
38
+
39
+
40
+ def sha256_file(path: str | Path) -> str:
41
+ h = hashlib.sha256()
42
+ with open(path, "rb") as f:
43
+ for chunk in iter(lambda: f.read(1 << 20), b""):
44
+ h.update(chunk)
45
+ return h.hexdigest()
46
+
47
+
48
+ def download_sources(raw_dir: str | Path, names: list[str] | None = None) -> dict[str, list[Path]]:
49
+ """Download the pinned corpus files and write a provenance.json record."""
50
+ from huggingface_hub import hf_hub_download
51
+
52
+ raw_dir = Path(raw_dir)
53
+ out: dict[str, list[Path]] = {}
54
+ for name in names or list(SOURCES):
55
+ src = SOURCES[name]
56
+ dest = raw_dir / name
57
+ paths = [
58
+ Path(hf_hub_download(src["repo_id"], f, repo_type=src["repo_type"], revision=src["revision"], local_dir=dest))
59
+ for f in src["files"]
60
+ ]
61
+ record = {k: v for k, v in src.items()} | {"sha256": {p.name: sha256_file(p) for p in paths}}
62
+ (dest / "provenance.json").write_text(json.dumps(record, indent=2))
63
+ out[name] = paths
64
+ return out
65
+
66
+
67
+ def load_tinystories(raw_dir: str | Path, split: str) -> list[str]:
68
+ import pyarrow.parquet as pq
69
+
70
+ files = sorted((Path(raw_dir) / "tinystories" / "data").glob(f"{split}-*.parquet"))
71
+ if not files:
72
+ raise FileNotFoundError(f"No TinyStories {split} parquet under {raw_dir}; run scripts/generate_dataset.py download")
73
+ texts: list[str] = []
74
+ for f in files:
75
+ texts += [t.strip() for t in pq.read_table(f, columns=["text"]).column("text").to_pylist() if t and t.strip()]
76
+ return texts
77
+
78
+
79
+ def load_dolly(raw_dir: str | Path) -> list[dict]:
80
+ rows = []
81
+ with open(Path(raw_dir) / "dolly" / "databricks-dolly-15k.jsonl", encoding="utf-8") as f:
82
+ for line in f:
83
+ r = json.loads(line)
84
+ if r["instruction"].strip() and r["response"].strip():
85
+ rows.append(r)
86
+ return rows
87
+
88
+
89
+ def tokenize_stream(texts: list[str], encode_batch, prefix: list[int], eos_id: int, out_path: str | Path) -> int:
90
+ """Concatenate documents as <prefix> doc [EOS] ... into one uint16 token stream (for packing)."""
91
+ out_path = Path(out_path)
92
+ out_path.parent.mkdir(parents=True, exist_ok=True)
93
+ n = 0
94
+ with open(out_path, "wb") as f:
95
+ for i in range(0, len(texts), 4096):
96
+ arrs = [np.array([*prefix, *ids, eos_id], dtype=np.uint16) for ids in encode_batch(texts[i : i + 4096])]
97
+ buf = np.concatenate(arrs)
98
+ buf.tofile(f)
99
+ n += len(buf)
100
+ return n
101
+
102
+
103
+ def validate_sequence(tokens: np.ndarray, vocab) -> None:
104
+ """Raise ValueError if a token sequence violates the unified-vocabulary structure rules."""
105
+ vocab.check_structure(tokens)
106
+
107
+
108
+ def dataset_stats(cache_dir: str | Path) -> dict:
109
+ """Summarise every cache file (token counts / sample counts) for docs and the dashboard."""
110
+ cache_dir = Path(cache_dir)
111
+ stats: dict[str, dict] = {}
112
+ for p in sorted(cache_dir.glob("*.bin")):
113
+ stats[p.name] = {"tokens": p.stat().st_size // 2}
114
+ for p in sorted(cache_dir.glob("*.npz")):
115
+ with np.load(p) as z:
116
+ stats[p.name] = {k: list(z[k].shape) for k in z.files}
117
+ return stats
118
+
119
+
120
+ # ----------------------------------------------------------------------------- media loading
121
+ def prepare_image(img, res: int = 64) -> np.ndarray:
122
+ """PIL / array / path -> uint8 [res,res,3] (centre square crop, area resize)."""
123
+ if isinstance(img, (str, Path)):
124
+ img = Image.open(img)
125
+ if isinstance(img, np.ndarray):
126
+ img = Image.fromarray(img.astype(np.uint8))
127
+ img = img.convert("RGB")
128
+ w, h = img.size
129
+ s = min(w, h)
130
+ img = img.crop(((w - s) // 2, (h - s) // 2, (w - s) // 2 + s, (h - s) // 2 + s))
131
+ return np.asarray(img.resize((res, res), Image.BOX if s >= res else Image.BICUBIC))
132
+
133
+
134
+ def load_video(src, max_frames: int = 8, fps: float = 4.0, res: int = 64) -> np.ndarray:
135
+ """Video file (mp4/gif/webm...) or frame list -> uint8 [T,res,res,3], resampled to ~`fps`."""
136
+ if not isinstance(src, (str, Path)):
137
+ return np.stack([prepare_image(f, res) for f in list(src)[:max_frames]])
138
+ import imageio_ffmpeg
139
+
140
+ reader = imageio_ffmpeg.read_frames(str(src))
141
+ meta = next(reader)
142
+ w, h = meta["size"]
143
+ stride = max(1, round(float(meta.get("fps") or fps) / fps))
144
+ frames = []
145
+ for i, raw in enumerate(reader):
146
+ if i % stride == 0:
147
+ frames.append(prepare_image(np.frombuffer(raw, np.uint8).reshape(h, w, 3), res))
148
+ if len(frames) == max_frames:
149
+ break
150
+ reader.close()
151
+ if not frames:
152
+ raise ValueError(f"could not decode any frames from {src}")
153
+ return np.stack(frames)
154
+
155
+
156
+ def video_windows(path: str | Path, n_frames: int = 8, fps: float = 4.0, max_windows: int = 4, res: int = 64) -> list[np.ndarray]:
157
+ """Cut a video file into up to `max_windows` consecutive `n_frames`-frame clips at ~`fps`.
158
+ Clips too short for one window are sampled evenly at native frame rate; < n_frames frames -> []."""
159
+ clip = load_video(path, max_frames=n_frames * max_windows, fps=fps, res=res)
160
+ if len(clip) >= n_frames:
161
+ return [clip[i : i + n_frames] for i in range(0, len(clip) - n_frames + 1, n_frames)]
162
+ native = load_video(path, max_frames=10_000, fps=1e9, res=res) # every frame
163
+ if len(native) < n_frames:
164
+ return []
165
+ return [native[np.linspace(0, len(native) - 1, n_frames).round().astype(int)]]
166
+
167
+
168
+ # ----------------------------------------------------------------------------- user datasets
169
+ IMAGE_EXT = {".png", ".jpg", ".jpeg", ".webp", ".bmp"}
170
+ VIDEO_EXT = {".mp4", ".mov", ".webm", ".mkv", ".avi", ".gif"}
171
+ USER_KEYS = ("user", "instruction", "prompt", "question", "input")
172
+ ASSISTANT_KEYS = ("assistant", "response", "output", "answer", "completion")
173
+
174
+
175
+ def scan_user_dataset(root: str | Path) -> dict[str, list]:
176
+ """Collect samples from a user-provided folder or .jsonl file, routing records by their keys.
177
+
178
+ JSONL records (paths relative to the .jsonl file):
179
+ {"text": ...} -> text corpus
180
+ {"image": p, "caption": ...} -> image-caption (t2i + i2t)
181
+ {"image": p, "target_image": p, "instruction": ...} -> image editing (i2i)
182
+ {"video": p, "caption": ...} (caption optional) -> video-caption (t2v, v2t) / continuation
183
+ {"instruction"|"prompt"|"user": ..., "response"|"output"|"assistant": ...} -> instruction tuning
184
+ {"messages": [{"role": "user", ...}, {"role": "assistant", ...}, ...]} -> instruction tuning
185
+ Folders: *.txt -> text documents; image/video files with a same-name .txt sidecar -> captioned
186
+ media (images without a caption are skipped; videos without one are kept for continuation).
187
+ """
188
+ root = Path(root)
189
+ out: dict[str, list] = {"texts": [], "images": [], "edits": [], "videos": [], "chats": [], "skipped": []}
190
+ files = [root] if root.is_file() else sorted(p for p in root.rglob("*") if p.is_file())
191
+ sidecar = {p.with_suffix(".txt") for p in files if p.suffix.lower() in IMAGE_EXT | VIDEO_EXT}
192
+ for p in files:
193
+ if p.suffix.lower() == ".jsonl":
194
+ for ln, line in enumerate(p.read_text(encoding="utf-8", errors="replace").splitlines()):
195
+ if not line.strip():
196
+ continue
197
+ try:
198
+ _route(json.loads(line), p.parent, out)
199
+ except (ValueError, KeyError, TypeError) as e:
200
+ out["skipped"].append(f"{p.name}:{ln + 1}: {e}")
201
+ # media referenced by a manifest record is not also scanned as a loose file
202
+ referenced = {Path(x).resolve() for k in ("images", "videos") for x, _ in out[k]} | {Path(x).resolve() for e in out["edits"] for x in e[:2]}
203
+ for p in files:
204
+ ext = p.suffix.lower()
205
+ if ext == ".jsonl" or p.resolve() in referenced:
206
+ continue
207
+ if ext == ".txt" and p not in sidecar:
208
+ t = p.read_text(encoding="utf-8", errors="replace").strip()
209
+ if t:
210
+ out["texts"].append(t)
211
+ elif ext in IMAGE_EXT | VIDEO_EXT:
212
+ cap_p = p.with_suffix(".txt")
213
+ cap = cap_p.read_text(encoding="utf-8", errors="replace").strip() if cap_p.exists() else ""
214
+ if ext in VIDEO_EXT:
215
+ out["videos"].append((p, cap))
216
+ elif cap:
217
+ out["images"].append((p, cap))
218
+ else:
219
+ out["skipped"].append(f"{p.name}: image without caption")
220
+ return out
221
+
222
+
223
+ def _route(r: dict, base: Path, out: dict) -> None:
224
+ get = lambda keys: next((str(r[k]).strip() for k in keys if r.get(k)), "")
225
+ if "messages" in r:
226
+ msgs = r["messages"]
227
+ for a, b in zip(msgs, msgs[1:]):
228
+ if a.get("role") == "user" and b.get("role") == "assistant" and a.get("content") and b.get("content"):
229
+ out["chats"].append((str(a["content"]).strip(), str(b["content"]).strip()))
230
+ elif "video" in r:
231
+ out["videos"].append((base / r["video"], get(("caption", "text", "prompt"))))
232
+ elif "image" in r and ("target_image" in r or "edited_image" in r):
233
+ out["edits"].append((base / r["image"], base / (r.get("target_image") or r["edited_image"]), get(("instruction", "edit", "prompt"))))
234
+ elif "image" in r:
235
+ cap = get(("caption", "text", "prompt"))
236
+ if not cap:
237
+ raise ValueError("image record without caption")
238
+ out["images"].append((base / r["image"], cap))
239
+ elif get(USER_KEYS) and get(ASSISTANT_KEYS):
240
+ user = get(USER_KEYS) + (("\n\n" + str(r["context"]).strip()) if r.get("context") else "")
241
+ out["chats"].append((user, get(ASSISTANT_KEYS)))
242
+ elif get(("text", "content", "story")):
243
+ out["texts"].append(get(("text", "content", "story")))
244
+ else:
245
+ raise ValueError(f"unrecognised record keys {sorted(r)}")
246
+
247
+
248
+ def split_of(key: str, val_pct: float) -> str:
249
+ """Deterministic train/val assignment from a content hash (stable across runs and machines)."""
250
+ return "val" if int(hashlib.sha1(key.encode("utf-8", "replace")).hexdigest()[:8], 16) % 10_000 < val_pct * 100 else "train"
quasar/data/synthetic.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Procedural images and videos with exact ground truth, plus an analyzer that measures them.
2
+
3
+ Everything here is generated locally (no external data, no license questions). The same
4
+ attribute vocabulary is used for rendering, captioning and for *measuring* model outputs, so
5
+ prompt-following and motion can be scored quantitatively instead of by eyeballing samples.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from dataclasses import dataclass, field, replace
11
+
12
+ import numpy as np
13
+ from PIL import Image, ImageDraw
14
+
15
+ RES = 64
16
+ SS = 4 # supersampling factor for anti-aliased edges
17
+
18
+ COLORS: dict[str, tuple[int, int, int]] = {
19
+ "red": (220, 40, 40), "green": (40, 180, 60), "blue": (50, 90, 235), "yellow": (240, 220, 50),
20
+ "purple": (150, 60, 200), "orange": (245, 140, 30), "white": (240, 240, 240), "cyan": (40, 210, 220),
21
+ }
22
+ BACKGROUNDS: dict[str, tuple[int, int, int]] = {
23
+ "black": (15, 15, 15), "gray": (105, 105, 105), "navy": (20, 30, 85), "brown": (85, 55, 30),
24
+ }
25
+ SHAPES = ("circle", "square", "triangle")
26
+ SIZES = {"small": 5.0, "medium": 9.0, "large": 13.0} # half-extent in pixels
27
+ GRID = { # name -> (col, row) in a 3x3 layout
28
+ "top left": (0, 0), "top": (1, 0), "top right": (2, 0),
29
+ "left": (0, 1), "center": (1, 1), "right": (2, 1),
30
+ "bottom left": (0, 2), "bottom": (1, 2), "bottom right": (2, 2),
31
+ }
32
+ CELL_CENTERS = (16.0, 32.0, 48.0)
33
+ POS_PHRASE = {
34
+ "top left": "in the top left", "top": "at the top", "top right": "in the top right",
35
+ "left": "on the left", "center": "in the center", "right": "on the right",
36
+ "bottom left": "in the bottom left", "bottom": "at the bottom", "bottom right": "in the bottom right",
37
+ }
38
+ # (shape, color) pairs never shown during training: compositional-generalisation probe.
39
+ HOLDOUT_COMBOS = {("triangle", "purple"), ("circle", "orange"), ("square", "cyan")}
40
+
41
+ MOTIONS = ("left", "right", "up", "down", "bounce", "cw", "ccw", "grow", "shrink", "still", "collide")
42
+ MOTION_PHRASE = {
43
+ "left": "moving left", "right": "moving right", "up": "moving up", "down": "moving down",
44
+ "cw": "rotating clockwise", "ccw": "rotating counterclockwise", "grow": "growing",
45
+ "shrink": "shrinking", "still": "standing still",
46
+ }
47
+ DIR_VEC = {"left": (-1, 0), "right": (1, 0), "up": (0, -1), "down": (0, 1)}
48
+ SHAPE_WORDS = {"circle": ["circle", "ball", "dot"], "square": ["square", "box"], "triangle": ["triangle"]}
49
+
50
+
51
+ @dataclass
52
+ class Obj:
53
+ shape: str
54
+ color: str
55
+ half: float
56
+ cx: float
57
+ cy: float
58
+ angle: float = 0.0 # degrees, clockwise on screen
59
+
60
+ @property
61
+ def size_name(self) -> str:
62
+ return min(SIZES, key=lambda k: abs(SIZES[k] - self.half))
63
+
64
+
65
+ @dataclass
66
+ class ImageSpec:
67
+ obj: Obj
68
+ bg: str
69
+ pos: str
70
+
71
+
72
+ @dataclass
73
+ class VideoSpec:
74
+ frames: list[list[Obj]] # per frame, list of objects
75
+ bg: str
76
+ motion: str
77
+ hdir: str = "" # horizontal drift for "bounce"
78
+ meta: dict = field(default_factory=dict)
79
+
80
+
81
+ # ----------------------------------------------------------------------------- rendering
82
+ def _polygon(o: Obj) -> list[tuple[float, float]]:
83
+ h = o.half
84
+ if o.shape == "square":
85
+ pts = [(-h, -h), (h, -h), (h, h), (-h, h)]
86
+ else: # triangle: apex up, base at bottom, fills half of its bounding square
87
+ pts = [(0.0, -h), (h, h), (-h, h)]
88
+ a = math.radians(o.angle)
89
+ c, s = math.cos(a), math.sin(a)
90
+ # y points down, so this standard rotation appears clockwise on screen for positive angles
91
+ return [((x * c - y * s + o.cx) * SS, (x * s + y * c + o.cy) * SS) for x, y in pts]
92
+
93
+
94
+ def render(objs: list[Obj], bg: str) -> np.ndarray:
95
+ """Render objects onto a background; returns uint8 [64, 64, 3]."""
96
+ im = Image.new("RGB", (RES * SS, RES * SS), BACKGROUNDS[bg])
97
+ d = ImageDraw.Draw(im)
98
+ for o in objs:
99
+ if o.shape == "circle":
100
+ d.ellipse([(o.cx - o.half) * SS, (o.cy - o.half) * SS, (o.cx + o.half) * SS, (o.cy + o.half) * SS], fill=COLORS[o.color])
101
+ else:
102
+ d.polygon(_polygon(o), fill=COLORS[o.color])
103
+ return np.asarray(im.resize((RES, RES), Image.BOX))
104
+
105
+
106
+ def render_video(spec: VideoSpec) -> np.ndarray:
107
+ return np.stack([render(objs, spec.bg) for objs in spec.frames])
108
+
109
+
110
+ # ----------------------------------------------------------------------------- sampling
111
+ def _combo_ok(shape: str, color: str, holdout: str) -> bool:
112
+ """holdout: 'exclude' (training), 'only' (generalisation probe) or 'any'."""
113
+ held = (shape, color) in HOLDOUT_COMBOS
114
+ return holdout == "any" or (held if holdout == "only" else not held)
115
+
116
+
117
+ def _pick_shape_color(rng: np.random.Generator, holdout: str, shapes=SHAPES) -> tuple[str, str]:
118
+ while True:
119
+ shape, color = str(rng.choice(shapes)), str(rng.choice(list(COLORS)))
120
+ if _combo_ok(shape, color, holdout):
121
+ return shape, color
122
+
123
+
124
+ def sample_image_spec(rng: np.random.Generator, holdout: str = "exclude") -> ImageSpec:
125
+ shape, color = _pick_shape_color(rng, holdout)
126
+ size = str(rng.choice(list(SIZES)))
127
+ pos = str(rng.choice(list(GRID)))
128
+ return _place(shape, color, size, pos, str(rng.choice(list(BACKGROUNDS))), rng)
129
+
130
+
131
+ def _place(shape: str, color: str, size: str, pos: str, bg: str, rng: np.random.Generator) -> ImageSpec:
132
+ half = SIZES[size]
133
+ col, row = GRID[pos]
134
+ j = lambda: float(rng.uniform(-3, 3))
135
+ cx = float(np.clip(CELL_CENTERS[col] + j(), half + 1, RES - half - 1))
136
+ cy = float(np.clip(CELL_CENTERS[row] + j(), half + 1, RES - half - 1))
137
+ return ImageSpec(Obj(shape, color, half, cx, cy), bg, pos)
138
+
139
+
140
+ def make_image_spec(shape: str, color: str, size: str, pos: str, bg: str, seed: int = 0) -> ImageSpec:
141
+ return _place(shape, color, size, pos, bg, np.random.default_rng(seed))
142
+
143
+
144
+ EDITS = ("color", "move", "size", "background", "shape")
145
+
146
+
147
+ def sample_edit(rng: np.random.Generator, src: ImageSpec) -> tuple[str, ImageSpec, str]:
148
+ """Pick an edit; returns (edit_type, target_spec, canonical instruction)."""
149
+ o = src.obj
150
+ while True:
151
+ kind = str(rng.choice(EDITS))
152
+ if kind == "color":
153
+ c = str(rng.choice([c for c in COLORS if c != o.color]))
154
+ if _combo_ok(o.shape, c, "exclude"):
155
+ return kind, replace(src, obj=replace(o, color=c)), f"change the color to {c}"
156
+ elif kind == "shape":
157
+ s = str(rng.choice([s for s in SHAPES if s != o.shape]))
158
+ if _combo_ok(s, o.color, "exclude"):
159
+ return kind, replace(src, obj=replace(o, shape=s)), f"turn it into a {s}"
160
+ elif kind == "size":
161
+ z = str(rng.choice([z for z in SIZES if z != o.size_name]))
162
+ t = _place(o.shape, o.color, z, src.pos, src.bg, rng)
163
+ return kind, replace(t, obj=replace(t.obj, cx=float(np.clip(o.cx, SIZES[z] + 1, RES - SIZES[z] - 1)),
164
+ cy=float(np.clip(o.cy, SIZES[z] + 1, RES - SIZES[z] - 1)))), f"make it {z}"
165
+ elif kind == "move":
166
+ p = str(rng.choice([p for p in GRID if p != src.pos]))
167
+ return kind, _place(o.shape, o.color, o.size_name, p, src.bg, rng), f"move it to the {p}"
168
+ else:
169
+ b = str(rng.choice([b for b in BACKGROUNDS if b != src.bg]))
170
+ return kind, replace(src, bg=b), f"change the background to {b}"
171
+
172
+
173
+ def sample_video_spec(rng: np.random.Generator, n_frames: int = 8, holdout: str = "exclude", motion: str | None = None) -> VideoSpec:
174
+ motion = motion or str(rng.choice(MOTIONS))
175
+ bg = str(rng.choice(list(BACKGROUNDS)))
176
+ T = n_frames
177
+ if motion == "collide":
178
+ # exclude: neither ball is a held-out combo; only: at least one is (only orange circles are held out)
179
+ ok = lambda a, b: holdout == "any" or (("circle", a) in HOLDOUT_COMBOS or ("circle", b) in HOLDOUT_COMBOS) == (holdout == "only")
180
+ c1, c2 = rng.choice([c for c in COLORS], size=2, replace=False)
181
+ while not ok(str(c1), str(c2)):
182
+ c1, c2 = rng.choice([c for c in COLORS], size=2, replace=False)
183
+ half, v = 6.0, float(rng.uniform(3, 4.5))
184
+ y = float(rng.uniform(16, 48))
185
+ x1, x2 = float(rng.uniform(8, 14)), float(rng.uniform(50, 56))
186
+ frames = []
187
+ v1, v2 = v, -v
188
+ for _ in range(T):
189
+ frames.append([Obj("circle", str(c1), half, x1, y), Obj("circle", str(c2), half, x2, y)])
190
+ x1, x2 = x1 + v1, x2 + v2
191
+ if x2 - x1 <= 2 * half: # elastic collision: swap velocities
192
+ v1, v2 = v2, v1
193
+ x1, x2 = x1 + 2 * v1, x2 + 2 * v2
194
+ return VideoSpec(frames, bg, motion, meta={"colors": [str(c1), str(c2)]})
195
+
196
+ shapes = ("circle",) if motion == "bounce" else ("square", "triangle") if motion in ("cw", "ccw") else SHAPES
197
+ shape, color = _pick_shape_color(rng, holdout, shapes)
198
+ size = str(rng.choice(["small", "medium"] if motion in ("left", "right", "up", "down", "bounce") else list(SIZES)))
199
+ half = SIZES[size]
200
+ lo, hi = half + 1, RES - half - 1
201
+ frames: list[list[Obj]] = []
202
+ hdir = ""
203
+ if motion in DIR_VEC:
204
+ dx, dy = DIR_VEC[motion]
205
+ v = float(rng.uniform(3, 5))
206
+ travel = v * (T - 1)
207
+ start = lambda d: float(rng.uniform(lo, hi - travel)) if d > 0 else float(rng.uniform(lo + travel, hi)) if d < 0 else float(rng.uniform(lo, hi))
208
+ x, y = start(dx), start(dy)
209
+ frames = [[Obj(shape, color, half, x + dx * v * t, y + dy * v * t)] for t in range(T)]
210
+ elif motion == "bounce":
211
+ vx = float(rng.uniform(1.5, 3)) * (1 if rng.random() < 0.5 else -1)
212
+ hdir = "right" if vx > 0 else "left"
213
+ x = float(rng.uniform(lo, hi - 7 * abs(vx))) if vx > 0 else float(rng.uniform(lo + 7 * abs(vx), hi))
214
+ y, vy, g = float(rng.uniform(lo, 26)), 0.0, float(rng.uniform(2.5, 4.0))
215
+ for _ in range(T):
216
+ frames.append([Obj(shape, color, half, x, y)])
217
+ for _ in range(4): # substeps for a clean bounce
218
+ vy += g / 4
219
+ y += vy / 4
220
+ if y > hi:
221
+ y, vy = 2 * hi - y, -0.9 * vy
222
+ x += vx
223
+ elif motion in ("cw", "ccw"):
224
+ step = float(rng.uniform(12, 22)) * (1 if motion == "cw" else -1)
225
+ x, y, a0 = float(rng.uniform(lo, hi)), float(rng.uniform(lo, hi)), float(rng.uniform(0, 90))
226
+ frames = [[Obj(shape, color, half, x, y, a0 + step * t)] for t in range(T)]
227
+ elif motion in ("grow", "shrink"):
228
+ a, b = (4.0, 14.0) if motion == "grow" else (14.0, 4.0)
229
+ x, y = float(rng.uniform(15, 49)), float(rng.uniform(15, 49))
230
+ frames = [[Obj(shape, color, a + (b - a) * t / (T - 1), x, y)] for t in range(T)]
231
+ else: # still
232
+ x, y = float(rng.uniform(lo, hi)), float(rng.uniform(lo, hi))
233
+ frames = [[Obj(shape, color, half, x, y)] for _ in range(T)]
234
+ return VideoSpec(frames, bg, motion, hdir)
235
+
236
+
237
+ def render_clutter(rng: np.random.Generator) -> np.ndarray:
238
+ """Tokenizer-only augmentation: 1-6 shapes in arbitrary RGB colours on a random gradient.
239
+
240
+ Broadens what the visual codec can represent beyond the 8-colour palette (e.g. user uploads).
241
+ """
242
+ c0, c1 = rng.integers(0, 256, 3), rng.integers(0, 256, 3)
243
+ t = np.linspace(0, 1, RES * SS)[:, None, None] if rng.random() < 0.5 else np.linspace(0, 1, RES * SS)[None, :, None]
244
+ base = (c0 * (1 - t) + c1 * t).astype(np.uint8)
245
+ im = Image.fromarray(np.broadcast_to(base, (RES * SS, RES * SS, 3)).copy())
246
+ d = ImageDraw.Draw(im)
247
+ for _ in range(int(rng.integers(1, 7))):
248
+ o = Obj(str(rng.choice(SHAPES)), "red", float(rng.uniform(3, 20)), float(rng.uniform(0, RES)), float(rng.uniform(0, RES)), float(rng.uniform(0, 360)))
249
+ fill = tuple(int(v) for v in rng.integers(0, 256, 3))
250
+ if o.shape == "circle":
251
+ d.ellipse([(o.cx - o.half) * SS, (o.cy - o.half) * SS, (o.cx + o.half) * SS, (o.cy + o.half) * SS], fill=fill)
252
+ else:
253
+ d.polygon(_polygon(o), fill=fill)
254
+ return np.asarray(im.resize((RES, RES), Image.BOX))
255
+
256
+
257
+ # ----------------------------------------------------------------------------- captions
258
+ def caption_image(spec: ImageSpec, rng: np.random.Generator | None = None) -> str:
259
+ """Canonical caption when rng is None (used as a target); otherwise a random paraphrase (prompt)."""
260
+ o = spec.obj
261
+ if rng is None:
262
+ return f"a {o.size_name} {o.color} {o.shape} {POS_PHRASE[spec.pos]} on a {spec.bg} background"
263
+ w = str(rng.choice(SHAPE_WORDS[o.shape]))
264
+ templates = [
265
+ f"a {o.size_name} {o.color} {w} {POS_PHRASE[spec.pos]} on a {spec.bg} background",
266
+ f"a {o.color} {w} {POS_PHRASE[spec.pos]} on a {spec.bg} background, {o.size_name}",
267
+ f"{spec.bg} background with a {o.size_name} {o.color} {w} {POS_PHRASE[spec.pos]}",
268
+ f"an image of a {o.size_name} {o.color} {w} {POS_PHRASE[spec.pos]}, {spec.bg} background",
269
+ f"a {o.size_name} {o.color} {w} {POS_PHRASE[spec.pos]} on a {spec.bg} background",
270
+ ]
271
+ return str(rng.choice(templates))
272
+
273
+
274
+ def caption_video(spec: VideoSpec, rng: np.random.Generator | None = None) -> str:
275
+ if spec.motion == "collide":
276
+ c1, c2 = spec.meta["colors"]
277
+ return f"a {c1} circle and a {c2} circle colliding on a {spec.bg} background"
278
+ o = spec.frames[0][0]
279
+ size = "medium" if spec.motion in ("grow", "shrink") else o.size_name
280
+ if spec.motion == "bounce":
281
+ name = "ball" if rng is None else str(rng.choice(["ball", "circle"]))
282
+ what = f"a {size} {o.color} {name} bouncing to the {spec.hdir}"
283
+ else:
284
+ name = o.shape if rng is None else str(rng.choice(SHAPE_WORDS[o.shape]))
285
+ what = f"a {size} {o.color} {name} {MOTION_PHRASE[spec.motion]}"
286
+ if spec.motion in ("grow", "shrink"):
287
+ what = f"a {o.color} {name} {MOTION_PHRASE[spec.motion]}"
288
+ if rng is None or rng.random() < 0.6:
289
+ return f"{what} on a {spec.bg} background"
290
+ return str(rng.choice([f"{what}", f"video of {what} on a {spec.bg} background", f"{spec.bg} background, {what}"]))
291
+
292
+
293
+ def parse_caption(text: str) -> dict:
294
+ """Extract attribute words from (generated) caption text; missing attributes map to None."""
295
+ t = " " + text.lower().replace(",", " ") + " "
296
+ find = lambda opts: next((o for o in sorted(opts, key=len, reverse=True) if f" {o} " in t), None)
297
+ shape = find(["circle", "square", "triangle", "ball", "box", "dot"])
298
+ shape = {"ball": "circle", "dot": "circle", "box": "square"}.get(shape, shape)
299
+ pos = next((p for p, ph in sorted(POS_PHRASE.items(), key=lambda kv: -len(kv[1])) if ph in t), None)
300
+ bg = next((b for b in BACKGROUNDS if f"{b} background" in t), None)
301
+ motion = next((m for m, ph in MOTION_PHRASE.items() if ph in t), None)
302
+ if " bouncing" in t:
303
+ motion = "bounce"
304
+ if " colliding" in t:
305
+ motion = "collide"
306
+ color = next((w for w in t.split() if w in COLORS), None) # fg and bg colour names never overlap
307
+ return {"shape": shape, "color": color, "size": find(list(SIZES)), "pos": pos, "bg": bg, "motion": motion}
308
+
309
+
310
+ # ----------------------------------------------------------------------------- analysis
311
+ def _nearest(rgb: np.ndarray, table: dict[str, tuple[int, int, int]]) -> str:
312
+ names = list(table)
313
+ d = ((np.array([table[n] for n in names], dtype=np.float32) - rgb.astype(np.float32)) ** 2).sum(-1)
314
+ return names[int(d.argmin())]
315
+
316
+
317
+ def analyze_image(img: np.ndarray, min_area: int = 12) -> dict:
318
+ """Measure background, dominant object colour/shape/size/position of a uint8 [H,W,3] image."""
319
+ img = np.asarray(img)
320
+ border = np.concatenate([img[0], img[-1], img[:, 0], img[:, -1]]).astype(np.float32)
321
+ bg_rgb = np.median(border, axis=0)
322
+ bg = _nearest(bg_rgb, BACKGROUNDS)
323
+ dist = np.sqrt(((img.astype(np.float32) - bg_rgb) ** 2).sum(-1))
324
+ mask = dist > 70
325
+ area = int(mask.sum())
326
+ out = {"bg": bg, "area": area, "color": None, "shape": None, "size": None, "pos": None, "cx": None, "cy": None}
327
+ if area < min_area:
328
+ return out
329
+ ys, xs = np.nonzero(mask)
330
+ w, h = xs.max() - xs.min() + 1, ys.max() - ys.min() + 1
331
+ cx, cy = (xs.min() + xs.max()) / 2.0, (ys.min() + ys.max()) / 2.0 # bbox centre (triangle mass centroid sits low)
332
+ fill = area / float(w * h)
333
+ out.update(
334
+ color=_nearest(np.median(img[mask], axis=0), COLORS),
335
+ shape="square" if fill > 0.88 else "circle" if fill > 0.69 else "triangle",
336
+ size=min(SIZES, key=lambda k: abs(SIZES[k] - max(w, h) / 2)),
337
+ cx=cx, cy=cy,
338
+ pos=next(p for p, (c, r) in GRID.items() if c == min(2, int(cx // (RES / 3))) and r == min(2, int(cy // (RES / 3)))),
339
+ )
340
+ return out
341
+
342
+
343
+ def analyze_video(frames: np.ndarray) -> dict:
344
+ """Per-frame analysis + trajectory summary for single-object clips."""
345
+ per = [analyze_image(f) for f in frames]
346
+ pts = [(p["cx"], p["cy"]) for p in per if p["cx"] is not None]
347
+ diffs = [float(np.abs(frames[i + 1].astype(np.float32) - frames[i].astype(np.float32)).mean()) for i in range(len(frames) - 1)]
348
+ out = {"frames": per, "detected": len(pts), "frame_diffs": diffs}
349
+ if len(pts) >= 2:
350
+ out["dx"], out["dy"] = pts[-1][0] - pts[0][0], pts[-1][1] - pts[0][1]
351
+ areas = [p["area"] for p in per if p["cx"] is not None]
352
+ out["area_ratio"] = areas[-1] / max(1, areas[0])
353
+ return out
354
+
355
+
356
+ def motion_direction(dx: float, dy: float, min_travel: float = 6.0) -> str:
357
+ """Classify a displacement as left/right/up/down/still."""
358
+ if max(abs(dx), abs(dy)) < min_travel:
359
+ return "still"
360
+ if abs(dx) >= abs(dy):
361
+ return "right" if dx > 0 else "left"
362
+ return "down" if dy > 0 else "up"
363
+
364
+
365
+ def _self_check() -> None:
366
+ rng = np.random.default_rng(0)
367
+ ok = 0
368
+ for _ in range(200):
369
+ s = sample_image_spec(rng)
370
+ a = analyze_image(render([s.obj], s.bg))
371
+ ok += (a["color"], a["shape"], a["pos"], a["bg"], a["size"]) == (s.obj.color, s.obj.shape, s.pos, s.bg, s.obj.size_name)
372
+ print(f"analyzer exact-match on ground truth renders: {ok}/200")
373
+ p = parse_caption(caption_image(s))
374
+ assert (p["shape"], p["color"], p["pos"], p["bg"], p["size"]) == (s.obj.shape, s.obj.color, s.pos, s.bg, s.obj.size_name), p
375
+ for m in ("left", "right", "up", "down"):
376
+ v = sample_video_spec(rng, motion=m)
377
+ a = analyze_video(render_video(v))
378
+ assert motion_direction(a["dx"], a["dy"]) == m, (m, a["dx"], a["dy"])
379
+ assert parse_caption(caption_video(v))["motion"] == m
380
+
381
+
382
+ if __name__ == "__main__":
383
+ _self_check()
quasar/data/text_preparation.py ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bounded offline preparation of the initial real text/chat/Python corpus.
2
+
3
+ SQLite spools documents and deduplication keys instead of retaining a corpus in
4
+ RAM. No downloads, archive extraction, GPU calls, or optimizer steps occur here.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from collections import Counter
9
+ from copy import deepcopy
10
+ import hashlib
11
+ import json
12
+ import os
13
+ from pathlib import Path
14
+ import shutil
15
+ import sqlite3
16
+ import time
17
+ from urllib.parse import urlsplit, urlunsplit
18
+
19
+ import numpy as np
20
+ import pyarrow.parquet as pq
21
+ import yaml
22
+
23
+ from .adapters import normalize
24
+ from .curated import canonical, digest, verify_record
25
+ from .preprocessing import sha256_file
26
+ from ..tokenizers.text import TextTokenizer
27
+ from ..tokenizers.universal import UniversalVocab
28
+
29
+
30
+ def atomic_json(path: Path, value: dict) -> None:
31
+ path.parent.mkdir(parents=True, exist_ok=True)
32
+ tmp = path.with_name(path.name + ".tmp")
33
+ tmp.write_text(json.dumps(value, indent=2, ensure_ascii=False), encoding="utf-8")
34
+ for attempt in range(8):
35
+ try:
36
+ os.replace(tmp, path)
37
+ return
38
+ except PermissionError:
39
+ if attempt == 7:
40
+ raise
41
+ time.sleep(0.01 * 2**attempt)
42
+
43
+
44
+ def normalized_hash(text: str) -> str:
45
+ return hashlib.sha256(" ".join(text.split()).encode("utf-8")).hexdigest()
46
+
47
+
48
+ def document_group(url: str, text: str) -> str:
49
+ """Group HTTP/HTTPS aliases and fragments of the same document together."""
50
+ if not url:
51
+ return normalized_hash(text)
52
+ p = urlsplit(url)
53
+ return urlunsplit(("https", p.netloc.lower(), p.path.rstrip("/"), p.query, ""))
54
+
55
+
56
+ def parquet_rows(path: Path):
57
+ for batch in pq.ParquetFile(path).iter_batches(batch_size=128):
58
+ yield from batch.to_pylist()
59
+
60
+
61
+ def pinned_source_sha(path: Path, root: Path, dataset: str, revision: str, ledger: dict) -> str:
62
+ """Starter downloads retain pinned LFS hashes in HF's local metadata file."""
63
+ rel = path.relative_to(root).as_posix()
64
+ if rel in ledger:
65
+ expected = ledger[rel]["sha256"]
66
+ else:
67
+ relative = path.relative_to(root / dataset)
68
+ metadata = root / dataset / ".cache/huggingface/download" / (str(relative) + ".metadata")
69
+ lines = metadata.read_text(encoding="utf-8").splitlines()
70
+ if len(lines) < 2 or lines[0] != revision:
71
+ raise ValueError(f"missing or mismatched pinned acquisition metadata: {rel}")
72
+ expected = lines[1]
73
+ if len(expected) != 64 or any(c not in "0123456789abcdef" for c in expected):
74
+ raise ValueError(f"source has no pinned SHA256: {rel}")
75
+ return expected
76
+
77
+
78
+ def text_windows(text: str, tok: TextTokenizer, limit: int):
79
+ """Segment at Unicode character/whitespace boundaries; never cut byte tokens."""
80
+ remaining = text.strip()
81
+ while remaining:
82
+ encoded = tok.tok.encode(remaining)
83
+ if len(encoded.ids) <= limit:
84
+ yield remaining
85
+ return
86
+ cut = encoded.offsets[limit - 1][1]
87
+ # Prefer a nearby complete word while retaining at least ~75% capacity.
88
+ lower = encoded.offsets[max(0, limit * 3 // 4 - 1)][1]
89
+ boundary = max(remaining.rfind(" ", lower, cut), remaining.rfind("\n", lower, cut))
90
+ if boundary > lower:
91
+ cut = boundary
92
+ while cut > 0 and len(tok.encode(remaining[:cut])) > limit:
93
+ cut -= 1
94
+ if cut <= 0:
95
+ raise ValueError("cannot segment text within context")
96
+ yield remaining[:cut].strip()
97
+ remaining = remaining[cut:].strip()
98
+
99
+
100
+ def text_sample(record: dict, tok: TextTokenizer, vocab: UniversalVocab, context: int):
101
+ """Exact UniversalVocab text packet layout, validated in bulk with NumPy."""
102
+ s = vocab.special
103
+ ids = [s("bos"), s("task:" + record["task"]), s("input")]
104
+ for segment in record["inputs"]:
105
+ ids.extend([s("text"), *tok.encode(segment["text"]), s("/text")])
106
+ ids.append(s("output"))
107
+ target = len(ids)
108
+ for segment in record["outputs"]:
109
+ ids.extend([s("text"), *tok.encode(segment["text"]), s("/text")])
110
+ ids.append(s("eos"))
111
+ tokens = np.array(ids, dtype="<u2")
112
+ validate_text_sample(tokens, target, record["task"], vocab, context)
113
+ return tokens, target
114
+
115
+
116
+ def validate_text_sample(tokens: np.ndarray, target: int, task: str, v: UniversalVocab, context: int) -> None:
117
+ """Strict text-only subset of the universal grammar; reserved IDs are rejected."""
118
+ s = v.special
119
+ if task not in ("text", "chat_text", "coding") or not 7 <= len(tokens) <= context + 1:
120
+ raise ValueError("invalid task or context length")
121
+ if tokens[:3].tolist() != [s("bos"), s("task:" + task), s("input")] or tokens[-1] != s("eos"):
122
+ raise ValueError("invalid sequence boundary")
123
+ if not 4 <= target < len(tokens) or tokens[target - 1] != s("output"):
124
+ raise ValueError("invalid supervision boundary")
125
+ for start, end, required in ((3, target - 1, task != "text"), (target, len(tokens) - 1, True)):
126
+ count = 0
127
+ while start < end:
128
+ if tokens[start] != s("text"):
129
+ raise ValueError("invalid text packet start")
130
+ closes = np.flatnonzero(tokens[start + 1:end] >= v.text_size)
131
+ if not len(closes):
132
+ raise ValueError("unterminated text packet")
133
+ close = start + 1 + int(closes[0])
134
+ if tokens[close] != s("/text"):
135
+ raise ValueError("non-text token in text packet")
136
+ start, count = close + 1, count + 1
137
+ if required and count == 0:
138
+ raise ValueError("empty packet side")
139
+
140
+
141
+ class TextPreparation:
142
+ def __init__(self, config: dict, status_path: Path | None = None):
143
+ self.cfg, self.status_path = config, status_path
144
+ self.work, self.out = Path(config["work"]), Path(config["output"])
145
+ if self.work.exists() or self.out.exists():
146
+ raise FileExistsError("preserve existing preparation/cache; inspect it before selecting a new work directory")
147
+ self.guard()
148
+ self.work.mkdir(parents=True)
149
+ self.catalog = yaml.safe_load(Path(config["catalog"]).read_text(encoding="utf-8"))
150
+ self.catalog = deepcopy(self.catalog)
151
+ self.counts, self.source_files = Counter(), []
152
+ self.started = time.time()
153
+ self.db = sqlite3.connect(self.work / "records.sqlite")
154
+ self.db.executescript("""
155
+ PRAGMA journal_mode=WAL;
156
+ PRAGMA cache_size=-32768;
157
+ CREATE TABLE records (id INTEGER PRIMARY KEY, dataset TEXT, split TEXT, payload TEXT);
158
+ CREATE TABLE groups (dataset TEXT, name TEXT, split TEXT, PRIMARY KEY(dataset,name));
159
+ CREATE TABLE contents (hash TEXT PRIMARY KEY, split TEXT);
160
+ """)
161
+
162
+ def guard(self):
163
+ anchor = self.work if self.work.exists() else self.work.parent
164
+ if shutil.disk_usage(anchor).free < self.cfg["reserve_gib"] * 2**30:
165
+ raise RuntimeError("25 GiB free-space reserve reached")
166
+ if self.work.exists():
167
+ used = sum(p.stat().st_size for p in self.work.rglob("*") if p.is_file())
168
+ if self.out.exists():
169
+ used += sum(p.stat().st_size for p in self.out.rglob("*") if p.is_file())
170
+ if used > self.cfg["max_work_gib"] * 2**30:
171
+ raise RuntimeError("bounded preparation disk budget reached")
172
+
173
+ def progress(self, phase: str, **fields):
174
+ self.guard()
175
+ record = {"phase": phase, "pid": os.getpid(), "updated": time.time(), "elapsed_s": time.time() - self.started,
176
+ "training_started": False, "counts": dict(self.counts), **fields}
177
+ atomic_json(self.work / "progress.json", record)
178
+ if self.status_path:
179
+ state = json.loads(self.status_path.read_text(encoding="utf-8")) if self.status_path.exists() else {}
180
+ state.update(state="preparing_data", updated=time.time(),
181
+ message="CPU data preparation: " + phase, preprocessing_elapsed_s=record["elapsed_s"])
182
+ atomic_json(self.status_path, state)
183
+ print(json.dumps({k: v for k, v in record.items() if k != "counts"}), flush=True)
184
+
185
+ def source(self, path: Path, expected: str | None = None) -> str:
186
+ sha = sha256_file(path)
187
+ if expected and sha != expected:
188
+ raise ValueError(f"source checksum mismatch: {path}")
189
+ self.source_files.append({"path": str(path), "bytes": path.stat().st_size, "sha256": sha})
190
+ return sha
191
+
192
+ def provenance(self, ds: str, ident: str, split: str, path: Path, sha: str, source_split: str, **extra):
193
+ d = self.catalog["datasets"][ds]
194
+ return {"id": ds + ":" + ident, "revision": d.get("revision", d.get("release")), "license": d["license"],
195
+ "attribution": d.get("attribution", d.get("repo_id", ds)) + "; see pinned dataset card and retained source metadata",
196
+ "source_url": d["source"], "source_file": str(path), "source_sha256": sha,
197
+ "source_split": source_split, "split": split, **extra}
198
+
199
+ def add(self, record: dict) -> bool:
200
+ verify_record(record, self.catalog)
201
+ if any(not segment.get("text", "").strip() for segment in record["inputs"] + record["outputs"]):
202
+ self.counts["empty_text_excluded"] += 1
203
+ return False
204
+ key = normalized_hash("\n".join(x["text"] for x in record["inputs"] + record["outputs"]))
205
+ old = self.db.execute("SELECT split FROM contents WHERE hash=?", (key,)).fetchone()
206
+ group = (record["dataset"], str(record["group"]))
207
+ prior = self.db.execute("SELECT split FROM groups WHERE dataset=? AND name=?", group).fetchone()
208
+ if old or (prior and prior[0] != record["split"]):
209
+ self.counts["cross_split_duplicates_excluded" if (old and old[0] != record["split"]) or
210
+ (prior and prior[0] != record["split"]) else "duplicates_excluded"] += 1
211
+ return False
212
+ self.db.execute("INSERT INTO contents VALUES (?,?)", (key, record["split"]))
213
+ self.db.execute("INSERT OR IGNORE INTO groups VALUES (?,?,?)", (*group, record["split"]))
214
+ record["document_content_sha256"] = key
215
+ self.db.execute("INSERT INTO records(dataset,split,payload) VALUES (?,?,?)",
216
+ (record["dataset"], record["split"], canonical(record)))
217
+ self.counts[f"documents/{record['split']}/{record['dataset']}"] += 1
218
+ return True
219
+
220
+ def acquired_sources(self):
221
+ root = Path(self.cfg["acquired"])
222
+ ledger = {}
223
+ for line in (root / "files.jsonl").open(encoding="utf-8"):
224
+ r = json.loads(line)
225
+ ledger[r["path"]] = r
226
+ for ds in ("mbpp", "smoltalk", "fineweb_edu"):
227
+ for path in sorted((root / ds).rglob("*.parquet")):
228
+ expected = pinned_source_sha(path, root, ds, self.catalog["datasets"][ds]["revision"], ledger)
229
+ self.source(path, expected)
230
+ hashes = {r["path"]: r["sha256"] for r in self.source_files}
231
+ # Held-out MBPP precedes train so duplicate solutions cannot contaminate it.
232
+ for split, source_split in (("test", "test"), ("val", "validation"), ("train", "train")):
233
+ p = next((root / "mbpp/full").glob(source_split + "-*.parquet"))
234
+ for row in parquet_rows(p):
235
+ prov = self.provenance("mbpp", str(row["task_id"]), split, p, hashes[str(p)], source_split)
236
+ try:
237
+ self.add(normalize("mbpp", row, prov)[0])
238
+ except SyntaxError:
239
+ self.counts["mbpp_syntax_rejected/" + split] += 1
240
+ self.db.commit()
241
+ for source_split, split, capkey in (("test", "val", "smoltalk_val_per_subset"),
242
+ ("train", "train", "smoltalk_train_per_subset")):
243
+ for subset in self.catalog["datasets"]["smoltalk"]["subsets"]:
244
+ accepted = 0
245
+ for p in sorted((root / "smoltalk/data" / subset).glob(source_split + "-*.parquet")):
246
+ for i, row in enumerate(parquet_rows(p)):
247
+ prov = self.provenance("smoltalk", f"{subset}/{p.name}/{i}", split, p, hashes[str(p)], source_split,
248
+ component=subset)
249
+ try:
250
+ accepted += self.add(normalize("smoltalk", row, prov)[0])
251
+ except (ValueError, KeyError, TypeError):
252
+ self.counts["smoltalk_invalid"] += 1
253
+ if accepted >= self.cfg[capkey]:
254
+ break
255
+ if accepted >= self.cfg[capkey]:
256
+ break
257
+ self.db.commit()
258
+ self.progress("source_selection", component=subset, split=split, accepted=accepted)
259
+ # FineWeb source budget is publisher-token based; actual BPE budget is applied later.
260
+ source_tokens = 0
261
+ for p in sorted((root / "fineweb_edu").rglob("*.parquet")):
262
+ for i, row in enumerate(parquet_rows(p)):
263
+ if row.get("language") != "en" or float(row["score"]) < self.cfg["fineweb_min_score"]:
264
+ self.counts["fineweb_quality_rejected"] += 1
265
+ continue
266
+ text = row["text"].strip()
267
+ if not text or len(text) > 500000:
268
+ self.counts["fineweb_size_rejected"] += 1
269
+ continue
270
+ group = document_group(row.get("url", ""), text)
271
+ split = "val" if int(digest(group)[:8], 16) % 100 < 2 else "train"
272
+ prov = self.provenance("fineweb_edu", str(row["id"]), split, p, hashes[str(p)], "sample-10BT",
273
+ original_url=row.get("url"), crawl_dump=row.get("dump"), warc_file=row.get("file_path"),
274
+ educational_score=row["score"])
275
+ record = normalize("fineweb_edu", row, prov)[0]
276
+ record["group"] = group
277
+ if self.add(record):
278
+ source_tokens += int(row["token_count"])
279
+ if i % 2000 == 0:
280
+ self.db.commit()
281
+ self.progress("source_selection", component="fineweb_edu", publisher_tokens=source_tokens)
282
+ if source_tokens >= self.cfg["fineweb_source_tokens"]:
283
+ break
284
+ if source_tokens >= self.cfg["fineweb_source_tokens"]:
285
+ break
286
+ self.db.commit()
287
+
288
+ def legacy_sources(self):
289
+ raw, cache = Path(self.cfg["legacy_raw"]), Path(self.cfg["legacy_cache"])
290
+ stats = json.loads((cache / "stats.json").read_text())
291
+ for ds in ("tinystories", "dolly"):
292
+ p = raw / ds / "provenance.json"
293
+ src = json.loads(p.read_text())
294
+ self.source(p)
295
+ self.catalog["datasets"][ds] = {"role": "core", "repo_id": src["repo_id"], "revision": src["revision"],
296
+ "license": src["license"], "source": src["url"], "tasks": ["text" if ds == "tinystories" else "chat_text"],
297
+ "attribution": src["repo_id"], "notes": src["notes"], "retained_builtin": True}
298
+ if src["license"] not in self.catalog["policy"]["allowed_asset_licenses"]:
299
+ self.catalog["policy"]["allowed_asset_licenses"].append(src["license"])
300
+ for filename in src["files"]:
301
+ self.source(raw / ds / filename, src["sha256"][Path(filename).name])
302
+ for split, source_split in (("val", "validation"), ("train", "train")):
303
+ p = next((raw / "tinystories/data").glob(source_split + "-*.parquet"))
304
+ sha = next(r["sha256"] for r in self.source_files if r["path"] == str(p))
305
+ for i, row in enumerate(parquet_rows(p)):
306
+ if i >= stats["text"][split]["documents"]:
307
+ break
308
+ text = row["text"].strip()
309
+ self.add({**self.provenance("tinystories", f"{source_split}/{i}", split, p, sha, source_split),
310
+ "dataset": "tinystories", "task": "text", "group": normalized_hash(text),
311
+ "inputs": [], "outputs": [{"type": "text", "text": text}]})
312
+ # Preserve the actual historical Dolly split and pairs, not a new random split.
313
+ for split in ("val", "train"):
314
+ p = cache / f"chat_{split}.json"
315
+ sha = self.source(p)
316
+ for i, row in enumerate(json.loads(p.read_text())):
317
+ self.add({**self.provenance("dolly", f"legacy-{split}/{i}", split, p, sha, "legacy-" + split),
318
+ "dataset": "dolly", "task": "chat_text", "group": normalized_hash(row["user"]),
319
+ "inputs": [{"type": "text", "text": row["user"]}],
320
+ "outputs": [{"type": "text", "text": row["assistant"]}]})
321
+ self.db.commit()
322
+
323
+ def fit_tokenizer(self):
324
+ fitted = Counter()
325
+ def corpus():
326
+ for ds, cap in self.cfg["tokenizer_characters"].items():
327
+ for (payload,) in self.db.execute("SELECT payload FROM records WHERE dataset=? AND split='train' ORDER BY id", (ds,)):
328
+ r = json.loads(payload)
329
+ for segment in r["inputs"] + r["outputs"]:
330
+ text = segment["text"][:cap - fitted[ds]]
331
+ if text:
332
+ fitted[ds] += len(text)
333
+ yield text
334
+ if fitted[ds] >= cap:
335
+ break
336
+ self.progress("fit_training_only_bpe")
337
+ tok = TextTokenizer.train(corpus(), self.cfg["text_vocab_size"])
338
+ if tok.vocab_size != self.cfg["text_vocab_size"]:
339
+ raise ValueError("BPE did not reach the exact shared vocabulary size")
340
+ tok.save(self.work / "text_tokenizer.json")
341
+ atomic_json(self.work / "tokenizer_fit.json", {"split": "train", "characters_by_dataset": dict(fitted),
342
+ "sha256": sha256_file(self.work / "text_tokenizer.json")})
343
+ return tok
344
+
345
+ def compile(self, tok: TextTokenizer):
346
+ v, context = UniversalVocab(), self.cfg["context"]
347
+ if tok.vocab_size != v.text_size:
348
+ raise ValueError("text vocabulary mismatch")
349
+ self.out.mkdir()
350
+ (self.out / "INCOMPLETE").write_text("awaiting complete validation")
351
+ handles, offsets, seen = {}, Counter(), {}
352
+ manifest_hash = hashlib.sha256()
353
+ targets, lengths, sources = Counter(), Counter(), Counter()
354
+ fineweb_train_tokens, total = 0, 0
355
+ for split in ("train", "val", "test"):
356
+ handles[split] = ((self.out / f"{split}.bin").open("wb"), (self.out / f"{split}.index.jsonl").open("w", encoding="utf-8"))
357
+ try:
358
+ with (self.out / "provenance.jsonl").open("w", encoding="utf-8") as provenance:
359
+ for row_id, payload in self.db.execute("SELECT id,payload FROM records ORDER BY id"):
360
+ manifest_hash.update((payload + "\n").encode("utf-8"))
361
+ r = json.loads(payload)
362
+ chunks = text_windows(r["outputs"][0]["text"], tok, context - 6) if r["task"] == "text" else [r["outputs"][0]["text"]]
363
+ for chunk_id, chunk in enumerate(chunks):
364
+ candidate = r | {"outputs": [{"type": "text", "text": chunk}]}
365
+ try:
366
+ tokens, target = text_sample(candidate, tok, v, context)
367
+ except ValueError as exc:
368
+ if r["task"] == "text" or "context length" not in str(exc):
369
+ raise
370
+ self.counts[f"over_context_rejected/{r['split']}/{r['dataset']}"] += 1
371
+ continue
372
+ if r["dataset"] == "fineweb_edu" and r["split"] == "train":
373
+ if fineweb_train_tokens + len(tokens) - target > self.cfg["fineweb_train_tokens"]:
374
+ self.counts["fineweb_bpe_budget_excluded"] += 1
375
+ continue
376
+ content_key = normalized_hash("\n".join(x["text"] for x in candidate["inputs"] + candidate["outputs"]))
377
+ if content_key in seen:
378
+ self.counts["window_duplicates_excluded"] += 1
379
+ continue
380
+ seen[content_key] = r["split"]
381
+ split, task = r["split"], r["task"]
382
+ ident = r["id"] + f"/window-{chunk_id}"
383
+ idx = {"offset": offsets[split], "length": len(tokens), "target_start": target,
384
+ "task": task, "id": ident, "dataset": r["dataset"], "group": r["group"], "split": split,
385
+ "document_content_sha256": r["document_content_sha256"], "content_sha256": content_key,
386
+ "tokens_sha256": hashlib.sha256(tokens.tobytes()).hexdigest()}
387
+ tokens.tofile(handles[split][0]); handles[split][1].write(canonical(idx) + "\n")
388
+ prov = {k: val for k, val in r.items() if k not in ("inputs", "outputs")}
389
+ prov.update(id=ident, window=chunk_id, token_offset=offsets[split], token_length=len(tokens),
390
+ tokens_sha256=idx["tokens_sha256"], content_sha256=content_key)
391
+ provenance.write(canonical(prov) + "\n")
392
+ offsets[split] += len(tokens)
393
+ self.counts[f"samples/{split}/{task}"] += 1
394
+ targets[f"{split}/{task}"] += len(tokens) - target
395
+ sources[f"{split}/{r['dataset']}"] += 1
396
+ lengths[str(min(context, max(128, 1 << (len(tokens) - 2).bit_length())))] += 1
397
+ if r["dataset"] == "fineweb_edu" and split == "train":
398
+ fineweb_train_tokens += len(tokens) - target
399
+ total += 1
400
+ if row_id % 1000 == 0:
401
+ self.progress("encode_cache", documents=row_id, samples=total, fineweb_train_targets=fineweb_train_tokens)
402
+ finally:
403
+ for pair in handles.values():
404
+ for f in pair:
405
+ f.close()
406
+ tok.save(self.out / "text_tokenizer.json")
407
+ atomic_json(self.out / "vocab_v2.json", v.to_dict())
408
+ atomic_json(self.out / "catalog.json", self.catalog)
409
+ report = {"training_started": False, "records": total, "context": context, "counts": dict(self.counts),
410
+ "target_tokens_by_task": dict(targets), "samples_by_dataset": dict(sources), "bucket_lengths": dict(lengths),
411
+ "input_tokens_by_split": dict(offsets), "token_bytes": sum(offsets.values()) * 2,
412
+ "sources": self.catalog["datasets"], "source_files": self.source_files,
413
+ "tokenizer_fit": json.loads((self.work / "tokenizer_fit.json").read_text()),
414
+ "user_data_included": False, "media_included": False,
415
+ "limitations": ["exact whitespace-normalized dedup only; near-duplicate/template overlap is unmeasured",
416
+ "over-context chat/coding examples are rejected, never truncated",
417
+ "FineWeb page rights remain governed by underlying sources; crawl URLs retained"],
418
+ "elapsed_preparation_s": time.time() - self.started}
419
+ report["normalized_manifest_sha256"] = manifest_hash.hexdigest()
420
+ atomic_json(self.out / "stats.json", report)
421
+ self.progress("validate_cache", samples=total)
422
+ validation = validate_cache(self.out)
423
+ atomic_json(self.out / "validation.json", validation)
424
+ checksums = {p.name: sha256_file(p) for p in self.out.iterdir() if p.is_file() and p.name != "INCOMPLETE"}
425
+ atomic_json(self.out / "checksums.json", checksums)
426
+ (self.out / "INCOMPLETE").unlink()
427
+ self.progress("complete", samples=total, validation_passed=True)
428
+ return report
429
+
430
+ def run(self):
431
+ try:
432
+ self.progress("verify_sources")
433
+ self.legacy_sources()
434
+ self.acquired_sources()
435
+ return self.compile(self.fit_tokenizer())
436
+ finally:
437
+ self.db.close()
438
+
439
+
440
+ def validate_cache(cache: Path, require_tasks: bool = True, expected_vocab: UniversalVocab | None = None) -> dict:
441
+ cache = Path(cache)
442
+ v = UniversalVocab(**json.loads((cache / "vocab_v2.json").read_text()))
443
+ if v.to_dict() != (expected_vocab or UniversalVocab()).to_dict():
444
+ raise ValueError("not the exact expected UniversalVocab")
445
+ tok = TextTokenizer.load(cache / "text_tokenizer.json")
446
+ if tok.vocab_size != v.text_size:
447
+ raise ValueError("tokenizer vocabulary mismatch")
448
+ stats = json.loads((cache / "stats.json").read_text())
449
+ groups, contents, documents, identities = {}, {}, {}, {}
450
+ counts, total = Counter(), 0
451
+ for split in ("train", "val", "test"):
452
+ p = cache / f"{split}.bin"
453
+ if p.stat().st_size % 2:
454
+ raise ValueError("misaligned uint16 stream")
455
+ tokens = np.memmap(p, dtype="<u2", mode="r") if p.stat().st_size else np.array([], dtype="<u2")
456
+ offset = 0
457
+ with (cache / f"{split}.index.jsonl").open(encoding="utf-8") as index:
458
+ for line in index:
459
+ r = json.loads(line)
460
+ if r["offset"] != offset or offset + r["length"] > len(tokens) or r["split"] != split:
461
+ raise ValueError("invalid index offset/length/split")
462
+ sample = tokens[offset:offset + r["length"]]
463
+ if r["task"] in ("text", "chat_text", "coding"):
464
+ validate_text_sample(sample, r["target_start"], r["task"], v, stats["context"])
465
+ else:
466
+ if len(sample)-1 > stats["context"] or not 0 < r["target_start"] < len(sample):
467
+ raise ValueError("invalid multimodal context or target boundary")
468
+ if int(sample[r["target_start"]-1]) != v.special("output"):
469
+ raise ValueError("multimodal targets must start after the output marker")
470
+ v.validate(sample.tolist())
471
+ if hashlib.sha256(sample.tobytes()).hexdigest() != r["tokens_sha256"]:
472
+ raise ValueError("sample token checksum mismatch")
473
+ for key, store in ((r["dataset"] + ":" + r["group"], groups),
474
+ (r["content_sha256"], contents), (r["document_content_sha256"], documents)):
475
+ if key in store and store[key] != split:
476
+ raise ValueError("train/heldout content or source-group leakage")
477
+ store[key] = split
478
+ if r["id"] in identities:
479
+ raise ValueError("duplicate sample identity")
480
+ identities[r["id"]] = (split, r["tokens_sha256"], r["dataset"], r["group"])
481
+ offset += r["length"]; counts[split + "/" + r["task"]] += 1; total += 1
482
+ if offset != len(tokens):
483
+ raise ValueError("unindexed trailing tokens")
484
+ catalog = json.loads((cache / "catalog.json").read_text())
485
+ checked = set()
486
+ with (cache / "provenance.jsonl").open(encoding="utf-8") as f:
487
+ for line in f:
488
+ r = json.loads(line)
489
+ key = r["id"]
490
+ if key in checked or identities.get(key) != (r["split"], r["tokens_sha256"], r["dataset"], r["group"]):
491
+ raise ValueError("provenance/index mismatch")
492
+ ds = catalog["datasets"][r["dataset"]]
493
+ if r["revision"] != ds.get("revision", ds.get("release")) or r["license"] != ds["license"]:
494
+ raise ValueError("provenance identity/license mismatch")
495
+ if not all(r.get(k) for k in ("attribution", "source_url", "source_file", "source_sha256")):
496
+ raise ValueError("missing source provenance")
497
+ if r["dataset"] == "mbpp" and {"train":"train","validation":"val","test":"test"}.get(r["source_split"]) != r["split"]:
498
+ raise ValueError("MBPP heldout partition changed")
499
+ checked.add(key)
500
+ if len(checked) != total or total != stats["records"]:
501
+ raise ValueError("provenance/statistics count mismatch")
502
+ if require_tasks:
503
+ for split in ("train", "val"):
504
+ for task in ("text", "chat_text", "coding"):
505
+ if not counts[split + "/" + task]:
506
+ raise ValueError("missing required task coverage")
507
+ checksums = cache / "checksums.json"
508
+ if checksums.exists():
509
+ for name, sha in json.loads(checksums.read_text()).items():
510
+ if Path(name).name != name or sha256_file(cache / name) != sha:
511
+ raise ValueError("cache file checksum mismatch: " + name)
512
+ return {"passed": True, "records": total, "counts": dict(counts), "source_groups": len(groups),
513
+ "content_leakage": 0, "context": stats["context"], "vocab_size": v.size,
514
+ "validated_at": time.time(), "training_started": False}
quasar/evaluation/__init__.py ADDED
File without changes
quasar/inference/__init__.py ADDED
File without changes
quasar/inference/decoding.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Constrained decoding: a per-sequence structure state machine + token sampling.
2
+
3
+ The state machine guarantees every generated media block is well formed: inside <image> only
4
+ visual codes are allowed and exactly G of them; inside a video, frames are
5
+ <frame><t_k> G codes </frame> with consecutive timestamps; text spans can only contain text
6
+ tokens. Purely structural tokens are *forced* (no sampling); content tokens are sampled from
7
+ the model's distribution restricted to the legal set.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ import torch
14
+
15
+ from ..tokenizers.multimodal import MAX_FRAMES, UnifiedVocab
16
+
17
+
18
+ @dataclass
19
+ class Sampling:
20
+ temperature: float = 1.0
21
+ top_k: int = 0
22
+ top_p: float = 1.0
23
+ repetition_penalty: float = 1.0 # applied to previously generated *text* tokens only
24
+ max_new_tokens: int = 256
25
+ seed: int = 0
26
+ cfg_scale: float = 1.0 # classifier-free guidance on visual tokens (1.0 = off)
27
+
28
+
29
+ class Masks:
30
+ """Boolean legal-token sets, precomputed once per vocabulary/device."""
31
+
32
+ def __init__(self, v: UnifiedVocab, device):
33
+ V = v.size
34
+
35
+ def mk(ids=(), ranges=()):
36
+ m = torch.zeros(V, dtype=torch.bool, device=device)
37
+ for lo, hi in ranges:
38
+ m[lo:hi] = True
39
+ if ids:
40
+ m[list(ids)] = True
41
+ return m
42
+
43
+ self.text = mk([v.eos], [(0, v.text_size)])
44
+ self.text_media = mk([v.eos, v.img_start, v.vid_start], [(0, v.text_size)])
45
+ self.visual = mk(ranges=[(v.visual_offset, v.visual_offset + v.visual_size)])
46
+ self.timestamp = mk(ranges=[(v.timestamp(0), v.timestamp(0) + MAX_FRAMES)])
47
+ self.frame_or_end = mk([v.frm_start, v.vid_end])
48
+
49
+
50
+ class StructureFSM:
51
+ """Tracks where one sequence is in the media grammar.
52
+
53
+ mode:
54
+ "text" free text until <eos> (media tags disallowed unless allow_media)
55
+ "image" we are right after <image>: G codes, </image>, then <eos> if stop_after_media
56
+ "video" we are inside <video> right before the first <frame>
57
+ n_frames: exact total frame count for videos (None = model decides, bounded by max_timestamp)
58
+ first_ts: timestamp of the next frame to generate (None = sampled by the model)
59
+ frames_done: frames already present in the prefix (prefix then ends right after a </frame>)
60
+ codes_done: image codes already present after <image> (image completion)
61
+ """
62
+
63
+ def __init__(self, v: UnifiedVocab, mode: str = "text", allow_media: bool = False, stop_after_media: bool = True,
64
+ n_frames: int | None = None, first_ts: int | None = None, max_timestamp: int = 7, frames_done: int = 0, codes_done: int = 0):
65
+ self.v, self.allow_media, self.stop_after_media = v, allow_media, stop_after_media
66
+ self.n_frames, self.next_ts, self.max_ts = n_frames, first_ts, max_timestamp
67
+ self.done = False
68
+ self.frames = frames_done
69
+ self.state = {"text": "text", "image": "img_codes", "video": "after_frame" if frames_done else "frame_start"}[mode]
70
+ self.count = codes_done if mode == "image" else 0
71
+
72
+ def constraint(self, masks: Masks) -> tuple[int | None, torch.Tensor | None]:
73
+ """(forced_token, None) or (None, legal_mask)."""
74
+ v, s = self.v, self.state
75
+ if self.done:
76
+ return v.pad, None
77
+ if s == "text":
78
+ return None, masks.text_media if self.allow_media else masks.text
79
+ if s in ("img_codes", "frm_codes"):
80
+ return None, masks.visual
81
+ if s == "img_end":
82
+ return v.img_end, None
83
+ if s == "frm_end":
84
+ return v.frm_end, None
85
+ if s == "frame_start":
86
+ return v.frm_start, None
87
+ if s == "timestamp":
88
+ return (v.timestamp(self.next_ts), None) if self.next_ts is not None else (None, masks.timestamp)
89
+ if s == "after_frame":
90
+ last = self.next_ts - 1
91
+ if (self.n_frames is not None and self.frames >= self.n_frames) or last >= self.max_ts:
92
+ return v.vid_end, None
93
+ return (v.frm_start, None) if self.n_frames is not None else (None, masks.frame_or_end)
94
+ if s == "eos":
95
+ return v.eos, None
96
+ raise AssertionError(s)
97
+
98
+ def advance(self, tok: int) -> None:
99
+ v, s = self.v, self.state
100
+ if self.done:
101
+ return
102
+ G = v.tokens_per_frame
103
+ if s == "text":
104
+ if tok == v.eos:
105
+ self.done = True
106
+ elif tok == v.img_start:
107
+ self.state, self.count = "img_codes", 0
108
+ elif tok == v.vid_start:
109
+ self.state = "frame_start"
110
+ elif s == "img_codes":
111
+ self.count += 1
112
+ if self.count == G:
113
+ self.state = "img_end"
114
+ elif s == "img_end":
115
+ self.state = "eos" if self.stop_after_media else "text"
116
+ elif s == "frame_start":
117
+ self.state = "timestamp"
118
+ elif s == "timestamp":
119
+ self.next_ts = tok - v.timestamp(0) + 1
120
+ self.state, self.count = "frm_codes", 0
121
+ elif s == "frm_codes":
122
+ self.count += 1
123
+ if self.count == G:
124
+ self.state = "frm_end"
125
+ elif s == "frm_end":
126
+ self.frames += 1
127
+ self.state = "after_frame"
128
+ elif s == "after_frame":
129
+ if tok == v.frm_start:
130
+ self.state = "timestamp"
131
+ else: # </video>
132
+ self.state = "eos" if self.stop_after_media else "text"
133
+ elif s == "eos":
134
+ self.done = True
135
+
136
+ @property
137
+ def in_visual(self) -> bool:
138
+ return self.state in ("img_codes", "frm_codes")
139
+
140
+
141
+ def sample_next(logits: torch.Tensor, legal: torch.Tensor, sp: Sampling, gen: torch.Generator,
142
+ history: torch.Tensor | None = None, text_size: int = 0) -> torch.Tensor:
143
+ """logits [B,V] (float32), legal [B,V] bool -> sampled ids [B]."""
144
+ logits = logits.float().clone()
145
+ if sp.repetition_penalty != 1.0 and history is not None and history.numel():
146
+ prev = history.clamp(min=0)
147
+ is_text = prev < text_size
148
+ vals = logits.gather(1, prev)
149
+ pen = torch.where(vals > 0, vals / sp.repetition_penalty, vals * sp.repetition_penalty)
150
+ logits.scatter_(1, prev, torch.where(is_text, pen, vals))
151
+ logits = logits.masked_fill(~legal, float("-inf"))
152
+ if sp.temperature <= 0:
153
+ return logits.argmax(-1)
154
+ logits = logits / sp.temperature
155
+ if sp.top_k > 0:
156
+ kth = logits.topk(min(sp.top_k, logits.size(-1)), dim=-1).values[:, -1:]
157
+ logits = logits.masked_fill(logits < kth, float("-inf"))
158
+ if sp.top_p < 1.0:
159
+ srt, idx = logits.sort(dim=-1, descending=True)
160
+ probs = srt.softmax(-1)
161
+ drop = probs.cumsum(-1) - probs > sp.top_p # keep the smallest prefix with mass >= top_p
162
+ logits = logits.masked_fill(drop.scatter(1, idx, drop), float("-inf"))
163
+ return torch.multinomial(logits.softmax(-1), 1, generator=gen).squeeze(1)
quasar/inference/extended.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Audio, geometry and schema-checked calls from the same shared generator.
2
+
3
+ Recorded training exposure is required. Structural masks constrain packets;
4
+ semantic usefulness must be established separately on trained checkpoints.
5
+ """
6
+ from pathlib import Path
7
+ import json
8
+ import numpy as np
9
+ import torch
10
+ from .universal import UniversalGenerator
11
+ from .decoding import Sampling, sample_next
12
+ from ..data.curated import validate_calls
13
+ from ..data.text_preparation import sha256_file
14
+ from ..tokenizers.universal import TokenGrammar
15
+ from ..tokenizers.audio_aligned import AlignedAudioTokenizer
16
+
17
+
18
+ class ExtendedGenerator(UniversalGenerator):
19
+ def require_task(self, task):
20
+ if not self.meta.get('task_target_tokens', {}).get(task):
21
+ raise RuntimeError('This checkpoint has no recorded training exposure for ' + task)
22
+
23
+ def audio(self):
24
+ if not hasattr(self, '_audio'):
25
+ identity = self.meta.get('data', {}).get('preparation', {}).get('audio_codec')
26
+ if not identity or identity.get('implementation') != 'quasar.tokenizers.audio_aligned.AlignedAudioTokenizer':
27
+ raise RuntimeError('Checkpoint has no compatible frozen audio codec identity')
28
+ path = Path(identity['path'])
29
+ if not path.is_absolute():
30
+ path = (self.path / path).resolve()
31
+ for name in ('config.json', 'model.safetensors'):
32
+ if sha256_file(path / name) != identity['sha256'][name]:
33
+ raise ValueError('Audio codec differs from training')
34
+ self._audio = AlignedAudioTokenizer(path, identity['alignment_samples'], identity['codebooks'], device=self.device)
35
+ return self._audio
36
+
37
+ def _candidates(self, grammar, kind, remaining):
38
+ v, s, state = self.vocab, self.vocab.special, grammar.state
39
+ if state == 'audio':
40
+ book = grammar.count % v.audio_books
41
+ lo = v.ranges['audio'][0] + book * v.audio_size
42
+ # Keep two boundary guards within the two-second source budget.
43
+ if grammar.count >= 152 * v.audio_books or (book == 0 and grammar.count >= 64 and remaining <= 5):
44
+ return [s('/audio')]
45
+ tokens = list(range(lo, lo + v.audio_size))
46
+ if grammar.count >= 64 and book == 0:
47
+ tokens.append(s('/audio'))
48
+ return tokens
49
+ if state == 'mesh':
50
+ return [s('vertices')]
51
+ if state == 'vertices':
52
+ if grammar.count >= 9 and grammar.count % 3 == 0 and remaining <= 8:
53
+ return [s('faces')]
54
+ return list(range(*v.ranges['coordinate'])) + [s('faces')]
55
+ if state == 'faces':
56
+ if grammar.count >= 3 and grammar.count % 3 == 0 and remaining <= 5:
57
+ return [s('/mesh')]
58
+ lo = v.ranges['index'][0]
59
+ return list(range(lo, lo + grammar.vertices)) + [s('/mesh')]
60
+ if state == 'calls':
61
+ return [s('/calls')] if remaining <= 2 else list(range(v.text_size)) + [s('/calls')]
62
+ if state == 'packets':
63
+ return [s('eos')]
64
+ raise ValueError('Unsupported generated packet state: ' + state)
65
+
66
+ @torch.inference_mode()
67
+ def packet(self, task, inputs, kind, sp):
68
+ self.require_task(task)
69
+ v, s = self.vocab, self.vocab.special
70
+ prefix = [s('bos'), s('task:' + task), s('input')]
71
+ for segment in inputs:
72
+ prefix += v.packet(segment, self.tok.encode)
73
+ prefix += [s('output'), s(kind)]
74
+ budget = min(sp.max_new_tokens, self.model.cfg.max_seq_len - len(prefix))
75
+ minimum = {'audio': 66, 'mesh': 16, 'calls': 3}[kind]
76
+ if budget < minimum:
77
+ raise ValueError('Complete packet and conditioning exceed the requested context/output budget')
78
+ grammar = TokenGrammar(v)
79
+ for token in prefix:
80
+ grammar.advance(token)
81
+ device = next(self.model.parameters()).device
82
+ amp = device.type == 'cuda'
83
+ cache = self.model.new_cache(1, dtype=torch.bfloat16 if amp else torch.float32)
84
+ rng = torch.Generator(device=device).manual_seed(sp.seed)
85
+ emitted = []
86
+ with torch.autocast(device.type, dtype=torch.bfloat16, enabled=amp):
87
+ logits = self.model(torch.tensor([prefix], device=device), cache=cache)[:, -1]
88
+ while grammar.state != 'done' and len(emitted) < budget:
89
+ candidates = [t for t in self._candidates(grammar, kind, budget - len(emitted)) if grammar.accepts(t)]
90
+ if not candidates:
91
+ raise RuntimeError('No structurally valid completion fits the packet budget')
92
+ if len(candidates) == 1:
93
+ token = candidates[0]
94
+ else:
95
+ legal = torch.zeros((1, v.size), dtype=torch.bool, device=device)
96
+ legal[:, candidates] = True
97
+ history = torch.tensor([emitted], dtype=torch.long, device=device)
98
+ token = int(sample_next(logits, legal, sp, rng, history, v.text_size)[0])
99
+ grammar.advance(token); emitted.append(token)
100
+ if grammar.state != 'done':
101
+ logits = self.model(torch.tensor([[token]], device=device), cache=cache, start=cache.next_pos)[:, -1]
102
+ if grammar.state != 'done':
103
+ raise RuntimeError('Model did not finish a valid packet within the output budget')
104
+ v.validate(prefix + emitted)
105
+ return emitted
106
+
107
+ def generate_audio(self, prompt, sp=None):
108
+ tokens = self.packet('t2a', [{'type': 'text', 'text': prompt}], 'audio',
109
+ sp or Sampling(temperature=.8, top_k=64, top_p=.9, max_new_tokens=642))
110
+ raw = np.asarray(tokens[:-2], dtype=np.int64).reshape(-1, self.vocab.audio_books)
111
+ offsets = self.vocab.ranges['audio'][0] + np.arange(self.vocab.audio_books) * self.vocab.audio_size
112
+ packet = {'type': 'audio', 'codes': (raw - offsets).tolist()}
113
+ codec = self.audio()
114
+ return codec.decode(packet), codec.sr
115
+
116
+ def describe_audio(self, waveform, sample_rate, sp=None):
117
+ self.require_task('a2t')
118
+ packet = self.audio().encode(waveform, sample_rate)
119
+ return self._predict('a2t', [packet], 'text', sp or Sampling(temperature=.5, max_new_tokens=128))[0]
120
+
121
+ def generate_mesh(self, prompt, sp=None):
122
+ t = self.packet('t2m', [{'type': 'text', 'text': prompt}], 'mesh',
123
+ sp or Sampling(temperature=.8, top_k=64, top_p=.9, max_new_tokens=780))
124
+ s = self.vocab.special
125
+ vertices = np.asarray(t[t.index(s('vertices')) + 1:t.index(s('faces'))]).reshape(-1, 3)
126
+ faces = np.asarray(t[t.index(s('faces')) + 1:t.index(s('/mesh'))]).reshape(-1, 3)
127
+ vertices -= self.vocab.ranges['coordinate'][0]; faces -= self.vocab.ranges['index'][0]
128
+ if len(np.unique(vertices, axis=0)) != len(vertices) or np.any(np.linalg.norm(
129
+ np.cross(vertices[faces[:, 1]] - vertices[faces[:, 0]], vertices[faces[:, 2]] - vertices[faces[:, 0]]), axis=1) == 0):
130
+ raise RuntimeError('Generated mesh has collapsed or degenerate geometry; semantic generation is unverified')
131
+ return {'type': 'mesh', 'vertices': vertices.tolist(), 'faces': faces.tolist()}
132
+
133
+ def describe_mesh(self, packet, sp=None):
134
+ return self._predict('m2t', [packet], 'text', sp or Sampling(temperature=.5, max_new_tokens=128))[0]
135
+
136
+ def propose_calls(self, prompt, tools, sp=None):
137
+ self.require_task('tools')
138
+ validate_calls(tools, [])
139
+ tokens = self.packet('tools', [{'type': 'tools', 'value': tools}, {'type': 'text', 'text': 'user: ' + prompt}],
140
+ 'calls', sp or Sampling(temperature=.5, max_new_tokens=256))
141
+ text = self.tok.decode([t for t in tokens if t < self.vocab.text_size])
142
+ def reject_constant(value):
143
+ raise ValueError('Non-finite JSON constant: ' + value)
144
+ result = json.loads(text, parse_constant=reject_constant)
145
+ if not isinstance(result, list):
146
+ raise ValueError('Generated calls must be a JSON list')
147
+ validate_calls(tools, result)
148
+ return result
quasar/inference/generate.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unified generation engine: every capability is one call into the same trained backbone.
2
+
3
+ eng = QuasarEngine("checkpoints/quasar-exp/instruct/final")
4
+ eng.generate_text("Once upon a time")
5
+ eng.generate_image("a large red circle in the center on a black background")
6
+ eng.generate_video("a small blue circle moving right on a navy background")
7
+ eng.describe_image(img); eng.describe_video(frames); eng.continue_video(frames)
8
+ eng.edit_image(img, "change the color to green"); eng.image_to_video(img, "... moving up ...")
9
+ eng.chat("Make a video of a blue ball bouncing.")
10
+
11
+ The visual tokenizer only converts pixels <-> tokens; all content tokens are sampled from the
12
+ Quasar transformer. Capabilities the checkpoint was never trained on raise CapabilityError
13
+ unless force=True, so untrained noise is never presented as a result.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from pathlib import Path
18
+
19
+ import numpy as np
20
+ import torch
21
+ from PIL import Image
22
+
23
+ from ..data.preprocessing import load_video, prepare_image
24
+ from ..tokenizers.text import TextTokenizer
25
+ from ..tokenizers.vision import VisualTokenizer
26
+ from ..training.checkpoint import load_quasar
27
+ from .decoding import Masks, Sampling, StructureFSM, sample_next
28
+
29
+ CAPABILITY_TASKS = {
30
+ "generate_text": ("text",), "chat": ("chat_text", "chat_mm"), "generate_image": ("t2i",), "complete_image": ("t2i",),
31
+ "describe_image": ("i2t",), "edit_image": ("i2i",), "generate_video": ("t2v",), "image_to_video": ("t2v",),
32
+ "continue_video": ("vcont",), "describe_video": ("v2t",),
33
+ }
34
+ TRAINED_FRAMES = 8 # clips in training have 8 frames (t0..t7)
35
+
36
+
37
+ class CapabilityError(RuntimeError):
38
+ pass
39
+
40
+
41
+ # ------------------------------------------------------------------------------- media io
42
+ def save_video(frames: np.ndarray, path: str | Path, fps: int = 4, scale: int = 4) -> Path:
43
+ """Write uint8 [T,H,W,3] as .mp4 (H.264, browser-playable) or .gif, upscaled `scale`x (nearest)."""
44
+ path = Path(path)
45
+ path.parent.mkdir(parents=True, exist_ok=True)
46
+ big = np.asarray(frames).repeat(scale, 1).repeat(scale, 2)
47
+ if path.suffix.lower() == ".gif":
48
+ ims = [Image.fromarray(f) for f in big]
49
+ ims[0].save(path, save_all=True, append_images=ims[1:], duration=int(1000 / fps), loop=0)
50
+ return path
51
+ import imageio_ffmpeg
52
+
53
+ T, H, W, _ = big.shape
54
+ w = imageio_ffmpeg.write_frames(str(path), (W, H), fps=fps, codec="libx264", pix_fmt_out="yuv420p", macro_block_size=1,
55
+ output_params=["-crf", "18"])
56
+ w.send(None)
57
+ for f in big:
58
+ w.send(np.ascontiguousarray(f))
59
+ w.close()
60
+ return path
61
+
62
+
63
+ def normalize_prompt(p: str) -> str:
64
+ """Training captions are lower-case without a trailing period."""
65
+ return p.strip().rstrip(".").lower()
66
+
67
+
68
+ # ------------------------------------------------------------------------------- engine
69
+ class QuasarEngine:
70
+ def __init__(self, ckpt: str | Path, device: str | None = None):
71
+ self.ckpt = Path(ckpt)
72
+ self.device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
73
+ self.model, self.vocab, self.meta = load_quasar(self.ckpt, self.device)
74
+ if getattr(self.vocab, "schema_version", 1) in (2,3):
75
+ raise CapabilityError("This is a v2 preparation checkpoint. V2 generation/codec reconstruction must be validated and trained before exposing it in the legacy demo.")
76
+ self.model.eval()
77
+ self.tok = TextTokenizer.load(self.ckpt / "tokenizers" / "text_tokenizer.json")
78
+ self.vq = VisualTokenizer.load(self.ckpt / "tokenizers" / "vq", self.device)
79
+ self.masks = Masks(self.vocab, self.device)
80
+ self.amp = torch.bfloat16 if self.device.type == "cuda" and torch.cuda.is_bf16_supported() else None
81
+
82
+ # -- capability bookkeeping ------------------------------------------------------------
83
+ def capabilities(self) -> dict[str, dict]:
84
+ tt: dict[str, int] = {}
85
+ for k, n in self.meta.get("task_target_tokens", {}).items(): # 'user/t2i' counts toward 't2i'
86
+ base = k.rpartition("/")[2]
87
+ tt[base] = tt.get(base, 0) + int(n)
88
+ return {m: {"trained": all(tt.get(t, 0) > 0 for t in ts), "target_tokens": {t: tt.get(t, 0) for t in ts}}
89
+ for m, ts in CAPABILITY_TASKS.items()}
90
+
91
+ def _require(self, method: str, force: bool) -> None:
92
+ if force:
93
+ return
94
+ cap = self.capabilities()[method]
95
+ if not cap["trained"]:
96
+ raise CapabilityError(
97
+ f"'{method}' has not been trained in this checkpoint (stage '{self.meta.get('stage')}', "
98
+ f"target tokens seen: {cap['target_tokens']}). Train a stage that includes these tasks, "
99
+ f"or pass force=True to sample the untrained model anyway.")
100
+
101
+ # -- token helpers ---------------------------------------------------------------------
102
+ def enc(self, s: str) -> list[int]:
103
+ return self.tok.encode(s)
104
+
105
+ def image_codes(self, image) -> np.ndarray:
106
+ return self.vq.encode(prepare_image(image)[None])[0]
107
+
108
+ def video_codes(self, frames) -> np.ndarray:
109
+ return self.vq.encode(load_video(frames)[None])[0]
110
+
111
+ # -- core decode loop ------------------------------------------------------------------
112
+ @torch.no_grad()
113
+ def _decode(self, prefix: list[int], fsm_kwargs: dict, n: int, sp: Sampling, uncond_prefix: list[int] | None = None) -> list[list[int]]:
114
+ m, dev = self.model, self.device
115
+ if len(prefix) >= m.cfg.max_seq_len:
116
+ raise ValueError(f"prompt is {len(prefix)} tokens; the model context is {m.cfg.max_seq_len}")
117
+ max_len = min(m.cfg.max_seq_len, len(prefix) + sp.max_new_tokens)
118
+ guided = uncond_prefix is not None and sp.cfg_scale != 1.0
119
+ amp = torch.autocast(dev.type, dtype=self.amp) if self.amp else torch.autocast(dev.type, enabled=False)
120
+
121
+ def start(pref):
122
+ cache = m.new_cache(n, max_len)
123
+ hist = torch.tensor(pref, device=dev).repeat(n, 1)
124
+ with amp:
125
+ return cache, hist, m(hist, cache=cache)[:, -1].float()
126
+
127
+ cache, hist, logits = start(prefix)
128
+ if guided:
129
+ ucache, uhist, ulogits = start(uncond_prefix)
130
+ fsms = [StructureFSM(self.vocab, **fsm_kwargs) for _ in range(n)]
131
+ gen = torch.Generator(device=dev).manual_seed(sp.seed)
132
+ out = torch.empty(n, 0, dtype=torch.long, device=dev)
133
+ pos, upos = len(prefix), len(uncond_prefix or [])
134
+ while pos < max_len and not all(f.done for f in fsms):
135
+ cons = [f.constraint(self.masks) for f in fsms]
136
+ forced = torch.tensor([c[0] if c[0] is not None else -1 for c in cons], device=dev)
137
+ if (forced >= 0).all():
138
+ nxt = forced
139
+ else:
140
+ legal = torch.stack([c[1] if c[1] is not None else self.masks.text for c in cons])
141
+ lg = logits
142
+ if guided:
143
+ vis = torch.tensor([f.in_visual for f in fsms], device=dev)[:, None]
144
+ lg = torch.where(vis, ulogits + sp.cfg_scale * (logits - ulogits), logits)
145
+ nxt = sample_next(lg, legal, sp, gen, out, self.vocab.text_size)
146
+ nxt = torch.where(forced >= 0, forced, nxt)
147
+ for f, t in zip(fsms, nxt.tolist()):
148
+ f.advance(t)
149
+ out = torch.cat([out, nxt[:, None]], 1)
150
+ hist = torch.cat([hist, nxt[:, None]], 1)
151
+ if all(f.done for f in fsms) or pos + 1 >= max_len:
152
+ break
153
+ with amp:
154
+ logits = m(nxt[:, None], vis_pos=m.visual_positions(hist)[:, -1:], cache=cache, start=pos)[:, -1].float()
155
+ if guided:
156
+ uhist = torch.cat([uhist, nxt[:, None]], 1)
157
+ ulogits = m(nxt[:, None], vis_pos=m.visual_positions(uhist)[:, -1:], cache=ucache, start=upos)[:, -1].float()
158
+ pos += 1
159
+ upos += 1
160
+ res = []
161
+ for row in out.tolist():
162
+ res.append(row[: row.index(self.vocab.eos)] if self.vocab.eos in row else row)
163
+ return res
164
+
165
+ def _text(self, ids: list[int]) -> str:
166
+ return self.tok.decode([i for i in ids if i < self.vocab.text_size]).strip()
167
+
168
+ def _visual(self, ids: list[int], kind: str) -> np.ndarray:
169
+ segs = [s for k, s in self.vocab.parse(ids) if k == kind]
170
+ if not segs:
171
+ raise RuntimeError(f"generation produced no {kind} block")
172
+ return self.vq.decode(segs[0][None])[0]
173
+
174
+ # -- public API ------------------------------------------------------------------------
175
+ def generate_text(self, prompt: str, sp: Sampling | None = None, force: bool = False) -> str:
176
+ """Continue `prompt` with the base language-model head (TinyStories-style text)."""
177
+ self._require("generate_text", force)
178
+ sp = sp or Sampling(temperature=0.8, top_k=50, top_p=0.95, repetition_penalty=1.1, max_new_tokens=200)
179
+ return self._text(self._decode([self.vocab.bos, self.vocab.task("text"), *self.enc(prompt)], {"mode": "text"}, 1, sp)[0])
180
+
181
+ def generate_image(self, prompt: str, n: int = 1, sp: Sampling | None = None, force: bool = False) -> list[np.ndarray]:
182
+ self._require("generate_image", force)
183
+ sp = sp or Sampling(temperature=1.0, top_k=100, cfg_scale=3.0)
184
+ v = self.vocab
185
+ prefix = [v.bos, v.task("t2i"), *self.enc(normalize_prompt(prompt)), v.img_start]
186
+ rows = self._decode(prefix, {"mode": "image"}, n, sp, uncond_prefix=[v.bos, v.task("t2i"), v.img_start])
187
+ return [self.vq.decode(np.array(r[: v.tokens_per_frame]).reshape(1, *v.grid) - v.visual_offset)[0] for r in rows]
188
+
189
+ def complete_image(self, image, prompt: str = "", keep_rows: int = 4, n: int = 1, sp: Sampling | None = None, force: bool = False) -> list[np.ndarray]:
190
+ """Keep the top `keep_rows` token rows of `image` and let the model fill in the rest."""
191
+ self._require("complete_image", force)
192
+ sp = sp or Sampling(temperature=1.0, top_k=100)
193
+ v = self.vocab
194
+ keep = (self.image_codes(image).reshape(-1)[: keep_rows * v.grid[1]] + v.visual_offset).tolist()
195
+ prefix = [v.bos, v.task("t2i"), *self.enc(normalize_prompt(prompt)), v.img_start, *keep]
196
+ rows = self._decode(prefix, {"mode": "image", "codes_done": len(keep)}, n, sp)
197
+ return [self.vq.decode(np.array(keep + r[: v.tokens_per_frame - len(keep)]).reshape(1, *v.grid) - v.visual_offset)[0] for r in rows]
198
+
199
+ def describe_image(self, image, sp: Sampling | None = None, force: bool = False) -> str:
200
+ self._require("describe_image", force)
201
+ sp = sp or Sampling(temperature=0.0, max_new_tokens=48)
202
+ v = self.vocab
203
+ return self._text(self._decode([v.bos, v.task("i2t"), *v.image_tokens(self.image_codes(image))], {"mode": "text"}, 1, sp)[0])
204
+
205
+ def edit_image(self, image, instruction: str, n: int = 1, sp: Sampling | None = None, force: bool = False) -> list[np.ndarray]:
206
+ self._require("edit_image", force)
207
+ sp = sp or Sampling(temperature=1.0, top_k=100)
208
+ v = self.vocab
209
+ prefix = [v.bos, v.task("i2i"), *self.enc(normalize_prompt(instruction)), *v.image_tokens(self.image_codes(image)), v.img_start]
210
+ rows = self._decode(prefix, {"mode": "image"}, n, sp)
211
+ return [self.vq.decode(np.array(r[: v.tokens_per_frame]).reshape(1, *v.grid) - v.visual_offset)[0] for r in rows]
212
+
213
+ def _check_frames(self, n_frames: int, force: bool) -> None:
214
+ if n_frames > TRAINED_FRAMES and not force:
215
+ raise CapabilityError(f"clips were trained with {TRAINED_FRAMES} frames; use continue_video to extend beyond that")
216
+
217
+ def generate_video(self, prompt: str, n_frames: int = 8, n: int = 1, sp: Sampling | None = None, force: bool = False) -> list[np.ndarray]:
218
+ self._require("generate_video", force)
219
+ self._check_frames(n_frames, force)
220
+ sp = sp or Sampling(temperature=1.0, top_k=100, cfg_scale=3.0, max_new_tokens=n_frames * (self.vocab.tokens_per_frame + 3) + 4)
221
+ v = self.vocab
222
+ prefix = [v.bos, v.task("t2v"), *self.enc(normalize_prompt(prompt)), v.vid_start]
223
+ rows = self._decode(prefix, {"mode": "video", "n_frames": n_frames, "first_ts": 0}, n, sp, uncond_prefix=[v.bos, v.task("t2v"), v.vid_start])
224
+ return [self.vq.decode(self.vocab.parse([v.vid_start, *r])[0][1]) for r in rows]
225
+
226
+ def image_to_video(self, image, prompt: str, n_frames: int = 8, n: int = 1, sp: Sampling | None = None, force: bool = False) -> list[np.ndarray]:
227
+ """Use `image` as frame 0 of a text-conditioned clip and generate the remaining frames."""
228
+ self._require("image_to_video", force)
229
+ self._check_frames(n_frames, force)
230
+ sp = sp or Sampling(temperature=1.0, top_k=100, cfg_scale=3.0, max_new_tokens=n_frames * (self.vocab.tokens_per_frame + 3) + 4)
231
+ v = self.vocab
232
+ f0 = v.frame_tokens(self.image_codes(image), 0)
233
+ prefix = [v.bos, v.task("t2v"), *self.enc(normalize_prompt(prompt)), v.vid_start, *f0]
234
+ rows = self._decode(prefix, {"mode": "video", "n_frames": n_frames, "first_ts": 1, "frames_done": 1}, n, sp,
235
+ uncond_prefix=[v.bos, v.task("t2v"), v.vid_start, *f0])
236
+ return [self.vq.decode(self.vocab.parse([v.vid_start, *f0, *r])[0][1]) for r in rows]
237
+
238
+ def continue_video(self, frames, n_new: int = 4, context: int = 4, sp: Sampling | None = None, force: bool = False) -> np.ndarray:
239
+ """Append `n_new` frames. Rolls a window of the last `context` frames (re-timestamped from t0),
240
+ so clips can be extended beyond the 8-frame training length."""
241
+ self._require("continue_video", force)
242
+ v = self.vocab
243
+ codes = self.video_codes(frames)
244
+ context = min(context, TRAINED_FRAMES - 1)
245
+ out = list(codes)
246
+ while len(out) < len(codes) + n_new:
247
+ ctx = np.stack(out[-context:])
248
+ k = len(ctx)
249
+ todo = min(TRAINED_FRAMES - k, len(codes) + n_new - len(out))
250
+ s = sp or Sampling(temperature=1.0, top_k=100, seed=len(out))
251
+ s = Sampling(**{**s.__dict__, "max_new_tokens": todo * (v.tokens_per_frame + 3) + 4})
252
+ prefix = [v.bos, v.task("vcont"), *v.video_tokens(ctx, close=False)]
253
+ r = self._decode(prefix, {"mode": "video", "n_frames": k + todo, "first_ts": k, "frames_done": k}, 1, s)[0]
254
+ new = v.parse([v.vid_start, *v.video_tokens(ctx, close=False)[1:], *r])[0][1][k:]
255
+ out += list(new)
256
+ return self.vq.decode(np.stack(out))
257
+
258
+ def describe_video(self, frames, sp: Sampling | None = None, force: bool = False) -> str:
259
+ self._require("describe_video", force)
260
+ sp = sp or Sampling(temperature=0.0, max_new_tokens=48)
261
+ v = self.vocab
262
+ return self._text(self._decode([v.bos, v.task("v2t"), *v.video_tokens(self.video_codes(frames))], {"mode": "text"}, 1, sp)[0])
263
+
264
+ def chat(self, message: str, image=None, video=None, sp: Sampling | None = None, force: bool = False) -> list[tuple[str, object]]:
265
+ """Instruction-following turn. Returns segments: ('text', str) / ('image', array) / ('video', array)."""
266
+ self._require("chat", force)
267
+ v = self.vocab
268
+ sp = sp or Sampling(temperature=0.7, top_k=50, top_p=0.95, repetition_penalty=1.1, max_new_tokens=700)
269
+ user: list[int] = []
270
+ if image is not None:
271
+ user += v.image_tokens(self.image_codes(image))
272
+ if video is not None:
273
+ user += v.video_tokens(self.video_codes(video))
274
+ user += self.enc((" " if user else "") + message.strip())
275
+ prefix = [v.bos, v.user, *user, v.assistant]
276
+ ids = self._decode(prefix, {"mode": "text", "allow_media": True, "stop_after_media": False}, 1, sp)[0]
277
+ segs: list[tuple[str, object]] = []
278
+ for kind, val in v.parse(ids):
279
+ if kind == "text":
280
+ segs.append(("text", self._text(val)))
281
+ elif kind == "image":
282
+ segs.append(("image", self.vq.decode(val[None])[0]))
283
+ else:
284
+ segs.append(("video", self.vq.decode(val)))
285
+ return segs
quasar/inference/universal.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generation for the actual v2 packet grammar and a shared Axiom checkpoint."""
2
+ from __future__ import annotations
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import torch
6
+ from ..tokenizers.text import TextTokenizer
7
+ from ..tokenizers.vision import VisualTokenizer
8
+ from ..training.checkpoint import load_quasar
9
+ from .decoding import Sampling,sample_next
10
+
11
+
12
+ class UniversalGenerator:
13
+ def __init__(self,checkpoint: str|Path,device: str='cuda'):
14
+ self.path=Path(checkpoint)
15
+ self.model,self.vocab,self.meta=load_quasar(self.path,device)
16
+ self.model.eval()
17
+ if not hasattr(self.vocab,'special'):raise ValueError('UniversalGenerator requires a packet-v2 checkpoint')
18
+ self.tok=TextTokenizer.load(self.path/'tokenizers/text_tokenizer.json')
19
+ self.codec=None;self.device=device
20
+
21
+ def vision(self):
22
+ if self.codec is None:self.codec=VisualTokenizer.load(self.path/'tokenizers/vq',self.device)
23
+ return self.codec
24
+
25
+ @torch.inference_mode()
26
+ def _predict(self,task,inputs,kind,sp: Sampling,frames=8,resolution=64):
27
+ if not self.meta.get('task_target_tokens',{}).get(task):
28
+ raise RuntimeError(f'This checkpoint has no recorded training exposure for {task}')
29
+ v=self.vocab;s=v.special
30
+ prefix=[s('bos'),s('task:'+task),s('input')]
31
+ for segment in inputs:prefix+=v.packet(segment,self.tok.encode)
32
+ prefix+=[s('output'),s(kind)]
33
+ # This is structural forcing only. All content codes/tokens are predicted by Quasar.
34
+ if kind=='image':
35
+ if resolution not in (64,128) or (resolution==128 and v.schema_version!=3):
36
+ raise ValueError('128px image generation requires a schema-v3 checkpoint')
37
+ if resolution==128:prefix += [s('resolution:128')]
38
+ plan=[('vision',(resolution//8)**2),('/image',1),('eos',1)]
39
+ elif kind=='video':
40
+ if not 1<=frames<=v.max_frames:raise ValueError('frame count exceeds vocabulary')
41
+ plan=[]
42
+ for i in range(frames):plan += [('frame',1),('time:'+str(i),1),('vision',64),('/frame',1)]
43
+ plan += [('/video',1),('eos',1)]
44
+ else:plan=[('free_text',sp.max_new_tokens)]
45
+ needed=sum(n for _,n in plan)
46
+ if len(prefix)+needed>self.model.cfg.max_seq_len:raise ValueError('requested input/output exceeds trained context')
47
+ device=next(self.model.parameters()).device;use_amp=device.type=='cuda'
48
+ cache=self.model.new_cache(1,dtype=torch.bfloat16 if use_amp else torch.float32)
49
+ gen=torch.Generator(device=device).manual_seed(sp.seed)
50
+ emitted=[];content=[];stop=False
51
+ with torch.autocast(device.type,dtype=torch.bfloat16,enabled=use_amp):
52
+ logits=self.model(torch.tensor([prefix],device=device),cache=cache)[:,-1]
53
+ for state,n in plan:
54
+ for _ in range(n):
55
+ if state in ('vision','free_text'):
56
+ legal=torch.zeros((1,v.size),dtype=torch.bool,device=device)
57
+ lo,hi=v.ranges['vision' if state=='vision' else 'text'];legal[:,lo:hi]=True
58
+ if state=='free_text':legal[:,s('/text')]=True
59
+ history=torch.tensor([content],dtype=torch.long,device=device)
60
+ token=int(sample_next(logits,legal,sp,gen,history,v.text_size)[0])
61
+ if state=='free_text' and token==s('/text'):
62
+ emitted += [token,s('eos')];stop=True;break
63
+ content.append(token)
64
+ else:token=s(state)
65
+ emitted.append(token)
66
+ # A one-token cache append cannot infer its location in the
67
+ # current visual run. Preserve the full-prefix spatial slot,
68
+ # resetting at image/frame boundaries exactly as training does.
69
+ history_tokens=torch.tensor([prefix+emitted],device=device)
70
+ position=self.model.visual_positions(history_tokens)[:,-1:]
71
+ logits=self.model(history_tokens[:,-1:],vis_pos=position,
72
+ cache=cache,start=cache.next_pos)[:,-1]
73
+ if stop:break
74
+ if kind=='text':return self.tok.decode(content),{'learned_text_stop':stop,'token_limit':sp.max_new_tokens}
75
+ v.validate(prefix+emitted)
76
+ codes=np.asarray(content,dtype=np.int64)-v.visual_offset
77
+ return codes.reshape((resolution//8,resolution//8) if kind=='image' else (frames,8,8)),{'structurally_valid':True}
78
+
79
+ def generate_text(self,prompt,sp=None):
80
+ return self._predict('chat_text',[{'type':'text','text':prompt}],'text',sp or Sampling(temperature=.5,top_p=.9))[0]
81
+
82
+ def generate_image(self,prompt,sp=None,resolution=64):
83
+ if resolution==128 and not self.meta.get('image_128_trained',False):
84
+ raise RuntimeError('128px image generation has no verified training exposure in this checkpoint')
85
+ codes,_=self._predict('t2i',[{'type':'text','text':prompt}],'image',sp or Sampling(),resolution=resolution)
86
+ return self.vision().decode(codes[None])[0]
87
+
88
+ def generate_video(self,prompt,frames=8,sp=None):
89
+ codes,_=self._predict('t2v',[{'type':'text','text':prompt}],'video',sp or Sampling(),frames)
90
+ return self.vision().decode(codes)
91
+
92
+ def describe_image(self,pixels,sp=None):
93
+ codes=self.vision().encode(np.asarray(pixels)[None])[0]
94
+ return self._predict('i2t',[{'type':'image','codes':codes}],'text',sp or Sampling(temperature=.5))[0]
95
+
96
+ def describe_video(self,frames,sp=None):
97
+ codes=self.vision().encode(np.asarray(frames))
98
+ return self._predict('v2t',[{'type':'video','codes':codes}],'text',sp or Sampling(temperature=.5))[0]
99
+
100
+ def continue_video(self,frames,new_frames=4,sp=None):
101
+ codes=self.vision().encode(np.asarray(frames))
102
+ generated,_=self._predict('vcont',[{'type':'video','codes':codes}],'video',sp or Sampling(),new_frames)
103
+ return self.vision().decode(generated)
quasar/model/__init__.py ADDED
File without changes
quasar/model/attention.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Causal (grouped-query) self-attention on top of torch's scaled_dot_product_attention.
2
+
3
+ SDPA picks the best available backend at runtime (cuDNN / memory-efficient / flash / math);
4
+ nothing here requires a specific kernel, so CPU and any CUDA build work.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+
12
+ from .config import ModelConfig
13
+ from .layers import apply_rope
14
+
15
+
16
+ class KVCache:
17
+ """Pre-allocated per-layer key/value buffers stored with n_kv_heads (GQA saves cache memory)."""
18
+
19
+ def __init__(self, cfg: ModelConfig, batch: int, max_len: int, device, dtype):
20
+ shape = (batch, cfg.n_kv_heads, max_len, cfg.head_dim)
21
+ self.k = [torch.zeros(shape, device=device, dtype=dtype) for _ in range(cfg.n_layers)]
22
+ self.v = [torch.zeros(shape, device=device, dtype=dtype) for _ in range(cfg.n_layers)]
23
+ self.max_len = max_len
24
+
25
+
26
+ class Attention(nn.Module):
27
+ def __init__(self, cfg: ModelConfig):
28
+ super().__init__()
29
+ self.n_heads, self.n_kv, self.hd = cfg.n_heads, cfg.n_kv_heads, cfg.head_dim
30
+ self.wq = nn.Linear(cfg.d_model, cfg.n_heads * cfg.head_dim, bias=False)
31
+ self.wk = nn.Linear(cfg.d_model, cfg.n_kv_heads * cfg.head_dim, bias=False)
32
+ self.wv = nn.Linear(cfg.d_model, cfg.n_kv_heads * cfg.head_dim, bias=False)
33
+ self.wo = nn.Linear(cfg.n_heads * cfg.head_dim, cfg.d_model, bias=False)
34
+
35
+ def forward(self, x, cos, sin, cache: KVCache | None = None, layer: int = 0, start: int = 0) -> torch.Tensor:
36
+ B, L, _ = x.shape
37
+ q = self.wq(x).view(B, L, self.n_heads, self.hd).transpose(1, 2)
38
+ k = self.wk(x).view(B, L, self.n_kv, self.hd).transpose(1, 2)
39
+ v = self.wv(x).view(B, L, self.n_kv, self.hd).transpose(1, 2)
40
+ q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin)
41
+
42
+ if cache is not None:
43
+ cache.k[layer][:, :, start : start + L] = k
44
+ cache.v[layer][:, :, start : start + L] = v
45
+ k, v = cache.k[layer][:, :, : start + L], cache.v[layer][:, :, : start + L]
46
+
47
+ if self.n_kv != self.n_heads: # expand KV groups; works with every SDPA backend
48
+ k = k.repeat_interleave(self.n_heads // self.n_kv, dim=1)
49
+ v = v.repeat_interleave(self.n_heads // self.n_kv, dim=1)
50
+
51
+ if L == 1: # single decode step attends to the whole cache
52
+ o = F.scaled_dot_product_attention(q, k, v)
53
+ elif start == 0:
54
+ o = F.scaled_dot_product_attention(q, k, v, is_causal=True)
55
+ else: # chunk appended after a cached prefix: causal w.r.t. absolute positions
56
+ mask = torch.ones(L, start + L, dtype=torch.bool, device=x.device).tril(diagonal=start)
57
+ o = F.scaled_dot_product_attention(q, k, v, attn_mask=mask)
58
+ return self.wo(o.transpose(1, 2).reshape(B, L, -1))
quasar/model/axiom.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Axiom-1.1 token mixers and dense LawFFN, adapted to Quasar's interface.
2
+
3
+ Architecture reference: user-owned Axiom/axiom/lm.py and lm_variants.py.
4
+ No fission, dormant slots, or Mamba dependency. FP32 recurrence state; ordinary
5
+ PyTorch SDPA attention. Cache appends support both one token and causal chunks.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import math
10
+
11
+ import torch
12
+ from torch import nn
13
+ from torch.nn import functional as F
14
+
15
+ from .config import ModelConfig
16
+ from .layers import RMSNorm
17
+
18
+
19
+ def scan(log_a: torch.Tensor, u: torch.Tensor, chunk: int = 64, initial: torch.Tensor | None = None):
20
+ """Parallel-within-chunk stable diagonal recurrence; inputs [B,T,H], [B,T,H,P]."""
21
+ B, T, H, P = u.shape
22
+ pad = (-T) % chunk
23
+ la = F.pad(log_a.float(), (0, 0, 0, pad)).reshape(B, -1, chunk, H)
24
+ uu = F.pad(u.float(), (0, 0, 0, 0, 0, pad)).reshape(B, -1, chunk, H, P)
25
+ A = la.cumsum(2)
26
+ causal = torch.ones(chunk, chunk, dtype=torch.bool, device=u.device).tril()
27
+ diff = (A[:, :, :, None] - A[:, :, None]).masked_fill(~causal[None, None, :, :, None], -math.inf)
28
+ weights = diff.exp() * -torch.expm1(la)[:, :, None]
29
+ local = torch.einsum("bnijh,bnjhp->bnihp", weights, uu)
30
+ state = u.new_zeros(B, H, P, dtype=torch.float32) if initial is None else initial.float()
31
+ chunks = []
32
+ for k in range(la.shape[1]):
33
+ out = local[:, k] + A[:, k].exp()[..., None] * state[:, None]
34
+ state = out[:, -1]
35
+ chunks.append(out)
36
+ result = torch.cat(chunks, 1)[:, :T]
37
+ return result, result[:, -1]
38
+
39
+
40
+ class HybridCache:
41
+ """Only attention layers store growing KV; recurrent layers store one FP32 vector."""
42
+ def __init__(self, cfg: ModelConfig, batch: int, max_len: int, device, dtype):
43
+ self.max_len, self.next_pos = max_len, 0
44
+ shape = (batch, cfg.n_heads, max_len, cfg.head_dim)
45
+ self.k = {i: torch.zeros(shape, device=device, dtype=dtype) for i in cfg.attention_layers}
46
+ self.v = {i: torch.zeros(shape, device=device, dtype=dtype) for i in cfg.attention_layers}
47
+ self.states = {i: torch.zeros(batch, cfg.n_heads, cfg.head_dim, device=device, dtype=torch.float32)
48
+ for i in range(cfg.n_layers) if i not in cfg.attention_layers}
49
+
50
+
51
+ class Recurrence(nn.Module):
52
+ def __init__(self, cfg: ModelConfig):
53
+ super().__init__()
54
+ self.h, self.chunk = cfg.n_heads, cfg.scan_chunk
55
+ self.inp = nn.Linear(cfg.d_model, 2 * cfg.d_model + cfg.n_heads, bias=False)
56
+ self.out = nn.Linear(cfg.d_model, cfg.d_model, bias=False)
57
+ self.decay_bias = nn.Parameter(torch.linspace(math.log(math.expm1(1.0)), math.log(math.expm1(1e-3)), self.h))
58
+
59
+ def forward(self, x, cache=None, layer=0):
60
+ B, T, D = x.shape
61
+ u, g, z = self.inp(x).split([D, D, self.h], -1)
62
+ with torch.autocast(x.device.type, enabled=False):
63
+ la = -F.softplus(z.float() + self.decay_bias)
64
+ s, final = scan(la, u.reshape(B, T, self.h, D // self.h), self.chunk,
65
+ None if cache is None else cache.states[layer])
66
+ if cache is not None:
67
+ cache.states[layer] = final.detach()
68
+ return self.out(s.reshape(B, T, D).to(g.dtype) * F.silu(g))
69
+
70
+
71
+ class AxiomAttention(nn.Module):
72
+ def __init__(self, cfg: ModelConfig):
73
+ super().__init__()
74
+ self.h = cfg.n_heads
75
+ self.qkv = nn.Linear(cfg.d_model, 3 * cfg.d_model, bias=False)
76
+ self.out = nn.Linear(cfg.d_model, cfg.d_model, bias=False)
77
+
78
+ def forward(self, x, cos, sin, cache=None, layer=0, start=0):
79
+ B, T, D = x.shape
80
+ q, k, v = self.qkv(x).view(B, T, 3, self.h, D // self.h).permute(2, 0, 3, 1, 4)
81
+ def rotate(t): # Axiom uses interleaved pairs, rather than Quasar-v1's half pairs
82
+ a, b = t.float()[..., 0::2], t.float()[..., 1::2]
83
+ return torch.stack((a * cos - b * sin, a * sin + b * cos), -1).flatten(-2).to(t.dtype)
84
+ q, k = rotate(q), rotate(k)
85
+ if cache is not None:
86
+ cache.k[layer][:, :, start:start + T] = k
87
+ cache.v[layer][:, :, start:start + T] = v
88
+ k, v = cache.k[layer][:, :, :start + T], cache.v[layer][:, :, :start + T]
89
+ mask = None
90
+ if start and T > 1:
91
+ mask = torch.ones(T, start + T, dtype=torch.bool, device=x.device).tril(start)
92
+ o = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, is_causal=start == 0 and T > 1)
93
+ return self.out(o.transpose(1, 2).reshape(B, T, D))
94
+
95
+
96
+ class LawFFN(nn.Module):
97
+ def __init__(self, cfg: ModelConfig):
98
+ super().__init__()
99
+ self.router = nn.Linear(cfg.d_model, cfg.n_cells)
100
+ self.cells = nn.ModuleList(nn.Sequential(nn.Linear(cfg.d_model, cfg.d_cell), nn.GELU(),
101
+ nn.Linear(cfg.d_cell, cfg.d_model)) for _ in range(cfg.n_cells))
102
+
103
+ def forward(self, x):
104
+ weights = self.router(x).float().softmax(-1)
105
+ y = sum(cell(x) * weights[..., i:i + 1].to(x.dtype) for i, cell in enumerate(self.cells))
106
+ # Switch-style fraction is 1/K for top-K == K. This is identically 1;
107
+ # retain source semantics without claiming it regularizes expert weights.
108
+ balance = weights.mean(dim=(0, 1)).sum()
109
+ return y, balance
110
+
111
+
112
+ class HybridBlock(nn.Module):
113
+ def __init__(self, cfg: ModelConfig, index: int):
114
+ super().__init__()
115
+ self.is_attention = index in cfg.attention_layers
116
+ self.n1, self.n2 = RMSNorm(cfg.d_model, cfg.norm_eps), RMSNorm(cfg.d_model, cfg.norm_eps)
117
+ self.mix = AxiomAttention(cfg) if self.is_attention else Recurrence(cfg)
118
+ self.ffn = LawFFN(cfg)
119
+
120
+ def forward(self, x, cos, sin, cache=None, layer=0, start=0):
121
+ y = self.mix(self.n1(x), cos, sin, cache, layer, start) if self.is_attention else self.mix(self.n1(x), cache, layer)
122
+ x = x + y
123
+ f, balance = self.ffn(self.n2(x))
124
+ return x + f, balance
quasar/model/config.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Model configuration and YAML profile loading."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import asdict, dataclass, fields
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+
10
+ @dataclass
11
+ class ModelConfig:
12
+ architecture: str = "transformer" # old checkpoints default to the original transformer
13
+ vocab_size: int = 9280
14
+ d_model: int = 512
15
+ n_layers: int = 8
16
+ n_heads: int = 8
17
+ n_kv_heads: int = 8 # < n_heads => grouped-query attention
18
+ ffn_mult: float = 8 / 3 # SwiGLU hidden = ffn_mult * d_model, rounded up to a multiple of 128
19
+ max_seq_len: int = 1024
20
+ rope_theta: float = 10000.0
21
+ norm_eps: float = 1e-5
22
+ vis_positions: int = 64 # spatial slots per frame (8x8 grid); 0 disables visual position embedding
23
+ visual_offset: int = 0 # first visual-code token id (from UnifiedVocab)
24
+ visual_size: int = 0 # number of visual-code ids
25
+ tie_embeddings: bool = True
26
+ init_std: float = 0.02
27
+ grad_checkpoint: bool = False
28
+ attention_layers: tuple[int, ...] = (3, 7, 11) # zero-based Axiom-1.1 indices
29
+ d_cell: int = 1280
30
+ n_cells: int = 2
31
+ top_k: int = 2
32
+ scan_chunk: int = 64
33
+ balance_weight: float = 0.01
34
+
35
+ def __post_init__(self):
36
+ assert self.d_model % self.n_heads == 0, "d_model must divide by n_heads"
37
+ assert self.n_heads % self.n_kv_heads == 0, "n_heads must divide by n_kv_heads"
38
+ if self.architecture not in ("transformer", "axiom1_1"):
39
+ raise ValueError(f"unknown architecture: {self.architecture}")
40
+ if self.architecture == "axiom1_1":
41
+ self.attention_layers = tuple(self.attention_layers)
42
+ if self.n_cells != 2 or self.top_k != 2:
43
+ raise ValueError("Axiom-1.1 uses two cells, both active; sparse routing is not implemented")
44
+ if self.n_kv_heads != self.n_heads or self.head_dim % 2:
45
+ raise ValueError("Axiom-1.1 requires MHA and an even head dimension")
46
+ if len(set(self.attention_layers)) != len(self.attention_layers) or any(i < 0 or i >= self.n_layers for i in self.attention_layers):
47
+ raise ValueError("attention_layers must contain distinct zero-based layer indices")
48
+ if self.scan_chunk < 1 or self.d_cell < 1:
49
+ raise ValueError("scan_chunk and d_cell must be positive")
50
+
51
+ @property
52
+ def head_dim(self) -> int:
53
+ return self.d_model // self.n_heads
54
+
55
+ @property
56
+ def ffn_hidden(self) -> int:
57
+ return -(-int(self.ffn_mult * self.d_model) // 128) * 128
58
+
59
+ def to_dict(self) -> dict:
60
+ return asdict(self)
61
+
62
+ @classmethod
63
+ def from_dict(cls, d: dict) -> "ModelConfig":
64
+ names = {f.name for f in fields(cls)}
65
+ unknown = set(d) - names
66
+ if unknown:
67
+ raise ValueError(f"unknown model config keys: {sorted(unknown)}")
68
+ return cls(**d)
69
+
70
+
71
+ def load_yaml(path: str | Path) -> dict:
72
+ with open(path) as f:
73
+ return yaml.safe_load(f) or {}
quasar/model/layers.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Transformer building blocks: RMSNorm, rotary position embedding, SwiGLU."""
2
+ from __future__ import annotations
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+
8
+
9
+ class RMSNorm(nn.Module):
10
+ def __init__(self, dim: int, eps: float = 1e-5):
11
+ super().__init__()
12
+ self.eps = eps
13
+ self.weight = nn.Parameter(torch.ones(dim))
14
+
15
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
16
+ xf = x.float()
17
+ return (xf * torch.rsqrt(xf.pow(2).mean(-1, keepdim=True) + self.eps)).type_as(x) * self.weight
18
+
19
+
20
+ def rope_tables(head_dim: int, max_len: int, theta: float, device=None) -> tuple[torch.Tensor, torch.Tensor]:
21
+ """cos/sin tables [max_len, head_dim/2] (float32)."""
22
+ inv = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device, dtype=torch.float32) / head_dim))
23
+ ang = torch.outer(torch.arange(max_len, device=device, dtype=torch.float32), inv)
24
+ return ang.cos(), ang.sin()
25
+
26
+
27
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
28
+ """x: [B, H, L, D]; cos/sin: [L, D/2]. Rotates interleaved-as-halves pairs (GPT-NeoX style)."""
29
+ x1, x2 = x.float().chunk(2, dim=-1)
30
+ out = torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
31
+ return out.type_as(x)
32
+
33
+
34
+ class SwiGLU(nn.Module):
35
+ def __init__(self, dim: int, hidden: int):
36
+ super().__init__()
37
+ self.w_gate = nn.Linear(dim, hidden, bias=False)
38
+ self.w_up = nn.Linear(dim, hidden, bias=False)
39
+ self.w_down = nn.Linear(hidden, dim, bias=False)
40
+
41
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
42
+ return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))
quasar/model/transformer.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Quasar: one decoder-only transformer over the unified text/image/video token space.
2
+
3
+ Pre-norm blocks (RMSNorm -> GQA attention with RoPE -> residual -> RMSNorm -> SwiGLU -> residual),
4
+ tied input/output embeddings, and one extra signal for visual tokens: a learned embedding of the
5
+ token's spatial slot inside its frame (0 for non-visual tokens). The slot is a pure function of
6
+ the token sequence (index within the current contiguous run of visual codes), so it carries no
7
+ information the tokens do not already imply; it just saves a small model from learning to count.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import math
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ from torch.utils.checkpoint import checkpoint
17
+
18
+ from .attention import Attention, KVCache
19
+ from .axiom import HybridBlock, HybridCache
20
+ from .config import ModelConfig
21
+ from .layers import RMSNorm, SwiGLU, rope_tables
22
+
23
+
24
+ class Block(nn.Module):
25
+ def __init__(self, cfg: ModelConfig):
26
+ super().__init__()
27
+ self.attn_norm = RMSNorm(cfg.d_model, cfg.norm_eps)
28
+ self.attn = Attention(cfg)
29
+ self.ffn_norm = RMSNorm(cfg.d_model, cfg.norm_eps)
30
+ self.ffn = SwiGLU(cfg.d_model, cfg.ffn_hidden)
31
+
32
+ def forward(self, x, cos, sin, cache=None, layer=0, start=0):
33
+ x = x + self.attn(self.attn_norm(x), cos, sin, cache, layer, start)
34
+ return x + self.ffn(self.ffn_norm(x))
35
+
36
+
37
+ class Quasar(nn.Module):
38
+ def __init__(self, cfg: ModelConfig):
39
+ super().__init__()
40
+ self.cfg = cfg
41
+ self.embed = nn.Embedding(cfg.vocab_size, cfg.d_model)
42
+ self.vis_embed = nn.Embedding(cfg.vis_positions + 1, cfg.d_model, padding_idx=0) if cfg.vis_positions else None
43
+ self.hybrid = cfg.architecture == "axiom1_1"
44
+ self.blocks = nn.ModuleList(HybridBlock(cfg, i) if self.hybrid else Block(cfg) for i in range(cfg.n_layers))
45
+ self.norm = RMSNorm(cfg.d_model, cfg.norm_eps)
46
+ self.head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
47
+ if cfg.tie_embeddings:
48
+ self.head.weight = self.embed.weight
49
+ cos, sin = rope_tables(cfg.head_dim, cfg.max_seq_len, cfg.rope_theta)
50
+ self.register_buffer("rope_cos", cos, persistent=False)
51
+ self.register_buffer("rope_sin", sin, persistent=False)
52
+ self.apply(self._init)
53
+ for b in self.blocks: # scale residual-branch outputs (GPT-2 style)
54
+ residuals = [b.mix.out.weight, *(c[2].weight for c in b.ffn.cells)] if self.hybrid else [b.attn.wo.weight, b.ffn.w_down.weight]
55
+ for w in residuals:
56
+ nn.init.normal_(w, std=cfg.init_std / math.sqrt(2 * cfg.n_layers))
57
+
58
+ def _init(self, m: nn.Module) -> None:
59
+ if isinstance(m, (nn.Linear, nn.Embedding)):
60
+ nn.init.normal_(m.weight, std=self.cfg.init_std)
61
+ if isinstance(m, nn.Linear) and m.bias is not None:
62
+ nn.init.zeros_(m.bias)
63
+ if isinstance(m, nn.Embedding) and m.padding_idx is not None:
64
+ with torch.no_grad():
65
+ m.weight[m.padding_idx].zero_()
66
+
67
+ def num_params(self, include_embedding: bool = True) -> int:
68
+ n = sum(p.numel() for p in self.parameters()) # tied weights counted once
69
+ return n if include_embedding else n - self.embed.weight.numel()
70
+
71
+ def visual_positions(self, tokens: torch.Tensor) -> torch.Tensor:
72
+ """1-based index of each visual token inside its contiguous run (0 for other tokens)."""
73
+ lo, hi = self.cfg.visual_offset, self.cfg.visual_offset + self.cfg.visual_size
74
+ is_vis = (tokens >= lo) & (tokens < hi)
75
+ idx = torch.arange(tokens.shape[-1], device=tokens.device).expand_as(tokens)
76
+ run_start = torch.where(is_vis, torch.zeros_like(idx), idx + 1).cummax(dim=-1).values
77
+ return torch.where(is_vis, idx - run_start + 1, torch.zeros_like(idx)).clamp(max=self.cfg.vis_positions)
78
+
79
+ def forward(self, tokens: torch.Tensor, vis_pos: torch.Tensor | None = None, cache: KVCache | HybridCache | None = None, start: int = 0,
80
+ return_aux: bool = False):
81
+ """tokens [B, L] -> logits [B, L, V]. With a cache, `start` is the absolute position of tokens[:, 0]."""
82
+ B, L = tokens.shape
83
+ if start + L > self.cfg.max_seq_len:
84
+ raise ValueError(f"sequence length {start + L} exceeds max_seq_len {self.cfg.max_seq_len}")
85
+ if cache is not None and self.hybrid:
86
+ if not isinstance(cache, HybridCache) or start != cache.next_pos or start + L > cache.max_len:
87
+ raise ValueError("hybrid cache requires consecutive appends within its capacity; use a fresh cache for a new sequence")
88
+ x = self.embed(tokens)
89
+ if self.vis_embed is not None:
90
+ x = x + self.vis_embed(self.visual_positions(tokens) if vis_pos is None else vis_pos)
91
+ cos, sin = self.rope_cos[start : start + L], self.rope_sin[start : start + L]
92
+ balances = []
93
+ for i, blk in enumerate(self.blocks):
94
+ if self.cfg.grad_checkpoint and self.training and cache is None:
95
+ result = checkpoint(blk, x, cos, sin, use_reentrant=False)
96
+ else:
97
+ result = blk(x, cos, sin, cache, i, start)
98
+ if self.hybrid:
99
+ x, balance = result
100
+ balances.append(balance)
101
+ else:
102
+ x = result
103
+ if cache is not None and self.hybrid:
104
+ cache.next_pos = start + L
105
+ logits = self.head(self.norm(x))
106
+ aux = torch.stack(balances).mean() if balances else logits.new_zeros(())
107
+ return (logits, aux) if return_aux else logits
108
+
109
+ def loss(self, tokens: torch.Tensor, labels: torch.Tensor, include_aux: bool = True) -> tuple[torch.Tensor, torch.Tensor]:
110
+ """Mean target-token CE, optionally plus Axiom balance loss; (objective, n_targets)."""
111
+ logits, aux = self(tokens, return_aux=True)
112
+ n = (labels != -100).sum()
113
+ loss = F.cross_entropy(logits.float().reshape(-1, logits.size(-1)), labels.reshape(-1), ignore_index=-100)
114
+ return loss + (self.cfg.balance_weight * aux if include_aux else 0), n
115
+
116
+ def new_cache(self, batch: int, max_len: int | None = None, dtype=None) -> KVCache:
117
+ p = next(self.parameters())
118
+ cache_type = HybridCache if self.hybrid else KVCache
119
+ return cache_type(self.cfg, batch, max_len or self.cfg.max_seq_len, p.device, dtype or p.dtype)
quasar/tokenizers/__init__.py ADDED
File without changes
quasar/tokenizers/audio.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Frozen DAC interface. Loads ONLY explicitly supplied local safetensors weights.
2
+
3
+ No implicit downloads. The shared Quasar model predicts the codes; this module
4
+ only reconstructs waveforms. Four-of-32 codebook quality is unmeasured here.
5
+ """
6
+ from pathlib import Path
7
+ import math
8
+ import numpy as np
9
+ import torch
10
+ import torch.nn.functional as F
11
+
12
+
13
+ class AudioTokenizer:
14
+ def __init__(self, local_path: str | Path, codebooks: int = 4, max_seconds: float = 2.0, device: str = "cpu"):
15
+ p = Path(local_path)
16
+ if not (p / "config.json").exists() or not (p / "model.safetensors").exists():
17
+ raise FileNotFoundError(f"DAC is not installed at {p}; provision the pinned codec explicitly before audio preprocessing")
18
+ from transformers import DacModel
19
+ self.model = DacModel.from_pretrained(str(p), local_files_only=True, use_safetensors=True).to(device).eval()
20
+ self.model.requires_grad_(False)
21
+ self.sr, self.books, self.max_seconds = self.model.config.sampling_rate, codebooks, max_seconds
22
+ self.hop = math.prod(self.model.config.downsampling_ratios)
23
+ if not 1 <= codebooks <= self.model.config.n_codebooks:
24
+ raise ValueError("requested codebooks exceed the codec")
25
+ self.device = device
26
+
27
+ @torch.no_grad()
28
+ def encode(self, waveform, sample_rate: int) -> dict:
29
+ x = torch.as_tensor(waveform, dtype=torch.float32, device=self.device)
30
+ if sample_rate != self.sr:
31
+ raise ValueError(f"resample to {self.sr} Hz before encoding; received {sample_rate}")
32
+ if x.ndim != 1 or not x.numel() or not torch.isfinite(x).all() or x.abs().max() > 1.001:
33
+ raise ValueError("expected finite mono waveform in [-1,1]")
34
+ if x.numel() > self.sr * self.max_seconds:
35
+ raise ValueError("audio exceeds duration budget; use transcript-aligned clips")
36
+ n = x.numel()
37
+ x = F.pad(x, (0, (-n) % self.hop))[None, None]
38
+ codes = self.model.encode(x, n_quantizers=self.books).audio_codes[0].T
39
+ return {"type": "audio", "codes": codes.cpu().numpy().astype(np.int32).tolist(), "samples": n, "sample_rate": self.sr}
40
+
41
+ @torch.no_grad()
42
+ def decode(self, packet: dict) -> np.ndarray:
43
+ a = np.asarray(packet["codes"])
44
+ if a.ndim != 2 or a.shape[1] != self.books or a.dtype.kind not in "iu" or not a.size or a.min() < 0 or a.max() >= self.model.config.codebook_size:
45
+ raise ValueError("invalid DAC codes")
46
+ codes = torch.tensor(a.T[None], dtype=torch.long, device=self.device)
47
+ y = self.model.decode(audio_codes=codes).audio_values[0]
48
+ return y[:packet.get("samples", len(y))].cpu().numpy()
quasar/tokenizers/audio_aligned.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Frozen DAC interface with explicit, training-calibrated boundary alignment.
2
+
3
+ Source utterances remain whole. Silent boundary guards are codec padding, and
4
+ the fixed decoder offset is recorded separately from the generative weights.
5
+ """
6
+ import numpy as np
7
+ from .audio import AudioTokenizer
8
+
9
+
10
+ class AlignedAudioTokenizer(AudioTokenizer):
11
+ def __init__(self, local_path, alignment_samples: int, codebooks=4, max_seconds=2.0, device='cpu'):
12
+ super().__init__(local_path, codebooks, max_seconds, device)
13
+ if self.sr != 24000 or self.hop != 320 or abs(alignment_samples) > 32:
14
+ raise ValueError('alignment is only supported for the pinned 24 kHz/320-hop codec')
15
+ self.source_max_seconds = max_seconds
16
+ self.max_seconds = max_seconds + 2 * self.hop / self.sr
17
+ self.alignment_samples = alignment_samples
18
+
19
+ def encode(self, waveform, sample_rate):
20
+ x = np.asarray(waveform)
21
+ if x.ndim != 1 or not 0 < len(x) <= self.source_max_seconds * self.sr:
22
+ raise ValueError('complete source utterance exceeds duration budget')
23
+ packet = super().encode(np.pad(x, (self.hop, self.hop)), sample_rate)
24
+ packet['samples'] = len(x)
25
+ return packet
26
+
27
+ def decode(self, packet):
28
+ # Ask the base decoder for all samples, before applying boundary guards.
29
+ expanded = dict(packet, samples=(len(packet['codes']) + 2) * self.hop)
30
+ raw = super().decode(expanded)
31
+ start = self.hop + self.alignment_samples
32
+ n = packet.get('samples', min(int(self.source_max_seconds * self.sr), len(raw) - 2 * self.hop))
33
+ if not 0 < n <= self.source_max_seconds * self.sr or start + n > len(raw):
34
+ raise ValueError('decoded audio cannot preserve the complete source duration')
35
+ return raw[start:start + n]
quasar/tokenizers/audio_complete.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DAC boundary padding for complete source utterances; no source cropping."""
2
+ import numpy as np
3
+ from .audio import AudioTokenizer
4
+
5
+
6
+ class CompleteAudioTokenizer(AudioTokenizer):
7
+ """Guard the decoder's eight-sample edge shortfall with a full silent hop.
8
+
9
+ The original source must fit the two-second budget. Only codec boundary padding
10
+ extends beyond it; the packet retains the original sample count for decoding.
11
+ """
12
+ def __init__(self, local_path, codebooks=4, max_seconds=2.0, device='cpu'):
13
+ super().__init__(local_path, codebooks, max_seconds, device)
14
+ self.source_max_seconds = max_seconds
15
+ self.max_seconds = max_seconds + self.hop / self.sr
16
+
17
+ def encode(self, waveform, sample_rate):
18
+ x = np.asarray(waveform)
19
+ if x.ndim != 1 or not 0 < len(x) <= self.source_max_seconds * self.sr:
20
+ raise ValueError('complete source utterance exceeds duration budget')
21
+ packet = super().encode(np.pad(x, (0, self.hop)), sample_rate)
22
+ packet['samples'] = len(x)
23
+ return packet
quasar/tokenizers/mesh.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic quantized triangle-mesh codec; no pretrained generator or mesh simplification."""
2
+ from pathlib import Path
3
+ import numpy as np
4
+
5
+
6
+ def quantize_mesh(vertices, faces, bins: int = 512, max_vertices: int = 128, max_faces: int = 128):
7
+ v, f = np.asarray(vertices, dtype=np.float64), np.asarray(faces)
8
+ if v.ndim != 2 or v.shape[1] != 3 or not 3 <= len(v) <= max_vertices or not np.isfinite(v).all():
9
+ raise ValueError("expected finite mesh vertices; simplify externally if over the vertex budget")
10
+ if f.ndim != 2 or f.shape[1] != 3 or not 1 <= len(f) <= max_faces or f.dtype.kind not in "iu" or f.min() < 0 or f.max() >= len(v):
11
+ raise ValueError("invalid triangle faces or face budget exceeded")
12
+ center = (v.min(0) + v.max(0)) / 2
13
+ scale = float(np.ptp(v, axis=0).max())
14
+ if scale <= 1e-12:
15
+ raise ValueError("zero-extent mesh")
16
+ q = np.rint(((v - center) / scale + 0.5) * (bins - 1)).clip(0, bins - 1).astype(np.int32)
17
+ # Quantization can merge vertices or collapse skinny triangles: reject these samples.
18
+ if len(np.unique(q, axis=0)) != len(q) or np.any(np.linalg.norm(np.cross(q[f[:, 1]] - q[f[:, 0]], q[f[:, 2]] - q[f[:, 0]]), axis=1) == 0):
19
+ raise ValueError("degenerate geometry after quantization")
20
+ return {"type": "mesh", "vertices": q.tolist(), "faces": f.tolist()}, {"center": center.tolist(), "scale": scale, "bins": bins}
21
+
22
+
23
+ def decode_mesh(packet: dict, transform: dict | None = None, bins: int = 512):
24
+ v = np.asarray(packet["vertices"], dtype=np.float64) / (bins - 1) - 0.5
25
+ if transform is not None:
26
+ v = v * transform["scale"] + transform["center"]
27
+ return v, np.asarray(packet["faces"], dtype=np.int32)
28
+
29
+
30
+ def write_obj(packet: dict, path: str | Path, transform: dict | None = None):
31
+ """Export geometry as a real OBJ; materials/textures are deliberately outside this codec."""
32
+ v, f = decode_mesh(packet, transform)
33
+ text = "".join(f"v {x:.7f} {y:.7f} {z:.7f}\n" for x, y, z in v)
34
+ text += "".join(f"f {a+1} {b+1} {c+1}\n" for a, b, c in f)
35
+ Path(path).write_text(text, encoding="utf-8")
quasar/tokenizers/multimodal.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unified token space shared by text, images and video.
2
+
3
+ ID layout (non-overlapping ranges):
4
+
5
+ [0, T) text BPE tokens
6
+ [T, T + 64) structural / special tokens (64 reserved slots)
7
+ [T + 64, T + 64 + V) visual codes from the frozen FSQ tokenizer
8
+ padded up to a multiple of 64 (unused tail ids are never produced or targeted)
9
+
10
+ Sequence formats (one per task; loss is applied from `target_start` onward):
11
+
12
+ text <bos> text... <eos>
13
+ t2i <bos><task:t2i> caption <image> v*G </image> <eos>
14
+ i2t <bos><task:i2t> <image> v*G </image> caption <eos>
15
+ i2i <bos><task:i2i> instruction <image> src </image> <image> tgt </image> <eos>
16
+ t2v <bos><task:t2v> caption <video> (<frame><t_k> v*G </frame>)*F </video> <eos>
17
+ v2t <bos><task:v2t> <video> ... </video> caption <eos>
18
+ vcont <bos><task:vcont> <video> ... </video> <eos>
19
+ chat <bos><user> text [media] <assistant> text [media] <eos>
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ from dataclasses import dataclass
25
+
26
+ import numpy as np
27
+
28
+ N_SPECIAL_SLOTS = 64
29
+ MAX_FRAMES = 32
30
+ SPECIALS = [
31
+ "<pad>", "<bos>", "<eos>",
32
+ "<task:text>", "<task:t2i>", "<task:i2t>", "<task:i2i>", "<task:t2v>", "<task:v2t>", "<task:vcont>",
33
+ "<user>", "<assistant>",
34
+ "<image>", "</image>", "<video>", "</video>", "<frame>", "</frame>",
35
+ ] + [f"<t{i}>" for i in range(MAX_FRAMES)] # frame timestamps (index; seconds = k / fps)
36
+ assert len(SPECIALS) <= N_SPECIAL_SLOTS
37
+
38
+
39
+ @dataclass
40
+ class Sample:
41
+ tokens: np.ndarray # int32 [L]
42
+ target_start: int # loss on predicting tokens[target_start:]
43
+
44
+
45
+ class UnifiedVocab:
46
+ def __init__(self, text_size: int, visual_size: int, grid: tuple[int, int] = (8, 8), fps: int = 4):
47
+ self.text_size = text_size
48
+ self.visual_size = visual_size
49
+ self.grid = tuple(grid)
50
+ self.fps = fps
51
+ self.special_offset = text_size
52
+ self.visual_offset = text_size + N_SPECIAL_SLOTS
53
+ self.size = -(-(self.visual_offset + visual_size) // 64) * 64
54
+ self.ids = {name: self.special_offset + i for i, name in enumerate(SPECIALS)}
55
+ self.names = {v: k for k, v in self.ids.items()}
56
+ self.pad, self.bos, self.eos = self.ids["<pad>"], self.ids["<bos>"], self.ids["<eos>"]
57
+ self.img_start, self.img_end = self.ids["<image>"], self.ids["</image>"]
58
+ self.vid_start, self.vid_end = self.ids["<video>"], self.ids["</video>"]
59
+ self.frm_start, self.frm_end = self.ids["<frame>"], self.ids["</frame>"]
60
+ self.user, self.assistant = self.ids["<user>"], self.ids["<assistant>"]
61
+
62
+ @property
63
+ def tokens_per_frame(self) -> int:
64
+ return self.grid[0] * self.grid[1]
65
+
66
+ def task(self, name: str) -> int:
67
+ return self.ids[f"<task:{name}>"]
68
+
69
+ def timestamp(self, k: int) -> int:
70
+ return self.ids[f"<t{k}>"]
71
+
72
+ def is_visual(self, t) -> np.ndarray:
73
+ t = np.asarray(t)
74
+ return (t >= self.visual_offset) & (t < self.visual_offset + self.visual_size)
75
+
76
+ def is_text(self, t) -> np.ndarray:
77
+ t = np.asarray(t)
78
+ return (t >= 0) & (t < self.text_size)
79
+
80
+ # ------------------------------------------------------------------ media -> tokens
81
+ def image_tokens(self, codes: np.ndarray) -> list[int]:
82
+ codes = np.asarray(codes).reshape(-1)
83
+ assert codes.size == self.tokens_per_frame and codes.min() >= 0 and codes.max() < self.visual_size
84
+ return [self.img_start, *(codes + self.visual_offset).tolist(), self.img_end]
85
+
86
+ def frame_tokens(self, codes: np.ndarray, k: int) -> list[int]:
87
+ codes = np.asarray(codes).reshape(-1)
88
+ return [self.frm_start, self.timestamp(k), *(codes + self.visual_offset).tolist(), self.frm_end]
89
+
90
+ def video_tokens(self, codes: np.ndarray, first_index: int = 0, close: bool = True) -> list[int]:
91
+ out = [self.vid_start]
92
+ for k, f in enumerate(np.asarray(codes)):
93
+ out += self.frame_tokens(f, first_index + k)
94
+ return out + ([self.vid_end] if close else [])
95
+
96
+ # ------------------------------------------------------------------ task builders
97
+ def build(self, task: str, text: list[int] | None = None, image=None, image2=None, video=None) -> Sample:
98
+ b = [self.bos]
99
+ if task == "t2i":
100
+ p = b + [self.task("t2i"), *text]
101
+ return self._s(p, self.image_tokens(image) + [self.eos])
102
+ if task == "i2t":
103
+ return self._s(b + [self.task("i2t"), *self.image_tokens(image)], [*text, self.eos])
104
+ if task == "i2i":
105
+ return self._s(b + [self.task("i2i"), *text, *self.image_tokens(image)], self.image_tokens(image2) + [self.eos])
106
+ if task == "t2v":
107
+ return self._s(b + [self.task("t2v"), *text], self.video_tokens(video) + [self.eos])
108
+ if task == "v2t":
109
+ return self._s(b + [self.task("v2t"), *self.video_tokens(video)], [*text, self.eos])
110
+ if task == "vcont":
111
+ return self._s(b + [self.task("vcont")], self.video_tokens(video) + [self.eos])
112
+ raise ValueError(f"unknown task {task}")
113
+
114
+ def chat(self, user: list[int], assistant: list[int]) -> Sample:
115
+ """user/assistant are already-tokenized turn contents (text and/or media tokens)."""
116
+ return self._s([self.bos, self.user, *user, self.assistant], [*assistant, self.eos])
117
+
118
+ @staticmethod
119
+ def _s(prompt: list[int], target: list[int]) -> Sample:
120
+ return Sample(np.array(prompt + target, dtype=np.int32), len(prompt))
121
+
122
+ # ------------------------------------------------------------------ structure
123
+ def check_structure(self, tokens) -> None:
124
+ """Validate media blocks: right number of codes, balanced tags, ordered timestamps."""
125
+ t = np.asarray(tokens).tolist()
126
+ G = self.tokens_per_frame
127
+ i, n = 0, len(t)
128
+ while i < n:
129
+ tok = t[i]
130
+ if tok == self.img_start:
131
+ body = t[i + 1 : i + 1 + G]
132
+ if len(body) != G or not self.is_visual(body).all() or i + 1 + G >= n or t[i + 1 + G] != self.img_end:
133
+ raise ValueError(f"malformed image block at {i}")
134
+ i += G + 2
135
+ elif tok == self.vid_start:
136
+ i += 1
137
+ k = None
138
+ while i < n and t[i] == self.frm_start:
139
+ ts = t[i + 1] - self.timestamp(0) if i + 1 < n else -1
140
+ if not (0 <= ts < MAX_FRAMES) or (k is not None and ts != k + 1):
141
+ raise ValueError(f"bad frame timestamp at {i + 1}")
142
+ k = ts
143
+ body = t[i + 2 : i + 2 + G]
144
+ if len(body) != G or not self.is_visual(body).all() or i + 2 + G >= n or t[i + 2 + G] != self.frm_end:
145
+ raise ValueError(f"malformed frame at {i}")
146
+ i += G + 3
147
+ if i >= n or t[i] != self.vid_end or k is None:
148
+ raise ValueError("video block not closed or empty")
149
+ i += 1
150
+ elif self.is_visual(tok) or tok in (self.img_end, self.vid_end, self.frm_start, self.frm_end):
151
+ raise ValueError(f"visual/structural token {tok} outside a media block at {i}")
152
+ else:
153
+ i += 1
154
+
155
+ def parse(self, tokens) -> list[tuple[str, object]]:
156
+ """Split a generated sequence into ('text', ids) / ('image', codes[h,w]) / ('video', codes[T,h,w])."""
157
+ t = list(np.asarray(tokens).tolist())
158
+ G, (h, w) = self.tokens_per_frame, self.grid
159
+ out: list[tuple[str, object]] = []
160
+ txt: list[int] = []
161
+ i = 0
162
+ while i < len(t):
163
+ if t[i] == self.img_start and i + G < len(t):
164
+ out += [("text", txt)] if txt else []
165
+ txt = []
166
+ out.append(("image", (np.array(t[i + 1 : i + 1 + G]) - self.visual_offset).reshape(h, w)))
167
+ i += G + 2
168
+ elif t[i] == self.vid_start:
169
+ out += [("text", txt)] if txt else []
170
+ txt = []
171
+ frames = []
172
+ i += 1
173
+ while i + 2 + G <= len(t) and t[i] == self.frm_start:
174
+ frames.append((np.array(t[i + 2 : i + 2 + G]) - self.visual_offset).reshape(h, w))
175
+ i += G + 3
176
+ if frames:
177
+ out.append(("video", np.stack(frames)))
178
+ i += 1 if i < len(t) and t[i] == self.vid_end else 0
179
+ else:
180
+ if self.is_text(t[i]):
181
+ txt.append(t[i])
182
+ i += 1
183
+ return out + ([("text", txt)] if txt else [])
184
+
185
+ def to_dict(self) -> dict:
186
+ return {"text_size": self.text_size, "visual_size": self.visual_size, "grid": list(self.grid), "fps": self.fps,
187
+ "special_offset": self.special_offset, "visual_offset": self.visual_offset, "size": self.size, "specials": SPECIALS}
188
+
189
+ @classmethod
190
+ def from_dict(cls, d: dict) -> "UnifiedVocab":
191
+ v = cls(d["text_size"], d["visual_size"], tuple(d["grid"]), d.get("fps", 4))
192
+ assert v.to_dict()["specials"] == d.get("specials", SPECIALS), "special-token table changed since checkpoint"
193
+ return v
194
+
195
+ def save(self, path) -> None:
196
+ with open(path, "w") as f:
197
+ json.dump(self.to_dict(), f, indent=2)
quasar/tokenizers/multires_emu.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Isolated candidate codec adapter; not registered with the live v2 trainer.
2
+
3
+ Frozen full Emu3 weights support square 64/128 images. This changes shapes only;
4
+ packet grammar, spatial embedding migration and training admission are separate.
5
+ """
6
+ import numpy as np
7
+ import torch
8
+ from .restricted_emu import RestrictedEmuCodec
9
+
10
+
11
+ class MultiResolutionEmuCodec(RestrictedEmuCodec):
12
+ def __init__(self, path, device='cpu'):
13
+ super().__init__(path, device)
14
+ if self.config.get('codec') != 'emu3_full_32768' or self.codebook_size != 32768:
15
+ raise ValueError('Only the pinned full visual codebook is supported')
16
+
17
+ @torch.inference_mode()
18
+ def encode(self, images, batch=1):
19
+ images = np.asarray(images)
20
+ if images.dtype != np.uint8 or images.ndim < 4 or images.shape[-3:] not in ((64, 64, 3), (128, 128, 3)):
21
+ raise ValueError('Expected uint8 square 64px or 128px RGB batches')
22
+ if batch < 1 or images.size == 0:
23
+ raise ValueError('Empty image batch or invalid microbatch')
24
+ lead, resolution = images.shape[:-3], images.shape[-2]
25
+ flat, out = images.reshape(-1, resolution, resolution, 3), []
26
+ for i in range(0, len(flat), batch):
27
+ x = torch.tensor(flat[i:i + batch], device=self.selected.device).permute(0, 3, 1, 2).float() / 127.5 - 1
28
+ codes = self.full_to_local[self.model.encode(x)].cpu().numpy()
29
+ expected = (len(x), resolution // 8, resolution // 8)
30
+ if codes.shape != expected or codes.min() < 0 or codes.max() >= self.codebook_size:
31
+ raise ValueError('Codec produced unexpected shape or token range')
32
+ out.append(codes)
33
+ return np.concatenate(out).reshape(*lead, resolution // 8, resolution // 8)
34
+
35
+ @torch.inference_mode()
36
+ def decode(self, codes, batch=1):
37
+ codes = np.asarray(codes)
38
+ if (codes.ndim < 3 or codes.shape[-2:] not in ((8, 8), (16, 16)) or codes.dtype.kind not in 'iu'
39
+ or codes.size == 0 or batch < 1 or codes.min() < 0 or codes.max() >= self.codebook_size):
40
+ raise ValueError('Invalid visual codes or token grid')
41
+ lead, grid = codes.shape[:-2], codes.shape[-1]
42
+ flat, out = codes.reshape(-1, grid, grid), []
43
+ for i in range(0, len(flat), batch):
44
+ full = self.selected[torch.as_tensor(flat[i:i + batch], device=self.selected.device, dtype=torch.long)]
45
+ x = self.model.decode(full)
46
+ if x.shape != (len(full), 3, grid * 8, grid * 8):
47
+ raise ValueError('Codec decoder returned unexpected spatial shape')
48
+ out.append(((x.float().clamp(-1, 1) + 1) * 127.5).round().byte().permute(0, 2, 3, 1).cpu().numpy())
49
+ return np.concatenate(out).reshape(*lead, grid * 8, grid * 8, 3)
quasar/tokenizers/restricted_emu.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Frozen pinned Emu3 pixel codec with a training-only 1,000-entry subset.
2
+
3
+ Quasar still predicts every image code. The frozen codec maps those codes back
4
+ to pixels. No pretrained generative backbone is loaded. Per-frame mode retains
5
+ the existing 64-code grammar and avoids changing the trained text vocabulary.
6
+ """
7
+ from pathlib import Path
8
+ import json
9
+ import numpy as np
10
+ import torch
11
+ from torch import nn
12
+ from safetensors.torch import load_file
13
+
14
+
15
+ class RestrictedEmuCodec(nn.Module):
16
+ codebook_size = 1000
17
+ downsample = 8
18
+
19
+ def __init__(self, path, device='cpu'):
20
+ super().__init__()
21
+ from transformers import AutoModel
22
+ path = Path(path)
23
+ self.config = json.loads((path/'vq_config.json').read_text())
24
+ self.codebook_size = self.config['codebook_size']
25
+ base = Path(self.config['base_path'])
26
+ if not base.is_absolute():
27
+ base = (path / base).resolve()
28
+ self.model = AutoModel.from_pretrained(str(base), trust_remote_code=True,
29
+ local_files_only=True).to(device).eval()
30
+ self.model.requires_grad_(False)
31
+ mapping = load_file(str(path/'vq.safetensors'))
32
+ self.register_buffer('selected', mapping['selected'].to(device))
33
+ self.register_buffer('full_to_local', mapping['full_to_local'].to(device))
34
+
35
+ @torch.inference_mode()
36
+ def encode(self, images, batch=8):
37
+ images=np.asarray(images);lead=images.shape[:-3];flat=images.reshape(-1,*images.shape[-3:]);out=[]
38
+ for i in range(0,len(flat),batch):
39
+ x=torch.tensor(flat[i:i+batch],device=self.selected.device).permute(0,3,1,2).float()/127.5-1
40
+ full=self.model.encode(x)
41
+ out.append(self.full_to_local[full].cpu().numpy())
42
+ return np.concatenate(out).reshape(*lead,8,8)
43
+
44
+ @torch.inference_mode()
45
+ def decode(self, codes, batch=8):
46
+ codes=np.asarray(codes);lead=codes.shape[:-2];flat=codes.reshape(-1,8,8);out=[]
47
+ if codes.dtype.kind not in 'iu' or codes.min()<0 or codes.max()>=self.codebook_size:raise ValueError('invalid codec codes')
48
+ for i in range(0,len(flat),batch):
49
+ full=self.selected[torch.tensor(flat[i:i+batch],device=self.selected.device,dtype=torch.long)]
50
+ x=self.model.decode(full)
51
+ out.append(((x.float().clamp(-1,1)+1)*127.5).round().byte().permute(0,2,3,1).cpu().numpy())
52
+ return np.concatenate(out).reshape(*lead,64,64,3)
quasar/tokenizers/text.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Byte-level BPE text tokenizer (trained locally with HF `tokenizers`).
2
+
3
+ Byte-level means every UTF-8 string is encodable (no UNK). The tokenizer has no special
4
+ tokens of its own: structural tokens live in the unified vocabulary (multimodal.py), above the
5
+ text range, so text ids are exactly [0, vocab_size).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import Iterable
11
+
12
+ from tokenizers import Tokenizer, decoders, models, pre_tokenizers, trainers
13
+
14
+
15
+ class TextTokenizer:
16
+ def __init__(self, tok: Tokenizer):
17
+ self.tok = tok
18
+
19
+ @classmethod
20
+ def train(cls, texts: Iterable[str], vocab_size: int = 8192) -> "TextTokenizer":
21
+ tok = Tokenizer(models.BPE())
22
+ tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
23
+ tok.decoder = decoders.ByteLevel()
24
+ trainer = trainers.BpeTrainer(vocab_size=vocab_size, initial_alphabet=pre_tokenizers.ByteLevel.alphabet(), show_progress=False)
25
+ tok.train_from_iterator(texts, trainer)
26
+ return cls(tok)
27
+
28
+ @property
29
+ def vocab_size(self) -> int:
30
+ return self.tok.get_vocab_size()
31
+
32
+ def encode(self, text: str) -> list[int]:
33
+ return self.tok.encode(text).ids
34
+
35
+ def encode_batch(self, texts: list[str]) -> list[list[int]]:
36
+ return [e.ids for e in self.tok.encode_batch(texts)]
37
+
38
+ def decode(self, ids: Iterable[int]) -> str:
39
+ return self.tok.decode([i for i in ids if 0 <= i < self.vocab_size])
40
+
41
+ def save(self, path: str | Path) -> None:
42
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
43
+ self.tok.save(str(path))
44
+
45
+ @classmethod
46
+ def load(cls, path: str | Path) -> "TextTokenizer":
47
+ return cls(Tokenizer.from_file(str(path)))