Skip to main content

logicaffeine_compile/spatial/decode_engine/
ddr.rs

1//! The engine's memory traffic on the card's DDR4 channels: what the controller model
2//! ([`crate::codegen_sva::ddr4_jedec`]) makes of each kind of stream a pass issues, at the card's own timing.
3//!
4//! A pass streams three kinds of lines through a channel. The weights are one long read of the channel's share of the
5//! weight image. A Gated-DeltaNet memory, rewritten every pass, is read and written back in place a chunk at a time,
6//! the chunk being what the recurrence unit holds between its read and its write. An attention block's cache is one
7//! long read of a sequence's positions and an append of the new one after them. [`ddr_ledger`] prices each on one
8//! channel, the state and cache bytes from the decode pricer's own accounting of Qwen3.8-27B's header (an f16 memory
9//! and conv ring, an int8 cache), at both of the engine's beats, four sequences back to back so the turns between
10//! them are paid; every trace is held to JESD79-4 as it is issued.
11
12use std::path::Path;
13
14use serde::Serialize;
15
16use crate::codegen_sva::ddr4_jedec::{measure, Ddr4Facts, Ddr4Grade, Ddr4Timing, D5005_EMIF_IP, LOOKAHEAD};
17use crate::codegen_sva::dram::StreamDesc;
18use crate::spatial::d5005::{Beat, DDR_CHANNELS};
19use crate::spatial::decode::{DecodeStep, Deployment, GgufHeader, StageKind};
20
21/// The checkpoint whose state and cache the ledger streams.
22pub const HEADER: &str = "qwen3.8-27b-q4_k_m";
23/// Lines of the weight stream priced: long enough to pay its refreshes as a whole pass does.
24pub const WEIGHT_LINES: u64 = 1 << 16;
25/// Lines the recurrence unit reads before it writes them back.
26pub const STATE_CHUNKS: [u64; 4] = [8, 32, 128, 512];
27/// Positions a sequence's cache holds when it is read.
28pub const CACHE_POSITIONS: [u64; 3] = [256, 2048, 8192];
29/// Sequences each state and cache row streams back to back.
30pub const SEQUENCES: u64 = 4;
31
32/// One stream priced on one channel.
33#[derive(Clone, Debug, PartialEq, Serialize)]
34pub struct DdrRow {
35    pub pattern: String,
36    pub beat: Beat,
37    pub line_bytes: u64,
38    pub facts: Ddr4Facts,
39    pub efficiency: f64,
40    /// Controller clocks the stream takes.
41    pub controller_ck: u64,
42    pub channel_bytes_per_s: f64,
43    /// The four channels each streaming alike.
44    pub card_bytes_per_s: f64,
45}
46
47/// The ledger: the channel's clocked rules, the controller model's queue, the bytes the streams are sized from, and
48/// every stream's price.
49#[derive(Clone, Debug, PartialEq, Serialize)]
50pub struct DdrLedger {
51    pub source: &'static str,
52    pub timing: Ddr4Timing,
53    pub lookahead: usize,
54    pub header: &'static str,
55    /// Bytes of one sequence's Gated-DeltaNet memory and conv ring in one block, f16.
56    pub state_bytes: u64,
57    /// Bytes one position of one attention block's int8 cache holds.
58    pub position_bytes: u64,
59    pub rows: Vec<DdrRow>,
60}
61
62fn line_bytes(beat: Beat, t: &Ddr4Timing) -> u64 {
63    match beat {
64        Beat::Data64 => 64,
65        Beat::Full72 => t.burst_bits() / 8,
66    }
67}
68
69fn run(base: u64, bytes: u64, write: bool) -> StreamDesc {
70    StreamDesc { base, elem_bytes: bytes, stride_bytes: bytes, count: 1, write }
71}
72
73/// One sequence's state and one position of its cache, from the decode pricer's stages of `h` with an f16 state and
74/// an int8 cache.
75pub fn stream_bytes(h: &GgufHeader) -> Result<(u64, u64), String> {
76    let d = Deployment { kv_bytes: 1.0, state_bytes: 2.0, ..Deployment::as_stored(1) };
77    let step = DecodeStep::of(h, &d).map_err(|e| format!("{e:?}"))?;
78    let state = step.stages.iter().find(|s| s.kind == StageKind::Recurrence).ok_or("no recurrent block")?.state_read;
79    let position = step.stages.iter().find(|s| s.kind == StageKind::Attention).ok_or("no attention block")?.state_write;
80    Ok((state as u64, position as u64))
81}
82
83/// Every stream of a pass priced on one D5005 channel.
84pub fn ddr_ledger(decode_dir: &Path) -> Result<DdrLedger, String> {
85    let h = GgufHeader::read_ledger(&decode_dir.join(format!("headers/{HEADER}.json")))?;
86    let (state_bytes, position_bytes) = stream_bytes(&h)?;
87    let t = Ddr4Grade::D5005_EMIF.at();
88    let mut rows = Vec::new();
89    for beat in [Beat::Data64, Beat::Full72] {
90        let lb = line_bytes(beat, &t);
91        let mut price = |pattern: String, streams: Vec<StreamDesc>| -> Result<(), String> {
92            let facts = measure(&t, lb, &streams).map_err(|v| format!("{pattern}: {v}"))?;
93            let channel = facts.bytes_per_s(&t, lb);
94            rows.push(DdrRow {
95                pattern,
96                beat,
97                line_bytes: lb,
98                facts,
99                efficiency: facts.efficiency(),
100                controller_ck: t.controller_ck(facts.end),
101                channel_bytes_per_s: channel,
102                card_bytes_per_s: channel * DDR_CHANNELS as f64,
103            });
104            Ok(())
105        };
106        price(format!("weights: one read of {WEIGHT_LINES} lines"), vec![run(0, WEIGHT_LINES * lb, false)])?;
107        let state_lines = state_bytes.div_ceil(lb);
108        for chunk in STATE_CHUNKS {
109            let mut streams = Vec::new();
110            for s in 0..SEQUENCES {
111                let base = s * state_lines;
112                let mut at = 0;
113                while at < state_lines {
114                    let n = chunk.min(state_lines - at);
115                    streams.push(run((base + at) * lb, n * lb, false));
116                    streams.push(run((base + at) * lb, n * lb, true));
117                    at += n;
118                }
119            }
120            price(format!("Gated-DeltaNet state: {SEQUENCES} memories of {state_lines} lines, read and written back {chunk} lines at a time"), streams)?;
121        }
122        for positions in CACHE_POSITIONS {
123            let region = ((positions + 1) * position_bytes).div_ceil(lb);
124            let mut streams = Vec::new();
125            for s in 0..SEQUENCES {
126                let base = s * region * lb;
127                streams.push(run(base, positions * position_bytes, false));
128                streams.push(run(base + positions * position_bytes, position_bytes, true));
129            }
130            price(format!("attention cache: {SEQUENCES} sequences of {positions} positions read, one appended"), streams)?;
131        }
132    }
133    Ok(DdrLedger { source: D5005_EMIF_IP, timing: t, lookahead: LOOKAHEAD, header: HEADER, state_bytes, position_bytes, rows })
134}