DRU-RE-Yehia is a reproducible 4-bit QLoRA system for directed Arabic relation
extraction with Navid-AI/Yehia-7B-preview. It converts each relation decision
into a row-specific Arabic multiple-choice problem and trains Yehia to return
one Arabic option code. The model does not have to reproduce a long relation
phrase or generate explanatory text.
This repository contains the clean training, dataset, audit, inference, and
release-staging source, the selected best inference adapter, and the latest
complete resumable Trainer checkpoint. It intentionally excludes downloaded
base weights, local dataset JSONL files, all older numbered checkpoints, caches,
notebooks, validation/working prediction dumps, and rolling logs. The reviewed
official submission predictions are included because they are a release asset.
Release status: all three epochs and the final 1,687-row validation are
complete at global step 2,943. The selected step-2,750 best adapter is staged
at the repository root and passed the complete local release verifier: 448/448
tensors are finite and nonzero, all 41 worked classes are covered, and every
manifest hash matches. The full latest step-2,943 optimizer/scheduler/RNG
checkpoint is preserved separately under latest_checkpoint/. The released
best adapter has also produced and validated the complete 2,074-row official
submission.zip. Hugging Face publication provenance is written to
release/hf_remote_verification.json by the guarded publisher.
At a glance
Item
Selected value
Task
Directed Arabic relation extraction
Direction
[الأول] → [الثاني]
Positive relations
40
Negative class
لا توجد علاقة / metadata label no_relation
Base model
Navid-AI/Yehia-7B-preview
Prompt version
yehia_re_ar_v3_one_token_choice
Output contract
One displayed Arabic option code
SFT dataset
U4RASD/Yehia-RE-SFT
Adapter repository
U4RASD/DRU-RE-Yehia
Quantization
4-bit NF4, double quantization, BF16 compute
PEFT targeting
target_modules="all-linear"
LoRA
rank 16, alpha 32, dropout 0.05, RS-LoRA
Training length
3 epochs / 2,943 optimizer steps on this dataset
Effective batch
16 = physical batch 4 × gradient accumulation 4
Best-checkpoint metric
Validation positive micro F1
Scheduled evaluation
All 1,687 validation rows every 250 optimizer steps
After a successful full release-staging operation, the Hugging Face model root
additionally contains PEFT adapter/tokenizer files and inference_config.json;
final run evidence is placed under release/, and the complete latest Trainer
state is placed under latest_checkpoint/. The Azure copy intentionally omits
the root adapter tensor and the entire checkpoint directory. Full Yehia base
weights are never copied to either destination.
Task definition
Each example contains an Arabic sentence and two entities. The first entity is
surrounded by:
[الأول] ... [/الأول]
The second entity is surrounded by:
[الثاني] ... [/الثاني]
The question is always directional: what relation holds from the first marked
entity to the second marked entity? A reverse relation is not interchangeable.
If no compatible displayed relation is expressed in that direction, the correct
choice is لا توجد علاقة.
Canonical dataset labels such as PERS, ORG, employee_of, or
Affiliation.employee_of remain metadata. They are never rendered inside the
system, user, or assistant message.
Final prompt
Exact system message
text
1أنت مصنّف علاقات. حدّد العلاقة الموجّهة من الكيان [الأول] إلى الكيان [الثاني].
2اختر علاقة واحدة من القائمة تناسب معنى الجملة ونوعي الكيانين.
3إن لم تنطبق أي علاقة فاختر «لا توجد علاقة».
4أجب برمز الخيار فقط: حرف عربي واحد دون رقم أو شرح.
Exact user-message template
text
1الجملة:
2{الجملة العربية مع [الأول]...[/الأول] و[الثاني]...[/الثاني]}
34نوع الأول: {النوع العربي للأول}
5نوع الثاني: {النوع العربي للثاني}
67الخيارات:
8أ) {علاقة عربية متوافقة}
9ب) {علاقة عربية متوافقة أخرى}
10...
11ي) لا توجد علاقة
1213رمز الإجابة:
The number and letters vary by row. The option-code inventory is:
أ، ب، ج، د، ه، و، ز، ح، ط، ي
All ten codes were verified to be exactly one token under Yehia's native
tokenizer when preceded by the assistant-template space. A row uses only the
prefix required by its number of displayed options.
Exact assistant contract
The assistant content is one code and nothing else, for example:
ز
The code is interpreted through that row's displayed list. It is not a global
relation ID. Because لا توجد علاقة is always last, its code changes with the
number of compatible positive options in the row.
No relation-class tokens were added to Yehia's vocabulary. The letters
أ ب ج د ه و ز ح ط ي already exist in the native Yehia tokenizer and each was
verified to occupy exactly one token in the assistant position. They are
row-local addresses, not global class IDs.
Three arrays on every transformed row have the same length and order:
The first array is shown to and emitted by the model. The second array is the
Arabic meaning shown in the prompt. The third array is metadata used to recover
the canonical dataset class. Index i ties them together.
For example, two different rows may contain:
Row
Letter أ means on that row
Gold output
Decoded class
train_5482
الأول هو قائد الثاني
أ
Administration.leader_of
train_908
الأول لغتها الرسمية الثاني
أ
GPE.official_language
There is no contradiction: the prompt itself defines the meaning of أ for
that row. Likewise, the same relation can be assigned different letters on
different rows because the compatible positive options are deterministically
shuffled. The decoder must therefore retain each row's mapping until after the
model makes its choice.
لا توجد علاقة is always the final displayed option, but it does not always
have the same letter:
if it is the only compatible option, its code is أ;
after two positive options, its code is ج;
after nine positive options, its code is ي.
This is why a downstream application must never interpret a bare letter without
the originating row's option arrays.
Fully filled positive example
This is the exact real transformed row train_13110. The model receives the
following system message:
text
1أنت مصنّف علاقات. حدّد العلاقة الموجّهة من الكيان [الأول] إلى الكيان [الثاني].
2اختر علاقة واحدة من القائمة تناسب معنى الجملة ونوعي الكيانين.
3إن لم تنطبق أي علاقة فاختر «لا توجد علاقة».
4أجب برمز الخيار فقط: حرف عربي واحد دون رقم أو شرح.
It then receives this exact user message:
text
1الجملة:
2[الأول] سوق [الثاني] مدينة يافا [/الثاني] [/الأول] .
34نوع الأول: منشأة
5نوع الثاني: كيان جيوسياسي
67الخيارات:
8أ) الأول يقع بالقرب من الثاني
9ب) الأول يقع في الثاني
10ج) لا توجد علاقة
1112رمز الإجابة:
The exact assistant target/output is:
ب
For this row only, ب is index 1, whose Arabic phrase is
الأول يقع في الثاني and whose metadata class is Location.located_in.
Fully filled negative example
This is the exact real transformed row train_neg-62666-62670AwLOC. The system
message is identical. Its exact user message is:
text
1الجملة:
2[الأول] جبال نابلس [/الأول] [الثاني] والكرمل [/الثاني] .
34نوع الأول: موقع جغرافي
5نوع الثاني: شخص
67الخيارات:
8أ) لا توجد علاقة
910رمز الإجابة:
The exact assistant target/output is:
أ
Here أ decodes to no_relation because the negative choice is the only
ontology-compatible option for that direction and type pair.
Full prompt/output coverage for every class
examples/ALL_41_CASES.md contains one real,
fully filled system prompt, user prompt, assistant output, and row-local mapping
table for all 40 positive relations plus no_relation. Its coverage index
makes the examples easy to scan. The identical selected records are preserved
in machine-readable form at
examples/all_41_class_examples.jsonl.
Regenerate the appendix deterministically from the pinned transformed training
split with:
python tools/build_examples_appendix.py
The generator requires all 15,686 training rows, validates every selected
mapping and output, requires exactly 41 classes, and deliberately documents
no_relation last.
How the prompt was selected
The original experiment asked Yehia to generate the complete Arabic relation
phrase. Interactive testing exposed a distinction between semantic mistakes and
surface-format mistakes: the base model often copied list punctuation, prefixed
the answer with words such as “الخيار”, or produced an explanation around an
otherwise recognizable choice.
Prompt development followed this sequence:
Exercise all relation types with the locally downloaded base model.
Remove the demonstration and redundant rules.
Simplify the system instruction while preserving direction, entity types,
negative behavior, and the answer-only requirement.
Keep لا توجد علاقة visibly and structurally last.
Replace free-form phrase generation with a row-specific one-token code.
Score candidate prompts over the complete 1,687-row validation split with
the same constrained decision rule used during training evaluation.
The selected explicit prompt achieved calibrated base-model positive micro F1
0.3616; the shorter compact alternative achieved 0.3563. The selected prompt
was therefore slightly stronger and more explicit about direction and output
format. There is no demonstration in the final prompt.
Scientific caveat
The validation split was used to choose the prompt and calibrate the
no-relation decision bias. It is a development set, not an untouched test set.
Validation-tuned numbers must not be presented as final held-out test
performance. Official/test inference must freeze:
the final prompt text;
option construction and order;
the selected adapter checkpoint;
the selected no-relation bias;
the constrained decoding rule.
No official labels are used during training or intermediate evaluation.
The transformed revision was independently downloaded after publication and
matched local row counts, byte counts, and SHA-256 hashes.
Split roles and counts
Split
Rows
Positive
No relation
Role
train
15,686
9,009
6,677
QLoRA training only
validation
1,687
867
820
Prompt development and checkpoint evaluation only
official
2,074
Unlabeled
Unlabeled
Final inference only
Training never merges validation or official rows. Official examples contain
system and user messages only; they have no assistant target or gold relation.
build_dataset.py delegates all locked transformation behavior to
re_sft_common.py. For each source row it performs the following operations:
Read without dropping rows. Load train, validation, and official source
files separately and enforce their expected counts.
Validate exact spans. Confirm that subject_start:subject_end and
object_start:object_end recover the stored mention strings exactly.
Insert markers by character offsets. Add first/second markers at the
source positions rather than using string replacement. This remains correct
when mention strings repeat.
Handle span edge cases deterministically. The insertion routine defines
ordering for nested, overlapping, adjacent, and equal spans and then verifies
exactly one opening and closing marker for each entity.
Window long context. Preserve both entities and retain up to 500 source
characters on each side. The unmodified full sentence and original numeric
spans stay in metadata; numeric offsets are not shown as prompt instructions.
Map entity types to Arabic. Translate the two coarse source types using
the fixed 21-entry entity_type_ar_mapper.json.
Apply the directed ontology. Select only relations whose ontology domain
contains the first entity type and whose range contains the second entity
type.
Use packaged Arabic templates verbatim. Render each allowed positive
relation with its packaged template_ar answer. Awkward or misspelled source
templates are deliberately not corrected during transformation.
Verify reversibility. Require all 40 Arabic answers to be unique, remain
unique after normalization, and map back to exactly one ontology relation.
Shuffle positive options deterministically. Derive a stable per-row seed
from seed 42, split identity, and row identity, then shuffle only positive
compatible choices.
Append the negative option. Add exactly one لا توجد علاقة after the
positive shuffle. It is always the final displayed option.
Assign one-token codes. Label displayed options sequentially with the
prefix of أ، ب، ج، د، ه، و، ز، ح، ط، ي.
Map the gold answer. On labeled splits, verify the gold relation is
compatible and displayed exactly once, then store its displayed position,
Arabic phrase, and one-token code.
Create conversations. Labeled rows receive system, user, and assistant
messages. Official rows receive only system and user messages.
Audit and hash. Check IDs, counts, mappings, markers, options, answers,
leakage, and split roles before writing a summary and optional Hub upload.
The number of displayed options ranges from 1 to 10 according to the directed
entity-type pair. A one-option row contains only لا توجد علاقة.
Determinism boundaries
With identical source JSONL bytes, resource files, transformation code, seed
42, and context size 500, the transformed JSONL content is deterministic.
dataset_summary.json contains a creation timestamp, so that metadata field can
differ even when the three JSONL hashes are identical.
Dataset record schema
Each JSONL line is a self-contained object. Important fields are:
Field
Meaning
id
Stable transformed-row ID
sentence_id, triple_id
Source identifiers
sentence
Original unmodified sentence
subject, object
Original mention strings
subject_start/end
Original first-entity character span
object_start/end
Original second-entity character span
subject_type, object_type
Canonical metadata types, never rendered
first_type_ar, second_type_ar
Arabic types rendered in the prompt
marked_sentence
Context with directional markers
window_start_char, window_end_char
Selected source-context boundary
context_was_windowed
Whether context was shortened
allowed_relation_full_labels
Canonical labels in displayed order
allowed_relation_ontology_ids
Ontology IDs in displayed order
allowed_options_ar
Arabic phrases in displayed order
option_codes
One-token codes aligned with displayed options
option_records
Full reversible mapping records
gold_relation_full
Gold canonical relation on labeled splits
gold_relation_ontology_id
Gold ontology ID for a positive row
gold_answer_ar
Gold displayed Arabic phrase
gold_option_index
Zero-based displayed gold position
gold_answer_code
Actual one-token supervised target
prompt_messages
System and user messages only
messages
Prompt plus assistant code on labeled rows
prompt_version
yehia_re_ar_v3_one_token_choice
Canonical fields exist for evaluation and reversibility; the prompt-leakage
audit examines only conversation content and confirms that these identifiers do
not reach the model as text.
Packaged ontology and Arabic resources
The resources/ directory is part of the experiment definition:
File
Purpose
wojood_ontology.json
Coarse entity types and directed relation domain/range rules
entity_type_ar_mapper.json
Exact mapping for all 21 coarse types
relation_mapper.json
40 full labels plus no_relation mapping
relation_template_ar_mapper.json
Exact packaged Arabic answer for each positive relation
relation_template_ar_reverse_mapper.json
Arabic-answer-to-relation reverse mapping
resource_manifest.json
Resource provenance and integrity metadata
These files are not suggestions. Changing templates, type names, or ontology
compatibility creates a different experiment and requires a new prompt/data
version.
Dataset validation guarantees
The independent audit_dataset.py pass validates all 19,447 transformed rows:
exact split counts and unique IDs;
hashes against dataset_summary.json;
exactly 21 entity-type mappings;
exactly 40 positive relation templates;
uniqueness and reversibility of all Arabic relation answers;
41 labeled classes including no_relation;
exactly one instance of every directional marker;
marked sentence presence inside the user message;
ontology-compatible and unique choices;
sequential one-token option-code structure;
لا توجد علاقة present exactly once and last;
exact agreement of gold full label, Arabic phrase, index, code, and assistant;
no assistant/gold leakage in official rows;
zero canonical type/relation identifier leakage in conversation content.
The audit writes validation_audit.json beside the downloaded/built dataset.
Training objective
This is not regex loss, sequence-wide language-model loss, or loss over the
Arabic relation phrase. It is full-vocabulary causal-language-model
cross-entropy on exactly one assistant token: the gold displayed option code.
For every encoded row:
Token region
Label
System message
-100
User message
-100
Chat-template prompt boundary
-100
Assistant decision code
Actual one-token ID
Any trailing template token
-100
Padding
-100
Conceptually, one encoded training example looks like this:
Transformers' causal language-model implementation shifts the labels
internally. Therefore, the logits at the position immediately before the gold
letter are trained to predict that letter. If g is the token ID of the gold
letter and z_v is the model logit for vocabulary token v, the one-row loss
is exactly:
loss = -log(exp(z_g) / sum(exp(z_v) for every token v in Yehia's vocabulary))
Equivalently, it is -log(p_gold). Some useful intuition:
Probability assigned to the correct letter
One-token loss
0.90
0.105
0.70
0.357
0.50
0.693
0.10
2.303
0.01
4.605
The batch loss is the mean over the supervised letter positions in that batch.
Because there is exactly one supervised token per row, a long prompt does not
silently contribute more loss than a short prompt.
The mapping step is outside the differentiable loss:
During dataset construction, the gold canonical relation is located in that
row's aligned option arrays.
Its row-local index selects the gold Arabic letter.
Only that existing letter token becomes a non--100 label.
Backpropagation updates LoRA parameters so Yehia assigns more probability to
that letter in this context.
During inference, the predicted letter's row-local index maps back to the
Arabic relation and canonical metadata class.
No regular expression changes the training labels or loss. No fuzzy phrase
match is used in backpropagation. No 41-class classification head, 41 new
tokens, or global letter-to-relation table exists. Regex/fuzzy parsing belonged
only to abandoned free-form prompt experiments and is not part of the final
trainer.
The trainer applies Yehia's native chat template exactly once to the prompt and
once to the complete labeled conversation, finds their exact common boundary,
and verifies that the first assistant token is the expected code. It rejects a
row if the boundary differs, the sequence exceeds 1,024 tokens, or no decision
token remains.
Full-vocabulary loss means the correct code must outrank every token in Yehia's
vocabulary. A candidate-only softmax objective was evaluated during engineering,
but it was not selected: the tested path was slower and did not directly
penalize unrelated vocabulary tokens. The clean model.py contains only the
selected full-vocabulary objective.
The saved masking evidence includes a positive example, a negative example, and
a long prompt. In every audited row:
Only LoRA weights receive gradients. The 4-bit base weights, embeddings, and
language-model output head remain frozen. Thus the model learns the task by
changing low-rank adapter paths throughout the transformer while preserving the
gated base checkpoint as an external dependency.
Final QLoRA configuration
The selected configuration in .env.example and model.py is:
Setting
Value
Base model
Navid-AI/Yehia-7B-preview
Exact base revision
b9dda4715eafee7e8090d2c83cfe078d75f4ebb8
Quantization
4-bit NF4
Double quantization
Enabled
Compute dtype
BF16
Model device map
Entire quantized model on GPU 0
Attention implementation
SDPA
Maximum sequence length
1,024
Gradient checkpointing
Enabled
Training use_cache
False
PEFT target
target_modules="all-linear"
LoRA rank
16
LoRA alpha
32
LoRA dropout
0.05
RS-LoRA
Enabled
LoRA bias
None
PEFT task type
CAUSAL_LM
Physical train batch
4
Gradient accumulation
4
Effective batch
16
Validation batch
16
Epochs
3
Optimizer steps
2,943 on the exact training split
Learning rate
5e-5
Weight decay
0.0
Warmup
5%
Scheduler
Cosine
Optimizer
paged_adamw_8bit
Gradient clipping
1.0
Train logging
Every 20 optimizer steps
Evaluation/save
Every 250 optimizer steps
Numbered checkpoint retention
2
Random seed
42
Sampling
Inverse class frequency, exponent 0.25
Rank 16/alpha 32 is the selected run, not the package's earlier rank 64/alpha
128 proposal. Pilot evidence showed strong learning with the smaller adapter,
reducing trainable parameters and release size while retaining the required
all-linear coverage.
Class sampling
Training uses a deterministic WeightedRandomSampler with replacement. For
class count n_c, the relative row weight is proportional to:
n_c ^ -0.25
This softens severe class imbalance without forcing every class to be equally
common. The exact class weights are stored in
evidence/sampling_strategy.json.
True all-linear PEFT coverage
target_modules="all-linear" is passed directly to LoraConfig. The trainer
does not infer success from the presence of seven familiar suffixes.
PEFT resolves the sentinel against the concrete architecture and may serialize
the resulting suffix set (q_proj, k_proj, v_proj, o_proj, gate_proj,
up_proj, down_proj) in adapter_config.json. That serialized expansion is
not the evidence for the claim. The immutable run configuration records the
original request as the literal string "all-linear", and the before/after
full-name inventories below prove actual coverage of every supported internal
module while excluding only the output head.
Before PEFT attachment it inventories every supported torch.nn.Linear,
Transformers Conv1D, and compatible bitsandbytes linear module by full name
and class. It separately identifies embeddings and output heads. After
attachment it inventories every LoRA module and compares the full internal set.
For Yehia 7B the selected run recorded:
Audit item
Result
Eligible supported linear/head modules
225
LoRA-wrapped internal modules
224
Transformer layers
32
Wrapped modules per layer
7
Wrapped q_proj
32
Wrapped k_proj
32
Wrapped v_proj
32
Wrapped o_proj
32
Wrapped gate_proj
32
Wrapped up_proj
32
Wrapped down_proj
32
Output lm_head adapted
No
Embeddings adapted
No
Quantized base weights trainable
No
Trainable parameters
39,976,960
Trainable percentage
0.567811%
The one eligible-but-unwrapped module is the language-model output head, which
PEFT intentionally excludes under all-linear targeting. Only LoRA parameters
are trainable.
The selected physical batch was tested after attaching all-linear adapters with
representative long examples, 1,024-token maximum length, 4-bit NF4, double
quantization, BF16, and gradient checkpointing.
The recorded physical-batch-4 forward/backward smoke test produced:
Item
Result
Status
Passed
Loss
0.619789, finite
Physical batch
4
Gradient accumulation
4
Finite nonzero LoRA gradient tensors
224
Peak allocated VRAM
9.044 GiB
Peak reserved VRAM
10.172 GiB
The complete active process uses additional memory for model state, optimizer,
evaluation, and allocator caching; the live L40S run remained well below the
approximately 45 GB device capacity. See
evidence/smoke_test.json.
On a smaller GPU, keep effective batch 16 by trying physical batch 2 with
gradient accumulation 8. Do not silently shorten sequences, remove all-linear
targeting, or alter the prompt to solve an OOM; those changes define a different
experiment.
Evaluation and checkpoint selection
At steps 0, 250, 500, 750, ..., evaluation processes every one of the 1,687
validation examples. “Step” means optimizer/global update after gradient
accumulation, not micro-batch count.
For each validation row, the evaluator:
Applies Yehia's native chat template to prompt_messages exactly once.
Obtains the logits predicting the next assistant token.
Selects logits only for option codes actually displayed in that row.
Optionally adds one development-calibrated bias to the final no-relation
option.
Chooses the highest-scoring code.
Maps its displayed position back to the Arabic option and canonical label.
Every metric artifact records:
positive micro precision, recall, and F1;
41-class accuracy;
41-class macro F1;
weighted F1;
decision negative log-likelihood;
invalid count and rate;
evaluated row count;
raw constrained metrics;
unconstrained full-vocabulary argmax metrics;
selected no-relation bias;
prediction artifact path.
Constrained versus unconstrained output
The production decoder is constrained to codes displayed in the current row,
so its structural invalid-output count is zero by construction. This does not
prove that the unconstrained model would always emit a valid code. The evaluator
therefore also records the full-vocabulary next-token argmax:
After step 250, the selected run's unconstrained invalid count was also zero in
the evaluations available at packaging time.
No-relation calibration
The released decoder applies one global additive bias to the score of the final
لا توجد علاقة (no_relation) option before taking argmax. The selected
best checkpoint uses:
no_relation_logit_bias = -6.6600000000000215
For a row with valid option logits z, inference is equivalent to:
All positive-relation logits remain unchanged. If z_no_relation is the raw
no-relation logit and z_best_positive is the highest raw positive-relation
logit, the released decoder selects no-relation only when:
z_no_relation - z_best_positive > 6.66
Without calibration (bias = 0), no-relation wins whenever its logit exceeds
the best positive logit by any amount. The bias is therefore another expression
of a decision-threshold adjustment: Yehia still uses argmax, but only after
the no-relation score has been shifted.
The value -6.66 is not a subtraction of 6.66 percentage points. Logits are
unbounded scores, not probabilities. If the adjusted valid-option logits are
passed through softmax, this shift multiplies the odds of no-relation relative
to the positive options by:
exp(-6.66) ~= 0.00128
This is approximately a 781-fold reduction in the no-relation odds. In a
two-option illustration, no-relation would need a raw probability above about
99.872% to remain the winner after this shift. There is no single equivalent
probability threshold for the real decoder because a row can contain several
positive options with different logits. The magnitude is nevertheless a strong
correction toward predicting positive relations.
The correction was useful because the selected adapter's raw constrained
decision rule was conservative on development data. Its effect on the complete
1,687-row labeled validation split was:
Decision rule
Positive predictions
TP
FP
FN
Positive precision
Positive recall
Positive micro F1
Raw constrained argmax (bias = 0)
779
490
289
377
0.6290
0.5652
0.5954
Released calibrated argmax (bias = -6.66)
1,009
594
415
273
0.5887
0.6851
0.6333
Calibration recovered 104 additional true positives while adding 126 false
positives. Precision decreased, recall increased, and positive micro F1
improved. The calibrated system still predicted no-relation for 678 of the
1,687 validation rows; the correction does not force every row to be positive.
The evaluator selects the bias on validation by maximizing positive micro F1,
with accuracy and then smaller absolute bias used as tie-breakers. This process
does not retrain or alter the adapter weights. Setting the bias to zero is
sufficient to recover raw constrained decoding; no retraining is required.
Because the bias was selected using validation labels, all calibrated validation
scores are development metrics rather than untouched held-out results. No
official/test labels were used to select it. tools/stage_release.py freezes
the selected value in inference_config.json, and official/test inference must
reuse that value without further tuning. Selecting or changing the bias using
hidden test labels would invalidate the held-out evaluation.
Best checkpoint
The locked checkpoint-selection metric is generation/decision-based validation
positive micro F1. It is not training loss or teacher-forced validation loss.
The trainer maintains:
latest_checkpoint/: complete named copy of the final step-2,943 checkpoint;
best_checkpoint/: complete named copy of the best validation checkpoint;
best_adapter/: adapter/tokenizer exported after training completes.
Numbered checkpoint rotation never deletes the named latest/best copies.
Complete scheduled development metrics
These values cover every scheduled full validation from the completed selected
run. They are engineering/development evidence, not final official/test claims.
Step
Positive P
Positive R
Positive F1
Accuracy
Macro F1
Weighted F1
Decision NLL
Unconstrained F1
Unconstrained invalid
0
0.2793
0.5225
0.3640
0.2964
0.5420
0.2423
2.3701
0.3295
2
250
0.5674
0.6021
0.5842
0.6586
0.6391
0.6477
1.3317
0.5754
0
500
0.5785
0.6159
0.5966
0.6823
0.6691
0.6590
1.3186
0.5944
0
750
0.5913
0.5825
0.5869
0.6609
0.6405
0.6461
1.5595
0.5820
0
1000
0.5560
0.6471
0.5981
0.6598
0.6842
0.6458
1.9229
0.5758
0
1250
0.5356
0.6586
0.5908
0.6343
0.6699
0.6300
2.0380
0.5904
0
1500
0.6006
0.6678
0.6324
0.7030
0.6625
0.6849
2.2494
0.5807
0
1750
0.6045
0.6205
0.6124
0.6882
0.6429
0.6687
2.1202
0.5940
0
2000
0.6121
0.6298
0.6208
0.7012
0.6462
0.6814
2.2389
0.5944
0
2250
0.5824
0.6851
0.6296
0.6935
0.6849
0.6790
2.1502
0.5938
0
2500
0.5902
0.6678
0.6266
0.6924
0.6831
0.6767
2.3231
0.5999
0
2750
0.5887
0.6851
0.6333
0.6971
0.6889
0.6828
2.2822
0.5954
0
Step 2,750 is the selected best checkpoint by the locked positive-micro-F1
metric. Step 1,500 retained the strongest accuracy and weighted F1, step 2,750
the strongest macro F1, step 500 the best decision NLL, and step 2,500 the best
unconstrained positive F1. This divergence is why complete scheduled evaluation
and durable best-checkpoint preservation matter even when training loss falls.
After step 2,943, the trainer reloaded the selected step-2,750 adapter and ran
the complete final validation again. It reproduced positive precision 0.588702,
recall 0.685121, F1 0.633262, accuracy 0.697095, macro F1 0.688899,
weighted F1 0.682849, and zero unconstrained invalid outputs. Aggregate
three-epoch train loss was 0.218184; this is not the checkpoint-selection
metric.
Local JSONL; no datasets runtime dependency required
huggingface_hub
0.36.0
scikit-learn
1.5.2
Use the pinned stack first. If a different GPU/driver requires a compatibility
change, record the exact failure and smallest changed dependency before claiming
reproducibility.
Credentials and access boundaries
Copy .env.example to .env and provide tokens locally. Never commit .env.
Token responsibilities are deliberately separate:
HF_TOKENONE: gated read access to Navid-AI/Yehia-7B-preview only;
HF_TOKENTWO: U4RASD dataset access and optional U4RASD dataset/adapter
publication.
No script prints token values. The clean repository contains token variable
names only. The preferred workflow downloads Yehia once into
models/Yehia-7B-preview and performs all subsequent model loads locally.
Quick start: reproduce training from the published SFT dataset
Edit .env and set HF_TOKENONE and HF_TOKENTWO. Keep the exact dataset
revision, exact Yehia base revision, and selected hyperparameters unchanged for
a faithful reproduction. The selected base commit is
b9dda4715eafee7e8090d2c83cfe078d75f4ebb8; future downloads do not silently
follow a moving main branch.
The setup script installs the pinned stack, compiles every final Python entry
point, checks Bash syntax, imports the QLoRA stack, reports CUDA/BF16 status, and
runs the local prompt/resource self-test.
download_dataset.py pins the verified dataset commit from .env, downloads
only dataset/resource/card files, and checks all three row counts and hashes.
audit_dataset.py then performs the exhaustive content audit.
4. Start detached training
./train_tmux.sh
Attach and detach with:
bash
1tmux attach -t dru_re_yehia_training
2# detach with Ctrl+b, then d
To run in the foreground instead:
./train.sh
On the first run, model.py uses an existing local Yehia snapshot if
models/Yehia-7B-preview/config.json exists. Otherwise it downloads the gated
base repository at the pinned revision with HF_TOKENONE into that directory.
Training and later inference load only from the local snapshot.
Alternative: rebuild the SFT dataset from source
The already-published transformed revision is the simplest way to reproduce
training. To reproduce the transformation itself, either:
set SOURCE_DATASET_DIR to a local directory containing the three exact
source filenames; or
leave it blank and grant HF_TOKENTWO read access to
U4RASD/WojoodRelationsAnnotated.
The launcher runs the builder and then the independent audit. If the output
directory already exists, the builder stops rather than deleting it. Set
OVERWRITE_DATASET_OUTPUT=true only when replacement is intentional.
To publish an independently verified rebuild, explicitly set
PUSH_SFT_DATASET=true. Publishing is never the default.
Monitoring
Trainer log
tail -f logs/train_tmux.log
The internal Python log is normally logs/train.log.
GPU
watch -n 1 nvidia-smi
Resource time series
./tools/monitor_resources.sh
The default CSV is written inside the ignored run directory at
runs/DRU-RE-Yehia/artifacts/resource_monitor.csv.
Loss plots
Render once:
python tools/plot_losses.py --once
Continuously refresh every 20 seconds:
python tools/plot_losses.py
The generated PNGs are placed under runs/DRU-RE-Yehia/plots/ and are ignored
by Git.
Resume behavior
CHOICE_RESUME=true is the selected default. When training starts, the trainer
uses Transformers' get_last_checkpoint in
runs/DRU-RE-Yehia/checkpoints/. Restarting train.sh or train_tmux.sh
therefore resumes the newest numbered checkpoint, including optimizer,
scheduler, RNG, and global-step state.
Before resuming, do not change:
dataset JSONL content or order;
prompt version;
tokenizer/base model;
LoRA rank, alpha, targets, or RS-LoRA setting;
physical batch or gradient accumulation;
sampling exponent;
learning-rate schedule;
maximum sequence length.
Changing those values while reusing optimizer state invalidates a strict
reproduction. Start a new output directory for a new experiment.
Generated run layout
Training writes only to ignored local directories:
The full 7B base model is never written inside the run or adapter repository.
Stage the clean model release
After all three epochs and final validation finish, run:
python tools/stage_release.py
The staging utility refuses to proceed unless:
run_summary.json exists and declares status=complete;
global step is exactly 2,943 and final validation contains all 1,687 rows;
scheduled metric artifacts exist at step 0 and every 250 steps through 2,750;
best_adapter/ contains adapter config and safetensors;
persisted run configuration states target_modules="all-linear";
rank, alpha, dropout, RS-LoRA, bias, and task type match the selected setup;
all-linear evidence reports 225 eligible and 224 wrapped modules with only
LoRA parameters trainable and the output head frozen;
all 448 saved adapter tensors are finite and nonzero;
the best-checkpoint metric artifact exists;
latest_checkpoint/ and numbered checkpoint-2943/ are byte-identical;
the latest checkpoint has finite adapter/optimizer state, complete scheduler
state, trainer global step 2,943, RNG state, and training arguments.
It copies only:
adapter safetensors and PEFT configuration;
tokenizer/chat-template files required by the adapter;
frozen inference configuration, including best-step no-relation bias;
final run summary and best validation metrics;
small audit evidence and a SHA-256 release manifest;
the complete latest step-2,943 resumable checkpoint, with a separate
checkpoint integrity manifest.
All correctness checks finish before repository files are changed. Each staged
file is copied through a temporary sibling and atomically renamed, preventing an
interrupted copy from appearing as a complete release artifact.
It explicitly excludes:
Yehia base weights;
all older numbered/named checkpoints other than the staged final latest copy;
logs and resource time series;
per-row validation prediction dumps;
caches and temporary files.
The full repository is then ready for review and Hugging Face upload. Publish
the trained adapter and checkpoint only with the supplied Hugging Face client.
Authenticate with the U4RASD token without putting its value in a command
history or repository file. Build Azure's separate weight-free copy with
tools/stage_azure_release.py.
Staging also replaces PEFT's machine-local
base_model_name_or_path=/root/workspace/... metadata with the portable base
repository ID and exact commit. This does not alter adapter tensors; it prevents
a fresh machine from trying to resolve a path that existed only on the training
pod.
Durable release, Hugging Face, Azure, and pod deletion
What must live where
The deletion-safe release is intentionally split across durable repositories:
Asset
Durable location
Why it is needed
Exact transformed train/validation/official JSONL and resources
Hugging Face dataset U4RASD/Yehia-RE-SFT at commit a060e47f56025778b97344d4d3de60a8fd53be7c
Azure Git and Hugging Face model U4RASD/DRU-RE-Yehia
Reproduce, audit, train, and understand the system
Best adapter, tokenizer files, inference_config.json, final metrics, and release manifest
Hugging Face model repository only
Actual model inference and checkpoint provenance
Latest complete checkpoint-2943 adapter, optimizer, scheduler, RNG, trainer state, tokenizer, and training arguments
latest_checkpoint/ in the Hugging Face model repository only
Exact archival resume state from the completed run
Ready official prediction archive plus auditable row-level decisions
submission.zip and submission/ in both model and Azure repositories
Submit directly or verify/regenerate all 2,074 predictions
Yehia 7B base at commit b9dda4715eafee7e8090d2c83cfe078d75f4ebb8
Upstream gated Navid-AI/Yehia-7B-preview
Required underneath the PEFT adapter
Token permissions
An external password manager or secret store, never either repository
Future access to gated/private repositories
The Hugging Face adapter is not a standalone 7B model. It is deliberately about
160 MB rather than a copy of the full base weights. Future inference requires
continued authorized access to the pinned gated Yehia base. If the upstream
base is removed or access is revoked, the adapter alone cannot reconstruct it.
Do not mirror the base into Azure or the model repository unless its license and
owner explicitly permit redistribution.
Local release verification
After staging the full Hugging Face release, run:
python tools/verify_release.py --repo-dir .
The verifier fails if any required source/resource/example/release file is
missing. It also checks:
41 unique fully filled worked examples and correct row-local outputs;
secret-free evidence that the gated base resolved to the exact pinned commit;
the exact transformed-dataset commit, counts, and local/remote hashes;
the SHA-256 and byte size of every staged manifest entry;
exactly 2,074 official predictions, unique IDs in the pinned source order,
legal relation names, row/debug agreement, deterministic ZIP metadata, and a
one-member submission.zip whose CRC and recorded hashes pass;
absence of full base weights and any unrecognized checkpoint payloads;
in remote-clean mode, absence of every unrecognized or stale remote file.
This command intentionally fails before the final adapter is staged. A source
folder by itself is reproducible code, not a finished model release. The
weight-free Azure package has a different intentional contract and must pass:
That verifier fails if it finds any safetensors file, Trainer checkpoint,
adapter chunk, model binary, cache, credential file, or unexpected payload.
Publish and independently re-download Hugging Face
After local verification passes and .env contains HF_TOKENTWO, run:
python tools/publish_huggingface.py
That publisher is hard-locked to U4RASD/DRU-RE-Yehia. It:
runs the complete local release verifier;
uploads the entire clean folder while excluding .env, caches, local data,
models, runs, logs, and the disposable predictions/ work directory (the
reviewed submission/ artifacts and root submission.zip are included);
resolves the immutable uploaded model commit;
downloads that commit into a fresh temporary directory;
reruns the verifier in remote-clean mode;
uploads a secret-free release/hf_remote_verification.json record;
downloads and verifies the final remote commit once more.
The printed final_revision is the immutable Hugging Face model commit to keep
with the experiment record. No token value is printed or persisted.
Push the weight-free reproducibility folder to Azure Git
The durable Azure target for this release is branch
add-dru-re-entitypair-twohead of KG_eval-2026, under the new directory
DRU-RE-Yehia/. Azure is deliberately the code, documentation, evidence, and
submission mirror. It must contain no adapter weights, split adapter chunks,
optimizer state, or Trainer checkpoint. The best adapter and full latest
checkpoint are stored only in U4RASD/DRU-RE-Yehia on Hugging Face.
Stage the Azure-specific copy from the already verified full release:
Authenticate through a credential manager or masked prompt. Never embed an
Azure token in the remote URL, shell history, README, or Git files. If the Azure
repository already has history, integrate it normally instead of overwriting
it.
Azure DevOps Git is a durable source/model mirror, not automatically an Azure
Machine Learning online endpoint. Deployment to Azure ML would additionally
need an approved compute/environment image, scoring entrypoint, and endpoint
configuration; those are deployment choices, not retraining prerequisites.
Fresh Azure checkout test
Before pod deletion, verify that Azure has all reproducibility and submission
files and zero model payloads. Use an environment outside the checkout so
--remote-clean examines a pristine repository:
The Azure verifier rejects .safetensors, .pt, .pth, .bin, split-part
files, latest_checkpoint/, azure_large_files/, .env, and unrecognized
files. It still validates the complete 2,074-row submission, prompt/examples,
dataset/base provenance, all-linear evidence, final metrics, and immutable
Hugging Face publication pointer.
Exact resume versus retraining
The two durable repositories together are sufficient to:
understand and audit the experiment;
reproduce the transformed dataset;
retrain from optimizer step 0;
load the completed best adapter for inference;
reproduce constrained decoding and output mapping;
restore the exact latest completed Trainer state from step 2,943.
In Hugging Face, root adapter_model.safetensors is the selected step-2,750
best inference adapter. Hugging Face's
latest_checkpoint/adapter_model.safetensors is the different final step-2,943
training state accompanied by its optimizer, scheduler, RNG, trainer state,
and training arguments. Applications should use the root best adapter; the
checkpoint directory exists for archival resume/recovery. Neither file nor any
lossless encoding of its bytes belongs in Azure.
To restore it into the default run layout:
python tools/restore_latest_checkpoint.py
This verifies every checkpoint hash and tensor first, then atomically creates:
runs/DRU-RE-Yehia/checkpoints/checkpoint-2943/
With the locked three-epoch configuration, that checkpoint is already at the
training endpoint, so a normal resume performs no additional optimizer update
and proceeds to finalization. Extending it to more epochs or changing the
learning-rate schedule is a new experiment, not an exact continuation of the
documented run. Older intermediate checkpoints are intentionally excluded.
Deletion gate
The training machine is safe to delete only after every item below is true:
Three-epoch training and final 1,687-row validation completed normally.
run_summary.json says status=complete.
tools/stage_release.py staged the best—not merely latest—adapter.
latest_checkpoint/checkpoint_manifest.json proves the complete final
step-2,943 optimizer/scheduler/RNG checkpoint is present.
Local tools/verify_release.py passed.
submission.zip contains only predictions.txt; all 2,074 lines, IDs,
class names, source order, hashes, and CRC pass the release verifier.
tools/publish_huggingface.py passed its fresh final-download check.
The Hugging Face model final_revision was recorded.
The pinned SFT dataset commit remains remotely readable and hash-verified.
The Azure folder contains source, documentation, evidence, and the
submission, but no adapter, adapter chunks, optimizer, or checkpoint bytes.
A fresh Azure clone passed
tools/verify_azure_release.py --remote-clean and reported
trained_payloads_in_azure=0.
The best adapter and complete latest checkpoint were independently
downloaded and verified from Hugging Face only.
At least one end-to-end inference smoke test passed from a fresh checkout
using the gated pinned Yehia base, prepared input, and staged adapter.
Azure Git commit, Hugging Face model commit, Hugging Face dataset commit,
and Yehia base commit were recorded together.
Required future repository credentials/permissions exist outside the pod.
Until that entire gate passes, deleting the pod is premature. After it passes,
temporary caches, plots, rolling logs, downloaded base files, transformed local
copies, and live checkpoints on this machine are disposable.
The model is a relation classifier, not a named-entity recognizer. The caller
must supply the two entity mentions, their direction, and their coarse types.
Predict the published official split
predict.py accepts already transformed rows containing prompt_messages,
aligned Arabic options/codes, and canonical mappings. After release staging:
If the exact pinned transformed dataset is absent, the script downloads it from
U4RASD/Yehia-RE-SFT at commit
a060e47f56025778b97344d4d3de60a8fd53be7c. predict.py likewise downloads
the gated Yehia base at commit
b9dda4715eafee7e8090d2c83cfe078d75f4ebb8 when its verified local snapshot is
absent. It then loads the root best adapter, runs all 2,074 unlabeled official
rows, and produces:
The builder refuses a different source hash/count/order, any official gold
label, a missing or duplicate triple_id, malformed row-local options, an
illegal prediction, an inconsistent no-relation bias, or a malformed ZIP.
submission.zip contains only root predictions.txt; each line is:
triple_id<TAB>canonical_relation
The dataset/model use no_relation internally. Only the competition payload
converts that value to the required no-relation. The current artifact has
SHA-256
a6bbd74ff4e32a570ac3fc1c12ce66aa3fe0c47986e66a3906a36231f3cbb0d5,
contains 904 negative and 1,170 positive predictions, and preserves the exact
official ID order. Those counts describe an unlabeled prediction distribution;
they are not an official-set accuracy claim.
For an audit-only deterministic repackage of an already generated model dump:
First inspect the 21 accepted canonical coarse type names:
python prepare_input.py --list-types
Then prepare a row. When each mention occurs exactly once, character spans can
be inferred safely:
bash
1python prepare_input.py \2 --id demo_1 \3 --sentence 'يعمل أحمد في جامعة بيرزيت.'\4 --subject 'أحمد'\5 --object 'جامعة بيرزيت'\6 --subject-type PERS \7 --object-type ORG \8 --output predictions/prepared_inputs.jsonl
The subject is always [الأول], the object is always [الثاني], and the
question is subject-to-object. To ask the reverse question, prepare a separate
row with subject/object and their types reversed.
If a mention string occurs more than once, prepare_input.py deliberately
stops instead of guessing. Supply zero-based, end-exclusive Unicode character
offsets:
prepare_input.py uses the same marker insertion, 21-type Arabic mapper,
directed ontology filter, 40 packaged Arabic templates, deterministic seed-42
shuffle, 500-character context logic, exact v3 prompt, and final
لا توجد علاقة rule as dataset construction. It forces any accidental input
relation field to empty, so gold labels cannot leak into inference prompts.
Prepare a batch
Pass --input a source-shaped JSONL file. One minimal line looks like:
{"id":"request_1","sentence":"يعمل أحمد في جامعة بيرزيت.","subject":"أحمد","object":"جامعة بيرزيت","subject_start":5,"subject_end":9,"object_start":13,"object_end":25,"subject_type":"PERS","object_type":"ORG"}
Exact spans are strongly recommended in production. The input need not include
relation labels, prompt text, option lists, Arabic type names, or entity
markers; the preparation utility derives them from the packaged resources.
What predict.py computes
The script:
downloads the pinned Yehia base revision if it is not already local;
loads the staged PEFT adapter;
applies the native Yehia chat template exactly once;
obtains the next-token logits after رمز الإجابة:;
keeps the logits for only the valid letters displayed on that row;
adds the frozen best-checkpoint no-relation bias to the final letter;
takes argmax and maps that row-local index back to the Arabic phrase,
canonical relation, and ontology ID.
It does not ask the model to freely generate a phrase. Consequently, every
returned code is structurally valid for its row. The saved unconstrained
generation metrics remain in the evaluation evidence as an honest diagnostic
of whether the model also follows the output format without this guardrail.
Each output line contains fields such as:
json
1{2"id":"official_example_id",3"predicted_option_index":2,4"predicted_code":"ج",5"predicted_option_ar":"الأول يعمل لدى الثاني",6"predicted_relation_full":"Affiliation.employee_of",7"predicted_relation_ontology_id":"employee_of",8"no_relation_logit_bias":-6.66000000000002159}
The shown bias is the frozen value selected on the labeled development split
for the released best checkpoint. It is reused unchanged on official rows.
Do not call a generic text-generation pipeline on the bare adapter and then
interpret أ as a permanent class. A correct reimplementation must preserve
the row's option order, score only its valid code tokens, apply the frozen
no-relation bias, and decode through the same index. The supplied
prepare_input.py and predict.py are the reference implementation.
What is intentionally not in the full Hugging Face release
Real Hugging Face tokens or .env.
The gated 7B base model.
The 133+ MB transformed JSONL dataset.
Any source-dataset cache.
Intermediate checkpoints and optimizer states other than the final staged
latest_checkpoint/ copy.
Notebook prompt experiments.
Temporary benchmark scripts and abandoned objectives.
Rolling trainer/resource logs.
Full per-row validation predictions.
The official per-row predictions are intentionally included under
submission/ because they audit the submitted ZIP; unlabeled official rows are
not validation labels or training data.
A premature adapter snapshot from an incomplete run.
The Azure mirror is intentionally narrower: in addition to everything listed
above, it omits the trained root adapter and the entire latest_checkpoint/
directory. Their hashes and provenance remain in its evidence, while their
actual bytes exist only in the Hugging Face model repository.
This keeps the model repository understandable while preserving every file
needed to download, audit, rebuild, retrain from step 0, evaluate, stage, use
the selected system, and restore the final optimizer state.
Troubleshooting
Gated Yehia download returns 401/403
Confirm that HF_TOKENONE belongs to an account with access to
Navid-AI/Yehia-7B-preview. Do not substitute HF_TOKENTWO; the roles are
separate.
Model is missing while offline mode is enabled
If HF_HUB_OFFLINE=1, Yehia must already exist at
LOCAL_YEHIA_MODEL_DIR. Disable offline mode for the first download, then
re-enable it for subsequent local-only runs.
Do not point the trainer directly at the source WojoodRelations files; they do
not contain the transformed prompt/code fields.
Dataset output already exists
This is a safety stop. Audit or move the existing directory first. Set
OVERWRITE_DATASET_OUTPUT=true only when a destructive rebuild is intended.
CUDA out of memory
First use physical batch 2 and gradient accumulation 8. Keep max length 1,024,
effective batch 16, all-linear targeting, and the same LoRA rank. Reduce
validation batch separately if evaluation is the only failing stage.
Option code is not one token
This indicates a tokenizer/base-model mismatch. Use Yehia's native tokenizer
from the same local snapshot and do not replace the option-code inventory.
Chat-template boundary mismatch
Do not pre-render chat-template tokens in the dataset. Records must contain raw
messages; model.py applies the native template. A boundary error protects
against double templating or tokenizer changes.
tmux session already exists
Attach to the existing session or stop it deliberately. The launcher refuses to
create a second trainer with the same session name.
Loss falls while validation does not improve
Use complete scheduled validation and the locked positive-micro-F1 selection
metric. Do not replace the preserved best checkpoint with the latest checkpoint
based only on training loss.
Reproducibility checklist
Before claiming a faithful retrain, verify:
Dataset commit is a060e47f56025778b97344d4d3de60a8fd53be7c.
Yehia base commit is b9dda4715eafee7e8090d2c83cfe078d75f4ebb8.
All three dataset counts and hashes pass.
audit_dataset.py passes all 19,447 rows.
Prompt version is yehia_re_ar_v3_one_token_choice.
The worked appendix covers all 41 classes with real full prompts/outputs.
لا توجد علاقة is last in every row.
All decision codes are one Yehia token.
Only one assistant decision token contributes to loss.
Base model is 4-bit NF4 with double quantization and BF16 compute.
PEFT receives target_modules="all-linear".
Eligible/wrapped counts are 225/224 and lm_head is frozen.
Trainable parameter count is 39,976,960.
Physical batch 4 and gradient accumulation 4 pass a finite smoke test.
Full validation runs every 250 optimizer steps.
Best checkpoint is chosen by validation positive micro F1.
Official rows are never used for training or intermediate evaluation.
Official submission contains exactly 2,074 IDs in the pinned source order
and only legal canonical relations (no-relation for the negative class).
Release staging occurs only after final training/validation completion.
tools/verify_release.py passes on the local staged folder.
A fresh Hugging Face download passes tools/verify_release.py --remote-clean.
A fresh Azure clone passes tools/verify_azure_release.py --remote-clean
with zero trained payloads.
An end-to-end prepared-input inference passes from a fresh checkout.
Immutable Azure, model, dataset, and base revisions are recorded.
The explicitly manifested final optimizer checkpoint is present on
Hugging Face only; Azure contains no trained model/checkpoint bytes, and
neither destination contains base weights, older checkpoints, logs, caches,
or credentials.
Pre-publication responsibility
No new license file is invented by this package. Before making a public release,
the repository owner must verify and communicate the licenses, access terms,
and redistribution requirements of the Yehia base model, the source dataset,
and the derived dataset. A PEFT adapter does not remove those upstream
obligations.