| import math
|
| import os
|
| import threading
|
| import tkinter as tk
|
| from pathlib import Path
|
| from tkinter import filedialog, messagebox, ttk
|
| import time
|
|
|
| import torch
|
| import torch.nn as nn
|
| import torch.nn.functional as F
|
| from safetensors.torch import load_file, save_file
|
|
|
|
|
|
|
|
|
|
|
|
|
| PREDICTOR_PATH = "./model.safetensors"
|
|
|
| HIDDEN_DIM = 384
|
| NUM_LAYERS = 8
|
|
|
| GLOBAL_FEATURES = 24
|
| SHAPE_CONT_FEATURES = 32
|
| MAX_SHAPE_DIMS = 8
|
| BITS_PER_DIM = 16
|
| SHAPE_BINARY_FEATURES = MAX_SHAPE_DIMS * BITS_PER_DIM
|
|
|
| FEATURES = (
|
| 8
|
| + 24
|
| + 64
|
| + GLOBAL_FEATURES
|
| + SHAPE_CONT_FEATURES
|
| + SHAPE_BINARY_FEATURES
|
| )
|
|
|
| CHUNK = 65536
|
|
|
|
|
|
|
|
|
|
|
|
|
| def get_device():
|
| if not torch.cuda.is_available():
|
| raise RuntimeError("CUDA GPU required.")
|
| return torch.device("cuda")
|
|
|
|
|
|
|
|
|
|
|
|
|
| def requires_fp32(shape, limit):
|
| """
|
| Safety fallback.
|
|
|
| If any tensor dimension exceeds the configured limit,
|
| the tensor is processed entirely through the FP32 path
|
| (only relevant when the user selected FP64 mode + fallback).
|
| """
|
| return any(int(size) > limit for size in shape)
|
|
|
|
|
| def is_simple_shape(shape):
|
| """True for scalars / pure 1-D (or effectively 1-D) tensors."""
|
| return sum(size > 1 for size in shape) <= 1
|
|
|
|
|
|
|
|
|
|
|
|
|
| def gather_coordinates(tensor, coordinates):
|
| indices = tuple(
|
| coordinates[:, dim]
|
| for dim in range(coordinates.shape[1])
|
| )
|
| return tensor[indices]
|
|
|
|
|
| def gather_neighbor(tensor, coordinates, dimension, offset):
|
| coords = coordinates.clone()
|
|
|
| coords[:, dimension] = (
|
| coords[:, dimension] + offset
|
| ).clamp(
|
| 0,
|
| tensor.shape[dimension] - 1,
|
| )
|
|
|
| return gather_coordinates(tensor, coords)
|
|
|
|
|
| def make_coordinates(linear, shape):
|
| coords = []
|
| rem = linear
|
|
|
| for dimension in reversed(range(len(shape))):
|
| size = shape[dimension]
|
| coords.append(rem % size)
|
| rem = rem // size
|
|
|
| coords.reverse()
|
|
|
| return torch.stack(coords, dim=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
| def element_features(quantized, coordinates, dtype):
|
| """
|
| EXACTLY matches the training architecture.
|
|
|
| 8 features:
|
| x
|
| abs(x)
|
| sign(x)
|
| sign(x) * log1p(abs(x))
|
| x^2
|
| x^3
|
| 1 / (1 + abs(x))
|
| tanh(x)
|
| """
|
|
|
| x = gather_coordinates(
|
| quantized,
|
| coordinates,
|
| ).to(dtype)
|
|
|
| abs_x = x.abs()
|
|
|
| return torch.stack(
|
| [
|
| x,
|
| abs_x,
|
| torch.sign(x),
|
| torch.sign(x) * torch.log1p(abs_x),
|
| x * x,
|
| x * x * x,
|
| 1.0 / (1.0 + abs_x),
|
| torch.tanh(x),
|
| ],
|
| dim=1,
|
| )
|
|
|
|
|
| def local_features(tensor, coordinates, dtype):
|
| """
|
| EXACTLY matches the training architecture.
|
|
|
| Four dimensions maximum × eight statistics = 32,
|
| then truncated/padded to 24.
|
| """
|
|
|
| summaries = []
|
|
|
| for dim in range(min(tensor.ndim, 4)):
|
| values = torch.stack(
|
| [
|
| gather_neighbor(
|
| tensor,
|
| coordinates,
|
| dim,
|
| offset,
|
| ).to(dtype)
|
| for offset in (-2, -1, 0, 1, 2)
|
| ],
|
| dim=1,
|
| )
|
|
|
| center = values[:, 2]
|
|
|
| summaries.append(
|
| torch.stack(
|
| [
|
| values.mean(1),
|
| values.std(1),
|
| values.min(1).values,
|
| values.max(1).values,
|
| center - values[:, 1],
|
| values[:, 3] - center,
|
| values[:, 3] - 2 * center + values[:, 1],
|
| (
|
| values[:, 4]
|
| - 4 * values[:, 3]
|
| + 6 * center
|
| - 4 * values[:, 1]
|
| + values[:, 0]
|
| ),
|
| ],
|
| dim=1,
|
| )
|
| )
|
|
|
| if summaries:
|
| result = torch.cat(summaries, dim=1)
|
|
|
| if result.shape[1] >= 24:
|
| return result[:, :24]
|
|
|
| return F.pad(
|
| result,
|
| (0, 24 - result.shape[1]),
|
| )
|
|
|
| return torch.zeros(
|
| coordinates.shape[0],
|
| 24,
|
| device=tensor.device,
|
| dtype=dtype,
|
| )
|
|
|
|
|
| def dimension_features(shape, coordinates, dtype):
|
| """
|
| EXACTLY matches training.
|
|
|
| 64 features.
|
| """
|
|
|
| count = coordinates.shape[0]
|
|
|
| result = torch.zeros(
|
| count,
|
| 64,
|
| device=coordinates.device,
|
| dtype=dtype,
|
| )
|
|
|
| result[:, 0] = len(shape) / 8.0
|
|
|
| for dim in range(min(len(shape), 8)):
|
| size = max(shape[dim], 1)
|
|
|
| n = (
|
| coordinates[:, dim].to(dtype)
|
| / max(size - 1, 1)
|
| )
|
|
|
| base = 1 + dim * 7
|
|
|
| result[:, base:base + 7] = torch.stack(
|
| [
|
| n,
|
| torch.sin(n * math.pi),
|
| torch.cos(n * math.pi),
|
| torch.sin(n * 2 * math.pi),
|
| torch.cos(n * 2 * math.pi),
|
| n * n,
|
| n - 0.5,
|
| ],
|
| dim=1,
|
| )
|
|
|
| return result
|
|
|
|
|
| def global_statistics(tensor, dtype):
|
| """
|
| EXACTLY matches the training script.
|
|
|
| IMPORTANT:
|
| This produces 23 values, not 24.
|
| """
|
|
|
| x = tensor.to(dtype).reshape(-1)
|
|
|
| if x.numel() > 65536:
|
| x = x[
|
| torch.randint(
|
| 0,
|
| x.numel(),
|
| (65536,),
|
| device=x.device,
|
| )
|
| ]
|
|
|
| mean = x.mean()
|
|
|
| centered = x - mean
|
|
|
| std = torch.sqrt(
|
| centered.pow(2).mean() + 1e-12
|
| )
|
|
|
| normalized = centered / std
|
|
|
| return torch.stack(
|
| [
|
| mean,
|
| std,
|
| x.abs().mean(),
|
| torch.sqrt(
|
| x.pow(2).mean() + 1e-12
|
| ),
|
| x.min(),
|
| x.max(),
|
|
|
| *[
|
| torch.quantile(x, q)
|
| for q in (
|
| 0.01,
|
| 0.05,
|
| 0.10,
|
| 0.25,
|
| 0.50,
|
| 0.75,
|
| 0.90,
|
| 0.95,
|
| 0.99,
|
| )
|
| ],
|
|
|
| normalized.pow(3).mean(),
|
| normalized.pow(4).mean() - 3.0,
|
|
|
| (x > 0).to(dtype).mean(),
|
| (x < 0).to(dtype).mean(),
|
| (x == 0).to(dtype).mean(),
|
|
|
| (x - mean).abs().mean(),
|
|
|
| torch.sqrt(
|
| x.pow(2).mean() + 1e-12
|
| ),
|
|
|
| normalized.abs().mean(),
|
|
|
| torch.tanh(x).mean(),
|
| ]
|
| )
|
|
|
|
|
| def shape_features_continuous(shape, count, device, dtype):
|
| """
|
| Original continuous 32-dimensional shape features.
|
| """
|
|
|
| result = torch.zeros(
|
| count,
|
| SHAPE_CONT_FEATURES,
|
| device=device,
|
| dtype=dtype,
|
| )
|
|
|
| total = math.prod(shape)
|
|
|
| result[:, 0] = len(shape) / 8.0
|
| result[:, 1] = math.log1p(float(total))
|
|
|
| for dim in range(min(len(shape), 8)):
|
| base = 2 + dim * 3
|
| size = float(shape[dim])
|
|
|
| result[:, base] = math.log1p(size)
|
| result[:, base + 1] = (
|
| size / max(float(total), 1.0)
|
| )
|
| result[:, base + 2] = float(size > 1)
|
|
|
| return result
|
|
|
|
|
| def shape_features_binary(shape, count, device, dtype):
|
| """
|
| 128 binary shape features.
|
|
|
| 8 dimensions × 16 bits.
|
| """
|
|
|
| result = torch.zeros(
|
| count,
|
| SHAPE_BINARY_FEATURES,
|
| device=device,
|
| dtype=dtype,
|
| )
|
|
|
| for dim in range(
|
| min(len(shape), MAX_SHAPE_DIMS)
|
| ):
|
| size = int(shape[dim])
|
| base = dim * BITS_PER_DIM
|
|
|
| for bit in range(BITS_PER_DIM):
|
| result[:, base + bit] = float(
|
| (size >> bit) & 1
|
| )
|
|
|
| return result
|
|
|
|
|
| def build_features(
|
| fp_high,
|
| quantized,
|
| coordinates,
|
| statistics,
|
| dtype,
|
| ):
|
| """
|
| EXACT feature layout from training.
|
| """
|
|
|
| element = element_features(
|
| quantized,
|
| coordinates,
|
| dtype,
|
| )
|
|
|
| local = local_features(
|
| quantized,
|
| coordinates,
|
| dtype,
|
| )
|
|
|
| dimension = dimension_features(
|
| tuple(fp_high.shape),
|
| coordinates,
|
| dtype,
|
| )
|
|
|
| statistics = (
|
| statistics
|
| .to(dtype)
|
| .unsqueeze(0)
|
| .expand(coordinates.shape[0], -1)
|
| )
|
|
|
| shape_cont = shape_features_continuous(
|
| tuple(fp_high.shape),
|
| coordinates.shape[0],
|
| coordinates.device,
|
| dtype,
|
| )
|
|
|
| shape_binary = shape_features_binary(
|
| tuple(fp_high.shape),
|
| coordinates.shape[0],
|
| coordinates.device,
|
| dtype,
|
| )
|
|
|
| features = torch.cat(
|
| [
|
| element,
|
| local,
|
| dimension,
|
| statistics,
|
| shape_cont,
|
| shape_binary,
|
| ],
|
| dim=1,
|
| )
|
|
|
| if features.shape[1] != FEATURES:
|
| raise RuntimeError(
|
| f"Feature mismatch: expected {FEATURES}, "
|
| f"got {features.shape[1]}"
|
| )
|
|
|
| return features
|
|
|
|
|
|
|
|
|
|
|
|
|
| class ResidualBlock(nn.Module):
|
| """
|
| EXACTLY matches the training script.
|
| """
|
|
|
| def __init__(self, hidden):
|
| super().__init__()
|
|
|
| self.norm = nn.LayerNorm(hidden)
|
|
|
| self.fc1 = nn.Linear(
|
| hidden,
|
| hidden * 2,
|
| )
|
|
|
| self.fc2 = nn.Linear(
|
| hidden * 2,
|
| hidden,
|
| )
|
|
|
| self.dropout = nn.Dropout(0.05)
|
|
|
| def forward(self, x):
|
| residual = x
|
|
|
| h = self.norm(x)
|
| h = F.gelu(self.fc1(h))
|
| h = self.dropout(self.fc2(h))
|
|
|
| return F.gelu(
|
| residual + h
|
| )
|
|
|
|
|
| class UniversalWeightReconstructor(nn.Module):
|
| """
|
| EXACTLY matches the training architecture.
|
|
|
| Heads:
|
|
|
| element_head -> per-element residual
|
|
|
| tensor_head -> scalar per-tensor correction
|
|
|
| Final reconstruction residual:
|
|
|
| element_pred + tensor_pred
|
| """
|
|
|
| def __init__(
|
| self,
|
| input_dim,
|
| hidden_dim,
|
| layers,
|
| ):
|
| super().__init__()
|
|
|
| self.input = nn.Sequential(
|
| nn.Linear(
|
| input_dim,
|
| hidden_dim,
|
| ),
|
| nn.LayerNorm(hidden_dim),
|
| nn.GELU(),
|
| )
|
|
|
| self.blocks = nn.ModuleList(
|
| [
|
| ResidualBlock(hidden_dim)
|
| for _ in range(layers)
|
| ]
|
| )
|
|
|
| self.element_head = nn.Sequential(
|
| nn.LayerNorm(hidden_dim),
|
| nn.Linear(
|
| hidden_dim,
|
| hidden_dim // 2,
|
| ),
|
| nn.GELU(),
|
| nn.Linear(
|
| hidden_dim // 2,
|
| 1,
|
| ),
|
| )
|
|
|
| self.tensor_head = nn.Sequential(
|
| nn.LayerNorm(hidden_dim),
|
| nn.Linear(
|
| hidden_dim,
|
| hidden_dim // 4,
|
| ),
|
| nn.GELU(),
|
| nn.Linear(
|
| hidden_dim // 4,
|
| 1,
|
| ),
|
| )
|
|
|
| def forward(self, x):
|
| h = self.input(x)
|
|
|
| for block in self.blocks:
|
| h = block(h)
|
|
|
| element_pred = (
|
| self.element_head(h)
|
| .squeeze(-1)
|
| )
|
|
|
| tensor_pred = (
|
| self.tensor_head(
|
| h.mean(
|
| dim=0,
|
| keepdim=True,
|
| )
|
| )
|
| .squeeze()
|
| )
|
|
|
| return (
|
| element_pred,
|
| tensor_pred,
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
| def load_predictor(device, model_dtype):
|
| predictor_path = Path(
|
| PREDICTOR_PATH
|
| )
|
|
|
| if not predictor_path.exists():
|
| raise FileNotFoundError(
|
| f"Predictor checkpoint not found:\n"
|
| f"{predictor_path}"
|
| )
|
|
|
| state = load_file(
|
| str(predictor_path),
|
| device="cpu",
|
| )
|
|
|
| model = UniversalWeightReconstructor(
|
| input_dim=FEATURES,
|
| hidden_dim=HIDDEN_DIM,
|
| layers=NUM_LAYERS,
|
| )
|
|
|
| expected_keys = set(
|
| model.state_dict().keys()
|
| )
|
|
|
| actual_keys = set(
|
| state.keys()
|
| )
|
|
|
| missing = expected_keys - actual_keys
|
| unexpected = actual_keys - expected_keys
|
|
|
| if missing or unexpected:
|
| raise RuntimeError(
|
| "Predictor checkpoint architecture mismatch.\n\n"
|
| f"Expected {FEATURES} input features.\n"
|
| f"Expected HIDDEN_DIM={HIDDEN_DIM}.\n"
|
| f"Expected NUM_LAYERS={NUM_LAYERS}.\n\n"
|
| f"Missing keys:\n{sorted(missing)}\n\n"
|
| f"Unexpected keys:\n{sorted(unexpected)}"
|
| )
|
|
|
|
|
| state = {
|
| key: value.to(model_dtype)
|
| for key, value in state.items()
|
| }
|
|
|
| model.load_state_dict(
|
| state,
|
| strict=True,
|
| )
|
|
|
| model = model.to(
|
| device=device,
|
| dtype=model_dtype,
|
| )
|
|
|
| model.eval()
|
|
|
| return model
|
|
|
|
|
|
|
|
|
|
|
|
|
| @torch.inference_mode()
|
| def predict_tensor(
|
| predictor_fp64,
|
| predictor_fp32,
|
| fp_high,
|
| quantized,
|
| use_fp32_path,
|
| output_dtype,
|
| ):
|
| """
|
| Reconstruct one tensor.
|
|
|
| Normal (FP64 mode, small tensors):
|
| FP64 feature generation + FP64 predictor
|
|
|
| Safety fallback / pure FP32 mode:
|
| FP32 feature generation + FP32 predictor
|
|
|
| Final output dtype is controlled by the GUI selection
|
| (FP64 or FP32).
|
| """
|
|
|
| device = fp_high.device
|
| shape = tuple(fp_high.shape)
|
| n = fp_high.numel()
|
|
|
| if use_fp32_path:
|
| predictor = predictor_fp32
|
| compute_dtype = torch.float32
|
| else:
|
| predictor = predictor_fp64
|
| compute_dtype = torch.float64
|
|
|
|
|
| output = torch.empty(
|
| n,
|
| device=device,
|
| dtype=compute_dtype,
|
| )
|
|
|
|
|
|
|
| statistics = global_statistics(
|
| fp_high,
|
| compute_dtype,
|
| )
|
|
|
| done = 0
|
|
|
| while done < n:
|
| end = min(
|
| done + CHUNK,
|
| n,
|
| )
|
|
|
| linear = torch.arange(
|
| done,
|
| end,
|
| device=device,
|
| dtype=torch.long,
|
| )
|
|
|
| coordinates = make_coordinates(
|
| linear,
|
| shape,
|
| )
|
|
|
| features = build_features(
|
| fp_high,
|
| quantized,
|
| coordinates,
|
| statistics,
|
| compute_dtype,
|
| )
|
|
|
| element_pred, tensor_pred = predictor(
|
| features
|
| )
|
|
|
|
|
|
|
|
|
| residual = (
|
| element_pred
|
| + tensor_pred
|
| )
|
|
|
| base = gather_coordinates(
|
| quantized,
|
| coordinates,
|
| ).to(compute_dtype)
|
|
|
| output[
|
| done:end
|
| ] = base + residual
|
|
|
| done = end
|
|
|
| return (
|
| output
|
| .to(output_dtype)
|
| .reshape(shape)
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
| def run_conversion(
|
| path,
|
| log,
|
| log_bold,
|
| save_path,
|
| skip_simple,
|
| prefer_fp64,
|
| use_large_fp32_fallback,
|
| fp32_shape_limit,
|
| ):
|
| device = get_device()
|
|
|
| output_dtype = torch.float64 if prefer_fp64 else torch.float32
|
| output_label = "FP64" if prefer_fp64 else "FP32"
|
|
|
| log(
|
| f"GPU: "
|
| f"{torch.cuda.get_device_name(0)}"
|
| )
|
|
|
| properties = (
|
| torch.cuda.get_device_properties(0)
|
| )
|
|
|
| log(
|
| f"VRAM: "
|
| f"{properties.total_memory / 1024**3:.2f} GB"
|
| )
|
|
|
| log(
|
| f"Predictor architecture: "
|
| f"{FEATURES} -> {HIDDEN_DIM} "
|
| f"with {NUM_LAYERS} residual blocks"
|
| )
|
|
|
| model_dtype = torch.float64 if prefer_fp64 else torch.float32
|
|
|
| log(
|
| f"Selected precision mode: "
|
| f"{output_label}"
|
| )
|
|
|
| if prefer_fp64 and use_large_fp32_fallback:
|
| log(
|
| f"Large-tensor FP32 fallback enabled: "
|
| f"any dim > {fp32_shape_limit:,}"
|
| )
|
| else:
|
| log(
|
| "Large-tensor FP32 fallback: disabled"
|
| )
|
|
|
| log(
|
| f"Output dtype: "
|
| f"{output_dtype}"
|
| )
|
|
|
| log(
|
| f"Skip 1D/simple: "
|
| f"{skip_simple}"
|
| )
|
|
|
| log(
|
| f"Loading source model: {path}"
|
| )
|
|
|
| state = load_file(
|
| path,
|
| device="cuda",
|
| )
|
|
|
| log(
|
| f"Loaded {len(state):,} tensors"
|
| )
|
|
|
|
|
| predictor_primary = load_predictor(
|
| device,
|
| model_dtype,
|
| )
|
|
|
|
|
|
|
| if prefer_fp64:
|
| predictor_fp64 = predictor_primary
|
|
|
| predictor_fp32 = (
|
| UniversalWeightReconstructor(
|
| input_dim=FEATURES,
|
| hidden_dim=HIDDEN_DIM,
|
| layers=NUM_LAYERS,
|
| )
|
| .to(
|
| device=device,
|
| dtype=torch.float32,
|
| )
|
| )
|
|
|
| predictor_fp32.load_state_dict(
|
| {
|
| key: value.to(torch.float32)
|
| for key, value in predictor_fp64.state_dict().items()
|
| },
|
| strict=True,
|
| )
|
| predictor_fp32.eval()
|
|
|
| log("Loaded matched FP64 predictor.")
|
| log("Created FP32 safety predictor.")
|
| else:
|
| predictor_fp64 = None
|
| predictor_fp32 = predictor_primary
|
| log("Loaded FP32 predictor.")
|
|
|
| output = {}
|
|
|
| total_tensors = len(state)
|
|
|
| converted = 0
|
| skipped = 0
|
| fp32_fallbacks = 0
|
| unsupported_casts = 0
|
|
|
| start_time = time.time()
|
|
|
| for index, (name, tensor) in enumerate(
|
| state.items(),
|
| 1,
|
| ):
|
| shape = tuple(
|
| tensor.shape
|
| )
|
|
|
|
|
|
|
|
|
|
|
| if not tensor.is_floating_point():
|
| output[name] = tensor
|
|
|
| log(
|
| f"[{index}/{total_tensors}] "
|
| f"{name} {shape} "
|
| f"unchanged (non-float)"
|
| )
|
|
|
| continue
|
|
|
|
|
|
|
|
|
|
|
| original_dtype = tensor.dtype
|
| src_label = str(original_dtype).replace("torch.", "")
|
|
|
| if original_dtype in (
|
| torch.float16,
|
| torch.bfloat16,
|
| ):
|
|
|
| quantized = tensor
|
| cast_note = ""
|
| else:
|
|
|
| unsupported_casts += 1
|
| log_bold(
|
| f"WARNING: Tensor '{name}' has dtype {src_label}. "
|
| f"The model was NOT trained for this dtype. "
|
| f"Casting to BF16 before reconstruction."
|
| )
|
| quantized = tensor.to(torch.bfloat16)
|
| cast_note = f" (cast {src_label}→BF16)"
|
|
|
|
|
|
|
|
|
|
|
| if (
|
| skip_simple
|
| and is_simple_shape(shape)
|
| ):
|
| output[name] = (
|
| quantized
|
| .to(output_dtype)
|
| .contiguous()
|
| )
|
|
|
| skipped += 1
|
|
|
| log(
|
| f"[{index}/{total_tensors}] "
|
| f"{name} {shape} "
|
| f"simple shape -> {output_label} "
|
| f"(skipped reconstruction){cast_note}"
|
| )
|
|
|
| continue
|
|
|
|
|
|
|
|
|
|
|
| use_fp32_path = False
|
|
|
| if not prefer_fp64:
|
|
|
| use_fp32_path = True
|
| precision_label = "FP32"
|
| else:
|
|
|
| if (
|
| use_large_fp32_fallback
|
| and requires_fp32(shape, fp32_shape_limit)
|
| ):
|
| use_fp32_path = True
|
| fp32_fallbacks += 1
|
| precision_label = "FP32 SAFETY"
|
| else:
|
| use_fp32_path = False
|
| precision_label = "FP64"
|
|
|
|
|
|
|
| compute_dtype = (
|
| torch.float32 if use_fp32_path else torch.float64
|
| )
|
| fp_high = quantized.to(compute_dtype)
|
|
|
| try:
|
| predicted = predict_tensor(
|
| predictor_fp64,
|
| predictor_fp32,
|
| fp_high,
|
| quantized,
|
| use_fp32_path,
|
| output_dtype,
|
| )
|
|
|
| output[name] = (
|
| predicted
|
| .contiguous()
|
| )
|
|
|
| converted += 1
|
|
|
| if original_dtype == torch.bfloat16:
|
| src_dtype = "BF16"
|
| elif original_dtype == torch.float16:
|
| src_dtype = "FP16"
|
| else:
|
| src_dtype = src_label
|
|
|
| log(
|
| f"[{index}/{total_tensors}] "
|
| f"{name} {shape} "
|
| f"{src_dtype} -> "
|
| f"{precision_label} predictor -> "
|
| f"{output_label} reconstructed{cast_note}"
|
| )
|
|
|
| except Exception as exc:
|
| raise RuntimeError(
|
| f"\nReconstruction failed.\n"
|
| f"Tensor: {name}\n"
|
| f"Shape: {shape}\n"
|
| f"Source dtype: {original_dtype}\n"
|
| f"Predictor dtype: {compute_dtype}\n"
|
| f"Original error: {exc}"
|
| ) from exc
|
|
|
| finally:
|
| del quantized
|
| del fp_high
|
|
|
| if "predicted" in locals():
|
| del predicted
|
|
|
| if converted % 10 == 0:
|
| torch.cuda.empty_cache()
|
|
|
|
|
|
|
|
|
|
|
| log("")
|
| log(
|
| f"Reconstructed tensors : "
|
| f"{converted:,}"
|
| )
|
|
|
| log(
|
| f"Simple tensors skipped: "
|
| f"{skipped:,}"
|
| )
|
|
|
| log(
|
| f"FP32 safety fallbacks : "
|
| f"{fp32_fallbacks:,}"
|
| )
|
|
|
| if unsupported_casts > 0:
|
| log_bold(
|
| f"Unsupported dtypes cast to BF16: "
|
| f"{unsupported_casts:,}"
|
| )
|
|
|
| log(
|
| f"Writing {output_label} model..."
|
| )
|
|
|
| save_file(
|
| output,
|
| save_path,
|
| )
|
|
|
| elapsed = (
|
| time.time()
|
| - start_time
|
| )
|
|
|
| log(
|
| f"Saved: {save_path}"
|
| )
|
|
|
| log(
|
| f"Time : {elapsed:.2f}s"
|
| )
|
|
|
| del output
|
| del state
|
| if predictor_fp64 is not None:
|
| del predictor_fp64
|
| del predictor_fp32
|
|
|
| torch.cuda.empty_cache()
|
|
|
| log("")
|
| log("DONE")
|
|
|
|
|
|
|
|
|
|
|
|
|
| class App:
|
| def __init__(self, root):
|
| self.root = root
|
|
|
| root.title(
|
| "Weight Reconstructor "
|
| "(FP16/BF16 → FP32/FP64)"
|
| )
|
|
|
| root.geometry(
|
| "1000x820"
|
| )
|
|
|
| self.path = tk.StringVar()
|
|
|
|
|
| top = ttk.Frame(
|
| root,
|
| padding=10,
|
| )
|
| top.pack(fill="x")
|
|
|
| ttk.Label(
|
| top,
|
| text="Source Model:",
|
| ).pack(side="left")
|
|
|
| ttk.Entry(
|
| top,
|
| textvariable=self.path,
|
| width=70,
|
| ).pack(
|
| side="left",
|
| padx=8,
|
| fill="x",
|
| expand=True,
|
| )
|
|
|
| ttk.Button(
|
| top,
|
| text="Browse",
|
| command=self.browse,
|
| ).pack(side="left")
|
|
|
|
|
| controls = ttk.Frame(
|
| root,
|
| padding=(10, 0),
|
| )
|
| controls.pack(fill="x")
|
|
|
| self.save = tk.BooleanVar(value=True)
|
|
|
| ttk.Checkbutton(
|
| controls,
|
| text="Save output",
|
| variable=self.save,
|
| ).pack(side="left")
|
|
|
| self.skip_simple = tk.BooleanVar(
|
| value=False
|
| )
|
|
|
| ttk.Checkbutton(
|
| controls,
|
| text=(
|
| "Skip 1D / simple-shape "
|
| "tensors (cast only)"
|
| ),
|
| variable=self.skip_simple,
|
| ).pack(
|
| side="left",
|
| padx=(20, 0),
|
| )
|
|
|
| self.convert_button = ttk.Button(
|
| controls,
|
| text="Convert",
|
| command=self.start,
|
| )
|
| self.convert_button.pack(
|
| side="left",
|
| padx=20,
|
| )
|
|
|
|
|
| precision_frame = ttk.LabelFrame(
|
| root,
|
| text="Precision mode (compute + save)",
|
| padding=10,
|
| )
|
| precision_frame.pack(
|
| fill="x",
|
| padx=10,
|
| pady=(8, 0),
|
| )
|
|
|
| self.precision_mode = tk.StringVar(value="FP64")
|
|
|
| ttk.Radiobutton(
|
| precision_frame,
|
| text="FP64 (higher precision)",
|
| variable=self.precision_mode,
|
| value="FP64",
|
| command=self._update_fallback_state,
|
| ).pack(side="left")
|
|
|
| ttk.Radiobutton(
|
| precision_frame,
|
| text="FP32 (faster / lower VRAM)",
|
| variable=self.precision_mode,
|
| value="FP32",
|
| command=self._update_fallback_state,
|
| ).pack(side="left", padx=(20, 0))
|
|
|
|
|
| self.use_large_fp32 = tk.BooleanVar(value=True)
|
|
|
| self.fallback_check = ttk.Checkbutton(
|
| precision_frame,
|
| text="Use FP32 for tensors larger than",
|
| variable=self.use_large_fp32,
|
| command=self._update_fallback_state,
|
| )
|
| self.fallback_check.pack(side="left", padx=(30, 4))
|
|
|
| self.fp32_limit = tk.StringVar(value="16326")
|
|
|
| self.limit_entry = ttk.Entry(
|
| precision_frame,
|
| textvariable=self.fp32_limit,
|
| width=8,
|
| )
|
| self.limit_entry.pack(side="left")
|
|
|
| ttk.Label(
|
| precision_frame,
|
| text="(per dimension)",
|
| ).pack(side="left", padx=(4, 0))
|
|
|
|
|
| self._update_fallback_state()
|
|
|
|
|
| self.text = tk.Text(
|
| root,
|
| bg="#0d0d0d",
|
| fg="#dddddd",
|
| insertbackground="white",
|
| font=("Consolas", 9),
|
| wrap="none",
|
| )
|
| self.text.pack(
|
| fill="both",
|
| expand=True,
|
| padx=10,
|
| pady=10,
|
| )
|
|
|
|
|
| self.text.tag_configure(
|
| "bold",
|
| font=("Consolas", 9, "bold"),
|
| foreground="#ffcc00",
|
| )
|
|
|
| def _update_fallback_state(self):
|
| """Enable/disable the large-tensor controls based on mode."""
|
| is_fp64 = self.precision_mode.get() == "FP64"
|
|
|
| state = "normal" if is_fp64 else "disabled"
|
| self.fallback_check.configure(state=state)
|
|
|
|
|
| if is_fp64 and self.use_large_fp32.get():
|
| self.limit_entry.configure(state="normal")
|
| else:
|
| self.limit_entry.configure(state="disabled")
|
|
|
| def browse(self):
|
| path = filedialog.askopenfilename(
|
| title=(
|
| "Select model "
|
| "(SafeTensors)"
|
| ),
|
| filetypes=[
|
| (
|
| "SafeTensors",
|
| "*.safetensors",
|
| ),
|
| (
|
| "All files",
|
| "*.*",
|
| ),
|
| ],
|
| )
|
|
|
| if path:
|
| self.path.set(path)
|
|
|
| def log(self, text):
|
| def write():
|
| self.text.insert(
|
| "end",
|
| text + "\n",
|
| )
|
| self.text.see("end")
|
|
|
| self.root.after(0, write)
|
|
|
| def log_bold(self, text):
|
| def write():
|
| self.text.insert(
|
| "end",
|
| text + "\n",
|
| "bold",
|
| )
|
| self.text.see("end")
|
|
|
| self.root.after(0, write)
|
|
|
| def start(self):
|
| path = self.path.get().strip()
|
|
|
| if not path:
|
| messagebox.showerror(
|
| "Error",
|
| "Select a source model first.",
|
| )
|
| return
|
|
|
| if not os.path.isfile(path):
|
| messagebox.showerror(
|
| "Error",
|
| "Selected model does not exist.",
|
| )
|
| return
|
|
|
| if (
|
| os.path.abspath(path)
|
| == os.path.abspath(PREDICTOR_PATH)
|
| ):
|
| messagebox.showerror(
|
| "Error",
|
| "The selected model cannot "
|
| "also be the predictor checkpoint.",
|
| )
|
| return
|
|
|
|
|
| try:
|
| limit = int(self.fp32_limit.get().strip())
|
| if limit < 1:
|
| raise ValueError
|
| except ValueError:
|
| messagebox.showerror(
|
| "Error",
|
| "Tensor size limit must be a positive integer.",
|
| )
|
| return
|
|
|
| prefer_fp64 = self.precision_mode.get() == "FP64"
|
| output_label = "fp64" if prefer_fp64 else "fp32"
|
|
|
| base = os.path.splitext(path)[0]
|
| save_path = base + f"_predicted_{output_label}.safetensors"
|
|
|
| self.text.delete("1.0", "end")
|
| self.convert_button.configure(state="disabled")
|
|
|
| skip_simple = self.skip_simple.get()
|
| use_large_fp32 = self.use_large_fp32.get() and prefer_fp64
|
|
|
| self.log("Starting conversion...")
|
| self.log(
|
| "Architecture: "
|
| f"{FEATURES} features / "
|
| f"{HIDDEN_DIM} hidden / "
|
| f"{NUM_LAYERS} residual blocks"
|
| )
|
| self.log(
|
| f"Precision mode (compute + save): "
|
| f"{'FP64' if prefer_fp64 else 'FP32'}"
|
| )
|
| if prefer_fp64 and use_large_fp32:
|
| self.log(
|
| f"Large-tensor FP32 fallback: "
|
| f"enabled (limit {limit:,})"
|
| )
|
| else:
|
| self.log("Large-tensor FP32 fallback: disabled")
|
| self.log(
|
| f"Skip 1D/simple tensors: {skip_simple}"
|
| )
|
|
|
| threading.Thread(
|
| target=self.worker,
|
| args=(
|
| path,
|
| save_path,
|
| skip_simple,
|
| prefer_fp64,
|
| use_large_fp32,
|
| limit,
|
| ),
|
| daemon=True,
|
| ).start()
|
|
|
| def worker(
|
| self,
|
| path,
|
| save_path,
|
| skip_simple,
|
| prefer_fp64,
|
| use_large_fp32_fallback,
|
| fp32_shape_limit,
|
| ):
|
| try:
|
| run_conversion(
|
| path,
|
| self.log,
|
| self.log_bold,
|
| save_path,
|
| skip_simple,
|
| prefer_fp64,
|
| use_large_fp32_fallback,
|
| fp32_shape_limit,
|
| )
|
|
|
| output_label = "FP64" if prefer_fp64 else "FP32"
|
|
|
| self.root.after(
|
| 0,
|
| lambda: (
|
| self.convert_button.configure(
|
| state="normal"
|
| ),
|
| messagebox.showinfo(
|
| "Complete",
|
| f"{output_label} reconstruction finished.",
|
| ),
|
| ),
|
| )
|
|
|
| except Exception as exc:
|
| self.log("")
|
| self.log("ERROR:")
|
| self.log(str(exc))
|
|
|
| self.root.after(
|
| 0,
|
| lambda: (
|
| self.convert_button.configure(
|
| state="normal"
|
| ),
|
| messagebox.showerror(
|
| "Conversion Error",
|
| str(exc),
|
| ),
|
| ),
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
| root = tk.Tk()
|
| App(root)
|
| root.mainloop() |