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
- SIMD e Vetorização em Zig — SIMD para ML
- Zig em Sistemas Embarcados — ML no edge
- WebAssembly com Zig — ML no navegador
- Otimização de Performance — Série de performance
- Interoperabilidade Zig-C — Integrar libs C