Wiself commited on
Commit
b6c04da
Β·
1 Parent(s): 7b9216f

fix delta corruption. removed dead code. added delta for --multi tensor cast.

Browse files
Files changed (1) hide show
  1. voice.py +334 -178
voice.py CHANGED
@@ -1929,6 +1929,15 @@ def cmd_cast(args):
1929
  target_format = "gguf" if _is_gguf_file(target) else "safetensors"
1930
  out_path = _out_path(args, target, voice_arg)
1931
  _check_overwrite(out_path, args)
 
 
 
 
 
 
 
 
 
1932
 
1933
  # auto-detect delta file via marker even without voice.json (e.g. shipped style.safetensors)
1934
  # do this after _check_overwrite so prompt and Detected don't interleave
@@ -2161,63 +2170,11 @@ def _quantize_to_Q(fused, req_q):
2161
  return quantize(fused, req_q), req_q
2162
 
2163
 
2164
- def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=None, strict_target=False):
 
2165
  import numpy as np
2166
- from gguf import GGUFReader, GGUFWriter
2167
- from gguf.gguf_writer import GGUFValue
2168
  from gguf.constants import GGMLQuantizationType as Q
2169
  from gguf.quants import quantize
2170
-
2171
- reader = GGUFReader(target)
2172
- arch = None
2173
- for field in reader.fields.values():
2174
- if field.name == "general.architecture":
2175
- try:
2176
- raw = field.parts[field.data[0]]
2177
- arch = raw.tobytes().decode() if isinstance(raw, np.ndarray) else str(raw)
2178
- except Exception:
2179
- arch = None
2180
- break
2181
-
2182
- target_tensor = None
2183
- if vref.name:
2184
- for t in reader.tensors:
2185
- if t.name == vref.name:
2186
- target_tensor = t
2187
- break
2188
- if target_tensor is None and strict_target:
2189
- _fail(f" βœ— Tensor '{vref.name}' not found in target {target}. Voicepack requires exact match (not mapping to another tensor).")
2190
- if target_tensor is None:
2191
- for preferred in ("output.weight", "token_embd.weight"):
2192
- for t in reader.tensors:
2193
- if t.name == preferred:
2194
- target_tensor = t
2195
- break
2196
- if target_tensor is not None:
2197
- break
2198
- if target_tensor is None:
2199
- # largest 2-D
2200
- best, best_n = None, 0
2201
- for t in reader.tensors:
2202
- if len(t.shape) == 2:
2203
- n = int(t.shape[0]) * int(t.shape[1])
2204
- if n > best_n:
2205
- best, best_n = t, n
2206
- target_tensor = best
2207
- if target_tensor is None:
2208
- _fail(" βœ— Could not find an output tensor in the GGUF file.")
2209
-
2210
- tname = target_tensor.name
2211
- logical = tuple(reversed(tuple(int(x) for x in target_tensor.shape)))
2212
-
2213
- # arch mismatch warning
2214
- varch = (meta or {}).get("architecture")
2215
- if varch and arch and varch.lower() != arch.lower():
2216
- _warn(f"This voice was trained for a {varch} model, but the target is {arch}. It may not sound right.")
2217
- if not _confirm(" Continue anyway? [y/N]: ", args):
2218
- _note("Cancelled.")
2219
- sys.exit(0)
2220
-
2221
  # Philosophy: preserve quality, avoid dequant->quant cycles.
2222
  # - GGUF quant voice (vraw) is kept at its obtained quant (never upscale Q2->Q6, no inventing data).
2223
  # - Safetensors high-fidelity lm_head (voice) is quantized once to Q8_0.
@@ -2527,7 +2484,14 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2527
  except Exception:
2528
  t_fuse0 = time.time()
2529
  _note(f"Fusing done in {time.time()-t_fuse0:.1f}s")
 
2530
 
 
 
 
 
 
 
2531
  if not arch:
2532
  _warn(" Target architecture unknown β€” labeling output 'llama' (GGUF requires an arch).")
2533
  writer = GGUFWriter(out_path, arch or "llama", use_temp_file=True)
@@ -2552,8 +2516,9 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2552
  _warn(f"Could not copy field {field.name}")
2553
 
2554
  for t in reader.tensors:
2555
- if t.name == tname:
2556
- writer.add_tensor(t.name, q_head, raw_dtype=out_qtype)
 
2557
  elif t.tensor_type in (Q.F32, Q.F16):
2558
  writer.add_tensor(t.name, t.data)
2559
  else:
@@ -2565,7 +2530,7 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2565
  try:
2566
  total_write = 8192
2567
  for t in reader.tensors:
2568
- if t.name == tname:
2569
  continue
2570
  try:
2571
  total_write += int(t.data.nbytes)
@@ -2573,26 +2538,31 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2573
  if VERBOSE:
2574
  _warn(f"progress estimate for {t.name} failed: {e}")
2575
  try:
2576
- total_write += int(q_head.nbytes)
2577
- except Exception as e:
2578
- if VERBOSE:
2579
- _warn(f"progress estimate for q_head failed: {e}")
2580
  try:
2581
- total_write += int(q_head.size * q_head.itemsize)
 
2582
  except Exception:
2583
  pass
2584
  if VERBOSE:
2585
- _note(f"Estimated write {total_write/1e6:.1f} MB (q_head {q_head.nbytes/1e6:.1f} MB + {len([t for t in reader.tensors if t.name != tname])} tensors)")
2586
  except Exception as e:
2587
  if VERBOSE:
2588
  _warn(f"progress estimate failed: {e}")
2589
  total_write = 0
2590
- # ensure total_write is at least header + q_head for small models, never 0 for progress
2591
  if total_write < 1000:
2592
  try:
2593
- total_write = int(q_head.nbytes) + 8192
 
 
 
 
 
2594
  for t in reader.tensors:
2595
- if t.name != tname:
2596
  try:
2597
  total_write += int(t.data.nbytes)
2598
  except Exception:
@@ -2673,6 +2643,17 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2673
  pass
2674
 
2675
  file_size = os.path.getsize(out_path)
 
 
 
 
 
 
 
 
 
 
 
2676
  log_operation({"op": op, "target": target, "format": "gguf", "tensor": tname,
2677
  "dtype": out_qtype.name, "shape": list(logical), "output": out_path, "status": "ok"})
2678
  _ok(f"Done β†’ {out_path}")
@@ -2690,7 +2671,8 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2690
  elif is_fallback:
2691
  _say(f" Head kept as F16 (Q8_0 needs last dim %32, fallback) ({file_size/1e6:.1f} MB).")
2692
  elif out_qtype == Q.Q8_0 and _is_head_msg:
2693
- _kept_q8 = (vraw is not None and isinstance(vqtype, str) and vqtype == Q.Q8_0.name and _quant_override in ("keep",))
 
2694
  if _kept_q8:
2695
  _say(f" Head kept as Q8_0 (from {vqtype}, {file_size/1e6:.1f} MB).")
2696
  else:
@@ -2700,6 +2682,154 @@ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=N
2700
  _say(f" {_label} '{tname}' kept as {out_qtype.name} (from {_from}, {file_size/1e6:.1f} MB).")
2701
  _say(f" Load it: llama serve -m {out_path}")
2702
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2703
 
2704
  def _write_fetched(fetched, tensor_name, out_path):
2705
  """Write a fetched tensor to a standalone artifact (gguf when quantized)."""
@@ -2891,6 +3021,17 @@ def cmd_tensor_get(args):
2891
  # CMD: tensor cast β€” cast any named tensor onto a file
2892
  # ---------------------------------------------------------------------------
2893
 
 
 
 
 
 
 
 
 
 
 
 
2894
  def cmd_tensor_cast(args):
2895
  target = args.target
2896
  if not os.path.exists(target):
@@ -2917,52 +3058,13 @@ def cmd_tensor_cast(args):
2917
  target_format = "gguf" if _is_gguf_file(target) else "safetensors"
2918
  out_path = _out_path(args, target, src_path)
2919
  _check_overwrite(out_path, args)
2920
- _cast_core(target, target_format, out_path, f32, ref, None, args, op="tensor_cast", vraw=raw, vqtype=qtype)
2921
  return
2922
  if len(_all_names) > 1:
2923
- # Voicepack multi-cast: replace all (minus excluded) in one output, with progress.
2924
- # Validate excludes exist (warn on unknown, fail on all-excluded).
2925
- for e in excludes:
2926
- if e not in _all_names and not any(e in n for n in _all_names):
2927
- _warn(f" --exclude '{e}' not in pack (has {len(_all_names)} tensors). Ignoring.")
2928
- # Resolve excludes via substring single-match like get (exact first).
2929
- _resolved_ex = set()
2930
- for e in excludes:
2931
- if e in _all_names:
2932
- _resolved_ex.add(e)
2933
- else:
2934
- _m = [n for n in _all_names if e in n]
2935
- if len(_m) == 1:
2936
- _resolved_ex.add(_m[0])
2937
- to_cast = [n for n in _all_names if n not in _resolved_ex]
2938
- if not to_cast:
2939
- _fail(" βœ— --exclude removes all tensors. Nothing to cast.")
2940
- _step(f"Casting {len(to_cast)} tensors from {Path(src_path).name} onto {Path(target).name}…")
2941
- if excludes:
2942
- _note(f"Excluding {len(_resolved_ex)}: {', '.join(sorted(_resolved_ex))}")
2943
- target_format = "gguf" if _is_gguf_file(target) else "safetensors"
2944
  out_path = _out_path(args, target, src_path)
2945
  _check_overwrite(out_path, args)
2946
- # Chain single casts via temp files (efficient for <50MB tests; single-pass for GGUF would be N rewrites anyway).
2947
- # For correctness with philosophy (keep target quant per tensor), reuse _cast_core per tensor.
2948
- import tempfile, shutil
2949
- _cur_in = target
2950
- _tmpdir = Path(tempfile.mkdtemp(prefix="voicepack-cast-"))
2951
- try:
2952
- for i, tname in enumerate(to_cast):
2953
- is_last = (i == len(to_cast) - 1)
2954
- _out = out_path if is_last else str(_tmpdir / f"step{i}.tmp")
2955
- f32, ref, raw, qtype = _load_tensor(src_path, tname)
2956
- _step(f"[{i+1}/{len(to_cast)}] {tname} ({ref.dtype} {tuple(ref.shape)})…")
2957
- _cast_core(_cur_in, "gguf" if _is_gguf_file(_cur_in) else "safetensors",
2958
- _out, f32, ref, None, args, op="tensor_cast", vraw=raw, vqtype=qtype,
2959
- strict_target=True)
2960
- _cur_in = _out
2961
- finally:
2962
- try:
2963
- shutil.rmtree(_tmpdir, ignore_errors=True)
2964
- except Exception:
2965
- pass
2966
  return
2967
  # Single-tensor pack (or single file) without --tensor flag: original behavior.
2968
  f32, ref, raw, qtype = _load_tensor(src_path, None)
@@ -2971,7 +3073,7 @@ def cmd_tensor_cast(args):
2971
  target_format = "gguf" if _is_gguf_file(target) else "safetensors"
2972
  out_path = _out_path(args, target, src_path)
2973
  _check_overwrite(out_path, args)
2974
- _cast_core(target, target_format, out_path, f32, ref, None, args, op="tensor_cast", vraw=raw, vqtype=qtype)
2975
 
2976
 
2977
  def _load_tensor(path, name=None):
@@ -3167,14 +3269,18 @@ def cmd_delta(args):
3167
  "dtype": "F16",
3168
  "downloaded_at": _now_iso(),
3169
  }
3170
- # enrich with shape/dtype from the delta file
3171
  try:
3172
  hdr, tens = read_safetensors(str(delta_folder / "voice.safetensors"))
3173
- k = list(tens.keys())[0]
3174
- meta["tensor_name"] = k
3175
- meta["shape"] = list(tens[k]["shape"])
3176
- meta["dtype"] = tens[k]["dtype"]
3177
- meta["bytes"] = tens[k]["data_offsets"][1]
 
 
 
 
3178
  except Exception:
3179
  pass
3180
  jtmp = delta_folder / "voice.json.tmp"
@@ -3381,20 +3487,19 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3381
  b_names = [n for n in b_src.names() if n != "delta.voice.marker"]
3382
  if not v_names or not b_names:
3383
  _fail(" βœ— No tensors found for delta.")
3384
- # Prefer same name if possible, else first.
3385
- vname = v_names[0]
3386
- # Try to match voice name in base.
3387
  try:
3388
  arch, cfg = _source_arch(v_src)
3389
  _cand = _resolve_output_name(v_src, cfg, v_names)
3390
- if _cand in v_names:
3391
- vname = _cand
3392
  except Exception:
3393
- pass
3394
- bname = vname if vname in b_names else b_names[0]
3395
- vref, bref = v_src.ref(vname), b_src.ref(bname)
3396
- if tuple(vref.shape) != tuple(bref.shape):
3397
- _fail(f" βœ— Voice shape {tuple(vref.shape)} doesn't fit base {tuple(bref.shape)}.")
 
 
3398
  # Gates (marker check works for both safetensors header and GGUF? GGUF has no marker, so False unless safetensors delta).
3399
  is_voice_delta = _is_delta_safetensors(str(voice_path)) if str(voice_path).endswith(".safetensors") else False
3400
  is_base_delta = _is_delta_safetensors(str(base_path)) if str(base_path).endswith(".safetensors") else False
@@ -3413,11 +3518,6 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3413
  " The voice and base are both delta-voices.",
3414
  " delta βˆ’ delta = style difference of style differences."],
3415
  "BIG BRAIN", "delta-on-delta needs explicit confirmation")
3416
- _warn(f" Dequanting {vname} ({vref.dtype}) and {bname} ({bref.dtype}) once to F32 for delta math (unavoidable for GGUF quant).")
3417
- v_f32, b_f32 = v_src.read_f32(vname), b_src.read_f32(bname)
3418
- if v_f32.shape != b_f32.shape:
3419
- _fail(f" βœ— Shape mismatch after dequant: {v_f32.shape} vs {b_f32.shape}.")
3420
- delta_f16 = (v_f32.astype(np.float32) - b_f32.astype(np.float32)).astype("float16")
3421
  _assert_not_symlink(delta_folder)
3422
  delta_folder.mkdir(parents=True, exist_ok=True)
3423
  try:
@@ -3426,17 +3526,32 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3426
  pass
3427
  out_path = delta_folder / "voice.safetensors"
3428
  tmp_out = delta_folder / "voice.safetensors.tmp"
3429
- total_elems = int(delta_f16.size)
3430
- header = {
3431
- vname: {"dtype": "F16", "shape": list(delta_f16.shape), "data_offsets": [0, total_elems * 2]},
3432
- "delta.voice.marker": {"dtype": "U8", "shape": [1], "data_offsets": [total_elems * 2, total_elems * 2 + 1]},
3433
- "__metadata__": {"type": "delta", "base": _public_source(str(base_path)), "voice": _public_source(str(voice_path))},
3434
- }
 
 
 
 
 
 
 
 
3435
  hj = json.dumps(header).encode("utf-8")
3436
  with open(tmp_out, "wb") as out:
3437
  out.write(struct.pack("<Q", len(hj)))
3438
  out.write(hj)
3439
- out.write(delta_f16.tobytes())
 
 
 
 
 
 
 
3440
  out.write(b"\x01")
3441
  out.flush()
3442
  try:
@@ -3446,13 +3561,15 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3446
  tmp_out.replace(out_path)
3447
  return out_path
3448
  # Original chunked safetensors-safetensors path below (no dequant beyond F32 math per chunk).
3449
- # filter marker for tensor selection (marker is not a real tensor)
3450
  v_keys = [k for k in v_tensors.keys() if k != "delta.voice.marker"]
3451
- b_keys = [k for k in b_tensors.keys() if k != "delta.voice.marker"]
3452
- vname = v_keys[0] if v_keys else list(v_tensors.keys())[0]
3453
- bname = b_keys[0] if b_keys else list(b_tensors.keys())[0]
3454
- vinfo, binfo = v_tensors[vname], b_tensors[bname]
3455
- vshape, bshape = tuple(vinfo["shape"]), tuple(binfo["shape"])
 
 
3456
  # gates for delta-on-delta niche cases (marker indicates delta file)
3457
  is_voice_delta = _is_delta_safetensors(str(voice_path))
3458
  is_base_delta = _is_delta_safetensors(str(base_path))
@@ -3470,21 +3587,33 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3470
  " The voice and base are both delta-voices.",
3471
  " delta βˆ’ delta = style difference of style differences."],
3472
  "BIG BRAIN", "delta-on-delta needs explicit confirmation")
3473
- if vshape != bshape:
3474
- _fail(f" βœ— Voice shape {vshape} doesn't fit base {bshape}.")
3475
- total_elems = 1
3476
- for d in vshape:
3477
- total_elems *= int(d)
3478
-
3479
  with open(voice_path, "rb") as f:
3480
  v_hs = struct.unpack("<Q", f.read(8))[0]
3481
  with open(base_path, "rb") as f:
3482
  b_hs = struct.unpack("<Q", f.read(8))[0]
3483
- v_start = 8 + v_hs + vinfo["data_offsets"][0]
3484
- b_start = 8 + b_hs + binfo["data_offsets"][0]
3485
- v_bpe = dtype_bytes(vinfo["dtype"])
3486
- b_bpe = dtype_bytes(binfo["dtype"])
3487
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3488
  _assert_not_symlink(delta_folder)
3489
  delta_folder.mkdir(parents=True, exist_ok=True)
3490
  try:
@@ -3493,14 +3622,6 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3493
  pass
3494
  out_path = delta_folder / "voice.safetensors"
3495
  tmp_out = delta_folder / "voice.safetensors.tmp"
3496
- marker_name = "delta.voice.marker"
3497
- header = {
3498
- vname: {"dtype": "F16", "shape": list(vshape), "data_offsets": [0, total_elems * 2]},
3499
- marker_name: {"dtype": "U8", "shape": [1], "data_offsets": [total_elems * 2, total_elems * 2 + 1]},
3500
- "__metadata__": {"type": "delta", "base": _public_source(str(base_path)), "voice": _public_source(str(voice_path))},
3501
- }
3502
- hj = json.dumps(header).encode("utf-8")
3503
-
3504
  chunk = 1 << 20
3505
  total_bytes = total_elems * 2
3506
  t0 = time.time()
@@ -3521,23 +3642,28 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
3521
  with open(voice_path, "rb") as fv, open(base_path, "rb") as fb, open(tmp_out, "wb") as out:
3522
  out.write(struct.pack("<Q", len(hj)))
3523
  out.write(hj)
3524
- fv.seek(v_start)
3525
- fb.seek(b_start)
3526
  processed = 0
3527
  _delta_progress(force=True)
3528
- while processed < total_elems:
3529
- cur = min(chunk, total_elems - processed)
3530
- v_raw = fv.read(cur * v_bpe)
3531
- b_raw = fb.read(cur * b_bpe)
3532
- if len(v_raw) != cur * v_bpe or len(b_raw) != cur * b_bpe:
3533
- sys.stdout.write("\n")
3534
- _fail(" βœ— Short read during delta compute.")
3535
- v_f32 = decode_to_f32(v_raw, vinfo["dtype"], (cur,))
3536
- b_f32 = decode_to_f32(b_raw, binfo["dtype"], (cur,))
3537
- delta_f16 = (v_f32 - b_f32).astype("float16")
3538
- out.write(delta_f16.tobytes())
3539
- processed += cur
3540
- _delta_progress()
 
 
 
 
 
 
 
3541
  _delta_progress(force=True)
3542
  sys.stdout.write("\n")
3543
  # marker byte for auto-detect (delta.voice.marker)
@@ -3782,6 +3908,33 @@ def _verify_voice_file(path, meta):
3782
  except Exception:
3783
  return [" ? Could not read the artifact header; showing voice.json as-is."]
3784
  names = [k for k in tens if k != "delta.voice.marker"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3785
  tname = meta.get("tensor_name")
3786
  if tname not in tens and names:
3787
  tname = names[0]
@@ -3834,6 +3987,9 @@ def cmd_info(args):
3834
  _say(f" Tensor: {meta.get('tensor_name', 'β€”')}")
3835
  _say(f" Shape: {meta.get('shape', 'β€”')}")
3836
  _say(f" Dtype: {meta.get('dtype', 'β€”')}")
 
 
 
3837
  b = meta.get("bytes")
3838
  if isinstance(b, int):
3839
  _say(f" Bytes: {b} ({b/1e6:.1f} MB)")
 
1929
  target_format = "gguf" if _is_gguf_file(target) else "safetensors"
1930
  out_path = _out_path(args, target, voice_arg)
1931
  _check_overwrite(out_path, args)
1932
+ # Voicepack in the registry (or a pack path): cast every tensor, not just the first.
1933
+ try:
1934
+ _probe = open_source(str(voice_path))
1935
+ _pack_names = [n for n in _probe.names() if n != "delta.voice.marker"]
1936
+ except Exception:
1937
+ _pack_names = []
1938
+ if len(_pack_names) > 1:
1939
+ _cast_pack(target, str(voice_path), out_path, args, op="cast", meta=voice_meta)
1940
+ return
1941
 
1942
  # auto-detect delta file via marker even without voice.json (e.g. shipped style.safetensors)
1943
  # do this after _check_overwrite so prompt and Detected don't interleave
 
2170
  return quantize(fused, req_q), req_q
2171
 
2172
 
2173
+ def _gguf_fuse_one(target, target_tensor, tname, logical, vf32, vref, meta, args, op, vraw=None, vqtype=None):
2174
+ """Fuse one voice tensor into its target GGUF tensor form. Returns (q_head, out_qtype)."""
2175
  import numpy as np
 
 
2176
  from gguf.constants import GGMLQuantizationType as Q
2177
  from gguf.quants import quantize
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2178
  # Philosophy: preserve quality, avoid dequant->quant cycles.
2179
  # - GGUF quant voice (vraw) is kept at its obtained quant (never upscale Q2->Q6, no inventing data).
2180
  # - Safetensors high-fidelity lm_head (voice) is quantized once to Q8_0.
 
2484
  except Exception:
2485
  t_fuse0 = time.time()
2486
  _note(f"Fusing done in {time.time()-t_fuse0:.1f}s")
2487
+ return q_head, out_qtype
2488
 
2489
+ def _gguf_write_repl(target, reader, arch, repl, out_path, args, op, detail=None):
2490
+ """Single-pass GGUF write. repl maps tensor name -> (array, ggml_type).
2491
+ detail (single-tensor callers): (tname, logical, vref, vraw, vqtype, meta) for rich messages."""
2492
+ from gguf import GGUFWriter
2493
+ from gguf.gguf_writer import GGUFValue
2494
+ from gguf.constants import GGMLQuantizationType as Q
2495
  if not arch:
2496
  _warn(" Target architecture unknown β€” labeling output 'llama' (GGUF requires an arch).")
2497
  writer = GGUFWriter(out_path, arch or "llama", use_temp_file=True)
 
2516
  _warn(f"Could not copy field {field.name}")
2517
 
2518
  for t in reader.tensors:
2519
+ if t.name in repl:
2520
+ _qh, _qt = repl[t.name]
2521
+ writer.add_tensor(t.name, _qh, raw_dtype=_qt)
2522
  elif t.tensor_type in (Q.F32, Q.F16):
2523
  writer.add_tensor(t.name, t.data)
2524
  else:
 
2530
  try:
2531
  total_write = 8192
2532
  for t in reader.tensors:
2533
+ if t.name in repl:
2534
  continue
2535
  try:
2536
  total_write += int(t.data.nbytes)
 
2538
  if VERBOSE:
2539
  _warn(f"progress estimate for {t.name} failed: {e}")
2540
  try:
2541
+ for _qh, _qt in repl.values():
2542
+ total_write += int(_qh.nbytes)
2543
+ except Exception:
 
2544
  try:
2545
+ for _qh, _qt in repl.values():
2546
+ total_write += int(_qh.size * _qh.itemsize)
2547
  except Exception:
2548
  pass
2549
  if VERBOSE:
2550
+ _note(f"Estimated write {total_write/1e6:.1f} MB ({len(repl)} replaced + {len([t for t in reader.tensors if t.name not in repl])} copied)")
2551
  except Exception as e:
2552
  if VERBOSE:
2553
  _warn(f"progress estimate failed: {e}")
2554
  total_write = 0
2555
+ # ensure total_write is at least header + heads for small models, never 0 for progress
2556
  if total_write < 1000:
2557
  try:
2558
+ total_write = 8192
2559
+ for _qh, _qt in repl.values():
2560
+ try:
2561
+ total_write += int(_qh.nbytes)
2562
+ except Exception:
2563
+ pass
2564
  for t in reader.tensors:
2565
+ if t.name not in repl:
2566
  try:
2567
  total_write += int(t.data.nbytes)
2568
  except Exception:
 
2643
  pass
2644
 
2645
  file_size = os.path.getsize(out_path)
2646
+ if detail is None:
2647
+ summed = [(n, _qt.name if hasattr(_qt, "name") else str(_qt)) for n, (_qh, _qt) in repl.items()]
2648
+ log_operation({"op": op, "target": target, "format": "gguf", "tensors": summed,
2649
+ "output": out_path, "status": "ok"})
2650
+ _ok(f"Done β†’ {out_path}")
2651
+ for n, _qt in summed:
2652
+ _say(f" Cast '{n}' β†’ {_qt} ({file_size/1e6:.1f} MB total).")
2653
+ _say(f" Load it: llama serve -m {out_path}")
2654
+ return
2655
+ tname, logical, vref, vraw, vqtype, meta = detail
2656
+ _qh0, out_qtype = repl[tname]
2657
  log_operation({"op": op, "target": target, "format": "gguf", "tensor": tname,
2658
  "dtype": out_qtype.name, "shape": list(logical), "output": out_path, "status": "ok"})
2659
  _ok(f"Done β†’ {out_path}")
 
2671
  elif is_fallback:
2672
  _say(f" Head kept as F16 (Q8_0 needs last dim %32, fallback) ({file_size/1e6:.1f} MB).")
2673
  elif out_qtype == Q.Q8_0 and _is_head_msg:
2674
+ _qo = _parse_quant_override(getattr(args, "quant", None) or "keep")
2675
+ _kept_q8 = (vraw is not None and isinstance(vqtype, str) and vqtype == Q.Q8_0.name and _qo in ("keep",))
2676
  if _kept_q8:
2677
  _say(f" Head kept as Q8_0 (from {vqtype}, {file_size/1e6:.1f} MB).")
2678
  else:
 
2682
  _say(f" {_label} '{tname}' kept as {out_qtype.name} (from {_from}, {file_size/1e6:.1f} MB).")
2683
  _say(f" Load it: llama serve -m {out_path}")
2684
 
2685
+ def _cast_gguf(target, out_path, vf32, vref, meta, args, op, vraw=None, vqtype=None, strict_target=False):
2686
+ import numpy as np
2687
+ from gguf import GGUFReader
2688
+
2689
+ reader = GGUFReader(target)
2690
+ arch = None
2691
+ for field in reader.fields.values():
2692
+ if field.name == "general.architecture":
2693
+ try:
2694
+ raw = field.parts[field.data[0]]
2695
+ arch = raw.tobytes().decode() if isinstance(raw, np.ndarray) else str(raw)
2696
+ except Exception:
2697
+ arch = None
2698
+ break
2699
+
2700
+ target_tensor = None
2701
+ if vref.name:
2702
+ for t in reader.tensors:
2703
+ if t.name == vref.name:
2704
+ target_tensor = t
2705
+ break
2706
+ if target_tensor is None and strict_target:
2707
+ _fail(f" βœ— Tensor '{vref.name}' not found in target {target}. Voicepack requires exact match (not mapping to another tensor).")
2708
+ if target_tensor is None:
2709
+ for preferred in ("output.weight", "token_embd.weight"):
2710
+ for t in reader.tensors:
2711
+ if t.name == preferred:
2712
+ target_tensor = t
2713
+ break
2714
+ if target_tensor is not None:
2715
+ break
2716
+ if target_tensor is None:
2717
+ # largest 2-D
2718
+ best, best_n = None, 0
2719
+ for t in reader.tensors:
2720
+ if len(t.shape) == 2:
2721
+ n = int(t.shape[0]) * int(t.shape[1])
2722
+ if n > best_n:
2723
+ best, best_n = t, n
2724
+ target_tensor = best
2725
+ if target_tensor is None:
2726
+ _fail(" βœ— Could not find an output tensor in the GGUF file.")
2727
+
2728
+ tname = target_tensor.name
2729
+ logical = tuple(reversed(tuple(int(x) for x in target_tensor.shape)))
2730
+
2731
+ # arch mismatch warning
2732
+ varch = (meta or {}).get("architecture")
2733
+ if varch and arch and varch.lower() != arch.lower():
2734
+ _warn(f"This voice was trained for a {varch} model, but the target is {arch}. It may not sound right.")
2735
+ if not _confirm(" Continue anyway? [y/N]: ", args):
2736
+ _note("Cancelled.")
2737
+ sys.exit(0)
2738
+
2739
+ q_head, out_qtype = _gguf_fuse_one(target, target_tensor, tname, logical, vf32, vref, meta, args, op, vraw=vraw, vqtype=vqtype)
2740
+ _gguf_write_repl(target, reader, arch, {tname: (q_head, out_qtype)}, out_path, args, op,
2741
+ detail=(tname, logical, vref, vraw, vqtype, meta))
2742
+
2743
+
2744
+
2745
+ def _cast_gguf_pack(target, out_path, items, args, op):
2746
+ """Single-pass pack cast onto GGUF. items: [(vf32, vref, meta, vraw, vqtype)]."""
2747
+ import numpy as np
2748
+ from gguf import GGUFReader
2749
+ reader = GGUFReader(target)
2750
+ arch = None
2751
+ for field in reader.fields.values():
2752
+ if field.name == "general.architecture":
2753
+ try:
2754
+ raw = field.parts[field.data[0]]
2755
+ arch = raw.tobytes().decode() if isinstance(raw, np.ndarray) else str(raw)
2756
+ except Exception:
2757
+ arch = None
2758
+ break
2759
+ varch = (items[0][2] or {}).get("architecture") if items else None
2760
+ if varch and arch and varch.lower() != arch.lower():
2761
+ _warn(f"This voice was trained for a {varch} model, but the target is {arch}. It may not sound right.")
2762
+ if not _confirm(" Continue anyway? [y/N]: ", args):
2763
+ _note("Cancelled.")
2764
+ sys.exit(0)
2765
+ repl = {}
2766
+ for vf32, vref, meta, vraw, vqtype in items:
2767
+ target_tensor = None
2768
+ if vref.name:
2769
+ for t in reader.tensors:
2770
+ if t.name == vref.name:
2771
+ target_tensor = t
2772
+ break
2773
+ if target_tensor is None:
2774
+ _fail(f" βœ— Tensor '{vref.name}' not found in target {target}. Voicepack requires exact match (not mapping to another tensor).")
2775
+ tname = target_tensor.name
2776
+ logical = tuple(reversed(tuple(int(x) for x in target_tensor.shape)))
2777
+ q_head, out_qtype = _gguf_fuse_one(target, target_tensor, tname, logical, vf32, vref, meta, args, op, vraw=vraw, vqtype=vqtype)
2778
+ repl[tname] = (q_head, out_qtype)
2779
+ _gguf_write_repl(target, reader, arch, repl, out_path, args, op)
2780
+
2781
+
2782
+ def _cast_pack(target, src_path, out_path, args, op, excludes=(), meta=None):
2783
+ """Cast every tensor in a pack file onto target (minus excludes).
2784
+ GGUF targets get one write pass; safetensors targets chain per-tensor.
2785
+ Delta marker on the pack is detected once and threaded into every tensor."""
2786
+ src = open_source(str(src_path))
2787
+ all_names = [n for n in src.names() if n != "delta.voice.marker"]
2788
+ for e in excludes:
2789
+ if e not in all_names and not any(e in n for n in all_names):
2790
+ _warn(f" --exclude '{e}' not in pack (has {len(all_names)} tensors). Ignoring.")
2791
+ resolved_ex = set()
2792
+ for e in excludes:
2793
+ if e in all_names:
2794
+ resolved_ex.add(e)
2795
+ else:
2796
+ _m = [n for n in all_names if e in n]
2797
+ if len(_m) == 1:
2798
+ resolved_ex.add(_m[0])
2799
+ to_cast = [n for n in all_names if n not in resolved_ex]
2800
+ if not to_cast:
2801
+ _fail(" βœ— --exclude removes all tensors. Nothing to cast.")
2802
+ _step(f"Casting {len(to_cast)} tensors from {Path(src_path).name} onto {Path(target).name}…")
2803
+ if resolved_ex:
2804
+ _note(f"Excluding {len(resolved_ex)}: {', '.join(sorted(resolved_ex))}")
2805
+ is_delta_file = str(src_path).endswith(".safetensors") and _is_delta_safetensors(str(src_path))
2806
+ if is_delta_file:
2807
+ _note("Detected delta voice β€” fusing style (delta + head)…")
2808
+ if meta is None and is_delta_file:
2809
+ meta = {"type": "delta", "source": str(src_path)}
2810
+ items = []
2811
+ for i, tname in enumerate(to_cast):
2812
+ f32, ref, raw, qtype = _load_tensor(src_path, tname)
2813
+ _step(f"[{i+1}/{len(to_cast)}] {tname} ({ref.dtype} {tuple(ref.shape)})…")
2814
+ items.append((f32, ref, meta, raw, qtype))
2815
+ if _is_gguf_file(target):
2816
+ _cast_gguf_pack(target, out_path, items, args, op)
2817
+ return
2818
+ import tempfile, shutil
2819
+ cur_in = target
2820
+ tmpdir = Path(tempfile.mkdtemp(prefix="voicepack-cast-"))
2821
+ try:
2822
+ for i, (f32, ref, meta, raw, qtype) in enumerate(items):
2823
+ is_last = (i == len(items) - 1)
2824
+ _out = out_path if is_last else str(tmpdir / f"step{i}.tmp")
2825
+ _cast_core(cur_in, "safetensors", _out, f32, ref, meta, args, op="tensor_cast",
2826
+ vraw=raw, vqtype=qtype, strict_target=True)
2827
+ cur_in = _out
2828
+ finally:
2829
+ try:
2830
+ shutil.rmtree(tmpdir, ignore_errors=True)
2831
+ except Exception:
2832
+ pass
2833
 
2834
  def _write_fetched(fetched, tensor_name, out_path):
2835
  """Write a fetched tensor to a standalone artifact (gguf when quantized)."""
 
3021
  # CMD: tensor cast β€” cast any named tensor onto a file
3022
  # ---------------------------------------------------------------------------
3023
 
3024
+ def _tensor_src_meta(src_path):
3025
+ """File-level delta detection for the tensor path (the marker lives in the file; no voice.json)."""
3026
+ if str(src_path).endswith(".safetensors"):
3027
+ try:
3028
+ if _is_delta_safetensors(str(src_path)):
3029
+ return {"type": "delta", "source": str(src_path)}
3030
+ except Exception:
3031
+ pass
3032
+ return None
3033
+
3034
+
3035
  def cmd_tensor_cast(args):
3036
  target = args.target
3037
  if not os.path.exists(target):
 
3058
  target_format = "gguf" if _is_gguf_file(target) else "safetensors"
3059
  out_path = _out_path(args, target, src_path)
3060
  _check_overwrite(out_path, args)
3061
+ _cast_core(target, target_format, out_path, f32, ref, _tensor_src_meta(src_path), args, op="tensor_cast", vraw=raw, vqtype=qtype)
3062
  return
3063
  if len(_all_names) > 1:
3064
+ # Voicepack: one pass for GGUF, chained for safetensors (see _cast_pack).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3065
  out_path = _out_path(args, target, src_path)
3066
  _check_overwrite(out_path, args)
3067
+ _cast_pack(target, src_path, out_path, args, op="tensor_cast", excludes=excludes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3068
  return
3069
  # Single-tensor pack (or single file) without --tensor flag: original behavior.
3070
  f32, ref, raw, qtype = _load_tensor(src_path, None)
 
3073
  target_format = "gguf" if _is_gguf_file(target) else "safetensors"
3074
  out_path = _out_path(args, target, src_path)
3075
  _check_overwrite(out_path, args)
3076
+ _cast_core(target, target_format, out_path, f32, ref, _tensor_src_meta(src_path), args, op="tensor_cast", vraw=raw, vqtype=qtype)
3077
 
3078
 
3079
  def _load_tensor(path, name=None):
 
3269
  "dtype": "F16",
3270
  "downloaded_at": _now_iso(),
3271
  }
3272
+ # enrich with shape/dtype from the delta file (all tensors, not just the first)
3273
  try:
3274
  hdr, tens = read_safetensors(str(delta_folder / "voice.safetensors"))
3275
+ keys = [k for k in tens.keys() if k != "delta.voice.marker"]
3276
+ meta["tensors"] = [{"name": k, "shape": list(tens[k]["shape"]), "dtype": tens[k]["dtype"],
3277
+ "bytes": tens[k]["data_offsets"][1] - tens[k]["data_offsets"][0]} for k in keys]
3278
+ if len(keys) == 1:
3279
+ # backward compat: single-tensor voices keep flat fields
3280
+ meta["tensor_name"] = keys[0]
3281
+ meta["shape"] = list(tens[keys[0]]["shape"])
3282
+ meta["dtype"] = tens[keys[0]]["dtype"]
3283
+ meta["bytes"] = tens[keys[0]]["data_offsets"][1]
3284
  except Exception:
3285
  pass
3286
  jtmp = delta_folder / "voice.json.tmp"
 
3487
  b_names = [n for n in b_src.names() if n != "delta.voice.marker"]
3488
  if not v_names or not b_names:
3489
  _fail(" βœ— No tensors found for delta.")
3490
+ # Match by exact name: every voice tensor needs a same-named base tensor.
3491
+ # Output head first for deterministic order, then voice order.
 
3492
  try:
3493
  arch, cfg = _source_arch(v_src)
3494
  _cand = _resolve_output_name(v_src, cfg, v_names)
 
 
3495
  except Exception:
3496
+ _cand = None
3497
+ ordered = ([_cand] if _cand in v_names else []) + [n for n in v_names if n != _cand]
3498
+ missing = [n for n in ordered if n not in b_names]
3499
+ if missing:
3500
+ shown = ", ".join(missing[:5]) + ("…" if len(missing) > 5 else "")
3501
+ _fail(f" βœ— {len(missing)} voice tensor(s) have no match in base: {shown}. Delta needs same names on both sides.")
3502
+ pairs = [(n, n) for n in ordered]
3503
  # Gates (marker check works for both safetensors header and GGUF? GGUF has no marker, so False unless safetensors delta).
3504
  is_voice_delta = _is_delta_safetensors(str(voice_path)) if str(voice_path).endswith(".safetensors") else False
3505
  is_base_delta = _is_delta_safetensors(str(base_path)) if str(base_path).endswith(".safetensors") else False
 
3518
  " The voice and base are both delta-voices.",
3519
  " delta βˆ’ delta = style difference of style differences."],
3520
  "BIG BRAIN", "delta-on-delta needs explicit confirmation")
 
 
 
 
 
3521
  _assert_not_symlink(delta_folder)
3522
  delta_folder.mkdir(parents=True, exist_ok=True)
3523
  try:
 
3526
  pass
3527
  out_path = delta_folder / "voice.safetensors"
3528
  tmp_out = delta_folder / "voice.safetensors.tmp"
3529
+ # Header first (offsets need shapes only), then stream one pair at a
3530
+ # time so packs never hold every tensor in RAM at once.
3531
+ header, off = {}, 0
3532
+ for vname, bname in pairs:
3533
+ vref, bref = v_src.ref(vname), b_src.ref(bname)
3534
+ if tuple(vref.shape) != tuple(bref.shape):
3535
+ _fail(f" βœ— Voice tensor '{vname}' shape {tuple(vref.shape)} doesn't fit base {tuple(bref.shape)}.")
3536
+ nbytes = 2
3537
+ for d in vref.shape:
3538
+ nbytes *= int(d)
3539
+ header[vname] = {"dtype": "F16", "shape": [int(x) for x in vref.shape], "data_offsets": [off, off + nbytes]}
3540
+ off += nbytes
3541
+ header["delta.voice.marker"] = {"dtype": "U8", "shape": [1], "data_offsets": [off, off + 1]}
3542
+ header["__metadata__"] = {"type": "delta", "base": _public_source(str(base_path)), "voice": _public_source(str(voice_path))}
3543
  hj = json.dumps(header).encode("utf-8")
3544
  with open(tmp_out, "wb") as out:
3545
  out.write(struct.pack("<Q", len(hj)))
3546
  out.write(hj)
3547
+ for vname, bname in pairs:
3548
+ vref, bref = v_src.ref(vname), b_src.ref(bname)
3549
+ _warn(f" Dequanting {vname} ({vref.dtype}) and base ({bref.dtype}) once to F32 for delta math (unavoidable for GGUF quant).")
3550
+ v_f32, b_f32 = v_src.read_f32(vname), b_src.read_f32(bname)
3551
+ if v_f32.shape != b_f32.shape:
3552
+ sys.stdout.write("\n")
3553
+ _fail(f" βœ— Shape mismatch after dequant for '{vname}': {v_f32.shape} vs {b_f32.shape}.")
3554
+ out.write((v_f32.astype(np.float32) - b_f32.astype(np.float32)).astype("float16").tobytes())
3555
  out.write(b"\x01")
3556
  out.flush()
3557
  try:
 
3561
  tmp_out.replace(out_path)
3562
  return out_path
3563
  # Original chunked safetensors-safetensors path below (no dequant beyond F32 math per chunk).
3564
+ # Match by exact name: every voice tensor needs a same-named base tensor.
3565
  v_keys = [k for k in v_tensors.keys() if k != "delta.voice.marker"]
3566
+ if not v_keys:
3567
+ _fail(" βœ— No tensors found for delta.")
3568
+ missing = [k for k in v_keys if k not in b_tensors]
3569
+ if missing:
3570
+ shown = ", ".join(missing[:5]) + ("…" if len(missing) > 5 else "")
3571
+ _fail(f" βœ— {len(missing)} voice tensor(s) have no match in base: {shown}. Delta needs same names on both sides.")
3572
+ pairs = [(k, k) for k in v_keys]
3573
  # gates for delta-on-delta niche cases (marker indicates delta file)
3574
  is_voice_delta = _is_delta_safetensors(str(voice_path))
3575
  is_base_delta = _is_delta_safetensors(str(base_path))
 
3587
  " The voice and base are both delta-voices.",
3588
  " delta βˆ’ delta = style difference of style differences."],
3589
  "BIG BRAIN", "delta-on-delta needs explicit confirmation")
 
 
 
 
 
 
3590
  with open(voice_path, "rb") as f:
3591
  v_hs = struct.unpack("<Q", f.read(8))[0]
3592
  with open(base_path, "rb") as f:
3593
  b_hs = struct.unpack("<Q", f.read(8))[0]
3594
+ specs = []
3595
+ header, off = {}, 0
3596
+ for vname, bname in pairs:
3597
+ vinfo, binfo = v_tensors[vname], b_tensors[bname]
3598
+ vshape, bshape = tuple(vinfo["shape"]), tuple(binfo["shape"])
3599
+ if vshape != bshape:
3600
+ _fail(f" βœ— Voice tensor '{vname}' shape {vshape} doesn't fit base {bshape}.")
3601
+ elems = 1
3602
+ for d in vshape:
3603
+ elems *= int(d)
3604
+ nbytes = elems * 2
3605
+ header[vname] = {"dtype": "F16", "shape": list(vshape), "data_offsets": [off, off + nbytes]}
3606
+ off += nbytes
3607
+ specs.append({"name": vname, "vinfo": vinfo, "binfo": binfo,
3608
+ "v_start": 8 + v_hs + vinfo["data_offsets"][0],
3609
+ "b_start": 8 + b_hs + binfo["data_offsets"][0],
3610
+ "v_bpe": dtype_bytes(vinfo["dtype"]), "b_bpe": dtype_bytes(binfo["dtype"]),
3611
+ "elems": elems})
3612
+ total_elems = off // 2
3613
+ marker_name = "delta.voice.marker"
3614
+ header[marker_name] = {"dtype": "U8", "shape": [1], "data_offsets": [off, off + 1]}
3615
+ header["__metadata__"] = {"type": "delta", "base": _public_source(str(base_path)), "voice": _public_source(str(voice_path))}
3616
+ hj = json.dumps(header).encode("utf-8")
3617
  _assert_not_symlink(delta_folder)
3618
  delta_folder.mkdir(parents=True, exist_ok=True)
3619
  try:
 
3622
  pass
3623
  out_path = delta_folder / "voice.safetensors"
3624
  tmp_out = delta_folder / "voice.safetensors.tmp"
 
 
 
 
 
 
 
 
3625
  chunk = 1 << 20
3626
  total_bytes = total_elems * 2
3627
  t0 = time.time()
 
3642
  with open(voice_path, "rb") as fv, open(base_path, "rb") as fb, open(tmp_out, "wb") as out:
3643
  out.write(struct.pack("<Q", len(hj)))
3644
  out.write(hj)
 
 
3645
  processed = 0
3646
  _delta_progress(force=True)
3647
+ for spec in specs:
3648
+ fv.seek(spec["v_start"])
3649
+ fb.seek(spec["b_start"])
3650
+ vinfo, binfo = spec["vinfo"], spec["binfo"]
3651
+ v_bpe, b_bpe = spec["v_bpe"], spec["b_bpe"]
3652
+ done = 0
3653
+ while done < spec["elems"]:
3654
+ cur = min(chunk, spec["elems"] - done)
3655
+ v_raw = fv.read(cur * v_bpe)
3656
+ b_raw = fb.read(cur * b_bpe)
3657
+ if len(v_raw) != cur * v_bpe or len(b_raw) != cur * b_bpe:
3658
+ sys.stdout.write("\n")
3659
+ _fail(f" βœ— Short read during delta compute ({spec['name']}).")
3660
+ v_f32 = decode_to_f32(v_raw, vinfo["dtype"], (cur,))
3661
+ b_f32 = decode_to_f32(b_raw, binfo["dtype"], (cur,))
3662
+ delta_f16 = (v_f32 - b_f32).astype("float16")
3663
+ out.write(delta_f16.tobytes())
3664
+ done += cur
3665
+ processed += cur
3666
+ _delta_progress()
3667
  _delta_progress(force=True)
3668
  sys.stdout.write("\n")
3669
  # marker byte for auto-detect (delta.voice.marker)
 
3908
  except Exception:
3909
  return [" ? Could not read the artifact header; showing voice.json as-is."]
3910
  names = [k for k in tens if k != "delta.voice.marker"]
3911
+ claims = meta.get("tensors")
3912
+ if isinstance(claims, list) and claims:
3913
+ for entry in claims:
3914
+ tname = entry.get("name")
3915
+ if tname not in tens:
3916
+ out.append(f" ! MISMATCH tensor: voice.json lists '{tname}', file doesn't hold it.")
3917
+ continue
3918
+ info = tens[tname]
3919
+ if list(entry.get("shape", [])) != list(info["shape"]):
3920
+ out.append(f" ! MISMATCH shape '{tname}': voice.json says {list(entry.get('shape', []))}, "
3921
+ f"file holds {list(info['shape'])} β€” file wins.")
3922
+ if (entry.get("dtype") or "").upper() != str(info["dtype"]).upper():
3923
+ out.append(f" ! MISMATCH dtype '{tname}': voice.json says {entry.get('dtype')}, "
3924
+ f"file holds {info['dtype']} β€” file wins.")
3925
+ claimed_names = {e.get("name") for e in claims}
3926
+ for extra in names:
3927
+ if extra not in claimed_names:
3928
+ out.append(f" ! File holds '{extra}' which voice.json doesn't list.")
3929
+ if not names:
3930
+ out.append(" ? No tensors found in the artifact.")
3931
+ has_marker = "delta.voice.marker" in tens
3932
+ if meta.get("type") == "delta" and not has_marker:
3933
+ out.append(" ! voice.json claims delta but the file has no delta marker.")
3934
+ if meta.get("type") != "delta" and has_marker:
3935
+ out.append(" ! File carries a delta marker but voice.json type is "
3936
+ f"'{meta.get('type', 'voice')}'.")
3937
+ return out
3938
  tname = meta.get("tensor_name")
3939
  if tname not in tens and names:
3940
  tname = names[0]
 
3987
  _say(f" Tensor: {meta.get('tensor_name', 'β€”')}")
3988
  _say(f" Shape: {meta.get('shape', 'β€”')}")
3989
  _say(f" Dtype: {meta.get('dtype', 'β€”')}")
3990
+ if isinstance(meta.get("tensors"), list) and len(meta["tensors"]) != 1:
3991
+ for entry in meta["tensors"]:
3992
+ _say(f" - {entry.get('name')}: {entry.get('shape')} Β· {entry.get('dtype')}")
3993
  b = meta.get("bytes")
3994
  if isinstance(b, int):
3995
  _say(f" Bytes: {b} ({b/1e6:.1f} MB)")