1use anyhow::Result;
17use rlx_ir::HirGraphExt;
18use rlx_ir::hir::HirMut;
19use rlx_ir::op::MaskKind;
20use rlx_ir::shape;
21
22use super::BlockStage;
23use crate::context::FlowCtx;
24use crate::value::FlowValue;
25#[derive(Debug, Clone)]
26pub struct LlamaDecodeLayerSpec {
27 pub num_heads: usize,
28 pub head_dim: usize,
29 pub n_rot: usize,
32 pub num_kv_heads: usize,
33 pub kv_group_size: usize,
34 pub eps: f32,
35 pub use_custom_mask: bool,
36 pub hidden_shape: rlx_ir::Shape,
37 pub rope_style: rlx_ir::RopeStyle,
40}
41
42#[derive(Debug, Clone)]
43pub struct LlamaDecodeLayerStage {
44 pub layer_prefix: String,
45 pub spec: LlamaDecodeLayerSpec,
46 pub layer_idx: usize,
47 pub kv_out: std::sync::Arc<std::sync::Mutex<Vec<rlx_ir::HirNodeId>>>,
48 pub aux_in_out: Option<std::sync::Arc<std::sync::Mutex<Vec<rlx_ir::HirNodeId>>>>,
53}
54
55impl LlamaDecodeLayerStage {
56 pub fn layer(
57 layer_idx: usize,
58 spec: LlamaDecodeLayerSpec,
59 kv_out: std::sync::Arc<std::sync::Mutex<Vec<rlx_ir::HirNodeId>>>,
60 ) -> Self {
61 Self {
62 layer_prefix: format!("model.layers.{layer_idx}"),
63 spec,
64 layer_idx,
65 kv_out,
66 aux_in_out: None,
67 }
68 }
69
70 pub fn with_aux_input_tap(
71 mut self,
72 sink: std::sync::Arc<std::sync::Mutex<Vec<rlx_ir::HirNodeId>>>,
73 ) -> Self {
74 self.aux_in_out = Some(sink);
75 self
76 }
77}
78
79impl BlockStage for LlamaDecodeLayerStage {
80 fn emit(&self, ctx: &mut FlowCtx<'_>, input: FlowValue) -> Result<Option<FlowValue>> {
81 if let Some(sink) = self.aux_in_out.as_ref() {
82 sink.lock().expect("aux in out").push(input.id);
83 }
84
85 let decode = ctx
86 .state
87 .decode
88 .clone()
89 .ok_or_else(|| anyhow::anyhow!("LlamaDecodeLayer requires BindDecodeInputs"))?;
90 let zero_beta = ctx
91 .state
92 .zero_beta
93 .ok_or_else(|| anyhow::anyhow!("LlamaDecodeLayer requires ZeroBeta"))?;
94
95 let lp = &self.layer_prefix;
96 let spec = &self.spec;
97 let in_ln_g = ctx.load_param(&format!("{lp}.input_layernorm.weight"), false)?;
98 let q_w = ctx.load_param(&format!("{lp}.self_attn.q_proj.weight"), true)?;
99 let k_w = ctx.load_param(&format!("{lp}.self_attn.k_proj.weight"), true)?;
100 let v_w = ctx.load_param(&format!("{lp}.self_attn.v_proj.weight"), true)?;
101 let o_w = ctx.load_param(&format!("{lp}.self_attn.o_proj.weight"), true)?;
102 let post_ln_g = ctx.load_param(&format!("{lp}.post_attention_layernorm.weight"), false)?;
103 let gate_w = ctx.load_param(&format!("{lp}.mlp.gate_proj.weight"), true)?;
104 let up_w = ctx.load_param(&format!("{lp}.mlp.up_proj.weight"), true)?;
105 let down_w = ctx.load_param(&format!("{lp}.mlp.down_proj.weight"), true)?;
106
107 let past_k = decode.past_k.get(self.layer_idx);
108 let past_v = decode.past_v.get(self.layer_idx);
109
110 let mut gb = HirMut::new(ctx.hir());
111 let normed_in = gb.rms_norm(input.id, in_ln_g, zero_beta, spec.eps);
112 let q = gb.mm(normed_in, q_w);
113 let k = gb.mm(normed_in, k_w);
114 let v = gb.mm(normed_in, v_w);
115
116 let q_rope = gb.rope_n_styled(
117 q,
118 decode.cos,
119 decode.sin,
120 spec.head_dim,
121 spec.n_rot,
122 spec.rope_style,
123 );
124 let k_rope = gb.rope_n_styled(
125 k,
126 decode.cos,
127 decode.sin,
128 spec.head_dim,
129 spec.n_rot,
130 spec.rope_style,
131 );
132
133 let (new_k, new_v) = match (past_k, past_v) {
134 (Some(past_k), Some(past_v)) => (
135 gb.concat_(vec![*past_k, k_rope], 1),
136 gb.concat_(vec![*past_v, v], 1),
137 ),
138 _ => (k_rope, v),
139 };
140 self.kv_out.lock().expect("kv out").push(new_k);
141 self.kv_out.lock().expect("kv out").push(new_v);
142
143 let k_rep = super::self_attn::repeat_kv(
144 &mut gb,
145 new_k,
146 spec.num_kv_heads,
147 spec.head_dim,
148 spec.kv_group_size,
149 );
150 let v_rep = super::self_attn::repeat_kv(
151 &mut gb,
152 new_v,
153 spec.num_kv_heads,
154 spec.head_dim,
155 spec.kv_group_size,
156 );
157
158 let attn_shape = shape::attention_shape(gb.shape(q_rope));
159 let attn = if spec.use_custom_mask {
160 let mask = decode
161 .mask
162 .ok_or_else(|| anyhow::anyhow!("custom mask requested but not bound"))?;
163 gb.attention(
164 q_rope,
165 k_rep,
166 v_rep,
167 mask,
168 spec.num_heads,
169 spec.head_dim,
170 attn_shape,
171 )
172 } else {
173 gb.attention_kind(
174 q_rope,
175 k_rep,
176 v_rep,
177 spec.num_heads,
178 spec.head_dim,
179 MaskKind::Causal,
180 attn_shape,
181 )
182 };
183
184 let attn_out = gb.mm(attn, o_w);
185 let post_attn = gb.add(input.id, attn_out);
186 let normed_post = gb.rms_norm(post_attn, post_ln_g, zero_beta, spec.eps);
187 let gate = gb.mm(normed_post, gate_w);
188 let up = gb.mm(normed_post, up_w);
189 let gate_act = gb.silu(gate);
190 let swiglu = gb.mul(gate_act, up);
191 let ffn_out = gb.mm(swiglu, down_w);
192 let h_id = gb.add(post_attn, ffn_out);
193
194 Ok(Some(ctx.wrap(h_id, spec.hidden_shape.clone())))
195 }
196}