Skip to main content

logicaffeine_compile/spatial/
d5005.rs

1//! The Intel PAC D5005: a Stratix 10 SX 2800 on four channels of DDR4, as a decode card.
2//!
3//! The card's facts are its data sheet's, its shell's and its device's resource table's. What a design adds to them
4//! is an [`EnginePlan`]: how many DSP blocks multiply 4-bit weight codes, two products to each of a block's two 18×19
5//! multipliers; how many soft multiply-adds the logic carries beside them; how many block-scale lanes turn the
6//! array's integer sums into f32, and the DSP blocks and logic they take; how many DSP blocks run in f32 for the
7//! vector units; how much of each DDR4 beat carries data; how many lines of a recurrent state the stream engines
8//! read before writing them back; and the clock the array closes at. Until the engine is fitted the clock, the soft
9//! multiply-adds and the lanes' logic are modeled, and every price that rests on them says so. Until the card's
10//! stream receipt is taken, the share of the DDR4 peak each way a pass moves its bytes sustains is the DDR4 ledger's
11//! ([`DDR_STREAMS`]): the shell's own controller timing, issued by the JESD79-4 controller model.
12//!
13//! [`roofline`] prices Qwen3.8-27B on the card at every knob point of the decode engine, one trick at a time,
14//! from the checkpoints' own headers: what each trick buys, which term binds, and how many sequences fit.
15
16use std::path::Path;
17
18use serde::Serialize;
19
20use super::decode::{Chip, GgufHeader, Link, Tier, TierKind};
21use super::decode_engine::ddr::{CACHE_POSITIONS, STATE_CHUNKS};
22use super::decode_engine::numerics::ActScaling;
23use super::part::{MemoryKind, PartResources};
24use super::rdu_chip::{Fact, Provenance};
25use super::serving::{
26    best_serving, AttentionMath, Card, ContextClass, ContextMix, Draft, HostLink, Serving, ServingCost, StateFlush, Streams,
27};
28use crate::codegen_sva::fabric_target::Precision;
29
30/// The card's data sheet.
31pub const D5005_DATA_SHEET: &str = "cdrdv2-public.intel.com/691516/ds-pac-d5005-683568-691516.pdf";
32/// The Open FPGA Stack guide for the card: its shell's memory and host interfaces.
33pub const D5005_OFS_GUIDE: &str = "ofs.github.io/ofs-2024.2-1/hw/d5005/dev_guides/afu_dev/ug_dev_afu_d5005";
34/// The device's overview and its product table: its resources.
35pub use super::part::{S10_OVERVIEW, S10_PRODUCT_TABLE};
36/// The host link's signalling.
37pub const PCIE3_SPEC: &str = "PCI-SIG PCI Express Base Specification Revision 3.0";
38
39pub const D5005_SOURCES: &[&str] =
40    &[D5005_DATA_SHEET, D5005_OFS_GUIDE, D5005_FIM_EMIF, S10_OVERVIEW, S10_PRODUCT_TABLE, PCIE3_SPEC];
41
42/// DSP blocks on the device: half its 18×19 multipliers, two to a block.
43pub const DSP_BLOCKS: u32 = 5_760;
44/// M20K blocks and the bits each holds.
45pub const M20K_BLOCKS: u32 = 11_721;
46pub const M20K_BITS: u32 = 20_480;
47/// Adaptive logic modules.
48pub const ALMS: u32 = 933_120;
49/// Products of a 4-bit weight code and an 8-bit activation code one DSP block retires per cycle: each of its
50/// two 18×19 multipliers takes an activation code on one port and two rows' weight codes on the other, so two
51/// rows' products leave every multiplier in disjoint lanes (`decode_engine::rtl::array`).
52pub const ARRAY_MACS_PER_DSP: u32 = 4;
53/// DSP blocks one block-scale lane takes: its block scale times the activation scale times the block's integer sum
54/// and its second scale's alike, two 27×27 products, and its f32 accumulate.
55pub const SCALE_LANE_DSPS: u32 = 3;
56/// Array cycles per lane cycle the standard plan's lanes keep up with: Qwen3.8-27B Q4_K_M's own mix, its K-quant
57/// matrices a lane cycle per 256 elements, its Q8_0 matrices one per 32 over two passes, its drafting layer's Q4_0
58/// one per 32, with room for the drafting layer.
59pub const LANE_MIX: f64 = 216.0;
60/// DDR4 channels, the transfers per second of each and the bytes per transfer (64 data bits; ECC beside them).
61pub const DDR_CHANNELS: u32 = 4;
62pub const DDR_TRANSFERS_PER_S: f64 = 2.4e9;
63pub const DDR_BYTES_PER_TRANSFER: f64 = 8.0;
64/// Bytes each channel's DIMM holds: 1 Gi words of 64 data bits.
65pub const DDR_CHANNEL_BYTES: f64 = (1u64 << 33) as f64;
66/// The card's shell: its four memory controllers built without ECC, each exposing all 72 bits of a beat.
67pub const D5005_FIM_EMIF: &str = "OFS ofs-d5005 2024.1-1 ipss/mem/ip/emif_ddr4_no_ecc.ip";
68/// Bytes per second one direction of the PCIe Gen3 x16 host link carries: 16 lanes of 8 GT/s, 128b/130b.
69pub const PCIE3_X16_BYTES_PER_S: f64 = 16.0 * 8e9 * 128.0 / 130.0 / 8.0;
70
71/// The card as a batch-one decode sees it: the M20K blocks and the DDR4 behind them.
72pub fn chip() -> Chip {
73    let plan = EnginePlan::standard();
74    Chip {
75        name: "d5005",
76        tiers: vec![on_chip(&plan), memory(plan.beat)],
77        matrix: vec![(
78            Precision::Int8,
79            Fact {
80                value: plan.array_rate(),
81                provenance: plan.clock_hz.provenance,
82                cite: "not published: an int8 operand is two 4-bit digits, two passes of the 4-bit array, so int8 \
83                       operations per second (two per multiply-add) equal the array's multiply-adds per second at the \
84                       standard engine plan's modeled clock",
85            },
86        )],
87        stage_overhead: None,
88        link: Some(Link {
89            bandwidth: Some(Fact::derived(
90                2.0 * 100e9 / 8.0,
91                "cdrdv2-public.intel.com/691516/ds-pac-d5005-683568-691516.pdf: \"Dual QSFP28 Ports: 2x100G\"; two \
92                 ports of 100 Gb/s",
93            )),
94            hop_latency: None,
95        }),
96        power: Some(Fact::published(
97            215.0,
98            "cdrdv2-public.intel.com/691516/ds-pac-d5005-683568-691516.pdf: \"Power 215 W (Under sufficient cooling \
99             capabilities)\"",
100        )),
101        sources: D5005_SOURCES,
102    }
103}
104
105fn memory(beat: Beat) -> Tier {
106    match beat {
107        Beat::Data64 => Tier {
108            kind: TierKind::Ddr,
109            capacity: Fact::derived(
110                DDR_CHANNELS as f64 * DDR_CHANNEL_BYTES,
111                "cdrdv2-public.intel.com/691516/ds-pac-d5005-683568-691516.pdf: \"4x 8GB DDR4-2400 with ECC\"; \
112                 ofs.github.io/ofs-2024.2-1/hw/d5005/dev_guides/afu_dev/ug_dev_afu_d5005: \"8GB RDIMM modules organized \
113                 as 1 Gb × 72\", 1 Gi words of 64 data bits each, four channels",
114            ),
115            bandwidth: Fact::derived(
116                DDR_CHANNELS as f64 * DDR_TRANSFERS_PER_S * DDR_BYTES_PER_TRANSFER,
117                "cdrdv2-public.intel.com/691516/ds-pac-d5005-683568-691516.pdf: \"4x 8GB DDR4-2400 with ECC\"; four \
118                 channels of 2400 MT/s, 8 data bytes a transfer",
119            ),
120        },
121        Beat::Full72 => Tier {
122            kind: TierKind::Ddr,
123            capacity: Fact::derived(
124                DDR_CHANNELS as f64 * DDR_CHANNEL_BYTES * 9.0 / 8.0,
125                "ofs.github.io/ofs-2024.2-1/hw/d5005/dev_guides/afu_dev/ug_dev_afu_d5005: \"8GB RDIMM modules organized \
126                 as 1 Gb × 72\"; OFS ofs-d5005 2024.1-1 ipss/mem/ip/emif_ddr4_no_ecc.ip: MEM_DDR4_DQ_WIDTH 72, \
127                 CTRL_DDR4_ECC_EN false; 1 Gi words of all 72 bits each, four channels",
128            ),
129            bandwidth: Fact::derived(
130                DDR_CHANNELS as f64 * DDR_TRANSFERS_PER_S * DDR_BYTES_PER_TRANSFER * 9.0 / 8.0,
131                "OFS ofs-d5005 2024.1-1 ipss/mem/ip/emif_ddr4_no_ecc.ip: MEM_DDR4_DQ_WIDTH 72, CTRL_DDR4_ECC_EN false, \
132                 MEM_DDR4_SPEEDBIN_ENUM DDR4_SPEEDBIN_2400; ipss/mem/includes/ofs_fim_emif_cfg_pkg.sv: AVMM_DATA_WIDTH 576; \
133                 four channels of 2400 MT/s, 9 bytes a transfer",
134            ),
135        },
136    }
137}
138
139fn on_chip(plan: &EnginePlan) -> Tier {
140    let m20k = PartResources::sx2800().memory(MemoryKind::M20k).expect("the SX 2800 carries M20K blocks");
141    Tier {
142        kind: TierKind::Sram,
143        capacity: Fact::derived(m20k.count.value as f64 * m20k.bits.value as f64 / 8.0, m20k.count.cite),
144        bandwidth: Fact {
145            value: m20k.count.value as f64 * m20k.read_bits_per_cycle.value as f64 / 8.0 * plan.clock_hz.value,
146            provenance: plan.clock_hz.provenance.max(m20k.read_bits_per_cycle.provenance),
147            cite: "not published: every M20K block's one widest read port at the standard engine plan's modeled clock",
148        },
149    }
150}
151
152/// How much of each DDR4 beat carries data.
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
154pub enum Beat {
155    /// 64 of the 72 bits, the other 8 left to an error-correcting code.
156    Data64,
157    /// All 72: the shell builds its controllers without ECC, so the eight check bits of every beat are data. A
158    /// flipped bit is found, not corrected: the weight image carries a check word per tile and is scrubbed, and
159    /// every state and cache row is written with a check word and recomputed from its sequence when one fails.
160    Full72,
161}
162
163/// How the decode engine spends the device: the multiplier budget, the block-scale lanes, the memory beat and the
164/// clock it closes at.
165#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
166pub struct EnginePlan {
167    /// The array's clock.
168    pub clock_hz: Fact<f64>,
169    /// DSP blocks in the 4-bit array, [`ARRAY_MACS_PER_DSP`] multiply-adds each per cycle.
170    pub array_dsp_blocks: u32,
171    /// Multiply-adds of a 4-bit weight code and an 8-bit activation code carried in logic beside the DSP blocks.
172    pub array_soft_macs: Fact<f64>,
173    /// Block-scale lanes, each one cycle a weight block at [`SCALE_LANE_DSPS`] DSP blocks and its logic.
174    pub scale_lanes: u32,
175    /// Logic one lane takes: its sub-block multipliers, the exact two-term sum, its rounding.
176    pub scale_lane_alms: Fact<f64>,
177    /// DSP blocks in f32 mode for the vector units, one multiply-add each per cycle.
178    pub vector_dsp_blocks: u32,
179    pub beat: Beat,
180    /// Lines of a recurrent state a stream engine reads before it writes them back: the memory's bus turns twice a
181    /// chunk, and the engine holds a chunk read ahead and a chunk written behind.
182    pub state_chunk_lines: u64,
183}
184
185impl EnginePlan {
186    /// The plan the engine is built to: 768 DSP blocks of f32 vector units, 24,576 soft multiply-adds, as many
187    /// block-scale lanes as the array's multiply-adds fill at [`LANE_MIX`] a lane cycle, the rest of the DSP blocks in
188    /// the array, 64 data bits of every beat, and a recurrent state rewritten 512 lines at a time — a value head's
189    /// binary16 state of 128 by 128, at 64 bytes a line.
190    pub fn standard() -> EnginePlan {
191        const VECTOR: u32 = 768;
192        const SOFT: f64 = 24_576.0;
193        // The fewest lanes that keep up with what the DSP blocks left to the array multiply.
194        let lanes = {
195            let mut l = 0u32;
196            while ((DSP_BLOCKS - VECTOR - SCALE_LANE_DSPS * l) as f64 * ARRAY_MACS_PER_DSP as f64 + SOFT) / LANE_MIX > l as f64 {
197                l += 1;
198            }
199            l
200        };
201        EnginePlan {
202            clock_hz: Fact::modeled(
203                450e6,
204                "not published: the clock a Hyperflex-retimed array is planned to close at; replaced by the fitted \
205                 engine's timing",
206            ),
207            array_dsp_blocks: DSP_BLOCKS - VECTOR - SCALE_LANE_DSPS * lanes,
208            array_soft_macs: Fact::modeled(
209                SOFT,
210                "not tabulated: soft multiply-adds of a 4-bit code and an 8-bit code in compressor trees, about 15 ALMs \
211                 each over 40% of the device's ALMs; replaced by the engine's synthesized census",
212            ),
213            scale_lanes: lanes,
214            scale_lane_alms: Fact::modeled(
215                650.0,
216                "not tabulated: sixteen radix-4 Booth products of a 6-to-8-bit multiplier and a 17-to-26-bit sum, the \
217                 activation scale's 11×8 product, a two-term exact sum over 29 binary places and its rounding to f32; \
218                 replaced by the engine's synthesized census",
219            ),
220            vector_dsp_blocks: VECTOR,
221            beat: Beat::Data64,
222            state_chunk_lines: 512,
223        }
224    }
225
226    /// The standard plan reading and writing all 72 bits of every beat.
227    pub fn full_beat() -> EnginePlan {
228        EnginePlan { beat: Beat::Full72, ..EnginePlan::standard() }
229    }
230
231    /// Multiply-adds per second the array retires.
232    pub fn array_rate(&self) -> f64 {
233        (self.array_dsp_blocks as f64 * ARRAY_MACS_PER_DSP as f64 + self.array_soft_macs.value) * self.clock_hz.value
234    }
235
236    /// Lane cycles per second the block-scale lanes retire.
237    pub fn scale_rate(&self) -> f64 {
238        self.scale_lanes as f64 * self.clock_hz.value
239    }
240
241    /// DSP blocks the plan spends: the array's, the lanes', the vector units'.
242    pub fn dsp_blocks(&self) -> u32 {
243        self.array_dsp_blocks + SCALE_LANE_DSPS * self.scale_lanes + self.vector_dsp_blocks
244    }
245
246    /// ALMs the plan's soft multiply-adds and lanes take, at the modeled 15 a soft multiply-add.
247    pub fn alms(&self) -> f64 {
248        self.array_soft_macs.value * 15.0 + self.scale_lanes as f64 * self.scale_lane_alms.value
249    }
250
251    /// f32 multiply-adds per second the vector units retire.
252    pub fn vector_rate(&self) -> f64 {
253        self.vector_dsp_blocks as f64 * self.clock_hz.value
254    }
255}
256
257/// The card as a serving sees it, under `plan`.
258pub fn card(plan: &EnginePlan) -> Card {
259    assert!(plan.dsp_blocks() <= DSP_BLOCKS, "the plan spends {} DSP blocks of {DSP_BLOCKS}", plan.dsp_blocks());
260    assert!(plan.alms() <= ALMS as f64, "the plan spends {} ALMs of {ALMS}", plan.alms());
261    let weakest = plan.clock_hz.provenance.max(plan.array_soft_macs.provenance);
262    Card {
263        name: "d5005",
264        memory: memory(plan.beat),
265        on_chip: on_chip(plan),
266        streams: streams(plan),
267        array: Fact {
268            value: plan.array_rate(),
269            provenance: weakest,
270            cite: "not published: the plan's array DSP blocks at 4 multiply-adds a cycle plus its soft multiply-adds, \
271                   at its modeled clock",
272        },
273        vector: Fact {
274            value: plan.vector_rate(),
275            provenance: plan.clock_hz.provenance,
276            cite: "not published: the plan's f32 DSP blocks, one multiply-add a cycle each (S10-OVERVIEW Table 11: one \
277                   single precision multiplier and adder a block), at its modeled clock",
278        },
279        scale: Fact {
280            value: plan.scale_rate(),
281            provenance: plan.clock_hz.provenance.max(plan.scale_lane_alms.provenance),
282            cite: "not published: the plan's block-scale lanes, one lane cycle each a cycle, at its modeled clock",
283        },
284        host: Some(host_link()),
285        sources: D5005_SOURCES,
286    }
287}
288
289/// The clocks a channel carried data in of the clocks one stream took, as the DDR4 ledger counts them.
290#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
291pub struct StreamClocks {
292    pub data_ck: u64,
293    pub end: u64,
294}
295
296impl StreamClocks {
297    /// The share of the channel's peak the stream sustained.
298    pub fn share(self) -> f64 {
299        self.data_ck as f64 / self.end as f64
300    }
301}
302
303/// One beat's streams in the DDR4 ledger (`benchmarks/silicon/d5005/ddr4_efficiency.json`, written by
304/// [`super::decode_engine::ddr::ddr_ledger`] from the shell's EMIF timing through the JESD79-4 controller model), in the
305/// ledger's order: a long read of weights; a Gated-DeltaNet memory read and written back
306/// [`STATE_CHUNKS`] lines at a time; a cache read with its new position appended after it, at each of
307/// [`CACHE_POSITIONS`].
308#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
309pub struct BeatStreams {
310    pub beat: Beat,
311    pub weights: StreamClocks,
312    pub state: [StreamClocks; STATE_CHUNKS.len()],
313    pub cache: [StreamClocks; CACHE_POSITIONS.len()],
314}
315
316const fn clocks(data_ck: u64, end: u64) -> StreamClocks {
317    StreamClocks { data_ck, end }
318}
319
320/// The DDR4 ledger's streams at both beats.
321pub const DDR_STREAMS: [BeatStreams; 2] = [
322    BeatStreams {
323        beat: Beat::Data64,
324        weights: clocks(262_144, 275_498),
325        state: [clocks(817_152, 1_262_867), clocks(817_152, 962_962), clocks(817_152, 885_772), clocks(817_152, 866_952)],
326        cache: [clocks(131_584, 138_155), clocks(1_049_088, 1_102_936), clocks(4_194_816, 4_411_164)],
327    },
328    BeatStreams {
329        beat: Beat::Full72,
330        weights: clocks(262_144, 275_498),
331        state: [clocks(726_368, 1_122_940), clocks(726_368, 856_196), clocks(726_368, 787_655), clocks(726_368, 770_807)],
332        cache: [clocks(116_992, 123_094), clocks(932_544, 980_425), clocks(3_728_752, 3_920_783)],
333    },
334];
335
336/// The shares of the card's DDR4 the stream engines sustain under `plan`, from the ledger's streams at its beat: the
337/// weights' long read; the state rewritten at the plan's chunk; the least of the caches', which holds at every context
338/// the ledger prices.
339pub fn streams(plan: &EnginePlan) -> Streams {
340    let at = DDR_STREAMS.iter().find(|s| s.beat == plan.beat).expect("the ledger streams both beats");
341    let chunk = STATE_CHUNKS.iter().position(|&c| c == plan.state_chunk_lines).unwrap_or_else(|| {
342        panic!("the plan rewrites a state {} lines at a time; the DDR4 ledger prices {STATE_CHUNKS:?}", plan.state_chunk_lines)
343    });
344    Streams {
345        read: Fact::modeled(
346            at.weights.share(),
347            "not published: the share of DDR4 peak the shell's controllers sustain on one long read, as the JESD79-4 \
348             controller model issues it at the shell's EMIF timing (benchmarks/silicon/d5005/ddr4_efficiency.json, the \
349             weights row of the plan's beat); replaced by the card's measured stream receipt",
350        ),
351        rewrite: Fact::modeled(
352            at.state[chunk].share(),
353            "not published: the share of DDR4 peak the shell's controllers sustain on a memory read and written back a \
354             chunk at a time, as the JESD79-4 controller model issues it at the shell's EMIF timing \
355             (benchmarks/silicon/d5005/ddr4_efficiency.json, the state row of the plan's chunk and beat); replaced by the \
356             card's measured stream receipt",
357        ),
358        append: Fact::modeled(
359            at.cache.iter().map(|c| c.share()).fold(f64::INFINITY, f64::min),
360            "not published: the share of DDR4 peak the shell's controllers sustain on a long read with a row appended \
361             after it, as the JESD79-4 controller model issues it at the shell's EMIF timing \
362             (benchmarks/silicon/d5005/ddr4_efficiency.json, the least of the cache rows of the plan's beat); replaced by \
363             the card's measured stream receipt",
364        ),
365    }
366}
367
368/// The host memory the card reaches over PCIe Gen3 x16, both directions at once.
369pub fn host_link() -> HostLink {
370    let direction = |cite: &'static str| Fact::derived(PCIE3_X16_BYTES_PER_S, cite);
371    HostLink {
372        capacity: Fact::modeled(
373            (64u64 << 30) as f64,
374            "not published: the host memory a serving pins for the card, 64 GiB; the card's host server is not yet \
375             chosen",
376        ),
377        read: direction(
378            "ofs.github.io/ofs-2024.2-1/hw/d5005/dev_guides/afu_dev/ug_dev_afu_d5005: \"AXI Streaming (AXI-S) interface \
379             to the Host via PCIe Gen3x16\"; PCI-SIG PCI Express Base Specification Revision 3.0: 8.0 GT/s a lane, \
380             128b/130b encoding; 16 lanes host to card",
381        ),
382        write: direction(
383            "ofs.github.io/ofs-2024.2-1/hw/d5005/dev_guides/afu_dev/ug_dev_afu_d5005: \"AXI Streaming (AXI-S) interface \
384             to the Host via PCIe Gen3x16\"; PCI-SIG PCI Express Base Specification Revision 3.0: 8.0 GT/s a lane, \
385             128b/130b encoding; 16 lanes card to host",
386        ),
387        stream: Fact::modeled(
388            0.8,
389            "not published: the share of a PCIe Gen3 x16 direction the card's DMA sustains with 256-byte payloads; \
390             replaced by the card's measured host-stream receipt",
391        ),
392    }
393}
394
395// ── The roofline ────────────────────────────────────────────────────────────────────────────────────
396
397/// The ledger the measured MTP acceptance is read from.
398pub const ACCEPTANCE_LEDGER: &str = "benchmarks/results/qwen38_27b.json";
399/// The JSON pointer of the one-draft acceptance measured on Qwen3.8-27B.
400pub const ACCEPTANCE_POINTER: &str = "/logos/speculative_decode_2026_09_21/b1/accept_rate";
401
402/// The one-draft acceptance, as measured.
403pub fn measured_acceptance(value: f64) -> Fact<f64> {
404    Fact::measured(
405        value,
406        "benchmarks/results/qwen38_27b.json /logos/speculative_decode_2026_09_21/b1/accept_rate: the blk.64 draft \
407         verified against the main model, H100 NVL, 512 prompt tokens and 128 generated",
408    )
409}
410
411/// Vocabulary rows the drafting head reads when it keeps only the most frequent tokens.
412pub const DRAFT_HEAD_ROWS: u64 = 32_768;
413/// Positions per KV page.
414pub const KV_PAGE: u64 = 256;
415
416/// One knob point of the engine and its price at the batch that emits the most tokens per second.
417#[derive(Clone, Debug, PartialEq, Serialize)]
418pub struct RooflineRow {
419    pub label: String,
420    /// The header ledger the checkpoint is read from.
421    pub header: &'static str,
422    pub serving: Serving,
423    pub cost: ServingCost,
424    pub tokens_per_s: f64,
425}
426
427#[derive(Clone, Debug, PartialEq, Serialize)]
428pub struct Roofline {
429    pub card: Card,
430    pub plan: EnginePlan,
431    pub rows: Vec<RooflineRow>,
432    /// Every context's last rung and the mixed contexts again on the plan that reads all 72 bits of every beat.
433    pub full_beat: FullBeat,
434}
435
436#[derive(Clone, Debug, PartialEq, Serialize)]
437pub struct FullBeat {
438    pub card: Card,
439    pub plan: EnginePlan,
440    pub rows: Vec<RooflineRow>,
441}
442
443/// The engine's knob points, one trick added at a time, then every trick at every weight format.
444pub fn ladder(acceptance: f64) -> Vec<(String, &'static str, Serving)> {
445    let base = |limit: u64| Serving {
446        batch: 1,
447        contexts: ContextMix::uniform(limit as f64 / 2.0, limit),
448        kv_page: None,
449        attention: AttentionMath::Vector,
450        kv_bytes: 2.0,
451        state_bytes: 4.0,
452        activation_words: 2,
453        scaling: ActScaling::Sub32,
454        kv_rotated: false,
455        draft: None,
456        flush: StateFlush::EveryPass,
457        host_memory: false,
458    };
459    let per_block = |s: Serving| Serving { scaling: ActScaling::PerBlock, ..s };
460    let f16_state = |s: Serving| Serving { state_bytes: 2.0, ..s };
461    // An int8 KV element with an f16 scale per 256-element head row, each row rotated before its codes.
462    let int8_kv = |s: Serving| Serving { kv_bytes: 1.0 + 2.0 / 256.0, kv_rotated: true, ..s };
463    let lazy = |s: Serving| Serving { flush: StateFlush::Every { tokens: 16 }, ..s };
464    let mtp = |s: Serving| Serving {
465        draft: Some(Draft { tokens: 1, acceptance: vec![measured_acceptance(acceptance)], head_rows: None }),
466        ..s
467    };
468    let fr_spec = |s: Serving| Serving { draft: s.draft.clone().map(|d| Draft { head_rows: Some(DRAFT_HEAD_ROWS), ..d }), ..s };
469    let paged = |s: Serving| Serving { kv_page: Some(KV_PAGE), ..s };
470    let host = |s: Serving| Serving { host_memory: true, ..s };
471    let one_word = |s: Serving| Serving { activation_words: 1, ..s };
472    let q8 = "qwen3.8-27b-q8_0";
473    let q4 = "qwen3.8-27b-q4_k_m";
474    let mut rows = Vec::new();
475    for (ctx, tag) in [(2048u64, "2k"), (8192, "8k")] {
476        let s0 = base(ctx);
477        let sb = per_block(s0.clone());
478        let s1 = f16_state(sb.clone());
479        let s2 = int8_kv(s1.clone());
480        let s3 = lazy(s2.clone());
481        let s4 = mtp(s3.clone());
482        let s5 = one_word(s4.clone());
483        let s6 = fr_spec(s5.clone());
484        let s7 = paged(s6.clone());
485        let s8 = host(s7.clone());
486        for (label, header, s) in [
487            ("Q8_0, f16 KV, f32 state, two-word activations", q8, s0.clone()),
488            ("Q4_K_M", q4, s0),
489            ("+ activations scaled per weight block", q4, sb),
490            ("+ f16 state", q4, s1),
491            ("+ int8 KV, rotated", q4, s2),
492            ("+ state flushed every 16 tokens", q4, s3),
493            ("+ one MTP draft", q4, s4),
494            ("+ one-word activations", q4, s5),
495            ("+ 32k-row drafting head", q4, s6),
496            ("+ paged KV", q4, s7),
497            ("+ host memory as a fifth channel", q4, s8.clone()),
498        ] {
499            rows.push((format!("{tag}: {label}"), header, s));
500        }
501        if tag == "2k" {
502            for (label, header) in [
503                ("Q8_0", "qwen3.8-27b-q8_0"),
504                ("Q6_K", "qwen3.8-27b-q6_k"),
505                ("Q5_K_M", "qwen3.8-27b-q5_k_m"),
506                ("IQ4_XS", "qwen3.8-27b-iq4_xs"),
507                ("Q4_0", "qwen3.8-27b-q4_0"),
508                ("Q3_K_M", "qwen3.8-27b-q3_k_m"),
509            ] {
510                rows.push((format!("{tag}, every trick: {label}"), header, s8.clone()));
511            }
512        }
513    }
514    // Every trick across the contexts a sequence may be allowed, attention on the vector units and on the array.
515    let every = |contexts: ContextMix, attention: AttentionMath| Serving {
516        contexts,
517        attention,
518        ..host(paged(fr_spec(one_word(mtp(lazy(int8_kv(f16_state(per_block(base(2048))))))))))
519    };
520    for limit in CONTEXT_SWEEP {
521        for (math, name) in [(AttentionMath::Vector, "vector units"), (AttentionMath::Array, "array")] {
522            let label = format!("context {} of {}, attention on the {name}", limit / 2, limit);
523            rows.push((label, q4, every(ContextMix::uniform(limit as f64 / 2.0, limit), math)));
524        }
525    }
526    for (math, name) in [(AttentionMath::Vector, "vector units"), (AttentionMath::Array, "array")] {
527        rows.push((format!("mixed contexts, attention on the {name}"), q4, every(mixed(), math)));
528    }
529    rows
530}
531
532/// Context limits the sweep allows its sequences, each halfway through its limit: up to the checkpoint's own.
533pub const CONTEXT_SWEEP: [u64; 7] = [1024, 4096, 16384, 32768, 65536, 131072, 262144];
534
535/// A serving's mix of chats, documents and long contexts: 70% of sequences at 512 of 2,048 positions, 25% at
536/// 4,096 of 8,192, 5% at 65,536 of 131,072.
537pub fn mixed() -> ContextMix {
538    ContextMix {
539        classes: vec![
540            ContextClass { share: 0.70, position: 512.0, limit: 2048 },
541            ContextClass { share: 0.25, position: 4096.0, limit: 8192 },
542            ContextClass { share: 0.05, position: 65536.0, limit: 131072 },
543        ],
544    }
545}
546
547/// Every knob point of [`ladder`] priced on the card under the standard plan, at its fastest batch.
548pub fn roofline(decode_dir: &Path) -> Result<Roofline, String> {
549    let ledger: serde_json::Value = serde_json::from_str(
550        &std::fs::read_to_string(decode_dir.join("../../results/qwen38_27b.json")).map_err(|e| format!("{ACCEPTANCE_LEDGER}: {e}"))?,
551    )
552    .map_err(|e| format!("{ACCEPTANCE_LEDGER}: {e}"))?;
553    let acceptance = ledger
554        .pointer(ACCEPTANCE_POINTER)
555        .and_then(|v| v.as_f64())
556        .ok_or_else(|| format!("{ACCEPTANCE_LEDGER}: no number at {ACCEPTANCE_POINTER}"))?;
557    let price = |card: &Card, label: String, header: &'static str, s: Serving| -> Result<RooflineRow, String> {
558        let h = GgufHeader::read_ledger(&decode_dir.join(format!("headers/{header}.json")))?;
559        let cost = best_serving(&h, card, &s).map_err(|e| format!("{label}: {e:?}"))?;
560        let serving = Serving { batch: cost.batch, ..s };
561        Ok(RooflineRow { label, header, tokens_per_s: cost.tokens_per_s(), serving, cost })
562    };
563    let plan = EnginePlan::standard();
564    let standard = card(&plan);
565    let full_plan = EnginePlan::full_beat();
566    let full = card(&full_plan);
567    let rungs = ladder(acceptance);
568    let mut rows = Vec::new();
569    for (label, header, s) in rungs.iter().cloned() {
570        rows.push(price(&standard, label, header, s)?);
571    }
572    let tops = rungs
573        .iter()
574        .filter(|(label, ..)| label.ends_with("host memory as a fifth channel") || label.starts_with("mixed contexts"))
575        .cloned();
576    let mut full_rows = Vec::new();
577    for (label, header, s) in tops {
578        full_rows.push(price(&full, format!("{label}, all 72 bits of every beat"), header, s)?);
579    }
580    Ok(Roofline { card: standard, plan, rows, full_beat: FullBeat { card: full, plan: full_plan, rows: full_rows } })
581}
582
583/// The weakest provenance of every fact a roofline row rests on.
584pub fn weakest(rows: &[RooflineRow]) -> Provenance {
585    rows.iter().map(|r| r.cost.provenance).max().unwrap_or(Provenance::Published)
586}