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\
|
| 1248 |
"metadata": {
|
| 1249 |
"id": "save-header"
|
| 1250 |
}
|
| 1251 |
},
|
| 1252 |
{
|
| 1253 |
"cell_type": "code",
|
| 1254 |
-
"source": "from
|
| 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=
|
| 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\
|
| 1248 |
"metadata": {
|
| 1249 |
"id": "save-header"
|
| 1250 |
}
|
| 1251 |
},
|
| 1252 |
{
|
| 1253 |
"cell_type": "code",
|
| 1254 |
-
"source": "from
|
| 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=
|
| 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
|