#!/usr/bin/env python3
"""Dump tensor info from qwen3_assets.gguf and extract embeddings to npy."""
import sys, os, struct
import numpy as np

GGUF_PATH = "/models/qwen3-tts/qwen3_assets.gguf"
OUT_DIR = "/tmp/embeddings"

# Try using the gguf library
try:
    from gguf import GGUFReader
    print("Using gguf library GGUFReader")
    reader = GGUFReader(GGUF_PATH)
    print(f"Tensors in {GGUF_PATH}:")
    for i, tensor in enumerate(reader.tensors):
        print(f"  [{i}] name={tensor.name}, shape={tensor.shape}, type={tensor.tensor_type}")
    
    # Name mapping: GGUF tensor name -> npy filename
    NAME_MAP = {
        "text_embd": "text_embedding_projected.npy",
        "proj.weight": "proj_weight.npy",
        "proj.bias": "proj_bias.npy",
    }
    for j in range(16):
        NAME_MAP[f"codec_embd.{j}"] = f"codec_embedding_{j}.npy"

    os.makedirs(OUT_DIR, exist_ok=True)
    
    for tensor in reader.tensors:
        name = tensor.name
        outname = NAME_MAP.get(name, name.replace("/", "_").replace(".", "_") + ".npy")
        
        # tensor.data is a numpy array (may be quantized view)
        data = tensor.data
        print(f"  Processing: {name} -> {outname}, raw shape={data.shape}, dtype={data.dtype}")
        
        # For Q8_0: block size 32, each block = 2 bytes scale + 32 bytes ints
        # The gguf library should dequantize automatically via .data
        # If dtype is already float, use as-is. Otherwise cast.
        if data.dtype in (np.float32, np.float64):
            arr = data.astype(np.float32)
        elif data.dtype == np.float16:
            arr = data.astype(np.float32)
        else:
            # Quantized — try dequantizing manually for Q8_0
            print(f"    WARNING: dtype={data.dtype}, attempting Q8_0 dequant for shape {tensor.shape}")
            target_shape = list(tensor.shape)
            # Q8_0: block_size=32, each block: 1 fp16 scale + 32 int8
            n_elements = 1
            for d in target_shape:
                n_elements *= d
            n_blocks = n_elements // 32
            raw = data.tobytes()
            # Each Q8_0 block: 2 bytes (fp16 scale) + 32 bytes (int8 quants) = 34 bytes
            block_size = 34
            if len(raw) == n_blocks * block_size:
                scales = np.zeros(n_blocks, dtype=np.float32)
                quants = np.zeros(n_elements, dtype=np.float32)
                for bi in range(n_blocks):
                    offset = bi * block_size
                    s = np.frombuffer(raw[offset:offset+2], dtype=np.float16)[0]
                    scales[bi] = float(s)
                    qs = np.frombuffer(raw[offset+2:offset+block_size], dtype=np.int8)
                    quants[bi*32:(bi+1)*32] = qs.astype(np.float32) * float(s)
                arr = quants.reshape(target_shape)
            else:
                print(f"    ERROR: Cannot dequantize, raw_bytes={len(raw)}, expected={n_blocks * block_size}")
                arr = data.astype(np.float32) if data.dtype.kind == 'f' else None
                if arr is None:
                    print(f"    SKIPPING tensor {name}")
                    continue
        
        outpath = os.path.join(OUT_DIR, outname)
        np.save(outpath, arr)
        print(f"  Saved: {outpath} shape={arr.shape} dtype={arr.dtype}")
    
    print(f"\nDone! Files in {OUT_DIR}:")
    for f in sorted(os.listdir(OUT_DIR)):
        sz = os.path.getsize(os.path.join(OUT_DIR, f))
        print(f"  {f} ({sz} bytes)")

except ImportError:
    print("gguf library not available, trying manual parse...")
    # Minimal GGUF tensor listing
    with open(GGUF_PATH, "rb") as f:
        magic = f.read(4)
        print(f"Magic: {magic}")
        version = struct.unpack("<I", f.read(4))[0]
        print(f"Version: {version}")
        n_tensors = struct.unpack("<Q", f.read(8))[0]
        n_kv = struct.unpack("<Q", f.read(8))[0]
        print(f"Tensors: {n_tensors}, KV pairs: {n_kv}")
