111 lines
3.5 KiB
PowerShell
111 lines
3.5 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string] $DatasetDir,
|
|
[Parameter(Mandatory = $true)]
|
|
[string] $OutputDir,
|
|
[Parameter(Mandatory = $true)]
|
|
[string] $PythonExe,
|
|
[string] $CacheDir,
|
|
[string] $CheckpointPath,
|
|
[int] $BatchSize = 4,
|
|
[int] $NumWorkers = 2,
|
|
[int] $MaxEpochs = 2000,
|
|
[switch] $SmokeTest
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$dataset = [IO.Path]::GetFullPath($DatasetDir)
|
|
$output = [IO.Path]::GetFullPath($OutputDir)
|
|
$python = [IO.Path]::GetFullPath($PythonExe)
|
|
$metadata = Join-Path $dataset 'metadata.csv'
|
|
$audioDir = Join-Path $dataset 'wav'
|
|
$configPath = Join-Path $output 'aletheia_ru.onnx.json'
|
|
$cacheDir = if ($CacheDir) {
|
|
[IO.Path]::GetFullPath($CacheDir)
|
|
} else {
|
|
Join-Path $output 'cache'
|
|
}
|
|
$checkpointDir = Join-Path $output 'checkpoints'
|
|
|
|
if (-not (Test-Path -LiteralPath $python -PathType Leaf)) {
|
|
throw "Python executable not found: $python"
|
|
}
|
|
if (-not (Test-Path -LiteralPath $metadata -PathType Leaf)) {
|
|
throw "Piper metadata not found: $metadata"
|
|
}
|
|
if (-not (Test-Path -LiteralPath $audioDir -PathType Container)) {
|
|
throw "Piper audio directory not found: $audioDir"
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $output, $cacheDir, $checkpointDir | Out-Null
|
|
|
|
$fitArgs = @(
|
|
'-m', 'piper.train', 'fit',
|
|
'--data.voice_name', 'aletheia_ru',
|
|
'--data.csv_path', $metadata,
|
|
'--data.audio_dir', $audioDir,
|
|
'--data.espeak_voice', 'ru',
|
|
'--data.cache_dir', $cacheDir,
|
|
'--data.config_path', $configPath,
|
|
'--data.batch_size', $BatchSize,
|
|
'--data.num_workers', $NumWorkers,
|
|
'--model.sample_rate', '22050',
|
|
'--trainer.accelerator', 'gpu',
|
|
'--trainer.devices', '1',
|
|
'--trainer.precision', '16-mixed',
|
|
'--trainer.max_epochs', $MaxEpochs,
|
|
'--trainer.default_root_dir', $checkpointDir
|
|
)
|
|
|
|
if ($SmokeTest) {
|
|
$fitArgs += @('--trainer.fast_dev_run', 'true', '--trainer.num_sanity_val_steps', '0')
|
|
}
|
|
if ($CheckpointPath) {
|
|
$checkpointToResume = [IO.Path]::GetFullPath($CheckpointPath)
|
|
if (-not (Test-Path -LiteralPath $checkpointToResume -PathType Leaf)) {
|
|
throw "Resume checkpoint not found: $checkpointToResume"
|
|
}
|
|
$fitArgs += @('--ckpt_path', $checkpointToResume)
|
|
}
|
|
|
|
& $python @fitArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Piper training exited with code $LASTEXITCODE"
|
|
}
|
|
|
|
if ($SmokeTest) {
|
|
Write-Output 'PIPER_CUDA_SMOKE_TEST_OK'
|
|
exit 0
|
|
}
|
|
|
|
$checkpoint = Get-ChildItem -LiteralPath $checkpointDir -Filter '*.ckpt' -File -Recurse |
|
|
Sort-Object LastWriteTimeUtc -Descending |
|
|
Select-Object -First 1
|
|
if (-not $checkpoint) {
|
|
throw "Training finished without a checkpoint under $checkpointDir"
|
|
}
|
|
|
|
$onnxPath = Join-Path $output 'aletheia_ru.onnx'
|
|
& $python -m piper.train.export_onnx --checkpoint $checkpoint.FullName --output-file $onnxPath
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Piper ONNX export exited with code $LASTEXITCODE"
|
|
}
|
|
|
|
$artifacts = [ordered]@{}
|
|
foreach ($artifactPath in @($onnxPath, $configPath)) {
|
|
if (-not (Test-Path -LiteralPath $artifactPath -PathType Leaf)) {
|
|
throw "Expected artifact not found: $artifactPath"
|
|
}
|
|
$item = Get-Item -LiteralPath $artifactPath
|
|
$artifacts[$item.Name] = [ordered]@{
|
|
bytes = $item.Length
|
|
sha256 = (Get-FileHash -LiteralPath $item.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
}
|
|
}
|
|
|
|
$manifestPath = Join-Path $output 'artifacts.json'
|
|
$artifacts | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $manifestPath -Encoding utf8
|
|
$artifacts | ConvertTo-Json -Depth 4
|