Wiself commited on
Commit
91d026f
Β·
1 Parent(s): 285bb31

Update voice.py, vlib, tests, bundles

Browse files
Files changed (7) hide show
  1. tests/_helpers.py +13 -0
  2. tests/test_units.py +707 -1
  3. vlib/cache.py +29 -1
  4. vlib/delta.py +65 -3
  5. voice-cli.tar.gz +2 -2
  6. voice.py +1 -1
  7. voice.pyz +2 -2
tests/_helpers.py CHANGED
@@ -91,6 +91,9 @@ class IsolatedVoices(unittest.TestCase):
91
  self._old = voice.VOICES_DIR
92
  self.vdir = Path(tempfile.mkdtemp(prefix="voices-"))
93
  voice.VOICES_DIR = self.vdir
 
 
 
94
  # registry + reports + parts all derive from VOICES_DIR at call time,
95
  # except these module constants: repoint them too.
96
  self._old_reports = (voice.REPORTS_JSONL, voice.REPORTS_OUTBOX, voice.OPERATIONS_LOG,
@@ -99,11 +102,21 @@ class IsolatedVoices(unittest.TestCase):
99
  voice.REPORTS_OUTBOX = self.vdir / "outbox"
100
  voice.OPERATIONS_LOG = self.vdir / "operations.log"
101
  voice.HF_TOKEN_PATH = self.vdir / ".hf_token"
 
 
 
 
 
102
 
103
  def tearDown(self):
104
  voice.VOICES_DIR = self._old
105
  (voice.REPORTS_JSONL, voice.REPORTS_OUTBOX, voice.OPERATIONS_LOG,
106
  voice.HF_TOKEN_PATH) = self._old_reports
 
 
 
 
 
107
 
108
 
109
  def ns(**kw):
 
91
  self._old = voice.VOICES_DIR
92
  self.vdir = Path(tempfile.mkdtemp(prefix="voices-"))
93
  voice.VOICES_DIR = self.vdir
94
+ voice_ctx = sys.modules.get("vlib.ctx")
95
+ if voice_ctx is not None:
96
+ voice_ctx.VOICES_DIR = self.vdir
97
  # registry + reports + parts all derive from VOICES_DIR at call time,
98
  # except these module constants: repoint them too.
99
  self._old_reports = (voice.REPORTS_JSONL, voice.REPORTS_OUTBOX, voice.OPERATIONS_LOG,
 
102
  voice.REPORTS_OUTBOX = self.vdir / "outbox"
103
  voice.OPERATIONS_LOG = self.vdir / "operations.log"
104
  voice.HF_TOKEN_PATH = self.vdir / ".hf_token"
105
+ if voice_ctx is not None:
106
+ voice_ctx.REPORTS_JSONL = voice.REPORTS_JSONL
107
+ voice_ctx.REPORTS_OUTBOX = voice.REPORTS_OUTBOX
108
+ voice_ctx.OPERATIONS_LOG = voice.OPERATIONS_LOG
109
+ voice_ctx.HF_TOKEN_PATH = voice.HF_TOKEN_PATH
110
 
111
  def tearDown(self):
112
  voice.VOICES_DIR = self._old
113
  (voice.REPORTS_JSONL, voice.REPORTS_OUTBOX, voice.OPERATIONS_LOG,
114
  voice.HF_TOKEN_PATH) = self._old_reports
115
+ voice_ctx = sys.modules.get("vlib.ctx")
116
+ if voice_ctx is not None:
117
+ voice_ctx.VOICES_DIR = self._old
118
+ (voice_ctx.REPORTS_JSONL, voice_ctx.REPORTS_OUTBOX, voice_ctx.OPERATIONS_LOG,
119
+ voice_ctx.HF_TOKEN_PATH) = self._old_reports
120
 
121
 
122
  def ns(**kw):
tests/test_units.py CHANGED
@@ -1,5 +1,6 @@
1
  """Offline unit tests: pure logic, no network. Must stay fast (<60s total)."""
2
  import json
 
3
  import struct
4
  import sys
5
  import tempfile
@@ -8,6 +9,7 @@ from pathlib import Path
8
 
9
  sys.path.insert(0, str(Path(__file__).resolve().parent))
10
  from _helpers import IsolatedVoices, ns, voice # noqa: E402
 
11
 
12
  import numpy as np
13
 
@@ -55,6 +57,25 @@ class TestNames(unittest.TestCase):
55
  with self.assertRaises(SystemExit):
56
  voice._resolve_name(ns(name="bad name!"), "fallback")
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def test_registry_folder_lowercases(self):
59
  self.assertTrue(str(voice._registry_folder("StyleTune-V2")).endswith("styletune-v2"))
60
 
@@ -271,7 +292,163 @@ class TestDeltaGates(unittest.TestCase):
271
  v, b = self._pair(d)
272
  out = voice._compute_delta(str(v), str(b), d / "out", "delta-x", ns())
273
  got = read_arr(out, "output.weight")
274
- self.assertTrue((got == 1.0).all())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
  def test_niche_gates_fail_fast_non_interactive(self):
277
  for mv, mb in ((False, True), (True, False), (True, True)):
@@ -353,6 +530,160 @@ class TestQuantShapes(unittest.TestCase):
353
 
354
 
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  class TestRunGuarded(unittest.TestCase):
357
  def test_timeout_reaps_child(self):
358
  import subprocess
@@ -382,5 +713,380 @@ class TestRunGuarded(unittest.TestCase):
382
  r = run_guarded(["echo", "hi"], timeout=30)
383
  self.assertEqual((r.returncode, r.stdout.strip()), (0, "hi"))
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  if __name__ == "__main__":
386
  unittest.main()
 
1
  """Offline unit tests: pure logic, no network. Must stay fast (<60s total)."""
2
  import json
3
+ import os
4
  import struct
5
  import sys
6
  import tempfile
 
9
 
10
  sys.path.insert(0, str(Path(__file__).resolve().parent))
11
  from _helpers import IsolatedVoices, ns, voice # noqa: E402
12
+ from vlib.pack import _resolve_pack_tensors # noqa: E402
13
 
14
  import numpy as np
15
 
 
57
  with self.assertRaises(SystemExit):
58
  voice._resolve_name(ns(name="bad name!"), "fallback")
59
 
60
+ def test_public_source_passthrough(self):
61
+ for good in ("org/model", "google/gemma-4-12b", "model.gguf",
62
+ "voice-name", "", "β€”"):
63
+ self.assertEqual(voice._public_source(good), good, good)
64
+
65
+ def test_public_source_strips_local_paths(self):
66
+ self.assertEqual(
67
+ voice._public_source("/Users/someone/Downloads/m.gguf"), "m.gguf")
68
+ self.assertEqual(voice._public_source("./rel/m.gguf"), "m.gguf")
69
+ self.assertEqual(voice._public_source("~/m.gguf"), "m.gguf")
70
+
71
+ def test_public_source_strips_url_secrets(self):
72
+ self.assertEqual(
73
+ voice._public_source("https://user:pw@huggingface.co/o/m"),
74
+ "https://huggingface.co/o/m")
75
+ self.assertEqual(
76
+ voice._public_source("https://cdn.example/x.bin?sig=SECRET&t=1"),
77
+ "https://cdn.example/x.bin")
78
+
79
  def test_registry_folder_lowercases(self):
80
  self.assertTrue(str(voice._registry_folder("StyleTune-V2")).endswith("styletune-v2"))
81
 
 
292
  v, b = self._pair(d)
293
  out = voice._compute_delta(str(v), str(b), d / "out", "delta-x", ns())
294
  got = read_arr(out, "output.weight")
295
+ self.assertTrue((got == 1.0).all(), "delta must equal voice(2.0) - base(1.0)")
296
+
297
+ def test_tied_base_head_falls_back_to_embed(self):
298
+ import io
299
+ from contextlib import redirect_stdout
300
+ d = Path(tempfile.mkdtemp())
301
+ v, b = d / "v.safetensors", d / "b.safetensors"
302
+ write_st(v, {"lm_head.weight": (np.full((4, 4), 2.0, dtype=np.float16), "F16"),
303
+ "model.language_model.embed_tokens.weight": (np.full((4, 4), 7.0, dtype=np.float16), "F16")})
304
+ write_st(b, {"model.language_model.embed_tokens.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16")})
305
+ buf = io.StringIO()
306
+ with redirect_stdout(buf):
307
+ out = voice._compute_delta(str(v), str(b), d / "out", "delta-x", ns())
308
+ self.assertTrue((read_arr(out, "lm_head.weight") == 1.0).all(), "head delta must equal voice head(2.0) - tied base embed(1.0)")
309
+ self.assertTrue((read_arr(out, "model.language_model.embed_tokens.weight") == 6.0).all(), "embed delta must equal voice embed(7.0) - base embed(1.0)")
310
+ self.assertIn("tied", buf.getvalue(), "fallback must be loud, not silent")
311
+
312
+ def test_tied_base_shape_mismatch_fails(self):
313
+ d = Path(tempfile.mkdtemp())
314
+ v, b = d / "v.safetensors", d / "b.safetensors"
315
+ write_st(v, {"lm_head.weight": (np.full((4, 4), 2.0, dtype=np.float16), "F16")})
316
+ write_st(b, {"model.language_model.embed_tokens.weight": (np.full((4, 8), 1.0, dtype=np.float16), "F16")})
317
+ with self.assertRaises(SystemExit):
318
+ voice._compute_delta(str(v), str(b), d / "out", "delta-x", ns())
319
+
320
+ def test_headless_baseless_still_fails(self):
321
+ d = Path(tempfile.mkdtemp())
322
+ v, b = d / "v.safetensors", d / "b.safetensors"
323
+ write_st(v, {"lm_head.weight": (np.full((4, 4), 2.0, dtype=np.float16), "F16")})
324
+ write_st(b, {"blk.0.attn.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16")})
325
+ with self.assertRaises(SystemExit):
326
+ voice._compute_delta(str(v), str(b), d / "out", "delta-x", ns())
327
+
328
+ def test_tied_head_want_reroutes_to_embed(self):
329
+ d = Path(tempfile.mkdtemp())
330
+ b = d / "tied.safetensors"
331
+ write_st(b, {"model.language_model.embed_tokens.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16")})
332
+ self.assertEqual(voice._tied_head_want(str(b), None, ["lm_head.weight"]),
333
+ ["model.language_model.embed_tokens.weight"])
334
+
335
+ def test_tied_head_want_dedupes_embed(self):
336
+ d = Path(tempfile.mkdtemp())
337
+ b = d / "tied.safetensors"
338
+ write_st(b, {"model.language_model.embed_tokens.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16")})
339
+ self.assertEqual(voice._tied_head_want(str(b), None,
340
+ ["lm_head.weight", "model.language_model.embed_tokens.weight"]),
341
+ ["model.language_model.embed_tokens.weight"])
342
+
343
+ def test_tied_head_want_untied_unchanged(self):
344
+ d = Path(tempfile.mkdtemp())
345
+ b = d / "untied.safetensors"
346
+ write_st(b, {"lm_head.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16"),
347
+ "model.language_model.embed_tokens.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16")})
348
+ self.assertEqual(voice._tied_head_want(str(b), None, ["lm_head.weight"]), ["lm_head.weight"])
349
+
350
+ def test_tied_head_want_headless_unchanged(self):
351
+ d = Path(tempfile.mkdtemp())
352
+ b = d / "bare.safetensors"
353
+ write_st(b, {"blk.0.attn.weight": (np.full((4, 4), 1.0, dtype=np.float16), "F16")})
354
+ self.assertEqual(voice._tied_head_want(str(b), None, ["lm_head.weight"]), ["lm_head.weight"])
355
+
356
+ def test_gguf_delta_chunked_matches_full(self):
357
+ # Slow path (either side GGUF) must stream dequant in bounded chunks:
358
+ # patch the seam small + unaligned, spy max slice, assert values equal
359
+ # full-dequant math and every slice bounded by the seam.
360
+ from gguf import GGUFWriter
361
+ from gguf.constants import GGMLQuantizationType as Q
362
+ from gguf.quants import quantize
363
+ d = Path(tempfile.mkdtemp())
364
+ shape = (64, 128)
365
+ rng = np.random.default_rng(7)
366
+ vf = (rng.normal(size=shape) * 2).astype(np.float32)
367
+ bf = (rng.normal(size=shape) * 2).astype(np.float32)
368
+ vp, bp = d / "v.gguf", d / "b.gguf"
369
+ for p, f in ((vp, vf), (bp, bf)):
370
+ w = GGUFWriter(str(p), "llama", use_temp_file=False)
371
+ w.add_tensor("output.weight", quantize(f, Q.Q8_0), raw_dtype=Q.Q8_0)
372
+ w.write_header_to_file()
373
+ w.write_kv_data_to_file()
374
+ w.write_tensors_to_file()
375
+ w.close()
376
+ old_chunk = voice._DELTA_CHUNK_ELEMS
377
+ voice._DELTA_CHUNK_ELEMS = 100 # small and unaligned vs Q8_0 block 32
378
+ max_slice = []
379
+ orig_slice = voice._gguf_raw_slice_to_f32
380
+ def _spy(raw, qt, off, n):
381
+ max_slice.append(n)
382
+ return orig_slice(raw, qt, off, n)
383
+ voice._gguf_raw_slice_to_f32 = _spy
384
+ try:
385
+ out = voice._compute_delta(str(vp), str(bp), d / "out", "delta-x", ns())
386
+ finally:
387
+ voice._DELTA_CHUNK_ELEMS = old_chunk
388
+ voice._gguf_raw_slice_to_f32 = orig_slice
389
+ self.assertGreater(len(max_slice), 2, "expected many bounded chunks")
390
+ self.assertLessEqual(max(max_slice), 100, max_slice[:5])
391
+ vsrc, bsrc = voice.open_source(str(vp)), voice.open_source(str(bp))
392
+ want = (vsrc.read_f32("output.weight").astype(np.float32)
393
+ - bsrc.read_f32("output.weight").astype(np.float32)).astype("float16")
394
+ got = read_arr(out, "output.weight")
395
+ self.assertTrue((got == want).all())
396
+ def test_share_safe_delta_header(self):
397
+ import json as _j
398
+ d = Path(tempfile.mkdtemp())
399
+ home = d / "Users" / "someone" / "Desktop"
400
+ home.mkdir(parents=True)
401
+ self._pair(home)
402
+ out = voice._compute_delta(str(home / "v.safetensors"),
403
+ str(home / "b.safetensors"),
404
+ d / "out", "delta-x", ns())
405
+ outp = Path(str(out))
406
+ data = (outp / "voice.safetensors").read_bytes() \
407
+ if outp.is_dir() else outp.read_bytes()
408
+ hs = struct.unpack("<Q", data[:8])[0]
409
+ meta = _j.loads(data[8:8 + hs]).get("__metadata__", {})
410
+ blob = _j.dumps(meta)
411
+ self.assertNotIn("Users", blob)
412
+ self.assertNotIn("someone", blob)
413
+ self.assertEqual(meta.get("base"), "b.safetensors")
414
+ self.assertEqual(meta.get("voice"), "v.safetensors")
415
+
416
+ def test_verify_agrees(self):
417
+ d = Path(tempfile.mkdtemp())
418
+ write_st(d / "v.safetensors",
419
+ {"output.weight": (np.zeros((4, 4), dtype=np.float16), "F16")})
420
+ meta = {"tensor_name": "output.weight", "shape": [4, 4],
421
+ "dtype": "F16", "type": "voice"}
422
+ self.assertEqual(voice._verify_voice_file(str(d / "v.safetensors"), meta), [])
423
+
424
+ def test_verify_flags_mismatch(self):
425
+ d = Path(tempfile.mkdtemp())
426
+ write_st(d / "v.safetensors",
427
+ {"output.weight": (np.zeros((4, 4), dtype=np.float16), "F16")})
428
+ meta = {"tensor_name": "output.weight", "shape": [262144, 2048],
429
+ "dtype": "BF16", "type": "voice"}
430
+ warns = voice._verify_voice_file(str(d / "v.safetensors"), meta)
431
+ blob = "\n".join(warns)
432
+ self.assertIn("MISMATCH shape", blob)
433
+ self.assertIn("MISMATCH dtype", blob)
434
+ self.assertIn("file wins", blob)
435
+
436
+ def test_gguf_slice_q4k_superblocks(self):
437
+ # K-quants share scales across 256-elem superblocks: slices crossing
438
+ # superblock boundaries must equal full dequantize cut to the same range.
439
+ # (gguf lib can't quantize Q4_K here, so synthesize raw superblock bytes.)
440
+ from gguf.constants import GGMLQuantizationType as Q
441
+ from gguf.quants import dequantize
442
+ import numpy as _np
443
+ elems, bs, ts = 1024, 256, 144
444
+ raw = _np.random.default_rng(3).integers(0, 256, size=elems // bs * ts).astype(_np.uint8).tobytes()
445
+ full = dequantize(_np.frombuffer(raw, dtype=_np.uint8).reshape(-1, ts), Q.Q4_K).reshape(-1)
446
+ for off, n in ((0, 100), (200, 100), (100, 500), (900, 124), (0, 1024)):
447
+ b0 = off // bs
448
+ b1 = (off + n + bs - 1) // bs
449
+ seg = raw[b0 * ts:b1 * ts]
450
+ got = voice._gguf_raw_slice_to_f32(seg, Q.Q4_K, off - b0 * bs, n)
451
+ self.assertTrue((got == full[off:off + n]).all(), f"off={off} n={n}")
452
 
453
  def test_niche_gates_fail_fast_non_interactive(self):
454
  for mv, mb in ((False, True), (True, False), (True, True)):
 
530
 
531
 
532
 
533
+ class TestVoicePacks(IsolatedVoices):
534
+ def _pack_pair(self, d):
535
+ write_st(d / "v.safetensors", {
536
+ "output.weight": (np.full((4, 4), 3.0, np.float16), "F16"),
537
+ "model.norm.weight": (np.full((8,), 5.0, np.float16), "F16")})
538
+ write_st(d / "b.safetensors", {
539
+ "output.weight": (np.full((4, 4), 2.0, np.float16), "F16"),
540
+ "model.norm.weight": (np.full((8,), 4.0, np.float16), "F16"),
541
+ "extra.weight": (np.zeros((2, 2), np.float16), "F16")})
542
+ return d / "v.safetensors", d / "b.safetensors"
543
+
544
+ def test_pack_delta_computes_all(self):
545
+ d = Path(tempfile.mkdtemp())
546
+ v, b = self._pack_pair(d)
547
+ out = voice._compute_delta(str(v), str(b), d / "out", "delta-x", ns())
548
+ for name, want in (("output.weight", 1.0), ("model.norm.weight", 1.0)):
549
+ got = read_arr(out, name)
550
+ self.assertTrue((got == want).all(), name)
551
+
552
+ def test_pack_delta_missing_name_fails(self):
553
+ d = Path(tempfile.mkdtemp())
554
+ write_st(d / "v.safetensors",
555
+ {"ghost.weight": (np.zeros((2, 2), np.float16), "F16")})
556
+ write_st(d / "b.safetensors",
557
+ {"other.weight": (np.zeros((2, 2), np.float16), "F16")})
558
+ with self.assertRaises(SystemExit):
559
+ voice._compute_delta(str(d / "v.safetensors"), str(d / "b.safetensors"),
560
+ d / "out", "delta-x", ns())
561
+
562
+ def test_tensor_cast_delta_fuses_not_replaces(self):
563
+ from gguf import GGUFReader, GGUFWriter
564
+ from gguf.constants import GGMLQuantizationType as Q
565
+ d = Path(tempfile.mkdtemp())
566
+ write_st(d / "v.safetensors",
567
+ {"output.weight": (np.full((4, 4), 3.0, np.float16), "F16")})
568
+ write_st(d / "b.safetensors",
569
+ {"output.weight": (np.full((4, 4), 2.0, np.float16), "F16")})
570
+ dout = voice._compute_delta(str(d / "v.safetensors"), str(d / "b.safetensors"),
571
+ d / "dout", "delta-x", ns())
572
+ tp = d / "t.gguf"
573
+ w = GGUFWriter(str(tp), "llama", use_temp_file=False)
574
+ w.add_architecture()
575
+ w.add_tensor("output.weight", np.full((4, 4), 10.0, np.float16), raw_dtype=Q.F16)
576
+ w.write_header_to_file()
577
+ w.write_kv_data_to_file()
578
+ w.write_tensors_to_file()
579
+ w.close()
580
+ out = d / "voiced.gguf"
581
+ voice.cmd_cast(ns(target=str(tp), voice=str(dout), out=str(out)))
582
+ got = [t for t in GGUFReader(str(out)).tensors if t.name == "output.weight"][0]
583
+ # base 10 + delta (3-2) = 11. Raw-delta replace would give 1.
584
+ self.assertTrue((np.asarray(got.data, dtype=np.float32) == 11.0).all())
585
+
586
+ def test_pack_cast_gguf_fuses_all(self):
587
+ from gguf import GGUFReader, GGUFWriter
588
+ from gguf.constants import GGMLQuantizationType as Q
589
+ d = Path(tempfile.mkdtemp())
590
+ v, b = self._pack_pair(d)
591
+ dout = voice._compute_delta(str(v), str(b), d / "dout", "delta-x", ns())
592
+ tp = d / "t.gguf"
593
+ w = GGUFWriter(str(tp), "llama", use_temp_file=False)
594
+ w.add_architecture()
595
+ w.add_tensor("output.weight", np.full((4, 4), 10.0, np.float16), raw_dtype=Q.F16)
596
+ w.add_tensor("model.norm.weight", np.full((8,), 20.0, np.float32), raw_dtype=Q.F32)
597
+ w.write_header_to_file()
598
+ w.write_kv_data_to_file()
599
+ w.write_tensors_to_file()
600
+ w.close()
601
+ out = d / "voiced.gguf"
602
+ voice.cmd_cast(ns(target=str(tp), voice=str(dout), out=str(out)))
603
+ back = {t.name: t for t in GGUFReader(str(out)).tensors}
604
+ self.assertTrue((np.asarray(back["output.weight"].data, dtype=np.float32) == 11.0).all())
605
+ self.assertTrue((np.asarray(back["model.norm.weight"].data, dtype=np.float32) == 21.0).all())
606
+
607
+ def test_pack_cast_safetensors_fuses_all(self):
608
+
609
+ d = Path(tempfile.mkdtemp())
610
+ v, b = self._pack_pair(d)
611
+ dout = voice._compute_delta(str(v), str(b), d / "dout", "delta-x", ns())
612
+ write_st(d / "t.safetensors", {
613
+ "output.weight": (np.full((4, 4), 10.0, np.float16), "F16"),
614
+ "model.norm.weight": (np.full((8,), 20.0, np.float16), "F16")})
615
+ out = d / "o.safetensors"
616
+ voice.cmd_cast(ns(target=str(d / "t.safetensors"),
617
+ voice=str(dout), out=str(out)))
618
+ for name, want in (("output.weight", 11.0), ("model.norm.weight", 21.0)):
619
+ got = read_arr(out, name)
620
+ self.assertTrue((got == want).all(), name)
621
+
622
+ def test_pack_cast_safetensors_single_pass(self):
623
+ # The old chain called rewrite_safetensors_tensor once per tensor
624
+ # (O(N.file) full copies). The fused path must succeed with it disabled.
625
+ d = Path(tempfile.mkdtemp())
626
+ v, b = self._pack_pair(d)
627
+ dout = voice._compute_delta(str(v), str(b), d / "dout", "delta-x", ns())
628
+ write_st(d / "t.safetensors", {
629
+ "output.weight": (np.full((4, 4), 10.0, np.float16), "F16"),
630
+ "model.norm.weight": (np.full((8,), 20.0, np.float16), "F16")})
631
+ out = d / "o.safetensors"
632
+ orig = voice.rewrite_safetensors_tensor
633
+ def _boom(*a, **k):
634
+ raise AssertionError("chained per-tensor rewrite used")
635
+ voice.rewrite_safetensors_tensor = _boom
636
+ try:
637
+ voice.cmd_cast(ns(target=str(d / "t.safetensors"),
638
+ voice=str(dout), out=str(out)))
639
+ finally:
640
+ voice.rewrite_safetensors_tensor = orig
641
+ for name, want in (("output.weight", 11.0), ("model.norm.weight", 21.0)):
642
+ got = read_arr(out, name)
643
+ self.assertTrue((got == want).all(), name)
644
+
645
+
646
+ class TestBaseCache(unittest.TestCase):
647
+ def _cache(self, d, tensors):
648
+ write_st(d / "base.safetensors", tensors)
649
+ return d / "base.safetensors"
650
+
651
+ def test_validate_hit(self):
652
+ d = Path(tempfile.mkdtemp())
653
+ p = self._cache(d, {"a": (np.zeros((4, 4), np.float16), "F16")})
654
+ self.assertEqual(voice._cache_validate(str(p), ["a"]), [])
655
+
656
+ def test_validate_missing_and_truncated(self):
657
+ import struct as _st, json as _j
658
+ d = Path(tempfile.mkdtemp())
659
+ p = self._cache(d, {"a": (np.zeros((4, 4), np.float16), "F16")})
660
+ raw = bytearray(p.read_bytes())
661
+ hs = _st.unpack("<Q", bytes(raw[:8]))[0]
662
+ hdr = _j.loads(bytes(raw[8:8 + hs]))
663
+ hdr["b"] = {"dtype": "F16", "shape": [8], "data_offsets": [32, 48]}
664
+ hj = _j.dumps(hdr).encode()
665
+ p.write_bytes(_st.pack("<Q", len(hj)) + hj + bytes(raw[8 + hs:]))
666
+ self.assertEqual(sorted(voice._cache_validate(str(p), ["a", "b", "c"])), ["b", "c"])
667
+
668
+ def test_validate_garbage(self):
669
+ d = Path(tempfile.mkdtemp())
670
+ p = d / "base.safetensors"
671
+ p.write_bytes(b"not-a-safetensors-file")
672
+ self.assertEqual(voice._cache_validate(str(p), ["a"]), ["a"])
673
+
674
+ def test_topup_write_roundtrip(self):
675
+ d = Path(tempfile.mkdtemp())
676
+ p = self._cache(d, {"a": (np.full((4, 4), 7.0, np.float16), "F16")})
677
+ rawf = d / "raw.bin"
678
+ rawf.write_bytes(np.full((8,), 9.0, np.float16).tobytes())
679
+ staged = [("b", np.full((2, 2), 3.0, np.float16).tobytes(), "F16", (2, 2), None),
680
+ ("c", None, "F16", (8,), rawf)]
681
+ new_hdr = voice._cache_topup_write(p, staged)
682
+ self.assertEqual(sorted(k for k in new_hdr if k != "__metadata__"), ["a", "b", "c"])
683
+ for name, want in (("a", 7.0), ("b", 3.0), ("c", 9.0)):
684
+ self.assertTrue((read_arr(p, name) == want).all(), name)
685
+
686
+
687
  class TestRunGuarded(unittest.TestCase):
688
  def test_timeout_reaps_child(self):
689
  import subprocess
 
713
  r = run_guarded(["echo", "hi"], timeout=30)
714
  self.assertEqual((r.returncode, r.stdout.strip()), (0, "hi"))
715
 
716
+ class TestAliases(unittest.TestCase):
717
+ GGUF_NAMES = ["token_embd.weight", "blk.28.attn_o.weight", "blk.29.attn_o.weight",
718
+ "blk.29.ffn_down.weight", "output_norm.weight", "output.weight"]
719
+
720
+ def test_alias_both_directions(self):
721
+ self.assertEqual(voice._pack_aliases("model.language_model.embed_tokens.weight", self.GGUF_NAMES),
722
+ ["token_embd.weight"])
723
+ self.assertEqual(voice._pack_aliases("token_embd.weight",
724
+ ["model.language_model.embed_tokens.weight", "output.weight"]),
725
+ ["model.language_model.embed_tokens.weight"])
726
+ self.assertEqual(voice._pack_aliases("model.language_model.layers.29.self_attn.o_proj.weight",
727
+ self.GGUF_NAMES),
728
+ ["blk.29.attn_o.weight"])
729
+ self.assertEqual(voice._pack_aliases("lm_head.weight", self.GGUF_NAMES), ["output.weight"])
730
+
731
+ def test_alias_block_mismatch_is_no_match(self):
732
+ self.assertEqual(voice._pack_aliases("model.language_model.layers.28.self_attn.o_proj.weight",
733
+ ["blk.29.attn_o.weight"]), [])
734
+ self.assertEqual(voice._pack_aliases("someday.weight", self.GGUF_NAMES), [])
735
+
736
+ def test_alias_ambiguous_lists_all(self):
737
+ self.assertEqual(sorted(voice._pack_aliases("model.embed_tokens.weight",
738
+ ["a.token_embd.weight", "b.token_embd.weight"])),
739
+ ["a.token_embd.weight", "b.token_embd.weight"])
740
+
741
+ def test_alias_modern_gguf_variants(self):
742
+ # Evidenced live: Gemma3/Qwen3 GGUFs use attn_output + post_attention_norm.
743
+ names = ["blk.29.attn_output.weight", "blk.29.post_attention_norm.weight"]
744
+ self.assertEqual(voice._pack_aliases("model.layers.29.self_attn.o_proj.weight", names),
745
+ ["blk.29.attn_output.weight"])
746
+ self.assertEqual(voice._pack_aliases("model.layers.29.post_attention_layernorm.weight", names),
747
+ ["blk.29.post_attention_norm.weight"])
748
+
749
+ def test_alias_glm_generations(self):
750
+ # Evidenced live: ChatGLM-style HF names vs llama.cpp GGUF names.
751
+ gguf = ["token_embd.weight", "blk.39.attn_output.weight",
752
+ "blk.39.ffn_down.weight", "output_norm.weight", "output.weight"]
753
+ self.assertEqual(voice._pack_aliases("transformer.encoder.layers.39.self_attention.dense.weight", gguf),
754
+ ["blk.39.attn_output.weight"])
755
+ self.assertEqual(voice._pack_aliases("transformer.encoder.layers.39.mlp.dense_4h_to_h.weight", gguf),
756
+ ["blk.39.ffn_down.weight"])
757
+ self.assertEqual(voice._pack_aliases("transformer.encoder.final_layernorm.weight", gguf),
758
+ ["output_norm.weight"])
759
+ self.assertEqual(voice._pack_aliases("transformer.output_layer.weight", gguf),
760
+ ["output.weight"])
761
+
762
+ def test_pack_preset_head_only(self):
763
+ # Preset is the output head, nothing else. Tied repos resolve the embed
764
+ # (it doubles as head); untied resolve lm_head.
765
+ d = Path(tempfile.mkdtemp())
766
+ write_st(d / "tied.safetensors", {
767
+ "model.language_model.embed_tokens.weight": (np.zeros((8, 4), np.float16), "F16"),
768
+ "model.language_model.norm.weight": (np.zeros((4,), np.float16), "F16")})
769
+ write_st(d / "untied.safetensors", {
770
+ "lm_head.weight": (np.zeros((8, 4), np.float16), "F16"),
771
+ "model.embed_tokens.weight": (np.zeros((8, 4), np.float16), "F16"),
772
+ "model.norm.weight": (np.zeros((4,), np.float16), "F16")})
773
+ src = voice.open_source(str(d / "tied.safetensors"))
774
+ self.assertEqual(_resolve_pack_tensors(src, "r", None, src.names()),
775
+ ["model.language_model.embed_tokens.weight"])
776
+ src = voice.open_source(str(d / "untied.safetensors"))
777
+ self.assertEqual(_resolve_pack_tensors(src, "r", None, src.names()),
778
+ ["lm_head.weight"])
779
+
780
+ def test_alias_moe_experts(self):
781
+ # Evidenced live: Qwen3-30B-A3B GGUF experts vs HF experts.* names.
782
+ gguf = ["blk.5.ffn_down_exps.weight", "blk.5.ffn_gate_inp.weight"]
783
+ self.assertEqual(voice._pack_aliases("model.layers.5.experts.down_proj", gguf),
784
+ ["blk.5.ffn_down_exps.weight"])
785
+ # fused HF gate_up has no 1:1 GGUF counterpart (split exps) β€” no match, fail loud downstream
786
+ self.assertEqual(voice._pack_aliases("model.layers.5.experts.gate_up_proj", gguf), [])
787
+ # routers never map (semantics differ per arch)
788
+ self.assertEqual(voice._pack_aliases("model.layers.5.router.per_expert_scale", gguf), [])
789
+
790
+ def test_pack_cast_bridges_naming(self):
791
+ # ST pack names onto a GGUF target speaking llama.cpp names (replace mode).
792
+ from gguf import GGUFReader, GGUFWriter
793
+ from gguf.constants import GGMLQuantizationType as Q
794
+ d = Path(tempfile.mkdtemp())
795
+ write_st(d / "pack.safetensors", {
796
+ "model.language_model.embed_tokens.weight": (np.full((4, 4), 3.0, np.float16), "F16"),
797
+ "lm_head.weight": (np.full((4, 4), 7.0, np.float16), "F16")})
798
+ tp = d / "t.gguf"
799
+ w = GGUFWriter(str(tp), "llama", use_temp_file=False)
800
+ w.add_architecture()
801
+ w.add_tensor("token_embd.weight", np.zeros((4, 4), dtype=np.float16), raw_dtype=Q.F16)
802
+ w.add_tensor("output.weight", np.zeros((4, 4), dtype=np.float16), raw_dtype=Q.F16)
803
+ w.write_header_to_file()
804
+ w.write_kv_data_to_file()
805
+ w.write_tensors_to_file()
806
+ w.close()
807
+ out = d / "voiced.gguf"
808
+ voice.cmd_cast(ns(target=str(tp), voice=str(d / "pack.safetensors"), out=str(out)))
809
+ back = {t.name: np.asarray(t.data, dtype=np.float32) for t in GGUFReader(str(out)).tensors}
810
+ self.assertTrue((back["token_embd.weight"] == 3.0).all())
811
+ self.assertTrue((back["output.weight"] == 7.0).all())
812
+
813
+
814
+ def test_nearest_encodable_mapping(self):
815
+ from gguf.constants import GGMLQuantizationType as Q
816
+ self.assertEqual(voice._nearest_encodable(Q.IQ4_NL), Q.Q4_0) # same 4.5bpp
817
+ self.assertEqual(voice._nearest_encodable(Q.Q4_K), Q.Q4_0)
818
+ self.assertEqual(voice._nearest_encodable(Q.Q6_K), Q.Q5_1)
819
+
820
+ def test_unencodable_target_keeps_nearest(self):
821
+ # Q4_K target (decode-only in gguf lib): fused output lands on Q4_0, not F16.
822
+ from gguf import GGUFReader, GGUFWriter
823
+ from gguf.constants import GGMLQuantizationType as Q
824
+ from gguf.quants import quant_shape_to_byte_shape
825
+ d = Path(tempfile.mkdtemp())
826
+ logical = (256, 256)
827
+ raw = np.zeros(quant_shape_to_byte_shape(logical, Q.Q4_K), dtype=np.uint8)
828
+ tp = d / "t.gguf"
829
+ w = GGUFWriter(str(tp), "llama", use_temp_file=False)
830
+ w.add_architecture()
831
+ w.add_tensor("blk.0.ffn_down.weight", raw, raw_dtype=Q.Q4_K)
832
+ w.write_header_to_file()
833
+ w.write_kv_data_to_file()
834
+ w.write_tensors_to_file()
835
+ w.close()
836
+ got = [t for t in GGUFReader(str(tp)).tensors if t.name == "blk.0.ffn_down.weight"][0]
837
+ self.assertEqual(got.tensor_type, Q.Q4_K)
838
+ write_st(d / "v.safetensors",
839
+ {"blk.0.ffn_down.weight": (np.full(logical, 1.0, np.float16), "F16")})
840
+ write_st(d / "b.safetensors",
841
+ {"blk.0.ffn_down.weight": (np.zeros(logical, np.float16), "F16")})
842
+ dout = voice._compute_delta(str(d / "v.safetensors"), str(d / "b.safetensors"),
843
+ d / "dout", "delta-x", ns())
844
+ out = d / "voiced.gguf"
845
+ voice.cmd_cast(ns(target=str(tp), voice=str(dout), out=str(out)))
846
+ fused = [t for t in GGUFReader(str(out)).tensors if t.name == "blk.0.ffn_down.weight"][0]
847
+ self.assertEqual(fused.tensor_type, Q.Q4_0)
848
+
849
+
850
+ def test_batch_miss_reports_all_with_hints(self):
851
+ # One failure names EVERY miss (no fix-forward) with same-size hints.
852
+ import io
853
+ from contextlib import redirect_stdout
854
+ names = ["token_embd.weight", "blk.29.attn_o.weight", "other.weight"]
855
+ counts = {"token_embd.weight": 16, "blk.29.attn_o.weight": 16, "other.weight": 64}
856
+ pairs = [("model.language_model.layers.29.self_attn.o_proj.weight", (4, 4)),
857
+ ("ghost.weight", (8, 8)),
858
+ ("phantom.weight", (2, 2))]
859
+ buf = io.StringIO()
860
+ with redirect_stdout(buf):
861
+ with self.assertRaises(SystemExit):
862
+ voice._resolve_pack_targets(pairs, names, "t.gguf", counts)
863
+ blob = buf.getvalue()
864
+ self.assertIn("ghost.weight", blob)
865
+ self.assertIn("phantom.weight", blob)
866
+ self.assertIn("other.weight", blob) # same-size hint for the 8x8 ghost
867
+ self.assertIn("2 tensor(s)", blob) # both misses at once; the aliased one is not listed
868
+ # all-resolvable returns the mapping (exact + alias)
869
+ got = voice._resolve_pack_targets(
870
+ [("model.language_model.layers.29.self_attn.o_proj.weight", (4, 4)),
871
+ ("token_embd.weight", (4, 4))],
872
+ names, "t.gguf", counts)
873
+ self.assertEqual(got, {"model.language_model.layers.29.self_attn.o_proj.weight": "blk.29.attn_o.weight",
874
+ "token_embd.weight": "token_embd.weight"})
875
+
876
+ def test_pack_cast_bridges_same_format_prefix(self):
877
+ # Qwen-style prefixed voice onto bare names: same (block, trailing
878
+ # role) + equal count maps without growing _ROLE_ALIASES.
879
+ import io
880
+ from contextlib import redirect_stdout
881
+ got = voice._resolve_pack_targets(
882
+ [("model.language_model.layers.29.self_attn.o_proj.weight", (4, 4))],
883
+ ["model.layers.29.self_attn.o_proj.weight"], "t.safetensors",
884
+ {"model.layers.29.self_attn.o_proj.weight": 16})
885
+ self.assertEqual(got, {"model.language_model.layers.29.self_attn.o_proj.weight":
886
+ "model.layers.29.self_attn.o_proj.weight"})
887
+ # …but a count mismatch stays loud, not a silent wrong-slot write.
888
+ buf = io.StringIO()
889
+ with redirect_stdout(buf):
890
+ with self.assertRaises(SystemExit):
891
+ voice._resolve_pack_targets(
892
+ [("model.language_model.layers.29.self_attn.o_proj.weight", (4, 8))],
893
+ ["model.layers.29.self_attn.o_proj.weight"], "t.safetensors",
894
+ {"model.layers.29.self_attn.o_proj.weight": 16})
895
+ self.assertIn("not found", buf.getvalue())
896
+
897
+ class TestEntry(IsolatedVoices):
898
+ """Launch-path proofs: the P3 split moved commands into vlib/, so entry
899
+ identity (__main__) must be resolved explicitly, never via __file__."""
900
+
901
+ def test_entry_script_resolves_main(self):
902
+ import types
903
+ fake = types.SimpleNamespace(__file__="voice.py")
904
+ old = sys.modules.get("__main__")
905
+ sys.modules["__main__"] = fake
906
+ try:
907
+ got = voice._entry_script()
908
+ finally:
909
+ if old is not None:
910
+ sys.modules["__main__"] = old
911
+ self.assertTrue(os.path.isabs(got), got)
912
+ self.assertTrue(got.endswith("voice.py"), got)
913
+
914
+ def test_entry_script_unknown_is_none(self):
915
+ import types
916
+ old = sys.modules.get("__main__")
917
+ sys.modules["__main__"] = types.SimpleNamespace()
918
+ try:
919
+ self.assertIsNone(voice._entry_script())
920
+ finally:
921
+ if old is not None:
922
+ sys.modules["__main__"] = old
923
+
924
+ def test_venv_reexec_targets_entry(self):
925
+ # Re-exec must run voice.py (absolute) with argv intact β€” never the module.
926
+ import types
927
+ d = Path(tempfile.mkdtemp())
928
+ vp = d / ".venv" / "bin"
929
+ vp.mkdir(parents=True)
930
+ (vp / "python").symlink_to(sys.executable)
931
+ from vlib import ctx
932
+ old_dir, old_main, old_argv = ctx.VOICES_DIR, sys.modules.get("__main__"), sys.argv[:]
933
+ old_venv = os.environ.pop("VOICE_NO_VENV", None)
934
+ ctx.VOICES_DIR = d
935
+ sys.modules["__main__"] = types.SimpleNamespace(__file__="voice.py")
936
+ sys.argv = ["voice.py", "delta", "x"]
937
+ captured = {}
938
+ orig_execv = os.execv
939
+
940
+ class _Stop(BaseException):
941
+ pass
942
+
943
+ os.execv = lambda p, a: captured.update(path=p, argv=a) or (_ for _ in ()).throw(_Stop())
944
+ try:
945
+ with self.assertRaises(_Stop):
946
+ voice._ensure_venv("delta")
947
+ finally:
948
+ os.execv = orig_execv
949
+ ctx.VOICES_DIR = old_dir
950
+ if old_main is not None:
951
+ sys.modules["__main__"] = old_main
952
+ sys.argv = old_argv
953
+ if old_venv is not None:
954
+ os.environ["VOICE_NO_VENV"] = old_venv
955
+ self.assertTrue(captured["argv"][1].endswith("/voice.py"), captured["argv"])
956
+ self.assertNotIn("cmds_misc", captured["argv"][1])
957
+ self.assertEqual(captured["argv"][2:], ["delta", "x"])
958
+
959
+ def test_entry_help_runs_main(self):
960
+ # The suite calls cmd_* directly and never executes main()'s parser
961
+ # wiring β€” a deleted flag or bad indent kills every real invocation
962
+ # while the suite stays green. This pins the entry path end to end.
963
+ import subprocess
964
+ root = Path(__file__).resolve().parent.parent
965
+ env = dict(os.environ, VOICE_NO_VENV="1")
966
+ outs = []
967
+ for argv in (["--help"], ["cast", "--help"], ["list"]):
968
+ r = subprocess.run([sys.executable, str(root / "voice.py"), *argv],
969
+ capture_output=True, text=True, timeout=120, env=env)
970
+ self.assertEqual(r.returncode, 0, (argv, r.stderr[-500:]))
971
+ outs.append(r.stdout)
972
+ self.assertIn("{get,cast,delta,add,list,info,remove,cleanup,auth,report,path,doctor}", outs[0])
973
+ self.assertIn("--speak", outs[1])
974
+
975
+ def test_voice_tensors_coerces_bare_strings(self):
976
+ # Hand-written voice.json may list tensors as bare strings; readers
977
+ # must render them, never traceback (P1 crash: only.get on a str).
978
+ got, synth = voice._voice_tensors({"tensors": ["lm_head.weight"]})
979
+ self.assertEqual(got, [{"name": "lm_head.weight"}])
980
+ self.assertFalse(synth)
981
+
982
+ def test_quant_override_byte_change_fails_fast(self):
983
+ # --quant f16 on an F32 safetensors target can never fit the fixed
984
+ # offsets: fail up front with guidance, not warn-then-dump (B1).
985
+ import io
986
+ from contextlib import redirect_stdout
987
+ d = Path(tempfile.mkdtemp())
988
+ write_st(d / "t.safetensors",
989
+ {"output.weight": (np.zeros((4, 4), np.float32), "F32")})
990
+ write_st(d / "v.safetensors",
991
+ {"output.weight": (np.zeros((4, 4), np.float32), "F32")})
992
+ header, tensors = voice.read_safetensors(str(d / "t.safetensors"))
993
+ src = voice.open_source(str(d / "v.safetensors"))
994
+ ref = src.ref("output.weight")
995
+ buf = io.StringIO()
996
+ with redirect_stdout(buf):
997
+ with self.assertRaises(SystemExit):
998
+ voice._cast_st_prepare(str(d / "t.safetensors"), tensors,
999
+ src.read_f32("output.weight"), ref, {},
1000
+ ns(quant="f16"))
1001
+ self.assertIn("different byte size", buf.getvalue())
1002
+
1003
+ def test_cancel_exits_nonzero(self):
1004
+ # Abandoned prompts (EOF/decline) must exit nonzero so scripts can
1005
+ # tell cancel from success (B2 contract).
1006
+ from unittest import mock
1007
+ from vlib.ui import _confirm
1008
+ with mock.patch("builtins.input", side_effect=EOFError), \
1009
+ mock.patch.object(sys.stdin, "isatty", return_value=True):
1010
+ with self.assertRaises(SystemExit) as cm:
1011
+ _confirm(" Overwrite? [y/N]: ",
1012
+ ns(non_interactive=False, yes=False))
1013
+ self.assertNotEqual(cm.exception.code, 0)
1014
+
1015
+ def test_requested_subset_routes_by_role(self):
1016
+ # An explicitly named tensor must land in its own slot, never the
1017
+ # output-first fallback (D2: embed request fitted onto lm_head).
1018
+ d = Path(tempfile.mkdtemp())
1019
+ write_st(d / "v.safetensors", {
1020
+ "model.embed_tokens.weight": (np.full((4, 3), 7.0, np.float16), "F16"),
1021
+ "model.lm_head.weight": (np.full((8, 4), 8.0, np.float16), "F16")})
1022
+ write_st(d / "t.safetensors", {
1023
+ "model.embed_tokens.weight": (np.full((4, 3), 1.0, np.float16), "F16"),
1024
+ "model.lm_head.weight": (np.full((8, 4), 2.0, np.float16), "F16")})
1025
+ out = d / "o.safetensors"
1026
+ voice.cmd_cast(ns(target=str(d / "t.safetensors"), voice=str(d / "v.safetensors"),
1027
+ out=str(out), tensors=["model.embed_tokens.weight"]))
1028
+ self.assertTrue((read_arr(out, "model.embed_tokens.weight", np.float16) == 7.0).all())
1029
+ self.assertTrue((read_arr(out, "model.lm_head.weight", np.float16) == 2.0).all())
1030
+
1031
+
1032
+ class TestSymlinkHardening(IsolatedVoices):
1033
+ """Proof tests for the P3 symlink hardening (local CLI, self-owned dir)."""
1034
+
1035
+ def test_unresolvable_path_fails_closed(self):
1036
+ # Old code swallowed resolve errors and returned (fail-open).
1037
+ with self.assertRaises(SystemExit):
1038
+ voice._assert_not_symlink("/tmp/voices-probe\x00evil")
1039
+
1040
+ def test_relative_path_under_symlinked_subdir_rejected(self):
1041
+ # Old code compared raw strings, so a cwd-relative in-registry path
1042
+ # never matched the absolute VOICES_DIR scope and skipped the walk.
1043
+ import os as _os
1044
+ t = Path(tempfile.mkdtemp())
1045
+ (t / "real" / ".voices").mkdir(parents=True)
1046
+ (t / "elsewhere").mkdir()
1047
+ try:
1048
+ (t / "real" / ".voices" / "sub").symlink_to(t / "elsewhere")
1049
+ except OSError:
1050
+ self.skipTest("symlinks unsupported here")
1051
+ ctx_mod = sys.modules["vlib.ctx"]
1052
+ old, old_ctx = voice.VOICES_DIR, ctx_mod.VOICES_DIR
1053
+ prev_cwd = _os.getcwd()
1054
+ try:
1055
+ voice.VOICES_DIR = t / "real" / ".voices"
1056
+ ctx_mod.VOICES_DIR = t / "real" / ".voices"
1057
+ _os.chdir(t)
1058
+ with self.assertRaises(SystemExit):
1059
+ voice._assert_not_symlink("real/.voices/sub/evil")
1060
+ finally:
1061
+ _os.chdir(prev_cwd)
1062
+ voice.VOICES_DIR = old
1063
+ ctx_mod.VOICES_DIR = old_ctx
1064
+
1065
+ def test_scope_boundaries(self):
1066
+ # Legit nested registry path: silent. Prefix-adjacent sibling
1067
+ # (".voices2"-style, outside but string-matching): refused, as before.
1068
+ # Genuinely unrelated user path: untouched.
1069
+ (self.vdir / "myadd").mkdir()
1070
+ voice._assert_not_symlink(self.vdir / "myadd" / "voice.json")
1071
+ sib = self.vdir.parent / (self.vdir.name + "2")
1072
+ sib.mkdir(exist_ok=True)
1073
+ with self.assertRaises(SystemExit):
1074
+ voice._assert_not_symlink(sib / "voice.json")
1075
+ voice._assert_not_symlink(Path(tempfile.mkdtemp()) / "voice.json")
1076
+
1077
+ def test_topup_write_refuses_symlink(self):
1078
+ d = Path(tempfile.mkdtemp())
1079
+ hdr = {"a": {"dtype": "F16", "shape": [4], "data_offsets": [0, 8]}}
1080
+ hj = json.dumps(hdr).encode()
1081
+ real = d / "base.safetensors"
1082
+ real.write_bytes(struct.pack("<Q", len(hj)) + hj + b"\x00" * 8)
1083
+ link = d / "link.safetensors"
1084
+ try:
1085
+ link.symlink_to(real)
1086
+ except OSError:
1087
+ self.skipTest("symlinks unsupported here")
1088
+ with self.assertRaises(SystemExit):
1089
+ voice._cache_topup_write(link, [])
1090
+
1091
  if __name__ == "__main__":
1092
  unittest.main()
vlib/cache.py CHANGED
@@ -5,11 +5,12 @@ import json
5
  import os
6
  import struct
7
  from vlib import ctx
8
- from vlib.ui import _fail, _now_iso, _step, _warn
9
  from vlib.net import _cleanup_tmp, _safe_id, http_get_json
10
  from vlib.tensors import _is_ggml_quant, _write_safetensors_streaming, read_safetensors, write_safetensors
11
  from vlib.sources import _base_paths, open_source
12
  from vlib.fetch import _fetch_tensor, _resolve_output_name, _source_arch
 
13
  from vlib.registry import _assert_not_symlink
14
 
15
 
@@ -118,6 +119,32 @@ def _cache_topup_write(base_file, staged):
118
  return new_hdr
119
 
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def _ensure_base_tensors(base_id, names, args=None):
122
  """Base cache with per-tensor trust: top-ups what's missing, self-heals partial caches.
123
  All fetches pin one revision so a repo update mid-cache can't mix commits. Returns base_file."""
@@ -199,6 +226,7 @@ def _ensure_base_tensors_locked(base_id, base_file, base_json, want):
199
  elif not verified:
200
  _warn(" Base cache lineage unverified (offline?) β€” proceeding, mixed revisions possible.")
201
  rev = _base_rev(base_id, base_file)
 
202
  missing = _cache_validate(base_file, want)
203
  if not missing:
204
  return base_file
 
5
  import os
6
  import struct
7
  from vlib import ctx
8
+ from vlib.ui import _fail, _now_iso, _say, _step, _warn
9
  from vlib.net import _cleanup_tmp, _safe_id, http_get_json
10
  from vlib.tensors import _is_ggml_quant, _write_safetensors_streaming, read_safetensors, write_safetensors
11
  from vlib.sources import _base_paths, open_source
12
  from vlib.fetch import _fetch_tensor, _resolve_output_name, _source_arch
13
+ from vlib.delta import _tied_embed_name, _voice_head_name
14
  from vlib.registry import _assert_not_symlink
15
 
16
 
 
119
  return new_hdr
120
 
121
 
122
+ def _tied_head_want(base_id, rev, want):
123
+ """Reroute a wanted voice head onto the base's tied embed tensor.
124
+ Voice untied + base tied: the base can never serve the head name, so
125
+ want the embed instead (the delta pairs them later). Returns the
126
+ want list, order-preserved and deduplicated. Anything unresolvable
127
+ (offline, base has the head, base not tied) returns want unchanged
128
+ and downstream fails exactly as before."""
129
+ head = _voice_head_name(want)
130
+ if head is None or head not in want:
131
+ return want
132
+ try:
133
+ src = open_source(base_id, rev=rev)
134
+ names = [n for n in src.names() if n != "delta.voice.marker"]
135
+ except SystemExit:
136
+ raise
137
+ except Exception:
138
+ return want
139
+ if head in names:
140
+ return want
141
+ emb = _tied_embed_name(names)
142
+ if emb is None:
143
+ return want
144
+ _say(f" β†’ Base head is tied; caching '{emb}' for the head delta.")
145
+ return list(dict.fromkeys(emb if n == head else n for n in want))
146
+
147
+
148
  def _ensure_base_tensors(base_id, names, args=None):
149
  """Base cache with per-tensor trust: top-ups what's missing, self-heals partial caches.
150
  All fetches pin one revision so a repo update mid-cache can't mix commits. Returns base_file."""
 
226
  elif not verified:
227
  _warn(" Base cache lineage unverified (offline?) β€” proceeding, mixed revisions possible.")
228
  rev = _base_rev(base_id, base_file)
229
+ want = _tied_head_want(base_id, rev, want)
230
  missing = _cache_validate(base_file, want)
231
  if not missing:
232
  return base_file
vlib/delta.py CHANGED
@@ -9,11 +9,57 @@ import time
9
  from vlib.ui import _fail, _noninteractive, _progress_pref, _say, _transform, _warn
10
  from vlib.net import _fmt_eta
11
  from vlib.tensors import _is_delta_safetensors, _is_ggml_quant, decode_to_f32, dtype_bytes, read_safetensors
12
- from vlib.sources import _detect_base, _normalize_hf_repo, _public_source, open_source
13
  from vlib.fetch import _resolve_output_name, _source_arch
14
  from vlib.registry import _assert_not_symlink, _iter_voices, _read_meta
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def _choose_voice(args):
18
  voices = []
19
  for d in _iter_voices():
@@ -162,10 +208,18 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
162
  _cand = None
163
  ordered = ([_cand] if _cand in v_names else []) + [n for n in v_names if n != _cand]
164
  missing = [n for n in ordered if n not in b_names]
 
 
 
 
 
 
 
 
165
  if missing:
166
  shown = ", ".join(missing[:5]) + ("…" if len(missing) > 5 else "")
167
  _fail(f" βœ— {len(missing)} voice tensor(s) have no match in base: {shown}. Delta needs same names on both sides.")
168
- pairs = [(n, n) for n in ordered]
169
  _check_delta_gate(args, voice_path, base_path)
170
  _assert_not_symlink(delta_folder)
171
  delta_folder.mkdir(parents=True, exist_ok=True)
@@ -247,10 +301,18 @@ def _compute_delta(voice_path, base_path, delta_folder, delta_name, args=None):
247
  if not v_keys:
248
  _fail(" βœ— No tensors found for delta.")
249
  missing = [k for k in v_keys if k not in b_tensors]
 
 
 
 
 
 
 
 
250
  if missing:
251
  shown = ", ".join(missing[:5]) + ("…" if len(missing) > 5 else "")
252
  _fail(f" βœ— {len(missing)} voice tensor(s) have no match in base: {shown}. Delta needs same names on both sides.")
253
- pairs = [(k, k) for k in v_keys]
254
  _check_delta_gate(args, voice_path, base_path)
255
  with open(voice_path, "rb") as f:
256
  v_hs = struct.unpack("<Q", f.read(8))[0]
 
9
  from vlib.ui import _fail, _noninteractive, _progress_pref, _say, _transform, _warn
10
  from vlib.net import _fmt_eta
11
  from vlib.tensors import _is_delta_safetensors, _is_ggml_quant, decode_to_f32, dtype_bytes, read_safetensors
12
+ from vlib.sources import _detect_base, _normalize_hf_repo, _public_source, open_source, OUTPUT_ALIASES, EMBEDDING_ALIASES
13
  from vlib.fetch import _resolve_output_name, _source_arch
14
  from vlib.registry import _assert_not_symlink, _iter_voices, _read_meta
15
 
16
 
17
+ _EMBED_SUFFIXES = ("embed_tokens.weight", "word_embeddings.weight", "wte.weight", "token_embd.weight")
18
+
19
+
20
+ def _tied_embed_name(b_names):
21
+ """Base-side tied counterpart: the embed tensor doubling as output head.
22
+ Returns the embed name only when the base is truly headless (no head
23
+ tensor at all); otherwise None."""
24
+ for n in b_names:
25
+ if n in OUTPUT_ALIASES:
26
+ return None
27
+ if n.endswith("weight") and ("lm_head" in n or "output_layer" in n or "embed_out" in n):
28
+ return None
29
+ for n in b_names:
30
+ if n in EMBEDDING_ALIASES or n.endswith(_EMBED_SUFFIXES):
31
+ return n
32
+ return None
33
+
34
+
35
+ def _voice_head_name(v_names):
36
+ """Voice-side output head: exact alias first, head-like pattern after."""
37
+ for n in v_names:
38
+ if n in OUTPUT_ALIASES:
39
+ return n
40
+ for n in v_names:
41
+ if n.endswith("weight") and ("lm_head" in n or "output_layer" in n or "embed_out" in n):
42
+ return n
43
+ return None
44
+
45
+
46
+ def _tied_head_pair(head, b_names, vshape, bshape_of):
47
+ """Voice untied + base tied: pair the voice head with the base's tied
48
+ embed tensor (it serves as both input and output projection, so it is
49
+ the only valid counterpart). Returns the base name, or None when the
50
+ base is not tied. Shapes must match exactly."""
51
+ if head is None:
52
+ return None
53
+ b_emb = _tied_embed_name(b_names)
54
+ if b_emb is None:
55
+ return None
56
+ bshape = tuple(bshape_of(b_emb))
57
+ if tuple(vshape) != bshape:
58
+ _fail(f" βœ— Voice head '{head}' {list(vshape)} doesn't fit tied base '{b_emb}' {list(bshape)} β€” layouts differ.")
59
+ _say(f" β†’ Base head is tied; '{head}' deltas against '{b_emb}'.")
60
+ return b_emb
61
+
62
+
63
  def _choose_voice(args):
64
  voices = []
65
  for d in _iter_voices():
 
208
  _cand = None
209
  ordered = ([_cand] if _cand in v_names else []) + [n for n in v_names if n != _cand]
210
  missing = [n for n in ordered if n not in b_names]
211
+ fallback = {}
212
+ v_head = _voice_head_name(v_names)
213
+ if v_head in missing:
214
+ b = _tied_head_pair(v_head, b_names, tuple(v_src.ref(v_head).shape),
215
+ lambda n: tuple(b_src.ref(n).shape))
216
+ if b is not None:
217
+ fallback[v_head] = b
218
+ missing = [n for n in missing if n not in fallback]
219
  if missing:
220
  shown = ", ".join(missing[:5]) + ("…" if len(missing) > 5 else "")
221
  _fail(f" βœ— {len(missing)} voice tensor(s) have no match in base: {shown}. Delta needs same names on both sides.")
222
+ pairs = [(n, fallback.get(n, n)) for n in ordered]
223
  _check_delta_gate(args, voice_path, base_path)
224
  _assert_not_symlink(delta_folder)
225
  delta_folder.mkdir(parents=True, exist_ok=True)
 
301
  if not v_keys:
302
  _fail(" βœ— No tensors found for delta.")
303
  missing = [k for k in v_keys if k not in b_tensors]
304
+ fallback = {}
305
+ v_head = _voice_head_name(v_keys)
306
+ if v_head in missing:
307
+ b = _tied_head_pair(v_head, list(b_tensors.keys()), tuple(v_tensors[v_head]["shape"]),
308
+ lambda n: tuple(b_tensors[n]["shape"]))
309
+ if b is not None:
310
+ fallback[v_head] = b
311
+ missing = [k for k in missing if k not in fallback]
312
  if missing:
313
  shown = ", ".join(missing[:5]) + ("…" if len(missing) > 5 else "")
314
  _fail(f" βœ— {len(missing)} voice tensor(s) have no match in base: {shown}. Delta needs same names on both sides.")
315
+ pairs = [(k, fallback.get(k, k)) for k in v_keys]
316
  _check_delta_gate(args, voice_path, base_path)
317
  with open(voice_path, "rb") as f:
318
  v_hs = struct.unpack("<Q", f.read(8))[0]
voice-cli.tar.gz CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:6f57c560666c407c280e5880e644e08fd09b2d020b317888f41afced8dc88b9d
3
- size 69538
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8ebae7075d10181b2d6a5a640d8b064358dc3cbdac34a46f91444c00f01c2abf
3
+ size 70556
voice.py CHANGED
@@ -49,7 +49,7 @@ from vlib.tensors import _validate_safetensors_header, read_safetensors, write_s
49
  from vlib.sources import OUTPUT_ALIASES, EMBEDDING_ALIASES, _family, _nested, detect_output_tensor, largest_2d, _hf_config, _hf_license, _hf_model_index, _normalize_hf_repo, _base_paths, _hf_api_base_model, _hf_mergekit_base, _detect_base, TensorRef, SafetensorsSource, GgufSource, open_source, _source_from_repo, _pick_gguf, _sanitize_voice_name, _NAME_RULES, _public_source, _slugify_voice_name
50
  from vlib.fetch import _source_arch, _resolve_output_name, _stream_safetensors_tensor, _fetch_tensor, _stream_gguf_tensor, _fetch_pack, _write_fetched, _write_voicepack_json
51
  from vlib.registry import log_operation, _atomic_jsonl_append, _write_outbox, _append_report, _iter_voices, _registry_resolve, _read_meta, _registry_name_exists, _registry_folder, _assert_not_symlink, _write_voice, _resolve_name, _out_path, _stem, _check_overwrite
52
- from vlib.cache import _base_rev, _cache_validate, _cache_topup_write, _ensure_base_tensors, _ensure_base_tensors_locked, _cache_base, _load_safetensors_or_gguf_f32
53
  from vlib.delta import _choose_voice, _resolve_base, _delta_gate, _check_delta_gate, _compute_delta
54
  from vlib.reports import _hash_file, _sanitize_note, _sanitize_reporter, _prompt_share, _push_reports_batch, _push_report, _last_operation, cmd_report
55
  from vlib.cmds_voices import cmd_get, cmd_cast, cmd_delta, cmd_add
 
49
  from vlib.sources import OUTPUT_ALIASES, EMBEDDING_ALIASES, _family, _nested, detect_output_tensor, largest_2d, _hf_config, _hf_license, _hf_model_index, _normalize_hf_repo, _base_paths, _hf_api_base_model, _hf_mergekit_base, _detect_base, TensorRef, SafetensorsSource, GgufSource, open_source, _source_from_repo, _pick_gguf, _sanitize_voice_name, _NAME_RULES, _public_source, _slugify_voice_name
50
  from vlib.fetch import _source_arch, _resolve_output_name, _stream_safetensors_tensor, _fetch_tensor, _stream_gguf_tensor, _fetch_pack, _write_fetched, _write_voicepack_json
51
  from vlib.registry import log_operation, _atomic_jsonl_append, _write_outbox, _append_report, _iter_voices, _registry_resolve, _read_meta, _registry_name_exists, _registry_folder, _assert_not_symlink, _write_voice, _resolve_name, _out_path, _stem, _check_overwrite
52
+ from vlib.cache import _base_rev, _cache_validate, _cache_topup_write, _ensure_base_tensors, _ensure_base_tensors_locked, _cache_base, _load_safetensors_or_gguf_f32, _tied_head_want
53
  from vlib.delta import _choose_voice, _resolve_base, _delta_gate, _check_delta_gate, _compute_delta
54
  from vlib.reports import _hash_file, _sanitize_note, _sanitize_reporter, _prompt_share, _push_reports_batch, _push_report, _last_operation, cmd_report
55
  from vlib.cmds_voices import cmd_get, cmd_cast, cmd_delta, cmd_add
voice.pyz CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c40ffe00455ad5333d675583f91175f9ae059bce03fa2a8a64dc16837379981e
3
- size 256579
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7568525f51fe2bbebeec82b6dd709bb3fb62f798e52485c0cf63290a7f5e4cec
3
+ size 260239