1use std::sync::LazyLock;
16
17use tracing::info;
18
19use crate::utils::ByteSize;
20use crate::utils::configuration_utils::parse_bool_value;
21
22const ROUND_TO: u64 = 8_000_000;
24
25#[derive(Debug, Clone, Copy)]
27pub struct UsableMemory {
28 pub host_total: Option<u64>,
30 pub cgroup_limit: Option<u64>,
32}
33
34impl UsableMemory {
35 pub fn usable(&self) -> Option<u64> {
38 match (self.host_total, self.cgroup_limit) {
39 (Some(host), Some(cgroup)) => Some(host.min(cgroup)),
40 (host, cgroup) => host.or(cgroup),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct DownloadBufferDefaults {
48 pub size: ByteSize,
49 pub perfile: ByteSize,
50 pub limit: ByteSize,
51}
52
53pub const STATIC_DEFAULTS: DownloadBufferDefaults = DownloadBufferDefaults {
57 size: ByteSize::new(2_000_000_000),
58 perfile: ByteSize::new(512_000_000),
59 limit: ByteSize::new(8_000_000_000),
60};
61
62pub const STATIC_HP_DEFAULTS: DownloadBufferDefaults = DownloadBufferDefaults {
65 size: ByteSize::new(16_000_000_000),
66 perfile: ByteSize::new(2_000_000_000),
67 limit: ByteSize::new(64_000_000_000),
68};
69
70struct Fractions {
72 size_divisor: u64,
73 perfile_divisor: u64,
74 limit_divisor: u64,
75}
76
77const STANDARD_FRACTIONS: Fractions = Fractions {
80 size_divisor: 16,
81 perfile_divisor: 64,
82 limit_divisor: 4,
83};
84
85const HIGH_PERFORMANCE_FRACTIONS: Fractions = Fractions {
87 size_divisor: 8,
88 perfile_divisor: 32,
89 limit_divisor: 2,
90};
91
92const SIZE_FLOOR: u64 = 64_000_000;
101const SIZE_CEILING: u64 = STATIC_HP_DEFAULTS.size.as_u64();
102const PERFILE_FLOOR: u64 = 16_000_000;
103const PERFILE_CEILING: u64 = STATIC_HP_DEFAULTS.perfile.as_u64();
104const LIMIT_FLOOR: u64 = 264_000_000;
105const LIMIT_CEILING: u64 = STATIC_HP_DEFAULTS.limit.as_u64();
106
107fn derive_download_buffer_defaults(usable: u64, fractions: &Fractions) -> DownloadBufferDefaults {
109 let apply = |divisor: u64, floor: u64, ceiling: u64| -> u64 {
110 let value = (usable / divisor).clamp(floor, ceiling);
111 (value / ROUND_TO) * ROUND_TO
112 };
113 DownloadBufferDefaults {
114 size: ByteSize::new(apply(fractions.size_divisor, SIZE_FLOOR, SIZE_CEILING)),
115 perfile: ByteSize::new(apply(fractions.perfile_divisor, PERFILE_FLOOR, PERFILE_CEILING)),
116 limit: ByteSize::new(apply(fractions.limit_divisor, LIMIT_FLOOR, LIMIT_CEILING)),
117 }
118}
119
120#[cfg(not(target_family = "wasm"))]
121fn probe_usable_memory() -> UsableMemory {
122 use sysinfo::{MemoryRefreshKind, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
123
124 let mut system =
125 System::new_with_specifics(RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()));
126 let host_total = match system.total_memory() {
127 0 => None,
128 total => Some(total),
129 };
130
131 let cgroup_limit = sysinfo::get_current_pid().ok().and_then(|pid| {
136 system.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing());
137 let limits = system.process(pid)?.cgroup_limits()?;
138 Some(limits.total_memory).filter(|&limit| host_total.is_none_or(|host| limit < host))
139 });
140
141 UsableMemory {
142 host_total,
143 cgroup_limit,
144 }
145}
146
147#[cfg(target_family = "wasm")]
148fn probe_usable_memory() -> UsableMemory {
149 UsableMemory {
150 host_total: None,
151 cgroup_limit: None,
152 }
153}
154
155pub static USABLE_MEMORY: LazyLock<UsableMemory> = LazyLock::new(probe_usable_memory);
161
162struct DerivedDefaults {
165 standard: DownloadBufferDefaults,
166 high_performance: DownloadBufferDefaults,
167}
168
169static DERIVED_DEFAULTS: LazyLock<DerivedDefaults> = LazyLock::new(|| match USABLE_MEMORY.usable() {
176 Some(usable) => DerivedDefaults {
177 standard: derive_download_buffer_defaults(usable, &STANDARD_FRACTIONS),
178 high_performance: derive_download_buffer_defaults(usable, &HIGH_PERFORMANCE_FRACTIONS),
179 },
180 None => DerivedDefaults {
181 standard: STATIC_DEFAULTS,
182 high_performance: STATIC_HP_DEFAULTS,
183 },
184});
185
186pub fn log_derived_defaults() {
191 if !memory_derived_defaults_enabled() {
192 info!(
193 "Memory-derived download buffer defaults disabled via HF_XET_DISABLE_MEMORY_DERIVED_DOWNLOAD_BUFFERS; using static defaults"
194 );
195 return;
196 }
197 let memory = &*USABLE_MEMORY;
198 match memory.usable() {
199 Some(usable) => {
200 let standard = DERIVED_DEFAULTS.standard;
201 info!(
202 host_total = ?memory.host_total,
203 cgroup_limit = ?memory.cgroup_limit,
204 usable,
205 "Derived download buffer defaults from system memory: size={} perfile={} limit={}",
206 standard.size,
207 standard.perfile,
208 standard.limit
209 );
210 },
211 None => {
212 info!("System memory could not be determined; using static download buffer defaults");
213 },
214 }
215}
216
217fn memory_derived_defaults_enabled() -> bool {
221 match std::env::var("HF_XET_DISABLE_MEMORY_DERIVED_DOWNLOAD_BUFFERS") {
222 Ok(value) => !parse_bool_value(&value).unwrap_or(false),
223 Err(_) => true,
224 }
225}
226
227pub fn default_download_buffer_sizes() -> DownloadBufferDefaults {
233 if memory_derived_defaults_enabled() {
234 DERIVED_DEFAULTS.standard
235 } else {
236 STATIC_DEFAULTS
237 }
238}
239
240pub fn high_performance_download_buffer_sizes() -> DownloadBufferDefaults {
245 if memory_derived_defaults_enabled() {
246 DERIVED_DEFAULTS.high_performance
247 } else {
248 STATIC_HP_DEFAULTS
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 fn triple(d: DownloadBufferDefaults) -> (u64, u64, u64) {
257 (d.size.as_u64(), d.perfile.as_u64(), d.limit.as_u64())
258 }
259
260 #[test]
261 fn test_usable_memory_min() {
262 let mem = |h, c| UsableMemory {
263 host_total: h,
264 cgroup_limit: c,
265 };
266 assert_eq!(mem(Some(10), Some(4)).usable(), Some(4));
267 assert_eq!(mem(Some(4), Some(10)).usable(), Some(4));
268 assert_eq!(mem(Some(10), None).usable(), Some(10));
269 assert_eq!(mem(None, Some(4)).usable(), Some(4));
270 assert_eq!(mem(None, None).usable(), None);
271 }
272
273 #[test]
274 fn test_standard_derivation_table() {
275 let cases: &[(u64, (u64, u64, u64))] = &[
277 (268_435_456, (64_000_000, 16_000_000, 264_000_000)), (512_000_000, (64_000_000, 16_000_000, 264_000_000)), (1_073_741_824, (64_000_000, 16_000_000, 264_000_000)), (2_000_000_000, (120_000_000, 24_000_000, 496_000_000)),
281 (8_000_000_000, (496_000_000, 120_000_000, 2_000_000_000)),
282 (16_000_000_000, (1_000_000_000, 248_000_000, 4_000_000_000)),
283 (32_000_000_000, (2_000_000_000, 496_000_000, 8_000_000_000)), (64_000_000_000, (4_000_000_000, 1_000_000_000, 16_000_000_000)),
285 (128_000_000_000, (8_000_000_000, 2_000_000_000, 32_000_000_000)),
286 (256_000_000_000, (16_000_000_000, 2_000_000_000, 64_000_000_000)), (2_000_000_000_000, (16_000_000_000, 2_000_000_000, 64_000_000_000)), ];
289 for &(usable, expected) in cases {
290 assert_eq!(
291 triple(derive_download_buffer_defaults(usable, &STANDARD_FRACTIONS)),
292 expected,
293 "standard, usable={usable}"
294 );
295 }
296 assert_eq!(derive_download_buffer_defaults(2_000_000_000_000, &STANDARD_FRACTIONS), STATIC_HP_DEFAULTS);
298 }
299
300 #[test]
301 fn test_high_performance_derivation_table() {
302 let cases: &[(u64, (u64, u64, u64))] = &[
303 (268_435_456, (64_000_000, 16_000_000, 264_000_000)), (512_000_000, (64_000_000, 16_000_000, 264_000_000)), (8_000_000_000, (1_000_000_000, 248_000_000, 4_000_000_000)),
306 (16_000_000_000, (2_000_000_000, 496_000_000, 8_000_000_000)),
307 (32_000_000_000, (4_000_000_000, 1_000_000_000, 16_000_000_000)),
308 (64_000_000_000, (8_000_000_000, 2_000_000_000, 32_000_000_000)),
309 (128_000_000_000, (16_000_000_000, 2_000_000_000, 64_000_000_000)),
310 (256_000_000_000, (16_000_000_000, 2_000_000_000, 64_000_000_000)),
311 (2_000_000_000_000, (16_000_000_000, 2_000_000_000, 64_000_000_000)),
312 ];
313 for &(usable, expected) in cases {
314 assert_eq!(
315 triple(derive_download_buffer_defaults(usable, &HIGH_PERFORMANCE_FRACTIONS)),
316 expected,
317 "hp, usable={usable}"
318 );
319 }
320 }
321
322 #[test]
323 fn test_derivation_invariants_across_sweep() {
324 for fractions in [&STANDARD_FRACTIONS, &HIGH_PERFORMANCE_FRACTIONS] {
327 let mut prev: Option<(u64, u64, u64)> = None;
328 let mut usable: u64 = 268_435_456;
329 while usable < 2_500_000_000_000 {
330 let (size, perfile, limit) = triple(derive_download_buffer_defaults(usable, fractions));
331
332 assert!(
334 size + 8 * perfile <= limit,
335 "invariant violated at usable={usable}: {size} + 8*{perfile} > {limit}"
336 );
337
338 for v in [size, perfile, limit] {
340 assert_eq!(v % ROUND_TO, 0, "not a multiple of {ROUND_TO} at usable={usable}");
341 }
342
343 assert!((SIZE_FLOOR..=SIZE_CEILING).contains(&size));
345 assert!((PERFILE_FLOOR..=PERFILE_CEILING).contains(&perfile));
346 assert!((LIMIT_FLOOR..=LIMIT_CEILING).contains(&limit));
347
348 if let Some((ps, pp, pl)) = prev {
350 assert!(size >= ps && perfile >= pp && limit >= pl, "not monotonic at usable={usable}");
351 }
352 prev = Some((size, perfile, limit));
353
354 usable = usable * 5 / 4;
355 }
356 }
357 }
358
359 #[test]
362 fn test_probe_reports_host_total() {
363 assert!(USABLE_MEMORY.host_total.is_some_and(|t| t > 0), "host total should be detectable on test hosts");
364 }
365
366 #[test]
367 #[serial_test::serial(config_env)]
368 fn test_kill_switch_restores_static_defaults() {
369 let _g = crate::utils::EnvVarGuard::set("HF_XET_DISABLE_MEMORY_DERIVED_DOWNLOAD_BUFFERS", "1");
370 assert_eq!(default_download_buffer_sizes(), STATIC_DEFAULTS);
371 assert_eq!(high_performance_download_buffer_sizes(), STATIC_HP_DEFAULTS);
372 }
373
374 #[test]
375 #[serial_test::serial(config_env)]
376 fn test_derived_defaults_match_probe() {
377 let _g = crate::utils::EnvVarGuard::set("HF_XET_DISABLE_MEMORY_DERIVED_DOWNLOAD_BUFFERS", "0");
378 let expected_std = match USABLE_MEMORY.usable() {
379 Some(u) => derive_download_buffer_defaults(u, &STANDARD_FRACTIONS),
380 None => STATIC_DEFAULTS,
381 };
382 let expected_hp = match USABLE_MEMORY.usable() {
383 Some(u) => derive_download_buffer_defaults(u, &HIGH_PERFORMANCE_FRACTIONS),
384 None => STATIC_HP_DEFAULTS,
385 };
386 assert_eq!(default_download_buffer_sizes(), expected_std);
387 assert_eq!(high_performance_download_buffer_sizes(), expected_hp);
388 }
389}