Skip to content

Commit e87c321

Browse files
committed
update requirements, format notebook, code and update model registration logic
1 parent 11b630b commit e87c321

File tree

9 files changed

+1007
-875
lines changed

9 files changed

+1007
-875
lines changed

sdk/python/foundation-models/system/reinforcement-learning/reinforcement-learning.ipynb

Lines changed: 759 additions & 747 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
azure-ai-ml
2-
azure-identity
3-
huggingface_hub
4-
matplotlib
1+
# use python3.12 or above
2+
azure-ai-ml==1.30.0
3+
azure-identity==1.25.1
4+
azureml-mlflow==1.60.0.post1
5+
huggingface-hub==1.1.5
6+
matplotlib==3.10.7
7+
mlflow==2.22.2
8+
ipykernel

sdk/python/foundation-models/system/reinforcement-learning/scripts/dataset.py

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ def download_finqa_dataset(src: str, target_dir: str = "data/raw"):
2929
with TemporaryDirectory() as tmpdir:
3030
print(f"Cloning raw FinQA dataset to {tmpdir} ...")
3131
subprocess.run(["git", "clone", src, tmpdir], check=True)
32+
os.makedirs(target_dir, exist_ok=True)
3233
print("Converting FinQA dataset to jsonl format ...")
3334
dataset_dir = os.path.join(tmpdir, "dataset")
34-
for file_name in os.listdir(dataset_dir):
35-
target_file_name = file_name.split(".")[0] + ".jsonl"
36-
os.makedirs(target_dir, exist_ok=True)
37-
convert_to_jsonl(current_path=os.path.join(dataset_dir, file_name), target_path=os.path.join(target_dir, target_file_name))
35+
filenames = ["train.json", "dev.json", "test.json"]
36+
for filename in filenames:
37+
target_file_name = filename.split(".")[0] + ".jsonl"
38+
convert_to_jsonl(
39+
current_path=os.path.join(dataset_dir, filename),
40+
target_path=os.path.join(target_dir, target_file_name),
41+
)
3842

3943

4044
def convert_to_jsonl(current_path: str, target_path: str):
@@ -46,8 +50,10 @@ def convert_to_jsonl(current_path: str, target_path: str):
4650
print(f"Converted {current_path} to {target_path}.")
4751

4852

49-
def prepare_finqa_dataset(ml_client: MLClient, data_dir: str = "data", register_datasets: bool = False) -> tuple[str, str, str]:
50-
"""Prepare the FinQA dataset for training and evaluation."""
53+
def prepare_finqa_dataset(
54+
ml_client: MLClient, data_dir: str = "data", register_datasets: bool = False
55+
) -> tuple[str, str, str]:
56+
"""Prepare the FinQA dataset for training and evaluation."""
5157
# VERL finetuning relies on acceptable data sources for reward modeling and evaluation
5258
data_source = "openai/gsm8k"
5359

@@ -68,30 +74,42 @@ def format_list_to_string(data_list: list):
6874
return "\n".join(str(item) for item in data_list)
6975

7076
def format_table(table_list: list):
71-
"""Format table data as string"""
72-
if not table_list:
73-
return ""
74-
table_str = "\nTable:\n"
75-
for row in table_list:
76-
if isinstance(row, list):
77-
table_str += " | ".join(str(cell) for cell in row) + "\n"
78-
else:
79-
table_str += str(row) + "\n"
80-
return table_str
77+
"""Format table data as string"""
78+
if not table_list:
79+
return ""
80+
table_str = "\nTable:\n"
81+
for row in table_list:
82+
if isinstance(row, list):
83+
table_str += " | ".join(str(cell) for cell in row) + "\n"
84+
else:
85+
table_str += str(row) + "\n"
86+
return table_str
8187

8288
def map_fn(example: pd.Series, idx: int, split: str):
8389
"""Map function to transform each example into desired format."""
8490
pre_instruction = "Please answer the following financial question based on the context provided."
85-
post_instruction = 'Let\'s think step by step and output the final answer after "####".'
91+
post_instruction = (
92+
'Let\'s think step by step and output the final answer after "####".'
93+
)
8694
qa = example.get("qa", {})
8795
question = qa.get("question", "")
88-
answer = qa.get('answer', qa.get('exe_ans', ''))
89-
gold_evidence = "\n".join(qa.get('gold_inds', {}).values())
96+
answer = qa.get("answer", qa.get("exe_ans", ""))
97+
gold_evidence = "\n".join(qa.get("gold_inds", {}).values())
9098
pre_text = format_list_to_string(example.get("pre_text", []))
9199
post_text = format_list_to_string(example.get("post_text", []))
92-
table = format_table(example.get('table', [])).strip()
100+
table = format_table(example.get("table", [])).strip()
93101
# Build prompt content according to specified schema
94-
prompt_content = "\n\n".join([pre_instruction, "Context: " + pre_text, gold_evidence, post_text, table, "Question: " + question, post_instruction])
102+
prompt_content = "\n\n".join(
103+
[
104+
pre_instruction,
105+
"Context: " + pre_text,
106+
gold_evidence,
107+
post_text,
108+
table,
109+
"Question: " + question,
110+
post_instruction,
111+
]
112+
)
95113
data = {
96114
"data_source": data_source,
97115
"prompt": [
@@ -117,9 +135,13 @@ def map_fn(example: pd.Series, idx: int, split: str):
117135
valid_dataset = pd.read_json(valid_dataset_path, lines=True)
118136

119137
# map datasets
120-
train_dataset = train_dataset.apply(lambda x: map_fn(x, x.name, split="train"), axis=1)
138+
train_dataset = train_dataset.apply(
139+
lambda x: map_fn(x, x.name, split="train"), axis=1
140+
)
121141
test_dataset = test_dataset.apply(lambda x: map_fn(x, x.name, split="test"), axis=1)
122-
valid_dataset = valid_dataset.apply(lambda x: map_fn(x, x.name, split="valid"), axis=1)
142+
valid_dataset = valid_dataset.apply(
143+
lambda x: map_fn(x, x.name, split="valid"), axis=1
144+
)
123145

124146
# save locally as jsonl
125147
train_dataset_path = os.path.join(data_dir, "train.jsonl")
@@ -134,7 +156,11 @@ def map_fn(example: pd.Series, idx: int, split: str):
134156
train_data = register_dataset(ml_client, "finqa_train", train_dataset_path)
135157
test_data = register_dataset(ml_client, "finqa_test", test_dataset_path)
136158
valid_data = register_dataset(ml_client, "finqa_valid", valid_dataset_path)
137-
if (train_data and train_data.id) and (test_data and test_data.id) and (valid_data and valid_data.id):
159+
if (
160+
(train_data and train_data.id)
161+
and (test_data and test_data.id)
162+
and (valid_data and valid_data.id)
163+
):
138164
return train_data.id, test_data.id, valid_data.id
139-
165+
140166
return train_dataset_path, test_dataset_path, valid_dataset_path

sdk/python/foundation-models/system/reinforcement-learning/scripts/deployment.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,51 +15,53 @@
1515

1616
def get_default_probe_settings() -> ProbeSettings:
1717
"""Get default probe settings for deployments."""
18-
return ProbeSettings( # Probes are APIs exposed by the deployment which informs the frameworktraffic
19-
initial_delay=1400, # if the deployment is healthy and ready to receive
18+
return ProbeSettings( # Probes are APIs exposed by the deployment which informs the frameworktraffic
19+
initial_delay=1400, # if the deployment is healthy and ready to receive
2020
period=30,
2121
timeout=2,
2222
success_threshold=1,
23-
failure_threshold=30
23+
failure_threshold=30,
2424
)
2525

2626

2727
def get_default_request_settings() -> OnlineRequestSettings:
2828
"""Get default request settings for deployments."""
29-
return OnlineRequestSettings( # Online request setting which controls timeout and concurrent request per instance
29+
return OnlineRequestSettings( # Online request setting which controls timeout and concurrent request per instance
3030
request_timeout_ms=90000,
3131
max_concurrent_requests_per_instance=4,
3232
)
3333

3434

3535
def create_managed_deployment(
3636
ml_client: MLClient,
37-
model_asset_id: str, # Asset ID of the model to deploy
38-
instance_type: str, # Supported instance type for managed deployment
39-
environment_asset_id: Optional[str] = None, # Asset ID of the serving engine to use
37+
model_asset_id: str, # Asset ID of the model to deploy
38+
instance_type: str, # Supported instance type for managed deployment
39+
environment_asset_id: Optional[str] = None, # Asset ID of the serving engine to use
4040
endpoint_name: Optional[str] = None,
4141
endpoint_description: str = "Sample endpoint",
4242
endpoint_tags: dict = {},
4343
deployment_name: Optional[str] = None,
4444
deployment_env_vars: dict = {},
4545
) -> str:
4646
"""Create a managed deployment."""
47-
guid = str(uuid.uuid4())[:8] # Unique suffix to avoid name collisions
47+
guid = str(uuid.uuid4())[:8] # Unique suffix to avoid name collisions
4848
endpoint_name = endpoint_name or f"rl-endpoint"
49-
endpoint_name = f"{endpoint_name}-{guid}" # Unique names prevent collisions and allow parallel experiments
49+
endpoint_name = f"{endpoint_name}-{guid}" # Unique names prevent collisions and allow parallel experiments
5050
deployment_name = deployment_name or "default"
5151

52-
endpoint = ManagedOnlineEndpoint( # Use AzureML endpoint abstraction for traffic management and auth
52+
endpoint = ManagedOnlineEndpoint( # Use AzureML endpoint abstraction for traffic management and auth
5353
name=endpoint_name,
5454
auth_mode="key",
5555
description=endpoint_description,
5656
tags=endpoint_tags,
5757
)
5858

5959
print(f"Creating endpoint: {endpoint_name}")
60-
ml_client.online_endpoints.begin_create_or_update(endpoint).wait() # Using there the endpoint object to trigger actual endpoint in AML workspace.
60+
ml_client.online_endpoints.begin_create_or_update(
61+
endpoint
62+
).wait() # Using there the endpoint object to trigger actual endpoint in AML workspace.
6163

62-
deployment = ManagedOnlineDeployment( # Use deployment abstraction for scaling, versioning, and isolation
64+
deployment = ManagedOnlineDeployment( # Use deployment abstraction for scaling, versioning, and isolation
6365
name=deployment_name,
6466
endpoint_name=endpoint_name,
6567
model=model_asset_id,
@@ -72,8 +74,8 @@ def create_managed_deployment(
7274
request_settings=get_default_request_settings(),
7375
)
7476

75-
print(f"Creating deployment (15-20 min)...") #
76-
ml_client.online_deployments.begin_create_or_update(deployment).wait()
77+
print(f"Creating deployment (15-20 min)...") #
78+
ml_client.online_deployments.begin_create_or_update(deployment).wait()
7779

7880
# Route all traffic to new deployment for immediate use
7981
endpoint.traffic = {deployment_name: 100}
@@ -86,10 +88,10 @@ def create_managed_deployment(
8688

8789
def create_kubernetes_deployment(
8890
ml_client: MLClient,
89-
model_asset_id: str, # Asset ID of the model to deploy
90-
environment_asset_id: str, # Asset ID of the serving engine to use
91-
instance_type: str, # Kubernetes supports partial node usage granular upto the GPU level
92-
compute_name: str, # Name of the compute which will be use for endpoint creation
91+
model_asset_id: str, # Asset ID of the model to deploy
92+
environment_asset_id: str, # Asset ID of the serving engine to use
93+
instance_type: str, # Kubernetes supports partial node usage granular upto the GPU level
94+
compute_name: str, # Name of the compute which will be use for endpoint creation
9395
endpoint_name: Optional[str] = None,
9496
endpoint_description: str = "Sample endpoint",
9597
endpoint_tags: dict = {},
@@ -98,15 +100,15 @@ def create_kubernetes_deployment(
98100
model_mount_path: str = "/var/model-mount",
99101
) -> str:
100102
"""Create endpoint using Kubernetes."""
101-
103+
102104
print("🌐 Creating endpoint...")
103105

104-
guid = str(uuid.uuid4())[:8] # Unique suffix to avoid name collisions
106+
guid = str(uuid.uuid4())[:8] # Unique suffix to avoid name collisions
105107
endpoint_name = endpoint_name or f"rl-endpoint"
106-
endpoint_name = f"{endpoint_name}-{guid}" # Unique names prevent collisions and allow parallel experiments
108+
endpoint_name = f"{endpoint_name}-{guid}" # Unique names prevent collisions and allow parallel experiments
107109
deployment_name = deployment_name or "default"
108110

109-
endpoint = KubernetesOnlineEndpoint( # Use AzureML endpoint abstraction for traffic management and auth
111+
endpoint = KubernetesOnlineEndpoint( # Use AzureML endpoint abstraction for traffic management and auth
110112
name=endpoint_name,
111113
auth_mode="key",
112114
compute=compute_name,
@@ -115,9 +117,11 @@ def create_kubernetes_deployment(
115117
)
116118

117119
print(f"Creating endpoint: {endpoint_name}")
118-
ml_client.online_endpoints.begin_create_or_update(endpoint).wait() # Using there the endpoint object to trigger actual endpoint in AML workspace.
120+
ml_client.online_endpoints.begin_create_or_update(
121+
endpoint
122+
).wait() # Using there the endpoint object to trigger actual endpoint in AML workspace.
119123

120-
deployment = KubernetesOnlineDeployment( # Use deployment abstraction for scaling, versioning, and isolation
124+
deployment = KubernetesOnlineDeployment( # Use deployment abstraction for scaling, versioning, and isolation
121125
name=deployment_name,
122126
endpoint_name=endpoint_name,
123127
model=model_asset_id,
@@ -131,8 +135,8 @@ def create_kubernetes_deployment(
131135
request_settings=get_default_request_settings(),
132136
)
133137

134-
print(f"Creating deployment (15-20 min)...") #
135-
ml_client.online_deployments.begin_create_or_update(deployment).wait()
138+
print(f"Creating deployment (15-20 min)...") #
139+
ml_client.online_deployments.begin_create_or_update(deployment).wait()
136140

137141
# Route all traffic to new deployment for immediate use
138142
endpoint.traffic = {deployment_name: 100}
@@ -165,7 +169,7 @@ def test_deployment(ml_client, endpoint_name):
165169
Context: A company has revenue of $1,000,000 and expenses of $750,000.
166170
167171
Question: What is the profit margin as a percentage?
168-
Let's think step by step and put final answer after ####."""
172+
Let's think step by step and put final answer after ####.""",
169173
}
170174
],
171175
"max_tokens": 512,

sdk/python/foundation-models/system/reinforcement-learning/scripts/evaluation.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from azure.ai.ml.entities import Job
66
from scripts.run import monitor_run
77

8-
class EvaluationPipeline():
8+
9+
class EvaluationPipeline:
910
"""Run Evaluation"""
1011

1112
DEFAULT_CONFIGS = {
@@ -23,8 +24,7 @@ def __init__(self, ml_client: MLClient, registry_ml_client: MLClient):
2324
self.guid = str(uuid.uuid4())[:8]
2425
self._ml_client = ml_client
2526
self._eval_pipeline_component = registry_ml_client.components.get(
26-
name="pipeline_model_evaluation",
27-
label="latest"
27+
name="pipeline_model_evaluation", label="latest"
2828
)
2929

3030
def create_evaluate_pipeline(
@@ -35,7 +35,7 @@ def create_evaluate_pipeline(
3535
validation_dataset_path: Input,
3636
base_model_path: Optional[Input] = None,
3737
instance_type: Optional[str] = None,
38-
config = {},
38+
config={},
3939
) -> Job:
4040
"""Create and submit evaluation pipeline job using registry component."""
4141

@@ -52,7 +52,7 @@ def create_pipeline():
5252
checkpoint_base_path_1=model_dir_1,
5353
checkpoint_base_path_2=model_dir_2,
5454
validation_file=validation_dataset_path,
55-
**self.DEFAULT_CONFIGS
55+
**self.DEFAULT_CONFIGS,
5656
)
5757
return {"evaluation_results": eval_pipeline.outputs.evaluation_results}
5858

@@ -68,7 +68,9 @@ def create_pipeline():
6868
# Submit job
6969
print("✓ Submitting Model Evaluation Pipeline ...")
7070
pipeline_object.display_name = f"evaluate-model-{self.guid}"
71-
eval_run = self._ml_client.jobs.create_or_update(pipeline_object, experiment_name="evaluate-model")
71+
eval_run = self._ml_client.jobs.create_or_update(
72+
pipeline_object, experiment_name="evaluate-model"
73+
)
7274

7375
print(f"✓ Job submitted: {eval_run.name}")
7476
print(f"📊 Studio URL: {eval_run.studio_url}")
@@ -93,8 +95,14 @@ def run_evaluation_pipeline(
9395

9496
grpo_model_input = Input(type=AssetTypes.URI_FOLDER, path=grpo_model_dir)
9597
rlpp_model_input = Input(type=AssetTypes.URI_FOLDER, path=rlpp_model_dir)
96-
base_model_input = Input(type=AssetTypes.URI_FOLDER, path=base_model_path) if isinstance(base_model_path, str) else base_model_path
97-
validation_dataset_input = Input(type=AssetTypes.URI_FILE, path=validation_dataset_path)
98+
base_model_input = (
99+
Input(type=AssetTypes.URI_FOLDER, path=base_model_path)
100+
if isinstance(base_model_path, str)
101+
else base_model_path
102+
)
103+
validation_dataset_input = Input(
104+
type=AssetTypes.URI_FILE, path=validation_dataset_path
105+
)
98106

99107
eval_job = pipeline.create_evaluate_pipeline(
100108
compute=compute_cluster,

0 commit comments

Comments
 (0)