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