RayMelius Claude Opus 4.6 commited on
Commit
a202fe1
Β·
1 Parent(s): 57e97ca

Replace model merge with direct adapter push to HF Hub

Browse files

- Skip merge_and_unload which causes peft/accelerate TypeError on T4
- Push LoRA adapter + tokenizer directly via HfApi.upload_folder
- Update inference test to load adapter with PeftModel.from_pretrained
- Remove redundant push cell

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

notebooks/ch_trader_finetune.ipynb CHANGED
@@ -1244,14 +1244,14 @@
1244
  },
1245
  {
1246
  "cell_type": "markdown",
1247
- "source": "## 5. Save & Push to HuggingFace Hub\n\nMerges LoRA adapters into the base model weights and pushes the full model.",
1248
  "metadata": {
1249
  "id": "save-header"
1250
  }
1251
  },
1252
  {
1253
  "cell_type": "code",
1254
- "source": "from peft import PeftModel\n\n# Reload base model in fp16 for merging (can't merge with 4-bit)\nprint(\"Reloading base model in fp16 for adapter merge...\")\ndel model\ntorch.cuda.empty_cache()\n\nbase_model = AutoModelForCausalLM.from_pretrained(\n BASE_MODEL,\n torch_dtype=torch.float16,\n device_map=\"auto\",\n trust_remote_code=True,\n)\nmerged_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR)\nmerged_model = merged_model.merge_and_unload()\nprint(\"Adapters merged.\")",
1255
  "metadata": {
1256
  "id": "save-model",
1257
  "trusted": true
@@ -1259,16 +1259,6 @@
1259
  "outputs": [],
1260
  "execution_count": null
1261
  },
1262
- {
1263
- "cell_type": "code",
1264
- "source": "print(f\"Pushing merged model to: {OUTPUT_REPO}\")\nmerged_model.push_to_hub(\n OUTPUT_REPO,\n token=HF_TOKEN,\n commit_message=\"StockEx CH Trader: QLoRA fine-tuned Qwen2.5-32B-Instruct\",\n)\ntokenizer.push_to_hub(\n OUTPUT_REPO,\n token=HF_TOKEN,\n commit_message=\"Tokenizer for StockEx CH Trader (Qwen2.5-32B-Instruct base)\",\n)\nprint(f\"βœ“ Model pushed to https://huggingface.co/{OUTPUT_REPO}\")",
1265
- "metadata": {
1266
- "id": "push-hub",
1267
- "trusted": true
1268
- },
1269
- "outputs": [],
1270
- "execution_count": null
1271
- },
1272
  {
1273
  "cell_type": "markdown",
1274
  "source": "## 6. Inference Test\n\nVerify the model generates valid JSON trading decisions.",
@@ -1278,7 +1268,7 @@
1278
  },
1279
  {
1280
  "cell_type": "code",
1281
- "source": "import re\nfrom transformers import pipeline\n\npipe = pipeline(\n \"text-generation\",\n model=merged_model,\n tokenizer=tokenizer,\n device_map=\"auto\",\n)\n\ntest_cases = [\n {\n \"desc\": \"New member, no holdings, must trade\",\n \"capital\": 100_000.0,\n \"holdings\": [],\n \"obligation\": 10,\n },\n {\n \"desc\": \"Experienced member with holdings, low obligation\",\n \"capital\": 65_000.0,\n \"holdings\": [\n {\"symbol\": \"ALPHA\", \"quantity\": 300, \"avg_cost\": 5.60},\n {\"symbol\": \"QUEST\", \"quantity\": 150, \"avg_cost\": 13.20},\n ],\n \"obligation\": 2,\n },\n {\n \"desc\": \"Low capital, large holdings\",\n \"capital\": 8_000.0,\n \"holdings\": [\n {\"symbol\": \"PEIR\", \"quantity\": 500, \"avg_cost\": 8.30},\n {\"symbol\": \"NBG\", \"quantity\": 200, \"avg_cost\": 7.95},\n ],\n \"obligation\": 5,\n },\n]\n\ntest_bbos = {s[\"symbol\"]: gen_bbo(s[\"base\"]) for s in SECURITIES}\n\nprint(\"=\" * 70)\nfor tc in test_cases:\n print(f\"\\nSCENARIO: {tc['desc']}\")\n prompt = build_prompt(\"USR01\", tc[\"capital\"], tc[\"holdings\"], tc[\"obligation\"], test_bbos)\n messages = [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": prompt},\n ]\n output = pipe(\n messages,\n max_new_tokens=60,\n temperature=0.3,\n do_sample=True,\n pad_token_id=tokenizer.eos_token_id,\n )\n response = output[0][\"generated_text\"][-1][\"content\"].strip()\n print(f\"RESPONSE: {response}\")\n try:\n m = re.search(r\"\\{[^}]+\\}\", response)\n if m:\n d = json.loads(m.group())\n assert d[\"side\"] in (\"BUY\", \"SELL\")\n assert d[\"symbol\"] in [s[\"symbol\"] for s in SECURITIES]\n assert d[\"quantity\"] > 0\n assert d[\"price\"] > 0\n print(f\"βœ“ Valid JSON: {d}\")\n else:\n print(\"βœ— No JSON found in response\")\n except Exception as e:\n print(f\"βœ— Invalid: {e}\")\n print(\"-\" * 70)",
1282
  "metadata": {
1283
  "id": "inference-test",
1284
  "trusted": true
 
1244
  },
1245
  {
1246
  "cell_type": "markdown",
1247
+ "source": "## 5. Save & Push to HuggingFace Hub\n\nPushes the LoRA adapter and tokenizer directly to HuggingFace Hub.\nThe adapter can be loaded at inference time with `PeftModel.from_pretrained()` β€” merging into the base model is not required and avoids OOM on T4.",
1248
  "metadata": {
1249
  "id": "save-header"
1250
  }
1251
  },
1252
  {
1253
  "cell_type": "code",
1254
+ "source": "from huggingface_hub import HfApi\n\n# Tokenizer was already saved in the training cell; ensure it's there\ntokenizer.save_pretrained(OUTPUT_DIR)\n\n# Push adapter + tokenizer to HF Hub\napi = HfApi(token=HF_TOKEN)\napi.upload_folder(\n folder_path=OUTPUT_DIR,\n repo_id=OUTPUT_REPO,\n commit_message=f\"StockEx CH Trader: QLoRA fine-tuned {BASE_MODEL} (adapter)\",\n)\nprint(f\"Pushed to https://huggingface.co/{OUTPUT_REPO}\")",
1255
  "metadata": {
1256
  "id": "save-model",
1257
  "trusted": true
 
1259
  "outputs": [],
1260
  "execution_count": null
1261
  },
 
 
 
 
 
 
 
 
 
 
1262
  {
1263
  "cell_type": "markdown",
1264
  "source": "## 6. Inference Test\n\nVerify the model generates valid JSON trading decisions.",
 
1268
  },
1269
  {
1270
  "cell_type": "code",
1271
+ "source": "import re\nfrom transformers import pipeline\nfrom peft import PeftModel\n\n# Load adapter for inference test\nprint(\"Loading base model + adapter for inference test...\")\ndel trainer\ntorch.cuda.empty_cache()\n\nbase_model = AutoModelForCausalLM.from_pretrained(\n BASE_MODEL,\n quantization_config=BitsAndBytesConfig(\n load_in_4bit=True,\n bnb_4bit_quant_type=\"nf4\",\n bnb_4bit_compute_dtype=torch.bfloat16,\n bnb_4bit_use_double_quant=True,\n ),\n device_map=\"auto\",\n trust_remote_code=True,\n)\ninference_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR)\ninference_model.eval()\n\npipe = pipeline(\n \"text-generation\",\n model=inference_model,\n tokenizer=tokenizer,\n device_map=\"auto\",\n)\n\ntest_cases = [\n {\n \"desc\": \"New member, no holdings, must trade\",\n \"capital\": 100_000.0,\n \"holdings\": [],\n \"obligation\": 10,\n },\n {\n \"desc\": \"Experienced member with holdings, low obligation\",\n \"capital\": 65_000.0,\n \"holdings\": [\n {\"symbol\": \"ALPHA\", \"quantity\": 300, \"avg_cost\": 5.60},\n {\"symbol\": \"QUEST\", \"quantity\": 150, \"avg_cost\": 13.20},\n ],\n \"obligation\": 2,\n },\n {\n \"desc\": \"Low capital, large holdings\",\n \"capital\": 8_000.0,\n \"holdings\": [\n {\"symbol\": \"PEIR\", \"quantity\": 500, \"avg_cost\": 8.30},\n {\"symbol\": \"NBG\", \"quantity\": 200, \"avg_cost\": 7.95},\n ],\n \"obligation\": 5,\n },\n]\n\ntest_bbos = {s[\"symbol\"]: gen_bbo(s[\"base\"]) for s in SECURITIES}\n\nprint(\"=\" * 70)\nfor tc in test_cases:\n print(f\"\\nSCENARIO: {tc['desc']}\")\n prompt = build_prompt(\"USR01\", tc[\"capital\"], tc[\"holdings\"], tc[\"obligation\"], test_bbos)\n messages = [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": prompt},\n ]\n output = pipe(\n messages,\n max_new_tokens=60,\n temperature=0.3,\n do_sample=True,\n pad_token_id=tokenizer.eos_token_id,\n )\n response = output[0][\"generated_text\"][-1][\"content\"].strip()\n print(f\"RESPONSE: {response}\")\n try:\n m = re.search(r\"\\{[^}]+\\}\", response)\n if m:\n d = json.loads(m.group())\n assert d[\"side\"] in (\"BUY\", \"SELL\")\n assert d[\"symbol\"] in [s[\"symbol\"] for s in SECURITIES]\n assert d[\"quantity\"] > 0\n assert d[\"price\"] > 0\n print(f\"Valid JSON: {d}\")\n else:\n print(\"No JSON found in response\")\n except Exception as e:\n print(f\"Invalid: {e}\")\n print(\"-\" * 70)",
1272
  "metadata": {
1273
  "id": "inference-test",
1274
  "trusted": true
notebooks/stockex-clearing-house-llm-fine-tuning.ipynb CHANGED
@@ -1244,14 +1244,14 @@
1244
  },
1245
  {
1246
  "cell_type": "markdown",
1247
- "source": "## 5. Save & Push to HuggingFace Hub\n\nMerges LoRA adapters into the base model weights and pushes the full model.",
1248
  "metadata": {
1249
  "id": "save-header"
1250
  }
1251
  },
1252
  {
1253
  "cell_type": "code",
1254
- "source": "from peft import PeftModel\n\n# Reload base model in fp16 for merging (can't merge with 4-bit)\nprint(\"Reloading base model in fp16 for adapter merge...\")\ndel model\ntorch.cuda.empty_cache()\n\nbase_model = AutoModelForCausalLM.from_pretrained(\n BASE_MODEL,\n torch_dtype=torch.float16,\n device_map=\"auto\",\n trust_remote_code=True,\n)\nmerged_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR)\nmerged_model = merged_model.merge_and_unload()\nprint(\"Adapters merged.\")",
1255
  "metadata": {
1256
  "id": "save-model",
1257
  "trusted": true
@@ -1259,16 +1259,6 @@
1259
  "outputs": [],
1260
  "execution_count": null
1261
  },
1262
- {
1263
- "cell_type": "code",
1264
- "source": "print(f\"Pushing merged model to: {OUTPUT_REPO}\")\nmerged_model.push_to_hub(\n OUTPUT_REPO,\n token=HF_TOKEN,\n commit_message=\"StockEx CH Trader: QLoRA fine-tuned Qwen2.5-32B-Instruct\",\n)\ntokenizer.push_to_hub(\n OUTPUT_REPO,\n token=HF_TOKEN,\n commit_message=\"Tokenizer for StockEx CH Trader (Qwen2.5-32B-Instruct base)\",\n)\nprint(f\"βœ“ Model pushed to https://huggingface.co/{OUTPUT_REPO}\")",
1265
- "metadata": {
1266
- "id": "push-hub",
1267
- "trusted": true
1268
- },
1269
- "outputs": [],
1270
- "execution_count": null
1271
- },
1272
  {
1273
  "cell_type": "markdown",
1274
  "source": "## 6. Inference Test\n\nVerify the model generates valid JSON trading decisions.",
@@ -1278,7 +1268,7 @@
1278
  },
1279
  {
1280
  "cell_type": "code",
1281
- "source": "import re\nfrom transformers import pipeline\n\npipe = pipeline(\n \"text-generation\",\n model=merged_model,\n tokenizer=tokenizer,\n device_map=\"auto\",\n)\n\ntest_cases = [\n {\n \"desc\": \"New member, no holdings, must trade\",\n \"capital\": 100_000.0,\n \"holdings\": [],\n \"obligation\": 10,\n },\n {\n \"desc\": \"Experienced member with holdings, low obligation\",\n \"capital\": 65_000.0,\n \"holdings\": [\n {\"symbol\": \"ALPHA\", \"quantity\": 300, \"avg_cost\": 5.60},\n {\"symbol\": \"QUEST\", \"quantity\": 150, \"avg_cost\": 13.20},\n ],\n \"obligation\": 2,\n },\n {\n \"desc\": \"Low capital, large holdings\",\n \"capital\": 8_000.0,\n \"holdings\": [\n {\"symbol\": \"PEIR\", \"quantity\": 500, \"avg_cost\": 8.30},\n {\"symbol\": \"NBG\", \"quantity\": 200, \"avg_cost\": 7.95},\n ],\n \"obligation\": 5,\n },\n]\n\ntest_bbos = {s[\"symbol\"]: gen_bbo(s[\"base\"]) for s in SECURITIES}\n\nprint(\"=\" * 70)\nfor tc in test_cases:\n print(f\"\\nSCENARIO: {tc['desc']}\")\n prompt = build_prompt(\"USR01\", tc[\"capital\"], tc[\"holdings\"], tc[\"obligation\"], test_bbos)\n messages = [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": prompt},\n ]\n output = pipe(\n messages,\n max_new_tokens=60,\n temperature=0.3,\n do_sample=True,\n pad_token_id=tokenizer.eos_token_id,\n )\n response = output[0][\"generated_text\"][-1][\"content\"].strip()\n print(f\"RESPONSE: {response}\")\n try:\n m = re.search(r\"\\{[^}]+\\}\", response)\n if m:\n d = json.loads(m.group())\n assert d[\"side\"] in (\"BUY\", \"SELL\")\n assert d[\"symbol\"] in [s[\"symbol\"] for s in SECURITIES]\n assert d[\"quantity\"] > 0\n assert d[\"price\"] > 0\n print(f\"βœ“ Valid JSON: {d}\")\n else:\n print(\"βœ— No JSON found in response\")\n except Exception as e:\n print(f\"βœ— Invalid: {e}\")\n print(\"-\" * 70)",
1282
  "metadata": {
1283
  "id": "inference-test",
1284
  "trusted": true
 
1244
  },
1245
  {
1246
  "cell_type": "markdown",
1247
+ "source": "## 5. Save & Push to HuggingFace Hub\n\nPushes the LoRA adapter and tokenizer directly to HuggingFace Hub.\nThe adapter can be loaded at inference time with `PeftModel.from_pretrained()` β€” merging into the base model is not required and avoids OOM on T4.",
1248
  "metadata": {
1249
  "id": "save-header"
1250
  }
1251
  },
1252
  {
1253
  "cell_type": "code",
1254
+ "source": "from huggingface_hub import HfApi\n\n# Tokenizer was already saved in the training cell; ensure it's there\ntokenizer.save_pretrained(OUTPUT_DIR)\n\n# Push adapter + tokenizer to HF Hub\napi = HfApi(token=HF_TOKEN)\napi.upload_folder(\n folder_path=OUTPUT_DIR,\n repo_id=OUTPUT_REPO,\n commit_message=f\"StockEx CH Trader: QLoRA fine-tuned {BASE_MODEL} (adapter)\",\n)\nprint(f\"Pushed to https://huggingface.co/{OUTPUT_REPO}\")",
1255
  "metadata": {
1256
  "id": "save-model",
1257
  "trusted": true
 
1259
  "outputs": [],
1260
  "execution_count": null
1261
  },
 
 
 
 
 
 
 
 
 
 
1262
  {
1263
  "cell_type": "markdown",
1264
  "source": "## 6. Inference Test\n\nVerify the model generates valid JSON trading decisions.",
 
1268
  },
1269
  {
1270
  "cell_type": "code",
1271
+ "source": "import re\nfrom transformers import pipeline\nfrom peft import PeftModel\n\n# Load adapter for inference test\nprint(\"Loading base model + adapter for inference test...\")\ndel trainer\ntorch.cuda.empty_cache()\n\nbase_model = AutoModelForCausalLM.from_pretrained(\n BASE_MODEL,\n quantization_config=BitsAndBytesConfig(\n load_in_4bit=True,\n bnb_4bit_quant_type=\"nf4\",\n bnb_4bit_compute_dtype=torch.bfloat16,\n bnb_4bit_use_double_quant=True,\n ),\n device_map=\"auto\",\n trust_remote_code=True,\n)\ninference_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR)\ninference_model.eval()\n\npipe = pipeline(\n \"text-generation\",\n model=inference_model,\n tokenizer=tokenizer,\n device_map=\"auto\",\n)\n\ntest_cases = [\n {\n \"desc\": \"New member, no holdings, must trade\",\n \"capital\": 100_000.0,\n \"holdings\": [],\n \"obligation\": 10,\n },\n {\n \"desc\": \"Experienced member with holdings, low obligation\",\n \"capital\": 65_000.0,\n \"holdings\": [\n {\"symbol\": \"ALPHA\", \"quantity\": 300, \"avg_cost\": 5.60},\n {\"symbol\": \"QUEST\", \"quantity\": 150, \"avg_cost\": 13.20},\n ],\n \"obligation\": 2,\n },\n {\n \"desc\": \"Low capital, large holdings\",\n \"capital\": 8_000.0,\n \"holdings\": [\n {\"symbol\": \"PEIR\", \"quantity\": 500, \"avg_cost\": 8.30},\n {\"symbol\": \"NBG\", \"quantity\": 200, \"avg_cost\": 7.95},\n ],\n \"obligation\": 5,\n },\n]\n\ntest_bbos = {s[\"symbol\"]: gen_bbo(s[\"base\"]) for s in SECURITIES}\n\nprint(\"=\" * 70)\nfor tc in test_cases:\n print(f\"\\nSCENARIO: {tc['desc']}\")\n prompt = build_prompt(\"USR01\", tc[\"capital\"], tc[\"holdings\"], tc[\"obligation\"], test_bbos)\n messages = [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": prompt},\n ]\n output = pipe(\n messages,\n max_new_tokens=60,\n temperature=0.3,\n do_sample=True,\n pad_token_id=tokenizer.eos_token_id,\n )\n response = output[0][\"generated_text\"][-1][\"content\"].strip()\n print(f\"RESPONSE: {response}\")\n try:\n m = re.search(r\"\\{[^}]+\\}\", response)\n if m:\n d = json.loads(m.group())\n assert d[\"side\"] in (\"BUY\", \"SELL\")\n assert d[\"symbol\"] in [s[\"symbol\"] for s in SECURITIES]\n assert d[\"quantity\"] > 0\n assert d[\"price\"] > 0\n print(f\"Valid JSON: {d}\")\n else:\n print(\"No JSON found in response\")\n except Exception as e:\n print(f\"Invalid: {e}\")\n print(\"-\" * 70)",
1272
  "metadata": {
1273
  "id": "inference-test",
1274
  "trusted": true