Gliner/Gpu/GlinerBackends.cs
using System;
using System.Threading.Tasks;
using Sandbox;
namespace GlinerPoc.Gpu;
/// <summary>
/// Phase 8E.1 — the narrow inference-backend contract. Exactly two
/// implementations exist (ScalarCpuBackend, GpuFp32Backend). Both consume
/// the SAME immutable GlinerEncodedRequest (produced once by the shared
/// GlinerProcessor — never tokenized twice) and return the SAME
/// GlinerDecisionResult contract; there is no semantic difference between
/// backends, only execution. The service/UI never see neural details.
/// A null result means the request was cancelled.
/// </summary>
public interface IGlinerInferenceBackend
{
string Name { get; }
bool IsReady { get; }
Task<GlinerPoc.Neural.GlinerDecisionResult> ClassifyEncodedAsync(
GlinerPoc.Preprocessing.GlinerEncodedRequest encoded, Func<bool> cancelled );
}
/// <summary>
/// Phase 8E.3 — the permanent numerical oracle behind the backend contract.
/// Wraps the validated scalar engine WITHOUT changing its math; execution
/// stays on the component worker thread (Task.RunInThreadAsync) because
/// scalar inference takes seconds and observes the cancel delegate between
/// encoder layers.
/// </summary>
public sealed class ScalarCpuBackend : IGlinerInferenceBackend
{
private readonly GlinerPoc.Neural.GlinerClassificationEngine _engine;
public string Name => "Native Scalar CPU";
public bool IsReady => _engine is not null;
public ScalarCpuBackend( GlinerPoc.Neural.GlinerClassificationEngine engine ) => _engine = engine;
public async Task<GlinerPoc.Neural.GlinerDecisionResult> ClassifyEncodedAsync(
GlinerPoc.Preprocessing.GlinerEncodedRequest encoded, Func<bool> cancelled )
{
var result = await GameTask.RunInThreadAsync( () =>
{
bool ok = _engine.TryClassifyEncoded( encoded, out var r, null, cancelled );
return ok ? r : null;
} );
return result;
}
}
/// <summary>
/// Phase 8E.4 — the validated GPU model behind the same contract. Owns the
/// GPU runtime stack (executor/render hook, runtime/shaders, model weights,
/// engine); weights initialize ONCE and stay resident — never re-uploaded per
/// request. Execution happens through the render-context executor; the
/// cancel delegate drops queued jobs / discards dispatched completions
/// (GPU kernels are never aborted mid-flight).
/// </summary>
public sealed class GpuFp32Backend : IGlinerInferenceBackend
{
private readonly GlinerGpuRuntime _runtime;
private readonly GlinerGpuModelWeights _model;
private readonly GlinerGpuClassificationEngine _engine;
public string Name => GlinerGpuClassificationEngine.BackendName;
public bool IsReady => _engine is not null;
public GlinerGpuRuntime Runtime => _runtime;
public GlinerGpuModelWeights Model => _model;
public GlinerGpuClassificationEngine Engine => _engine;
/// <summary>Persistent GPU weight bytes (diagnostics).</summary>
public long WeightBytes => _model?.WeightBytes ?? 0;
public GpuFp32Backend( GlinerGpuRuntime runtime, GlinerGpuModelWeights model,
GlinerGpuClassificationEngine engine )
{
_runtime = runtime;
_model = model;
_engine = engine;
}
public Task<GlinerPoc.Neural.GlinerDecisionResult> ClassifyEncodedAsync(
GlinerPoc.Preprocessing.GlinerEncodedRequest encoded, Func<bool> cancelled )
=> _engine.ClassifyEncodedAsync( encoded, diagnostic: false, cancelled );
public void Dispose()
{
_model?.Dispose();
_runtime?.Dispose();
}
}
// p8e1
// p8e2
// p8e3
// p8e4
// p8e5