mirror of
https://github.com/gufo-org/gufo.git
synced 2026-11-07 09:35:59 -10:00
refactor(ext): make the component cache the only continuation contract (#1026)
This commit is contained in:
1 parent
409cb6ff0e
commit
65fe68a9f3
46 files changed
+194
-456
No files matched your search
@@ -72,6 +72,8 @@ Changed lines, excluding generated files: **S** ≤ 300 · **M** 300–1,000 ·
|
||||
Cards 02–19 change no runtime behavior until card 19 wires them in and deletes
|
||||
the legacy cache (D1).
|
||||
|
||||
That was the plan; [D1](d1-transition.md) records what happened instead.
|
||||
|
||||
## Dependency graph
|
||||
|
||||
```mermaid
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
**Type:** decision · **Blocks:** 19 · **Status:** agreed
|
||||
|
||||
**Outcome:** the single switch-over did not happen. Serving routed by runner
|
||||
capability while DeepSeek V4 Flash and LFM3 migrated, and both caches ran side by
|
||||
side until the legacy cache was deleted. The routing went with it.
|
||||
|
||||
## Decision
|
||||
|
||||
Serving never runs the old and the new cache side by side, and the new cache
|
||||
|
||||
+34
-11
@@ -43,9 +43,33 @@ are rejected for external extensions; keep your own implementation headers
|
||||
inside your repository. The include checks enforce the source API boundary.
|
||||
Extension code still has arbitrary native-code privileges during build and run.
|
||||
|
||||
A text runner that provides continuation implements `cache::Adapter` and links
|
||||
`gufo_ext_cache`, the public target for the component cache and its HIP backing.
|
||||
It exists only in HIP builds, so list it under a target that requires `hip`.
|
||||
Every text runner provides continuation through the component cache. It
|
||||
implements `cache::Adapter` and links `gufo_ext_cache`, the public target for the
|
||||
component cache and its HIP backing. That target exists only in HIP builds, so
|
||||
list it under a target that requires `hip`.
|
||||
|
||||
## Provide continuation
|
||||
|
||||
The cache owns every execution slot and decides what to retain, evict and write
|
||||
to disk. A runner describes its state and moves bytes on request:
|
||||
|
||||
- `CreateComponentCacheResources` returns the adapter and its backing. Build the
|
||||
backing with `hip::CreateHipComponentCacheResources`, and charge memory to the
|
||||
ledger it receives.
|
||||
- `BindComponentState` wraps one slot in the runner's `TextRunnerState`.
|
||||
- `ReconcileComponentState` runs after the cache restores rows into a slot, and
|
||||
brings the runner's own bookkeeping to that position.
|
||||
- `cache::Adapter` declares each component as append rows or whole private state
|
||||
and copies it in and out. [adapter.hpp](../src/cache/adapter.hpp) states the
|
||||
ordering and failure rules. Report positions per component, never from prompt
|
||||
length.
|
||||
|
||||
`gufo/ext/transfer_pool.hpp` and `gufo/ext/snapshot_copy.hpp` are the supported
|
||||
helpers for those copies. Set `prefix_reuse` to false only for a runner which must
|
||||
start each request cold. The cache identity must change whenever retained bytes
|
||||
stop being valid: include the model artifact, context size and every setting that
|
||||
alters state. An adapter test should restore each component and compare the
|
||||
result byte for byte with an uninterrupted run.
|
||||
|
||||
## Start with the template
|
||||
|
||||
@@ -149,14 +173,13 @@ matches must remain errors; do not steal another provider's artifacts with an
|
||||
overly broad probe.
|
||||
|
||||
For a text model, own tokenization, chat/control framing and tool syntax in the
|
||||
extension. Declare supported inputs, state/persistence/resource capabilities and
|
||||
sampling defaults accurately. Implement incremental execution, cancellation,
|
||||
state isolation, snapshot/restore and batching only under the contracts you
|
||||
advertise. To capture prefill checkpoints without ending a pass at them, declare
|
||||
`in_pass_checkpoint` and `max_in_pass_checkpoints`, implement
|
||||
`PrefillCheckpointBytes` and override the boundaries form of `PrefillThrough`;
|
||||
each capture must equal the state a pass ending at its boundary leaves. The
|
||||
single-boundary form is deprecated and captures one checkpoint per call.
|
||||
extension. Declare supported inputs, state/resource capabilities and sampling
|
||||
defaults accurately. Implement incremental execution, cancellation, state
|
||||
isolation and batching only under the contracts you advertise. To capture prefill
|
||||
checkpoints without ending a pass at them, declare `in_pass_checkpoint` and
|
||||
`max_in_pass_checkpoints`, and implement `PrefillCheckpointBytes` and
|
||||
`PrefillThrough`; each capture must equal the state a pass ending at its boundary
|
||||
leaves.
|
||||
Preserve sampler/constraint state and cache compatibility identities
|
||||
across rollback and continuation. Draft models/heads and sidecars stay with the
|
||||
target model's artifact set. Wire the CLI prompt/chat/benchmark drivers as well
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "template_model",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.10",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "template_model_register",
|
||||
"registration_target": "gufo_template_model",
|
||||
"depends": [],
|
||||
|
||||
@@ -21,9 +21,7 @@ include/gufo/ext/experimental/hip_utils.hpp
|
||||
include/gufo/ext/experimental/hipblaslt_plan_database.hpp
|
||||
include/gufo/ext/experimental/host_allocations.hpp
|
||||
include/gufo/ext/experimental/managed_alloc.hpp
|
||||
include/gufo/ext/experimental/snapshot_copy.hpp
|
||||
include/gufo/ext/experimental/snapshot_transfer.hpp
|
||||
include/gufo/ext/experimental/transfer_pool.hpp
|
||||
include/gufo/ext/experimental/weight_upload.hpp
|
||||
include/gufo/ext/gguf_identity.hpp
|
||||
include/gufo/ext/gguf_reader.hpp
|
||||
@@ -49,9 +47,11 @@ include/gufo/ext/registry.hpp
|
||||
include/gufo/ext/sampling.hpp
|
||||
include/gufo/ext/session_mode.hpp
|
||||
include/gufo/ext/sha256.hpp
|
||||
include/gufo/ext/snapshot_copy.hpp
|
||||
include/gufo/ext/speculative_verifier.hpp
|
||||
include/gufo/ext/text.hpp
|
||||
include/gufo/ext/text_contracts.hpp
|
||||
include/gufo/ext/transfer_pool.hpp
|
||||
include/gufo/ext/tts.hpp
|
||||
include/gufo/ext/types.hpp
|
||||
include/gufo/ext/utf8.hpp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 0,
|
||||
"minor": 1,
|
||||
"minor": 2,
|
||||
"revision_base": 0
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Component cache is the only continuation contract
|
||||
|
||||
Breaking: series 0.2 starts here, and an extension must request `>= 0.2.0`. The
|
||||
legacy continuation cache is gone, so every text runner provides continuation
|
||||
through the component cache.
|
||||
|
||||
Removed from `TextModelRunner`: `CreateState`, `UsesComponentCache`,
|
||||
`SnapshotPayloadBytes`, `Snapshot`, `SnapshotForPersistence`, `RestoreOrFork`,
|
||||
`PersistentSnapshotPayloadBytes`, `SerializePersistentSnapshot`,
|
||||
`StreamPersistentSnapshot`, `RestorePersistentSnapshot`, `SnapshotSink` and the
|
||||
single-boundary `PrefillThrough`.
|
||||
|
||||
`CreateComponentCacheResources`, `BindComponentState` and
|
||||
`ReconcileComponentState` are now pure virtual. `InPassConsumer` takes only the
|
||||
boundary index, because a capture always goes through the lease.
|
||||
|
||||
Also removed: `TextRunnerSnapshot`, `ContinuationSnapshot`,
|
||||
`TextRunnerPersistenceDescriptor`, `TextRunnerDescriptor::persistence`,
|
||||
`TextRunnerCapabilities::snapshot` and `fork`,
|
||||
`TextRunnerRamCacheOptions::kMaxEntries`, and the
|
||||
`DiskStartupStats::legacy_probe_bytes_read` and `removed_legacy_bytes` counters.
|
||||
`prefix_reuse` alone now says whether a runner retains continuations.
|
||||
|
||||
`transfer_pool.hpp` and `snapshot_copy.hpp` move from `gufo/ext/experimental/`
|
||||
to `gufo/ext/`: they are the supported helpers for a continuation adapter.
|
||||
File renamed without changes.
File renamed without changes.
Vendored
+2
-2
@@ -87,9 +87,9 @@ private:
|
||||
std::uint64_t epoch_{};
|
||||
};
|
||||
struct DiskStartupStats {
|
||||
std::uint64_t manifest_bytes_read{}, legacy_probe_bytes_read{};
|
||||
std::uint64_t manifest_bytes_read{};
|
||||
std::uint64_t dependency_stats{}, rejected_manifests{};
|
||||
std::uint64_t managed_bytes{}, removed_legacy_bytes{};
|
||||
std::uint64_t managed_bytes{};
|
||||
};
|
||||
// Host-buffer publication only; caller retains and admits immutable buffers
|
||||
// until Publish returns. Full chunks already referenced by a checkpoint can be
|
||||
|
||||
@@ -60,7 +60,7 @@ struct DurableCapture {
|
||||
cache::DiskManifest manifest;
|
||||
std::vector<cache::PersistenceSource> sources;
|
||||
};
|
||||
class ComponentSnapshot final : public TextRunnerSnapshot {
|
||||
class ComponentSnapshot final : public ContinuationSnapshot {
|
||||
public:
|
||||
std::shared_ptr<const cache::Checkpoint> checkpoint;
|
||||
std::shared_ptr<DurableCapture> durable;
|
||||
@@ -730,10 +730,10 @@ struct ComponentTextCache::Lease::Impl {
|
||||
.reused = cached != 0,
|
||||
.reused_tokens = cached};
|
||||
}
|
||||
std::unique_ptr<TextRunnerSnapshot> CaptureDurable();
|
||||
std::unique_ptr<ContinuationSnapshot> CaptureDurable();
|
||||
};
|
||||
|
||||
std::unique_ptr<TextRunnerSnapshot>
|
||||
std::unique_ptr<ContinuationSnapshot>
|
||||
ComponentTextCache::Lease::Impl::CaptureDurable() {
|
||||
if (!persist || !cache->queue || !cache->resources->copy_observed_rows ||
|
||||
purpose == SnapshotPurpose::kHistory)
|
||||
@@ -1512,7 +1512,7 @@ std::size_t ComponentTextCache::Lease::CaptureBytes(
|
||||
impl_->cache->resources->adapter->PrefillCheckpointPositions(
|
||||
impl_->slot.Execution(), boundary));
|
||||
}
|
||||
std::unique_ptr<TextRunnerSnapshot> ComponentTextCache::Lease::Capture() {
|
||||
std::unique_ptr<ContinuationSnapshot> ComponentTextCache::Lease::Capture() {
|
||||
auto& request = *impl_;
|
||||
auto& cache = *request.cache;
|
||||
auto positions =
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace gufo::server {
|
||||
using ext::TextModelRunner;
|
||||
using ext::TextPromptContext;
|
||||
using ext::TextRunnerDiskCacheOptions;
|
||||
using ext::TextRunnerSnapshot;
|
||||
using ext::TextRunnerState;
|
||||
|
||||
// The capture/backing contracts are cache-owned (RFC step 6); these aliases
|
||||
@@ -58,7 +57,7 @@ public:
|
||||
void Invalidate() noexcept;
|
||||
[[nodiscard]] std::size_t CaptureBytes() const;
|
||||
[[nodiscard]] std::size_t CaptureBytes(std::size_t boundary) const;
|
||||
[[nodiscard]] std::unique_ptr<TextRunnerSnapshot> Capture();
|
||||
[[nodiscard]] std::unique_ptr<ContinuationSnapshot> Capture();
|
||||
void PreparePrefill(std::size_t tokens);
|
||||
[[nodiscard]] ComponentCacheMetrics Metrics() const;
|
||||
[[nodiscard]] std::vector<std::size_t> PlannedCaptures(
|
||||
|
||||
@@ -12,11 +12,24 @@ namespace gufo::server {
|
||||
// The continuation bases and token id are runtime contracts (RFC step 6).
|
||||
// These aliases keep the serving spelling.
|
||||
using ext::ContinuationInputPrefix;
|
||||
using ext::ContinuationSnapshot;
|
||||
using ext::ContinuationState;
|
||||
using ext::ContinuationToken;
|
||||
using ext::PrefixInputIdentity;
|
||||
|
||||
/// Immutable continuation payload retained by the cache.
|
||||
class ContinuationSnapshot {
|
||||
public:
|
||||
ContinuationSnapshot() = default;
|
||||
virtual ~ContinuationSnapshot() = default;
|
||||
|
||||
ContinuationSnapshot(const ContinuationSnapshot&) = delete;
|
||||
ContinuationSnapshot& operator=(const ContinuationSnapshot&) = delete;
|
||||
ContinuationSnapshot(ContinuationSnapshot&&) = delete;
|
||||
ContinuationSnapshot& operator=(ContinuationSnapshot&&) = delete;
|
||||
|
||||
[[nodiscard]] virtual std::size_t PayloadBytes() const noexcept = 0;
|
||||
};
|
||||
|
||||
enum class SnapshotEventReason : std::uint8_t {
|
||||
kByteCapacity,
|
||||
kCaptureFailure,
|
||||
|
||||
@@ -131,24 +131,6 @@ ValidatedRunner ValidateRunner(std::shared_ptr<TextModelRunner> runner,
|
||||
throw std::invalid_argument(
|
||||
"text runner maximum context must be at least one token");
|
||||
}
|
||||
if (descriptor.capabilities.fork && !descriptor.capabilities.snapshot) {
|
||||
throw std::invalid_argument(
|
||||
"text runner fork capability requires snapshot support");
|
||||
}
|
||||
if (descriptor.persistence.has_value()) {
|
||||
if (!descriptor.capabilities.snapshot || !descriptor.capabilities.fork) {
|
||||
throw std::invalid_argument(
|
||||
"text runner persistence requires snapshot and fork support");
|
||||
}
|
||||
if (descriptor.persistence->compatibility_identity.empty()) {
|
||||
throw std::invalid_argument(
|
||||
"text runner persistence identity must not be empty");
|
||||
}
|
||||
if (descriptor.persistence->payload_version == 0) {
|
||||
throw std::invalid_argument(
|
||||
"text runner persistence payload version must be nonzero");
|
||||
}
|
||||
}
|
||||
|
||||
auto resources = runner->ResourceClaim();
|
||||
const auto per_state_reservation = PerStateReservationBytes(resources);
|
||||
@@ -231,9 +213,6 @@ struct TextRunnerPool::Impl {
|
||||
std::optional<TextRunnerDiskCacheOptions> disk_cache_options,
|
||||
TextRunnerRamCacheOptions ram_cache_options)
|
||||
: validated(ValidateRunner(std::move(model_runner), state_count)) {
|
||||
if (!validated.runner->UsesComponentCache())
|
||||
throw std::invalid_argument(
|
||||
"text runner does not provide a component cache adapter");
|
||||
{
|
||||
const auto resources = validated.runner->ResourceClaim();
|
||||
const auto automatic =
|
||||
@@ -450,8 +429,7 @@ struct TextRunnerPool::Request::Impl {
|
||||
} catch (...) {
|
||||
return;
|
||||
}
|
||||
if (!capabilities.prefix_reuse || !capabilities.snapshot ||
|
||||
!capabilities.fork) {
|
||||
if (!capabilities.prefix_reuse) {
|
||||
return;
|
||||
}
|
||||
if (lease->cache_hit() && !lease->restored_from_disk() &&
|
||||
@@ -566,7 +544,7 @@ struct TextRunnerPool::Request::Impl {
|
||||
snapshot_future =
|
||||
std::async(std::launch::async, [this, position, purpose] {
|
||||
CaptureBoundarySnapshotNow(position, purpose);
|
||||
return std::unique_ptr<TextRunnerSnapshot>{};
|
||||
return std::unique_ptr<ContinuationSnapshot>{};
|
||||
});
|
||||
boundary_capture = true;
|
||||
} catch (...) {
|
||||
@@ -582,8 +560,7 @@ struct TextRunnerPool::Request::Impl {
|
||||
bool reserved = false;
|
||||
try {
|
||||
const auto capabilities = runner->Descriptor().capabilities;
|
||||
if (!capabilities.prefix_reuse || !capabilities.snapshot ||
|
||||
!capabilities.fork)
|
||||
if (!capabilities.prefix_reuse)
|
||||
return;
|
||||
const auto& state = dynamic_cast<const TextRunnerState&>(lease->state());
|
||||
if (runner->CheckpointPosition(state) != position)
|
||||
@@ -600,7 +577,7 @@ struct TextRunnerPool::Request::Impl {
|
||||
reserved = lease->TryReserveSnapshot(bytes, position, true, purpose);
|
||||
if (!reserved)
|
||||
return;
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = lease->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = lease->Capture();
|
||||
if (snapshot) {
|
||||
snapshot_metrics.snapshot_bytes += lease->PublishSnapshot(
|
||||
{prefix.begin(), prefix.end()}, snapshot, true);
|
||||
@@ -637,7 +614,7 @@ struct TextRunnerPool::Request::Impl {
|
||||
: SnapshotPurpose::kHistory);
|
||||
if (!reserved)
|
||||
return;
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = lease->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = lease->Capture();
|
||||
if (snapshot) {
|
||||
snapshot_metrics.snapshot_bytes += lease->PublishSnapshot(
|
||||
{prefix.begin(), prefix.end()}, snapshot, true);
|
||||
@@ -690,12 +667,12 @@ struct TextRunnerPool::Request::Impl {
|
||||
: std::span<const std::uint8_t>{};
|
||||
}
|
||||
bool prompt_snapshot_attempted{false};
|
||||
std::shared_ptr<const TextRunnerSnapshot> prompt_snapshot;
|
||||
std::shared_ptr<const ContinuationSnapshot> prompt_snapshot;
|
||||
TextRunnerPool::Request::CommitMetrics snapshot_metrics;
|
||||
std::size_t snapshot_bytes{0};
|
||||
bool retain_snapshot{false};
|
||||
std::chrono::steady_clock::time_point snapshot_start;
|
||||
std::future<std::unique_ptr<TextRunnerSnapshot>> snapshot_future;
|
||||
std::future<std::unique_ptr<ContinuationSnapshot>> snapshot_future;
|
||||
};
|
||||
|
||||
TextRunnerPool::Request::Request() = default;
|
||||
@@ -814,8 +791,7 @@ std::size_t TextRunnerPool::Request::ShareCheckpoint(
|
||||
if (!*this || impl_->decode_ready || impl_->stopped)
|
||||
return 0;
|
||||
const auto capabilities = impl_->runner->Descriptor().capabilities;
|
||||
if (!capabilities.incremental_prefill || !capabilities.prefix_reuse ||
|
||||
!capabilities.snapshot || !capabilities.fork)
|
||||
if (!capabilities.incremental_prefill || !capabilities.prefix_reuse)
|
||||
return 0;
|
||||
// The complete prompt is retained only after decoding starts; share the
|
||||
// last prefill position instead so peers can start sooner.
|
||||
@@ -1018,7 +994,7 @@ TextPrefillStep TextRunnerPool::Request::Prefill(std::size_t max_input_tokens) {
|
||||
struct PromptCapture {
|
||||
std::size_t position;
|
||||
std::size_t bytes;
|
||||
std::unique_ptr<TextRunnerSnapshot> snapshot;
|
||||
std::unique_ptr<ContinuationSnapshot> snapshot;
|
||||
/// Its reservation keeps the request's fallback.
|
||||
bool preserve{false};
|
||||
/// The cache refused the reservation; nothing was captured.
|
||||
@@ -1027,35 +1003,30 @@ TextPrefillStep TextRunnerPool::Request::Prefill(std::size_t max_input_tokens) {
|
||||
std::vector<PromptCapture> prompt_captures;
|
||||
// In-pass checkpoint `index`, read while the runner exposes it. Optional
|
||||
// retention may refuse it; the live prefill stays valid either way.
|
||||
const auto consume = [&](std::size_t index,
|
||||
std::unique_ptr<TextRunnerSnapshot> native) {
|
||||
const auto consume = [&](std::size_t index) {
|
||||
const auto& capture = in_pass.at(index);
|
||||
if (!capture.prompt) {
|
||||
if (!native)
|
||||
impl_->CaptureInPassCheckpoint(capture.position);
|
||||
impl_->CaptureInPassCheckpoint(capture.position);
|
||||
return;
|
||||
}
|
||||
PromptCapture taken{capture.position, impl_->snapshot_bytes,
|
||||
std::move(native)};
|
||||
PromptCapture taken{capture.position, impl_->snapshot_bytes, {}};
|
||||
// Only the first target keeps the request's fallback.
|
||||
taken.preserve = prompt_captures.empty() && impl_->retain_fallback &&
|
||||
impl_->fallback_position == 0;
|
||||
if (!taken.snapshot) {
|
||||
try {
|
||||
// A checkpoint captured before it in this pass took the lease's
|
||||
// reservation and shares rows with it; reserve what this capture
|
||||
// adds now.
|
||||
taken.bytes = impl_->lease->CaptureBytes();
|
||||
const auto tokens = std::span<const TextRunnerToken>(impl_->prompt)
|
||||
.first(capture.position);
|
||||
taken.refused = !impl_->lease->TryReserveSnapshot(
|
||||
taken.bytes, capture.position, taken.preserve,
|
||||
SnapshotPurpose::kContinuation, tokens);
|
||||
if (!taken.refused)
|
||||
taken.snapshot = impl_->lease->Capture();
|
||||
} catch (...) {
|
||||
taken.snapshot.reset();
|
||||
}
|
||||
try {
|
||||
// A checkpoint captured before it in this pass took the lease's
|
||||
// reservation and shares rows with it; reserve what this capture
|
||||
// adds now.
|
||||
taken.bytes = impl_->lease->CaptureBytes();
|
||||
const auto tokens = std::span<const TextRunnerToken>(impl_->prompt)
|
||||
.first(capture.position);
|
||||
taken.refused = !impl_->lease->TryReserveSnapshot(
|
||||
taken.bytes, capture.position, taken.preserve,
|
||||
SnapshotPurpose::kContinuation, tokens);
|
||||
if (!taken.refused)
|
||||
taken.snapshot = impl_->lease->Capture();
|
||||
} catch (...) {
|
||||
taken.snapshot.reset();
|
||||
}
|
||||
prompt_captures.push_back(std::move(taken));
|
||||
};
|
||||
@@ -1297,7 +1268,6 @@ TextRunnerPool::Request::CommitMetrics TextRunnerPool::Request::Commit() {
|
||||
impl_.reset();
|
||||
return {};
|
||||
}
|
||||
const auto capabilities = impl_->runner->Descriptor().capabilities;
|
||||
std::vector<ContinuationToken> checkpoint = impl_->prompt;
|
||||
checkpoint.insert(checkpoint.end(), impl_->generated.begin(),
|
||||
impl_->generated.end());
|
||||
@@ -1313,31 +1283,22 @@ TextRunnerPool::Request::CommitMetrics TextRunnerPool::Request::Commit() {
|
||||
}
|
||||
checkpoint.resize(position);
|
||||
// As in Cancel: an unaligned prefill frontier is not kept live. The lease
|
||||
// drops a component state after publishing the prompt snapshot, which
|
||||
// reads its positions; other states are reset here.
|
||||
// drops the state after publishing the prompt snapshot, which reads its
|
||||
// positions.
|
||||
if (position % impl_->alignment != 0 &&
|
||||
position > impl_->lease->cached_tokens() &&
|
||||
position <= impl_->prefill_offset) {
|
||||
checkpoint.clear();
|
||||
}
|
||||
if (capabilities.snapshot && capabilities.fork) {
|
||||
CommitMetrics metrics = impl_->snapshot_metrics;
|
||||
metrics.snapshot_bytes += impl_->lease->Commit(
|
||||
std::move(impl_->snapshot_tokens), std::move(impl_->prompt_snapshot),
|
||||
std::move(checkpoint));
|
||||
metrics.component_cache = component_cache_metrics();
|
||||
if (metrics.component_cache) {
|
||||
metrics.disk_queued_bytes = metrics.component_cache->disk_queued_bytes;
|
||||
metrics.disk_enqueue_ms = metrics.component_cache->disk_enqueue_ms;
|
||||
}
|
||||
impl_.reset();
|
||||
return metrics;
|
||||
CommitMetrics metrics = impl_->snapshot_metrics;
|
||||
metrics.snapshot_bytes += impl_->lease->Commit(
|
||||
std::move(impl_->snapshot_tokens), std::move(impl_->prompt_snapshot),
|
||||
std::move(checkpoint));
|
||||
metrics.component_cache = component_cache_metrics();
|
||||
if (metrics.component_cache) {
|
||||
metrics.disk_queued_bytes = metrics.component_cache->disk_queued_bytes;
|
||||
metrics.disk_enqueue_ms = metrics.component_cache->disk_enqueue_ms;
|
||||
}
|
||||
|
||||
std::unique_ptr<TextRunnerSnapshot> snapshot;
|
||||
CommitMetrics metrics;
|
||||
metrics.snapshot_bytes =
|
||||
impl_->lease->Commit(std::move(checkpoint), std::move(snapshot));
|
||||
impl_.reset();
|
||||
return metrics;
|
||||
}
|
||||
@@ -1390,13 +1351,9 @@ TextRunnerPool::Request::Cancel() noexcept {
|
||||
state.Invalidate();
|
||||
}
|
||||
CommitMetrics metrics = impl_->snapshot_metrics;
|
||||
if (capabilities.snapshot && capabilities.fork) {
|
||||
metrics.snapshot_bytes += impl_->lease->Commit(
|
||||
std::move(impl_->snapshot_tokens), std::move(impl_->prompt_snapshot),
|
||||
std::move(checkpoint));
|
||||
} else {
|
||||
metrics.snapshot_bytes = impl_->lease->Commit(std::move(checkpoint));
|
||||
}
|
||||
metrics.snapshot_bytes += impl_->lease->Commit(
|
||||
std::move(impl_->snapshot_tokens), std::move(impl_->prompt_snapshot),
|
||||
std::move(checkpoint));
|
||||
metrics.component_cache = component_cache_metrics();
|
||||
if (metrics.component_cache) {
|
||||
metrics.disk_queued_bytes = metrics.component_cache->disk_queued_bytes;
|
||||
|
||||
@@ -47,10 +47,8 @@ using ext::TextRunnerDescriptor;
|
||||
using ext::TextRunnerDiskCacheOptions;
|
||||
using ext::TextRunnerMeasuredResources;
|
||||
using ext::TextRunnerNgramRows;
|
||||
using ext::TextRunnerPersistenceDescriptor;
|
||||
using ext::TextRunnerRamCacheOptions;
|
||||
using ext::TextRunnerResourceClaim;
|
||||
using ext::TextRunnerSnapshot;
|
||||
using ext::TextRunnerState;
|
||||
using ext::TextRunnerToken;
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ public:
|
||||
|
||||
[[nodiscard]] virtual std::size_t PayloadBytes() const noexcept = 0;
|
||||
|
||||
/// Stable model-owned byte payload used by persistent continuation caches.
|
||||
/// Stable model-owned byte payload. Serving does not use it; model tests
|
||||
/// compare it as a byte-exact oracle.
|
||||
[[nodiscard]] virtual std::size_t PersistentPayloadBytes() const {
|
||||
throw std::logic_error(
|
||||
"draft backend snapshot does not support persistent sizing");
|
||||
|
||||
@@ -50,24 +50,4 @@ public:
|
||||
virtual void Invalidate() noexcept = 0;
|
||||
};
|
||||
|
||||
/// Immutable model-private continuation payload.
|
||||
class ContinuationSnapshot {
|
||||
public:
|
||||
ContinuationSnapshot() = default;
|
||||
virtual ~ContinuationSnapshot() = default;
|
||||
|
||||
ContinuationSnapshot(const ContinuationSnapshot&) = delete;
|
||||
ContinuationSnapshot& operator=(const ContinuationSnapshot&) = delete;
|
||||
ContinuationSnapshot(ContinuationSnapshot&&) = delete;
|
||||
ContinuationSnapshot& operator=(ContinuationSnapshot&&) = delete;
|
||||
|
||||
[[nodiscard]] virtual std::size_t PayloadBytes() const noexcept = 0;
|
||||
/// Prefer an available state that still holds this snapshot's borrowed rows.
|
||||
/// This is an allocation hint; every state must remain able to restore it.
|
||||
[[nodiscard]] virtual bool PrefersState(
|
||||
const ContinuationState&) const noexcept {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gufo::ext
|
||||
@@ -76,55 +76,6 @@ std::vector<TextDecodeStep> TextModelRunner::DecodeBatch(
|
||||
return steps;
|
||||
}
|
||||
|
||||
std::unique_ptr<TextRunnerSnapshot> TextModelRunner::Snapshot(
|
||||
const TextRunnerState&) const {
|
||||
throw std::logic_error("runner does not support snapshots");
|
||||
}
|
||||
|
||||
std::unique_ptr<TextRunnerSnapshot> TextModelRunner::SnapshotForPersistence(
|
||||
const TextRunnerState& state) const {
|
||||
return Snapshot(state);
|
||||
}
|
||||
|
||||
std::size_t TextModelRunner::SnapshotPayloadBytes(
|
||||
const TextRunnerState&) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TextModelRunner::RestoreOrFork(TextRunnerState&,
|
||||
const TextRunnerSnapshot&) const {
|
||||
throw std::logic_error("runner does not support snapshots");
|
||||
}
|
||||
|
||||
std::size_t TextModelRunner::PersistentSnapshotPayloadBytes(
|
||||
const TextRunnerSnapshot&) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t TextModelRunner::SerializePersistentSnapshot(
|
||||
const TextRunnerSnapshot&, std::span<std::uint8_t>) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TextModelRunner::RestorePersistentSnapshot(
|
||||
TextRunnerState&, std::span<const std::uint8_t>) const {
|
||||
throw std::logic_error("runner does not support persistent snapshots");
|
||||
}
|
||||
|
||||
std::unique_ptr<ComponentCacheResources>
|
||||
TextModelRunner::CreateComponentCacheResources(cache::ResourceLedger&,
|
||||
std::size_t) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<TextRunnerState> TextModelRunner::BindComponentState(
|
||||
cache::Adapter&, cache::Slot&) const {
|
||||
throw std::logic_error("runner does not support component cache");
|
||||
}
|
||||
|
||||
void TextModelRunner::ReconcileComponentState(TextRunnerState&,
|
||||
std::size_t) const {}
|
||||
|
||||
std::shared_ptr<const sampling::TokenConstraint>
|
||||
TextModelRunner::BindConstraint(
|
||||
std::shared_ptr<const sampling::JsonConstraint> grammar) const {
|
||||
@@ -197,16 +148,6 @@ TextModelRunner::BuildReasoningBudget(
|
||||
return budget;
|
||||
}
|
||||
|
||||
void TextModelRunner::StreamPersistentSnapshot(
|
||||
const TextRunnerSnapshot& snapshot, const SnapshotSink& sink) const {
|
||||
std::vector<std::uint8_t> payload(PersistentSnapshotPayloadBytes(snapshot));
|
||||
if (SerializePersistentSnapshot(snapshot, payload) != payload.size()) {
|
||||
throw std::runtime_error(
|
||||
"persistent snapshot serialized byte count mismatch");
|
||||
}
|
||||
sink(payload);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider & Tool Registries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+11
-85
@@ -56,7 +56,6 @@ struct TextPreparedPrompt {
|
||||
|
||||
struct TextRunnerRamCacheOptions {
|
||||
static constexpr std::size_t kAutomaticMaxBytes = std::size_t{32} << 30;
|
||||
static constexpr std::size_t kMaxEntries = 128;
|
||||
std::size_t capacity_bytes{0};
|
||||
bool enabled{true};
|
||||
};
|
||||
@@ -90,8 +89,6 @@ struct TextExecutionPlan {
|
||||
struct TextRunnerCapabilities {
|
||||
bool supports_vision{false};
|
||||
bool incremental_prefill{false};
|
||||
bool snapshot{false};
|
||||
bool fork{false};
|
||||
bool final_token_advance_required{true};
|
||||
bool incremental_text_is_exact{false};
|
||||
bool multi_token_decode{false};
|
||||
@@ -106,24 +103,15 @@ struct TextRunnerCapabilities {
|
||||
/// rounded down to it, except the complete prompt, which only an
|
||||
/// unchanged retry restores; one keeps every position.
|
||||
std::size_t prefill_alignment{1};
|
||||
/// Boundaries one PrefillThrough call may capture in place. A runner that
|
||||
/// overrides only the deprecated single-boundary form keeps one.
|
||||
/// Boundaries one PrefillThrough call may capture in place.
|
||||
std::size_t max_in_pass_checkpoints{1};
|
||||
};
|
||||
|
||||
struct TextRunnerPersistenceDescriptor {
|
||||
std::vector<std::uint8_t> compatibility_identity;
|
||||
std::uint32_t payload_version{0};
|
||||
|
||||
bool operator==(const TextRunnerPersistenceDescriptor&) const = default;
|
||||
};
|
||||
|
||||
struct TextRunnerDescriptor {
|
||||
std::string model_id;
|
||||
std::string state_abi;
|
||||
std::uint32_t max_context{0};
|
||||
TextRunnerCapabilities capabilities;
|
||||
std::optional<TextRunnerPersistenceDescriptor> persistence;
|
||||
sampling::TextModelPreset sampling_preset{
|
||||
sampling::TextModelPreset::kUnspecified};
|
||||
};
|
||||
@@ -204,17 +192,6 @@ private:
|
||||
std::span<const TextRunnerToken> prefill_lookahead_;
|
||||
};
|
||||
|
||||
class TextRunnerSnapshot : public ContinuationSnapshot {
|
||||
public:
|
||||
TextRunnerSnapshot() = default;
|
||||
~TextRunnerSnapshot() override = default;
|
||||
|
||||
TextRunnerSnapshot(const TextRunnerSnapshot&) = delete;
|
||||
TextRunnerSnapshot& operator=(const TextRunnerSnapshot&) = delete;
|
||||
TextRunnerSnapshot(TextRunnerSnapshot&&) = delete;
|
||||
TextRunnerSnapshot& operator=(TextRunnerSnapshot&&) = delete;
|
||||
};
|
||||
|
||||
struct TextRunnerAdvance {
|
||||
std::reference_wrapper<TextRunnerState> state;
|
||||
TextRunnerToken token{0};
|
||||
@@ -410,15 +387,14 @@ public:
|
||||
[[nodiscard]] virtual std::string Decode(
|
||||
std::span<const TextRunnerToken> tokens) const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<TextRunnerState> CreateState()
|
||||
const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool UsesComponentCache() const { return false; }
|
||||
/// Continuation contract. The cache owns every execution slot: it creates
|
||||
/// the resources once, binds a state to each slot, and reconciles a state
|
||||
/// after restoring rows into its slot at the given position.
|
||||
[[nodiscard]] virtual std::unique_ptr<ComponentCacheResources>
|
||||
CreateComponentCacheResources(cache::ResourceLedger&, std::size_t) const;
|
||||
CreateComponentCacheResources(cache::ResourceLedger&, std::size_t) const = 0;
|
||||
[[nodiscard]] virtual std::unique_ptr<TextRunnerState> BindComponentState(
|
||||
cache::Adapter&, cache::Slot&) const;
|
||||
virtual void ReconcileComponentState(TextRunnerState&, std::size_t) const;
|
||||
cache::Adapter&, cache::Slot&) const = 0;
|
||||
virtual void ReconcileComponentState(TextRunnerState&, std::size_t) const = 0;
|
||||
[[nodiscard]] virtual bool HasDecodeFrontier(const TextRunnerState&) const {
|
||||
return true;
|
||||
}
|
||||
@@ -456,38 +432,12 @@ public:
|
||||
/// with a PrefillCheckpointBytes claim) without ending a pass there. Each
|
||||
/// capture must equal the state a pass ending at its boundary leaves. The
|
||||
/// runner invokes `consume` with each boundary's index, synchronously while
|
||||
/// that boundary is readable as the lease's component state (it must drain
|
||||
/// its captures before returning), or with the boundary's own snapshot
|
||||
/// (native snapshot runners). Override this form; the default forwards one
|
||||
/// boundary to the deprecated single-boundary form.
|
||||
using InPassConsumer =
|
||||
std::function<void(std::size_t, std::unique_ptr<TextRunnerSnapshot>)>;
|
||||
[[nodiscard]] virtual TextPrefillStep PrefillThrough(
|
||||
TextRunnerState& state, std::span<const TextRunnerToken> prompt,
|
||||
std::size_t offset, std::size_t budget,
|
||||
std::span<const std::size_t> boundaries,
|
||||
const InPassConsumer& consume) const {
|
||||
if (boundaries.size() != 1)
|
||||
throw std::logic_error("runner captures one in-pass checkpoint per call");
|
||||
bool captured = false;
|
||||
std::unique_ptr<TextRunnerSnapshot> native;
|
||||
auto step = PrefillThrough(state, prompt, offset, budget, boundaries[0],
|
||||
&native, [&] {
|
||||
captured = true;
|
||||
consume(0, nullptr);
|
||||
return std::unique_ptr<TextRunnerSnapshot>{};
|
||||
});
|
||||
if (!captured)
|
||||
consume(0, std::move(native));
|
||||
return step;
|
||||
}
|
||||
/// Deprecated: captures one boundary, into `checkpoint` or through
|
||||
/// `capture` while the boundary is readable as the lease's component
|
||||
/// state. Override the boundaries form instead.
|
||||
/// that boundary is readable as the lease's component state; it must drain
|
||||
/// its captures before returning.
|
||||
using InPassConsumer = std::function<void(std::size_t)>;
|
||||
[[nodiscard]] virtual TextPrefillStep PrefillThrough(
|
||||
TextRunnerState&, std::span<const TextRunnerToken>, std::size_t,
|
||||
std::size_t, std::size_t, std::unique_ptr<TextRunnerSnapshot>*,
|
||||
const std::function<std::unique_ptr<TextRunnerSnapshot>()>& = {}) const {
|
||||
std::size_t, std::span<const std::size_t>, const InPassConsumer&) const {
|
||||
throw std::logic_error("runner does not support in-pass checkpoints");
|
||||
}
|
||||
[[nodiscard]] virtual TextDecodeSelection SelectNext(
|
||||
@@ -508,30 +458,6 @@ public:
|
||||
}
|
||||
[[nodiscard]] virtual bool DeviceUsable() const { return true; }
|
||||
|
||||
[[nodiscard]] virtual std::size_t SnapshotPayloadBytes(
|
||||
const TextRunnerState& state) const;
|
||||
[[nodiscard]] virtual std::unique_ptr<TextRunnerSnapshot> Snapshot(
|
||||
const TextRunnerState& state) const;
|
||||
[[nodiscard]] virtual std::unique_ptr<TextRunnerSnapshot>
|
||||
SnapshotForPersistence(const TextRunnerState& state) const;
|
||||
|
||||
virtual void RestoreOrFork(TextRunnerState& state,
|
||||
const TextRunnerSnapshot& snapshot) const;
|
||||
|
||||
[[nodiscard]] virtual std::size_t PersistentSnapshotPayloadBytes(
|
||||
const TextRunnerSnapshot& snapshot) const;
|
||||
|
||||
[[nodiscard]] virtual std::size_t SerializePersistentSnapshot(
|
||||
const TextRunnerSnapshot& snapshot,
|
||||
std::span<std::uint8_t> destination) const;
|
||||
|
||||
using SnapshotSink = std::function<void(std::span<const std::uint8_t>)>;
|
||||
virtual void StreamPersistentSnapshot(const TextRunnerSnapshot& snapshot,
|
||||
const SnapshotSink& sink) const;
|
||||
|
||||
virtual void RestorePersistentSnapshot(
|
||||
TextRunnerState& state, std::span<const std::uint8_t> payload) const;
|
||||
|
||||
private:
|
||||
mutable std::mutex constraint_mutex_;
|
||||
mutable std::shared_ptr<const sampling::ConstraintVocabulary>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <gufo/ext/experimental/snapshot_copy.hpp>
|
||||
#include <gufo/ext/snapshot_copy.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define GUFO_MODELS_DEEPSEEK_V4_FLASH_CONTINUATION_ADAPTER_HPP_
|
||||
|
||||
#include <gufo/ext/adapter.hpp>
|
||||
#include <gufo/ext/experimental/transfer_pool.hpp>
|
||||
#include <gufo/ext/transfer_pool.hpp>
|
||||
|
||||
#include "src/models/deepseek_v4_flash/continuation_layout.hpp"
|
||||
#include "src/models/deepseek_v4_flash/engine.hpp"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "deepseek_v4_flash",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "deepseek_v4_flash_register",
|
||||
"registration_target": "gufo_deepseek_v4_flash",
|
||||
"depends": [],
|
||||
|
||||
@@ -135,14 +135,10 @@ public:
|
||||
throw std::invalid_argument(
|
||||
"DSpark disk cache requires a support SHA-256 fingerprint");
|
||||
}
|
||||
persistence_ = TextRunnerPersistenceDescriptor{
|
||||
.compatibility_identity = DeepSeekCompatibilityIdentity(
|
||||
artifact_fingerprint,
|
||||
use_dspark_ ? support_fingerprint : std::string{}, max_context_,
|
||||
max_draft_tokens_),
|
||||
.payload_version = DS4_SESSION_PAYLOAD_VERSION,
|
||||
};
|
||||
component_identity_ = persistence_->compatibility_identity;
|
||||
component_identity_ = DeepSeekCompatibilityIdentity(
|
||||
artifact_fingerprint,
|
||||
use_dspark_ ? support_fingerprint : std::string{}, max_context_,
|
||||
max_draft_tokens_);
|
||||
} else {
|
||||
// Without file fingerprints the loaded model is the identity; durable
|
||||
// caches require the validated artifact identity above.
|
||||
@@ -151,7 +147,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UsesComponentCache() const override { return true; }
|
||||
[[nodiscard]] std::unique_ptr<ComponentCacheResources>
|
||||
CreateComponentCacheResources(cache::ResourceLedger& ledger,
|
||||
std::size_t ram) const override {
|
||||
@@ -187,8 +182,6 @@ public:
|
||||
.capabilities =
|
||||
TextRunnerCapabilities{
|
||||
.incremental_prefill = true,
|
||||
.snapshot = true,
|
||||
.fork = true,
|
||||
.final_token_advance_required = false,
|
||||
.incremental_text_is_exact = true,
|
||||
.multi_token_decode = dspark,
|
||||
@@ -196,7 +189,6 @@ public:
|
||||
.batched_multi_token_decode_max_width = 8u,
|
||||
.prefix_reuse = true,
|
||||
},
|
||||
.persistence = persistence_,
|
||||
.sampling_preset = sampling::TextModelPreset::kDeepSeekV4Flash,
|
||||
};
|
||||
}
|
||||
@@ -318,11 +310,6 @@ public:
|
||||
return text;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TextRunnerState> CreateState() const override {
|
||||
throw std::logic_error(
|
||||
"DeepSeek execution slots are owned by the component cache");
|
||||
}
|
||||
|
||||
void PreparePrefixReuse(
|
||||
TextRunnerState& state,
|
||||
std::span<const TextRunnerToken> prefix) const override {
|
||||
@@ -723,7 +710,6 @@ private:
|
||||
std::uint32_t max_context_;
|
||||
bool use_dspark_;
|
||||
std::uint32_t max_draft_tokens_;
|
||||
std::optional<TextRunnerPersistenceDescriptor> persistence_;
|
||||
cache::Identity component_identity_;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "embedding_gemma2",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "embedding_gemma2_register",
|
||||
"registration_target": "gufo_embedding_gemma2",
|
||||
"depends": [],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "minimax_h3",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "minimax_h3_register",
|
||||
"registration_target": "gufo_minimax_h3",
|
||||
"depends": [],
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <gufo/ext/experimental/snapshot_copy.hpp>
|
||||
#include <gufo/ext/snapshot_copy.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <gufo/ext/adapter.hpp>
|
||||
#include <gufo/ext/experimental/transfer_pool.hpp>
|
||||
#include <gufo/ext/transfer_pool.hpp>
|
||||
|
||||
#include "src/models/qwen/continuation_layout.hpp"
|
||||
#include "src/models/qwen/hip/dflash.hpp"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "qwen",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "qwen_register",
|
||||
"registration_target": "gufo_qwen",
|
||||
"depends": [],
|
||||
|
||||
@@ -609,21 +609,15 @@ public:
|
||||
execution_policy_(hip::QwenExecutionPolicy::Production()) {
|
||||
if (!artifact_fingerprint.empty()) {
|
||||
const bool speculative = dflash_model_ != nullptr;
|
||||
persistence_ = TextRunnerPersistenceDescriptor{
|
||||
.compatibility_identity = QwenCompatibilityIdentity(
|
||||
artifact_fingerprint, draft_artifact_fingerprint, max_context_,
|
||||
execution_policy_, speculative, speculative_options_,
|
||||
dflash_policy_),
|
||||
.payload_version = kQwenComponentPayloadVersion,
|
||||
};
|
||||
component_identity_ = persistence_->compatibility_identity;
|
||||
component_identity_ = QwenCompatibilityIdentity(
|
||||
artifact_fingerprint, draft_artifact_fingerprint, max_context_,
|
||||
execution_policy_, speculative, speculative_options_, dflash_policy_);
|
||||
} else {
|
||||
component_identity_ =
|
||||
QwenProcessIdentity(model_.get(), dflash_model_.get(), max_context_);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UsesComponentCache() const override { return true; }
|
||||
[[nodiscard]] std::unique_ptr<ComponentCacheResources>
|
||||
CreateComponentCacheResources(cache::ResourceLedger& ledger,
|
||||
std::size_t ram) const override {
|
||||
@@ -692,8 +686,6 @@ public:
|
||||
TextRunnerCapabilities{
|
||||
.supports_vision = model_->VisionEncoder() != nullptr,
|
||||
.incremental_prefill = true,
|
||||
.snapshot = true,
|
||||
.fork = true,
|
||||
.final_token_advance_required = !speculative_enabled,
|
||||
.incremental_text_is_exact = true,
|
||||
.multi_token_decode = speculative_enabled,
|
||||
@@ -705,7 +697,6 @@ public:
|
||||
.partial_prefill_retention = true,
|
||||
.prefill_rollback = true,
|
||||
},
|
||||
.persistence = persistence_,
|
||||
.sampling_preset = sampling::TextPreset(model_->GetConfig()),
|
||||
};
|
||||
}
|
||||
@@ -847,11 +838,6 @@ public:
|
||||
return model_->GetTokenizer().Decode(tokens);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TextRunnerState> CreateState() const override {
|
||||
throw std::logic_error(
|
||||
"Qwen execution slots are owned by the component cache");
|
||||
}
|
||||
|
||||
void PreparePrefixReuse(
|
||||
TextRunnerState& state,
|
||||
std::span<const TextRunnerToken> prefix) const override {
|
||||
@@ -1085,7 +1071,6 @@ private:
|
||||
/// Rows a lookup copy of a request decoding alone may verify (DFlash).
|
||||
std::uint32_t copy_rows_{static_cast<std::uint32_t>(
|
||||
hip::QwenGpuExecutor::kMaxWideVerificationRows)};
|
||||
std::optional<TextRunnerPersistenceDescriptor> persistence_;
|
||||
cache::Identity component_identity_;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <gufo/ext/experimental/snapshot_copy.hpp>
|
||||
#include <gufo/ext/snapshot_copy.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define GUFO_MODELS_QWEN38_FLASH_NEXT_CONTINUATION_ADAPTER_HPP_
|
||||
|
||||
#include <gufo/ext/adapter.hpp>
|
||||
#include <gufo/ext/experimental/transfer_pool.hpp>
|
||||
#include <gufo/ext/transfer_pool.hpp>
|
||||
|
||||
#include "src/models/qwen38_flash_next/continuation_layout.hpp"
|
||||
#include "src/models/qwen38_flash_next/engine.hpp"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "qwen38_flash_next",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "qwen38_flash_next_register",
|
||||
"registration_target": "gufo_qwen38_flash_next",
|
||||
"depends": [],
|
||||
|
||||
@@ -234,17 +234,12 @@ public:
|
||||
// lifetime is an exact process-local identity; durable caches still
|
||||
// require the validated full artifact fingerprints below.
|
||||
if (!artifact_fingerprint.empty()) {
|
||||
persistence_ = TextRunnerPersistenceDescriptor{
|
||||
.compatibility_identity = QwenFlashNextCompatibilityIdentity(
|
||||
artifact_fingerprint, use_mtp_ ? mtp_fingerprint : std::string{},
|
||||
use_mtp_, max_context_, max_draft_tokens_,
|
||||
model_->DecodeConcurrency(),
|
||||
models::qwen38_flash_next::RopePositionPolicy(
|
||||
model_->config().rope_scaling)),
|
||||
.payload_version =
|
||||
models::qwen38_flash_next::Session::kSnapshotPayloadVersion,
|
||||
};
|
||||
component_identity_ = persistence_->compatibility_identity;
|
||||
component_identity_ = QwenFlashNextCompatibilityIdentity(
|
||||
artifact_fingerprint, use_mtp_ ? mtp_fingerprint : std::string{},
|
||||
use_mtp_, max_context_, max_draft_tokens_,
|
||||
model_->DecodeConcurrency(),
|
||||
models::qwen38_flash_next::RopePositionPolicy(
|
||||
model_->config().rope_scaling));
|
||||
} else {
|
||||
component_identity_ = QwenFlashNextProcessIdentity(
|
||||
model_.get(), max_context_, use_mtp_, max_draft_tokens_,
|
||||
@@ -252,7 +247,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UsesComponentCache() const override { return true; }
|
||||
[[nodiscard]] std::unique_ptr<ComponentCacheResources>
|
||||
CreateComponentCacheResources(cache::ResourceLedger& ledger,
|
||||
std::size_t ram) const override {
|
||||
@@ -286,8 +280,6 @@ public:
|
||||
TextRunnerCapabilities{
|
||||
.supports_vision = model_->VisionEncoder() != nullptr,
|
||||
.incremental_prefill = true,
|
||||
.snapshot = true,
|
||||
.fork = true,
|
||||
.final_token_advance_required = false,
|
||||
.incremental_text_is_exact = true,
|
||||
.multi_token_decode = use_mtp_,
|
||||
@@ -302,7 +294,6 @@ public:
|
||||
std::max<std::size_t>(1, model_->config().compress_ratio),
|
||||
.max_in_pass_checkpoints = rocm::kMaxPassCheckpoints,
|
||||
},
|
||||
.persistence = persistence_,
|
||||
.sampling_preset = sampling::TextModelPreset::kQwen38,
|
||||
};
|
||||
}
|
||||
@@ -415,11 +406,6 @@ public:
|
||||
return model_->tokenizer().Decode(tokens);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TextRunnerState> CreateState() const override {
|
||||
throw std::logic_error(
|
||||
"Flash-Next execution slots are owned by the component cache");
|
||||
}
|
||||
|
||||
void PreparePrefixReuse(
|
||||
TextRunnerState& state,
|
||||
std::span<const TextRunnerToken> prefix) const override {
|
||||
@@ -489,7 +475,6 @@ public:
|
||||
return bytes ? std::optional<std::size_t>(bytes) : std::nullopt;
|
||||
}
|
||||
|
||||
using TextModelRunner::PrefillThrough;
|
||||
[[nodiscard]] TextPrefillStep PrefillThrough(
|
||||
TextRunnerState& state, std::span<const TextRunnerToken> prompt,
|
||||
std::size_t offset, std::size_t budget,
|
||||
@@ -519,7 +504,7 @@ public:
|
||||
qfn.slot(), prefix, points,
|
||||
[&](std::size_t index) {
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
consume(index, nullptr);
|
||||
consume(index);
|
||||
capture_ms += std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - started)
|
||||
.count();
|
||||
@@ -766,7 +751,6 @@ private:
|
||||
std::uint32_t max_context_;
|
||||
bool use_mtp_;
|
||||
std::uint32_t max_draft_tokens_;
|
||||
std::optional<TextRunnerPersistenceDescriptor> persistence_;
|
||||
cache::Identity component_identity_;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "qwen3_asr",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "qwen3_asr_register",
|
||||
"registration_target": "gufo_qwen3_asr",
|
||||
"depends": [],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "qwen3_tts",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "qwen3_tts_register",
|
||||
"registration_target": "gufo_qwen3_tts",
|
||||
"depends": [],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "qwen_image_21",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "qwen_image_21_register",
|
||||
"registration_target": "gufo_qwen_image_21",
|
||||
"depends": [],
|
||||
|
||||
@@ -43,8 +43,6 @@ public:
|
||||
bool defer_idle{};
|
||||
bool in_pass{};
|
||||
bool partial{};
|
||||
/// Overrides only the deprecated single-boundary PrefillThrough.
|
||||
bool legacy{};
|
||||
std::size_t alignment{1};
|
||||
/// Every prefill step's end and in-pass checkpoint, in order.
|
||||
mutable std::vector<std::size_t> pass_ends;
|
||||
@@ -53,20 +51,17 @@ public:
|
||||
cache::Rows rows_per_chunk{4};
|
||||
std::size_t row_backing_bytes{16};
|
||||
mutable std::size_t through_calls{};
|
||||
bool UsesComponentCache() const override { return true; }
|
||||
TextRunnerDescriptor Descriptor() const override {
|
||||
return {.model_id = "component-fake",
|
||||
.state_abi = "component-fake-v1",
|
||||
.max_context = 4096,
|
||||
.capabilities = {.incremental_prefill = true,
|
||||
.snapshot = true,
|
||||
.fork = true,
|
||||
.final_token_advance_required = false,
|
||||
.prefix_reuse = true,
|
||||
.in_pass_checkpoint = in_pass,
|
||||
.partial_prefill_retention = partial,
|
||||
.prefill_alignment = alignment,
|
||||
.max_in_pass_checkpoints = legacy ? 1U : 64U}};
|
||||
.max_in_pass_checkpoints = 64U}};
|
||||
}
|
||||
TextRunnerResourceClaim ResourceClaim() const override {
|
||||
return {.state_capacity_bytes = 1ULL << 30,
|
||||
@@ -87,9 +82,6 @@ public:
|
||||
std::string Decode(std::span<const TextRunnerToken>) const override {
|
||||
return "";
|
||||
}
|
||||
std::unique_ptr<TextRunnerState> CreateState() const override {
|
||||
throw std::logic_error("legacy state must never be constructed");
|
||||
}
|
||||
// Image prompts carry their cache identity in the context.
|
||||
void SetPromptContext(
|
||||
TextRunnerState&,
|
||||
@@ -137,15 +129,11 @@ public:
|
||||
? std::optional<std::size_t>(1)
|
||||
: std::nullopt;
|
||||
}
|
||||
using TextModelRunner::PrefillThrough;
|
||||
TextPrefillStep PrefillThrough(TextRunnerState& state,
|
||||
std::span<const TextRunnerToken> prompt,
|
||||
std::size_t offset, std::size_t budget,
|
||||
std::span<const std::size_t> boundaries,
|
||||
const InPassConsumer& consume) const override {
|
||||
if (legacy)
|
||||
return TextModelRunner::PrefillThrough(state, prompt, offset, budget,
|
||||
boundaries, consume);
|
||||
++through_calls;
|
||||
auto& s = dynamic_cast<State&>(state);
|
||||
const auto count = std::min(budget, prompt.size() - offset);
|
||||
@@ -154,7 +142,7 @@ public:
|
||||
const auto head = prompt.subspan(at, boundaries[i] - at);
|
||||
s.adapter.Append(s.slot, head, head);
|
||||
at = boundaries[i];
|
||||
consume(i, nullptr);
|
||||
consume(i);
|
||||
in_pass_points.push_back(boundaries[i]);
|
||||
}
|
||||
const auto tail = prompt.subspan(at, offset + count - at);
|
||||
@@ -162,28 +150,6 @@ public:
|
||||
pass_ends.push_back(offset + count);
|
||||
return {count, offset + count == prompt.size()};
|
||||
}
|
||||
TextPrefillStep PrefillThrough(
|
||||
TextRunnerState& state, std::span<const TextRunnerToken> prompt,
|
||||
std::size_t offset, std::size_t budget, std::size_t boundary,
|
||||
std::unique_ptr<TextRunnerSnapshot>* checkpoint,
|
||||
const std::function<std::unique_ptr<TextRunnerSnapshot>()>& capture)
|
||||
const override {
|
||||
++through_calls;
|
||||
auto& s = dynamic_cast<State&>(state);
|
||||
const auto count = std::min(budget, prompt.size() - offset);
|
||||
const auto head = prompt.subspan(offset, boundary - offset);
|
||||
s.adapter.Append(s.slot, head, head);
|
||||
try {
|
||||
*checkpoint = capture();
|
||||
} catch (...) {
|
||||
checkpoint->reset();
|
||||
}
|
||||
in_pass_points.push_back(boundary);
|
||||
const auto tail = prompt.subspan(boundary, offset + count - boundary);
|
||||
s.adapter.Append(s.slot, tail, tail);
|
||||
pass_ends.push_back(offset + count);
|
||||
return {count, offset + count == prompt.size()};
|
||||
}
|
||||
TextDecodeSelection SelectNext(TextRunnerState& state,
|
||||
sampling::SamplerState&) const override {
|
||||
auto& s = dynamic_cast<State&>(state);
|
||||
@@ -255,12 +221,9 @@ void InPassCapturePreservesBoundaryAndLiveTail() {
|
||||
Prefill(cold);
|
||||
const auto expected = Generate(cold);
|
||||
cold.Invalidate();
|
||||
for (const auto [fail, legacy] :
|
||||
{std::pair{false, false}, std::pair{true, false},
|
||||
std::pair{false, true}}) {
|
||||
for (const bool fail : {false, true}) {
|
||||
auto runner = std::make_shared<Runner>();
|
||||
runner->in_pass = true;
|
||||
runner->legacy = legacy;
|
||||
TextRunnerPool pool(runner, 2);
|
||||
auto seed =
|
||||
pool.Acquire(prompt, sampling::SamplingConfig{}, {}, {}, true, 35);
|
||||
@@ -651,7 +614,7 @@ void RestoreFromBusyBorrowedSource() {
|
||||
const auto hash = runner->adapter->RecurrentHash(state.slot);
|
||||
assert(source->TryReserveSnapshot(source->CaptureBytes(), prompt.size(),
|
||||
false, SnapshotPurpose::kContinuation, {}));
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = source->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = source->Capture();
|
||||
assert(source->PublishSnapshot(prompt, snapshot, false));
|
||||
auto gauges = source->Metrics().ledger;
|
||||
assert(gauges.reserved_bytes[static_cast<unsigned>(
|
||||
@@ -683,7 +646,7 @@ void ColdAssignmentPrefersPreservedSlot() {
|
||||
runner->adapter->Append(state.slot, prompt, prompt);
|
||||
assert(source->TryReserveSnapshot(source->CaptureBytes(), prompt.size(),
|
||||
false, SnapshotPurpose::kContinuation, {}));
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = source->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = source->Capture();
|
||||
assert(source->PublishSnapshot(prompt, snapshot, false));
|
||||
(void)source->Commit(prompt, {}, prompt);
|
||||
// Idle streams are unavailable. Replacing the first slot would require
|
||||
@@ -711,7 +674,7 @@ void ImageAfterCachedTextPrefixCaptures() {
|
||||
runner->adapter->Append(state.slot, text, text);
|
||||
assert(root->TryReserveSnapshot(root->CaptureBytes(), text.size(), false,
|
||||
SnapshotPurpose::kContinuation, {}));
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = root->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = root->Capture();
|
||||
assert(root->PublishSnapshot(text, snapshot, false));
|
||||
}
|
||||
auto image = text;
|
||||
@@ -742,7 +705,7 @@ void ImageAfterCachedTextPrefixCaptures() {
|
||||
const auto hash = runner->adapter->RecurrentHash(state.slot);
|
||||
assert(lease->TryReserveSnapshot(lease->CaptureBytes(), image.size(), false,
|
||||
SnapshotPurpose::kContinuation, {}));
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = lease->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = lease->Capture();
|
||||
assert(lease->PublishSnapshot(image, snapshot, false));
|
||||
|
||||
// Model the retry after discarding a cancelled assistant: the image prompt
|
||||
@@ -1492,7 +1455,7 @@ void FailedCaptureKeepsPublishedBoundary() {
|
||||
const auto hash = runner->adapter->RecurrentHash(state.slot);
|
||||
assert(lease->TryReserveSnapshot(lease->CaptureBytes(), 40, false,
|
||||
SnapshotPurpose::kContinuation, {}));
|
||||
std::shared_ptr<const TextRunnerSnapshot> snapshot = lease->Capture();
|
||||
std::shared_ptr<const ContinuationSnapshot> snapshot = lease->Capture();
|
||||
assert(lease->PublishSnapshot({prompt.begin(), prompt.begin() + 40}, snapshot,
|
||||
false));
|
||||
snapshot.reset();
|
||||
@@ -1644,7 +1607,7 @@ void FragmentedBackingKeepsCompletePrompt() {
|
||||
const auto hash = runner->adapter->RecurrentHash(state.slot);
|
||||
assert(lease->TryReserveSnapshot(lease->CaptureBytes(), prompt.size(), false,
|
||||
SnapshotPurpose::kContinuation, {}));
|
||||
std::unique_ptr<TextRunnerSnapshot> snapshot;
|
||||
std::unique_ptr<ContinuationSnapshot> snapshot;
|
||||
try {
|
||||
snapshot = lease->Capture();
|
||||
} catch (const cache::ResourceExhausted&) {
|
||||
|
||||
@@ -220,7 +220,7 @@ struct FakeControl {
|
||||
std::uint32_t max_context{128};
|
||||
std::optional<std::size_t> stop_after;
|
||||
std::optional<std::size_t> eos_after;
|
||||
// Retained state needs snapshot capabilities; a capture observer implies it.
|
||||
// Requests share nothing unless asked; a capture observer implies it.
|
||||
bool snapshots{false};
|
||||
std::function<void()> snapshot_callback;
|
||||
[[nodiscard]] bool Retains() const {
|
||||
@@ -312,8 +312,6 @@ public:
|
||||
.capabilities =
|
||||
TextRunnerCapabilities{
|
||||
.incremental_prefill = control_->incremental_prefill,
|
||||
.snapshot = control_->Retains(),
|
||||
.fork = control_->Retains(),
|
||||
.final_token_advance_required =
|
||||
control_->final_token_advance_required,
|
||||
.incremental_text_is_exact =
|
||||
@@ -322,9 +320,8 @@ public:
|
||||
.batched_multi_token_decode =
|
||||
control_->batched_multi_token_decode,
|
||||
.batched_multi_token_decode_max_width = 4,
|
||||
.prefix_reuse = control_->prefix_reuse,
|
||||
.prefix_reuse = control_->prefix_reuse && control_->Retains(),
|
||||
},
|
||||
.persistence = std::nullopt,
|
||||
.sampling_preset = control_->sampling_preset,
|
||||
};
|
||||
}
|
||||
@@ -395,11 +392,6 @@ public:
|
||||
return text;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TextRunnerState> CreateState() const override {
|
||||
throw std::logic_error("slots are owned by the component cache");
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UsesComponentCache() const override { return true; }
|
||||
[[nodiscard]] std::unique_ptr<gufo::server::ComponentCacheResources>
|
||||
CreateComponentCacheResources(gufo::cache::ResourceLedger& ledger,
|
||||
std::size_t) const override {
|
||||
@@ -820,7 +812,7 @@ void TestParkedFollowerSurvivesLeaderCancellation() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestSharedPrefixWaitRequiresSnapshots() {
|
||||
void TestSharedPrefixWaitRequiresPrefixReuse() {
|
||||
auto control = SharedPrefixControl(false);
|
||||
control->block_prefill_label = 1;
|
||||
auto scheduler = MakeScheduler(control, 2);
|
||||
@@ -833,7 +825,7 @@ void TestSharedPrefixWaitRequiresSnapshots() {
|
||||
const auto b = follower.Wait();
|
||||
Expect(b.tokens == ExpectedTokens(1, 2) && b.shared_prefix_wait_ms == 0.0 &&
|
||||
b.prefill_tokens == prompt.size(),
|
||||
"runners without snapshots never park concurrent requests");
|
||||
"runners without prefix reuse never park concurrent requests");
|
||||
}
|
||||
|
||||
void TestParkedFollowersReserveVisibleSessions() {
|
||||
@@ -3238,7 +3230,7 @@ int main() {
|
||||
TestConcurrentSharedPrefixesPrefillOnce();
|
||||
TestLeaderCancelledBeforeFollowerIsAdmitted();
|
||||
TestParkedFollowerSurvivesLeaderCancellation();
|
||||
TestSharedPrefixWaitRequiresSnapshots();
|
||||
TestSharedPrefixWaitRequiresPrefixReuse();
|
||||
TestParkedFollowersReserveVisibleSessions();
|
||||
TestRetainedHistoryBeatsSharedPrefixWait();
|
||||
TestAwaitedCheckpointSurvivesCachePressure();
|
||||
|
||||
@@ -47,7 +47,6 @@ using gufo::server::TextRunnerDiskCacheOptions;
|
||||
using gufo::server::TextRunnerMeasuredResources;
|
||||
using gufo::server::TextRunnerPool;
|
||||
using gufo::server::TextRunnerResourceClaim;
|
||||
using gufo::server::TextRunnerSnapshot;
|
||||
using gufo::server::TextRunnerState;
|
||||
using gufo::server::TextRunnerToken;
|
||||
|
||||
@@ -188,7 +187,6 @@ public:
|
||||
TextRunnerCapabilities{
|
||||
.incremental_prefill = true,
|
||||
},
|
||||
.persistence = std::nullopt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -248,11 +246,6 @@ public:
|
||||
return text;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<TextRunnerState> CreateState() const override {
|
||||
throw std::logic_error("slots are owned by the component cache");
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UsesComponentCache() const override { return true; }
|
||||
[[nodiscard]] std::unique_ptr<gufo::server::ComponentCacheResources>
|
||||
CreateComponentCacheResources(gufo::cache::ResourceLedger& ledger,
|
||||
std::size_t) const override {
|
||||
@@ -380,17 +373,7 @@ public:
|
||||
private:
|
||||
};
|
||||
|
||||
class SnapshotRunner : public FakeRunner {
|
||||
public:
|
||||
using FakeRunner::FakeRunner;
|
||||
|
||||
[[nodiscard]] TextRunnerDescriptor Descriptor() const override {
|
||||
auto descriptor = FakeRunner::Descriptor();
|
||||
descriptor.capabilities.snapshot = true;
|
||||
descriptor.capabilities.fork = true;
|
||||
return descriptor;
|
||||
}
|
||||
};
|
||||
using SnapshotRunner = FakeRunner;
|
||||
|
||||
void TestPromptCountingValidatesCapabilitiesBeforeInference() {
|
||||
const auto stats = std::make_shared<FakeStats>();
|
||||
@@ -563,8 +546,6 @@ void TestResourceClaimsAreValidatedBeforeAllocation() {
|
||||
"invalid resource claim is rejected before state allocation");
|
||||
}
|
||||
|
||||
// A runner written against the single-boundary PrefillThrough keeps working
|
||||
// through the default boundaries form, one checkpoint per call.
|
||||
std::size_t RegularFiles(const std::filesystem::path& directory) {
|
||||
std::size_t files = 0;
|
||||
for (const auto& entry :
|
||||
|
||||
@@ -81,7 +81,7 @@ gufo_target_extensions(fixture EXTENSIONS ${{selected}})
|
||||
def add(self, name, depends=None, family=False, hip=False):
|
||||
directory = self.source / (f"src/families/test/{name}" if family else f"src/models/{name}")
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
manifest = {"schema": 1, "name": name, "version": "1.2.3", "api_version": ">= 0.1.0",
|
||||
manifest = {"schema": 1, "name": name, "version": "1.2.3", "api_version": ">= 0.2.0",
|
||||
"depends": depends or [], "platforms": ["linux", "windows"],
|
||||
"targets": {f"{name}_registration": {"requires": []}}}
|
||||
if family:
|
||||
@@ -287,7 +287,7 @@ void {name}_provide(gufo::ext::Registry& r) {{ r.AddProvider(gufo::ext::generate
|
||||
"schema": 1,
|
||||
"name": "external_model",
|
||||
"version": "1.0.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "external_model_register",
|
||||
"registration_target": "gufo_external_model",
|
||||
"depends": [],
|
||||
@@ -328,7 +328,7 @@ void external_model_register(gufo::ext::Registry&) {}
|
||||
"schema": 1,
|
||||
"name": "bad_ext",
|
||||
"version": "1.0.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "bad_ext_register",
|
||||
"registration_target": "gufo_bad_ext",
|
||||
"depends": [],
|
||||
|
||||
@@ -78,31 +78,9 @@ private:
|
||||
bool invalidated_{false};
|
||||
};
|
||||
|
||||
class FakeSnapshot final : public gufo::ext::ContinuationSnapshot {
|
||||
public:
|
||||
explicit FakeSnapshot(const gufo::ext::ContinuationState* preferred)
|
||||
: preferred_(preferred) {}
|
||||
[[nodiscard]] std::size_t PayloadBytes() const noexcept override {
|
||||
return 4096;
|
||||
}
|
||||
[[nodiscard]] bool PrefersState(
|
||||
const gufo::ext::ContinuationState& state) const noexcept override {
|
||||
return &state == preferred_;
|
||||
}
|
||||
|
||||
private:
|
||||
const gufo::ext::ContinuationState* preferred_;
|
||||
};
|
||||
|
||||
void CheckContinuationBases() {
|
||||
FakeState state;
|
||||
FakeSnapshot snapshot(&state);
|
||||
gufo::ext::ContinuationState& base = state;
|
||||
gufo::ext::ContinuationSnapshot& snapshot_base = snapshot;
|
||||
Expect(snapshot_base.PayloadBytes() == 4096, "snapshot payload bytes");
|
||||
Expect(snapshot_base.PrefersState(base), "snapshot prefers its own state");
|
||||
FakeSnapshot unrelated(nullptr);
|
||||
Expect(!unrelated.PrefersState(base), "unrelated snapshot prefers nothing");
|
||||
base.Invalidate();
|
||||
Expect(state.invalidated(), "Invalidate reaches the model implementation");
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schema": 1,
|
||||
"name": "alpha",
|
||||
"version": "1.2.3",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"entry": "alpha_register",
|
||||
"registration_target": "alpha_registration",
|
||||
"depends": [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "base",
|
||||
"kind": "library",
|
||||
"version": "0.1.0",
|
||||
"api_version": ">= 0.1.0",
|
||||
"api_version": ">= 0.2.0",
|
||||
"depends": [],
|
||||
"platforms": [
|
||||
"linux",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schema": 1, "name": "beta", "version": "0.2.0", "api_version": ">= 0.1.0",
|
||||
"schema": 1, "name": "beta", "version": "0.2.0", "api_version": ">= 0.2.0",
|
||||
"entry": "beta_register", "registration_target": "beta_registration", "depends": [],
|
||||
"platforms": ["linux", "windows"],
|
||||
"providers": [{"id": "beta", "capability": "text_generation", "keys": ["gguf:beta"], "speculative": ["other"], "target": "beta_registration", "entry": "beta_provider_register"}],
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
#include <gufo/ext/experimental/hipblaslt_plan_database.hpp>
|
||||
#include <gufo/ext/experimental/host_allocations.hpp>
|
||||
#include <gufo/ext/experimental/managed_alloc.hpp>
|
||||
#include <gufo/ext/experimental/snapshot_copy.hpp>
|
||||
#include <gufo/ext/experimental/snapshot_transfer.hpp>
|
||||
#include <gufo/ext/experimental/transfer_pool.hpp>
|
||||
#include <gufo/ext/experimental/weight_upload.hpp>
|
||||
#include <gufo/ext/snapshot_copy.hpp>
|
||||
#include <gufo/ext/transfer_pool.hpp>
|
||||
#endif
|
||||
|
||||
// This symbol and registration function must be absent from a CPU executable.
|
||||
|
||||
@@ -155,7 +155,7 @@ def check_diff(read_old, read_new, labels, old_records=None, new_records=None):
|
||||
name, title = legacy
|
||||
normalized = f"{RECORDS}/0.1/{name}.md"
|
||||
description = re.sub(r"^# [0-9]+\.[0-9]+\.[0-9]+\n\n", "", body, count=1)
|
||||
description = description.replace('`api_version: ">= 0.1.0"`',
|
||||
description = description.replace('`api_version: ">= 0.2.0"`',
|
||||
'an `api_version` floor in the active compatibility series')
|
||||
if (not body.startswith(f"# {title}\n\n")
|
||||
and new_records.get(normalized) == f"# {title}\n\n{description}"):
|
||||
|
||||
Reference in new issue
Block a user