Machine Learning com Zig — É Possível?

Machine Learning com Zig — É Possível?

Python domina o mundo do Machine Learning, mas a inferência (execução de modelos treinados) é essencialmente álgebra linear — multiplicação de matrizes, ativações e operações vetoriais. Estas operações são ideais para linguagens de baixo nível como Zig, que oferece SIMD nativo, controle de memória explícito e compilação cruzada. Zig não vai substituir Python para treinamento, mas pode ser excelente para inferência de alta performance.

Álgebra Linear em Zig

Multiplicação de Matrizes

A operação fundamental de redes neurais:

const std = @import("std");

const Matriz = struct {
    dados: []f32,
    linhas: usize,
    colunas: usize,
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator, linhas: usize, colunas: usize) !Matriz {
        return .{
            .dados = try allocator.alloc(f32, linhas * colunas),
            .linhas = linhas,
            .colunas = colunas,
            .allocator = allocator,
        };
    }

    pub fn deinit(self: Matriz) void {
        self.allocator.free(self.dados);
    }

    pub fn get(self: Matriz, i: usize, j: usize) f32 {
        return self.dados[i * self.colunas + j];
    }

    pub fn set(self: *Matriz, i: usize, j: usize, val: f32) void {
        self.dados[i * self.colunas + j] = val;
    }

    /// Multiplicação C = A * B com SIMD
    pub fn multiplicar(a: Matriz, b: Matriz, c: *Matriz) void {
        std.debug.assert(a.colunas == b.linhas);
        std.debug.assert(c.linhas == a.linhas and c.colunas == b.colunas);

        const vec_len = 8;

        for (0..a.linhas) |i| {
            for (0..b.colunas) |j| {
                var soma_vec: @Vector(vec_len, f32) = @splat(0.0);
                var k: usize = 0;

                while (k + vec_len <= a.colunas) : (k += vec_len) {
                    const a_vec: @Vector(vec_len, f32) = a.dados[i * a.colunas + k ..][0..vec_len].*;

                    // Coletar coluna j de B (stride access)
                    var b_vec: @Vector(vec_len, f32) = undefined;
                    inline for (0..vec_len) |v| {
                        b_vec[v] = b.dados[(k + v) * b.colunas + j];
                    }

                    soma_vec += a_vec * b_vec;
                }

                var soma = @reduce(.Add, soma_vec);
                while (k < a.colunas) : (k += 1) {
                    soma += a.get(i, k) * b.get(k, j);
                }
                c.set(i, j, soma);
            }
        }
    }
};

Funções de Ativação

/// Funções de ativação vetorizadas
fn relu(dados: []f32) void {
    const vec_len = 8;
    const zero: @Vector(vec_len, f32) = @splat(0.0);

    var i: usize = 0;
    while (i + vec_len <= dados.len) : (i += vec_len) {
        const v: @Vector(vec_len, f32) = dados[i..][0..vec_len].*;
        dados[i..][0..vec_len].* = @max(v, zero);
    }
    while (i < dados.len) : (i += 1) {
        dados[i] = @max(dados[i], 0);
    }
}

fn sigmoid(dados: []f32) void {
    for (dados) |*v| {
        v.* = 1.0 / (1.0 + @exp(-v.*));
    }
}

fn softmax(dados: []f32) void {
    // Encontrar max para estabilidade numérica
    var max_val: f32 = dados[0];
    for (dados[1..]) |v| max_val = @max(max_val, v);

    var soma: f32 = 0;
    for (dados) |*v| {
        v.* = @exp(v.* - max_val);
        soma += v.*;
    }
    for (dados) |*v| v.* /= soma;
}

Rede Neural Simples

const CamadaDensa = struct {
    pesos: Matriz,
    bias: []f32,
    saida: []f32,
    ativacao: Ativacao,
    allocator: std.mem.Allocator,

    const Ativacao = enum { relu, sigmoid, softmax, nenhuma };

    pub fn init(
        allocator: std.mem.Allocator,
        entrada: usize,
        saida_dim: usize,
        ativacao: Ativacao,
    ) !CamadaDensa {
        return .{
            .pesos = try Matriz.init(allocator, entrada, saida_dim),
            .bias = try allocator.alloc(f32, saida_dim),
            .saida = try allocator.alloc(f32, saida_dim),
            .ativacao = ativacao,
            .allocator = allocator,
        };
    }

    pub fn deinit(self: CamadaDensa) void {
        self.pesos.deinit();
        self.allocator.free(self.bias);
        self.allocator.free(self.saida);
    }

    pub fn forward(self: *CamadaDensa, entrada: []const f32) []f32 {
        // saida = entrada * pesos + bias
        for (0..self.saida.len) |j| {
            var soma: f32 = self.bias[j];
            for (0..entrada.len) |i| {
                soma += entrada[i] * self.pesos.get(i, j);
            }
            self.saida[j] = soma;
        }

        // Aplicar ativação
        switch (self.ativacao) {
            .relu => relu(self.saida),
            .sigmoid => sigmoid(self.saida),
            .softmax => softmax(self.saida),
            .nenhuma => {},
        }

        return self.saida;
    }
};

Inferência de Modelos ONNX

Zig pode carregar e executar modelos treinados em Python via formato ONNX usando interop com C:

const c = @cImport({
    @cInclude("onnxruntime_c_api.h");
});

const ModeloOnnx = struct {
    env: *c.OrtEnv,
    session: *c.OrtSession,

    pub fn carregar(caminho: [*:0]const u8) !ModeloOnnx {
        var env: *c.OrtEnv = undefined;
        var session: *c.OrtSession = undefined;

        const api = c.OrtGetApiBase().*.GetApi(c.ORT_API_VERSION);

        _ = api.*.CreateEnv(c.ORT_LOGGING_LEVEL_WARNING, "zig-ml", &env);

        const options = blk: {
            var opts: *c.OrtSessionOptions = undefined;
            _ = api.*.CreateSessionOptions(&opts);
            break :blk opts;
        };

        _ = api.*.CreateSession(env, caminho, options, &session);

        return .{ .env = env, .session = session };
    }
};

Quando Usar Zig para ML

Zig Brilha Em

  • Inferência em edge/embedded: Dispositivos com pouca memória
  • Inferência de baixa latência: Sistemas de tempo real
  • Pré/pós-processamento: Preparar dados para modelos
  • Modelos customizados: Implementações especializadas
  • WASM ML: Modelos rodando no navegador

Python Continua Melhor Para

  • Treinamento de modelos
  • Experimentação e prototipagem
  • Ecossistema de bibliotecas (PyTorch, TensorFlow)
  • Visualização de dados

Conclusão

Machine Learning com Zig não é apenas possível — é uma opção excelente para cenários específicos. A combinação de SIMD nativo, controle de memória e compilação cruzada torna Zig ideal para inferência de alta performance, especialmente em edge computing e sistemas embarcados.

Conteúdo Relacionado

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.