use super::*;
use std::sync::Arc;
#[derive(Clone)]
pub struct TextContinuationIdentity(Arc<()>);
impl PartialEq for TextContinuationIdentity {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for TextContinuationIdentity {}
#[derive(Clone)]
pub struct TextDriverIdentity(Arc<()>);
impl PartialEq for TextDriverIdentity {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for TextDriverIdentity {}
#[derive(Debug, thiserror::Error)]
pub enum TextContinuationError<B, C>
where
B: std::error::Error + 'static,
C: std::error::Error + 'static,
{
#[error("text continuation belongs to a different runtime driver")]
IncompatibleDriver,
#[error("text continuation failed and cannot advance")]
Failed,
#[error("text continuation has no completed, drained boundary")]
NotQuiescent,
#[error(transparent)]
Generation(#[from] ControlledTextGenerationError<B, C>),
}
pub struct TextGenerationContinuation<B, C>
where
B: TextGenerationBackend,
C: TokenFilterController,
{
owner: Arc<()>,
identity: TextContinuationIdentity,
inner: TextGenerationMachine<B, C>,
failed: bool,
records_drained: bool,
}
impl<B, C> TextGenerationContinuation<B, C>
where
B: TextGenerationBackend,
C: TokenFilterController,
{
pub fn controller(&self) -> &C {
&self.inner.controller
}
pub fn controller_mut(&mut self) -> &mut C {
&mut self.inner.controller
}
pub fn remaining_tokens(&self) -> Option<usize> {
self.inner.remaining_tokens
}
pub fn is_prefill_pending(&self) -> bool {
matches!(self.inner.step, Some(PendingTextInput::Prefill(_)))
}
pub fn require_quiescent(&self) -> Result<(), TextContinuationError<B::Error, C::Error>> {
if self.failed {
return Err(TextContinuationError::Failed);
}
if !self.inner.completions.is_empty() || !self.records_drained {
return Err(TextContinuationError::NotQuiescent);
}
Ok(())
}
}
pub struct TextGenerationDriver<'a, B: TextGenerationBackend> {
runtime: &'a mut ModelRuntime<B>,
owner: Arc<()>,
}
impl<'a, B: TextGenerationBackend> TextGenerationDriver<'a, B> {
pub fn new(runtime: &'a mut ModelRuntime<B>) -> Self {
Self {
runtime,
owner: Arc::new(()),
}
}
pub fn runtime(&self) -> &ModelRuntime<B> {
self.runtime
}
pub fn start<C: TokenFilterController>(
&mut self,
prompt: B::Prompt,
config: TextGenerationConfig,
controller: C,
) -> Result<TextGenerationContinuation<B, C>, ControlledTextGenerationError<B::Error, C::Error>>
{
Ok(TextGenerationContinuation {
owner: Arc::clone(&self.owner),
identity: TextContinuationIdentity(Arc::new(())),
inner: TextGenerationMachine::new(self.runtime, prompt, config, controller)?,
failed: false,
records_drained: true,
})
}
fn validate<C: TokenFilterController>(
&self,
state: &TextGenerationContinuation<B, C>,
) -> Result<(), TextContinuationError<B::Error, C::Error>> {
if !Arc::ptr_eq(&self.owner, &state.owner) {
return Err(TextContinuationError::IncompatibleDriver);
}
Ok(())
}
pub fn quiescent<'d, 's, C: TokenFilterController>(
&'d mut self,
state: &'s mut TextGenerationContinuation<B, C>,
) -> Result<TextContinuationBoundary<'d, 's, B, C>, TextContinuationError<B::Error, C::Error>>
{
self.validate(state)?;
state.require_quiescent()?;
Ok(TextContinuationBoundary {
runtime: self.runtime,
state,
})
}
#[allow(clippy::type_complexity)]
pub fn advance<C: TokenFilterController>(
&mut self,
state: &mut TextGenerationContinuation<B, C>,
) -> Result<Option<ControlledToken<B::Token>>, TextContinuationError<B::Error, C::Error>> {
self.validate(state)?;
state.require_quiescent()?;
state.failed = true;
state.records_drained = false;
match state.inner.next_committed(self.runtime) {
None => {
state.failed = false;
state.records_drained = true;
Ok(None)
}
Some(Ok(token)) => {
state.failed = false;
Ok(Some(token))
}
Some(Err(error)) => Err(TextContinuationError::Generation(error)),
}
}
pub fn take_completed_step<C: TokenFilterController>(
&mut self,
state: &mut TextGenerationContinuation<B, C>,
) -> Result<Option<crate::capture::CapturedStep>, TextContinuationError<B::Error, C::Error>>
{
self.validate(state)?;
let was_failed = state.failed;
state.failed = true;
if let Err(error) = state.inner.resolve_completions_before_decode() {
return Err(ControlledTextGenerationError::Backend(error).into());
}
let records = B::take_text_capture(&mut state.inner.backend_state);
state.records_drained = true;
state.failed = was_failed;
Ok(records)
}
pub fn enable_capture<C: TokenFilterController>(
&mut self,
state: &mut TextGenerationContinuation<B, C>,
plan: crate::capture::AdmittedCapturePlan,
) -> Result<(), crate::capture::CaptureError> {
self.validate_installation(state)?;
B::configure_text_capture(self.runtime, &mut state.inner.backend_state, plan)
}
pub fn enable_interventions<C: TokenFilterController>(
&mut self,
state: &mut TextGenerationContinuation<B, C>,
capture: crate::capture::AdmittedCapturePlan,
plan: crate::intervention::AdmittedInterventionPlan,
) -> Result<(), crate::capture::CaptureError> {
self.validate_installation(state)?;
B::configure_text_interventions(self.runtime, &mut state.inner.backend_state, capture, plan)
}
fn validate_installation<C: TokenFilterController>(
&self,
state: &TextGenerationContinuation<B, C>,
) -> Result<(), crate::capture::CaptureError> {
self.validate(state)
.and_then(|()| state.require_quiescent())
.map_err(|error| crate::capture::CaptureError::Invalid(error.to_string()))?;
if !state.is_prefill_pending() {
return Err(crate::capture::CaptureError::Invalid(
"capture and interventions must be configured before generation".into(),
));
}
Ok(())
}
}
pub struct TextContinuationBoundary<'d, 's, B, C>
where
B: TextGenerationBackend,
C: TokenFilterController,
{
runtime: &'d mut ModelRuntime<B>,
state: &'s mut TextGenerationContinuation<B, C>,
}
impl<B: TextGenerationBackend, C: TokenFilterController> TextContinuationBoundary<'_, '_, B, C> {
pub fn identity(&self) -> TextContinuationIdentity {
self.state.identity.clone()
}
pub fn driver_identity(&self) -> TextDriverIdentity {
TextDriverIdentity(Arc::clone(&self.state.owner))
}
pub fn controller(&self) -> &C {
&self.state.inner.controller
}
pub fn remaining_tokens(&self) -> Option<usize> {
self.state.inner.remaining_tokens
}
#[allow(clippy::type_complexity)]
pub fn parts(
&self,
) -> (
&ModelRuntime<B>,
&B::TextGenerationState,
Option<PendingTextInput<&B::Prompt, &B::Token>>,
) {
(
self.runtime,
&self.state.inner.backend_state,
self.state.inner.step.as_ref().map(PendingTextInput::as_ref),
)
}
#[allow(clippy::type_complexity)]
pub fn mechanism_parts(
&mut self,
) -> (
&mut ModelRuntime<B>,
&mut B::TextGenerationState,
Option<PendingTextInput<&B::Prompt, &B::Token>>,
) {
(
self.runtime,
&mut self.state.inner.backend_state,
self.state.inner.step.as_ref().map(PendingTextInput::as_ref),
)
}
pub fn install_host_state(
&mut self,
controller: C,
pending: Option<PendingTextInput<B::Prompt, B::Token>>,
remaining_tokens: Option<usize>,
) {
self.state.inner.controller = controller;
self.state.inner.step = pending;
self.state.inner.remaining_tokens = remaining_tokens;
}
pub fn fork_host_state(
&self,
backend_state: B::TextGenerationState,
controller: C,
pending: Option<PendingTextInput<B::Prompt, B::Token>>,
remaining_tokens: Option<usize>,
) -> TextGenerationContinuation<B, C> {
TextGenerationContinuation {
owner: Arc::clone(&self.state.owner),
identity: TextContinuationIdentity(Arc::new(())),
inner: TextGenerationMachine {
backend_state,
controller,
step: pending,
completions: Vec::new(),
remaining_tokens,
},
failed: false,
records_drained: true,
}
}
pub fn fail(&mut self) {
self.state.failed = true;
}
}
impl<B: TextGenerationBackend, C: TokenFilterController> Drop
for TextContinuationBoundary<'_, '_, B, C>
{
fn drop(&mut self) {
if std::thread::panicking() {
self.state.failed = true;
}
}
}
impl<B: crate::execution_control::NativeTextStateBackend> TextGenerationDriver<'_, B> {
fn validate_boundary<C: TokenFilterController>(
&self,
state: &TextGenerationContinuation<B, C>,
) -> Result<(), TextContinuationError<B::Error, C::Error>> {
self.validate(state)?;
state.require_quiescent()
}
pub fn estimate_native_state<C: TokenFilterController>(
&self,
state: &TextGenerationContinuation<B, C>,
saved: Option<&B::NativeTextState>,
) -> Result<
Option<crate::execution_control::SnapshotEstimate>,
TextContinuationError<B::Error, C::Error>,
> {
self.validate_boundary(state)?;
B::estimate_native_text_state(self.runtime, saved)
.map_err(|error| ControlledTextGenerationError::Backend(error).into())
}
pub fn capture_native_state<C: TokenFilterController>(
&mut self,
state: &TextGenerationContinuation<B, C>,
) -> Result<B::NativeTextState, TextContinuationError<B::Error, C::Error>> {
self.validate_boundary(state)?;
B::capture_native_text_state(self.runtime)
.map_err(|error| ControlledTextGenerationError::Backend(error).into())
}
pub fn copy_native_state<C: TokenFilterController>(
&mut self,
state: &TextGenerationContinuation<B, C>,
saved: &B::NativeTextState,
) -> Result<B::NativeTextState, TextContinuationError<B::Error, C::Error>> {
self.validate_boundary(state)?;
B::copy_native_text_state(self.runtime, saved)
.map_err(|error| ControlledTextGenerationError::Backend(error).into())
}
pub fn exchange_native_state<C: TokenFilterController>(
&mut self,
state: &TextGenerationContinuation<B, C>,
slot: &mut B::NativeTextState,
) -> Result<(), TextContinuationError<B::Error, C::Error>> {
self.validate_boundary(state)?;
B::exchange_native_text_state(self.runtime, slot)
.map_err(|error| ControlledTextGenerationError::Backend(error).into())
}
}
impl<B: crate::execution_control::NativeTextStateBackend, C: TokenFilterController>
TextContinuationBoundary<'_, '_, B, C>
{
pub fn exchange_branch(
&mut self,
other: &mut TextGenerationContinuation<B, C>,
native: &mut B::NativeTextState,
) -> Result<(), TextContinuationError<B::Error, C::Error>> {
if !Arc::ptr_eq(&self.state.owner, &other.owner) {
return Err(TextContinuationError::IncompatibleDriver);
}
other.require_quiescent()?;
B::validate_native_text_state(self.runtime, native)
.map_err(ControlledTextGenerationError::Backend)?;
B::exchange_native_text_state(self.runtime, native)
.map_err(ControlledTextGenerationError::Backend)?;
std::mem::swap(self.state, other);
Ok(())
}
}