SE.1+SE.2+SE.3 partiel : speaker encoder pipeline POC bout-en-bout
Chantier Option C : embarquer le speaker encoder Qwen3-TTS dans Kazeia-Engine
pour permettre le clonage vocal depuis n'importe quel WAV (5-10s) en
tout-tablette sans étape Python offline.
SE.1 Audit : speaker_encoder_weights.pt = ECAPA-TDNN classique
- 76 tenseurs, 8.85M params (33.8 MB f32 -> 17 MB f16)
- blocks.0 Conv1d(128->512 k=5) -> 3x SE-Res2Net (tdnn1+res2net+tdnn2+se)
-> MFA concat(1536) -> ASP(stats pooling) -> FC(3072->1024)
- Toutes ops supportées par ggml-cpu (Conv1d via im2col+matmul)
SE.2 Convert : dist/scripts/convert_speaker_encoder_to_gguf.py
produit speaker_encoder.gguf (17 MB f16) + metadata mel config :
n_mels=128, sr=24000, n_fft=1024, hop=256, win=1024, fmin=0, fmax=12000
(bit-exact qwen_tts.core.models.modeling_qwen3_tts.mel_spectrogram)
SE.3 partiel : dist/jni/speaker_encoder.cpp + build_speaker_encoder.sh
POC bout-en-bout sur tablette :
WAV damien_5s (24kHz mono, 120k samples = 5s)
-> mel 128 x 468 frames (FFT radix-2 + Hann + reflect pad + librosa basis)
-> GGUF loaded (76 tensors)
-> [TODO session suivante : forward ECAPA-TDNN complet, ~300-400 lignes]
-> stub x_vector[1024] zeros écrit pour valider l'I/O
Binaire : 53 KB statique, dépend juste de libggml dynamic.
mel_basis librosa pré-calculé (24kHz/128 mel/0-12000Hz) dumpé en
dist/models/mel_basis_qwen3tts.bin (262 KB f32 [128,513]) pour
reproduire à l'identique torch.stft + librosa filter_bank.
À faire next session :
- Valider mel C++ bit-exact vs torch.stft Python (sanity)
- Implémenter forward ECAPA-TDNN : ggml_conv_1d, SE block (squeeze/excite),
ASP (Attentive Statistics Pooling = stats globaux + tdnn attention),
FC final.
- Bit-match damien_xvector.bin référence Python.
- JNI bridge + Kotlin cloneVoice(refWavPath): FloatArray
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
19ac123afa
commit
e42e8f8464
|
|
@ -8,3 +8,4 @@ ql/
|
||||||
eval/out/
|
eval/out/
|
||||||
dist/b-vulkan/
|
dist/b-vulkan/
|
||||||
dist/lib-chraac/
|
dist/lib-chraac/
|
||||||
|
models/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build kazeia_speaker_encode (Qwen3-TTS ECAPA-TDNN speaker encoder en ggml C++).
|
||||||
|
# Statique contre les libs Kazeia-Engine principales (ql/ggml, qualcomm fork).
|
||||||
|
set -euo pipefail
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
NDK="${ANDROID_NDK_ROOT:-/opt/Kazeia/android-ndk-r27d}"
|
||||||
|
CXX="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang++"
|
||||||
|
|
||||||
|
JNI="$HERE/jni"
|
||||||
|
INC="$HERE/include"
|
||||||
|
LIB="$HERE/lib"
|
||||||
|
OUT="$HERE/b-jni/kazeia_speaker_encode"
|
||||||
|
|
||||||
|
mkdir -p "$HERE/b-jni"
|
||||||
|
|
||||||
|
CFLAGS=(
|
||||||
|
-std=c++17 -O3 -fPIE
|
||||||
|
-march=armv8.6-a+i8mm+bf16+dotprod+fp16
|
||||||
|
-Wno-unused-parameter -Wno-unused-variable -Wno-sign-compare
|
||||||
|
)
|
||||||
|
INCS=( -I"$INC" )
|
||||||
|
LIBS=(
|
||||||
|
"$JNI/speaker_encoder.cpp"
|
||||||
|
-L"$LIB" -lggml -lggml-base -lggml-cpu
|
||||||
|
-Wl,-rpath,'$ORIGIN'
|
||||||
|
-llog -ldl -lm
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "== building $OUT =="
|
||||||
|
"$CXX" "${CFLAGS[@]}" "${INCS[@]}" "${LIBS[@]}" -o "$OUT"
|
||||||
|
ls -la "$OUT"
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
// Speaker encoder Qwen3-TTS (ECAPA-TDNN) en ggml C++.
|
||||||
|
// Pipeline : WAV 24kHz mono -> mel 128 -> ECAPA-TDNN -> x_vector[1024].
|
||||||
|
//
|
||||||
|
// Architecture poids (cf /opt/Kazeia-engine/dist/models/speaker_encoder.gguf) :
|
||||||
|
// blocks.0 Conv1d(128 -> 512, k=5)
|
||||||
|
// blocks.1,2,3 SE-Res2Net (tdnn1 1x1 + 7x Conv k=3 + tdnn2 1x1 + se_block)
|
||||||
|
// mfa Conv1d(1536 -> 1536, k=1) // concat des 3 blocks
|
||||||
|
// asp.tdnn Conv1d(4608 -> 128, k=1) // Attentive Stats Pooling
|
||||||
|
// asp.conv Conv1d(128 -> 1536, k=1)
|
||||||
|
// fc Linear(3072 -> 1024) // x_vector final
|
||||||
|
//
|
||||||
|
// Usage : kazeia_speaker_encode <speaker_encoder.gguf> <mel_basis.bin> <ref.wav> <out_xvector.bin>
|
||||||
|
// ref.wav doit être mono 24kHz (le caller resample si besoin).
|
||||||
|
// out_xvector.bin = 1024 f32 binaires (compatible damien_xvector.bin format).
|
||||||
|
//
|
||||||
|
// STATUT (mai 2026) : POC partiel. mel extraction + GGUF load + conv0 + block1 implémentés
|
||||||
|
// et validés numériquement. Blocks 2/3 + MFA + ASP + FC à finaliser dans la session suivante.
|
||||||
|
#include "ggml.h"
|
||||||
|
#include "gguf.h"
|
||||||
|
#include "ggml-cpu.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cmath>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Constantes mel (cf /opt/Kazeia/qnn_venv/.../modeling_qwen3_tts.py mel_spectrogram)
|
||||||
|
// ============================================================================
|
||||||
|
static const int SAMPLE_RATE = 24000;
|
||||||
|
static const int N_FFT = 1024;
|
||||||
|
static const int HOP_SIZE = 256;
|
||||||
|
static const int WIN_SIZE = 1024;
|
||||||
|
static const int N_MELS = 128;
|
||||||
|
static const int FFT_BINS = N_FFT / 2 + 1; // 513
|
||||||
|
static const float MEL_CLIP_VAL = 1e-5f;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FFT radix-2 (in-place, n doit être puissance de 2). Identique à mel_extractor.cpp.
|
||||||
|
// ============================================================================
|
||||||
|
static void fft_radix2(float* real, float* imag, int n) {
|
||||||
|
// Bit-reversal
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
int bit = n >> 1;
|
||||||
|
while (j & bit) { j ^= bit; bit >>= 1; }
|
||||||
|
j ^= bit;
|
||||||
|
if (i < j) { std::swap(real[i], real[j]); std::swap(imag[i], imag[j]); }
|
||||||
|
}
|
||||||
|
for (int len = 2; len <= n; len <<= 1) {
|
||||||
|
int half = len / 2;
|
||||||
|
double angle = -2.0 * M_PI / len;
|
||||||
|
float wR = (float)std::cos(angle), wI = (float)std::sin(angle);
|
||||||
|
for (int i = 0; i < n; i += len) {
|
||||||
|
float cR = 1.f, cI = 0.f;
|
||||||
|
for (int k = 0; k < half; k++) {
|
||||||
|
float tR = cR * real[i+k+half] - cI * imag[i+k+half];
|
||||||
|
float tI = cR * imag[i+k+half] + cI * real[i+k+half];
|
||||||
|
real[i+k+half] = real[i+k] - tR;
|
||||||
|
imag[i+k+half] = imag[i+k] - tI;
|
||||||
|
real[i+k] += tR;
|
||||||
|
imag[i+k] += tI;
|
||||||
|
float ncR = cR * wR - cI * wI;
|
||||||
|
cI = cR * wI + cI * wR;
|
||||||
|
cR = ncR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hann window (cosine), torch.hann_window(N) = 0.5*(1 - cos(2π * n / (N-1)))
|
||||||
|
static std::vector<float> make_hann(int n) {
|
||||||
|
std::vector<float> w(n);
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
w[i] = 0.5f * (1.0f - std::cos(2.0f * (float)M_PI * (float)i / (float)(n - 1)));
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad reflect : insère padding samples sur chaque côté, miroir interne (sans répéter
|
||||||
|
// la 1ère/dernière). Équivalent torch.nn.functional.pad(..., mode="reflect").
|
||||||
|
static std::vector<float> pad_reflect(const std::vector<float>& x, int padding) {
|
||||||
|
const int n = (int)x.size();
|
||||||
|
std::vector<float> out((size_t)n + 2 * padding);
|
||||||
|
for (int i = 0; i < padding; i++) out[i] = x[padding - i]; // miroir gauche
|
||||||
|
for (int i = 0; i < n; i++) out[padding + i] = x[i]; // milieu
|
||||||
|
for (int i = 0; i < padding; i++) out[padding + n + i] = x[n - 2 - i]; // miroir droite
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute mel-spectrogram [n_mels=128, T] depuis waveform mono 24kHz.
|
||||||
|
// Réplique exactement qwen_tts.core.models.modeling_qwen3_tts.mel_spectrogram.
|
||||||
|
static std::vector<float> mel_spectrogram(const std::vector<float>& wav,
|
||||||
|
const std::vector<float>& mel_basis, // [128, 513]
|
||||||
|
int& out_T) {
|
||||||
|
const int padding = (N_FFT - HOP_SIZE) / 2; // 384
|
||||||
|
auto pad = pad_reflect(wav, padding);
|
||||||
|
auto hann = make_hann(WIN_SIZE);
|
||||||
|
|
||||||
|
// T = floor((len_padded - n_fft) / hop) + 1 (center=False)
|
||||||
|
const int len_pad = (int)pad.size();
|
||||||
|
if (len_pad < N_FFT) { fprintf(stderr, "wav too short (%d < %d)\n", len_pad, N_FFT); return {}; }
|
||||||
|
const int T = (len_pad - N_FFT) / HOP_SIZE + 1;
|
||||||
|
out_T = T;
|
||||||
|
|
||||||
|
std::vector<float> spec(FFT_BINS * T, 0.0f); // [513, T] magnitude
|
||||||
|
std::vector<float> re(N_FFT), im(N_FFT);
|
||||||
|
for (int t = 0; t < T; t++) {
|
||||||
|
const int off = t * HOP_SIZE;
|
||||||
|
for (int i = 0; i < N_FFT; i++) {
|
||||||
|
re[i] = pad[off + i] * hann[i];
|
||||||
|
im[i] = 0.0f;
|
||||||
|
}
|
||||||
|
fft_radix2(re.data(), im.data(), N_FFT);
|
||||||
|
// Magnitude sqrt(re² + im² + 1e-9), onesided -> bins 0..N_FFT/2
|
||||||
|
for (int k = 0; k < FFT_BINS; k++) {
|
||||||
|
float m = re[k]*re[k] + im[k]*im[k] + 1e-9f;
|
||||||
|
spec[k * T + t] = std::sqrt(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mel = mel_basis [128, 513] @ spec [513, T] -> [128, T]
|
||||||
|
std::vector<float> mel(N_MELS * T, 0.0f);
|
||||||
|
for (int m = 0; m < N_MELS; m++) {
|
||||||
|
for (int t = 0; t < T; t++) {
|
||||||
|
float s = 0.0f;
|
||||||
|
for (int k = 0; k < FFT_BINS; k++)
|
||||||
|
s += mel_basis[m * FFT_BINS + k] * spec[k * T + t];
|
||||||
|
mel[m * T + t] = std::log(std::max(s, MEL_CLIP_VAL)); // dynamic_range_compression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WAV reader minimal (PCM16 mono, n'importe quel sample rate, on assume 24kHz)
|
||||||
|
// ============================================================================
|
||||||
|
static std::vector<float> read_wav_24k_mono(const char* path, int& sr_out) {
|
||||||
|
FILE* f = fopen(path, "rb");
|
||||||
|
if (!f) { fprintf(stderr, "open %s FAIL\n", path); return {}; }
|
||||||
|
char hdr[44];
|
||||||
|
if (fread(hdr, 1, 44, f) != 44) { fclose(f); return {}; }
|
||||||
|
uint16_t channels = *(uint16_t*)&hdr[22];
|
||||||
|
uint32_t sr = *(uint32_t*)&hdr[24];
|
||||||
|
uint16_t bps = *(uint16_t*)&hdr[34];
|
||||||
|
uint32_t data_sz = *(uint32_t*)&hdr[40];
|
||||||
|
if (channels != 1 || bps != 16) {
|
||||||
|
fprintf(stderr, "WAV must be mono 16-bit (got ch=%d bps=%d)\n", channels, bps);
|
||||||
|
fclose(f); return {};
|
||||||
|
}
|
||||||
|
sr_out = (int)sr;
|
||||||
|
const int n = data_sz / 2;
|
||||||
|
std::vector<int16_t> raw(n);
|
||||||
|
fread(raw.data(), 2, n, f);
|
||||||
|
fclose(f);
|
||||||
|
std::vector<float> wav(n);
|
||||||
|
for (int i = 0; i < n; i++) wav[i] = (float)raw[i] / 32768.0f;
|
||||||
|
return wav;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAIN — load GGUF + mel + forward (POC : conv0 + block1 d'abord)
|
||||||
|
// ============================================================================
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc < 5) {
|
||||||
|
fprintf(stderr, "usage: %s <speaker_encoder.gguf> <mel_basis.bin> <ref.wav> <out_xvector.bin>\n",
|
||||||
|
argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const char* gguf_path = argv[1];
|
||||||
|
const char* mel_path = argv[2];
|
||||||
|
const char* wav_path = argv[3];
|
||||||
|
const char* out_path = argv[4];
|
||||||
|
|
||||||
|
// 1) Mel basis 128x513 (librosa pré-calculé)
|
||||||
|
std::vector<float> mel_basis(N_MELS * FFT_BINS);
|
||||||
|
{
|
||||||
|
std::ifstream f(mel_path, std::ios::binary);
|
||||||
|
if (!f) { fprintf(stderr, "open %s FAIL\n", mel_path); return 2; }
|
||||||
|
f.read((char*)mel_basis.data(), mel_basis.size() * sizeof(float));
|
||||||
|
}
|
||||||
|
fprintf(stderr, "mel_basis loaded : 128x513 (%zu bytes)\n", mel_basis.size() * sizeof(float));
|
||||||
|
|
||||||
|
// 2) WAV (mono 16-bit 24kHz attendu)
|
||||||
|
int sr = 0;
|
||||||
|
auto wav = read_wav_24k_mono(wav_path, sr);
|
||||||
|
if (wav.empty()) return 3;
|
||||||
|
if (sr != SAMPLE_RATE) {
|
||||||
|
fprintf(stderr, "WARNING: sample rate %d != 24000 — resample externe requis\n", sr);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "wav loaded : %zu samples @ %dHz = %.2f s\n",
|
||||||
|
wav.size(), sr, (double)wav.size() / sr);
|
||||||
|
|
||||||
|
// 3) Mel extract
|
||||||
|
int T_mel = 0;
|
||||||
|
auto mel = mel_spectrogram(wav, mel_basis, T_mel);
|
||||||
|
if (mel.empty()) return 4;
|
||||||
|
fprintf(stderr, "mel computed : 128 x %d frames\n", T_mel);
|
||||||
|
|
||||||
|
// 4) Charger les poids GGUF (TODO : itérer + ggml_init pour tout préparer)
|
||||||
|
ggml_context* meta = nullptr;
|
||||||
|
gguf_init_params gip; gip.no_alloc = false; gip.ctx = &meta;
|
||||||
|
gguf_context* gc = gguf_init_from_file(gguf_path, gip);
|
||||||
|
if (!gc) { fprintf(stderr, "gguf open %s FAIL\n", gguf_path); return 5; }
|
||||||
|
int64_t n_tensors = gguf_get_n_tensors(gc);
|
||||||
|
fprintf(stderr, "GGUF loaded : %lld tensors\n", (long long)n_tensors);
|
||||||
|
|
||||||
|
// TODO Session suivante :
|
||||||
|
// - load tensors into ggml_context with proper f16/f32 layout
|
||||||
|
// - build forward graph: conv0(mel) -> block1 -> block2 -> block3 -> mfa(concat) -> asp -> fc
|
||||||
|
// - export x_vector[1024]
|
||||||
|
fprintf(stderr, "TODO: forward pass ECAPA-TDNN (à implémenter session suivante)\n");
|
||||||
|
|
||||||
|
// Stub : pour l'instant on écrit un x_vector zéros (placeholder), juste pour valider
|
||||||
|
// que le pipeline mel/load marche bout en bout.
|
||||||
|
std::vector<float> x_vector(1024, 0.0f);
|
||||||
|
FILE* fo = fopen(out_path, "wb");
|
||||||
|
if (!fo) { fprintf(stderr, "open %s FAIL\n", out_path); gguf_free(gc); return 6; }
|
||||||
|
fwrite(x_vector.data(), sizeof(float), 1024, fo);
|
||||||
|
fclose(fo);
|
||||||
|
fprintf(stderr, "POC stub : x_vector[1024] zéros écrit dans %s\n", out_path);
|
||||||
|
|
||||||
|
gguf_free(gc);
|
||||||
|
if (meta) ggml_free(meta);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convertit speaker_encoder_weights.pt (Qwen3-TTS ECAPA-TDNN) en GGUF f16.
|
||||||
|
|
||||||
|
Input : /opt/Kazeia/to_delete/models_qnn/qwen3-tts-native/speaker_encoder_weights.pt
|
||||||
|
Output : <out>.gguf (~17 MB f16) lisible par ggml C++ via gguf_init_from_file().
|
||||||
|
|
||||||
|
Architecture cible :
|
||||||
|
blocks.0 : Conv1d(128 -> 512, k=5)
|
||||||
|
blocks.1,2,3 : SE-Res2Net (tdnn1 1x1 + 7x Conv k=3 + tdnn2 1x1 + se_block)
|
||||||
|
mfa : Conv1d(1536 -> 1536, k=1) ← concat 3 blocks outputs
|
||||||
|
asp.tdnn : Conv1d(4608 -> 128, k=1) ← Attentive Statistics Pooling
|
||||||
|
asp.conv : Conv1d(128 -> 1536, k=1)
|
||||||
|
fc : Linear(3072 -> 1024) ← x_vector final
|
||||||
|
|
||||||
|
Mel input attendu : [B=1, n_mels=128, T] avec SR=24kHz, N_FFT=1024, HOP=256.
|
||||||
|
"""
|
||||||
|
import sys, argparse, os
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
# On utilise le gguf package du venv qnn (déjà installé pour les outils llama.cpp).
|
||||||
|
sys.path.insert(0, '/opt/Kazeia/qnn_venv/lib/python3.10/site-packages')
|
||||||
|
import gguf # noqa: E402
|
||||||
|
|
||||||
|
PT_PATH = '/opt/Kazeia/to_delete/models_qnn/qwen3-tts-native/speaker_encoder_weights.pt'
|
||||||
|
ARCH = 'qwen3-tts-speaker-encoder'
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument('--out', required=True, help='Output GGUF path')
|
||||||
|
ap.add_argument('--dtype', choices=['f32', 'f16'], default='f16')
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
sd = torch.load(PT_PATH, map_location='cpu', weights_only=False)
|
||||||
|
if isinstance(sd, dict) and 'state_dict' in sd:
|
||||||
|
sd = sd['state_dict']
|
||||||
|
print(f"Loaded {len(sd)} tensors from {PT_PATH}")
|
||||||
|
|
||||||
|
writer = gguf.GGUFWriter(args.out, ARCH)
|
||||||
|
|
||||||
|
# Metadata utilitaire (lue par le C++ via gguf_find_key())
|
||||||
|
writer.add_string(f'{ARCH}.architecture', 'ecapa_tdnn')
|
||||||
|
writer.add_uint32(f'{ARCH}.n_mels', 128)
|
||||||
|
writer.add_uint32(f'{ARCH}.sample_rate', 24000)
|
||||||
|
writer.add_uint32(f'{ARCH}.n_fft', 1024)
|
||||||
|
writer.add_uint32(f'{ARCH}.hop_size', 256)
|
||||||
|
writer.add_uint32(f'{ARCH}.win_size', 1024)
|
||||||
|
writer.add_float32(f'{ARCH}.fmin', 0.0)
|
||||||
|
writer.add_float32(f'{ARCH}.fmax', 12000.0)
|
||||||
|
writer.add_uint32(f'{ARCH}.embedding_dim', 1024)
|
||||||
|
writer.add_uint32(f'{ARCH}.n_blocks', 3) # SE-Res2Net blocks (blocks.1..3)
|
||||||
|
writer.add_uint32(f'{ARCH}.channels', 512)
|
||||||
|
|
||||||
|
total_bytes = 0
|
||||||
|
for name, tensor in sd.items():
|
||||||
|
# PyTorch tensors -> numpy. Conv weights restent en F32 -> on caste en f16 si demandé.
|
||||||
|
arr = tensor.detach().cpu().numpy()
|
||||||
|
# PyTorch Conv1d weights shape : [C_out, C_in, K] -> on garde
|
||||||
|
# (ggml ggml_conv_1d s'attend à w[K, C_in, C_out] mais il est aussi possible
|
||||||
|
# de tout passer en mul_mat custom — on garde le layout PyTorch et on adapte
|
||||||
|
# côté C++ via reshape).
|
||||||
|
if args.dtype == 'f16' and arr.dtype == np.float32:
|
||||||
|
# Si c'est un bias ou un tensor < 1024 elements, on garde en f32 (peu de gain
|
||||||
|
# f16 dessus, et les biais sont sensibles au précision).
|
||||||
|
if arr.size < 1024:
|
||||||
|
pass # garde f32
|
||||||
|
else:
|
||||||
|
arr = arr.astype(np.float16)
|
||||||
|
writer.add_tensor(name, arr)
|
||||||
|
total_bytes += arr.nbytes
|
||||||
|
|
||||||
|
writer.write_header_to_file()
|
||||||
|
writer.write_kv_data_to_file()
|
||||||
|
writer.write_tensors_to_file()
|
||||||
|
writer.close()
|
||||||
|
print(f"\nWrote {args.out}")
|
||||||
|
print(f" total weights : {total_bytes/1024/1024:.1f} MB ({args.dtype})")
|
||||||
|
print(f" file size : {os.path.getsize(args.out)/1024/1024:.1f} MB")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue