PML / Getting Started
Getting Started

Installation & Quick Start

Get PML running in minutes. You need PHP 8.2+, Composer, and a Linux x86-64 host with OpenBLAS.

Requirements

DependencyVersionNotes
PHP8.2+FFI extension must be enabled (ffi.enable=true)
Composer2.xPSR-4 autoloading
GCC11+AVX2 + OpenMP + C11
OpenBLAS0.3+libopenblas-dev
LAPACKEanyliblapacke-dev
Linux x86-64AVX2 CPU required (Intel Haswell / AMD Ryzen+)
Redis6+Only for the AutoML platform (core/), not for src/

Installation

# 1. Install system dependencies (Debian/Ubuntu)
sudo apt install gcc libopenblas-dev liblapacke-dev php8.2-cli php8.2-ffi

# 2. Clone the repo
git clone https://github.com/yourorg/pml-framework.git
cd pml-framework

# 3. Install PHP dependencies
composer install

Building libtensor.so

Important quant.c is compiled into a separate libquant.so — do NOT include it in the libtensor.so build command.
cd src/Lib

gcc -O3 -march=native -mtune=native -mfma \
    -fno-math-errno -funsafe-math-optimizations \
    -fopenmp -funroll-loops -fomit-frame-pointer \
    -D_GNU_SOURCE -shared -fPIC \
    -o libtensor.so.7 \
    tensor.c dataset_io.c inference.c autograd.c graph.c tokenizer.c \
    -lopenblas -llapacke -lm

ln -sf libtensor.so.7 libtensor.so

Verify the build:

nm -D libtensor.so | grep tensor_ | head -20
# Should show: tensor_create, tensor_matmul, tensor_softmax, tensor_col_stats, etc.

Building libquant.so

cd src/Lib

gcc -O3 -march=native -mtune=native -mfma \
    -fno-math-errno -funsafe-math-optimizations \
    -fopenmp -funroll-loops -fomit-frame-pointer \
    -D_GNU_SOURCE -shared -fPIC \
    -o libquant.so.1 quant.c \
    -lopenblas -lm

ln -sf libquant.so.1 libquant.so
nm -D libquant.so | grep qweight_
# Should show: qweight_quantize, qweight_dequantize, qweight_linear, qweight_memory, qweight_free

Building libvision.so

cd src/Lib

gcc -O3 -march=native -mtune=native -mfma \
    -fopenmp -funroll-loops -D_GNU_SOURCE -shared -fPIC \
    -o libvision.so \
    vision_core.c vision_io.c vision_transform.c vision_augment.c \
    vision_detect.c vision_segment.c vision_feature.c vision_model.c \
    -lopenblas -lm -ljpeg -lpng

ln -sf libvision.so libvision.so

Verification

# Quick smoke test
php -r "
require 'vendor/autoload.php';
use Pml\Lib\TensorEngine;
use Pml\Tensor;

\$a = Tensor::randomNormal([64, 128]);
\$b = Tensor::randomNormal([128, 32]);
\$c = \$a->matmul(\$b);
echo \$c->shape()[0] . 'x' . \$c->shape()[1] . PHP_EOL;
// → 64x32
"

Quick Start: Classic ML

Train a GBDT classifier on tabular data:

<?php
require 'vendor/autoload.php';

use Pml\Dataset;
use Pml\Estimators\Classifiers\GBDTClassifier;
use Pml\Transformers\StandardScaler;
use Pml\Pipeline;

// Load dataset from CSV (mmap — no PHP heap allocation)
$dataset = Dataset::fromCsv('data/iris.csv', labelColumn: 'species');
[$train, $test] = $dataset->stratifiedSplit(0.8);

// Build pipeline: scaler + classifier
$pipeline = new Pipeline([
    new StandardScaler(),
    new GBDTClassifier(
        estimators: 200,
        learningRate: 0.1,
        maxDepth: 5,
    ),
]);

$pipeline->train($train);
$predictions = $pipeline->predict($test);

// Accuracy
$correct = 0;
foreach ($test->labels() as $i => $label) {
    if ($predictions[$i] === $label) $correct++;
}
echo "Accuracy: " . ($correct / count($test)) * 100 . "%\n";

Quick Start: Neural Network

Build and train a deep MLP with the Sequential API:

<?php
require 'vendor/autoload.php';

use Pml\Dataset;
use Pml\NeuralNetwork\Sequential;
use Pml\NeuralNetwork\Layers\Dense;
use Pml\NeuralNetwork\Layers\Dropout;
use Pml\NeuralNetwork\Layers\BatchNormalization;
use Pml\NeuralNetwork\Layers\ReLU;
use Pml\NeuralNetwork\Layers\Softmax;
use Pml\NeuralNetwork\Optimizers\AdamW;
use Pml\Losses\CategoricalCrossEntropy;
use Pml\Transformers\StandardScaler;
use Psr\Log\NullLogger;

$dataset = Dataset::fromCsv('data/mnist_train.csv', labelColumn: 'label');
[$train, $val] = $dataset->stratifiedSplit(0.9);

$scaler = new StandardScaler();
$train = $scaler->fitTransform($train);
$val   = $scaler->transform($val);

$model = new Sequential(
    layers: [
        new Dense(784, 512),
        new BatchNormalization(),
        new ReLU(),
        new Dropout(0.3),
        new Dense(512, 256),
        new ReLU(),
        new Dropout(0.2),
        new Dense(256, 10),
        new Softmax(),
    ],
    lossFn: new CategoricalCrossEntropy(),
    optimizer: new AdamW(lr: 3e-4, weightDecay: 1e-4),
);

$model->setLogger(new YourPsrLogger());

$model->train($train,
    epochs:       30,
    batchSize:    128,
    validation:   $val,
    patience:     5,
    minDelta:     1e-4,
    clipGradNorm: 1.0,
);

// Save to directory (config.json + model.safetensors)
$model->save('checkpoints/mnist');

// Inference (reload and quantize for 4× faster decode)
$loaded = Sequential::load('checkpoints/mnist');
$loaded->quantize(groupSize: 32); // INT8 block quantization
$preds  = $loaded->predict($val);

Quick Start: LLM Inference

Run a LLaMA-style model using the built-in GQA inference engine:

<?php
require 'vendor/autoload.php';

use Pml\Inference\InferenceSession;
use Pml\Inference\ModelConfig;
use Pml\Inference\Tokenizer;

// Load model config from model directory (config.json + model.safetensors)
$config  = ModelConfig::fromFile('models/brain-slm/config.json');
$session = new InferenceSession($config);
$tok     = Tokenizer::fromFile('models/brain-slm/tokenizer.json');

$prompt  = "Explain gradient descent in one paragraph:";
$tokenIds= $tok->encode($prompt);

// Prefill + autoregressive decode with KV-cache
$session->prefill($tokenIds);

$output = '';
for ($i = 0; $i < 200; $i++) {
    $nextId = $session->decodeStep();     // 2 FFI crossings, O(1)
    if ($nextId === $tok->eosId()) break;
    $output .= $tok->decode([$nextId]);
}

echo $output;

PHP INI Settings

Required FFI must be enabled in your php.ini. For production, use ffi.enable=preload with an opcache preload script for best performance.
; php.ini
ffi.enable = true
memory_limit = 4G           ; large models need headroom
opcache.enable = 1
opcache.jit = tracing
opcache.jit_buffer_size = 256M

; Optional: preload TensorEngine cdef at server start (avoids parsing overhead)
opcache.preload = /path/to/src/preload.php
opcache.preload_user = www-data
Performance Tip Set OPENBLAS_NUM_THREADS equal to your physical core count, not the HyperThread count. For an 8-core machine: OPENBLAS_NUM_THREADS=8 php your_script.php