Skip to content

Commit 993e944

Browse files
Merge pull request #20 from CodeWithKyrian/add-qwen2-support
Add Qwen2 model support
2 parents c2c42fb + 4ec79a5 commit 993e944

14 files changed

Lines changed: 196 additions & 104 deletions

docs/getting-started.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Before installing TransformersPHP, ensure your system meets the following requir
1212
- Composer
1313
- PHP FFI extension
1414
- JIT compilation (optional)
15+
- Increased memory limit (for advanced tasks like text generation)
1516

1617
## Installation
1718

examples/pipelines/image-to-text.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
require_once './bootstrap.php';
1010

1111
ini_set('memory_limit', -1);
12-
$captioner = pipeline('image-to-text', 'Xenova/vit-gpt2-image-captioning');
13-
//$captioner = pipeline('image-to-text', 'Xenova/trocr-small-handwritten');
12+
//$captioner = pipeline('image-to-text', 'Xenova/vit-gpt2-image-captioning');
13+
$captioner = pipeline('image-to-text', 'Xenova/trocr-small-handwritten');
1414

1515
//$streamer = StdOutStreamer::make($captioner->tokenizer);
1616

examples/pipelines/text-generation.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,25 @@
1111

1212
ini_set('memory_limit', -1);
1313
//
14-
$generator = pipeline('text-generation', 'Xenova/gpt2');
14+
//$generator = pipeline('text-generation', 'Xenova/gpt2');
15+
$generator = pipeline('text-generation', 'Xenova/Qwen1.5-0.5B-Chat');
1516

1617
$streamer = StdOutStreamer::make($generator->tokenizer);
1718

1819
$messages = [
19-
['role' => 'user', 'content' => 'Hello!'],
20-
['role' => 'assistant', 'content' => 'Hi! How are you?'],
21-
['role' => 'user', 'content' => 'I am doing great. What about you?'],
20+
['role' => 'system', 'content' => 'You are a helpful assistant.'],
21+
['role' => 'user', 'content' => 'Who are you'],
2222
];
2323

24-
$output = $generator("I love going to school but I don't",
24+
$input = $generator->tokenizer->applyChatTemplate($messages, addGenerationPrompt: true, tokenize: false);
25+
26+
$output = $generator($messages,
2527
streamer: $streamer,
2628
maxNewTokens: 128,
2729
doSample: true,
28-
temperature: 0.7,
29-
repetitionPenalty: 1.3,
30-
earlyStopping: true
30+
// temperature: 0.7,
31+
// repetitionPenalty: 1.3,
32+
// earlyStopping: true
3133
);
3234

3335
//$generator = pipeline('text-generation', 'Xenova/codegen-350M-mono');

src/Models/Auto/AutoModel.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class AutoModel extends PretrainedMixin
3838
"gptj" => \Codewithkyrian\Transformers\Models\Pretrained\GPTJModel::class,
3939
"gpt_bigcode" => \Codewithkyrian\Transformers\Models\Pretrained\GPTBigCodeModel::class,
4040
"codegen" => \Codewithkyrian\Transformers\Models\Pretrained\CodeGenModel::class,
41+
"qwen2" => \Codewithkyrian\Transformers\Models\Pretrained\Qwen2Model::class,
4142
];
4243

4344
const MODEL_CLASS_MAPPINGS = [

src/Models/Auto/AutoModelForCausalLM.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class AutoModelForCausalLM extends PretrainedMixin
1313
'gpt_bigcode' => \Codewithkyrian\Transformers\Models\Pretrained\GPTBigCodeForCausalLM::class,
1414
'codegen' => \Codewithkyrian\Transformers\Models\Pretrained\CodeGenForCausalLM::class,
1515
'trocr' => \Codewithkyrian\Transformers\Models\Pretrained\TrOCRForCausalLM::class,
16-
16+
'qwen2' => \Codewithkyrian\Transformers\Models\Pretrained\Qwen2ForCausalLM::class
1717
];
1818

1919
const MODEL_CLASS_MAPPINGS = [

src/Models/ModelArchitecture.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Codewithkyrian\Transformers\Models\Pretrained\PretrainedModel;
1010
use Codewithkyrian\Transformers\Utils\GenerationConfig;
1111
use Codewithkyrian\Transformers\Utils\Tensor;
12+
use Interop\Polite\Math\Matrix\NDArray;
1213

1314
enum ModelArchitecture: string
1415
{
@@ -34,7 +35,7 @@ public function runBeam(PretrainedModel $model, array &$beam): array
3435
{
3536
return match ($this) {
3637
self::DecoderOnly => $this->decoderRunBeam($model, $beam),
37-
self::Seq2SeqLM, self::Vision2Seq => $this->seq2seqRunBeam($model, $beam),
38+
self::Seq2SeqLM, self::Vision2Seq => $this->seq2seqRunBeam($model, $beam),
3839
default => throw new \Error('This model type does not support beam search'),
3940
};
4041
}
@@ -114,10 +115,11 @@ protected function decoderRunBeam(PretrainedModel $model, array &$beam): array
114115
// 1. Prepare
115116
$modelInputs = [
116117
'input_ids' => $beam['model_input_ids'],
117-
'attention_mask' => new Tensor($attnMaskData, shape: [1, $attnMaskLength]),
118+
'attention_mask' => new Tensor($attnMaskData, NDArray::int64, [1, $attnMaskLength]),
118119
'past_key_values' => $beam['prev_model_outputs']['past_key_values'] ?? null,
119120
];
120121

122+
121123
// 2. Run
122124
$output = $model->forward($modelInputs);
123125

@@ -155,7 +157,7 @@ protected function decoderStartBeams(
155157
$attnMask = null;
156158
if ($inputsAttentionMask !== null) {
157159
$attnMask = $inputsAttentionMask[$beamId];
158-
$attnMask->reshape([1, ...$attnMask->shape()]);
160+
$attnMask = $attnMask->reshape([1, ...$attnMask->shape()]);
159161
} else {
160162
$attnMask = $model->prepareAttentionMask($tokens);
161163
}
@@ -189,8 +191,7 @@ protected function decoderStartBeams(
189191
protected function decoderUpdatebeam(array &$beam, int $newTokenId): void
190192
{
191193
$beam['output_token_ids'][] = $newTokenId;
192-
193-
$beam['model_input_ids'] = new Tensor([$newTokenId], shape: [1, 1]);
194+
$beam['model_input_ids'] = new Tensor([$newTokenId], NDArray::int64, [1, 1]);
194195
}
195196

196197
/**
@@ -221,6 +222,14 @@ protected function decoderForward(PretrainedModel $model, array $modelInputs): a
221222
$model->preparePositionIds($inputNames, $decoderFeeds, $useCacheBranch);
222223
$model->addPastKeyValues($decoderFeeds, $pastKeyValues);
223224

225+
// The initial past key values should have a shape of 0 in one of the dimensions, which
226+
// is the sequence length. However, I haven't found a way to pass a tensor with a shape of 0
227+
// to the model, so I'm using a sequence length of 1 instead for the first step, and then
228+
// offsetting the sequence length by 1 for the subsequent steps. This is a workaround for now.
229+
$prevSequenceLength = $decoderFeeds['past_key_values.0.key']->shape()[2];
230+
$attnMaskLength = $prevSequenceLength == 1 ? 1 : $prevSequenceLength + 1;
231+
$decoderFeeds['attention_mask'] = Tensor::ones([1, $attnMaskLength], dtype: NDArray::int64);
232+
224233
$decoderResults = $model->runSession($model->session, $decoderFeeds);
225234

226235
$logits = $decoderResults['logits'];

src/Models/Pretrained/PretrainedModel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ public function preparePositionIds(array $inputNames, array &$feeds, bool $useCa
399399

400400
if ($useCacheBranch) {
401401
// TODO: Fix this
402-
$feeds['position_ids'] = $feeds['position_ids']->slice(null, -1)->unsqueeze(-1);
402+
$feeds['position_ids'] = $feeds['position_ids']->slice(null, -1);
403403
}
404404
}
405405

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
namespace Codewithkyrian\Transformers\Models\Pretrained;
7+
8+
class Qwen2ForCausalLM extends Qwen2PreTrainedModel
9+
{
10+
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
namespace Codewithkyrian\Transformers\Models\Pretrained;
7+
8+
class Qwen2Model extends Qwen2PreTrainedModel
9+
{
10+
11+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
6+
namespace Codewithkyrian\Transformers\Models\Pretrained;
7+
8+
use Codewithkyrian\Transformers\Models\ModelArchitecture;
9+
use Codewithkyrian\Transformers\Utils\AutoConfig;
10+
use Codewithkyrian\Transformers\Utils\GenerationConfig;
11+
use OnnxRuntime\InferenceSession;
12+
13+
/**
14+
* The bare Qwen2 Model outputting raw hidden-states without any specific head on top.
15+
*/
16+
class Qwen2PreTrainedModel extends PreTrainedModel
17+
{
18+
protected int $numHeads;
19+
protected int $numLayers;
20+
protected int $dimKv;
21+
22+
public function __construct(
23+
AutoConfig $config,
24+
InferenceSession $session,
25+
public ModelArchitecture $modelArchitecture,
26+
public GenerationConfig $generationConfig
27+
)
28+
{
29+
parent::__construct($config, $session, $modelArchitecture);
30+
31+
// config doesn't contain pad_token_id, so we assume it is the eos_token_id
32+
$this->config['pad_token_id'] = $this->config['eos_token_id'];
33+
$this->config->padTokenId = $this->config['eos_token_id'];
34+
35+
$this->numHeads = $this->config['num_key_value_heads'] ?? $this->config['num_attention_heads'];
36+
$this->numLayers = $this->config['num_hidden_layers'];
37+
$this->dimKv = $this->config['hidden_size'] / $this->config['num_attention_heads'];
38+
}
39+
}

0 commit comments

Comments
 (0)