logicaffeine_compile/codegen_cuda/lane/
residence.rs1use std::collections::{BTreeMap, BTreeSet};
15
16use super::arena;
17use super::plan::{quant_in_place, Fill, Launch, Touch, QBLOCK};
18use super::weights::{Placement, Rows};
19use super::{Form, FormKind, LaneError};
20use crate::codegen_cuda::qwen_emit::Q8MatKind;
21use crate::model::quant::GgmlType;
22
23pub const CONTEXT_RESERVE: usize = 512 << 20;
26
27pub const SPARE: usize = 32 << 20;
30
31pub const SPARE_A_LAUNCH: usize = 16 << 10;
34
35pub const POOL_ALIGN: usize = arena::ALIGN;
37
38pub fn pooled(n: usize) -> usize {
40 n.next_multiple_of(POOL_ALIGN)
41}
42
43#[derive(Clone, Debug, Default, PartialEq, Eq)]
45pub struct Plan {
46 pub arena: usize,
48 pub fixed: usize,
51 pub order: Vec<(String, usize)>,
53 pub shares: BTreeMap<String, String>,
55 pub spare: usize,
57}
58
59impl Plan {
60 pub fn whole(&self) -> usize {
62 self.arena + self.fixed + self.order.iter().map(|(_, b)| b).sum::<usize>()
63 }
64}
65
66#[derive(Clone, Debug, Default, PartialEq, Eq)]
68pub struct Residence {
69 pub host: BTreeSet<String>,
71 pub moved: usize,
73 pub device_bytes: usize,
75 pub host_bytes: usize,
77}
78
79pub fn shared_names(forms: &[Form]) -> BTreeSet<String> {
81 let decode = forms.iter().find(|f| f.rows == 1);
82 forms
83 .iter()
84 .flat_map(|f| f.schedule.buffers.values())
85 .filter(|b| match b.fill {
86 Fill::Constant(_) => true,
87 Fill::Zero => decode.is_some_and(|d| d.schedule.buffers.contains_key(&b.name)),
88 _ => false,
89 })
90 .map(|b| b.name.clone())
91 .collect()
92}
93
94pub fn matrix_bytes(rows: usize, cols: usize, ty: GgmlType) -> usize {
97 match ty {
98 GgmlType::Q8_0 => pooled(rows * cols) + pooled(rows * (cols / QBLOCK) * 2),
99 _ => pooled(rows * ty.bytes_for(cols) + 32),
100 }
101}
102
103fn upload(r: &Rows) -> (u32, usize, Vec<(u64, usize)>) {
105 (r.ty.id(), r.cols, r.runs.iter().map(|run| (run.offset, run.rows)).collect())
106}
107
108pub fn plan(forms: &[Form], ctx: usize) -> Plan {
111 let two = forms[0].schedule.numerics == Q8MatKind::TwoPassInt8;
112 let shared = shared_names(forms);
113 let mut out = Plan { arena: forms.iter().map(|f| f.arena.bytes).max().unwrap_or(0), ..Plan::default() };
114 out.fixed = pooled(4) + pooled(ctx * 4) + pooled(4) + pooled(ctx * 4);
116 if let Some(b) = forms.iter().find(|f| f.kind == FormKind::Batch) {
117 out.fixed += pooled(b.rows * 4) + pooled(b.rows * ctx * 4) + pooled(b.rows * 4);
118 }
119 let mut matrices: BTreeMap<String, usize> = BTreeMap::new();
120 let mut uploads: BTreeMap<(u32, usize, Vec<(u64, usize)>), String> = BTreeMap::new();
121 let mut seen = BTreeSet::new();
122 for f in forms {
123 for buf in f.schedule.buffers.values() {
124 if f.schedule.io.positions().contains(&buf.name.as_str()) || (shared.contains(&buf.name) && !seen.insert(buf.name.clone())) {
125 continue;
126 }
127 match &buf.fill {
128 Fill::Constant(Placement::Rows(r)) => match uploads.get(&upload(r)) {
129 Some(of) => {
130 out.shares.insert(buf.name.clone(), of.clone());
131 }
132 None => {
133 uploads.insert(upload(r), buf.name.clone());
134 matrices.insert(buf.name.clone(), matrix_bytes(r.rows, r.cols, r.ty));
135 }
136 },
137 Fill::Constant(Placement::Values(v)) => out.fixed += pooled(v.len() * 4),
138 Fill::Alias { offset, .. } if buf.quantized && quant_in_place(*offset) => continue,
139 Fill::Alias { .. } => {}
140 Fill::Param | Fill::Zero | Fill::Computed => {
141 if buf.plain && f.arena.at(&buf.name, Touch::Plain).is_none() {
142 out.fixed += pooled(buf.len * buf.dtype.bytes());
143 }
144 }
145 }
146 if buf.quantized && f.arena.at(&buf.name, Touch::Quant).is_none() {
147 out.fixed += pooled(buf.len) + pooled(buf.len / QBLOCK * 4) + if two { pooled(buf.len) } else { 0 };
148 }
149 }
150 for (i, l) in f.schedule.launches.iter().enumerate() {
151 for sc in arena::scratch(l, f.schedule.numerics) {
152 if sc.counter || f.arena.scratch_at(i, sc.tag).is_none() {
153 out.fixed += pooled(sc.bytes);
154 }
155 }
156 }
157 }
158 out.order = eviction_order(forms, &matrices, &out.shares).into_iter().map(|m| {
159 let bytes = matrices[&m];
160 (m, bytes)
161 }).collect();
162 out.spare = SPARE + forms.iter().map(|f| f.schedule.launches.len()).sum::<usize>() * SPARE_A_LAUNCH;
163 out
164}
165
166pub fn place(plan: &Plan, budget: usize) -> Result<Residence, LaneError> {
170 let fixed = plan.arena + plan.fixed;
171 if fixed > budget {
172 return Err(LaneError::Memory { needed: fixed, budget });
173 }
174 let mut out = Residence { device_bytes: plan.whole(), ..Residence::default() };
175 for (name, bytes) in &plan.order {
176 if out.device_bytes <= budget {
177 break;
178 }
179 out.device_bytes -= bytes;
180 out.host_bytes += bytes;
181 out.host.insert(name.clone());
182 out.moved += 1;
183 }
184 Ok(out)
185}
186
187fn eviction_order(forms: &[Form], matrices: &BTreeMap<String, usize>, shares: &BTreeMap<String, String>) -> Vec<String> {
190 let decode = forms.iter().find(|f| f.rows == 1).expect("a lane has a decode step");
191 let mut class: BTreeMap<&str, u8> = BTreeMap::new();
192 let mut last: BTreeMap<&str, usize> = BTreeMap::new();
193 for f in forms {
194 for l in f.schedule.launches.iter().flat_map(|l| l.parts()) {
195 let experts: Vec<&str> = match l {
196 Launch::Moe { wg, wu, wd, .. } => vec![wg, wu, wd],
197 _ => Vec::new(),
198 };
199 for t in l.reads() {
200 let c = if matches!(l, Launch::Gather { .. }) {
201 0
202 } else if experts.contains(&t) {
203 1
204 } else {
205 2
206 };
207 let t = shares.get(t).map_or(t, String::as_str);
208 if matrices.contains_key(t) {
209 let e = class.entry(t).or_insert(c);
210 *e = (*e).max(c);
211 }
212 }
213 }
214 }
215 for (i, l) in decode.schedule.launches.iter().enumerate() {
216 for t in l.reads() {
217 let t = shares.get(t).map_or(t, String::as_str);
218 if matrices.contains_key(t) {
219 last.insert(t, i);
220 }
221 }
222 }
223 let mut order: Vec<&String> = matrices.keys().collect();
224 order.sort_by_key(|m| (class.get(m.as_str()).copied().unwrap_or(2), std::cmp::Reverse(last.get(m.as_str()).copied().unwrap_or(0)), m.as_str()));
225 order.into_iter().cloned().collect()
226}