|
| 1 | +/* |
| 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | + * All rights reserved. |
| 4 | + * |
| 5 | + * This source code is licensed under the BSD-style license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +// Engine/Session interfaces for model-specific LLM implementations. LLMEngine |
| 10 | +// owns loaded model resources; LLMSession owns one logical generation state. |
| 11 | +// Higher-level generation APIs can wrap this lower-level token-step contract. |
| 12 | + |
| 13 | +#pragma once |
| 14 | + |
| 15 | +#include <cstdint> |
| 16 | +#include <memory> |
| 17 | +#include <string> |
| 18 | +#include <unordered_map> |
| 19 | +#include <vector> |
| 20 | + |
| 21 | +#include <executorch/runtime/core/result.h> |
| 22 | +#include <executorch/runtime/platform/compiler.h> |
| 23 | + |
| 24 | +namespace executorch::extension::llm { |
| 25 | + |
| 26 | +/// Per-decode sampling parameters. Implementations apply supported fields and |
| 27 | +/// reject unsupported non-default values rather than silently ignoring them. -1 |
| 28 | +/// temperature means implementation default. |
| 29 | +struct ET_EXPERIMENTAL SamplingConfig { |
| 30 | + float temperature = -1.0f; |
| 31 | + float top_p = 1.0f; |
| 32 | + int32_t top_k = 0; // 0 = disabled |
| 33 | + uint64_t seed = 0; // 0 = unset |
| 34 | +}; |
| 35 | + |
| 36 | +/// One decoded step: the exact sampled token id and its decoded text piece |
| 37 | +/// (raw bytes; may be a partial UTF-8 sequence the caller assembles). |
| 38 | +/// |
| 39 | +/// `is_eos` is literal: the sampled token is an end-of-sequence token (use it |
| 40 | +/// for the "stop" finish reason, metrics, or accounting). `is_terminal` is |
| 41 | +/// the loop signal: generation ended at this step, either EOS or a cooperative |
| 42 | +/// stop() took effect. A decode loop should end when is_terminal is set; every |
| 43 | +/// EOS step is also terminal, but a stop step is terminal without being EOS. |
| 44 | +/// |
| 45 | +/// For a cooperative stop step (requested via stop()), no token is forwarded, |
| 46 | +/// position() must not advance, `token_id` must be 0, and `text_piece` must be |
| 47 | +/// empty. |
| 48 | +struct ET_EXPERIMENTAL DecodeResult { |
| 49 | + uint64_t token_id = 0; |
| 50 | + std::string text_piece; |
| 51 | + bool is_eos = false; |
| 52 | + bool is_terminal = false; |
| 53 | +}; |
| 54 | + |
| 55 | +/// How many physical sessions an engine can host without silently multiplying |
| 56 | +/// model memory. This is an engine-level capacity contract, distinct from how a |
| 57 | +/// session advances a conversation. |
| 58 | +struct ET_EXPERIMENTAL LLMServingCapacity { |
| 59 | + // Physical sessions creatable without duplicating model weights. |
| 60 | + int32_t max_physical_sessions_without_weight_duplication = 1; |
| 61 | + // Estimated device memory added per session, or 0 if unknown. |
| 62 | + int64_t estimated_bytes_per_session = 0; |
| 63 | +}; |
| 64 | + |
| 65 | +/// One logical generation state's mutable buffers and position cursor. Created |
| 66 | +/// by an LLMEngine. |
| 67 | +class ET_EXPERIMENTAL LLMSession { |
| 68 | + public: |
| 69 | + virtual ~LLMSession() = default; |
| 70 | + |
| 71 | + /// Prefill pre-tokenized input at the current position. Must be non-empty and |
| 72 | + /// fit the context window. |
| 73 | + /// |
| 74 | + /// `initial_sampling` is for implementations that sample the first generated |
| 75 | + /// token during prefill. Implementations that sample only in decode_one() may |
| 76 | + /// ignore null/default configs, but should reject unsupported non-default |
| 77 | + /// fields. |
| 78 | + /// |
| 79 | + /// ERROR CONTRACT: an error may be returned AFTER backend state has already |
| 80 | + /// mutated. On any error from prefill_tokens()/decode_one(), the session is |
| 81 | + /// POISONED -- position() may no longer agree with resident state. The |
| 82 | + /// caller must call reset() (and only proceed once it returns Ok) before any |
| 83 | + /// further prefill/decode; it must NOT retry the failed call. |
| 84 | + ET_NODISCARD virtual ::executorch::runtime::Error prefill_tokens( |
| 85 | + const std::vector<uint64_t>& tokens, |
| 86 | + const SamplingConfig* initial_sampling = nullptr) = 0; |
| 87 | + |
| 88 | + /// Decode one token from the pending state; looping reproduces a full |
| 89 | + /// generation while returning exact sampled token ids. A normal decode_one() |
| 90 | + /// runs one forward pass and is not interruptible mid-call. If stop() is |
| 91 | + /// pending, decode_one() instead returns the synthetic terminal stop result |
| 92 | + /// documented on DecodeResult without forwarding a token. |
| 93 | + /// On error the session is poisoned -- see the error contract on |
| 94 | + /// prefill_tokens() (reset() before any further use; never retry). |
| 95 | + ET_NODISCARD virtual ::executorch::runtime::Result<DecodeResult> decode_one( |
| 96 | + const SamplingConfig& sampling) = 0; |
| 97 | + |
| 98 | + /// Current logical token position for this session. |
| 99 | + virtual int64_t position() const = 0; |
| 100 | + |
| 101 | + /// Clear mutable state and position for a fresh conversation. |
| 102 | + ET_NODISCARD virtual ::executorch::runtime::Error reset() = 0; |
| 103 | + |
| 104 | + /// Request that a decode_one() loop stop. This is a TOKEN-BOUNDARY, |
| 105 | + /// cooperative stop: it is safe to call from another thread, but it does not |
| 106 | + /// abort a decode_one() that is already running. It takes effect at the next |
| 107 | + /// decode_one(), which then returns a terminal step (is_terminal set, is_eos |
| 108 | + /// false) without forwarding a new token. For that synthetic step, token_id |
| 109 | + /// is 0, text_piece is empty, and position() does not advance. The stop is |
| 110 | + /// cleared by the next prefill_tokens() or reset(). |
| 111 | + virtual void stop() = 0; |
| 112 | +}; |
| 113 | + |
| 114 | +/// Holds immutable model resources once and creates isolated sessions. How |
| 115 | +/// many sessions can be created without duplicating model weights is |
| 116 | +/// backend-dependent; see serving_capacity(). |
| 117 | +class ET_EXPERIMENTAL LLMEngine { |
| 118 | + public: |
| 119 | + virtual ~LLMEngine() = default; |
| 120 | + |
| 121 | + /// Build a new session that reuses this engine's model resources and owns |
| 122 | + /// its own mutable generation state. |
| 123 | + ET_NODISCARD virtual ::executorch::runtime::Result< |
| 124 | + std::unique_ptr<LLMSession>> |
| 125 | + create_session() = 0; |
| 126 | + |
| 127 | + /// How many physical sessions this engine can host without duplicating |
| 128 | + /// weights, plus an optional per-session memory estimate. |
| 129 | + virtual LLMServingCapacity serving_capacity() const = 0; |
| 130 | + |
| 131 | + /// Model metadata such as context length and tokenizer-specific IDs. |
| 132 | + virtual const std::unordered_map<std::string, int64_t>& metadata() const = 0; |
| 133 | +}; |
| 134 | + |
| 135 | +} // namespace executorch::extension::llm |
0 commit comments