1use std::collections::HashMap;
21
22#[cfg(not(any(target_os = "linux", target_os = "android")))]
23mod imp {
24 pub fn raise_memlock_limit() -> Option<u64> {
28 None
29 }
30 pub fn lock_slice(_b: &[u8]) -> std::io::Result<()> {
31 Err(std::io::Error::new(
32 std::io::ErrorKind::Unsupported,
33 "закрепление памяти поддержано только на Linux",
34 ))
35 }
36 pub fn phys_mem() -> Option<(u64, u64)> {
37 None
38 }
39}
40
41#[cfg(any(target_os = "linux", target_os = "android"))]
42mod imp {
43 pub fn raise_memlock_limit() -> Option<u64> {
46 unsafe {
47 let mut rl = libc::rlimit {
48 rlim_cur: 0,
49 rlim_max: 0,
50 };
51 if libc::getrlimit(libc::RLIMIT_MEMLOCK, &mut rl) != 0 {
52 return None;
53 }
54 for want in [
59 libc::rlimit {
60 rlim_cur: libc::RLIM_INFINITY,
61 rlim_max: libc::RLIM_INFINITY,
62 },
63 libc::rlimit {
64 rlim_cur: rl.rlim_max,
65 rlim_max: rl.rlim_max,
66 },
67 ] {
68 if libc::setrlimit(libc::RLIMIT_MEMLOCK, &want) == 0 {
69 rl.rlim_cur = want.rlim_cur;
70 break;
71 }
72 }
73 Some(rl.rlim_cur as u64)
74 }
75 }
76
77 pub fn phys_mem() -> Option<(u64, u64)> {
79 let s = std::fs::read_to_string("/proc/meminfo").ok()?;
80 let get = |k: &str| -> Option<u64> {
81 s.lines()
82 .find(|l| l.starts_with(k))?
83 .split_whitespace()
84 .nth(1)?
85 .parse::<u64>()
86 .ok()
87 .map(|kb| kb * 1024)
88 };
89 Some((get("MemTotal:")?, get("MemAvailable:")?))
90 }
91
92 pub fn lock_slice(b: &[u8]) -> std::io::Result<()> {
95 if b.is_empty() {
96 return Ok(());
97 }
98 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
99 let start = b.as_ptr() as usize;
100 let aligned = start & !(page - 1);
101 let len = (start - aligned) + b.len();
102 let rc = unsafe { libc::mlock(aligned as *const libc::c_void, len) };
103 if rc == 0 {
104 Ok(())
105 } else {
106 Err(std::io::Error::last_os_error())
107 }
108 }
109}
110
111pub use imp::raise_memlock_limit;
112use imp::{lock_slice, phys_mem};
113
114pub fn hot_experts(stats_path: &str, cover: f64) -> Option<HashMap<usize, Vec<usize>>> {
116 let text = std::fs::read_to_string(stats_path)
117 .map_err(|e| tracing::warn!("CMF_MOE_PIN: cannot read {stats_path}: {e}"))
118 .ok()?;
119 let map: HashMap<String, Vec<u64>> = serde_json::from_str(&text)
120 .map_err(|e| tracing::warn!("CMF_MOE_PIN: bad JSON in {stats_path}: {e}"))
121 .ok()?;
122 let mut out = HashMap::new();
123 for (k, counts) in map {
124 let Ok(li) = k.parse::<usize>() else { continue };
125 let total: u64 = counts.iter().sum();
126 if total == 0 {
127 continue;
128 }
129 let mut order: Vec<usize> = (0..counts.len()).collect();
130 order.sort_by_key(|&i| std::cmp::Reverse(counts[i]));
131 let mut acc = 0u64;
132 let mut keep = Vec::new();
133 for i in order {
134 if counts[i] == 0 {
135 break;
136 }
137 acc += counts[i];
138 keep.push(i);
139 if acc as f64 >= cover * total as f64 {
140 break;
141 }
142 }
143 out.insert(li, keep);
144 }
145 Some(out)
146}
147
148pub struct Pinned {
150 pub bytes: u64,
151 pub tensors: usize,
152 pub skipped: usize,
153 pub limit: Option<u64>,
154}
155
156pub fn pin_tensors(model: &cortiq_core::CmfModel, names: &[String]) -> Pinned {
159 let limit = raise_memlock_limit();
160 let mut want: u64 = 0;
161 for n in names {
162 if let Ok(b) = model.tensor_bytes(n) {
163 want += b.len() as u64;
164 }
165 }
166 if let Some((total, avail)) = phys_mem() {
167 if want > avail {
168 tracing::warn!(
169 "закрепление {:.1} ГБ при доступных {:.1} ГБ из {:.1} — \
170 закрепляю столько, сколько поместится, остальное останется \
171 подкачиваемым",
172 want as f64 / 1e9,
173 avail as f64 / 1e9,
174 total as f64 / 1e9
175 );
176 }
177 }
178 let mut out = Pinned {
179 bytes: 0,
180 tensors: 0,
181 skipped: 0,
182 limit,
183 };
184 let mut fails = 0usize;
185 for n in names {
186 let Ok(b) = model.tensor_bytes(n) else {
187 out.skipped += 1;
188 continue;
189 };
190 match lock_slice(b) {
191 Ok(()) => {
192 out.bytes += b.len() as u64;
193 out.tensors += 1;
194 }
195 Err(e) => {
196 out.skipped += 1;
200 fails += 1;
201 if fails == 1 {
202 tracing::warn!("первое закрепление не удалось ({n}): {e}");
203 }
204 if fails >= 64 && out.bytes < 1 << 30 {
205 tracing::warn!(
206 "закрепление упирается в RLIMIT_MEMLOCK = {:.0} МБ и поднять \
207 его не дали (контейнеры обычно снимают CAP_SYS_RESOURCE). \
208 Лечится вне процесса: `--ulimit memlock=-1` у докера, \
209 LimitMEMLOCK=infinity в systemd, или запуск с этой \
210 привилегией. Пропущено {} тензоров.",
211 limit.unwrap_or(0) as f64 / 1e6,
212 names.len() - out.skipped
213 );
214 break;
215 }
216 }
217 }
218 }
219 out
220}