Skip to documentation
SLOP

tiny.accy.tensor.session

Reference tiny.accy tensor session

Defined in tensor.

A way to compile tensor programs for the host CPU inside the calling process and run them directly on memory the caller supplies.

API (14)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callstensorsession
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/accy/src/tensor/session/session.zig:69

zig
/// A caller reads these descriptors to size and type the buffers it passes to a run: the value/// gives the element type, axes, byte length and required alignment of one input or output. `dims`/// points into the compiled program's own types and stays valid until that program is released.pub const Descriptor = struct {    dtype: tensor.DType,    dims: []const tensor.Dim,    byte_size: usize,    alignment: u32,};

Source: lib/accy/src/tensor/session/session.zig:20

zig
/// A caller matches on these to handle session failures: the set lists every failure a session call/// can return. Each failure maps to exactly one stable status number.pub const Error = error{    InvalidProgram,    UnsupportedVersion,    InvalidHandle,    InvalidBuffer,    TooManyPrograms,    OutOfMemory,    CompileFailed,    LaunchFailed,};

Source: lib/accy/src/tensor/session/session.zig:61

zig
/// A caller keeps this handle to name one compiled program in later calls by slot index and/// generation count. `Session.release` changes the slot's generation, so every copy of the old/// handle is refused with `error.InvalidHandle` from then on.pub const Handle = struct {    index: u32,    generation: u32,};

Source: lib/accy/src/tensor/session/session.zig:89

zig
/// A host creates a session to compile tensor programs for the CPU backend inside this process and/// run them on memory it supplies. The session runs programs forward only, with no transform that/// computes derivatives.pub const Session = struct {    allocator: std.mem.Allocator,    cpu: *CpuState,    entries: [max_programs]Entry = @splat(.{}),    /// A caller reads this field to log why the last failing call failed: the field holds the inner    /// error behind the most recent failure that went through the session's error mapping, and it    /// is for diagnostics only. Failures returned directly, such as a full table, a stale handle, a    /// buffer count mismatch or a failed allocation while describing buffers, leave it unchanged.    last_cause: ?anyerror = null,    pub fn init(allocator: std.mem.Allocator) Error!Session {        const cpu = allocator.create(CpuState) catch return error.OutOfMemory;        cpu.* = CpuState.init(allocator);        return .{ .allocator = allocator, .cpu = cpu };    }    pub fn deinit(self: *Session) void {        for (&self.entries) |*entry| {            if (entry.live) self.releaseEntry(entry);        }        self.cpu.deinit();        self.allocator.destroy(self.cpu);        self.* = undefined;    }    /// A caller passes a program's wire bytes, the versioned byte encoding of a tensor program, to    /// get a handle it can run. The call decodes the bytes, compiles the program for the CPU, and    /// loads the result into a free slot. Bad bytes return `error.InvalidProgram` or    /// `error.UnsupportedVersion`, a compile or load failure returns `error.CompileFailed` or    /// `error.OutOfMemory`, and a full table returns `error.TooManyPrograms`. The caller may free    /// `bytes` once the call returns, because decoding copies names and payloads into the program.    pub fn compile(self: *Session, bytes: []const u8) Error!Handle {        const index = self.freeIndex() orelse return error.TooManyPrograms;        var program = tensor.wire.decode(self.allocator, bytes) catch |err| {            return self.fail(decodeError(err), err);        };        errdefer program.deinit();        const cpu = self.cpu.handle();        const options = accy.executable.FragmentCompilerOptions{ .artifact_format = .cpu_object };        const compiled = tensor.lower.compileFragment(            self.allocator,            cpu,            &program,            options,        ) catch |err| return self.fail(compileError(err), err);        const fragment = accy.executable.loadFragment(            self.allocator,            cpu,            compiled,            options,        ) catch |err| return self.fail(compileError(err), err);        errdefer fragment.deinit();        const capabilities = cpu.queryCapabilities() catch |err| {            return self.fail(compileError(err), err);        };        const min_alignment = capabilities.memory.min_buffer_alignment;        const artifact_plan = compiled.artifactPlan();        const descriptors = try describe(self.allocator, &program, artifact_plan, min_alignment);        std.debug.assert(descriptors.len == program.parameters.len + program.outputs.len);        const entry = &self.entries[index];        std.debug.assert(!entry.live);        entry.* = .{            .live = true,            .generation = entry.generation,            .program = program,            .compiled = compiled,            .fragment = fragment,            .descriptors = descriptors,            .input_count = program.parameters.len,        };        return .{ .index = index, .generation = entry.generation };    }    pub fn inputDescriptors(self: *const Session, handle: Handle) Error![]const Descriptor {        const entry = &self.entries[try self.indexOf(handle)];        return entry.descriptors[0..entry.input_count];    }    pub fn outputDescriptors(self: *const Session, handle: Handle) Error![]const Descriptor {        const entry = &self.entries[try self.indexOf(handle)];        return entry.descriptors[entry.input_count..];    }    /// A caller runs a compiled program on its own input and output buffers. The input and output    /// slices follow the program's descriptors in order, and a count that does not match returns    /// `error.InvalidBuffer`. The run uses the caller's buffers in place wherever the plans allow,    /// and copies any input the program writes so the caller's inputs stay unchanged. Each output    /// must share no bytes with any input or with another output, and a buffer that breaks this or    /// the descriptor's size or alignment returns `error.InvalidBuffer`. `scratch` backs the    /// short-lived state of one launch, and a launch failure returns `error.LaunchFailed`.    pub fn invoke(        self: *Session,        handle: Handle,        scratch: std.mem.Allocator,        input_bytes: []const []const u8,        output_bytes: []const []u8,    ) Error!void {        const entry = &self.entries[try self.indexOf(handle)];        std.debug.assert(entry.live);        const output_count = entry.descriptors.len - entry.input_count;        if (input_bytes.len != entry.input_count) return error.InvalidBuffer;        if (output_bytes.len != output_count) return error.InvalidBuffer;        const bindings = binding.prepareBorrowed(            self.allocator,            self.cpu.handle(),            entry.compiled.artifactPlan(),            entry.compiled.launchPlan(),            input_bytes,            output_bytes,        ) catch |err| return self.fail(bindError(err), err);        defer bindings.deinit();        entry.fragment.submitInvocationWithOptions(scratch, bindings, .{}) catch |err| {            return self.fail(launchError(err), err);        };        entry.fragment.completeInvocationWithOptions(.{}) catch |err| {            return self.fail(launchError(err), err);        };        binding.completeBorrowed(bindings, output_bytes) catch |err| {            return self.fail(launchError(err), err);        };    }    /// Frees the compiled program, its descriptors and its decoded form. After the call the handle    /// and every descriptor slice from that program are invalid, and the handle is refused with    /// `error.InvalidHandle`.    pub fn release(self: *Session, handle: Handle) Error!void {        const entry = &self.entries[try self.indexOf(handle)];        self.releaseEntry(entry);    }    fn releaseEntry(self: *Session, entry: *Entry) void {        std.debug.assert(entry.live);        entry.fragment.deinit();        self.allocator.free(entry.descriptors);        entry.program.deinit();        entry.* = .{ .generation = entry.generation +% 1 };    }    fn indexOf(self: *const Session, handle: Handle) Error!u32 {        if (handle.index >= max_programs) return error.InvalidHandle;        const entry = &self.entries[handle.index];        if (!entry.live or entry.generation != handle.generation) return error.InvalidHandle;        return handle.index;    }    fn freeIndex(self: *const Session) ?u32 {        for (&self.entries, 0..) |*entry, index| {            if (!entry.live) return @intCast(index);        }        return null;    }    fn fail(self: *Session, public: Error, cause: anyerror) Error {        self.last_cause = cause;        return public;    }};

Source: lib/accy/src/tensor/session/session.zig:33

zig
/// A host sends these numbers to report failures across a language boundary: the codes run from 0/// for success to 8 for a failed launch. Once a number is assigned it keeps its meaning for good.pub const Status = enum(u32) {    ok = 0,    invalid_program = 1,    unsupported_version = 2,    invalid_handle = 3,    invalid_buffer = 4,    too_many_programs = 5,    out_of_memory = 6,    compile_failed = 7,    launch_failed = 8,    pub fn fromError(err: Error) Status {        return switch (err) {            error.InvalidProgram => .invalid_program,            error.UnsupportedVersion => .unsupported_version,            error.InvalidHandle => .invalid_handle,            error.InvalidBuffer => .invalid_buffer,            error.TooManyPrograms => .too_many_programs,            error.OutOfMemory => .out_of_memory,            error.CompileFailed => .compile_failed,            error.LaunchFailed => .launch_failed,        };    }};
Called byCallstest sourcelib.accy.src.tensor.session.testtest: tensor session reports stable s...tensor.lowercompileFragmentprivate sourcelib.accy.src.tensor.session.session.Sessionfailprivate sourcelib.accy.src.tensor.session.session.SessionfreeIndexprivate sourcelib.accy.src.tensor.session.sessioncompileErrorprivate sourcelib.accy.src.tensor.session.sessiondecodeError+3 moretensor.session.Sessioncompile
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallsNo direct callersprivate sourcelib.accy.src.tensor.session.session.SessionreleaseEntrytensor.session.Sessiondeinit
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallstest sourcelib.accy.src.tensor.session.testtest: tensor session reports stable s...private sourcelib.accy.src.tensor.session.session.SessionindexOftensor.session.SessioninputDescriptors
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.accy.src.tensor.session.testtest: tensor session reports stable s...executable.bindingcompleteBorrowedexecutable.bindingprepareBorrowedprivate sourcelib.accy.src.tensor.session.session.Sessionfailprivate sourcelib.accy.src.tensor.session.session.SessionindexOfprivate sourcelib.accy.src.tensor.session.sessionbindErrorprivate sourcelib.accy.src.tensor.session.sessionlaunchErrortensor.session.Sessioninvoke
Static calls · unresolved targets: 1 · external targets: 5.
Called byCallstest sourcelib.accy.src.tensor.session.testtest: tensor session reports stable s...private sourcelib.accy.src.tensor.session.session.SessionindexOftensor.session.SessionoutputDescriptors
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.accy.src.tensor.session.testtest: tensor session reports stable s...private sourcelib.accy.src.tensor.session.session.SessionindexOfprivate sourcelib.accy.src.tensor.session.session.SessionreleaseEntrytensor.session.Sessionrelease
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.accy.src.tensor.session.testexpectStatustensor.session.StatusfromError
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/accy/src/tensor/session/session.zig:16

zig
/// A caller sizes its bookkeeping by this bound on programs held at once: one session holds at most/// 64 compiled programs at a time. Compiling another program while all 64 are held returns/// `error.TooManyPrograms`.pub const max_programs: u32 = 64;

Source: lib/accy/src/tensor/root.zig:18

zig
pub const session = @import("session/root.zig");

Source: lib/accy/src/tensor/session/root.zig

zig
//! A way to compile tensor programs for the host CPU inside the calling process and run them//! directly on memory the caller supplies. A host program, possibly written in another language,//! needs to compile and run array programs through a few calls and report failures across a//! language boundary as plain numbers. A host that hands over large buffers wants them used in//! place on every run. Host code can keep a program's name after it released the program, and a//! reused slot would then run the wrong program.//!//! A table inside the host process (*session*) holds up to 64 compiled programs, which arrive as//! versioned bytes and run on the CPU backend. Each compiled program is named by its slot and a//! generation count that changes on release (*handle*), so a name kept past release is refused.//! Runs use the caller's buffers in place wherever the plans allow, and inputs the program writes//! are copied first so the caller's inputs stay unchanged. Every failure maps to one fixed number//! that never changes meaning (*status code*).//!//! - *descriptor*: the element type, axes, byte length and alignment of one program input or//!   output.const session = @import("session.zig");pub const Session = session.Session;pub const Handle = session.Handle;pub const Descriptor = session.Descriptor;pub const Error = session.Error;pub const Status = session.Status;pub const max_programs = session.max_programs;

Complete call list for tensor.session.Session.compile

8 direct calls.

Audit

Definitions15
Public names15
Members27
Version26.7.0
Revisiondaab053ee433