1use super::*;
4use std::sync::Arc;
5
6#[derive(Clone)]
8pub struct TextContinuationIdentity(Arc<()>);
9
10impl PartialEq for TextContinuationIdentity {
11 fn eq(&self, other: &Self) -> bool {
12 Arc::ptr_eq(&self.0, &other.0)
13 }
14}
15impl Eq for TextContinuationIdentity {}
16
17#[derive(Clone)]
20pub struct TextDriverIdentity(Arc<()>);
21impl PartialEq for TextDriverIdentity {
22 fn eq(&self, other: &Self) -> bool {
23 Arc::ptr_eq(&self.0, &other.0)
24 }
25}
26impl Eq for TextDriverIdentity {}
27
28#[derive(Debug, thiserror::Error)]
30pub enum TextContinuationError<B, C>
31where
32 B: std::error::Error + 'static,
33 C: std::error::Error + 'static,
34{
35 #[error("text continuation belongs to a different runtime driver")]
37 IncompatibleDriver,
38 #[error("text continuation failed and cannot advance")]
40 Failed,
41 #[error("text continuation has no completed, drained boundary")]
43 NotQuiescent,
44 #[error(transparent)]
46 Generation(#[from] ControlledTextGenerationError<B, C>),
47}
48
49pub struct TextGenerationContinuation<B, C>
56where
57 B: TextGenerationBackend,
58 C: TokenFilterController,
59{
60 owner: Arc<()>,
61 identity: TextContinuationIdentity,
62 inner: TextGenerationMachine<B, C>,
63 failed: bool,
64 records_drained: bool,
65}
66
67impl<B, C> TextGenerationContinuation<B, C>
68where
69 B: TextGenerationBackend,
70 C: TokenFilterController,
71{
72 pub fn controller(&self) -> &C {
74 &self.inner.controller
75 }
76
77 pub fn controller_mut(&mut self) -> &mut C {
79 &mut self.inner.controller
80 }
81
82 pub fn remaining_tokens(&self) -> Option<usize> {
84 self.inner.remaining_tokens
85 }
86
87 pub fn is_prefill_pending(&self) -> bool {
89 matches!(self.inner.step, Some(PendingTextInput::Prefill(_)))
90 }
91
92 pub fn require_quiescent(&self) -> Result<(), TextContinuationError<B::Error, C::Error>> {
95 if self.failed {
96 return Err(TextContinuationError::Failed);
97 }
98 if !self.inner.completions.is_empty() || !self.records_drained {
99 return Err(TextContinuationError::NotQuiescent);
100 }
101 Ok(())
102 }
103}
104
105pub struct TextGenerationDriver<'a, B: TextGenerationBackend> {
113 runtime: &'a mut ModelRuntime<B>,
114 owner: Arc<()>,
115}
116
117impl<'a, B: TextGenerationBackend> TextGenerationDriver<'a, B> {
118 pub fn new(runtime: &'a mut ModelRuntime<B>) -> Self {
122 Self {
123 runtime,
124 owner: Arc::new(()),
125 }
126 }
127
128 pub fn runtime(&self) -> &ModelRuntime<B> {
130 self.runtime
131 }
132
133 pub fn start<C: TokenFilterController>(
136 &mut self,
137 prompt: B::Prompt,
138 config: TextGenerationConfig,
139 controller: C,
140 ) -> Result<TextGenerationContinuation<B, C>, ControlledTextGenerationError<B::Error, C::Error>>
141 {
142 Ok(TextGenerationContinuation {
143 owner: Arc::clone(&self.owner),
144 identity: TextContinuationIdentity(Arc::new(())),
145 inner: TextGenerationMachine::new(self.runtime, prompt, config, controller)?,
146 failed: false,
147 records_drained: true,
148 })
149 }
150
151 fn validate<C: TokenFilterController>(
152 &self,
153 state: &TextGenerationContinuation<B, C>,
154 ) -> Result<(), TextContinuationError<B::Error, C::Error>> {
155 if !Arc::ptr_eq(&self.owner, &state.owner) {
156 return Err(TextContinuationError::IncompatibleDriver);
157 }
158 Ok(())
159 }
160
161 pub fn quiescent<'d, 's, C: TokenFilterController>(
166 &'d mut self,
167 state: &'s mut TextGenerationContinuation<B, C>,
168 ) -> Result<TextContinuationBoundary<'d, 's, B, C>, TextContinuationError<B::Error, C::Error>>
169 {
170 self.validate(state)?;
171 state.require_quiescent()?;
172 Ok(TextContinuationBoundary {
173 runtime: self.runtime,
174 state,
175 })
176 }
177
178 #[allow(clippy::type_complexity)]
182 pub fn advance<C: TokenFilterController>(
183 &mut self,
184 state: &mut TextGenerationContinuation<B, C>,
185 ) -> Result<Option<ControlledToken<B::Token>>, TextContinuationError<B::Error, C::Error>> {
186 self.validate(state)?;
187 state.require_quiescent()?;
188 state.failed = true;
190 state.records_drained = false;
191 match state.inner.next_committed(self.runtime) {
192 None => {
193 state.failed = false;
194 state.records_drained = true;
195 Ok(None)
196 }
197 Some(Ok(token)) => {
198 state.failed = false;
199 Ok(Some(token))
200 }
201 Some(Err(error)) => Err(TextContinuationError::Generation(error)),
202 }
203 }
204
205 pub fn take_completed_step<C: TokenFilterController>(
209 &mut self,
210 state: &mut TextGenerationContinuation<B, C>,
211 ) -> Result<Option<crate::capture::CapturedStep>, TextContinuationError<B::Error, C::Error>>
212 {
213 self.validate(state)?;
214 let was_failed = state.failed;
215 state.failed = true;
216 if let Err(error) = state.inner.resolve_completions_before_decode() {
217 return Err(ControlledTextGenerationError::Backend(error).into());
218 }
219 let records = B::take_text_capture(&mut state.inner.backend_state);
220 state.records_drained = true;
221 state.failed = was_failed;
222 Ok(records)
223 }
224
225 pub fn enable_capture<C: TokenFilterController>(
227 &mut self,
228 state: &mut TextGenerationContinuation<B, C>,
229 plan: crate::capture::AdmittedCapturePlan,
230 ) -> Result<(), crate::capture::CaptureError> {
231 self.validate_installation(state)?;
232 B::configure_text_capture(self.runtime, &mut state.inner.backend_state, plan)
233 }
234
235 pub fn enable_interventions<C: TokenFilterController>(
237 &mut self,
238 state: &mut TextGenerationContinuation<B, C>,
239 capture: crate::capture::AdmittedCapturePlan,
240 plan: crate::intervention::AdmittedInterventionPlan,
241 ) -> Result<(), crate::capture::CaptureError> {
242 self.validate_installation(state)?;
243 B::configure_text_interventions(self.runtime, &mut state.inner.backend_state, capture, plan)
244 }
245
246 fn validate_installation<C: TokenFilterController>(
247 &self,
248 state: &TextGenerationContinuation<B, C>,
249 ) -> Result<(), crate::capture::CaptureError> {
250 self.validate(state)
251 .and_then(|()| state.require_quiescent())
252 .map_err(|error| crate::capture::CaptureError::Invalid(error.to_string()))?;
253 if !state.is_prefill_pending() {
254 return Err(crate::capture::CaptureError::Invalid(
255 "capture and interventions must be configured before generation".into(),
256 ));
257 }
258 Ok(())
259 }
260}
261
262pub struct TextContinuationBoundary<'d, 's, B, C>
267where
268 B: TextGenerationBackend,
269 C: TokenFilterController,
270{
271 runtime: &'d mut ModelRuntime<B>,
272 state: &'s mut TextGenerationContinuation<B, C>,
273}
274
275impl<B: TextGenerationBackend, C: TokenFilterController> TextContinuationBoundary<'_, '_, B, C> {
276 pub fn identity(&self) -> TextContinuationIdentity {
278 self.state.identity.clone()
279 }
280
281 pub fn driver_identity(&self) -> TextDriverIdentity {
283 TextDriverIdentity(Arc::clone(&self.state.owner))
284 }
285
286 pub fn controller(&self) -> &C {
288 &self.state.inner.controller
289 }
290
291 pub fn remaining_tokens(&self) -> Option<usize> {
293 self.state.inner.remaining_tokens
294 }
295
296 #[allow(clippy::type_complexity)]
298 pub fn parts(
299 &self,
300 ) -> (
301 &ModelRuntime<B>,
302 &B::TextGenerationState,
303 Option<PendingTextInput<&B::Prompt, &B::Token>>,
304 ) {
305 (
306 self.runtime,
307 &self.state.inner.backend_state,
308 self.state.inner.step.as_ref().map(PendingTextInput::as_ref),
309 )
310 }
311
312 #[allow(clippy::type_complexity)]
316 pub fn mechanism_parts(
317 &mut self,
318 ) -> (
319 &mut ModelRuntime<B>,
320 &mut B::TextGenerationState,
321 Option<PendingTextInput<&B::Prompt, &B::Token>>,
322 ) {
323 (
324 self.runtime,
325 &mut self.state.inner.backend_state,
326 self.state.inner.step.as_ref().map(PendingTextInput::as_ref),
327 )
328 }
329
330 pub fn install_host_state(
333 &mut self,
334 controller: C,
335 pending: Option<PendingTextInput<B::Prompt, B::Token>>,
336 remaining_tokens: Option<usize>,
337 ) {
338 self.state.inner.controller = controller;
339 self.state.inner.step = pending;
340 self.state.inner.remaining_tokens = remaining_tokens;
341 }
342
343 pub fn fork_host_state(
347 &self,
348 backend_state: B::TextGenerationState,
349 controller: C,
350 pending: Option<PendingTextInput<B::Prompt, B::Token>>,
351 remaining_tokens: Option<usize>,
352 ) -> TextGenerationContinuation<B, C> {
353 TextGenerationContinuation {
354 owner: Arc::clone(&self.state.owner),
355 identity: TextContinuationIdentity(Arc::new(())),
356 inner: TextGenerationMachine {
357 backend_state,
358 controller,
359 step: pending,
360 completions: Vec::new(),
361 remaining_tokens,
362 },
363 failed: false,
364 records_drained: true,
365 }
366 }
367
368 pub fn fail(&mut self) {
370 self.state.failed = true;
371 }
372}
373
374impl<B: TextGenerationBackend, C: TokenFilterController> Drop
375 for TextContinuationBoundary<'_, '_, B, C>
376{
377 fn drop(&mut self) {
378 if std::thread::panicking() {
379 self.state.failed = true;
380 }
381 }
382}
383
384impl<B: crate::execution_control::NativeTextStateBackend> TextGenerationDriver<'_, B> {
385 fn validate_boundary<C: TokenFilterController>(
386 &self,
387 state: &TextGenerationContinuation<B, C>,
388 ) -> Result<(), TextContinuationError<B::Error, C::Error>> {
389 self.validate(state)?;
390 state.require_quiescent()
391 }
392
393 pub fn estimate_native_state<C: TokenFilterController>(
397 &self,
398 state: &TextGenerationContinuation<B, C>,
399 saved: Option<&B::NativeTextState>,
400 ) -> Result<
401 Option<crate::execution_control::SnapshotEstimate>,
402 TextContinuationError<B::Error, C::Error>,
403 > {
404 self.validate_boundary(state)?;
405 B::estimate_native_text_state(self.runtime, saved)
406 .map_err(|error| ControlledTextGenerationError::Backend(error).into())
407 }
408
409 pub fn capture_native_state<C: TokenFilterController>(
412 &mut self,
413 state: &TextGenerationContinuation<B, C>,
414 ) -> Result<B::NativeTextState, TextContinuationError<B::Error, C::Error>> {
415 self.validate_boundary(state)?;
416 B::capture_native_text_state(self.runtime)
417 .map_err(|error| ControlledTextGenerationError::Backend(error).into())
418 }
419
420 pub fn copy_native_state<C: TokenFilterController>(
423 &mut self,
424 state: &TextGenerationContinuation<B, C>,
425 saved: &B::NativeTextState,
426 ) -> Result<B::NativeTextState, TextContinuationError<B::Error, C::Error>> {
427 self.validate_boundary(state)?;
428 B::copy_native_text_state(self.runtime, saved)
429 .map_err(|error| ControlledTextGenerationError::Backend(error).into())
430 }
431
432 pub fn exchange_native_state<C: TokenFilterController>(
436 &mut self,
437 state: &TextGenerationContinuation<B, C>,
438 slot: &mut B::NativeTextState,
439 ) -> Result<(), TextContinuationError<B::Error, C::Error>> {
440 self.validate_boundary(state)?;
441 B::exchange_native_text_state(self.runtime, slot)
442 .map_err(|error| ControlledTextGenerationError::Backend(error).into())
443 }
444}
445
446impl<B: crate::execution_control::NativeTextStateBackend, C: TokenFilterController>
447 TextContinuationBoundary<'_, '_, B, C>
448{
449 pub fn exchange_branch(
454 &mut self,
455 other: &mut TextGenerationContinuation<B, C>,
456 native: &mut B::NativeTextState,
457 ) -> Result<(), TextContinuationError<B::Error, C::Error>> {
458 if !Arc::ptr_eq(&self.state.owner, &other.owner) {
459 return Err(TextContinuationError::IncompatibleDriver);
460 }
461 other.require_quiescent()?;
462 B::validate_native_text_state(self.runtime, native)
463 .map_err(ControlledTextGenerationError::Backend)?;
464 B::exchange_native_text_state(self.runtime, native)
465 .map_err(ControlledTextGenerationError::Backend)?;
466 std::mem::swap(self.state, other);
467 Ok(())
468 }
469}