From 83f2c1a8edd2f79af85e597a8789a0697f25b0bc Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 12 Dec 2023 22:11:23 +0200 Subject: [PATCH] #1524 Add FPS limiter to streamed rendering --- public/index.html | 13 +++++++++++++ public/script.js | 8 +++++++- public/scripts/power-user.js | 12 +++++++++++- public/scripts/utils.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index 7eeccf4e0..348a47fa3 100644 --- a/public/index.html +++ b/public/index.html @@ -2890,6 +2890,19 @@ +
+
+ Streaming FPS +
+
+
+ +
+
+ +
+
+
diff --git a/public/script.js b/public/script.js index bc29aff53..fdd5a76fd 100644 --- a/public/script.js +++ b/public/script.js @@ -143,6 +143,7 @@ import { onlyUnique, getBase64Async, humanFileSize, + Stopwatch, } from './scripts/utils.js'; import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, renderExtensionTemplate, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; @@ -2805,7 +2806,10 @@ class StreamingProcessor { } try { + const sw = new Stopwatch(1000 / power_user.streaming_fps); + const timestamps = []; for await (const { text, swipes } of this.generator()) { + timestamps.push(Date.now()); if (this.isStopped) { this.onStopStreaming(); return; @@ -2813,8 +2817,10 @@ class StreamingProcessor { this.result = text; this.swipes = swipes; - this.onProgressStreaming(this.messageId, message_already_generated + text); + await sw.tick(() => this.onProgressStreaming(this.messageId, message_already_generated + text)); } + const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000; + console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`); } catch (err) { console.error(err); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 9689fb212..2983047d9 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -114,6 +114,7 @@ let power_user = { }, markdown_escape_strings: '', chat_truncation: 100, + streaming_fps: 30, ui_mode: ui_mode.POWER, fast_ui_mode: true, @@ -1460,6 +1461,9 @@ function loadPowerUserSettings(settings, data) { $('#chat_truncation').val(power_user.chat_truncation); $('#chat_truncation_counter').val(power_user.chat_truncation); + $('#streaming_fps').val(power_user.streaming_fps); + $('#streaming_fps_counter').val(power_user.streaming_fps); + $('#font_scale').val(power_user.font_scale); $('#font_scale_counter').val(power_user.font_scale); @@ -2701,6 +2705,12 @@ $(document).ready(() => { saveSettingsDebounced(); }); + $('#streaming_fps').on('input', function () { + power_user.streaming_fps = Number($('#streaming_fps').val()); + $('#streaming_fps_counter').val(power_user.streaming_fps); + saveSettingsDebounced(); + }); + $('input[name="font_scale"]').on('input', async function (e) { power_user.font_scale = Number(e.target.value); $('#font_scale_counter').val(power_user.font_scale); @@ -3134,7 +3144,7 @@ $(document).ready(() => { saveSettingsDebounced(); }); - $('#reduced_motion').on('input', function() { + $('#reduced_motion').on('input', function () { power_user.reduced_motion = !!$(this).prop('checked'); localStorage.setItem(storage_keys.reduced_motion, String(power_user.reduced_motion)); switchReducedMotion(); diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 6ea0508d3..dfaba74ce 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -741,6 +741,38 @@ export function escapeRegex(string) { return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); } +export class Stopwatch { + /** + * Initializes a Stopwatch class. + * @param {number} interval Update interval in milliseconds. Must be a finite number above zero. + */ + constructor(interval) { + if (isNaN(interval) || !isFinite(interval) || interval <= 0) { + console.warn('Invalid interval for Stopwatch, setting to 1'); + interval = 1; + } + + this.interval = interval; + this.lastAction = Date.now(); + } + + /** + * Executes a function if the interval passed. + * @param {(arg0: any) => any} action Action function + * @returns Promise + */ + async tick(action) { + const passed = (Date.now() - this.lastAction); + + if (passed < this.interval) { + return; + } + + await action(); + this.lastAction = Date.now(); + } +} + /** * Provides an interface for rate limiting function calls. */