Download convert.py from maxffarrell/d3-lite-CoreAI: direct link, hf CLI and curl.
- Browser
- Download file 6.34 kB
-
https://huggingface.co/maxffarrell/d3-lite-CoreAI/resolve/main/convert.py
- Command line
-
hf download hf://maxffarrell/d3-lite-CoreAI/convert.py
-
curl -L -o convert.py https://huggingface.co/maxffarrell/d3-lite-CoreAI/resolve/main/convert.py
6.34 kB
| """Export pinned Decision 3.0 text inference; vision export is separate.""" | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| from coreai_torch import TorchConverter, get_decomp_table | |
| from safetensors.torch import load_file | |
| from transformers import Qwen3_5Model | |
| from transformers.models.qwen3_5.modeling_qwen3_5 import apply_rotary_pos_emb_vision | |
| from transformers.vision_utils import ( | |
| get_vision_interpolation_indices_and_weights, | |
| get_vision_position_ids, | |
| ) | |
| class DecisionText(torch.nn.Module): | |
| def __init__(self, backbone, readout): | |
| super().__init__() | |
| self.language_model = backbone.language_model | |
| self.register_buffer("readout", readout.float()) | |
| def forward( | |
| self, input_ids, attention_mask, position_ids, visual_embeddings, visual_mask | |
| ): | |
| embeddings = self.language_model.embed_tokens(input_ids) | |
| embeddings = torch.where( | |
| visual_mask, visual_embeddings.to(embeddings.dtype), embeddings | |
| ) | |
| hidden = self.language_model( | |
| inputs_embeds=embeddings, | |
| attention_mask={ | |
| "full_attention": attention_mask[:, None, None, :].bool(), | |
| "linear_attention": attention_mask, | |
| }, | |
| position_ids=position_ids, | |
| use_cache=False, | |
| ).last_hidden_state | |
| return hidden[:, -1].float() @ self.readout.T | |
| class DecisionVision(torch.nn.Module): | |
| """The unchanged vision weights at a fixed one-image 224px grid.""" | |
| def __init__(self, visual, side=224): | |
| super().__init__() | |
| self.visual = visual | |
| grid = torch.tensor( | |
| [[1, side // visual.config.patch_size, side // visual.config.patch_size]] | |
| ) | |
| indices, weights = get_vision_interpolation_indices_and_weights( | |
| grid, | |
| num_grid_per_side=visual.num_grid_per_side, | |
| mode=visual.interpolation_mode, | |
| align_corners=visual.interpolation_align_corners, | |
| spatial_merge_size=visual.config.spatial_merge_size, | |
| kwargs={}, | |
| ) | |
| self.register_buffer("indices", indices) | |
| self.register_buffer("weights", weights) | |
| self.register_buffer( | |
| "positions", | |
| get_vision_position_ids(grid, visual.spatial_merge_size, kwargs={}), | |
| ) | |
| def forward(self, pixel_values): | |
| v = self.visual | |
| hidden = F.linear( | |
| pixel_values.to(v.patch_embed.proj.weight.dtype), | |
| v.patch_embed.proj.weight.flatten(1), | |
| v.patch_embed.proj.bias, | |
| ) | |
| hidden = hidden + (v.pos_embed(self.indices) * self.weights[:, :, None]).sum( | |
| 1 | |
| ).to(hidden.dtype) | |
| cos, sin = v.rotary_pos_emb(hidden, self.positions) | |
| for block in v.blocks: | |
| x = block.norm1(hidden) | |
| attn = block.attn | |
| q, k, value = ( | |
| attn.qkv(x) | |
| .reshape(x.shape[0], 3, attn.num_heads, -1) | |
| .permute(1, 0, 2, 3) | |
| .unbind(0) | |
| ) | |
| q, k = apply_rotary_pos_emb_vision(q, k, cos, sin) | |
| y = F.scaled_dot_product_attention( | |
| q.transpose(0, 1).unsqueeze(0), | |
| k.transpose(0, 1).unsqueeze(0), | |
| value.transpose(0, 1).unsqueeze(0), | |
| scale=attn.scaling, | |
| ) | |
| hidden = hidden + attn.proj( | |
| y.squeeze(0).transpose(0, 1).reshape(x.shape[0], -1) | |
| ) | |
| hidden = hidden + block.mlp(block.norm2(hidden)) | |
| return v.merger(hidden).float() | |
| def save(model, sample, path): | |
| with torch.no_grad(): | |
| exported = torch.export.export(model, sample, strict=False) | |
| print("Decomposing", flush=True) | |
| exported = exported.run_decompositions(get_decomp_table()) | |
| print("Converting", flush=True) | |
| program = TorchConverter().add_exported_program(exported).to_coreai() | |
| program.save_asset(path) | |
| return exported.graph_signature.user_outputs[0] | |
| def main(): | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--source", default="upstream/d3-edge") | |
| p.add_argument("--output", default="bundles/d3-edge-CoreAI") | |
| p.add_argument("--length", type=int, default=512) | |
| p.add_argument("--precision", choices=["bfloat16", "float32"], default="bfloat16") | |
| a = p.parse_args() | |
| torch.set_num_threads(4) | |
| source = Path(a.source) | |
| output = Path(a.output) | |
| output.mkdir(parents=True, exist_ok=True) | |
| print("Loading backbone", flush=True) | |
| backbone = Qwen3_5Model.from_pretrained( | |
| source, dtype=getattr(torch, a.precision), attn_implementation="sdpa" | |
| ).eval() | |
| model = DecisionText( | |
| backbone, load_file(str(source / "readout.safetensors"))["weight"] | |
| ).eval() | |
| hidden = backbone.config.text_config.hidden_size | |
| sample = ( | |
| torch.ones(1, a.length, dtype=torch.int32), | |
| torch.ones(1, a.length, dtype=torch.int32), | |
| torch.arange(a.length, dtype=torch.int32).view(1, 1, -1).expand(4, 1, -1), | |
| torch.zeros(1, a.length, hidden), | |
| torch.zeros(1, a.length, 1, dtype=torch.bool), | |
| ) | |
| print("Exporting", flush=True) | |
| out = save(model, sample, output / "decision.aimodel") | |
| vision = DecisionVision(backbone.visual).eval() | |
| patches = (224 // backbone.config.vision_config.patch_size) ** 2 | |
| width = ( | |
| 3 | |
| * backbone.config.vision_config.temporal_patch_size | |
| * backbone.config.vision_config.patch_size**2 | |
| ) | |
| vision_out = save(vision, (torch.zeros(patches, width),), output / "vision.aimodel") | |
| (output / "manifest.json").write_text( | |
| json.dumps( | |
| { | |
| "source": source.name, | |
| "sequence_length": a.length, | |
| "hidden_size": hidden, | |
| "inputs": [ | |
| "input_ids", | |
| "attention_mask", | |
| "position_ids", | |
| "visual_embeddings", | |
| "visual_mask", | |
| ], | |
| "output": out, | |
| "vision_output": vision_out, | |
| "image_side": 224, | |
| "precision": a.precision, | |
| }, | |
| indent=2, | |
| ) | |
| + "\n" | |
| ) | |
| print("Saved", flush=True) | |
| if __name__ == "__main__": | |
| main() | |