quail-test / modeling_rwkv7.py
Ilikemechuri's picture
Update modeling_rwkv7.py
4373de4 verified
Raw
History Blame Contribute Delete
26 kB
########################################################################################################
# RWKV-7 "Goose" (x070 / g1d) HuggingFace modeling code
# Based on the reference implementation from https://github.com/BlinkDL/RWKV-LM
#
# This file provides a `trust_remote_code=True` compatible RWKV-7 model that:
# * uses a fused CUDA kernel (fwd + bwd, "wind_backstepping" bf16) when a GPU
# + a working CUDA toolchain are available (training / no-cache path), and
# * transparently falls back to a pure-PyTorch implementation otherwise
# (autograd handles the backward pass automatically in the fallback).
#
# Generation now uses a recurrent state cache (RNN mode) instead of recomputing
# the full sequence every step. Stateful inference calls run on a second,
# forward-only CUDA kernel ("wkv7s", state in/out, no chunk padding) when
# available, so both prefill and decode are kernel-speed under no_grad.
# The per-layer state is:
# (att_x_prev, wkv_state, ffn_x_prev)
# att_x_prev : (B, C) last post-ln1 token, for time-shift
# wkv_state : (B, H, N, N) float32 WKV matrix state
# ffn_x_prev : (B, C) last post-ln2 token, for time-shift
# The state is carried through `state=` / `outputs.state`, which HF's
# GenerationMixin propagates between steps (same convention as Rwkv/Mamba).
########################################################################################################
import os
import math
from dataclasses import dataclass
from typing import List, Optional, Tuple, Union
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers.modeling_utils import PreTrainedModel
from transformers.utils import ModelOutput
from transformers.generation import GenerationMixin
try:
from .configuration_rwkv7 import RWKV7Config
except ImportError: # allow running as a plain script (e.g. conversion)
from configuration_rwkv7 import RWKV7Config
# Per-layer recurrent state: (att_x_prev, wkv_state, ffn_x_prev)
LayerState = Tuple[torch.Tensor, torch.Tensor, torch.Tensor]
########################################################################################################
# HF output dataclasses (carry `state` so GenerationMixin can propagate it)
########################################################################################################
@dataclass
class RWKV7Output(ModelOutput):
last_hidden_state: torch.FloatTensor = None
state: Optional[List[LayerState]] = None
hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None
@dataclass
class RWKV7CausalLMOutput(ModelOutput):
loss: Optional[torch.FloatTensor] = None
logits: torch.FloatTensor = None
state: Optional[List[LayerState]] = None
hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None
########################################################################################################
# CUDA kernel loading (lazy, best-effort). If anything goes wrong we silently
# fall back to the pure-PyTorch path.
########################################################################################################
_KERNEL_STATE = {"loaded": False, "ok": False, "op": None, "chunk_len": 16, "head_size": 64}
def _try_load_cuda_kernel(head_size: int, chunk_len: int):
"""Compile & register the wind_backstepping RWKV-7 CUDA op. Returns True on success."""
if _KERNEL_STATE["loaded"]:
return _KERNEL_STATE["ok"]
_KERNEL_STATE["loaded"] = True
_KERNEL_STATE["head_size"] = head_size
_KERNEL_STATE["chunk_len"] = chunk_len
if not torch.cuda.is_available():
_KERNEL_STATE["ok"] = False
return False
try:
from torch.utils.cpp_extension import load
this_dir = os.path.dirname(os.path.abspath(__file__))
cuda_dir = os.path.join(this_dir, "cuda")
sources = [
os.path.join(cuda_dir, "wkv7_op.cpp"),
os.path.join(cuda_dir, "wkv7_cuda.cu"),
]
print(sources)
print([os.path.exists(s) for s in sources])
if not all(os.path.exists(s) for s in sources):
_KERNEL_STATE["ok"] = False
return False
flags = [
"-res-usage",
f"-D_C_={head_size}",
f"-D_CHUNK_LEN_={chunk_len}",
"--use_fast_math",
"-O3",
"-Xptxas -O3",
"--extra-device-vectorization",
]
load(
name=f"wind_backstepping_c{head_size}_l{chunk_len}",
sources=sources,
is_python_module=False,
verbose=False,
extra_cuda_cflags=flags,
)
_KERNEL_STATE["op"] = torch.ops.wind_backstepping
_KERNEL_STATE["ok"] = True
return True
except Exception as e: # noqa: BLE001 - any failure -> fallback
print(f"[RWKV7] CUDA kernel unavailable, using PyTorch fallback ({type(e).__name__}: {e})")
import traceback
traceback.print_exc()
_KERNEL_STATE["ok"] = False
return False
# Stateful, forward-only inference kernel ("wkv7s"): takes an fp32 WKV state
# and updates it in place -> gives us fast prefill + decode with the cache.
_KERNEL_S_STATE = {"loaded": False, "ok": False, "op": None, "head_size": 64}
def _try_load_cuda_kernel_s(head_size: int):
"""Compile & register the wkv7s stateful RWKV-7 CUDA op. Returns True on success."""
if _KERNEL_S_STATE["loaded"]:
return _KERNEL_S_STATE["ok"]
_KERNEL_S_STATE["loaded"] = True
_KERNEL_S_STATE["head_size"] = head_size
if not torch.cuda.is_available():
_KERNEL_S_STATE["ok"] = False
return False
try:
from torch.utils.cpp_extension import load
this_dir = os.path.dirname(os.path.abspath(__file__))
cuda_dir = os.path.join(this_dir, "cuda")
sources = [
os.path.join(cuda_dir, "wkv7s_op.cpp"),
os.path.join(cuda_dir, "wkv7s.cu"),
]
if not all(os.path.exists(s) for s in sources):
_KERNEL_S_STATE["ok"] = False
return False
flags = [
"-res-usage",
f"-D_N_={head_size}",
"--use_fast_math",
"-O3",
"-Xptxas -O3",
"--extra-device-vectorization",
]
load(
name=f"wkv7s_n{head_size}",
sources=sources,
is_python_module=False,
verbose=False,
extra_cuda_cflags=flags,
)
_KERNEL_S_STATE["op"] = torch.ops.wkv7s
_KERNEL_S_STATE["ok"] = True
return True
except Exception as e: # noqa: BLE001 - any failure -> fallback
print(f"[RWKV7] stateful CUDA kernel unavailable, using PyTorch fallback ({type(e).__name__}: {e})")
_KERNEL_S_STATE["ok"] = False
return False
class _WindBackstepping(torch.autograd.Function):
"""Fused RWKV-7 kernel wrapper (bf16). Implements both forward and backward.
Inputs are shaped (B, T, H, C) with T % CHUNK_LEN == 0 and dtype bfloat16.
NOTE: the kernel always starts from a zero WKV state and does not expose the
final state, so it is only used on the no-cache (training / plain forward) path.
"""
@staticmethod
def forward(ctx, w, q, k, v, z, b):
op = _KERNEL_STATE["op"]
chunk_len = _KERNEL_STATE["chunk_len"]
B, T, H, C = w.shape
assert T % chunk_len == 0, "pad T to a multiple of CHUNK_LEN"
assert all(i.dtype == torch.bfloat16 for i in [w, q, k, v, z, b])
assert all(i.is_contiguous() for i in [w, q, k, v, z, b])
y = torch.empty_like(v)
s = torch.empty(B, H, T // chunk_len, C, C, dtype=torch.float32, device=w.device)
sa = torch.empty(B, T, H, C, dtype=torch.float32, device=w.device)
op.forward(w, q, k, v, z, b, y, s, sa)
ctx.save_for_backward(w, q, k, v, z, b, s, sa)
return y
@staticmethod
def backward(ctx, dy):
op = _KERNEL_STATE["op"]
assert dy.dtype == torch.bfloat16
dy = dy.contiguous()
w, q, k, v, z, b, s, sa = ctx.saved_tensors
dw, dq, dk, dv, dz, db = [torch.empty_like(x) for x in [w, q, k, v, z, b]]
op.backward(w, q, k, v, z, b, dy, s, sa, dw, dq, dk, dv, dz, db)
return dw, dq, dk, dv, dz, db
def _rwkv7_cuda(r, w, k, v, a, b, head_size, chunk_len):
"""CUDA path. r,w,k,v,a,b are (B, T, C) bf16. a = -kk, b = kk*a_gate."""
B, T, C = r.shape
H = C // head_size
pad = (chunk_len - T % chunk_len) % chunk_len
if pad:
r, w, k, v, a, b = [F.pad(x, (0, 0, 0, pad)) for x in (r, w, k, v, a, b)]
Tp = T + pad
r, w, k, v, a, b = [x.view(B, Tp, H, head_size).contiguous() for x in (r, w, k, v, a, b)]
y = _WindBackstepping.apply(w, r, k, v, a, b).view(B, Tp, C)
if pad:
y = y[:, :T]
return y
def _rwkv7_cuda_stateful(r, w, k, v, a, b, head_size, initial_state=None):
"""Stateful CUDA path (wkv7s kernel). Forward-only (no backward), any T,
no chunk padding needed. The kernel updates the fp32 state in place, so we
always hand it a fresh tensor and return it as the final state."""
op = _KERNEL_S_STATE["op"]
B, T, C = r.shape
H = C // head_size
N = head_size
if initial_state is not None:
state = initial_state.to(device=r.device, dtype=torch.float32).contiguous().clone()
else:
state = torch.zeros(B, H, N, N, dtype=torch.float32, device=r.device)
r, w, k, v, a, b = [x.contiguous() for x in (r, w, k, v, a, b)]
y = torch.empty_like(v)
op.forward(B, T, C, H, state, r, w, k, v, a, b, y)
return y, state
def _rwkv7_pytorch(r, w, k, v, a, b, head_size, initial_state=None, output_final_state=False):
"""Pure-PyTorch reference (sequential over time). Differentiable via autograd.
w is the raw (pre-exp) log-decay; the recurrence uses exp(-exp(w)).
Supports an initial WKV state and optionally returns the final state,
which is what enables cached (RNN-mode) generation.
"""
B, T, C = r.size()
H = C // head_size
N = head_size
dtype_in = r.dtype
r = r.view(B, T, H, N).float()
k = k.view(B, T, H, N).float()
v = v.view(B, T, H, N).float()
a = a.view(B, T, H, N).float()
b = b.view(B, T, H, N).float()
w = torch.exp(-torch.exp(w.view(B, T, H, N).float()))
out = torch.zeros((B, T, H, N), device=r.device, dtype=torch.float32)
if initial_state is not None:
state = initial_state.to(device=r.device, dtype=torch.float32)
else:
state = torch.zeros((B, H, N, N), device=r.device, dtype=torch.float32)
for t in range(T):
kk = k[:, t, :].view(B, H, 1, N)
rr = r[:, t, :].view(B, H, N, 1)
vv = v[:, t, :].view(B, H, N, 1)
aa = a[:, t, :].view(B, H, N, 1)
bb = b[:, t, :].view(B, H, 1, N)
state = state * w[:, t, :, None, :] + state @ aa @ bb + vv @ kk
out[:, t, :] = (state @ rr).view(B, H, N)
final_state = state if output_final_state else None
return out.view(B, T, C).to(dtype=dtype_in), final_state
def run_rwkv7(
r, w, k, v, a, b, config,
initial_state=None,
output_final_state=False,
force_fallback=False,
):
stateful = initial_state is not None or output_final_state
common_ok = (
config.use_cuda_kernel
and not force_fallback
and r.is_cuda
and r.dtype == torch.bfloat16
)
print(
"RWKV7 DEBUG:",
"stateful=", stateful,
"common_ok=", common_ok,
"cuda=", r.is_cuda,
"dtype=", r.dtype,
"use_kernel=", config.use_cuda_kernel,
"force=", force_fallback,
"kernel_loaded=", _KERNEL_STATE["loaded"],
"kernel_ok=", _KERNEL_STATE["ok"],
)
if not stateful:
if common_ok and _try_load_cuda_kernel(config.head_size, config.chunk_len):
print("USING CUDA KERNEL")
return _rwkv7_cuda(
r,w,k,v,a,b,
config.head_size,
config.chunk_len
) ,None
print("USING PYTORCH FALLBACK")
return _rwkv7_pytorch(
r, w, k, v, a, b, config.head_size,
initial_state=initial_state, output_final_state=output_final_state,
)
########################################################################################################
# RWKV-7 time-mixing ("attention") block
########################################################################################################
class RWKV7TimeMix(nn.Module):
def __init__(self, config: RWKV7Config, layer_id: int):
super().__init__()
self.config = config
self.layer_id = layer_id
self.head_size = config.head_size
C = config.hidden_size
self.n_head = C // self.head_size
H, N = self.n_head, self.head_size
self.x_r = nn.Parameter(torch.empty(1, 1, C))
self.x_w = nn.Parameter(torch.empty(1, 1, C))
self.x_k = nn.Parameter(torch.empty(1, 1, C))
self.x_v = nn.Parameter(torch.empty(1, 1, C))
self.x_a = nn.Parameter(torch.empty(1, 1, C))
self.x_g = nn.Parameter(torch.empty(1, 1, C))
self.w0 = nn.Parameter(torch.empty(1, 1, C))
self.w1 = nn.Parameter(torch.empty(C, config.decay_lora))
self.w2 = nn.Parameter(torch.empty(config.decay_lora, C))
self.a0 = nn.Parameter(torch.empty(1, 1, C))
self.a1 = nn.Parameter(torch.empty(C, config.aaa_lora))
self.a2 = nn.Parameter(torch.empty(config.aaa_lora, C))
self.v0 = nn.Parameter(torch.empty(1, 1, C))
self.v1 = nn.Parameter(torch.empty(C, config.mv_lora))
self.v2 = nn.Parameter(torch.empty(config.mv_lora, C))
self.g1 = nn.Parameter(torch.empty(C, config.gate_lora))
self.g2 = nn.Parameter(torch.empty(config.gate_lora, C))
self.k_k = nn.Parameter(torch.empty(1, 1, C))
self.k_a = nn.Parameter(torch.empty(1, 1, C))
self.r_k = nn.Parameter(torch.empty(H, N))
self.time_shift = nn.ZeroPad2d((0, 0, 1, -1))
self.receptance = nn.Linear(C, C, bias=False)
self.key = nn.Linear(C, C, bias=False)
self.value = nn.Linear(C, C, bias=False)
self.output = nn.Linear(C, C, bias=False)
self.ln_x = nn.GroupNorm(H, C, eps=config.group_norm_epsilon)
def forward(self, x, v_first, x_prev=None, wkv_state=None, use_cache=False):
"""x_prev: (B, C) last input token from the previous chunk (post-ln1),
wkv_state: (B, H, N, N) float32 WKV state from the previous chunk."""
B, T, C = x.size()
H = self.n_head
if x_prev is None:
shifted = self.time_shift(x) # zero-pad == zero initial shift state
else:
shifted = torch.cat([x_prev.to(x.dtype).unsqueeze(1), x[:, :-1]], dim=1)
xx = shifted - x
new_x_prev = x[:, -1] if use_cache else None
xr = x + xx * self.x_r
xw = x + xx * self.x_w
xk = x + xx * self.x_k
xv = x + xx * self.x_v
xa = x + xx * self.x_a
xg = x + xx * self.x_g
r = self.receptance(xr)
# soft-clamp to (-inf, -0.5); the recurrence applies exp(-exp(w))
w = -F.softplus(-(self.w0 + torch.tanh(xw @ self.w1) @ self.w2)) - 0.5
k = self.key(xk)
v = self.value(xv)
if self.layer_id == 0:
v_first = v
else:
v = v + (v_first - v) * torch.sigmoid(self.v0 + (xv @ self.v1) @ self.v2)
a = torch.sigmoid(self.a0 + (xa @ self.a1) @ self.a2) # in-context learning rate
g = torch.sigmoid(xg @ self.g1) @ self.g2
kk = k * self.k_k
kk = F.normalize(kk.view(B, T, H, -1), dim=-1, p=2.0).view(B, T, C)
k = k * (1 + (a - 1) * self.k_a)
x, new_wkv_state = run_rwkv7(
r, w, k, v, -kk, kk * a, self.config,
initial_state=wkv_state, output_final_state=use_cache,
)
x = self.ln_x(x.view(B * T, C)).view(B, T, C)
x = x + (
(r.view(B, T, H, -1) * k.view(B, T, H, -1) * self.r_k).sum(dim=-1, keepdim=True)
* v.view(B, T, H, -1)
).view(B, T, C)
x = self.output(x * g)
return x, v_first, new_x_prev, new_wkv_state
########################################################################################################
# RWKV-7 channel-mixing (FFN) block
########################################################################################################
class RWKV7ChannelMix(nn.Module):
def __init__(self, config: RWKV7Config, layer_id: int):
super().__init__()
self.layer_id = layer_id
self.time_shift = nn.ZeroPad2d((0, 0, 1, -1))
self.x_k = nn.Parameter(torch.empty(1, 1, config.hidden_size))
self.key = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
self.value = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
def forward(self, x, x_prev=None, use_cache=False):
"""x_prev: (B, C) last input token from the previous chunk (post-ln2)."""
if x_prev is None:
shifted = self.time_shift(x)
else:
shifted = torch.cat([x_prev.to(x.dtype).unsqueeze(1), x[:, :-1]], dim=1)
xx = shifted - x
new_x_prev = x[:, -1] if use_cache else None
k = x + xx * self.x_k
k = torch.relu(self.key(k)) ** 2
return self.value(k), new_x_prev
class RWKV7Block(nn.Module):
def __init__(self, config: RWKV7Config, layer_id: int):
super().__init__()
self.layer_id = layer_id
eps = config.layer_norm_epsilon
if layer_id == 0:
self.ln0 = nn.LayerNorm(config.hidden_size, eps=eps)
self.ln1 = nn.LayerNorm(config.hidden_size, eps=eps)
self.ln2 = nn.LayerNorm(config.hidden_size, eps=eps)
self.att = RWKV7TimeMix(config, layer_id)
self.ffn = RWKV7ChannelMix(config, layer_id)
def forward(self, x, v_first, layer_state: Optional[LayerState] = None, use_cache: bool = False):
if self.layer_id == 0:
x = self.ln0(x)
if layer_state is not None:
att_x_prev, wkv_state, ffn_x_prev = layer_state
else:
att_x_prev = wkv_state = ffn_x_prev = None
x_attn, v_first, att_x_prev, wkv_state = self.att(
self.ln1(x), v_first, x_prev=att_x_prev, wkv_state=wkv_state, use_cache=use_cache
)
x = x + x_attn
ffn_out, ffn_x_prev = self.ffn(self.ln2(x), x_prev=ffn_x_prev, use_cache=use_cache)
x = x + ffn_out
new_state = (att_x_prev, wkv_state, ffn_x_prev) if use_cache else None
return x, v_first, new_state
########################################################################################################
# HuggingFace wrappers
########################################################################################################
class RWKV7PreTrainedModel(PreTrainedModel):
config_class = RWKV7Config
base_model_prefix = "rwkv"
supports_gradient_checkpointing = True
_no_split_modules = ["RWKV7Block"]
def _init_weights(self, module):
# Weights normally come from a pretrained checkpoint; this only covers
# freshly-created (e.g. re-sized embedding / head) parameters.
if isinstance(module, nn.Linear):
module.weight.data.normal_(mean=0.0, std=0.02)
if module.bias is not None:
module.bias.data.zero_()
elif isinstance(module, nn.Embedding):
module.weight.data.normal_(mean=0.0, std=1e-4)
elif isinstance(module, nn.LayerNorm):
module.weight.data.fill_(1.0)
module.bias.data.zero_()
class RWKV7Model(RWKV7PreTrainedModel):
def __init__(self, config: RWKV7Config):
super().__init__(config)
self.config = config
self.emb = nn.Embedding(config.vocab_size, config.hidden_size)
self.blocks = nn.ModuleList(
[RWKV7Block(config, i) for i in range(config.num_hidden_layers)]
)
self.ln_out = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_epsilon)
self.gradient_checkpointing = False
self.post_init()
def get_input_embeddings(self):
return self.emb
def set_input_embeddings(self, value):
self.emb = value
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
inputs_embeds: Optional[torch.FloatTensor] = None,
state: Optional[List[LayerState]] = None,
use_cache: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
**kwargs,
) -> Union[Tuple, RWKV7Output]:
return_dict = return_dict if return_dict is not None else True
output_hidden_states = (
output_hidden_states
if output_hidden_states is not None
else self.config.output_hidden_states
)
use_cache = (
use_cache if use_cache is not None else getattr(self.config, "use_cache", False)
)
if state is not None:
use_cache = True # an incoming state implies stateful mode
if self.gradient_checkpointing and self.training and use_cache:
use_cache = False
if inputs_embeds is None:
inputs_embeds = self.emb(input_ids)
x = inputs_embeds
all_hidden_states = () if output_hidden_states else None
new_states: Optional[List[LayerState]] = [] if use_cache else None
v_first = torch.empty_like(x)
for i, block in enumerate(self.blocks):
if output_hidden_states:
all_hidden_states += (x,)
layer_state = state[i] if state is not None else None
if self.gradient_checkpointing and self.training:
x, v_first, layer_new_state = self._gradient_checkpointing_func(
block.__call__, x, v_first, layer_state, use_cache
)
else:
x, v_first, layer_new_state = block(x, v_first, layer_state, use_cache)
if use_cache:
new_states.append(layer_new_state)
x = self.ln_out(x)
if output_hidden_states:
all_hidden_states += (x,)
if not return_dict:
return tuple(v for v in [x, new_states, all_hidden_states] if v is not None)
return RWKV7Output(
last_hidden_state=x,
state=new_states,
hidden_states=all_hidden_states,
)
class RWKV7ForCausalLM(RWKV7PreTrainedModel, GenerationMixin):
_tied_weights_keys = []
def __init__(self, config: RWKV7Config):
super().__init__(config)
self.rwkv = RWKV7Model(config)
self.head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
self.post_init()
def get_input_embeddings(self):
return self.rwkv.emb
def set_input_embeddings(self, value):
self.rwkv.emb = value
def get_output_embeddings(self):
return self.head
def set_output_embeddings(self, new_embeddings):
self.head = new_embeddings
def get_decoder(self):
return self.rwkv
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
inputs_embeds: Optional[torch.FloatTensor] = None,
state: Optional[List[LayerState]] = None,
use_cache: Optional[bool] = None,
labels: Optional[torch.LongTensor] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
**kwargs,
) -> Union[Tuple, RWKV7CausalLMOutput]:
return_dict = return_dict if return_dict is not None else True
outputs = self.rwkv(
input_ids=input_ids,
inputs_embeds=inputs_embeds,
state=state,
use_cache=use_cache,
output_hidden_states=output_hidden_states,
return_dict=True,
)
hidden = outputs.last_hidden_state
logits = self.head(hidden)
loss = None
if labels is not None:
labels = labels.to(logits.device)
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = labels[:, 1:].contiguous()
loss = F.cross_entropy(
shift_logits.view(-1, shift_logits.size(-1)).float(),
shift_labels.view(-1),
)
if not return_dict:
output = (logits, outputs.state, outputs.hidden_states)
output = tuple(v for v in output if v is not None)
return ((loss,) + output) if loss is not None else output
return RWKV7CausalLMOutput(
loss=loss,
logits=logits,
state=outputs.state,
hidden_states=outputs.hidden_states,
)
def prepare_inputs_for_generation(
self, input_ids, state=None, inputs_embeds=None, use_cache=True, **kwargs
):
# RNN mode: once we have a state, only the newly generated token needs
# to be fed; everything before it is already absorbed into the state.
if state is not None:
input_ids = input_ids[:, -1:]
model_inputs = {"input_ids": input_ids}
elif inputs_embeds is not None:
model_inputs = {"inputs_embeds": inputs_embeds}
else:
model_inputs = {"input_ids": input_ids}
model_inputs["state"] = state
model_inputs["use_cache"] = use_cache
return model_inputs
def _update_model_kwargs_for_generation(self, outputs, model_kwargs, **kwargs):
# Recent transformers versions pick up `state` automatically (it is in
# ALL_CACHE_NAMES); this override keeps older versions working too.
model_kwargs = super()._update_model_kwargs_for_generation(outputs, model_kwargs, **kwargs)
model_kwargs["state"] = getattr(outputs, "state", None)
return model_kwargs