SCReAM: Scale l4s_alpha update with steps_per_rtt and set g_down to 1/16 Previously, ScreamV2::UpdateL4SAlpha updated l4s_alpha on every feedback packet using fixed EWMA gain factors (l4s_avg_g_up = 1/8, l4s_avg_g_down = 1/128) regardless of the RTT. Because feedback arrives approximately every reaction_interval (25 ms) while ECN-CE marking and window adjustments occur over an RTT, updating l4s_alpha with unscaled gains caused l4s_alpha to react multiple times per RTT on longer-RTT paths, and the slow decay factor (1/128) kept l4s_alpha elevated well above the actual CE marking fraction between marking events. This change: 1. Updates the default value of l4s_avg_g_down from 1/128 to 1/16 so l4s_alpha decays faster when CE marking subsides. 2. Scales both l4s_avg_g_up and l4s_avg_g_down by steps_per_rtt = max(1.0, rtt / reaction_interval) in UpdateL4SAlpha (gated behind default-enabled field trial parameter ScaleL4sAlphaWithRtt). Scaling the EWMA gain factors by steps_per_rtt ensures that l4s_alpha converges over max(reaction_interval, rtt) rather than per feedback packet, better matching TCP Prague which updates alpha once per RTT (with a minimum interval of 25 ms) using g = 1/16. Bug: webrtc:447037083 Change-Id: Ie9c81e26a4932117c3d5a673d1f9c962e9533bb5 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/507841 Commit-Queue: Per Kjellander <perkj@webrtc.org> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org> Cr-Commit-Position: refs/heads/main@{#48824}
diff --git a/modules/congestion_controller/scream/scream_v2.cc b/modules/congestion_controller/scream/scream_v2.cc index ec23cbb..138efdd 100644 --- a/modules/congestion_controller/scream/scream_v2.cc +++ b/modules/congestion_controller/scream/scream_v2.cc
@@ -112,21 +112,33 @@ } } +TimeDelta ScreamV2::ReactionInterval() const { + return params_.use_feedback_interval_for_virtual_rtt.Get() + ? std::max(params_.virtual_rtt.Get(), feedback_interval_) + : params_.virtual_rtt.Get(); +} + void ScreamV2::UpdateL4SAlpha(const ScreamFeedback& parsed) { // 4.2.1.3. if (parsed.num_received_packets == 0) { return; } + double steps_per_rtt = + params_.scale_l4s_alpha_with_rtt.Get() + ? std::max(1.0, + delay_based_congestion_control_.rtt() / ReactionInterval()) + : 1.0; double fraction_marked = static_cast<double>(parsed.num_ce_marked_packets) / parsed.num_received_packets; // Fast attack slow decay EWMA filter. if (fraction_marked > l4s_alpha_) { - l4s_alpha_ = std::min(params_.l4s_avg_g_up.Get() * fraction_marked + - (1.0 - params_.l4s_avg_g_up.Get()) * l4s_alpha_, - 1.0); + double g_up = params_.l4s_avg_g_up.Get() / steps_per_rtt; + l4s_alpha_ = + std::min(g_up * fraction_marked + (1.0 - g_up) * l4s_alpha_, 1.0); } else { - l4s_alpha_ = (1.0 - params_.l4s_avg_g_down.Get()) * l4s_alpha_; + double g_down = params_.l4s_avg_g_down.Get() / steps_per_rtt; + l4s_alpha_ = (1.0 - g_down) * l4s_alpha_; } } void ScreamV2::UpdateRefWindow(const ScreamFeedback& parsed) { @@ -144,10 +156,7 @@ TimeDelta time_since_last_reaction = parsed.feedback_time - last_reaction_to_congestion_time_; - TimeDelta reaction_interval = - params_.use_feedback_interval_for_virtual_rtt.Get() - ? std::max(params_.virtual_rtt.Get(), feedback_interval_) - : params_.virtual_rtt.Get(); + TimeDelta reaction_interval = ReactionInterval(); if ((is_virtual_ce || is_ce || is_loss) && time_since_last_reaction >=
diff --git a/modules/congestion_controller/scream/scream_v2.h b/modules/congestion_controller/scream/scream_v2.h index 87c2f37..353ddeb 100644 --- a/modules/congestion_controller/scream/scream_v2.h +++ b/modules/congestion_controller/scream/scream_v2.h
@@ -135,6 +135,7 @@ void UpdateFeedbackInterval(Timestamp feedback_time); void UpdateTargetRate(const ScreamFeedback& parsed); void UpdateReceiveRate(const ScreamFeedback& parsed); + TimeDelta ReactionInterval() const; const Environment env_; const ScreamV2Parameters params_;
diff --git a/modules/congestion_controller/scream/scream_v2_parameters.cc b/modules/congestion_controller/scream/scream_v2_parameters.cc index 8e1e4df..5887978 100644 --- a/modules/congestion_controller/scream/scream_v2_parameters.cc +++ b/modules/congestion_controller/scream/scream_v2_parameters.cc
@@ -25,7 +25,8 @@ ScreamV2Parameters::ScreamV2Parameters(const FieldTrialsView* trials) : min_ref_window("MinRefWindow", DataSize::Bytes(1000)), l4s_avg_g_up("L4sAvgGUp", 1.0 / 8.0), - l4s_avg_g_down("L4sAvgGDown", 1.0 / 128.0), + l4s_avg_g_down("L4sAvgGDown", 1.0 / 16.0), + scale_l4s_alpha_with_rtt("ScaleL4sAlphaWithRtt", true), rtts_with_loss_before_backoff("RttsWithLossBeforeBackoff", 3), lossless_rtts_before_clear("LosslessRttsBeforeClear", 2), smoothed_rtt_avg_g("SmoothedRttAvgG", 1.0 / 8.0), @@ -90,6 +91,7 @@ {&min_ref_window, &l4s_avg_g_up, &l4s_avg_g_down, + &scale_l4s_alpha_with_rtt, &rtts_with_loss_before_backoff, &lossless_rtts_before_clear,
diff --git a/modules/congestion_controller/scream/scream_v2_parameters.h b/modules/congestion_controller/scream/scream_v2_parameters.h index a919c67..3b6c590 100644 --- a/modules/congestion_controller/scream/scream_v2_parameters.h +++ b/modules/congestion_controller/scream/scream_v2_parameters.h
@@ -29,6 +29,9 @@ // Exponentially Weighted Moving Average (EWMA) factor for l4s_alpha. FieldTrialParameter<double> l4s_avg_g_up; FieldTrialParameter<double> l4s_avg_g_down; + // If true, scales `l4s_avg_g_up` and `l4s_avg_g_down` by `steps_per_rtt` so + // `l4s_alpha` is updated relative to `max(reaction_interval, rtt)`. + FieldTrialParameter<bool> scale_l4s_alpha_with_rtt; // The number of consecutive RTTs with loss required to trigger a backoff. // Used as the step-up value (1.0 / rtts_with_loss_before_backoff) in the
diff --git a/modules/congestion_controller/scream/scream_v2_unittest.cc b/modules/congestion_controller/scream/scream_v2_unittest.cc index 184cf18..573334b 100644 --- a/modules/congestion_controller/scream/scream_v2_unittest.cc +++ b/modules/congestion_controller/scream/scream_v2_unittest.cc
@@ -10,6 +10,7 @@ #include "modules/congestion_controller/scream/scream_v2.h" #include <algorithm> +#include <cmath> #include "api/environment/environment.h" #include "api/transport/ecn_marking.h" @@ -235,18 +236,16 @@ Timestamp start_time = clock.CurrentTime(); TimeDelta feedback_interval = TimeDelta::Millis(25); - TransportPacketsFeedback feedback = - CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(10), - /*number_of_ect1_packets=*/20, - /*number_of_packets_in_flight=*/20); - // CE mark 20% of packets. - for (int i = 0; i < 4; ++i) { - feedback.packet_feedbacks[i].ecn = EcnMarking::kCe; - } - double l4s_alpha = scream.l4s_alpha(); while (clock.CurrentTime() < start_time + TimeDelta::Seconds(2)) { - feedback.feedback_time = clock.CurrentTime(); + TransportPacketsFeedback feedback = + CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(10), + /*number_of_ect1_packets=*/20, + /*number_of_packets_in_flight=*/20); + // CE mark 20% of packets. + for (int i = 0; i < 4; ++i) { + feedback.packet_feedbacks[i].ecn = EcnMarking::kCe; + } scream.OnTransportPacketsFeedback(feedback); EXPECT_GT(scream.l4s_alpha(), l4s_alpha); clock.AdvanceTime(feedback_interval); @@ -255,6 +254,42 @@ EXPECT_NEAR(scream.l4s_alpha(), 0.2, 0.01); } +TEST(ScreamV2Test, L4sAlphaScalesUpAndDownWithRtt) { + SimulatedClock clock(Timestamp::Seconds(1'234)); + Environment env = CreateTestEnvironment({.time = &clock}); + ScreamV2 scream(env); + + TimeDelta rtt = TimeDelta::Millis(100); + TimeDelta feedback_interval = TimeDelta::Millis(25); + + // First feedback with 100% CE marks at RTT = 100ms (steps_per_rtt = 4). + // With L4sAvgGUp = 1/8 and steps_per_rtt = 4, g_up = (1/8) / 4 = 1/32. + TransportPacketsFeedback ce_feedback = + CreateFeedback(clock.CurrentTime(), rtt, + /*number_of_ect1_packets=*/20, + /*number_of_packets_in_flight=*/20); + for (auto& packet : ce_feedback.packet_feedbacks) { + packet.ecn = EcnMarking::kCe; + } + scream.OnTransportPacketsFeedback(ce_feedback); + EXPECT_DOUBLE_EQ(scream.l4s_alpha(), (1.0 / 8.0) / 4.0); + + // Next 4 feedbacks (1 RTT) with 0% CE marks. + // With L4sAvgGDown = 1/16 and steps_per_rtt = 4, g_down = (1/16) / 4 = 1/64. + double alpha_before_decay = scream.l4s_alpha(); + for (int i = 0; i < 4; ++i) { + clock.AdvanceTime(feedback_interval); + TransportPacketsFeedback clear_feedback = + CreateFeedback(clock.CurrentTime(), rtt, + /*number_of_ect1_packets=*/20, + /*number_of_packets_in_flight=*/20); + scream.OnTransportPacketsFeedback(clear_feedback); + } + double expected_decay_factor = std::pow(1.0 - (1.0 / 16.0) / 4.0, 4); + EXPECT_NEAR(scream.l4s_alpha(), alpha_before_decay * expected_decay_factor, + 1e-6); +} + struct AdaptsToLinkCapacityParams { SimulatedNetwork::Config network_config; bool send_as_ect1 = true;