KF 冷启动调校记:gap-fill、max 与 steady_mode
问题:KF 的下沉与冷启动的饥饿
全局 Kalman BDP 估计kf_x的值会在真实竞争中下降——这本身是好事。它就是来做这个的:跟踪公平份额、反馈多流压力。坏的是,它下降之后回不来——至少在下一个连接建立以前回不来。
连接结束,kf_x 停在 150M。下一条连接建立的第一秒,从 1M 开始爬。
甜品启动的代码走了:init_bw = kf_x × 0.5 / 2.885。150M → 0.173 → 26M 等价的初始注入。STARTUP 乘 2.885 回到 ~75M。对于一个 600M 的链路,首秒 75M 是可以的。但 kf_x=150M 本身已经不够——因为这是被拉低之后的残留,不是链路能力。
根因不在 discount 链。是 kf_x 从没高到过它应该有的水平。
思路 1:上峰值地板——gap-fill
想法:记一个单调增长的kf_x_steady,每次 KF 更新时只升不降。初始注入时,不用 kf_x,而用 “kf_x 加上一部分 gap”:
ratio = kf_x / kf_x_steady fair = kf_x + gap × ratio = kf_x + (peak - kf_x) × (kf_x / peak) = kf_x × (2 - kf_x / peak)设计意图:gap 大(信号过时)→ 填多。gap 小(跟踪准)→ 填少。自适应,无参数。
实测效果:和没填差不多。
为什么?看一下 kf_x 极小的情况:
kf_x = 50, peak = 500 gap = 450, ratio = 0.1 fill = 450 × 0.1 = 45 fair = 95填了 95。不填的时候 kf_x=50,填了之后 95。翻了一倍。但没有本质上改变局面——50 和 95 在 600M 的链路上都是噪声。
公式里的 self-cancellation:gap × ratio中 ratio 和 kf_x 成正比——kf_x 小的时候,ratio 也小。乘法把自己吃掉了。
gaf-fill 在数学上优美,在冷启动场景上废在同一个性质:kf_x 越小,填得越少。而需要填的恰是 kf_x 极小的时候。
思路 2:直接取大——max
回到最简单的逻辑:如果峰值得到了保存,就取峰值。
fair = kf_x; if (kf_x_steady > fair) fair = kf_x_steady;没有乘法、没有 ratio、没有自适应。就一句话:峰值还在,就用峰值。
| kf_x | peak | max | gap-fill |
|---|---|---|---|
| 500 | 500 | 500 | 500 |
| 400 | 500 | 500 | 480 |
| 150 | 500 | 500 | 255 |
| 50 | 500 | 500 | 95 |
| 0 | 500 | 500 | 0 |
gap-fill 在 kf_x→0 时退化为 0。max 不退。
本质区别:gap-fill 从 kf_x 出发,往 peak 走。kf_x 本身垮了,出发点就没了。max 在 kf_x 和 peak 之间选,不需要出发点。
但 max 也有上限:peak 本身。第一条连接只跑到 150M、peak 停在 150M——max 也突破不了 150M。所有工具的共同上限是信息量——没人见过 600M,就没有 600M 的峰值。
工具不创造信息
这两条路最后收缩到同一个框架:
- gap-fill:适合修正偏移。kf_x 有值、偏低了——填回峰值。
- max:适合启动供给。kf_x 是零/接近零——取峰值。
- 两者都无法超越 peak。peak 由历史流量决定——没有高吞吐历史,就没有高初始注入。
这引出了一个更基本的事实:以 kf_x 为基底的任何公式,最终都是 kf_x 的函数。max 绕过了 kf_x——但它依赖 peak,而 peak 也是从 kf_x 的历史最大值来的。本质上,所有估值最终都是过去的 KF 在定价——没有一个能凭空创造带宽信息。
steady_mode:按需开启的保护
kcc_kf_steady_mode就是这个选择的包装——默认关(0),管理员酌情开。
# 默认配置(动态跟踪) kcc_kf_enable=1 kcc_kf_steady_mode=0 # 已知链路稳定时开启 kcc_kf_steady_mode=1mode=0:纯动态跟踪。kf_x 下沉、新连接受冷;但绝对安全——没有峰值保护意味着没有峰值高估。
mode=1:利用历史峰值。kf_x_steady 只升不降,init_bw 取max(kf_x, peak)。第一条连接无保护(peak=0),后续所有连接有底。
两种模式解决两种不同的问题:
- mode=0 解决"真实竞争来了怎么办"——下沉动态反馈。
- mode=1 解决"竞争走了启动怎么办"——峰值当底。
模式切换时,kf_x_steady 自动清零——从不混用,没有误判。
峰值的双重保护
x_steady 被两道墙保护:
- 单调性——只升不降。每次 KF 更新
x > x_steady才动,噪声下不来。 - 模式清零——steady_mode 从 1 切到 0 时清零。管理员的显式动作,下次再开重新来。
所以不存在"历史高估污染未来":你关了再开,peak 从零开始。
选路,不评分
没人能回答"gap-fill 比 max 好"——就像没人能回答"刹车比油门好"。它们做不同的事。
gap-fill 的设计直觉是对的:信号强时多填、信号弱时少填。它在信号中间值(kf_x=60%~80% of peak)的表现优于 max——max 在这里是不填的,而 gap-fill 进了 12%~16% of peak 的额外份额。
但它有一个 collapse point(信号→0),在这个点上它变得比 max 更差——max 在这里直接取 peak。
所以最终的选择不是 “max 赢了 gap-fill”,而是"用 max 解决 gap-fill 解决不了的问题,然后回来看看 gap-fill 原来的问题是否还在"。结果是 gap-fill 原来的问题(信号偏低的修正)在实际链路中很少发生——kf_x 不会漂移,它要么好、要么坏,没有中间状态。极少的中间状态下,两者差别小到可以忽略。
Tags: TCP, BBR, KCC, Kalman filter, cold start, bandwidth estimation, design philosophy