From 5bf824588537e405f560f64d110484603e1a2fe3 Mon Sep 17 00:00:00 2001 From: n00mkrad Date: Mon, 25 Jul 2022 10:37:44 +0200 Subject: [PATCH] RIFE-NCNN-VS: Limit GPU thread count to max available (compute queue count) --- Code/Flowframes.csproj | 1 + Code/Form1.cs | 1 + Code/Media/AvProcess.cs | 11 +-- Code/Os/AiProcess.cs | 132 ++++++++++-------------------------- Code/Os/OsUtils.cs | 13 ++++ Code/Ui/MainUiFunctions.cs | 2 + Code/Utilities/NcnnUtils.cs | 120 ++++++++++++++++++++++++++++++++ 7 files changed, 172 insertions(+), 108 deletions(-) create mode 100644 Code/Utilities/NcnnUtils.cs diff --git a/Code/Flowframes.csproj b/Code/Flowframes.csproj index 4e1d2c9..95dc338 100644 --- a/Code/Flowframes.csproj +++ b/Code/Flowframes.csproj @@ -467,6 +467,7 @@ + Form1.cs Designer diff --git a/Code/Form1.cs b/Code/Form1.cs index daf2372..4137e92 100644 --- a/Code/Form1.cs +++ b/Code/Form1.cs @@ -18,6 +18,7 @@ using System.Threading.Tasks; using System.Linq; using System.Runtime.InteropServices; using Flowframes.Media; +using Flowframes.Utilities; #pragma warning disable IDE1006 diff --git a/Code/Media/AvProcess.cs b/Code/Media/AvProcess.cs index 99632c3..5da2ba3 100644 --- a/Code/Media/AvProcess.cs +++ b/Code/Media/AvProcess.cs @@ -129,7 +129,7 @@ namespace Flowframes Logger.Log($"ffprobe -v {settings.LogLevel} {settings.Args}", true, false, "ffmpeg"); if (!asyncOutput) - return await Task.Run(() => GetProcOutput(ffprobe)); + return await Task.Run(() => OsUtils.GetProcStdOut(ffprobe)); if (!show) { @@ -153,15 +153,6 @@ namespace Flowframes return processOutput; } - private static string GetProcOutput (Process proc) - { - proc.Start(); - proc.PriorityClass = ProcessPriorityClass.BelowNormal; - string output = proc.StandardOutput.ReadToEnd(); - proc.WaitForExit(); - return output; - } - public static string GetFfprobeOutput(string args) { Process ffprobe = OsUtils.NewProcess(true); diff --git a/Code/Os/AiProcess.cs b/Code/Os/AiProcess.cs index 9014e10..814d5b8 100644 --- a/Code/Os/AiProcess.cs +++ b/Code/Os/AiProcess.cs @@ -13,6 +13,7 @@ using ImageMagick; using Paths = Flowframes.IO.Paths; using Flowframes.Media; using System.Drawing; +using Flowframes.Utilities; namespace Flowframes.Os { @@ -286,7 +287,7 @@ namespace Flowframes.Os //await RunRifeNcnnMulti(framesPath, outPath, factor, mdl); await RunRifeNcnnProcess(framesPath, factor, outPath, mdl); - await DeleteNcnnDupes(outPath, factor); + await NcnnUtils.DeleteNcnnDupes(outPath, factor); } catch (Exception e) { @@ -311,7 +312,7 @@ namespace Flowframes.Os string ttaStr = Config.GetBool(Config.Key.rifeNcnnUseTta, false) ? "-x" : ""; rifeNcnn.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnn.PkgDir).Wrap()} & rife-ncnn-vulkan.exe " + - $" -v -i {inPath.Wrap()} -o {outPath.Wrap()} {frames} -m {mdl.ToLower()} {ttaStr} {uhdStr} -g {Config.Get(Config.Key.ncnnGpus)} -f {GetNcnnPattern()} -j {GetNcnnThreads()}"; + $" -v -i {inPath.Wrap()} -o {outPath.Wrap()} {frames} -m {mdl.ToLower()} {ttaStr} {uhdStr} -g {Config.Get(Config.Key.ncnnGpus)} -f {NcnnUtils.GetNcnnPattern()} -j {NcnnUtils.GetNcnnThreads()}"; Logger.Log("cmd.exe " + rifeNcnn.StartInfo.Arguments, true); @@ -357,7 +358,31 @@ namespace Flowframes.Os IoUtils.CreateDir(outPath); Process rifeNcnnVs = OsUtils.NewProcess(!OsUtils.ShowHiddenCmd()); - Logger.Log($"Note: RIFE-NCNN-VS is experimental and may not work as expected with specific Flowframes features, such as FPS limiting and image sequence exporting."); + Logger.Log($"Note: RIFE-NCNN-VS is experimental and may not work as expected with certain Flowframes features, such as image sequence exporting."); + + string avDir = Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir); + string rtArgs = $"-window_title \"Flowframes Realtime Interpolation ({Interpolate.currentSettings.inFps.GetString()} FPS x{factor} = {Interpolate.currentSettings.outFps.GetString()} FPS - {mdl})\" -autoexit -seek_interval {VapourSynthUtils.GetSeekSeconds(Program.mainForm.currInDuration)} "; + + string pipedTargetArgs = rt ? $"{Path.Combine(avDir, "ffplay").Wrap()} {rtArgs} - " : $"{Path.Combine(avDir, "ffmpeg").Wrap()} -y -i pipe: {await Export.GetPipedFfmpegCmd()}"; + + string pkgDir = Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnnVs.PkgDir); + int gpuId = Config.Get(Config.Key.ncnnGpus).Split(',')[0].GetInt(); + + VapourSynthUtils.VsSettings vsSettings = new VapourSynthUtils.VsSettings() + { + InterpSettings = Interpolate.currentSettings, + ModelDir = mdl, + Factor = factor, + Res = res, + Uhd = InterpolateUtils.UseUhd(res), + GpuId = gpuId, + GpuThreads = await NcnnUtils.GetRifeNcnnGpuThreads(res, gpuId, Implementations.rifeNcnnVs), + SceneDetectSensitivity = Config.GetBool(Config.Key.scnDetect) ? Config.GetFloat(Config.Key.scnDetectValue) * 0.7f : 0f, + Loop = Config.GetBool(Config.Key.enableLoop), + MatchDuration = Config.GetBool(Config.Key.fixOutputDuration), + Dedupe = Config.GetInt(Config.Key.dedupMode) != 0, + Realtime = rt + }; if (rt) { @@ -367,32 +392,9 @@ namespace Flowframes.Os else { SetProgressCheck(Interpolate.currentMediaFile.FrameCount, factor, Implementations.rifeNcnnVs.LogFilename); - AiStarted(rifeNcnnVs, 1500, inPath); + AiStarted(rifeNcnnVs, 1000, inPath); } - string avDir = Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir); - string rtArgs = $"-window_title \"Flowframes Realtime Interpolation ({Interpolate.currentSettings.inFps.GetString()} FPS x{factor} = {Interpolate.currentSettings.outFps.GetString()} FPS - {mdl})\" -autoexit -seek_interval {VapourSynthUtils.GetSeekSeconds(Program.mainForm.currInDuration)} "; - - string pipedTargetArgs = rt ? $"{Path.Combine(avDir, "ffplay").Wrap()} {rtArgs} - " : $"{Path.Combine(avDir, "ffmpeg").Wrap()} -y -i pipe: {await Export.GetPipedFfmpegCmd()}"; - - string pkgDir = Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnnVs.PkgDir); - - VapourSynthUtils.VsSettings vsSettings = new VapourSynthUtils.VsSettings() - { - InterpSettings = Interpolate.currentSettings, - ModelDir = mdl, - Factor = factor, - Res = res, - Uhd = InterpolateUtils.UseUhd(res), - GpuId = Config.Get(Config.Key.ncnnGpus).Split(',')[0].GetInt(), - GpuThreads = GetRifeNcnnVsGpuThreads(res), - SceneDetectSensitivity = Config.GetBool(Config.Key.scnDetect) ? Config.GetFloat(Config.Key.scnDetectValue) * 0.7f : 0f, - Loop = Config.GetBool(Config.Key.enableLoop), - MatchDuration = Config.GetBool(Config.Key.fixOutputDuration), - Dedupe = Config.GetInt(Config.Key.dedupMode) != 0, - Realtime = rt - }; - rifeNcnnVs.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {pkgDir.Wrap()} & vspipe {VapourSynthUtils.CreateScript(vsSettings).Wrap()} -c y4m - | {pipedTargetArgs}"; Logger.Log("cmd.exe " + rifeNcnnVs.StartInfo.Arguments, true); @@ -422,7 +424,7 @@ namespace Flowframes.Os try { await RunDainNcnnProcess(framesPath, outPath, factor, mdl, tilesize); - await DeleteNcnnDupes(outPath, factor); + await NcnnUtils.DeleteNcnnDupes(outPath, factor); } catch (Exception e) { @@ -443,7 +445,7 @@ namespace Flowframes.Os int targetFrames = ((IoUtils.GetAmountOfFiles(lastInPath, false, "*.*") * factor).RoundToInt()); string args = $" -v -i {framesPath.Wrap()} -o {outPath.Wrap()} -n {targetFrames} -m {mdl.ToLower()}" + - $" -t {GetNcnnTilesize(tilesize)} -g {Config.Get(Config.Key.ncnnGpus)} -f {GetNcnnPattern()} -j 2:1:2"; + $" -t {NcnnUtils.GetNcnnTilesize(tilesize)} -g {Config.Get(Config.Key.ncnnGpus)} -f {NcnnUtils.GetNcnnPattern()} -j 2:1:2"; dain.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {dainDir.Wrap()} & dain-ncnn-vulkan.exe {args}"; Logger.Log("Running DAIN...", false); @@ -538,7 +540,7 @@ namespace Flowframes.Os Logger.Log($"Running IFRNet (NCNN){(await InterpolateUtils.UseUhd() ? " (UHD Mode)" : "")}...", false); await RunIfrnetNcnnProcess(framesPath, factor, outPath, mdl); - await DeleteNcnnDupes(outPath, factor); + await NcnnUtils.DeleteNcnnDupes(outPath, factor); } catch (Exception e) { @@ -561,7 +563,7 @@ namespace Flowframes.Os string ttaStr = ""; // Config.GetBool(Config.Key.rifeNcnnUseTta, false) ? "-x" : ""; ifrnetNcnn.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {Path.Combine(Paths.GetPkgPath(), Implementations.ifrnetNcnn.PkgDir).Wrap()} & ifrnet-ncnn-vulkan.exe " + - $" -v -i {inPath.Wrap()} -o {outPath.Wrap()} -m {mdl} {ttaStr} {uhdStr} -g {Config.Get(Config.Key.ncnnGpus)} -f {GetNcnnPattern()} -j {GetNcnnThreads()}"; + $" -v -i {inPath.Wrap()} -o {outPath.Wrap()} -m {mdl} {ttaStr} {uhdStr} -g {Config.Get(Config.Key.ncnnGpus)} -f {NcnnUtils.GetNcnnPattern()} -j {NcnnUtils.GetNcnnThreads()}"; Logger.Log("cmd.exe " + ifrnetNcnn.StartInfo.Arguments, true); @@ -700,72 +702,6 @@ namespace Flowframes.Os InterpolationProgress.UpdateLastFrameFromInterpOutput(line); } - static int GetRifeNcnnVsGpuThreads (Size res) - { - int threads = 3; - if(res.Width * res.Height > 2560 * 1440) threads = 2; - if(res.Width * res.Height > 3840 * 2160) threads = 1; - return threads; - } - - static string GetNcnnPattern() - { - return $"%0{Padding.interpFrames}d{Interpolate.currentSettings.interpExt}"; - } - - static string GetNcnnTilesize(int tilesize) - { - int gpusAmount = Config.Get(Config.Key.ncnnGpus).Split(',').Length; - string tilesizeStr = $"{tilesize}"; - - for (int i = 1; i < gpusAmount; i++) - tilesizeStr += $",{tilesize}"; - - return tilesizeStr; - } - - static string GetNcnnThreads(bool forceSingleThread = false) - { - int gpusAmount = Config.Get(Config.Key.ncnnGpus).Split(',').Length; - int procThreads = Config.GetInt(Config.Key.ncnnThreads); - string progThreadsStr = $"{procThreads}"; - - for (int i = 1; i < gpusAmount; i++) - progThreadsStr += $",{procThreads}"; - - return $"{(forceSingleThread ? 1 : (Interpolate.currentlyUsingAutoEnc ? 2 : 4))}:{progThreadsStr}:4"; // Read threads: 1 for singlethreaded, 2 for autoenc, 4 if order is irrelevant - } - - static async Task DeleteNcnnDupes(string dir, float factor) - { - int dupeCount = InterpolateUtils.GetRoundedInterpFramesPerInputFrame(factor); - var files = IoUtils.GetFileInfosSorted(dir, false).Reverse().Take(dupeCount).ToList(); - Logger.Log($"DeleteNcnnDupes: Calculated dupe count from factor; deleting last {dupeCount} interp frames of {IoUtils.GetAmountOfFiles(dir, false)} ({string.Join(", ", files.Select(x => x.Name))})", true); - - int attempts = 4; - - while (attempts > 0) - { - try - { - files.ForEach(x => x.Delete()); - break; - } - catch (Exception ex) - { - attempts--; - - if (attempts < 1) - { - Logger.Log($"DeleteNcnnDupes Error: {ex.Message}", true); - break; - } - else - { - await Task.Delay(500); - } - } - } - } + } } diff --git a/Code/Os/OsUtils.cs b/Code/Os/OsUtils.cs index d703e85..6d7f7bd 100644 --- a/Code/Os/OsUtils.cs +++ b/Code/Os/OsUtils.cs @@ -13,11 +13,24 @@ using Microsoft.VisualBasic.Devices; using Flowframes.MiscUtils; using System.Linq; using Tulpep.NotificationWindow; +using System.Threading; namespace Flowframes.Os { class OsUtils { + public static string GetProcStdOut(Process proc, bool includeStdErr = false, ProcessPriorityClass priority = ProcessPriorityClass.BelowNormal) + { + if (includeStdErr) + proc.StartInfo.Arguments += " 2>&1"; + + proc.Start(); + proc.PriorityClass = priority; + string output = proc.StandardOutput.ReadToEnd(); + proc.WaitForExit(); + return output; + } + public static bool IsUserAdministrator() { //bool value to hold our return value diff --git a/Code/Ui/MainUiFunctions.cs b/Code/Ui/MainUiFunctions.cs index 8030284..d06bbe8 100644 --- a/Code/Ui/MainUiFunctions.cs +++ b/Code/Ui/MainUiFunctions.cs @@ -97,6 +97,7 @@ namespace Flowframes.Ui { if (Interpolate.currentSettings == null || !Interpolate.currentSettings.stepByStep) return; string tmpFolder = InterpolateUtils.GetTempFolderLoc(inpath, outpath); + if (Directory.Exists(tmpFolder)) { int scnFrmAmount = IoUtils.GetAmountOfFiles(Path.Combine(tmpFolder, Paths.scenesDir), false, "*" + Interpolate.currentSettings.interpExt); // TODO: Make this work if the frames extension was changed @@ -108,6 +109,7 @@ namespace Flowframes.Ui string msg = $"A temporary folder for this video already exists. It contains {scnFrames}, {srcFrames}, {interpFrames}."; DialogResult dialogResult = UiUtils.ShowMessageBox($"{msg}\n\nClick \"Yes\" to use the existing files or \"No\" to delete them.", "Use files from existing temp folder?", MessageBoxButtons.YesNo); + if (dialogResult == DialogResult.No) { IoUtils.TryDeleteIfExists(tmpFolder); diff --git a/Code/Utilities/NcnnUtils.cs b/Code/Utilities/NcnnUtils.cs new file mode 100644 index 0000000..2c76510 --- /dev/null +++ b/Code/Utilities/NcnnUtils.cs @@ -0,0 +1,120 @@ +using Flowframes.Data; +using Flowframes.IO; +using Flowframes.Main; +using Flowframes.Os; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flowframes.Utilities +{ + class NcnnUtils + { + /// Get amount of GPU Compute Queues (VK) for each GPU + public static async Task> GetNcnnGpuComputeQueueCounts () + { + Dictionary queueCounts = new Dictionary(); // int gpuId, int queueCount + + Process rifeNcnn = OsUtils.NewProcess(true); + rifeNcnn.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} {Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnn.PkgDir, "rife-ncnn-vulkan.exe")} -i C: -o C:"; + + string output = await Task.Run(() => OsUtils.GetProcStdOut(rifeNcnn, true)); + var queueLines = output.SplitIntoLines().Where(x => x.MatchesWildcard(@"*queueC=*queue*")); + + foreach (var line in queueLines) + { + int gpuId = line.Split(' ')[0].GetInt(); + int queueCount = line.Split("queue")[1].Split('[')[1].Split(']')[0].GetInt(); + Logger.Log($"NCNN: Found GPU {gpuId} with compute queue count {queueCount}", true); + queueCounts[gpuId] = queueCount; + } + + return queueCounts; + } + + public static async Task GetRifeNcnnGpuThreads(Size res, int gpuId, AI ai) + { + int threads = 3; + if (res.Width * res.Height > 2560 * 1440) threads = 2; + if (res.Width * res.Height > 3840 * 2160) threads = 1; + + if (threads != 1) + { + int maxThreads = (await GetNcnnGpuComputeQueueCounts())[gpuId]; + threads = threads.Clamp(1, maxThreads); // To avoid exceeding the max queue count + Logger.Log($"Using {threads}/{maxThreads} GPU threads.", true, false, ai.LogFilename); + } + else + { + Logger.Log($"Using {threads} GPU thread.", true, false, ai.LogFilename); + } + + return threads; + } + + public static string GetNcnnPattern() + { + return $"%0{Padding.interpFrames}d{Interpolate.currentSettings.interpExt}"; + } + + public static string GetNcnnTilesize(int tilesize) + { + int gpusAmount = Config.Get(Config.Key.ncnnGpus).Split(',').Length; + string tilesizeStr = $"{tilesize}"; + + for (int i = 1; i < gpusAmount; i++) + tilesizeStr += $",{tilesize}"; + + return tilesizeStr; + } + + public static string GetNcnnThreads(bool forceSingleThread = false) + { + int gpusAmount = Config.Get(Config.Key.ncnnGpus).Split(',').Length; + int procThreads = Config.GetInt(Config.Key.ncnnThreads); + string progThreadsStr = $"{procThreads}"; + + for (int i = 1; i < gpusAmount; i++) + progThreadsStr += $",{procThreads}"; + + return $"{(forceSingleThread ? 1 : (Interpolate.currentlyUsingAutoEnc ? 2 : 4))}:{progThreadsStr}:4"; // Read threads: 1 for singlethreaded, 2 for autoenc, 4 if order is irrelevant + } + + public static async Task DeleteNcnnDupes(string dir, float factor) + { + int dupeCount = InterpolateUtils.GetRoundedInterpFramesPerInputFrame(factor); + var files = IoUtils.GetFileInfosSorted(dir, false).Reverse().Take(dupeCount).ToList(); + Logger.Log($"DeleteNcnnDupes: Calculated dupe count from factor; deleting last {dupeCount} interp frames of {IoUtils.GetAmountOfFiles(dir, false)} ({string.Join(", ", files.Select(x => x.Name))})", true); + + int attempts = 4; + + while (attempts > 0) + { + try + { + files.ForEach(x => x.Delete()); + break; + } + catch (Exception ex) + { + attempts--; + + if (attempts < 1) + { + Logger.Log($"DeleteNcnnDupes Error: {ex.Message}", true); + break; + } + else + { + await Task.Delay(500); + } + } + } + } + } +}