Assessment 3 — Model Customization

Brings a custom-imported model into the same chat app as the four native Bedrock foundation models, and fine-tunes it on the Bitext customer-support dataset. Everything below is live behind the same CloudFront + Cognito pair as the rest of the app.

1. Base Model

Qwen 2.5 1.5B Instruct, imported via Amazon Bedrock Custom Model Import (CMI). Chosen because:

2. Fine-Tuning Approach (L250)

Supervised LoRA fine-tune on the Bitext customer-support chatbot training dataset (26,872 rows). The task is intent extraction and action-plan generation — for each customer message the model should emit a single JSON object: {"intent", "category", "response"}.

SettingValue
TechniqueLoRA (r=16, α=32) on q_proj, k_proj, v_proj, o_proj
Epochs1
Effective batch size16 (batch 4 × grad accum 4)
Learning rate2e-4 with cosine schedule, 3% warmup
Precisionbf16 with gradient checkpointing
Split95 / 5 train / val (25,528 / 1,344)
Training platformSageMaker HuggingFace DLC, 1× ml.g5.xlarge (A10G 24 GB)
Training time57 minutes billable, ~$1.34

Adapters are merged back into the base weights at the end of training so CMI can import a standard HF-format model — CMI does not accept LoRA adapters directly.

3. Latency and Cost — vs Native Bedrock Models

3 prompts × 3 runs = 9 measurements per model, medians reported. Measured end-to-end through the production chat Lambda, same code path the UI uses.

ModelTTFTTotalOut tokensCost/req
Claude Sonnet 4.6 (native)1.06s5.17s207$0.003147
GPT-OSS 20B (native)0.39s1.42s242$0.000060
Qwen 2.5 1.5B (CMI base)0.18s1.23s216$0.000048
Qwen 2.5 1.5B (CMI Bitext FT)0.22s0.75s112$0.000027

The fine-tuned Qwen model is the fastest and cheapest of the four: 5× lower TTFT than Claude Sonnet, ~7× lower total time, ~116× lower cost per request. The speed-up over the base Qwen is entirely from shorter replies — the FT model learned the Bitext dataset's concise response style.

Cost note: CMI is actually billed per Custom Model Unit per 5-minute window, not per token. The figures above use the same per-token estimate we show in the chat UI so columns are directly comparable; at our traffic volume the two numbers are within ~10% of each other.

4. Five Example Inputs — Fine-Tuned Model

Produced by invoke_ft_model.py against the imported FT model.

[1] (1.13s) I want to cancel my subscription please

{"intent": "cancel_account", "category": "ACCOUNT", "response": "Thank you for reaching out about canceling your subscription. I understand that you may have changed your mind or need assistance in unsubscribing from our services. To proceed with the cancellation process, could you please provide me with some additional details? Specifically, I would like to know the email address associated with your subscription so that I can locate your account information accurately. Once I have this information, I will guide you through the steps necessary to cancel your subscription successfully."}

[2] (0.74s) Where is my refund? It's been 3 weeks.

{"intent": "track_refund", "category": "REFUND", "response": "I understand your frustration about the delay in receiving your refund. I apologize for any inconvenience caused. To provide you with an accurate update on the status of your refund, could you please provide me with your {{Order Number}} or {{Refund Reference}}? With this information, I will be able to assist you further and ensure that your refund is processed promptly."}

[3] (0.56s) Can you help me change the shipping address on my order?

{"intent": "change_shipping_address", "category": "SHIPPING", "response": "Sure! I can assist you in changing the shipping address for your order. Please provide me with the current shipping address and the new address you would like to update it to."}

[4] (0.68s) I got charged twice for the same item.

{"intent": "track_invoice", "category": "INVOICE", "response": "Thank you for bringing this to our attention. I apologize for the inconvenience caused by the duplicate charges on your account. To assist you further, could you please provide me with some additional details such as the {{Invoice Number}} or any other relevant information? This will help us investigate the issue promptly and ensure that it is resolved in a timely manner."}

[5] (1.66s) How do I reset my password?

{"intent": "recover_password", "category": "ACCOUNT", "response": "I'll get right on it! I understand that you're looking for guidance on how to reset your password. Let me walk you through the process step by step:\n\n1. Start by visiting our platform's \"{{Login Page URL}}\".\n...\nIf you encounter any difficulties during the process or if you have any further questions, feel free to reach out to us."}

Every reply is well-formed JSON with canonical Bitext intent labels (track_refund, recover_password, change_shipping_address, etc.) and the matching uppercase category (REFUND, ACCOUNT, SHIPPING). The dataset's {{placeholder}} convention for customer-specific details comes through in replies 2 and 5 — that's verbatim from the training set.

5. Trade-offs Observed

Base vs fine-tuned

The base Qwen model happily free-forms an answer in prose. The fine-tuned model always emits the JSON schema, uses the Bitext intent vocabulary, and picks up the dataset's stylistic quirks (explicit empathy statements, {{placeholders}} for unknown customer data, numbered step-by-step walkthroughs). Useful for a structured pipeline; less good for open-ended chat.

CMI vs native

6. Pipeline Summary

Base import (L200)
  HF snapshot (Qwen 2.5 1.5B)
    → upload to S3 (2.9 GB)
      → CreateModelImportJob (~2-10 min)
        → ARN in cmi.auto.tfvars
          → terraform apply (Lambda env + frontend dropdown)

Fine-tune (L250)
  Bitext CSV (27K rows)
    → JSONL with {intent, category, response} targets
      → SageMaker HuggingFace DLC, ml.g5.xlarge (~60 min, ~$1.50)
        → LoRA merged into base weights in /opt/ml/model
          → tar.gz → unpack_and_stage.py → flat HF in S3
            → CreateModelImportJob (~13 min)
              → ARN in cmi.auto.tfvars → terraform apply

7. Automated Evaluation (L250)

Account 577638392815 is granted s3:GetObject and prefix-scoped s3:ListBucket on the CMI bucket's two model prefixes (qwen2.5-1.5b-base/, qwen2.5-1.5b-bitext/) for the SUP SA daily leaderboard. No other actions, no access to training data.

See also: Eval Dashboard to run the LLM-as-judge test suite across all models including the two CMI variants.