Running AI Models Inside PHP

TYPO3 Developer Days 2026

Frank Berger

A bit about me

  • Frank Berger
  • Head of Engineering at sudhaus7.de, a label of the B-Factor GmbH, member of the code711.de network
  • Working with PHP since V3
  • Does TYPO3 since 2005
  • Lately doing stuff with AI

Goal of this talk

State of machine learning / artificial intelligence

Most is done in Python

"Reference" for ML and AI is NumPy

Hugging Face's "transformers" framework is in Python and NodeJS

So what about PHP?

There are options!

Rubix ML - native ML in PHP

If you want to go bare-bones and nitty-gritty

Training a Sentiment Model


foreach (['positive', 'negative'] as $label) {
    foreach (glob("train/$label/*.txt") as $file) {
        $samples[] = [file_get_contents($file)];
        $labels[] = $label;
    }
}
$dataset = new Labeled($samples, $labels);

$estimator = new PersistentModel(
    new Pipeline([
        new TextNormalizer(),
        new WordCountVectorizer(10000, 2, 0.4, new NGram(1, 2)),
        new TfIdfTransformer(),
        new ZScaleStandardizer(),
    ], new MultilayerPerceptron([
        new Dense(100),
        new Activation(new LeakyReLU()),
        new Dense(100),
        new Activation(new LeakyReLU()),
        new Dense(100, 0.0, false),
        new BatchNorm(),
        new Activation(new LeakyReLU()),
        new Dense(50),
        new PReLU(),
        new Dense(50),
        new PReLU(),
    ], 256, new AdaMax(0.0001))),
    new Filesystem('sentiment.rbx', true)
);
$estimator->train($dataset);
$estimator->save();
					

this runs for almost an hour

Using the Sentiment Model


$estimator = PersistentModel::load(
	new Filesystem('sentiment.rbx')
);

$text = $argv[1];
$dataset = new Unlabeled([
    [$text],
]);
$prediction = current($estimator->predict($dataset));

echo "The sentiment is: $prediction" . PHP_EOL;
					

Well it is pretty negative

PHP Extensions for faster calculations

  • ext-tensor (by RubixML, a wrapper for rubix/tensor)
  • ext-numpower - a NumPy implementation (by RubixML)
  • ext-ort - another NumPy implementation

All three use lapack(e) and OpenBLAS, both C libraries for scientific mathematics, that can be GPU accelerated!

Looking at Numpower

Python


						numpy.dot(a, b)
					

PHP


NumPower::dot( $a, $b );
					

nDArrays and GPU copy are available, as used in many Python based examples

How much faster is ext-Numpower?

Remember Woman + Monarch = Queen?

Native PHP implementation


	function distance($a,$b):float
	{
		return 1 - (dotp($a,$b) /
			sqrt(dotp($a,$a) * dotp($b,$b))
		);
	}
	// calculating the dot-product
	function dotp($a,$b):float
	{
		$products = array_map(function($da, $db) {
			return $da * $db;
		}, $a, $b);
		return (float)array_sum($products);
	}
					

NumPower implementation


function distance($a,$b) {
	return 1 - (
		NumPower::dot( $a, $b ) /
		NumPower::sqrt(
			NumPower::multiply(
				NumPower::dot( $a, $a ),
				NumPower::dot( $b, $b )
			)
		)
	);
}
					

cool cool.. but lets get to the real stuff

Enter ONNX

ONNX (Open Neural Network Exchange) is an open source standard for ML/AI models

Developed by Microsoft and Facebook in 2017 but is now part of the Linux foundation

Is intended as an universal translator, to translate from PyTorch, Tensorflow or other models

Runs in a multiplatform runtime environment, and is GPU accelerated (even on Mac Metal)!

How to find ONNX Models

https://huggingface.co/onnx-community

Running ONNX Models in PHP

ONNX models can be run in PHP using the composer req ankane/onnxruntime composer package, which will install the runtime for your platform as well

You will need FFI enabled in PHP!

Loading the model


$model = new OnnxRuntime\Model('models/sentiment/onnx/model.onnx');

$inputs = $model->inputs();
$outputs = $model->outputs();
$meta = $model->metadata();
					

$input will show how the input has to be formated and output how the output will be returned

As you can see it needs to be a tensor(int64)

The Problem - how to format the input?

Input data usually needs to be a series of numbers, no matter if it is text, audio or images

So, we need a Tokenizer that does that for us

composer req codewithkyrian/tokenizers

Model: onnx-community/twitter-xlm-roberta-base-sentiment-ONNX


use Codewithkyrian\Tokenizers\Tokenizer;

$model = new OnnxRuntime\Model('models/sentiment/onnx/model.onnx');
$tokenizer = Tokenizer::fromFile(
	'models/sentiment/tokenizer.json',
	'models/sentiment/tokenizer_config.json'
);

$tokens = $tokenizer->encode( $argv[1] );
$result = $model->predict([
	'input_ids'=>[$tokens->ids,1],
	'attention_mask'=>[[1],[1]]
]);
$sentiment = $result['logits'][0]
natsort($sentiment);
print_r($sentiment);

$map = ['negative','neutral','positive'];
$lastkey = array_key_last( $sentiment );
echo $argv[1]." : ".$map[$lastkey];
					

Usually, but not always

Usually the models on Huggingface are build with HF's transformers framework, which provide tokenizer configurations

But there are groups or projects which ship their own Python framework

Like in the following example

Something more advanced than just text

Model: onnx-community/KittenTTS-Mini-v0.8-ONNX


$model = new OnnxRuntime\Model('models/tts/onnx/model.onnx');

$voices = json_decode( file_get_contents( "voices.json" ), true );
$style = $voices['expr-voice-4-m'][0];

$tokenizer = Tokenizer::fromFile(
	'models/kitten-tokenizer.json',
	'models/kitten-tokenizer_config.json',
);

$text = ipatranscribe($argv[1]);
$tokens = $tokenizer->encode($text);

$result = $model->predict(
	[
		'input_ids'=>[ array_merge( $tokens->ids, [ 10,0 ] ) ],
		'style'=>[$style],
		'speed'=>[1.0],
	]
);

write_wav( 'output.wav', $result['waveform'], 24000, 1 );

printf( "wrote output.wav - %.2f s\n",
	count( $result['waveform'] ) / 24000 );
					

Lots of Boilerplate..

The Python examples look much simpler...

well.. Transformers PHP

composer req codewithkyrian/transformers

The sentiment example again


$input = $argv[1];
use function Codewithkyrian\Transformers\Pipelines\pipeline;
$pipe = pipeline('sentiment-analysis');
$out = $pipe($input);
echo $input." : ".$out['label'] . PHP_EOL;
					

input = sys.argv[1]
from transformers import pipeline
pipe = pipeline("sentiment-analysis")
out = pipe(input)
print(f"{input} : {out[label]}")
					

It will download and run ONNX Models and is analog to how the Python Library works

Transformers with custom model


use Codewithkyrian\Transformers\Models\Auto\AutoModel;
use Codewithkyrian\Transformers\PreTrainedTokenizers\AutoTokenizer;

$input = $argv[1];

$modelName = 'onnx-community/twitter-xlm-roberta-base-sentiment-ONNX';
$tokenizer = AutoTokenizer::fromPretrained($modelName);
$encodedInput = $tokenizer($input);
$model = AutoModel::fromPretrained($modelName);
$output = $model($encodedInput);

$result = $output['logits']->toArray();
$map = ['negative','neutral','positive'];
// ...
					

A fast image description example


use function Codewithkyrian\Transformers\Pipelines\pipeline;

$captioner = pipeline('image-to-text');
$result = $captioner($argv[1]);
print_r($result);
					
a laptop computer sitting on top of a wooden table
a mountain range with a mountain
a man with a beard and a red tie sitting at a laptop

We're not done yet..

ext-llama & llama.cpp

Rasmus Lerdorf himself (Father of PHP) wrote a PHP extension to run GGUF models via llama.cpp

GPU accelerated (CUDA and Mac Metal)

Getting info from the model


$modelfile = 'models/gemma-4-E4B-it-ultra-uncensored-heretic-Q4_K_M.gguf';
$model = new Llama\Model(realpath($modelfile));
print_r([
	$model->desc(),              // "llama 3B Q4_K - Medium"
	$model->size(),              // model file size in bytes
	$model->nParams(),           // parameter count
	$model->nEmbd(),             // embedding dimensions
	$model->nLayer(),            // layer count
	$model->chatTemplate(),      // built-in Jinja chat template, or null
	$model->meta('general.name'),// read GGUF metadata by key
]);
					

Running the Model - complete interface


$modelfile = 'models/gemma-4-E4B-it-ultra-uncensored-heretic-Q4_K_M.gguf';
$model = new Llama\Model(realpath($modelfile));
$ctx = new Llama\Context($model, [
	'n_ctx' => 2048,
	'n_gpu_layers' => -1, //  (-1=all, 0=CPU only)
]);
print( $ctx->complete(
	$argv[1],
	['max_tokens' => 2048]
));
					

Running the Model - chat interface


$modelfile = 'models/gemma-4-E4B-it-ultra-uncensored-heretic-Q4_K_M.gguf';
$model = new Llama\Model(realpath($modelfile));
$ctx = new Llama\Context($model, [
	'n_ctx' => 2048,
	'n_gpu_layers' => -1, // (-1=all, 0=CPU only)
]);
print( $ctx->chat([
		['role' => 'system','content' => 'You are an aspiring writer'],
		['role' => 'user', 'content' => $argv[1]]
	],
	['max_tokens' => 2048]
));
					

Running the Model - stream interface


$modelfile = 'models/gemma-4-E4B-it-ultra-uncensored-heretic-Q4_K_M.gguf';
$model = new Llama\Model(realpath($modelfile));
$ctx = new Llama\Context($model, [
	'n_ctx' => 2048,
	'n_gpu_layers' => -1, // (-1=all, 0=CPU only)
]);
foreach( $ctx->stream($argv[1], ['max_tokens' => 2048])
		as $chunk) {
	print($chunk);
	flush();
}
					

Non LLM models


$modelfile = 'models/snowflake-arctic-embed-l-v2.0.F16.gguf';

$model = new Llama\Model(realpath($modelfile));
$ctx = new Llama\Context($model, [
	'n_ctx' => 2048,
	'n_gpu_layers' => -1,
	'embeddings'=>true,
]);
$dimensions = $model->nEmbd();
$woman = $ctx->embed('woman');
$monarch = $ctx->embed('monarch');
$queen = $ctx->embed('queen');
// ... you know the rest
					

Further / Used resources

  • https://transformers.codewithkyrian.com/
  • https://github.com/krakjoe/ort ext-ort
  • https://github.com/RubixML
  • https://github.com/codename-hub/php-parquet - php extension for the parquet file format (used for large datasets)
  • https://github.com/kjdev/php-ext-snappy - snappy compression (often used for large datasets)
  • https://github.com/ankane/onnxruntime-php - onnx runtime for php
  • https://github.com/rlerdorf/ext-llama ext-llama
  • https://huggingface.co/docs/huggingface_hub/guides/cli huggingface cli

Conclusions

You absolutely can do ML/AI in PHP

But you have to read Python to understand the examples

And like the whole topic, it is constantly evolving

Caveat
The ecosystem of php extensions needs to be maintained and further developed. And some things are missing that would be beneficial (libsndfile for example, a new version of ext-opencv )

What are your questions?

Thank you, I am here all weekend

Fediverse: @foppel@phpc.social | Slack @FoppelFB
GitHub @FoppelFB | fberger@sudhaus7.de | https://sudhaus7.de/