
{"id":8147,"date":"2025-04-02T16:25:10","date_gmt":"2025-04-02T14:25:10","guid":{"rendered":"https:\/\/www.leviatan.io\/blog\/?p=8147"},"modified":"2025-04-02T18:50:39","modified_gmt":"2025-04-02T16:50:39","slug":"fine-tuning-de-modeles-multimodaux-avec-des-donnees-textuelles-uniquement","status":"publish","type":"post","link":"https:\/\/www.leviatan.io\/blog\/fine-tuning-de-modeles-multimodaux-avec-des-donnees-textuelles-uniquement\/","title":{"rendered":"Fine-tuning de mod\u00e8les multimodaux avec des donn\u00e9es textuelles uniquement"},"content":{"rendered":"\n<h2>Contexte et objectifs<\/h2>\n\n\n\n<p>Dans le domaine de l\u2019intelligence artificielle, un <strong>mod\u00e8le multimodal<\/strong> est capable de traiter plusieurs typologies de donn\u00e9es (par exemple du texte, des images, ou encore de l\u2019audio). Aujourd\u2019hui, de nombreuses applications requi\u00e8rent la compr\u00e9hension et le traitement de divers m\u00e9dias. Cependant, dans notre cas d\u2019usage sp\u00e9cifique, nous devons d\u2019abord nous concentrer sur l\u2019extraction automatique et la mise en forme de contenus exclusivement textuels. \u00c0 terme, nous pr\u00e9voyons d\u2019int\u00e9grer \u00e9galement le traitement d\u2019images associ\u00e9es \u00e0 ces contenus.<\/p>\n\n\n\n<p>Notre objectif est donc de <strong>ne r\u00e9-entra\u00eener que la partie langage du mod\u00e8le multimodal \u00e0 l\u2019aide de donn\u00e9es exclusivement textuelles<\/strong>, afin de pr\u00e9server les capacit\u00e9s d\u00e9j\u00e0 acquises en mati\u00e8re d\u2019analyse d\u2019images, tout en am\u00e9liorant la pr\u00e9cision et les performances pour les t\u00e2ches d\u2019extraction et de traitement du texte. Cette approche nous permettra de tirer pleinement parti des comp\u00e9tences existantes de grands mod\u00e8les, tout en sp\u00e9cialisant sa composante textuelle selon nos besoins.<\/p>\n\n\n\n<h2>D\u00e9fis de recherche<\/h2>\n\n\n\n<p>Dans la pratique, la principale difficult\u00e9 rencontr\u00e9e provient du fait que la plupart des tutoriels et exemples disponibles se concentrent exclusivement sur le <strong>fine-tuning de mod\u00e8les multimodaux avec des donn\u00e9es d\u2019entra\u00eenement combinant texte et images<\/strong>. Il existe tr\u00e8s peu de ressources d\u00e9crivant l\u2019adaptation d\u2019un mod\u00e8le multimodal exclusivement \u00e0 partir de donn\u00e9es textuelles. Pour relever ce d\u00e9fi, nous avons entrepris des travaux de recherches et men\u00e9 des exp\u00e9riences approfondies sur les trois grands mod\u00e8les multimodaux suivants :<\/p>\n\n\n\n<ol><li><strong>Llava 1.6 Vicuna 13B [6] <\/strong><\/li><li><strong>Pixtral 12B<strong> [8]<\/strong><\/strong><\/li><li><strong>Llama-3.2-11B-Vision <strong><strong>[5]<\/strong><\/strong><\/strong><\/li><\/ol>\n\n\n\n<p>Lors de ces travaux, nous avons utilis\u00e9 diff\u00e9rentes plates-formes de fine-tuning (telles que <strong>Unsloth <strong><strong>[4]<\/strong><\/strong><\/strong> ou <strong>PEFT <strong><strong>[3]<\/strong><\/strong> + bitsandbytes <strong><strong>[7]<\/strong><\/strong><\/strong>) afin d\u2019identifier les meilleures approches. <\/p>\n\n\n\n<h2>Strat\u00e9gies de fine-tuning et environnement d\u2019exp\u00e9rimentation<\/h2>\n\n\n\n<p>Dans cette section, nous pr\u00e9sentons les diff\u00e9rentes approches utilis\u00e9es pour le fine-tuning de mod\u00e8les multimodaux sur des donn\u00e9es exclusivement textuelles. Chaque mod\u00e8le a ses propres particularit\u00e9s en termes d\u2019architecture et de compatibilit\u00e9, ce qui n\u00e9cessite des strat\u00e9gies d\u2019adaptation sp\u00e9cifiques.<\/p>\n\n\n\n<ul><li>Llama-3.2-11B-Vision<ul><li>Mod\u00e8le de base (Checkpoint) : <strong>unsloth\/Llama-3.2-11B-Vision-Instruct<\/strong><\/li><li>M\u00e9thode d\u2019entra\u00eenement : utilisation du framework Unsloth<\/li><\/ul><\/li><li>Pixtral-12B<ul><li>Mod\u00e8le de base (Checkpoint) : <strong>unsloth\/Pixtral-12B-2409<\/strong><\/li><li>M\u00e9thode d\u2019entra\u00eenement : utilisation du framework Unsloth<\/li><\/ul><\/li><li>Llava-1.6-Vicuna-13B<ul><li>Mod\u00e8le de base (Checkpoint) : <strong>llava-hf\/llava-v1.6-vicuna-13b-hf<\/strong><\/li><li>M\u00e9thode d\u2019entra\u00eenement : utilisation de PEFT et de bitsandbytes (bnb)<\/li><\/ul><\/li><\/ul>\n\n\n\n<p>Dans cet article, nous pr\u00e9senterons et d\u00e9taillerons la proc\u00e9dure de<strong> fine-tuning de Llava-1.6-Vicuna-13B \u00e0 l\u2019aide de PEFT et bitsandbytes<\/strong>.<\/p>\n\n\n\n<h2>Fine-tuning de Llava-1.6-Vicuna-13B avec PEFT et bitsandbytes : focus sur les couches lin\u00e9aires<\/h2>\n\n\n\n<h3>1. Pr\u00e9paration de l&rsquo;environnement et chargement du mod\u00e8le<\/h3>\n\n\n\n<p>Avant de commencer le fine-tuning, nous devons installer les d\u00e9pendances n\u00e9cessaires pour la phase d&rsquo;entra\u00eenement. Cela inclut <strong>TRL<\/strong>, <strong>PEFT<\/strong>, <strong>Accelerate<\/strong> et <strong>BitsAndBytes<\/strong>, qui permettent l&rsquo;optimisation de la gestion de la m\u00e9moire et l&rsquo;utilisation de techniques d&rsquo;entra\u00eenement efficaces :<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\n! pip install trl peft accelerate\n! pip install -U bitsandbytes\n<\/pre>\n\n\n\n<p>Une fois l&rsquo;environnement pr\u00eat, nous pouvons charger le mod\u00e8le <strong>Llava-1.6-Vicuna-13B<\/strong>, accompagn\u00e9 de son processor :<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom transformers import AutoProcessor, AutoModelForImageTextToText\nimport torch\nfrom transformers import BitsAndBytesConfig\n\nMODEL_ID = &quot;llava-hf\/llava-v1.6-vicuna-13b-hf&quot;\n\n# Chargement du processor\nprocessor = AutoProcessor.from_pretrained(MODEL_ID)\nprocessor.tokenizer.padding_side = &quot;right&quot;\n\n# Configuration pour l'utilisation de QLoRA (quantization 4-bit)\nUSE_QLORA = True\nif USE_QLORA:\n    bnb_config = BitsAndBytesConfig(\n        load_in_4bit=True,\n        bnb_4bit_use_double_quant=True,\n        bnb_4bit_quant_type=&quot;nf4&quot;,\n        bnb_4bit_compute_dtype=torch.float16\n    )\n    model = AutoModelForImageTextToText.from_pretrained(\n        MODEL_ID,\n        torch_dtype=torch.float16,\n        quantization_config=bnb_config,\n    )\n<\/pre>\n\n\n\n<p>Nous utilisons <strong>QLoRA (4-bit quantization)<\/strong> <strong><strong><strong>[1]<strong><strong><strong>[2]<\/strong><\/strong><\/strong><\/strong><\/strong><\/strong> afin d\u2019acc\u00e9l\u00e9rer le fine-tuning et d\u2019optimiser l\u2019utilisation des ressources serveurs, permettant d\u2019entra\u00eener efficacement le mod\u00e8le sur du mat\u00e9riel \u00e0 m\u00e9moire limit\u00e9e.<\/p>\n\n\n\n<p>Le jeu de donn\u00e9es <strong>mlabonne\/FineTome-100k<\/strong> est \u00e9galement charg\u00e9 et les conversations sont transform\u00e9es pour pr\u00e9parer l&rsquo;entra\u00eenement.<\/p>\n\n\n\n<h3>2. Test d&rsquo;inf\u00e9rence en mode texte<\/h3>\n\n\n\n<p>Avant d&rsquo;entamer le fine-tuning, nous testons l\u2019inf\u00e9rence du mod\u00e8le en mode purement textuel (sans images).<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\n# Pr\u00e9paration d'un prompt de conversation\ninstruction = dataset&#x5B;0]&#x5B;&quot;conversations&quot;]&#x5B;0]&#x5B;'value']\nmessages = &#x5B;{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &#x5B;{&quot;type&quot;: &quot;text&quot;, &quot;text&quot;: instruction}]}]\ninput_text = processor.apply_chat_template(messages, add_generation_prompt=True)\n\n# Inf\u00e9rence\ninputs = processor(\n    images=None,\n    text=input_text,\n    add_special_tokens=False,\n    return_tensors=&quot;pt&quot;,\n).to(&quot;cuda&quot;)\ngenerate_ids = model.generate(**inputs, max_new_tokens=2000)\nprint(processor.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False))\n<\/pre>\n\n\n\n<h3>3. Analyse du mod\u00e8le et extraction des couches lin\u00e9aires du module de langage<\/h3>\n\n\n\n<p>Pour n\u2019entra\u00eener que la partie textuelle, nous recherchons dans le mod\u00e8le les modules <strong>Linear4bit<\/strong> qui appartiennent au sous-module <strong>language_model<\/strong> (\u00e0 l\u2019exclusion de <strong>lm_head<\/strong>) :<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport bitsandbytes as bnb\n\ncls = bnb.nn.Linear4bit\nlora_module_names = set()\nfor name, module in model.named_modules():\n    if 'language_model' in name and isinstance(module, cls) and 'lm_head' not in name:\n        lora_module_names.add(name)\nprint(lora_module_names)\n<\/pre>\n\n\n\n<p>Ensuite, nous configurons <strong>LoRA<\/strong> via <strong>PEFT<\/strong> pour cibler uniquement ces couches :<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model\n\nlora_config = LoraConfig(\n    r=8,\n    lora_alpha=8,\n    lora_dropout=0.1,\n    target_modules=lora_module_names,  # Ciblage exclusif des couches lin\u00e9aires du module de langage\n    init_lora_weights=&quot;gaussian&quot;,\n)\nmodel = prepare_model_for_kbit_training(model)\nmodel = get_peft_model(model, lora_config)\n<\/pre>\n\n\n\n<h3>4. Pr\u00e9traitement des donn\u00e9es et cr\u00e9ation du jeu de donn\u00e9es<\/h3>\n\n\n\n<p>Nous convertissons les conversations en texte brut et utilisons un collateur de donn\u00e9es personnalis\u00e9 (<strong>TextDataCollator<\/strong>) pour cr\u00e9er un jeu de donn\u00e9es pr\u00e9-trait\u00e9.<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport torch\n\nclass TextDataCollator:\n    def __init__(self, model, tokenizer, max_length=2048):\n        &quot;&quot;&quot;Initialisation du collateur de donn\u00e9es.&quot;&quot;&quot;\n        self.model = model\n        self.tokenizer = tokenizer\n        self.max_length = max_length\n\n    def __call__(self, examples):\n        &quot;&quot;&quot;Pr\u00e9pare un batch de donn\u00e9es pour l'entra\u00eenement.&quot;&quot;&quot;\n        input_ids = &#x5B;ex&#x5B;&quot;input_ids&quot;]&#x5B;:self.max_length] for ex in examples]\n        attention_mask = &#x5B;ex&#x5B;&quot;attention_mask&quot;]&#x5B;:self.max_length] for ex in examples]\n\n        # Padding des s\u00e9quences\n        input_ids = torch.nn.utils.rnn.pad_sequence(\n            &#x5B;torch.tensor(ids) for ids in input_ids],\n            batch_first=True,\n            padding_value=0\n        )\n\n        attention_mask = torch.nn.utils.rnn.pad_sequence(\n            &#x5B;torch.tensor(mask) for mask in attention_mask],\n            batch_first=True,\n            padding_value=0\n        )\n\n        labels = input_ids.clone()  # Les labels sont identiques aux input_ids\n\n        return {\n            &quot;input_ids&quot;: input_ids,\n            &quot;attention_mask&quot;: attention_mask,\n            &quot;labels&quot;: labels\n        }\n<\/pre>\n\n\n\n<h3>5. Fine-tuning du mod\u00e8le<\/h3>\n\n\n\n<p>Une fois le jeu de donn\u00e9es pr\u00eat, nous lan\u00e7ons l&rsquo;e fine-tuning avec <strong>SFTTrainer<\/strong> de <strong>TRL<\/strong>.<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom trl import SFTTrainer, SFTConfig\n\ntrainer = SFTTrainer(\n    model=model,\n    tokenizer=processor.tokenizer,\n    data_collator=TextDataCollator(model, processor),\n    train_dataset=preprocessed_dataset,  # Jeu de donn\u00e9es pr\u00e9trait\u00e9\n    args=SFTConfig(\n        per_device_train_batch_size=1,\n        gradient_accumulation_steps=4,\n        max_steps=30,\n        learning_rate=2e-4,\n        fp16=True,\n        logging_steps=1,\n        optim=&quot;adamw_8bit&quot;,\n        output_dir=&quot;outputs&quot;,\n    ),\n)\ntrainer.train()\n<\/pre>\n\n\n\n<h3>6. Test d&rsquo;inf\u00e9rence post-affinage<\/h3>\n\n\n\n<p>Apr\u00e8s la phase d&rsquo;entra\u00eenement, nous r\u00e9alisons un nouveau test d&rsquo;inf\u00e9rence pour v\u00e9rifier l&rsquo;impact du fine-tuning sur la performance du mod\u00e8le en mode texte :<\/p>\n\n\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\ninstruction = &quot;Explain what boolean operators are, ...&quot;\nmessages = &#x5B;{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &#x5B;{&quot;type&quot;: &quot;text&quot;, &quot;text&quot;: instruction}]}]\ninput_text = processor.apply_chat_template(messages, add_generation_prompt=True)\n\ninputs = processor(\n    images=None,\n    text=input_text,\n    add_special_tokens=False,\n    return_tensors=&quot;pt&quot;,\n).to(&quot;cuda&quot;)\ngenerate_ids = model.generate(**inputs, max_new_tokens=2000)\nprint(processor.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False))\n<\/pre>\n\n\n\n<h2>Conclusion<\/h2>\n\n\n\n<p>Cet article propose une m\u00e9thode compl\u00e8te permettant de fine-tunn\u00e9 un mod\u00e8le multimodal uniquement avec des donn\u00e9es textuelles, tout en conservant sa capacit\u00e9 de traitement d&rsquo;images. Nous avons principalement pr\u00e9sent\u00e9 l&rsquo;entra\u00eenement de <strong>Llava-1.6-Vicuna-13B<\/strong> \u00e0 l&rsquo;aide de <strong>PEFT<\/strong> et <strong>bitsandbytes<\/strong>, mais avons \u00e9galement entra\u00een\u00e9 <strong>Llama-3.2-11B-Vision<\/strong> et <strong>Pixtral-12B<\/strong> avec <strong>Unsloth<\/strong>, afin d&rsquo;assurer une adaptation efficace aux donn\u00e9es textuelles dans diff\u00e9rents frameworks.<\/p>\n\n\n\n<p>Nous avons valid\u00e9 nos exp\u00e9riences sur des ressources de petites tailles telles que des <strong>GPU T4<\/strong>. Pour des tests ou d\u00e9monstrations \u00e0 petite \u00e9chelle, un T4 est g\u00e9n\u00e9ralement suffisant. Cependant, pour un entra\u00eenement complet et de grande envergure, la puissance et la m\u00e9moire d\u2019un T4 peuvent \u00eatre insuffisantes.<\/p>\n\n\n\n<h2>Disponibilit\u00e9 et licence<\/h2>\n\n\n\n<p>L&rsquo;ensemble des programmes mentionn\u00e9s dans cet article est disponible en open source sous la licence MIT sur le <a href=\"https:\/\/github.com\/LeviatanAI\/fine-tuning-multimodal-text\">GitHub de LeviatanAI<\/a>.<\/p>\n\n\n\n<h2>R\u00e9f\u00e9rences<\/h2>\n\n\n\n<p>[1] T. Dettmers, A. Pagnoni, A. Holtzman, and L. Zettlemoyer, \u201cQLoRA: Efficient finetuning of quantized llms,\u201d arXiv.org. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/arxiv.org\/abs\/2305.14314<\/p>\n\n\n\n<p>[2] E. J. Hu <em>et al.<\/em>, \u201cLoRA: Low-Rank adaptation of large language models,\u201d arXiv.org. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/arxiv.org\/abs\/2106.09685<\/p>\n\n\n\n<p>[3] huggingface, \u201cGitHub &#8211; Huggingface\/peft: \ud83e\udd17 PEFT: State-of-the-art Parameter-Efficient Fine-Tuning.,\u201d GitHub. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/github.com\/huggingface\/peft<\/p>\n\n\n\n<p>[4] unslothai, \u201cGitHub &#8211; Unslothai\/unsloth: Finetune Llama 3.3, DeepSeek-R1, Mistral, Phi-4 &amp; Gemma 2 LLMs 2-5x faster with 70% less memory,\u201d GitHub. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/github.com\/unslothai\/unsloth<\/p>\n\n\n\n<p>[5] \u201cLlama,\u201d Meta Llama. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/www.llama.com\/<\/p>\n\n\n\n<p>[6] H. Liu, \u201cLLaVA-NeXT: Improved reasoning, OCR, and world knowledge,\u201d LLaVA. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/llava-vl.github.io\/blog\/2024-01-30-llava-next\/<\/p>\n\n\n\n<p>[7] bitsandbytes-foundation, \u201cGitHub &#8211; Bitsandbytes-foundation\/bitsandbytes: Accessible large language models via k-bit quantization for PyTorch.,\u201d GitHub. Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/github.com\/bitsandbytes-foundation\/bitsandbytes<\/p>\n\n\n\n<p>[8] \u201cmistralai\/Pixtral-12B-2409 \u00b7 Hugging Face.\u201d Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/huggingface.co\/mistralai\/Pixtral-12B-2409<\/p>\n\n\n\n<p>[9] \u201c\ud83e\udd17 Transformers.\u201d Accessed: Feb. 06, 2025. [Online]. Available: https:\/\/huggingface.co\/docs\/transformers\/index&nbsp;&nbsp;&nbsp;&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Contexte et objectifs Dans le domaine de l\u2019intelligence artificielle, un mod\u00e8le multimodal est capable de traiter plusieurs typologies de donn\u00e9es (par exemple du texte, des images, ou encore de l\u2019audio). Aujourd\u2019hui, de nombreuses applications requi\u00e8rent la compr\u00e9hension et le traitement de divers m\u00e9dias. Cependant, dans notre cas d\u2019usage sp\u00e9cifique, nous devons d\u2019abord nous concentrer sur [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[2,3],"tags":[],"_links":{"self":[{"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/posts\/8147"}],"collection":[{"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/comments?post=8147"}],"version-history":[{"count":134,"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/posts\/8147\/revisions"}],"predecessor-version":[{"id":8345,"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/posts\/8147\/revisions\/8345"}],"wp:attachment":[{"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/media?parent=8147"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/categories?post=8147"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.leviatan.io\/blog\/wp-json\/wp\/v2\/tags?post=8147"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}