MrMofer commited on
Commit
166a35f
·
verified ·
1 Parent(s): 977526a

feat: i2v vision tower + processor + PATCHES (update pipeline)

Browse files
PATCHES.md CHANGED
@@ -1,60 +1,153 @@
1
- # PATCHES.md — MiniMax-H3 MLX 8-bit pipeline modifications
2
 
3
- Two files are modified relative to the upstream `PipeNetwork/minimax-h3-mlx` port
4
- (base commit `b2f7e4d2b7861cefe68b75e4b59ab81cc4e7c318`, 2026-08-10). Both changes were
5
- validated with real generations including Turbo LoRA merges (see the validation report in
6
- `README.md`). Everything else is byte-identical to the base commit.
7
 
8
  | | |
9
  |---|---|
10
  | Base commit | `b2f7e4d2b7861cefe68b75e4b59ab81cc4e7c318` |
11
- | Branch | `video-lab-8bit-fixes` |
12
- | Patch commit | `7210b93e6df86bf9c7206091c9542b6983c10c30` |
13
- | Date | 2026-08-17 |
 
 
 
14
 
15
- ## `minimax_h3_mlx/dit.py` — DiT attention QKV layout fix
16
 
17
- - The raw-checkpoint `attn.qkv_proj` rows are **blocked**, not per-head interleaved:
18
- `[q | k | v]` in three contiguous parts of `heads * head_dim` columns each. The old code
19
- reshaped to `(B, S, heads, 3, head_dim)` and indexed q/k/v on axis -2, which scrambled the
20
- heads; the new code splits and reshapes each third independently:
21
 
 
 
 
 
22
  ```python
 
23
  qkv = self.qkv_proj(x)
24
  q, k, v = mx.split(qkv, 3, axis=-1)
25
  q = q.reshape(B, S, self.heads, self.head_dim)
26
  k = k.reshape(B, S, self.heads, self.head_dim)
27
  v = v.reshape(B, S, self.heads, self.head_dim)
28
  ```
 
 
 
 
 
29
 
30
- - The module docstring was updated to document the blocked layout — the same layout the
31
- mlx-serve runtime consumes via `splitEqual(qkv, 3)` — and to note that the video VAE's
32
- `to_qkv` (per-head interleaved) is a different codebase with a different convention.
33
 
34
- ## `minimax_h3_mlx/text_encoder.py` — 8-bit quantized text encoder loader
35
 
36
- - Detects the MLX affine 8-bit pack by the presence of `quant_config.json` in the model
37
- directory and quantizes the Qwen3-VL module tree *before* loading weights, so packed
38
- U32 weights / scales / biases key up 1:1:
39
 
 
 
40
  ```python
41
- self.quantized = (model_dir / "quant_config.json").exists()
42
- ...
43
  nn.quantize(self.language, group_size=64, bits=8,
44
  class_predicate=lambda _path, m: isinstance(m, nn.Linear))
45
  ```
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- - Quantized shards are loaded raw (no `astype(dtype)`), keeping the packed integers intact;
48
- only the dense path casts tensors.
 
 
 
 
 
 
 
 
 
 
49
 
50
- - The serve pack omits `model.norm` (never evaluated — H3 conditions pre-norm), so the loader
51
- fabricates the same zeros the dense converter wrote when the weight is missing:
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
 
 
 
 
 
53
  ```python
54
- buckets["language"]["norm.weight"] = mx.zeros(
55
- tuple(module.norm.weight.shape), dtype=mx.bfloat16
56
- )
57
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- - Full patch: `/tmp/h3-port-patches.patch` in the source workspace (`git diff` of the two
60
- files, 34 insertions / 6 deletions).
 
 
1
+ # PATCHES.md — MiniMax-H3 MLX 8-bit pipeline modifications (complete port)
2
 
3
+ All modifications relative to upstream `PipeNetwork/minimax-h3-mlx` base commit `b2f7e4d2b7861cefe68b75e4b59ab81cc4e7c318` (2026-08-10). The first committed patch is `video-lab-8bit-fixes` `7210b93e6df86bf9c7206091c9542b6983c10c30` (2026-08-17, two files); fixes S8/S9/S10 extend the same branch in the working tree (dit.py two-phase, pipeline release + TeaCache hook, text_encoder processor+scatter, teacache.py, generate.py flags, processor/ + vision shards). Everything else is byte-identical to the base commit.
 
 
 
4
 
5
  | | |
6
  |---|---|
7
  | Base commit | `b2f7e4d2b7861cefe68b75e4b59ab81cc4e7c318` |
8
+ | Branch | `video-lab-8bit-fixes` (+ working-tree S8/S9/S10 on `video-lab`) |
9
+ | Patch commit (committed) | `7210b93e6df86bf9c7206091c9542b6983c10c30` |
10
+ | Date (committed) | 2026-08-17 |
11
+ | Working-tree S8/S9/S10 | 2026-08-18–2026-08-19 (pipeline.py, dit.py extension, teacache.py, text_encoder.py B1/B2, generate.py, processor/ + vision shards) |
12
+ | Validated with | Real generations incl. Turbo LoRA merges + I2V encode checks (see README Validation report + tmp/S9_teacache_spike_RESULT.md) |
13
+ | License | Port: Apache-2.0 (PipeNetwork). Weights: MiniMax H3 Community License (`LICENSE`). Turbo LoRA: Apache-2.0 (larryvrh). |
14
 
15
+ ---
16
 
17
+ ## (a) `minimax_h3_mlx/dit.py` DiT attention QKV blocked layout + TeaCache two-phase hooks
 
 
 
18
 
19
+ **File:** `minimax_h3_mlx/dit.py` (~20 lines → ~230 lines added vs base)
20
+
21
+ **QKV blocked layout (S7, committed 7210b93, lines ~12–17 docstring + lines 153–165 in `Attention.__call__`):**
22
+ - Raw-checkpoint `attn.qkv_proj` rows are **blocked** `[q | k | v]` — three contiguous thirds of width `heads * head_dim` — not per-head interleaved `[h0: q,k,v][h1: q,k,v]...`. The old code reshaped to `(B, S, heads, 3, head_dim)` and indexed `qkv[...,0]` which scrambled heads. Fix splits and reshapes each third independently (same layout `mlx-serve` consumes via `splitEqual(qkv,3)`; video VAE `to_qkv` remains per-head interleaved — different codebase, different convention):
23
  ```python
24
+ # minimax_h3_mlx/dit.py:160-164 (Attention)
25
  qkv = self.qkv_proj(x)
26
  q, k, v = mx.split(qkv, 3, axis=-1)
27
  q = q.reshape(B, S, self.heads, self.head_dim)
28
  k = k.reshape(B, S, self.heads, self.head_dim)
29
  v = v.reshape(B, S, self.heads, self.head_dim)
30
  ```
31
+ - Module docstring (lines 12–17, 157–158) now documents the blocked layout and the VAE contrast.
32
+
33
+ **TeaCache two-phase forward (S9, working-tree, lines ~335–510):**
34
+ - New helpers `TYPE_CHECKING` import of `ModulationCache`, `_resolve_cache_layer` (lines 347–354), `_prepare_stages` (355–391, shared input projections + token_refiner + packed buffer + temb + adaln indices so the prefix graph is built exactly once), `_run_blocks` (393–409, iterates `blocks[i]` with optional `ModulationCache.get(i)`), `_finish_heads` (411–423, `final_layer.norm_out` + `video_out`/`audio_out` heads).
35
+ - Public two-phase entry points: `forward_to_cache_layer` (425–464, partial forward up to and including `cache_layer` — default last block `len(blocks)-1` — returning `(hidden, ctx)` where `hidden` is pre-final-norm residual after that block and `ctx` carries rotary/temb/adaln/layer bookkeeping) and `finish_from_cache_layer` (466–487, completes from cached `hidden`/`ctx` without recomputing prefix, returns `(video_velocity, audio_velocity)`). `__call__` (489–510) now delegates to `_prepare_stages`/`_run_blocks`/`_finish_heads` and gains `return_hidden` for probing. Saving per skip = suffix `blocks[layer+1..49]` + `final_layer.norm_out` + both heads — with default last block only heads+norm are saved (~<5% wall); moving to block 40 saves ~20% blocks per skip (see S9 spike result: 0/7 skips at last block, no wall saving).
36
 
37
+ ---
 
 
38
 
39
+ ## (b) `minimax_h3_mlx/text_encoder.py` — 8-bit quantized loader + processor fallback (B1) + positional scatter (B2)
40
 
41
+ **File:** `minimax_h3_mlx/text_encoder.py` (+84 lines vs base, ~6 ~17 imports)
 
 
42
 
43
+ **8-bit quantized loader (S7, committed 7210b93, lines ~64–110 + ~143–180):**
44
+ - Detects MLX affine 8-bit pack by presence of `quant_config.json` (line 71: `self.quantized = (model_dir / "quant_config.json").exists()`). Before loading, quantizes the module tree so packed U32/scales/biases key 1:1 — same recipe `load.py` replays from `quant_config.json`:
45
  ```python
46
+ # lines 90-92
 
47
  nn.quantize(self.language, group_size=64, bits=8,
48
  class_predicate=lambda _path, m: isinstance(m, nn.Linear))
49
  ```
50
+ - Vision tower: if `load_vision=True` and `vision_quant_config.json` exists, replays its recorded `quantized` paths (89 tensors) with same group/bits (lines 98–110):
51
+ ```python
52
+ with open(model_dir / "vision_quant_config.json") as fh: vq = json.load(fh)
53
+ paths = set(vq.get("quantized", ()))
54
+ nn.quantize(self.vision, group_size=vq.get("group_size",64), bits=vq.get("bits",8),
55
+ class_predicate=lambda _p, m: isinstance(m, nn.Linear) and _p in paths)
56
+ ```
57
+ - Quantized shards loaded raw (no `astype(dtype)`, lines 166–170: `buckets[bucket][path] = tensor` when `self.quantized` else `tensor.astype(dtype)`), preserving packed integers. Only dense path casts.
58
+ - Serve pack omits `model.norm` (never evaluated — H3 conditions pre-norm); loader fabricates zeros like dense converter (lines 176–180):
59
+ ```python
60
+ buckets["language"]["norm.weight"] = mx.zeros(tuple(module.norm.weight.shape), dtype=mx.bfloat16)
61
+ ```
62
 
63
+ **Processor property with torch-free fallback (B1, S10, lines 207–233):**
64
+ - `@property processor` (lines 207–233) tries `AutoProcessor.from_pretrained(processor_dir)` (full Qwen3VLProcessor with torch video/image sub-processors). On `ImportError` (no torch/torchvision; `Qwen3VLVideoProcessor` hard-requires them, and transformers 5.15 `AutoImageProcessor` is itself gated), falls back to PIL-only `Qwen2VLImageProcessorPil`:
65
+ ```python
66
+ try: self._processor = AutoProcessor.from_pretrained(processor_dir)
67
+ except Exception:
68
+ try: image_processor = AutoImageProcessor.from_pretrained(processor_dir)
69
+ except Exception:
70
+ from transformers.models.qwen2_vl.image_processing_pil_qwen2_vl import Qwen2VLImageProcessorPil
71
+ image_processor = Qwen2VLImageProcessorPil.from_pretrained(processor_dir)
72
+ self._processor = SimpleNamespace(image_processor=image_processor)
73
+ ```
74
+ The facade only ever reads `.image_processor` (`build_request` line 251), tokenizer comes from separate `tokenizer` property (lines 197–205), so image-only processor is sufficient for I2V.
75
 
76
+ **Encode positional scatter fix (B2, S10, lines 324–344):**
77
+ - `encode()` (lines 306–344) previously did `mx.where(image_mask[...,None], hidden[None], inputs_embeds)` which mis-broadcasts `(1,3096,1)` vs `(1,3072,5120)` because vision hidden rows are compact while image pads sit at arbitrary positions in the longer token row — a **positional scatter**, not a broadcast.
78
+ - Fix validates `hidden.shape[0] == num_image` (lines 330–334), then does positional REPLACE scatter via `scatter_add` (mlx 0.32 has no `nonzero`/`index_put`, only `at[].add`):
79
+ ```python
80
+ pos_np = np.nonzero(np.array(image_mask))[0] # line 339 (mlx has no nonzero, use numpy)
81
+ pos = mx.array(pos_np) # line 340
82
+ target = inputs_embeds[0] # line 341
83
+ inputs_embeds = target.at[pos].add(hidden.astype(target.dtype) - target[pos]) # line 342
84
+ inputs_embeds = inputs_embeds[None] # line 343
85
+ ```
86
+ Validated with `tmp/s10_validate_encode.py` (1,3096,5120) finite, 3074 video-tagged rows (start+3072 pads+end). `build_request` (lines 237–277) tags whole vision block as `TAG_VIDEO` (not text) — the DiT AdaLN key.
87
+
88
+ ---
89
+
90
+ ## (c) `minimax_h3_mlx/pipeline.py` — `release_text_encoder` headroom (S8/S8b) + TeaCache hook (S9) + checkpoint resume
91
 
92
+ **File:** `minimax_h3_mlx/pipeline.py` (+~205 lines vs base)
93
+
94
+ **Release text encoder (S8/S8b, lines 59–73 `__init__`, 77–118 `from_pretrained`, 228–331 `__call__`, 470–532 `_release_text_encoder_now`):**
95
+ - `MiniMaxH3Pipeline.__init__` gains `release_text_encoder: bool` (line 66, stored as `self._release_text_encoder` line 73). `from_pretrained` threads it (lines 84, 118).
96
+ - In `__call__` (lines 328–331), right after `prompt_embeds` built (line 1, text conditioning + vision rows already encoded, step-4 noise already drawn — trajectory fixed), opt-in `release_text_encoder or self._release_text_encoder` drops the encoder:
97
  ```python
98
+ if (release_text_encoder or self._release_text_encoder) and getattr(self, "text_encoder", None) is not None:
99
+ self._release_text_encoder_now()
 
100
  ```
101
+ - `_release_text_encoder_now` (lines 470–532) drops `self.text_encoder` (`del` + `gc.collect()` pattern), sizes correctly via `mlx.utils.tree_flatten` (lines 484,496,510 — previous `module.parameters().values()` summed dicts not arrays → 0.0 GB), calls `mx.clear_cache()` to return unified Metal memory (line 528), logs `released text encoder after conditioning (freeing ~N GB)` (line 532). Second call is no-op (line 471 guard). Safe: inner estimator errors just leave freed-size as "(sizing indeterminado)" while delete still happens. Measured: frees ~27.5 GB / ~22 GB resident before denoise loop; pipeline then only touches DiT+VAEs.
102
+ - CLI: `scripts/generate.py` `--release-encoder` (line 56) → `pipeline(release_text_encoder=args.release_encoder)` (line 96).
103
+ - Checkpoint resume (bonus, lines 371–389): `resume_from`/`checkpoint_dir` floats inside the loop (not a pipeline-level patch for HF, but present in `pipeline.py` working tree).
104
+
105
+ **TeaCache hook (S9, lines 43, 228–230, 342–410):**
106
+ - Imports `TeaCacheConfig/TeaCacheController` (line 43), `__call__` gains `teacache`, `teacache_config`, `teacache_layer` (lines 228–230 docstring 244–252). Controller instantiated when `teacache=True` (lines 342–350, resolves `tc_layer = num_layers-1 if None else int`, validates range). Per-step two-phase: `hidden, ctx = dit.forward_to_cache_layer(..., cache_layer=tc_layer)` → `mx.eval(hidden)` → `skip = controller.decide(i, hidden)` → on skip reuse `cached_preds = (video_pred, audio_pred)` joint (lines 391–399), else `finish_from_cache_layer` and cache preds (lines 406–410). Log: `step N/T teacache skip (rel_l1=..., thresh=...)` (lines 398–401). Default last-block hook measured **0/7 skips** at 768x448 turbo8 (see teacache.py below → OFF recommended).
107
+
108
+ **Other:** `load.py` one-line import fix for quantized path (line 1 added), `scripts/generate.py` adds `--teacache*`/`--release-encoder` flags (lines 45–56, 85–96).
109
+
110
+ ---
111
+
112
+ ## (d) `minimax_h3_mlx/teacache.py` — two-phase TeaCache controller (S9, new file)
113
+
114
+ **File:** `minimax_h3_mlx/teacache.py` (11 KB, new — 223 lines)
115
+
116
+ - **Intent:** feature-caching (ByteDance/ali-vilab line) — each step runs partial forward to a probe hidden, compares against previous step's feature, reuses previous `(video, audio)` velocity if similar. Joint reuse (both modalities from one forward must be reused together). Noise/keyframe sampling untouched → seed still fixes trajectory.
117
+ - **`TeaCacheConfig` (lines 40–71):** `rel_l1_thresh=0.2` (default, ref 0.15 for 10s 544x960), `metric='rel_l1'|'cosine'`, `start_at_step=3`, `compute_last_step=True` (protects first 3 and last step), `cache_type='avg'|'last'`. `__post_init__` validates.
118
+ - **Pure helpers (lines 87–134):** `relative_l1_distance` (mean|curr-prev|/mean|prev|, lines 87–100), `cosine_similarity` (lines 103–113), `teacache_gate` (1-min(dist,1) for rel_l1 so higher=more similar, lines 115–124), `teacache_should_skip` (lines 126–134: `rel_l1 <= thresh` or `cosine >= thresh`).
119
+ - **`TeaCacheController` (lines 148–242):** stateful per-run (`total_steps`, `cached_feature`, `computed/skipped`, `by_step`). `decide(step_index, feature)` (lines 171–210): `must_compute` if `step==0` or `step<start` or `cache is None` or `last step` → prime cache; else compute `metric_value` (cosine or rel_l1 distance), `skip = metric >=/<= thresh`, smoothing `avg` folds current into cache as `(prev+curr)/2` on skip, else replaces. Stats: `last_gate` (similarity-domain), `last_metric` (raw distance/similarity for honest log), `metric_name`, `to_stats()`.
120
+ - **Pipeline integration:** see (c) above; `dit.py` hooks provide the probe feature. **Measurement (M4 Max, 768x448 turbo8, 7 forwards):** default last-block `thresh 0.2` → 0 skips (+9.7% overhead), `0.35` → 1/7 skip (rel_l1 0.297) still no wall saving (partial forward already 49/50 blocks, only `final_layer.norm_out`+heads saved). `tmp/S9_teacache_spike_RESULT.md` — **verdict: TeaCache OFF by default**; aggressive needs earlier hook `layer 40` + `thresh 0.25–0.35` for double-digit % (trades quality).
121
+
122
+ ---
123
+
124
+ ## (e) `processor/` + vision shards (S10 I2V)
125
+
126
+ **Files added to checkpoint (not code patches but data required for I2V):**
127
+ - `processor/` (7 files, ~11.6 MB): `preprocessor_config.json` (390 B), `video_preprocessor_config.json` (385 B), `chat_template.json` (5.5 KB), `tokenizer_config.json` (11 KB), `tokenizer.json` (6.7 MB), `vocab.json` (2.6 MB), `merges.txt` (1.6 MB) — copied from `models/minimax-h3-ckpt/processor/` (`Qwen3VLProcessor`, `Qwen2VLImageProcessorFast` + `Qwen3VLVideoProcessor` configs; runtime uses PIL fallback per (b) B1).
128
+ - `text_encoder/model-00005-of-00008-vision.safetensors` (291,980,081 B), `model-00006-of-00008-vision.safetensors` (341,371,441 B), `model-00007-of-00008-vision.safetensors` (129,267,789 B) — 529 `model.visual.*` tensors (vision tower: depth 27, hidden 1152, patch 16, merge 2, out 5120). Built by `scripts/rebuild_h3_text_encoder_vision_8bit.py` which transposes `patch_embed.proj.weight` from torch `(O,C,kD,kH,kW)` to MLX channels-last and quantizes 89 paths.
129
+ - `text_encoder/vision_quant_config.json` (2,461 B, 89 quantized paths: `blocks.*.attn.qkv/proj`, `blocks.*.mlp.linear_fc1`, `merger.*`, `deepstack_merger_list.*`).
130
+
131
+ **Why this matters:** without `processor/` the pipeline cannot build `pixel_values`/`image_grid_thw` (I2V falls back to T2V). Without vision shards `text_encoder.encode(prompt, images)` raises `load_vision=False` or missing-tensor error. With both, `encode()` (b-B2) scatters 3072 vision patches into `inputs_embeds` and hits the DiT as `TAG_VIDEO` rows, then `pipeline._encode_keyframes` patches conditioning latents — FL2VA keyframe path (anchors `first`/`last`) validated end-to-end (see README `## Image-to-Video`).
132
+
133
+ ---
134
+
135
+ ## Other minor port fixes
136
+
137
+ - `minimax_h3_mlx/load.py`: quantization structure replay for DiT (already in committed 7210b93) — one-line import guard retained.
138
+ - `scripts/generate.py`: flags `--teacache`, `--teacache-thresh` (default 0.2), `--teacache-metric` (rel_l1/cosine), `--teacache-layer`, `--teacache-start` (default 3), `--release-encoder` (all S8/S9).
139
+ - `tests/test_release_text_encoder.py`, `tests/test_teacache.py`: unit/mocked tests for (c)/(d) (not shipped in HF package, kept in repo).
140
+
141
+ ## Reproducing the diff
142
+
143
+ ```bash
144
+ git -C models/minimax-h3-mlx diff b2f7e4d2 -- minimax_h3_mlx/dit.py minimax_h3_mlx/text_encoder.py minimax_h3_mlx/pipeline.py minimax_h3_mlx/teacache.py scripts/generate.py
145
+ # committed two-file patch:
146
+ git -C models/minimax-h3-mlx show 7210b93 --stat
147
+ ```
148
+
149
+ ## License
150
 
151
+ - **Weights**: MiniMax H3 Community License (included as `LICENSE`) not open source; territorial exclusions apply (EU/UK/KR/US excluded per mlx-serve pack note).
152
+ - **Port code**: Apache-2.0 (PipeNetwork/minimax-h3-mlx).
153
+ - **Turbo LoRA**: Apache-2.0 (larryvrh/MiniMax-H3-Turbo-Lora).
README.md CHANGED
@@ -57,6 +57,45 @@ The pipeline needs only this repository: `-c` supplies the VAEs, text encoder an
57
  `-t` points at the quantized DiT. Native canvas is a 768px short edge (e.g. 1344x768 16:9);
58
  smaller canvases are off-distribution and degrade quickly.
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  ## Example outputs
61
 
62
  ![MiniMax-H3 MLX comparison — 8-bit vs turbo variants](images/comparative.png)
@@ -77,7 +116,9 @@ Turbo variants: see rows 2-3 above (a 1344x768 4-bit video re-render is planned)
77
  | Path | Role | Representation |
78
  |---|---|---|
79
  | `transformer/transformer.safetensors` | 33B DiT (joint video+audio) | 8-bit affine, group 64 (AdaLN 8-bit), ~35.3 GB |
80
- | `text_encoder/` (5 shards) | Qwen3-VL-32B conditioner (64 layers, truncated to layer 50) | 8-bit quantized, ~27.5 GB |
 
 
81
  | `video_vae/` | Tiled causal video VAE (17-frame chunks, latents_mean/std) | fp16/bf16, 5.2 GB |
82
  | `audio_vae/` | DAC encoder + BigVGAN vocoder, stereo 32 kHz | fp32, 0.6 GB |
83
  | `tokenizer/` | Qwen3-VL tokenizer | json/vocab/merges |
@@ -88,10 +129,12 @@ Turbo variants: see rows 2-3 above (a 1344x768 4-bit video re-render is planned)
88
  ## Memory and speed (measured, M4 Max 68.7 GB)
89
 
90
  - Resident during generation: DiT ~21.5 GB + text encoder ~22 GB + VAEs ~6 GB (AdaLN projections
91
- are precomputed and dropped, freeing ~13.8 GB).
92
- - 768x448, 16 steps: **33.9 min** (119 s/step), peak RSS 24.7 GB, no swap.
93
  - 1344x768 (native 16:9), 8 steps: **~2 h** (996 s/step), peak ~57 GB + compressed memory; requires
94
  an otherwise idle machine (jetsam kills it under heavy ambient load).
 
 
95
  - Attention is dense (MiniMax has not released sparse attention); quantization does not reduce the
96
  attention cost — it exists to fit memory.
97
 
@@ -105,6 +148,8 @@ was judged by an independent vision-capable model on extracted frames (not stati
105
  | 8-bit (this repo) | 768x448, 16 steps | 33.9 min | **9/10** — coherent fox walking a mossy log; stable background; no melting |
106
  | 8-bit + Turbo LoRA | 768x448, 8 steps | ~24 min | High confidence — "impressive for a 4-step turbo LoRA" |
107
  | 4-bit + Turbo LoRA | 1344x768, 8 steps | 2 h 3 min | **95%** — full leap arc; no melting, no banding (frames; mp4 mux was a local script bug, not the model) |
 
 
108
 
109
  Key findings:
110
  - The VAE decoder is **not** the source of artifacts: parity with the reference diffusers
@@ -119,7 +164,7 @@ Key findings:
119
 
120
  ## Turbo LoRA
121
 
122
- `turbo_lora_4step_ema.safetensors` is the 4-step EMA distilled LoRA from
123
  [larryvrh/MiniMax-H3-Turbo-Lora](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora)
124
  (Apache-2.0; sha256 `5a6eeba1…`). Merge it into a transformer copy:
125
 
@@ -132,9 +177,7 @@ Key findings:
132
 
133
  ## Patches vs upstream
134
 
135
- `PATCHES.md` documents the two modified files (branch `video-lab-8bit-fixes`, commit `7210b93e6df86bf9c7206091c9542b6983c10c30`):
136
- an 8-bit quantized text-encoder loader path and DiT fixes validated with the turbo merges. The
137
- remaining pipeline is byte-identical to the base commit.
138
 
139
  ## License
140
 
 
57
  `-t` points at the quantized DiT. Native canvas is a 768px short edge (e.g. 1344x768 16:9);
58
  smaller canvases are off-distribution and degrade quickly.
59
 
60
+ ## Image-to-Video (FL2VA)
61
+
62
+ This repository is **I2V-capable** (partition `fl2va`, tasks `t2va` + `fl2va` in `model_index.json`). The image is **optional** — present as a keyframe conditioning row via `--image` / `--anchor`, or omitted for pure T2V.
63
+
64
+ **Processor + vision tower required for I2V.** The `processor/` directory (7 files) and the 3 vision shards (`text_encoder/model-00005..00007-of-00008-vision.safetensors` + `vision_quant_config.json`, 529 `model.visual.*` tensors, 89 quantized) must be present. Without them the pipeline cannot encode images (falls back to T2V only).
65
+
66
+ **Canonical I2V command (validated):**
67
+
68
+ ```bash
69
+ # 704×544 portrait (height 704 width 544) or 512×384 lightweight variant — both portrait 9:16 / 4:3
70
+ # Bridge image: inputs/bridge-20260813-181546-krea2_turbo-selfie-style-vertical-shot-a-young-woman-in-h.png
71
+ PROMPT=$(cat <<'PROMPT_EOF'
72
+ Vertical 9:16 TikTok-style UGC selfie video, handheld smartphone feel, natural indoor daylight near a window. A friendly creator speaks directly to camera with natural blinking, subtle head nods, and a warm smile. Add small human imperfections: a tiny hesitation, a soft breath, a quick smile mid-sentence, and a micro-pause before the last line. Realistic skin texture, stable identity, no face warping, minimal flicker, clean audio with natural room tone.
73
+
74
+ No subtitles. No on-screen text. No logos. No watermarks.
75
+
76
+ The creator says (exactly, with the same pacing and hesitations):
77
+ "Okay, entonces… eh… un datazo. Si estás trabado con tu código, solo da el primer pasito… como, abre tu terminal y escribe fran. (sonríe) Así de simple. Te vas a sorprender de lo rápido que todo se vuelve más fácil."
78
+ PROMPT_EOF
79
+ )
80
+
81
+ .venv/bin/python scripts/generate.py "$PROMPT" \
82
+ -c <this repo> -t <this repo>/transformer -s 8 --seed 1996783985 \
83
+ --height 704 --width 544 -d 5 \
84
+ --image inputs/bridge-20260813-181546-krea2_turbo-selfie-style-vertical-shot-a-young-woman-in-h.png \
85
+ --anchor first --release-encoder \
86
+ -o i2v-704x544.mp4
87
+
88
+ # Lightweight alternative (faster, same identity):
89
+ .venv/bin/python scripts/generate.py "$PROMPT" \
90
+ -c <this repo> -t <this repo>/transformer -s 8 --seed 1996783985 \
91
+ --height 512 --width 384 -d 5 \
92
+ --image inputs/bridge-20260813-181546-krea2_turbo-selfie-style-vertical-shot-a-young-woman-in-h.png \
93
+ --anchor first --release-encoder \
94
+ -o i2v-384x512.mp4
95
+ ```
96
+
97
+ Valid `anchors` are `first` and `last` (FL2VA). Omit `--image`/`--anchor` for text-to-video. The image is consumed by `text_encoder` via `processor/` (`Qwen2VLImageProcessorPil` fallback when torch absent) and the vision tower, then patched as video-conditioning rows (see `minimal I2V encode` in `PATCHES.md`).
98
+
99
  ## Example outputs
100
 
101
  ![MiniMax-H3 MLX comparison — 8-bit vs turbo variants](images/comparative.png)
 
116
  | Path | Role | Representation |
117
  |---|---|---|
118
  | `transformer/transformer.safetensors` | 33B DiT (joint video+audio) | 8-bit affine, group 64 (AdaLN 8-bit), ~35.3 GB |
119
+ | `text_encoder/` (5+3 shards) | Qwen3-VL-32B conditioner (64 layers, truncated to layer 50) | 8-bit quantized, ~27.5 GB language (5 shards) + ~0.76 GB vision (3 shards `model-00005..00007-of-00008-vision.safetensors`, 529 `model.visual.*` tensors) |
120
+ | `text_encoder/vision_quant_config.json` | Vision tower quantization recipe (affine 8-bit g64) | json, 89 quantized tensors (`blocks.*`, `merger.*`, `deepstack_merger_list.*`) |
121
+ | `processor/` | Qwen3-VL processor (image preprocessing for I2V) | 7 files: `preprocessor_config.json`, `video_preprocessor_config.json`, `chat_template.json`, `tokenizer.json`, `tokenizer_config.json`, `vocab.json`, `merges.txt` (~11.6 MB) |
122
  | `video_vae/` | Tiled causal video VAE (17-frame chunks, latents_mean/std) | fp16/bf16, 5.2 GB |
123
  | `audio_vae/` | DAC encoder + BigVGAN vocoder, stereo 32 kHz | fp32, 0.6 GB |
124
  | `tokenizer/` | Qwen3-VL tokenizer | json/vocab/merges |
 
129
  ## Memory and speed (measured, M4 Max 68.7 GB)
130
 
131
  - Resident during generation: DiT ~21.5 GB + text encoder ~22 GB + VAEs ~6 GB (AdaLN projections
132
+ are precomputed and dropped, freeing ~13.8 GB). With `--release-encoder`, the text encoder is **freed after conditioning** (`prompt_embeds` already built, before the denoise loop), freeing ~27.5 GB (log: `released text encoder after conditioning (freeing ~27.5 GB)` + `mx.clear_cache()` + `tree_flatten` sizing). Peak RSS drops by ~22 GB resident; overhead <1 s.
133
+ - 768x448, 16 steps: **33.9 min** (119 s/step), peak RSS 24.7 GB, no swap. With turbo merge: ~24 min (8 steps). With **turbo8** (pure 8-bit, no merge) at 768x448, 8 steps (7 forwards): **~18.5 min (139 s/step)** vs turbo merge **251 s/step** — **~1.6–1.8× per-pixel** speedup (same canvas, fewer denoise forwards).
134
  - 1344x768 (native 16:9), 8 steps: **~2 h** (996 s/step), peak ~57 GB + compressed memory; requires
135
  an otherwise idle machine (jetsam kills it under heavy ambient load).
136
+ - 704×544 I2V (portrait 9:16) turbo merge, 8 steps (7 forwards): **~33 min (251 s/step)**; turbo8: **~139 s/step** (measured 768x448 baseline, scales linearly per pixel). Lightweight 384×512 I2V is proportionally faster (off-distribution, useful for wiring checks).
137
+ - TeaCache: measured **0 skips / 7 forwards** with default last-block hook (`--teacache`, `thresh 0.2`, `start 3`, `compute_last_step`) at 768x448 turbo8 — probe feature pre-final-norm of last block (block 49/50) barely moves, so gate `rel_l1 <= 0.2` never fires; with `thresh 0.35` only 1/7 skips and still **no wall saving** (partial forward already traverses 49/50 blocks, only saves final norm + heads). Overhead +8–10% without skip. **Recommendation: keep TeaCache OFF by default**; if wall saving is needed, use an earlier hook `--teacache-layer 40` with `--teacache-thresh 0.25–0.35` (trades quality for ~20% block saving per skip). See `PATCHES.md` and `tmp/S9_teacache_spike_RESULT.md`.
138
  - Attention is dense (MiniMax has not released sparse attention); quantization does not reduce the
139
  attention cost — it exists to fit memory.
140
 
 
148
  | 8-bit (this repo) | 768x448, 16 steps | 33.9 min | **9/10** — coherent fox walking a mossy log; stable background; no melting |
149
  | 8-bit + Turbo LoRA | 768x448, 8 steps | ~24 min | High confidence — "impressive for a 4-step turbo LoRA" |
150
  | 4-bit + Turbo LoRA | 1344x768, 8 steps | 2 h 3 min | **95%** — full leap arc; no melting, no banding (frames; mp4 mux was a local script bug, not the model) |
151
+ | I2V E2E canonical (FL2VA, turbo merge) | 544×704 (704h×544w) 9:16, 8 steps (7 forwards), 5 s, seed 1996783985, `--image bridge-...png --anchor first --release-encoder` | ~33 min (251 s/step) | Keyframe anchored, identity preserved; requires `processor/` + vision tower (529 tensors); image optional (FL2VA otherwise uses key) |
152
+ | I2V Turbo8 (pure 8-bit, FL2VA) | 768×448, 8 steps (7 forwards), 5 s | ~18.5 min (139 s/step, ~1.6–1.8× per-pixel vs turbo merge) | Same I2V prompt/image, pure 8-bit DiT without LoRA merge; faster, same wiring |
153
 
154
  Key findings:
155
  - The VAE decoder is **not** the source of artifacts: parity with the reference diffusers
 
164
 
165
  ## Turbo LoRA
166
 
167
+ `turbo_lora_4step_ema.safetensors` (also present as `turbo_lora.safetensors` — hardlink alias, same 779,849,816 bytes) is the 4-step EMA distilled LoRA from
168
  [larryvrh/MiniMax-H3-Turbo-Lora](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora)
169
  (Apache-2.0; sha256 `5a6eeba1…`). Merge it into a transformer copy:
170
 
 
177
 
178
  ## Patches vs upstream
179
 
180
+ `PATCHES.md` documents **five** patch families on branch `video-lab` over base commit `b2f7e4d2` (2026-08-10): (a) DiT QKV blocked layout, (b) text-encoder 8-bit + processor fallback + positional scatter fix, (c) `release_text_encoder` headroom, (d) two-phase TeaCache, (e) I2V processor + vision shards. See `PATCHES.md` for per-file details and line references. The remaining pipeline is otherwise byte-identical to the base commit.
 
 
181
 
182
  ## License
183
 
processor/chat_template.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- for message in messages %}\n {%- if message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content_item in message.content %}\n {%- if 'text' in content_item %}\n {{- content_item.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and message.content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
3
+ }
processor/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
processor/preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "size": {
3
+ "longest_edge": 16777216,
4
+ "shortest_edge": 65536
5
+ },
6
+ "patch_size": 16,
7
+ "temporal_patch_size": 2,
8
+ "merge_size": 2,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "processor_class": "Qwen3VLProcessor",
20
+ "image_processor_type": "Qwen2VLImageProcessorFast"
21
+ }
processor/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
processor/tokenizer_config.json ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ }
213
+ },
214
+ "additional_special_tokens": [
215
+ "<|im_start|>",
216
+ "<|im_end|>",
217
+ "<|object_ref_start|>",
218
+ "<|object_ref_end|>",
219
+ "<|box_start|>",
220
+ "<|box_end|>",
221
+ "<|quad_start|>",
222
+ "<|quad_end|>",
223
+ "<|vision_start|>",
224
+ "<|vision_end|>",
225
+ "<|vision_pad|>",
226
+ "<|image_pad|>",
227
+ "<|video_pad|>",
228
+ "<d>",
229
+ "</d>",
230
+ "<|cutoff|>",
231
+ "<|lyrics_start|>",
232
+ "<|lyrics_end|>",
233
+ "<|caption_start|>",
234
+ "<|caption_end|>"
235
+ ],
236
+ "bos_token": null,
237
+ "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- for message in messages %}\n {%- if message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content_item in message.content %}\n {%- if 'text' in content_item %}\n {{- content_item.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and message.content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
238
+ "clean_up_tokenization_spaces": false,
239
+ "eos_token": "<|im_end|>",
240
+ "errors": "replace",
241
+ "model_max_length": 262144,
242
+ "pad_token": "<|endoftext|>",
243
+ "split_special_tokens": false,
244
+ "tokenizer_class": "Qwen2Tokenizer",
245
+ "unk_token": null
246
+ }
processor/video_preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "size": {
3
+ "longest_edge": 25165824,
4
+ "shortest_edge": 4096
5
+ },
6
+ "patch_size": 16,
7
+ "temporal_patch_size": 2,
8
+ "merge_size": 2,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "processor_class": "Qwen3VLProcessor",
20
+ "video_processor_type": "Qwen3VLVideoProcessor"
21
+ }
processor/vocab.json ADDED
The diff for this file is too large to render. See raw diff
 
text_encoder/model-00005-of-00008-vision.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3e4592712ef68cba15eb83fd730fbf2176ebe8c991b3240be457913c03be39a5
3
+ size 291980081
text_encoder/model-00006-of-00008-vision.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5d943fb9b1b23a476bba0ccb4b6b3fac4c8c5174427dc0e9dffbffc777be530d
3
+ size 341371441
text_encoder/model-00007-of-00008-vision.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec8e63c80cc27069e9ac3699dd2c81690cf67f6322d669b9fe48f21b6c1b4ba2
3
+ size 129267789
text_encoder/vision_quant_config.json ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bits": 8,
3
+ "group_size": 64,
4
+ "mode": "affine",
5
+ "quantized": [
6
+ "blocks.0.attn.proj",
7
+ "blocks.0.attn.qkv",
8
+ "blocks.0.mlp.linear_fc1",
9
+ "blocks.1.attn.proj",
10
+ "blocks.1.attn.qkv",
11
+ "blocks.1.mlp.linear_fc1",
12
+ "blocks.10.attn.proj",
13
+ "blocks.10.attn.qkv",
14
+ "blocks.10.mlp.linear_fc1",
15
+ "blocks.11.attn.proj",
16
+ "blocks.11.attn.qkv",
17
+ "blocks.11.mlp.linear_fc1",
18
+ "blocks.12.attn.proj",
19
+ "blocks.12.attn.qkv",
20
+ "blocks.12.mlp.linear_fc1",
21
+ "blocks.13.attn.proj",
22
+ "blocks.13.attn.qkv",
23
+ "blocks.13.mlp.linear_fc1",
24
+ "blocks.14.attn.proj",
25
+ "blocks.14.attn.qkv",
26
+ "blocks.14.mlp.linear_fc1",
27
+ "blocks.15.attn.proj",
28
+ "blocks.15.attn.qkv",
29
+ "blocks.15.mlp.linear_fc1",
30
+ "blocks.16.attn.proj",
31
+ "blocks.16.attn.qkv",
32
+ "blocks.16.mlp.linear_fc1",
33
+ "blocks.17.attn.proj",
34
+ "blocks.17.attn.qkv",
35
+ "blocks.17.mlp.linear_fc1",
36
+ "blocks.18.attn.proj",
37
+ "blocks.18.attn.qkv",
38
+ "blocks.18.mlp.linear_fc1",
39
+ "blocks.19.attn.proj",
40
+ "blocks.19.attn.qkv",
41
+ "blocks.19.mlp.linear_fc1",
42
+ "blocks.2.attn.proj",
43
+ "blocks.2.attn.qkv",
44
+ "blocks.2.mlp.linear_fc1",
45
+ "blocks.20.attn.proj",
46
+ "blocks.20.attn.qkv",
47
+ "blocks.20.mlp.linear_fc1",
48
+ "blocks.21.attn.proj",
49
+ "blocks.21.attn.qkv",
50
+ "blocks.21.mlp.linear_fc1",
51
+ "blocks.22.attn.proj",
52
+ "blocks.22.attn.qkv",
53
+ "blocks.22.mlp.linear_fc1",
54
+ "blocks.23.attn.proj",
55
+ "blocks.23.attn.qkv",
56
+ "blocks.23.mlp.linear_fc1",
57
+ "blocks.24.attn.proj",
58
+ "blocks.24.attn.qkv",
59
+ "blocks.24.mlp.linear_fc1",
60
+ "blocks.25.attn.proj",
61
+ "blocks.25.attn.qkv",
62
+ "blocks.25.mlp.linear_fc1",
63
+ "blocks.26.attn.proj",
64
+ "blocks.26.attn.qkv",
65
+ "blocks.26.mlp.linear_fc1",
66
+ "blocks.3.attn.proj",
67
+ "blocks.3.attn.qkv",
68
+ "blocks.3.mlp.linear_fc1",
69
+ "blocks.4.attn.proj",
70
+ "blocks.4.attn.qkv",
71
+ "blocks.4.mlp.linear_fc1",
72
+ "blocks.5.attn.proj",
73
+ "blocks.5.attn.qkv",
74
+ "blocks.5.mlp.linear_fc1",
75
+ "blocks.6.attn.proj",
76
+ "blocks.6.attn.qkv",
77
+ "blocks.6.mlp.linear_fc1",
78
+ "blocks.7.attn.proj",
79
+ "blocks.7.attn.qkv",
80
+ "blocks.7.mlp.linear_fc1",
81
+ "blocks.8.attn.proj",
82
+ "blocks.8.attn.qkv",
83
+ "blocks.8.mlp.linear_fc1",
84
+ "blocks.9.attn.proj",
85
+ "blocks.9.attn.qkv",
86
+ "blocks.9.mlp.linear_fc1",
87
+ "deepstack_merger_list.0.linear_fc1",
88
+ "deepstack_merger_list.0.linear_fc2",
89
+ "deepstack_merger_list.1.linear_fc1",
90
+ "deepstack_merger_list.1.linear_fc2",
91
+ "deepstack_merger_list.2.linear_fc1",
92
+ "deepstack_merger_list.2.linear_fc2",
93
+ "merger.linear_fc1",
94
+ "merger.linear_fc2"
95
+ ]
96
+ }
turbo_lora.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a6eeba171cf183020a4ad48774bb2968f29f8168afd6ec17a04987f3528b4ea
3
+ size 779849816