mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Modèles : corrections de relecture du lecteur GGUF — v2, première tranche sans tenseur et octets corrompus testés
La relecture n'a trouvé aucun défaut dans le lecteur lui-même : bornes, versions, tranches, cache et vérification du début des données tiennent. Trois cas réels restaient pourtant sans test. - GGUF v2 : même disposition que v3, désormais vérifié avec v1 et gros-boutiste. - gguf-split --no-tensor-first-split : la première tranche ne porte que les clés, aucun tenseur ; la tête MTP est trouvée dans la suivante. - Un octet corrompu à chaque position de l'en-tête (v1 et v3, valeurs 0xFF, 0x7F, 0x00) : le lecteur rend la main sans jamais tomber dans le filet du panic rattrapé, qui ne doit rester qu'un filet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e841c26bd
commit
d028e81ce2
1 file changed
+37
-1
@@ -249,6 +249,17 @@ func TestGGUFMetaTranches(t *testing.T) {
|
||||
if _, err := ggufMeta(filepath.Join(dir, "n-00001-of-00002.gguf")); err == nil {
|
||||
t.Fatal("tranche manquante acceptée")
|
||||
}
|
||||
// gguf-split --no-tensor-first-split : la première tranche ne porte que les
|
||||
// clés, aucun tenseur ni aucune donnée. Elle doit passer telle quelle.
|
||||
meta := newGGUF().s("general.architecture", "glm4moe").u32("glm4moe.block_count", 47).
|
||||
u32("split.count", 2)
|
||||
rest := newGGUF().u32("split.no", 1).tensor("blk.46.nextn.eh_proj.weight", 8, 8)
|
||||
writeGGUF(t, dir, "s-00001-of-00002.gguf", meta.bytes())
|
||||
writeGGUF(t, dir, "s-00002-of-00002.gguf", rest.bytes())
|
||||
got, err := ggufMeta(filepath.Join(dir, "s-00001-of-00002.gguf"))
|
||||
if err != nil || got.Arch != "glm4moe" || !got.HasNextNTensor || got.TensorCount != 1 {
|
||||
t.Fatalf("première tranche sans tenseur : %+v, %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
// v1 (compteurs 32 bits) et gros-boutiste : même résultat que le v3 ordinaire.
|
||||
@@ -256,9 +267,11 @@ func TestGGUFMetaVersionsEtOrdre(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
v1 := newGGUF()
|
||||
v1.version = 1
|
||||
v2 := newGGUF()
|
||||
v2.version = 2
|
||||
be := newGGUF()
|
||||
be.bo = binary.BigEndian
|
||||
for name, b := range map[string]*ggufBuilder{"v1.gguf": v1, "be.gguf": be} {
|
||||
for name, b := range map[string]*ggufBuilder{"v1.gguf": v1, "v2.gguf": v2, "be.gguf": be} {
|
||||
got, err := ggufMeta(writeGGUF(t, dir, name, qwenMTP(b).bytes()))
|
||||
if err != nil {
|
||||
t.Fatalf("%s : %v", name, err)
|
||||
@@ -345,6 +358,29 @@ func TestGGUFMetaFichiersAberrants(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Octet corrompu n'importe où dans l'en-tête (v1 et v3) : le lecteur doit rendre
|
||||
// la main vite, sans panic remonté ni allocation démesurée. Les valeurs 0xFF et
|
||||
// 0x7F transforment un compteur en nombre géant, le cas qui ferait mal.
|
||||
func TestGGUFMetaOctetCorrompu(t *testing.T) {
|
||||
v1 := newGGUF()
|
||||
v1.version = 1
|
||||
for _, b := range []*ggufBuilder{newGGUF(), v1} {
|
||||
full := qwenMTP(b).bytes()
|
||||
hdr := len(full) - 3*32 // le bourrage et les « poids » ne sont jamais lus
|
||||
for i := 0; i < hdr; i++ {
|
||||
for _, v := range []byte{0xFF, 0x7F, 0x00} {
|
||||
mut := append([]byte(nil), full...)
|
||||
mut[i] = v
|
||||
// le filet de sécurité existe, mais aucune entrée ne doit y tomber
|
||||
if _, err := parseGGUF(bytes.NewReader(mut), int64(len(mut))); err != nil &&
|
||||
strings.Contains(err.Error(), "illisible") {
|
||||
t.Fatalf("v%d, octet %d = %#x : %v", b.version, i, v, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Le cache tient tant que taille et date sont les mêmes, et se renouvelle dès
|
||||
// qu'elles changent (téléchargement terminé, fichier remplacé).
|
||||
func TestGGUFMetaCache(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user