دليل التجارب ← ملاحظات إصدار v0.1 المصدر
ember / experiments / مرجع

عقد الـ hooks

تضم الواجهة تسعة hooks في الـ lifecycle وأربعة أنواع context لا يمكن تعديلها. وللوصول إلى الـ tensor وتعديلها، توفر TensorAccess view لا تملك البيانات. وتوضح هذه الصفحة معنى كل حد وضماناته في ember v0.1.

مرجع API v0.1 غير مستقر تجربة نشطة واحدة
generation ناجح
on_model_loaded()
before_prefill()

prefill:
  layer hooks
  logits hooks

each decode evaluation:
  layer hooks
  logits hooks

on_generation_complete()
ترتيب ثابت وتنفيذ synchronous
حالة العقد

يصنّف الـ public API عناصر experiment الظاهرة فيه على أنها experimental، ويستخدم الكود #[non_exhaustive] حيث يلزم. وحتى تستقر هذه الـ API، قد تتغير الأسماء والحقول.

لا تشير أسماء الـ hooks إلى خطوات implementation قابلة للتبديل، بل إلى مراحل ذات معنى محدد. وينفذها ember synchronously وبترتيب ثابت، مع احتفاظ كل معمارية بمسارها العددي الصريح إلى الحد نفسه.

الـ lifecycle

في generation run عادي وناجح، يستدعي ember on_model_loaded() مرة واحدة، ثم before_prefill() مرة واحدة. بعد ذلك يجري تقييم prefill واحدًا عبر layer hooks وlogits hooks، ويليه صفر أو أكثر من تقييمات decode عبر الـ hooks نفسها. وعند اكتمال المسار، يستدعي on_generation_complete() مرة واحدة.

lifecycle في generation ناجح
on_model_loaded()
before_prefill()
prefill hooks
decode hooks × N
on_generation_complete()
ترتيب ثابت وتنفيذ synchronous

لا يواصل ember التنفيذ إلا بعد عودة الـ hook الحالي. فإذا فشل الـ hook، يتوقف generation الحالي، ولا يستدعي ember الـ hooks اللاحقة.

النطاق الـ hook القيمة المكشوفة الوصول
النموذج on_model_loaded ModelContext metadata
الإعداد before_prefill ExecutionContext metadata
كل طبقة before_layer الـ hidden state الداخلة view mutable وغير مالكة
كل طبقة after_attention مساهمة attention في الـ residual view mutable وغير مالكة
كل طبقة after_mlp مساهمة MLP في الـ residual view mutable وغير مالكة
كل طبقة after_layer الـ hidden state المكتملة للطبقة view mutable وغير مالكة
التقييم before_logits hidden state لآخر token بعد final normalization view mutable وغير مالكة
التقييم after_logits الـ logits التي يعيدها المسار بعد postprocessing الخاص بالعائلة view mutable وغير مالكة
generation on_generation_complete GenerationContext metadata

يبدأ تشغيل الـ hooks الستة التي تحمل tensors تحت phase=Prefill. ثم يشغلها ember تحت phase=Decode مع كل تقييم decode أحادي التوكن. ويبدأ ترقيم الطبقات من الصفر، وفق ترتيب تنفيذ النموذج.

واجهة الـ trait

لا يلزم تنفيذ سوى name. وتعيد كل method في الـ lifecycle افتراضيًا نتيجة ناجحة من دون أي عمل؛ وبذلك لا تطبّق التجربة المدمجة إلا الـ methods التي تحتاج إليها.

pub trait Experiment: Send {
    fn name(&self) -> &'static str;

    fn on_model_loaded(
        &mut self,
        ctx: &ModelContext<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn before_prefill(
        &mut self,
        ctx: &ExecutionContext<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn before_layer(
        &mut self,
        ctx: &LayerContext<'_>,
        hidden: &mut TensorAccess<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn after_attention(
        &mut self,
        ctx: &LayerContext<'_>,
        attention_output: &mut TensorAccess<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn after_mlp(
        &mut self,
        ctx: &LayerContext<'_>,
        mlp_output: &mut TensorAccess<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn after_layer(
        &mut self,
        ctx: &LayerContext<'_>,
        hidden: &mut TensorAccess<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn before_logits(
        &mut self,
        ctx: &ExecutionContext<'_>,
        hidden: &mut TensorAccess<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn after_logits(
        &mut self,
        ctx: &ExecutionContext<'_>,
        logits: &mut TensorAccess<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }

    fn on_generation_complete(
        &mut self,
        ctx: &GenerationContext<'_>,
    ) -> Result<(), ExperimentError> { Ok(()) }
}

أنواع الـ context

يعرّف العقد أربعة أنواع context، تأتي جميعها في بُنى خفيفة لا يمكن تعديلها، وتستعير البيانات التي تحتاج إليها. ولا تتيح هذه البُنى token buffers أو metadata GGUF الكاملة أو weight handles أو caches أو تفاصيل الـ backend الداخلية.

الـ context الحقول تُستخدم في
ModelContext العائلة، ومعرّف اختياري، والـ architecture، وعدد الطبقات، وحجم hidden state تحميل النموذج؛ وتدخل ضمن بقية الـ contexts
ExecutionContext النموذج، والـ phase، وموضع البداية، وعدد input tokens، وطول التسلسل الناتج، وحالة tracing hooks الخاصة بالـ prefill والطبقات والـ logits
LayerContext execution context ورقم طبقة يبدأ ترقيمه من الصفر أربعة tensor hooks في كل طبقة
GenerationContext النموذج، وعدد prompt tokens، وعدد التوكنات المولدة، وعدد تقييمات decode، وحالة tracing عند الاكتمال الناجح

موضع التنفيذ

start_position
الموضع المطلق لأول input token في هذا التقييم
input_token_count
عدد input tokens التي يعالجها هذا التقييم
sequence_length
start_position + input_token_count بعد إدخال التوكنات الحالية
token_position()
يعيد موضعاً مطلقاً فقط للتقييمات التي تحتوي token واحداً، كما في decode العادي؛ وإلا يعيد None

الـ enums الصغيرة

ModelFamily
Llama, Qwen3, Gemma4
ExecutionPhase
Prefill, Decode
TracingState
Disabled, Enabled
TensorDType
F32 في v0.1

TensorAccess

تتيح TensorAccess الوصول إلى activation قائمة من خلال view لا تملك البيانات، مع السماح بتعديلها. وفي v0.1، تكون هذه الـ activation contiguous وثنائية الأبعاد ومن نوع f32، ويثبت shape على [rows, columns]. وتظل ملكية allocation والـ buffer للـ runtime، فلا يحتاج الـ hook إلى نسخة مستقلة.

المسموح
  • فحص shape وdtype؛
  • قراءة قيم f32 المكشوفة؛
  • تعديل القيم داخل الـ slice الموجودة؛
  • تصفير القيم الموجودة.
غير المسموح
  • تغيير الحجم؛
  • إعادة تخصيص الذاكرة؛
  • تغيير shape أو dtype؛
  • استبدال الـ buffer أو أخذ ملكيته؛
  • الوصول إلى الأوزان أو caches أو scratch storage أو بيانات التوكنات.
let shape: &[usize; 2] = tensor.shape();
let dtype: TensorDType = tensor.dtype();
let observed: &[f32] = tensor.values();

// explicit intervention on the same allocation:
for value in tensor.values_mut() {
    *value *= 0.5;
}

// or replace existing values with zero:
tensor.zero();

لا يفرض نظام الأنواع في الواجهة سلوك observation-only؛ لذلك يتحمل implementation مسؤولية هذا الضمان. يحافظ استخدام values() على التنفيذ، في حين قد يغيّره استخدام values_mut() أو zero().

التنفيذ عند tensor hook
prefill / decode
semantic hook
tensor موجودة
observe أو mutate
استئناف التنفيذ

دلالات عائلات النماذج

تدل الأسماء المشتركة على الحدود نفسها، لكنها لا تجعل الـ blocks الداخلية متطابقة. فلكل معمارية ترتيبها وعملياتها العددية في الوصول إلى الحد، وتبقي واجهة الـ hooks هذه الفروق ظاهرة.

الحد LLaMA / Qwen3 Gemma 4
after_attention بعد O projection، وقبل residual addition بعد O projection وpost-attention RMS norm، وقبل residual addition
after_mlp بعد down projection، وقبل residual addition بعد down projection وpost-FFN RMS norm، وقبل residual addition
after_layer بعد MLP residual addition بعد عمليات الـ residual وPLE وscaling لناتج الطبقة
before_logits hidden state لآخر token بعد final normalization hidden state لآخر token بعد final normalization
after_logits LM head output ناتج LM head بعد final logit softcap
فروق Qwen تبقى صريحة

يستخدم Qwen3 تنفيذ الـ block المشترك مع عائلة LLaMA، مع الحفاظ على split-half RoPE وQK normalization وترتيب العمليات الخاص به. لذلك لا يعني اشتراكه في اسم الـ hook أن ما تكشفه المعماريتان قابل للتبادل.

تعمل single-token hooks النشطة في LLaMA مباشرة على decode workspace مخصصة مسبقًا، من غير أن تفرض generic tensor path. وفي Gemma، لا يتغير dispatch بين مساري MLP الـ packed والـ generic.

المسار المعطّل

لا يبني generation العادي ExperimentRunner أصلًا. وتكون دوال النموذج generic على نوع hooks adapter؛ فعند التعطيل تستخدم DisabledHooks، وهو adapter بحجم صفر، وتكون الـ methods داخله #[inline(always)] no-ops.

pub(crate) struct DisabledHooks;

impl<T, E> LayerHooks<T, E> for DisabledHooks {
    #[inline(always)]
    fn after_attention(
        &mut self,
        _layer_index: usize,
        _tensor: &mut T,
    ) -> Result<(), E> {
        Ok(())
    }
    // the other disabled methods have the same shape.
}
ضمان المسار المعطّل

أظهر فحص المسار المعطّل أن الكود الناتج لا يتضمن أي experiment-related dispatch. كذلك لم ترصد الاختبارات أي نمو في عدد allocations لكل طبقة بعد warm-up، وظلت قياسات A/B المضبوطة للـ prefill والـ single-token decode ضمن ضجيج القياس.

يواصل decode المحسّن في LLaMA استخدام workspace المخصصة مسبقًا، من غير أن تفرض الـ hooks الـ generic tensor path. ولا يتغير في Gemma dispatch الـ MLP بين packed وgeneric.

Failure path

تعيد تنفيذات الـ hooks ExperimentError، ثم يغلّفه ExperimentRunner داخل ExperimentFailure قبل أن يمر إلى مسار الأخطاء العادي في ember.

الحقل يُضمّن عندما يكون متاحاً
اسم التجربة دائماً
اسم الـ hook دائماً
مرحلة التنفيذ أخطاء prefill والطبقات والـ logits
رقم الطبقة أخطاء كل طبقة
رسالة الخطأ الأصلية دائماً
experiment 'my-experiment' failed in after_attention
(phase=decode, layer=7): expected a finite activation

تعمل الـ hooks synchronously. فإذا فشل أحدها، توقف generation الحالي، ولم يسجل ember اكتماله على أنه ناجح، ولم يستدعِ الـ hooks اللاحقة.

حدود واجهات الفحص

قيم التجربة النشطة ليست قيم dump

لا تعمل التجارب النشطة في v0.1 مع استخراج hidden states أو probes أو --dump-layers أو --dump-logits، ولا مع أوامر demo أو interactive أو benchmark الفرعية. ويرفض ember هذه التركيبات حتى لا يعرض قيمًا يلتبس معناها.

لم يحدد ember بعد ما إذا كانت واجهات الفحص ستعرض القيم قبل intervention أم بعده. فالمسألة تتعلق بمعنى القيم المعروضة، لا بغياب وسيلة لنسخ البيانات.

لا يغيّر هذا العقد tracing العادي من دون تجربة، ولا استخراج hidden states أو layer dumps أو logits dumps أو تقارير benchmark أو dispatch للـ packed kernels. ويمكن تشغيل structured tracing مع تجربة؛ وعندئذ يتيح ExecutionContext الاطلاع على حالته من دون تعديلها. ولا تستبدل الـ hooks trace stream أو تعيد تعريفه.

خريطة المصدر

src/experiments/mod.rs : الـ trait والـ runner وسياق الفشل والـ adapters النشطة والمعطّلة
src/experiments/context.rs : الـ contexts والـ enums وTensorAccess
src/experiments/activation_stats.rs : التنفيذ المرجعي للـ observation
src/experiments/zero_layer_output.rs : التنفيذ المرجعي للـ intervention
src/llama.rs : حدود prefill وfast decode في LLaMA/Qwen
src/gemma4.rs : الحدود الخاصة بعائلة Gemma 4
src/main.rs : إنشاء CLI والـ lifecycle والـ provenance