Skip to content

Commit 67b0655

Browse files
committed
Fix DistilBERT config compatibility in sentiment benchmark
Pin tensor_rt digest for PyTorch sentiment Dataflow benchmarks
1 parent dfe97e6 commit 67b0655

3 files changed

Lines changed: 68 additions & 19 deletions

File tree

.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Sentiment_Batch_DistilBert_Base_Uncased.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
--machine_type=n1-standard-2
1919
--num_workers=20
2020
--max_num_workers=250
21+
--timeout_ms=600000
2122
--disk_size_gb=50
2223
--autoscaling_algorithm=THROUGHPUT_BASED
2324
--staging_location=gs://temp-storage-for-perf-tests/loadtests
@@ -31,5 +32,7 @@
3132
--device=CPU
3233
--input_file=gs://apache-beam-ml/testing/inputs/sentences_50k.txt
3334
--runner=DataflowRunner
35+
--sdk_location=container
36+
--sdk_container_image=us.gcr.io/apache-beam-testing/python-postcommit-it/tensor_rt@sha256:884d67e96d9a3c22fb21fcd412c10a012d4c82a7c723f1c1ffe41fca609b5a6a
3437
--model_path=distilbert-base-uncased-finetuned-sst-2-english
3538
--model_state_dict_path=gs://apache-beam-ml/models/huggingface.sentiment.distilbert-base-uncased.pth

.github/workflows/load-tests-pipeline-options/beam_Inference_Python_Benchmarks_Dataflow_Pytorch_Sentiment_Streaming_DistilBert_Base_Uncased.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
--machine_type=n1-standard-2
1919
--num_workers=20
2020
--max_num_workers=250
21+
--timeout_ms=600000
2122
--disk_size_gb=50
2223
--autoscaling_algorithm=THROUGHPUT_BASED
2324
--staging_location=gs://temp-storage-for-perf-tests/loadtests
@@ -31,6 +32,8 @@
3132
--device=CPU
3233
--input_file=gs://apache-beam-ml/testing/inputs/sentences_50k.txt
3334
--runner=DataflowRunner
35+
--sdk_location=container
36+
--sdk_container_image=us.gcr.io/apache-beam-testing/python-postcommit-it/tensor_rt@sha256:884d67e96d9a3c22fb21fcd412c10a012d4c82a7c723f1c1ffe41fca609b5a6a
3437
--dataflow_service_options=worker_accelerator=type:nvidia-tesla-t4;count:1;install-nvidia-driver
3538
--model_path=distilbert-base-uncased-finetuned-sst-2-english
3639
--model_state_dict_path=gs://apache-beam-ml/models/huggingface.sentiment.distilbert-base-uncased.pth

sdks/python/apache_beam/examples/inference/pytorch_sentiment.py

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@
4747

4848
class SentimentPostProcessor(beam.DoFn):
4949
"""Processes PredictionResult to extract sentiment label and confidence."""
50-
def __init__(self, tokenizer: DistilBertTokenizerFast):
51-
self.tokenizer = tokenizer
52-
5350
def process(self, element: tuple[str, PredictionResult]) -> Iterable[dict]:
5451
text, prediction_result = element
5552
logits = prediction_result.inference['logits']
@@ -62,16 +59,35 @@ def process(self, element: tuple[str, PredictionResult]) -> Iterable[dict]:
6259
}
6360

6461

65-
def tokenize_text(text: str,
66-
tokenizer: DistilBertTokenizerFast) -> tuple[str, dict]:
67-
"""Tokenizes input text using the specified tokenizer."""
68-
tokenized = tokenizer(
69-
text,
70-
padding='max_length',
71-
truncation=True,
72-
max_length=128,
73-
return_tensors="pt")
74-
return text, {k: torch.squeeze(v) for k, v in tokenized.items()}
62+
class TokenizeTextDoFn(beam.DoFn):
63+
"""Initializes tokenizer per worker and tokenizes input text."""
64+
def __init__(self, model_path: str):
65+
self.model_path = model_path
66+
self.tokenizer = None
67+
68+
def setup(self):
69+
self.tokenizer = DistilBertTokenizerFast.from_pretrained(self.model_path)
70+
# Some transformers builds expose pad token through legacy attributes.
71+
if not hasattr(self.tokenizer, '_pad_token'):
72+
self.tokenizer._pad_token = '[PAD]'
73+
74+
def process(self, text: str) -> Iterable[tuple[str, dict]]:
75+
tokenized = self.tokenizer(
76+
text,
77+
padding='max_length',
78+
truncation=True,
79+
max_length=128,
80+
return_tensors="pt")
81+
yield text, {k: torch.squeeze(v) for k, v in tokenized.items()}
82+
83+
84+
class DistilBertForSequenceClassificationCompat(
85+
DistilBertForSequenceClassification):
86+
"""Builds config in worker runtime to avoid cross-env config drift."""
87+
def __init__(self, model_name: str, num_labels: int = 2):
88+
config = _ensure_transformers_config_compat(
89+
DistilBertConfig.from_pretrained(model_name, num_labels=num_labels))
90+
super().__init__(config)
7591

7692

7793
class RateLimitDoFn(beam.DoFn):
@@ -83,6 +99,31 @@ def process(self, element):
8399
yield element
84100

85101

102+
def _ensure_transformers_config_compat(
103+
config: DistilBertConfig) -> DistilBertConfig:
104+
"""Adds missing config attributes for cross-version transformers compatibility.
105+
106+
The benchmark can run with container images whose transformers version differs
107+
from the launcher environment. Some versions assume these attributes exist.
108+
"""
109+
# Use a default config instance as the source of canonical attributes for the
110+
# transformers version available on the worker. This avoids chasing one
111+
# missing field at a time (e.g. torchscript, output_attentions).
112+
default_config = DistilBertConfig()
113+
for key, value in default_config.to_dict().items():
114+
if not hasattr(config, key):
115+
setattr(config, key, value)
116+
117+
# Keep non-serialized fields explicitly for older/newer transformers mixes.
118+
if not hasattr(config, 'pruned_heads'):
119+
config.pruned_heads = {}
120+
if not hasattr(config, 'torchscript'):
121+
config.torchscript = False
122+
if not hasattr(config, 'return_dict'):
123+
config.return_dict = True
124+
return config
125+
126+
86127
def parse_known_args(argv):
87128
"""Parses command-line arguments for pipeline execution."""
88129
parser = argparse.ArgumentParser()
@@ -235,13 +276,14 @@ def run(
235276
pipeline_options.view_as(StandardOptions).streaming = True
236277

237278
model_handler = PytorchModelHandlerKeyedTensor(
238-
model_class=DistilBertForSequenceClassification,
239-
model_params={'config': DistilBertConfig(num_labels=2)},
279+
model_class=DistilBertForSequenceClassificationCompat,
280+
model_params={
281+
'model_name': known_args.model_path,
282+
'num_labels': 2,
283+
},
240284
state_dict_path=known_args.model_state_dict_path,
241285
device='GPU')
242286

243-
tokenizer = DistilBertTokenizerFast.from_pretrained(known_args.model_path)
244-
245287
pipeline = test_pipeline or beam.Pipeline(options=pipeline_options)
246288

247289
# Main pipeline: read, process, write result to BigQuery output table
@@ -264,9 +306,9 @@ def run(
264306

265307
_ = (
266308
input
267-
| 'Tokenize' >> beam.Map(lambda text: tokenize_text(text, tokenizer))
309+
| 'Tokenize' >> beam.ParDo(TokenizeTextDoFn(known_args.model_path))
268310
| 'RunInference' >> RunInference(KeyedModelHandler(model_handler))
269-
| 'PostProcess' >> beam.ParDo(SentimentPostProcessor(tokenizer))
311+
| 'PostProcess' >> beam.ParDo(SentimentPostProcessor())
270312
| 'WriteToBigQuery' >> beam.io.WriteToBigQuery(
271313
known_args.output_table,
272314
schema='text:STRING, sentiment:STRING, confidence:FLOAT',
@@ -277,6 +319,7 @@ def run(
277319
result = pipeline.run()
278320
result.wait_until_finish(duration=1800000) # 30 min
279321
result.cancel()
322+
result.wait_until_finish(duration=600000) # up to 10 min to settle cancel
280323

281324
cleanup_pubsub_resources(
282325
project=known_args.project,

0 commit comments

Comments
 (0)